Merge "Add ids to modes views" into main
diff --git a/Android.bp b/Android.bp
index 7f4871f..af205d8 100644
--- a/Android.bp
+++ b/Android.bp
@@ -261,7 +261,6 @@
         "devicepolicyprotosnano",
         "ImmutabilityAnnotation",
 
-        "com.android.sysprop.init",
         "com.android.sysprop.localization",
         "PlatformProperties",
     ],
diff --git a/apct-tests/perftests/core/src/android/text/VariableFontPerfTest.java b/apct-tests/perftests/core/src/android/text/VariableFontPerfTest.java
index fbe67a4..c34936f 100644
--- a/apct-tests/perftests/core/src/android/text/VariableFontPerfTest.java
+++ b/apct-tests/perftests/core/src/android/text/VariableFontPerfTest.java
@@ -19,6 +19,7 @@
 import android.graphics.Paint;
 import android.graphics.RecordingCanvas;
 import android.graphics.RenderNode;
+import android.graphics.Typeface;
 import android.perftests.utils.BenchmarkState;
 import android.perftests.utils.PerfStatusReporter;
 
@@ -120,13 +121,34 @@
     public void testSetFontVariationSettings() {
         final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
         final Paint paint = new Paint(PAINT);
-        final Random random = new Random(0);
         while (state.keepRunning()) {
             state.pauseTiming();
-            int weight = random.nextInt(1000);
+            paint.setTypeface(null);
+            paint.setFontVariationSettings(null);
+            Typeface.clearTypefaceCachesForTestingPurpose();
             state.resumeTiming();
 
-            paint.setFontVariationSettings("'wght' " + weight);
+            paint.setFontVariationSettings("'wght' 450");
         }
+        Typeface.clearTypefaceCachesForTestingPurpose();
     }
+
+    @Test
+    public void testSetFontVariationSettings_Cached() {
+        final BenchmarkState state = mPerfStatusReporter.getBenchmarkState();
+        final Paint paint = new Paint(PAINT);
+        Typeface.clearTypefaceCachesForTestingPurpose();
+
+        while (state.keepRunning()) {
+            state.pauseTiming();
+            paint.setTypeface(null);
+            paint.setFontVariationSettings(null);
+            state.resumeTiming();
+
+            paint.setFontVariationSettings("'wght' 450");
+        }
+
+        Typeface.clearTypefaceCachesForTestingPurpose();
+    }
+
 }
diff --git a/apex/jobscheduler/service/aconfig/job.aconfig b/apex/jobscheduler/service/aconfig/job.aconfig
index e856865..613b784 100644
--- a/apex/jobscheduler/service/aconfig/job.aconfig
+++ b/apex/jobscheduler/service/aconfig/job.aconfig
@@ -58,3 +58,10 @@
        purpose: PURPOSE_BUGFIX
    }
 }
+
+flag {
+   name: "remove_user_during_user_switch"
+   namespace: "backstage_power"
+   description: "Remove started user if user will be stopped due to user switch"
+   bug: "321598070"
+}
diff --git a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
index 5f2b01a..3d25ed5 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/JobSchedulerService.java
@@ -1669,6 +1669,20 @@
     }
 
     @Override
+    public void onUserSwitching(@Nullable TargetUser from, @NonNull TargetUser to) {
+        if (!Flags.removeUserDuringUserSwitch()
+                || from == null
+                || !mActivityManagerInternal.isEarlyPackageKillEnabledForUserSwitch(
+                                                                from.getUserIdentifier(),
+                                                                to.getUserIdentifier())) {
+            return;
+        }
+        synchronized (mLock) {
+            mStartedUsers = ArrayUtils.removeInt(mStartedUsers, from.getUserIdentifier());
+        }
+    }
+
+    @Override
     public void onUserStopping(@NonNull TargetUser user) {
         synchronized (mLock) {
             mStartedUsers = ArrayUtils.removeInt(mStartedUsers, user.getUserIdentifier());
diff --git a/cmds/screencap/screencap.cpp b/cmds/screencap/screencap.cpp
index 7e4f95b..01b20f4 100644
--- a/cmds/screencap/screencap.cpp
+++ b/cmds/screencap/screencap.cpp
@@ -14,6 +14,9 @@
  * limitations under the License.
  */
 
+#include <android/bitmap.h>
+#include <android/gui/DisplayCaptureArgs.h>
+#include <binder/ProcessState.h>
 #include <errno.h>
 #include <unistd.h>
 #include <stdio.h>
@@ -33,7 +36,6 @@
 
 #include <ftl/concat.h>
 #include <ftl/optional.h>
-#include <gui/DisplayCaptureArgs.h>
 #include <gui/ISurfaceComposer.h>
 #include <gui/SurfaceComposerClient.h>
 #include <gui/SyncScreenCaptureListener.h>
diff --git a/core/api/current.txt b/core/api/current.txt
index 7f2b004..66a1d30 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -26,6 +26,7 @@
     field public static final String BATTERY_STATS = "android.permission.BATTERY_STATS";
     field public static final String BIND_ACCESSIBILITY_SERVICE = "android.permission.BIND_ACCESSIBILITY_SERVICE";
     field public static final String BIND_APPWIDGET = "android.permission.BIND_APPWIDGET";
+    field @FlaggedApi("android.app.appfunctions.flags.enable_app_function_manager") public static final String BIND_APP_FUNCTION_SERVICE = "android.permission.BIND_APP_FUNCTION_SERVICE";
     field public static final String BIND_AUTOFILL_SERVICE = "android.permission.BIND_AUTOFILL_SERVICE";
     field public static final String BIND_CALL_REDIRECTION_SERVICE = "android.permission.BIND_CALL_REDIRECTION_SERVICE";
     field public static final String BIND_CARRIER_MESSAGING_CLIENT_SERVICE = "android.permission.BIND_CARRIER_MESSAGING_CLIENT_SERVICE";
@@ -8718,6 +8719,55 @@
 
 }
 
+package android.app.appfunctions {
+
+  @FlaggedApi("android.app.appfunctions.flags.enable_app_function_manager") public final class AppFunctionManager {
+  }
+
+  @FlaggedApi("android.app.appfunctions.flags.enable_app_function_manager") public final class ExecuteAppFunctionRequest implements android.os.Parcelable {
+    method public int describeContents();
+    method @NonNull public android.os.Bundle getExtras();
+    method @NonNull public String getFunctionIdentifier();
+    method @NonNull public android.app.appsearch.GenericDocument getParameters();
+    method @NonNull public String getTargetPackageName();
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.app.appfunctions.ExecuteAppFunctionRequest> CREATOR;
+  }
+
+  public static final class ExecuteAppFunctionRequest.Builder {
+    ctor public ExecuteAppFunctionRequest.Builder(@NonNull String, @NonNull String);
+    method @NonNull public android.app.appfunctions.ExecuteAppFunctionRequest build();
+    method @NonNull public android.app.appfunctions.ExecuteAppFunctionRequest.Builder setExtras(@NonNull android.os.Bundle);
+    method @NonNull public android.app.appfunctions.ExecuteAppFunctionRequest.Builder setParameters(@NonNull android.app.appsearch.GenericDocument);
+  }
+
+  @FlaggedApi("android.app.appfunctions.flags.enable_app_function_manager") public final class ExecuteAppFunctionResponse implements android.os.Parcelable {
+    method public int describeContents();
+    method @Nullable public String getErrorMessage();
+    method @NonNull public android.os.Bundle getExtras();
+    method public int getResultCode();
+    method @NonNull public android.app.appsearch.GenericDocument getResultDocument();
+    method public boolean isSuccess();
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.app.appfunctions.ExecuteAppFunctionResponse> CREATOR;
+    field public static final String PROPERTY_RETURN_VALUE = "returnValue";
+    field public static final int RESULT_APP_UNKNOWN_ERROR = 2; // 0x2
+    field public static final int RESULT_DENIED = 1; // 0x1
+    field public static final int RESULT_INTERNAL_ERROR = 3; // 0x3
+    field public static final int RESULT_INVALID_ARGUMENT = 4; // 0x4
+    field public static final int RESULT_OK = 0; // 0x0
+    field public static final int RESULT_TIMED_OUT = 5; // 0x5
+  }
+
+  @FlaggedApi("android.app.appfunctions.flags.enable_app_function_manager") public static final class ExecuteAppFunctionResponse.Builder {
+    ctor public ExecuteAppFunctionResponse.Builder(@NonNull android.app.appsearch.GenericDocument);
+    ctor public ExecuteAppFunctionResponse.Builder(int, @NonNull String);
+    method @NonNull public android.app.appfunctions.ExecuteAppFunctionResponse build();
+    method @NonNull public android.app.appfunctions.ExecuteAppFunctionResponse.Builder setExtras(@NonNull android.os.Bundle);
+  }
+
+}
+
 package android.app.assist {
 
   public class AssistContent implements android.os.Parcelable {
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index 445a572..fe792bc 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -139,6 +139,8 @@
     field @FlaggedApi("com.android.window.flags.untrusted_embedding_any_app_permission") public static final String EMBED_ANY_APP_IN_UNTRUSTED_MODE = "android.permission.EMBED_ANY_APP_IN_UNTRUSTED_MODE";
     field @FlaggedApi("android.content.pm.emergency_install_permission") public static final String EMERGENCY_INSTALL_PACKAGES = "android.permission.EMERGENCY_INSTALL_PACKAGES";
     field public static final String ENTER_CAR_MODE_PRIORITIZED = "android.permission.ENTER_CAR_MODE_PRIORITIZED";
+    field @FlaggedApi("android.app.appfunctions.flags.enable_app_function_manager") public static final String EXECUTE_APP_FUNCTIONS = "android.permission.EXECUTE_APP_FUNCTIONS";
+    field @FlaggedApi("android.app.appfunctions.flags.enable_app_function_manager") public static final String EXECUTE_APP_FUNCTIONS_TRUSTED = "android.permission.EXECUTE_APP_FUNCTIONS_TRUSTED";
     field public static final String EXEMPT_FROM_AUDIO_RECORD_RESTRICTIONS = "android.permission.EXEMPT_FROM_AUDIO_RECORD_RESTRICTIONS";
     field public static final String FORCE_BACK = "android.permission.FORCE_BACK";
     field public static final String FORCE_STOP_PACKAGES = "android.permission.FORCE_STOP_PACKAGES";
diff --git a/core/api/test-current.txt b/core/api/test-current.txt
index 8dc9652..177b859 100644
--- a/core/api/test-current.txt
+++ b/core/api/test-current.txt
@@ -941,7 +941,7 @@
     method public int getAudioRecordingSessionId(int);
     method public int getDeviceIdForDisplayId(int);
     method public int getDevicePolicy(int, int);
-    method @FlaggedApi("android.companion.virtual.flags.interactive_screen_mirror") public boolean isVirtualDeviceOwnedMirrorDisplay(int);
+    method public boolean isVirtualDeviceOwnedMirrorDisplay(int);
     method public void playSoundEffect(int, int);
   }
 
@@ -2579,9 +2579,10 @@
 
   @FlaggedApi("android.os.vibrator.vendor_vibration_effects") public static final class VibrationEffect.VendorEffect extends android.os.VibrationEffect {
     method @Nullable public long[] computeCreateWaveformOffOnTimingsOrNull();
+    method public float getAdaptiveScale();
     method public long getDuration();
     method public int getEffectStrength();
-    method public float getLinearScale();
+    method public float getScale();
     method @NonNull public android.os.PersistableBundle getVendorData();
     method public void writeToParcel(@NonNull android.os.Parcel, int);
     field @NonNull public static final android.os.Parcelable.Creator<android.os.VibrationEffect.VendorEffect> CREATOR;
diff --git a/core/java/android/app/ActivityOptions.java b/core/java/android/app/ActivityOptions.java
index 65acd49..91aa225 100644
--- a/core/java/android/app/ActivityOptions.java
+++ b/core/java/android/app/ActivityOptions.java
@@ -1587,6 +1587,16 @@
         }
     }
 
+    /** @hide */
+    public static boolean hasLaunchTargetContainer(ActivityOptions options) {
+        return options.getLaunchDisplayId() != INVALID_DISPLAY
+                || options.getLaunchTaskDisplayArea() != null
+                || options.getLaunchTaskDisplayAreaFeatureId() != FEATURE_UNDEFINED
+                || options.getLaunchRootTask() != null
+                || options.getLaunchTaskId() != -1
+                || options.getLaunchTaskFragmentToken() != null;
+    }
+
     /**
      * Gets whether the activity is to be launched into LockTask mode.
      * @return {@code true} if the activity is to be launched into LockTask mode.
diff --git a/core/java/android/app/AppCompatTaskInfo.java b/core/java/android/app/AppCompatTaskInfo.java
index 81e9df6..8370c2e 100644
--- a/core/java/android/app/AppCompatTaskInfo.java
+++ b/core/java/android/app/AppCompatTaskInfo.java
@@ -95,6 +95,8 @@
     private static final int FLAG_FULLSCREEN_OVERRIDE_SYSTEM = FLAG_BASE << 7;
     /** Top activity flag for whether has activity has been overridden to fullscreen by user. */
     private static final int FLAG_FULLSCREEN_OVERRIDE_USER = FLAG_BASE << 8;
+    /** Top activity flag for whether min aspect ratio of the activity has been overridden.*/
+    public static final int FLAG_HAS_MIN_ASPECT_RATIO_OVERRIDE = FLAG_BASE << 9;
 
     @Retention(RetentionPolicy.SOURCE)
     @IntDef(flag = true, value = {
@@ -108,7 +110,8 @@
             FLAG_IS_FROM_LETTERBOX_DOUBLE_TAP,
             FLAG_ELIGIBLE_FOR_USER_ASPECT_RATIO_BUTTON,
             FLAG_FULLSCREEN_OVERRIDE_SYSTEM,
-            FLAG_FULLSCREEN_OVERRIDE_USER
+            FLAG_FULLSCREEN_OVERRIDE_USER,
+            FLAG_HAS_MIN_ASPECT_RATIO_OVERRIDE
     })
     public @interface TopActivityFlag {}
 
@@ -118,7 +121,7 @@
     @TopActivityFlag
     private static final int FLAGS_ORGANIZER_INTERESTED = FLAG_IS_FROM_LETTERBOX_DOUBLE_TAP
             | FLAG_ELIGIBLE_FOR_USER_ASPECT_RATIO_BUTTON | FLAG_FULLSCREEN_OVERRIDE_SYSTEM
-            | FLAG_FULLSCREEN_OVERRIDE_USER;
+            | FLAG_FULLSCREEN_OVERRIDE_USER | FLAG_HAS_MIN_ASPECT_RATIO_OVERRIDE;
 
     @TopActivityFlag
     private static final int FLAGS_COMPAT_UI_INTERESTED = FLAGS_ORGANIZER_INTERESTED
@@ -301,6 +304,21 @@
         setTopActivityFlag(FLAG_LETTERBOXED, enable);
     }
 
+    /**
+     * @return {@code true} if the top activity's min aspect ratio has been overridden.
+     */
+    public boolean hasMinAspectRatioOverride() {
+        return isTopActivityFlagEnabled(FLAG_HAS_MIN_ASPECT_RATIO_OVERRIDE);
+    }
+
+    /**
+     * Sets the top activity flag for whether the min aspect ratio of the activity has been
+     * overridden.
+     */
+    public void setHasMinAspectRatioOverride(boolean enable) {
+        setTopActivityFlag(FLAG_HAS_MIN_ASPECT_RATIO_OVERRIDE, enable);
+    }
+
     /** Clear all top activity flags and set to false. */
     public void clearTopActivityFlags() {
         mTopActivityFlags = FLAG_UNDEFINED;
@@ -392,6 +410,7 @@
                 + " topActivityLetterboxAppHeight=" + topActivityLetterboxAppHeight
                 + " isUserFullscreenOverrideEnabled=" + isUserFullscreenOverrideEnabled()
                 + " isSystemFullscreenOverrideEnabled=" + isSystemFullscreenOverrideEnabled()
+                + " hasMinAspectRatioOverride=" + hasMinAspectRatioOverride()
                 + " cameraCompatTaskInfo=" + cameraCompatTaskInfo.toString()
                 + "}";
     }
diff --git a/core/java/android/app/ContextImpl.java b/core/java/android/app/ContextImpl.java
index 7e2a580..90fba29 100644
--- a/core/java/android/app/ContextImpl.java
+++ b/core/java/android/app/ContextImpl.java
@@ -1160,7 +1160,7 @@
         }
         mMainThread.getInstrumentation().execStartActivity(
                 getOuterContext(), mMainThread.getApplicationThread(), null,
-                (Activity) null, intent, -1, options);
+                (Activity) null, intent, -1, applyLaunchDisplayIfNeeded(options));
     }
 
     /** @hide */
@@ -1170,8 +1170,8 @@
             ActivityTaskManager.getService().startActivityAsUser(
                     mMainThread.getApplicationThread(), getOpPackageName(), getAttributionTag(),
                     intent, intent.resolveTypeIfNeeded(getContentResolver()),
-                    null, null, 0, Intent.FLAG_ACTIVITY_NEW_TASK, null, options,
-                    user.getIdentifier());
+                    null, null, 0, Intent.FLAG_ACTIVITY_NEW_TASK, null,
+                    applyLaunchDisplayIfNeeded(options), user.getIdentifier());
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
@@ -1194,7 +1194,8 @@
         }
         return mMainThread.getInstrumentation().execStartActivitiesAsUser(
                 getOuterContext(), mMainThread.getApplicationThread(), null,
-                (Activity) null, intents, options, userHandle.getIdentifier());
+                (Activity) null, intents, applyLaunchDisplayIfNeeded(options),
+                userHandle.getIdentifier());
     }
 
     @Override
@@ -1208,7 +1209,26 @@
         }
         mMainThread.getInstrumentation().execStartActivities(
                 getOuterContext(), mMainThread.getApplicationThread(), null,
-                (Activity) null, intents, options);
+                (Activity) null, intents, applyLaunchDisplayIfNeeded(options));
+    }
+
+    private Bundle applyLaunchDisplayIfNeeded(@Nullable Bundle options) {
+        if (!isAssociatedWithDisplay()) {
+            // return if this Context has no associated display.
+            return options;
+        }
+
+        final ActivityOptions activityOptions;
+        if (options != null) {
+            activityOptions = ActivityOptions.fromBundle(options);
+            if (ActivityOptions.hasLaunchTargetContainer(activityOptions)) {
+                // return if the options already has launching target.
+                return options;
+            }
+        } else {
+            activityOptions = ActivityOptions.makeBasic();
+        }
+        return activityOptions.setLaunchDisplayId(getAssociatedDisplayId()).toBundle();
     }
 
     @Override
diff --git a/core/java/android/app/SystemServiceRegistry.java b/core/java/android/app/SystemServiceRegistry.java
index 114a2c4..cb38cf2 100644
--- a/core/java/android/app/SystemServiceRegistry.java
+++ b/core/java/android/app/SystemServiceRegistry.java
@@ -475,7 +475,7 @@
                 if (service == null
                         && ctx.getPackageManager().hasSystemFeature(PackageManager.FEATURE_WATCH)
                         && android.server.Flags.allowRemovingVpnService()) {
-                    throw new ServiceNotFoundException(Context.VPN_MANAGEMENT_SERVICE);
+                    return null;
                 }
                 return new VpnManager(ctx, service);
             }});
diff --git a/core/java/android/app/WallpaperManager.java b/core/java/android/app/WallpaperManager.java
index 5903a7f..38f59ad 100644
--- a/core/java/android/app/WallpaperManager.java
+++ b/core/java/android/app/WallpaperManager.java
@@ -1571,12 +1571,29 @@
      */
     @Nullable
     public Rect peekBitmapDimensions(@SetWallpaperFlags int which, boolean returnDefault) {
+        if (multiCrop()) {
+            return peekBitmapDimensionsAsUser(which, returnDefault, mContext.getUserId());
+        }
         checkExactlyOneWallpaperFlagSet(which);
         return sGlobals.peekWallpaperDimensions(mContext, returnDefault, which,
                 mContext.getUserId());
     }
 
     /**
+     * Overload of {@link #peekBitmapDimensions(int, boolean)} with a userId argument.
+     * TODO(b/360120606): remove the SuppressWarnings
+     * @hide
+     */
+    @SuppressWarnings("AndroidFrameworkContextUserId")
+    @FlaggedApi(FLAG_MULTI_CROP)
+    @Nullable
+    public Rect peekBitmapDimensionsAsUser(@SetWallpaperFlags int which, boolean returnDefault,
+            int userId) {
+        checkExactlyOneWallpaperFlagSet(which);
+        return sGlobals.peekWallpaperDimensions(mContext, returnDefault, which, userId);
+    }
+
+    /**
      * For the current user, given a list of display sizes, return a list of rectangles representing
      * the area of the current wallpaper that would be shown for each of these sizes.
      *
diff --git a/core/java/android/app/appfunctions/AppFunctionManager.java b/core/java/android/app/appfunctions/AppFunctionManager.java
index a01e373..bf21549 100644
--- a/core/java/android/app/appfunctions/AppFunctionManager.java
+++ b/core/java/android/app/appfunctions/AppFunctionManager.java
@@ -16,6 +16,9 @@
 
 package android.app.appfunctions;
 
+import static android.app.appfunctions.flags.Flags.FLAG_ENABLE_APP_FUNCTION_MANAGER;
+
+import android.annotation.FlaggedApi;
 import android.annotation.SystemService;
 import android.content.Context;
 
@@ -25,8 +28,8 @@
  * <p>App function is a specific piece of functionality that an app offers to the system. These
  * functionalities can be integrated into various system features.
  *
- * @hide
  */
+@FlaggedApi(FLAG_ENABLE_APP_FUNCTION_MANAGER)
 @SystemService(Context.APP_FUNCTION_SERVICE)
 public final class AppFunctionManager {
     private final IAppFunctionManager mService;
diff --git a/core/java/android/app/appfunctions/ExecuteAppFunctionAidlRequest.aidl b/core/java/android/app/appfunctions/ExecuteAppFunctionAidlRequest.aidl
new file mode 100644
index 0000000..42ec45d
--- /dev/null
+++ b/core/java/android/app/appfunctions/ExecuteAppFunctionAidlRequest.aidl
@@ -0,0 +1,23 @@
+
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+*/
+
+package android.app.appfunctions;
+
+import android.app.appfunctions.ExecuteAppFunctionAidlRequest;
+
+/** {@hide} */
+parcelable ExecuteAppFunctionAidlRequest;
\ No newline at end of file
diff --git a/core/java/android/app/appfunctions/ExecuteAppFunctionAidlRequest.java b/core/java/android/app/appfunctions/ExecuteAppFunctionAidlRequest.java
new file mode 100644
index 0000000..2f3c555
--- /dev/null
+++ b/core/java/android/app/appfunctions/ExecuteAppFunctionAidlRequest.java
@@ -0,0 +1,113 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.app.appfunctions;
+
+import static android.app.appfunctions.flags.Flags.FLAG_ENABLE_APP_FUNCTION_MANAGER;
+
+import android.annotation.FlaggedApi;
+import android.annotation.NonNull;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.os.UserHandle;
+
+
+import java.util.Objects;
+
+/**
+ * An internal request to execute an app function.
+ *
+ * @hide
+ */
+@FlaggedApi(FLAG_ENABLE_APP_FUNCTION_MANAGER)
+public final class ExecuteAppFunctionAidlRequest implements Parcelable {
+
+    public static final Creator<ExecuteAppFunctionAidlRequest> CREATOR =
+            new Creator<ExecuteAppFunctionAidlRequest>() {
+                @Override
+                public ExecuteAppFunctionAidlRequest createFromParcel(Parcel in) {
+                    ExecuteAppFunctionRequest clientRequest =
+                            ExecuteAppFunctionRequest.CREATOR.createFromParcel(in);
+                    UserHandle userHandle =
+                            UserHandle.CREATOR.createFromParcel(in);
+                    String callingPackage = in.readString8();
+                    return new ExecuteAppFunctionAidlRequest(
+                            clientRequest, userHandle, callingPackage);
+                }
+
+                @Override
+                public ExecuteAppFunctionAidlRequest[] newArray(int size) {
+                    return new ExecuteAppFunctionAidlRequest[size];
+                }
+            };
+
+    /**
+     * The client request to execute an app function.
+     */
+    private final ExecuteAppFunctionRequest mClientRequest;
+
+    /**
+     * The user handle of the user to execute the app function.
+     */
+    private final UserHandle mUserHandle;
+
+    /**
+     * The package name of the app that is requesting to execute the app function.
+     */
+    private final String mCallingPackage;
+
+    public ExecuteAppFunctionAidlRequest(
+            ExecuteAppFunctionRequest clientRequest, UserHandle userHandle, String callingPackage) {
+        this.mClientRequest = Objects.requireNonNull(clientRequest);
+        this.mUserHandle = Objects.requireNonNull(userHandle);
+        this.mCallingPackage = Objects.requireNonNull(callingPackage);
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(@NonNull Parcel dest, int flags) {
+        mClientRequest.writeToParcel(dest, flags);
+        mUserHandle.writeToParcel(dest, flags);
+        dest.writeString8(mCallingPackage);
+    }
+
+    /**
+     * Returns the client request to execute an app function.
+     */
+    @NonNull
+    public ExecuteAppFunctionRequest getClientRequest() {
+        return mClientRequest;
+    }
+
+    /**
+     * Returns the user handle of the user to execute the app function.
+     */
+    @NonNull
+    public UserHandle getUserHandle() {
+        return mUserHandle;
+    }
+
+    /**
+     * Returns the package name of the app that is requesting to execute the app function.
+     */
+    @NonNull
+    public String getCallingPackage() {
+        return mCallingPackage;
+    }
+}
diff --git a/core/java/android/app/appfunctions/ExecuteAppFunctionRequest.aidl b/core/java/android/app/appfunctions/ExecuteAppFunctionRequest.aidl
new file mode 100644
index 0000000..a0b889e
--- /dev/null
+++ b/core/java/android/app/appfunctions/ExecuteAppFunctionRequest.aidl
@@ -0,0 +1,21 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+*/
+
+package android.app.appfunctions;
+
+import android.app.appfunctions.ExecuteAppFunctionRequest;
+
+parcelable ExecuteAppFunctionRequest;
\ No newline at end of file
diff --git a/core/java/android/app/appfunctions/ExecuteAppFunctionRequest.java b/core/java/android/app/appfunctions/ExecuteAppFunctionRequest.java
new file mode 100644
index 0000000..a50425e
--- /dev/null
+++ b/core/java/android/app/appfunctions/ExecuteAppFunctionRequest.java
@@ -0,0 +1,189 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.app.appfunctions;
+
+
+import static android.app.appfunctions.flags.Flags.FLAG_ENABLE_APP_FUNCTION_MANAGER;
+
+import android.annotation.FlaggedApi;
+import android.annotation.NonNull;
+import android.app.appsearch.GenericDocument;
+import android.os.Bundle;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.util.Objects;
+
+/**
+ * A request to execute an app function.
+ */
+@FlaggedApi(FLAG_ENABLE_APP_FUNCTION_MANAGER)
+public final class ExecuteAppFunctionRequest implements Parcelable {
+    @NonNull
+    public static final Creator<ExecuteAppFunctionRequest> CREATOR =
+            new Creator<>() {
+                @Override
+                public ExecuteAppFunctionRequest createFromParcel(Parcel parcel) {
+                    String targetPackageName = parcel.readString8();
+                    String functionIdentifier = parcel.readString8();
+                    GenericDocument parameters;
+                    parameters = GenericDocument.createFromParcel(parcel);
+                    Bundle extras = parcel.readBundle(Bundle.class.getClassLoader());
+                    return new ExecuteAppFunctionRequest(
+                            targetPackageName, functionIdentifier, extras, parameters);
+                }
+
+                @Override
+                public ExecuteAppFunctionRequest[] newArray(int size) {
+                    return new ExecuteAppFunctionRequest[size];
+                }
+            };
+    /**
+     * Returns the package name of the app that hosts the function.
+     */
+    @NonNull
+    private final String mTargetPackageName;
+    /**
+     * Returns the unique string identifier of the app function to be executed.
+     * TODO(b/357551503): Document how callers can get the available function identifiers.
+     */
+    @NonNull
+    private final String mFunctionIdentifier;
+    /**
+     * Returns additional metadata relevant to this function execution request.
+     */
+    @NonNull
+    private final Bundle mExtras;
+    /**
+     * Returns the parameters required to invoke this function. Within this [GenericDocument],
+     * the property names are the names of the function parameters and the property values are the
+     * values of those parameters.
+     *
+     * <p>The document may have missing parameters. Developers are advised to implement defensive
+     * handling measures.
+     *
+     * TODO(b/357551503): Document how function parameters can be obtained for function execution
+     */
+    @NonNull
+    private final GenericDocument mParameters;
+
+    private ExecuteAppFunctionRequest(
+            @NonNull String targetPackageName,
+            @NonNull String functionIdentifier,
+            @NonNull Bundle extras,
+            @NonNull GenericDocument parameters) {
+        mTargetPackageName = Objects.requireNonNull(targetPackageName);
+        mFunctionIdentifier = Objects.requireNonNull(functionIdentifier);
+        mExtras = Objects.requireNonNull(extras);
+        mParameters = Objects.requireNonNull(parameters);
+    }
+
+    /**
+     * Returns the package name of the app that hosts the function.
+     */
+    @NonNull
+    public String getTargetPackageName() {
+        return mTargetPackageName;
+    }
+
+    /**
+     * Returns the unique string identifier of the app function to be executed.
+     */
+    @NonNull
+    public String getFunctionIdentifier() {
+        return mFunctionIdentifier;
+    }
+
+    /**
+     * Returns the function parameters. The key is the parameter name, and the value is the
+     * parameter value.
+     * <p>
+     * The bundle may have missing parameters. Developers are advised to implement defensive
+     * handling measures.
+     */
+    @NonNull
+    public GenericDocument getParameters() {
+        return mParameters;
+    }
+
+    /**
+     * Returns the additional data relevant to this function execution.
+     */
+    @NonNull
+    public Bundle getExtras() {
+        return mExtras;
+    }
+
+    @Override
+    public void writeToParcel(@NonNull Parcel dest, int flags) {
+        dest.writeString8(mTargetPackageName);
+        dest.writeString8(mFunctionIdentifier);
+        mParameters.writeToParcel(dest, flags);
+        dest.writeBundle(mExtras);
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    /**
+     * Builder for {@link ExecuteAppFunctionRequest}.
+     */
+    public static final class Builder {
+        @NonNull
+        private final String mTargetPackageName;
+        @NonNull
+        private final String mFunctionIdentifier;
+        @NonNull
+        private Bundle mExtras = Bundle.EMPTY;
+        @NonNull
+        private GenericDocument mParameters = new GenericDocument.Builder<>("", "", "").build();
+
+        public Builder(@NonNull String targetPackageName, @NonNull String functionIdentifier) {
+            mTargetPackageName = Objects.requireNonNull(targetPackageName);
+            mFunctionIdentifier = Objects.requireNonNull(functionIdentifier);
+        }
+
+        /**
+         * Sets the additional data relevant to this function execution.
+         */
+        @NonNull
+        public Builder setExtras(@NonNull Bundle extras) {
+            mExtras = Objects.requireNonNull(extras);
+            return this;
+        }
+
+        /**
+         * Sets the function parameters.
+         */
+        @NonNull
+        public Builder setParameters(@NonNull GenericDocument parameters) {
+            mParameters = Objects.requireNonNull(parameters);
+            return this;
+        }
+
+        /**
+         * Builds the {@link ExecuteAppFunctionRequest}.
+         */
+        @NonNull
+        public ExecuteAppFunctionRequest build() {
+            return new ExecuteAppFunctionRequest(
+                    mTargetPackageName, mFunctionIdentifier, mExtras, mParameters);
+        }
+    }
+}
diff --git a/core/java/android/app/appfunctions/ExecuteAppFunctionResponse.aidl b/core/java/android/app/appfunctions/ExecuteAppFunctionResponse.aidl
new file mode 100644
index 0000000..5194e7a
--- /dev/null
+++ b/core/java/android/app/appfunctions/ExecuteAppFunctionResponse.aidl
@@ -0,0 +1,21 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+*/
+
+package android.app.appfunctions;
+
+import android.app.appfunctions.ExecuteAppFunctionResponse;
+
+parcelable ExecuteAppFunctionResponse;
\ No newline at end of file
diff --git a/core/java/android/app/appfunctions/ExecuteAppFunctionResponse.java b/core/java/android/app/appfunctions/ExecuteAppFunctionResponse.java
new file mode 100644
index 0000000..72205d9
--- /dev/null
+++ b/core/java/android/app/appfunctions/ExecuteAppFunctionResponse.java
@@ -0,0 +1,294 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.app.appfunctions;
+
+import static android.app.appfunctions.flags.Flags.FLAG_ENABLE_APP_FUNCTION_MANAGER;
+
+import android.annotation.FlaggedApi;
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.app.appsearch.GenericDocument;
+import android.os.Bundle;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.Objects;
+
+/**
+ * The response to an app function execution.
+ */
+@FlaggedApi(FLAG_ENABLE_APP_FUNCTION_MANAGER)
+public final class ExecuteAppFunctionResponse implements Parcelable {
+    @NonNull
+    public static final Creator<ExecuteAppFunctionResponse> CREATOR =
+            new Creator<ExecuteAppFunctionResponse>() {
+                @Override
+                public ExecuteAppFunctionResponse createFromParcel(Parcel parcel) {
+                    GenericDocument result =
+                            Objects.requireNonNull(GenericDocument.createFromParcel(parcel));
+                    Bundle extras = Objects.requireNonNull(
+                            parcel.readBundle(Bundle.class.getClassLoader()));
+                    int resultCode = parcel.readInt();
+                    String errorMessage = parcel.readString8();
+                    return new ExecuteAppFunctionResponse(result, extras, resultCode, errorMessage);
+                }
+
+                @Override
+                public ExecuteAppFunctionResponse[] newArray(int size) {
+                    return new ExecuteAppFunctionResponse[size];
+                }
+            };
+    /**
+     * The name of the property that stores the function return value within the
+     * {@code resultDocument}.
+     *
+     * <p>See {@link GenericDocument#getProperty(String)} for more information.
+     *
+     * <p>If the function returns {@code void} or throws an error, the {@code resultDocument}
+     * will be empty {@link GenericDocument}.
+     *
+     * <p>If the {@code resultDocument} is empty, {@link GenericDocument#getProperty(String)} will
+     * return {@code null}.
+     *
+     * <p>See {@link #getResultDocument} for more information on extracting the return value.
+     */
+    public static final String PROPERTY_RETURN_VALUE = "returnValue";
+
+    /**
+     * The call was successful.
+     */
+    public static final int RESULT_OK = 0;
+
+    /**
+     * The caller does not have the permission to execute an app function.
+     */
+    public static final int RESULT_DENIED = 1;
+
+    /**
+     * An unknown error occurred while processing the call in the AppFunctionService.
+     */
+    public static final int RESULT_APP_UNKNOWN_ERROR = 2;
+
+    /**
+     * An internal error occurred within AppFunctionManagerService.
+     *
+     * <p>This error may be considered similar to {@link IllegalStateException}
+     */
+    public static final int RESULT_INTERNAL_ERROR = 3;
+
+    /**
+     * The caller supplied invalid arguments to the call.
+     *
+     * <p>This error may be considered similar to {@link IllegalArgumentException}.
+     */
+    public static final int RESULT_INVALID_ARGUMENT = 4;
+
+    /**
+     * The operation was timed out.
+     */
+    public static final int RESULT_TIMED_OUT = 5;
+
+    /**
+     * The result code of the app function execution.
+     */
+    @ResultCode
+    private final int mResultCode;
+
+    /**
+     * The error message associated with the result, if any. This is {@code null} if the result code
+     * is {@link #RESULT_OK}.
+     */
+    @Nullable
+    private final String mErrorMessage;
+
+    /**
+     * Returns the return value of the executed function.
+     *
+     * <p>The return value is stored in a {@link GenericDocument} with the key
+     * {@link #PROPERTY_RETURN_VALUE}.
+     *
+     * <p>See {@link #getResultDocument} for more information on extracting the return value.
+     */
+    @NonNull
+    private final GenericDocument mResultDocument;
+
+    /**
+     * Returns the additional metadata data relevant to this function execution response.
+     */
+    @NonNull
+    private final Bundle mExtras;
+
+    private ExecuteAppFunctionResponse(@NonNull GenericDocument resultDocument,
+                                       @NonNull Bundle extras,
+                                       @ResultCode int resultCode,
+                                       @Nullable String errorMessage) {
+        mResultDocument = Objects.requireNonNull(resultDocument);
+        mExtras = Objects.requireNonNull(extras);
+        mResultCode = resultCode;
+        mErrorMessage = errorMessage;
+    }
+
+    /**
+     * Returns a generic document containing the return value of the executed function.
+     *
+     * <p>The {@link #PROPERTY_RETURN_VALUE} key can be used to obtain the return value.</p>
+     *
+     * <p>An empty document is returned if {@link #isSuccess} is {@code false} or if the executed
+     * function does not produce a return value.
+     *
+     * <p>Sample code for extracting the return value:
+     * <pre>
+     *     GenericDocument resultDocument = response.getResultDocument();
+     *     Object returnValue = resultDocument.getProperty(PROPERTY_RETURN_VALUE);
+     *     if (returnValue != null) {
+     *       // Cast returnValue to expected type, or use {@link GenericDocument#getPropertyString},
+     *       // {@link GenericDocument#getPropertyLong} etc.
+     *       // Do something with the returnValue
+     *     }
+     * </pre>
+     */
+    @NonNull
+    public GenericDocument getResultDocument() {
+        return mResultDocument;
+    }
+
+    /**
+     * Returns the extras of the app function execution.
+     */
+    @NonNull
+    public Bundle getExtras() {
+        return mExtras;
+    }
+
+    /**
+     * Returns {@code true} if {@link #getResultCode} equals
+     * {@link ExecuteAppFunctionResponse#RESULT_OK}.
+     */
+    public boolean isSuccess() {
+        return getResultCode() == RESULT_OK;
+    }
+
+    /**
+     * Returns one of the {@code RESULT} constants defined in {@link ExecuteAppFunctionResponse}.
+     */
+    @ResultCode
+    public int getResultCode() {
+        return mResultCode;
+    }
+
+    /**
+     * Returns the error message associated with this result.
+     *
+     * <p>If {@link #isSuccess} is {@code true}, the error message is always {@code null}.
+     */
+    @Nullable
+    public String getErrorMessage() {
+        return mErrorMessage;
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(@NonNull Parcel dest, int flags) {
+        mResultDocument.writeToParcel(dest, flags);
+        dest.writeBundle(mExtras);
+        dest.writeInt(mResultCode);
+        dest.writeString8(mErrorMessage);
+    }
+
+    /**
+     * Result codes.
+     *
+     * @hide
+     */
+    @IntDef(
+            prefix = {"RESULT_"},
+            value = {
+                    RESULT_OK,
+                    RESULT_DENIED,
+                    RESULT_APP_UNKNOWN_ERROR,
+                    RESULT_INTERNAL_ERROR,
+                    RESULT_INVALID_ARGUMENT,
+                    RESULT_TIMED_OUT,
+            })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface ResultCode {
+    }
+
+    /**
+     * The builder for creating {@link ExecuteAppFunctionResponse} instances.
+     */
+    @FlaggedApi(FLAG_ENABLE_APP_FUNCTION_MANAGER)
+    public static final class Builder {
+        @NonNull
+        private GenericDocument mResultDocument = new GenericDocument.Builder<>("", "", "").build();
+        @NonNull
+        private Bundle mExtras = Bundle.EMPTY;
+        private int mResultCode;
+        @Nullable
+        private String mErrorMessage;
+
+        /**
+         * Creates a new builder for {@link ExecuteAppFunctionResponse}.
+         */
+        private Builder() {
+        }
+
+        /**
+         * Creates a new builder for {@link ExecuteAppFunctionResponse} to build a success response
+         * with a result code of {@link #RESULT_OK} and a resultDocument.
+         */
+        public Builder(@NonNull GenericDocument resultDocument) {
+            mResultDocument = Objects.requireNonNull(resultDocument);
+            mResultCode = RESULT_OK;
+        }
+
+        /**
+         * Creates a new builder for {@link ExecuteAppFunctionResponse} to build an error response
+         * with a result code and an error message.
+         */
+        public Builder(@ResultCode int resultCode,
+                       @NonNull String errorMessage) {
+            mResultCode = resultCode;
+            mErrorMessage = Objects.requireNonNull(errorMessage);
+        }
+
+        /**
+         * Sets the extras of the app function execution.
+         */
+        @NonNull
+        public Builder setExtras(@NonNull Bundle extras) {
+            mExtras = Objects.requireNonNull(extras);
+            return this;
+        }
+
+        /**
+         * Builds the {@link ExecuteAppFunctionResponse} instance.
+         */
+        @NonNull
+        public ExecuteAppFunctionResponse build() {
+            return new ExecuteAppFunctionResponse(
+                    mResultDocument, mExtras, mResultCode, mErrorMessage);
+        }
+    }
+}
diff --git a/core/java/android/app/appfunctions/ServiceCallHelper.java b/core/java/android/app/appfunctions/ServiceCallHelper.java
new file mode 100644
index 0000000..cc882bd
--- /dev/null
+++ b/core/java/android/app/appfunctions/ServiceCallHelper.java
@@ -0,0 +1,86 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.app.appfunctions;
+
+import android.annotation.NonNull;
+import android.content.Intent;
+import android.os.UserHandle;
+
+/**
+ * Defines a contract for establishing temporary connections to services and executing operations
+ * within a specified timeout. Implementations of this interface provide mechanisms to ensure that
+ * services are properly unbound after the operation completes or a timeout occurs.
+ *
+ * @param <T> Class of wrapped service.
+ * @hide
+ */
+public interface ServiceCallHelper<T> {
+
+    /**
+     * Initiates service binding and executes a provided method when the service connects. Unbinds
+     * the service after execution or upon timeout. Returns the result of the bindService API.
+     *
+     * <p>When the service connection was made successfully, it's the caller responsibility to
+     * report the usage is completed and can be unbound by calling {@link
+     * ServiceUsageCompleteListener#onCompleted()}.
+     *
+     * <p>This method includes a timeout mechanism to prevent the system from being stuck in a state
+     * where a service is bound indefinitely (for example, if the binder method never returns). This
+     * helps ensure that the calling app does not remain alive unnecessarily.
+     *
+     * @param intent An Intent object that describes the service that should be bound.
+     * @param bindFlags Flags used to control the binding process See {@link
+     *     android.content.Context#bindService}.
+     * @param timeoutInMillis The maximum time in milliseconds to wait for the service connection.
+     * @param userHandle The UserHandle of the user for which the service should be bound.
+     * @param callback A callback to be invoked for various events. See {@link
+     *     RunServiceCallCallback}.
+     */
+    boolean runServiceCall(
+            @NonNull Intent intent,
+            int bindFlags,
+            long timeoutInMillis,
+            @NonNull UserHandle userHandle,
+            @NonNull RunServiceCallCallback<T> callback);
+
+    /** An interface for clients to signal that they have finished using a bound service. */
+    interface ServiceUsageCompleteListener {
+        /**
+         * Called when a client has finished using a bound service. This indicates that the service
+         * can be safely unbound.
+         */
+        void onCompleted();
+    }
+
+    interface RunServiceCallCallback<T> {
+        /**
+         * Called when the service connection has been established. Uses {@code
+         * serviceUsageCompleteListener} to report finish using the connected service.
+         */
+        void onServiceConnected(
+                @NonNull T service,
+                @NonNull ServiceUsageCompleteListener serviceUsageCompleteListener);
+
+        /** Called when the service connection was failed to establish. */
+        void onFailedToConnect();
+
+        /**
+         * Called when the whole operation(i.e. binding and the service call) takes longer than
+         * allowed.
+         */
+        void onTimedOut();
+    }
+}
diff --git a/core/java/android/app/appfunctions/ServiceCallHelperImpl.java b/core/java/android/app/appfunctions/ServiceCallHelperImpl.java
new file mode 100644
index 0000000..2e58546
--- /dev/null
+++ b/core/java/android/app/appfunctions/ServiceCallHelperImpl.java
@@ -0,0 +1,157 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.app.appfunctions;
+
+import android.annotation.NonNull;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.ServiceConnection;
+import android.os.Handler;
+import android.os.IBinder;
+import android.os.Looper;
+import android.os.UserHandle;
+import android.util.Log;
+
+import java.util.concurrent.Executor;
+import java.util.function.Function;
+
+/**
+ * An implementation of {@link android.app.appfunctions.ServiceCallHelper} that that is based on
+ * {@link Context#bindService}.
+ *
+ * @param <T> Class of wrapped service.
+ * @hide
+ */
+public class ServiceCallHelperImpl<T> implements ServiceCallHelper<T> {
+    private static final String TAG = "AppFunctionsServiceCall";
+
+    @NonNull private final Context mContext;
+    @NonNull private final Function<IBinder, T> mInterfaceConverter;
+    private final Handler mHandler = new Handler(Looper.getMainLooper());
+    private final Executor mExecutor;
+
+    /**
+     * @param interfaceConverter A function responsible for converting an IBinder object into the
+     *     desired service interface.
+     * @param executor An Executor instance to dispatch callback.
+     * @param context The system context.
+     */
+    public ServiceCallHelperImpl(
+            @NonNull Context context,
+            @NonNull Function<IBinder, T> interfaceConverter,
+            @NonNull Executor executor) {
+        mContext = context;
+        mInterfaceConverter = interfaceConverter;
+        mExecutor = executor;
+    }
+
+    @Override
+    public boolean runServiceCall(
+            @NonNull Intent intent,
+            int bindFlags,
+            long timeoutInMillis,
+            @NonNull UserHandle userHandle,
+            @NonNull RunServiceCallCallback<T> callback) {
+        OneOffServiceConnection serviceConnection =
+                new OneOffServiceConnection(
+                        intent, bindFlags, timeoutInMillis, userHandle, callback);
+
+        return serviceConnection.bindAndRun();
+    }
+
+    private class OneOffServiceConnection
+            implements ServiceConnection, ServiceUsageCompleteListener {
+        private final Intent mIntent;
+        private final int mFlags;
+        private final long mTimeoutMillis;
+        private final UserHandle mUserHandle;
+        private final RunServiceCallCallback<T> mCallback;
+        private final Runnable mTimeoutCallback;
+
+        OneOffServiceConnection(
+                @NonNull Intent intent,
+                int flags,
+                long timeoutMillis,
+                @NonNull UserHandle userHandle,
+                @NonNull RunServiceCallCallback<T> callback) {
+            mIntent = intent;
+            mFlags = flags;
+            mTimeoutMillis = timeoutMillis;
+            mCallback = callback;
+            mTimeoutCallback =
+                    () ->
+                            mExecutor.execute(
+                                    () -> {
+                                        safeUnbind();
+                                        mCallback.onTimedOut();
+                                    });
+            mUserHandle = userHandle;
+        }
+
+        public boolean bindAndRun() {
+            boolean bindServiceResult =
+                    mContext.bindServiceAsUser(mIntent, this, mFlags, mUserHandle);
+
+            if (bindServiceResult) {
+                mHandler.postDelayed(mTimeoutCallback, mTimeoutMillis);
+            } else {
+                safeUnbind();
+            }
+
+            return bindServiceResult;
+        }
+
+        @Override
+        public void onServiceConnected(ComponentName name, IBinder service) {
+            T serviceInterface = mInterfaceConverter.apply(service);
+
+            mExecutor.execute(() -> mCallback.onServiceConnected(serviceInterface, this));
+        }
+
+        @Override
+        public void onServiceDisconnected(ComponentName name) {
+            safeUnbind();
+            mExecutor.execute(mCallback::onFailedToConnect);
+        }
+
+        @Override
+        public void onBindingDied(ComponentName name) {
+            safeUnbind();
+            mExecutor.execute(mCallback::onFailedToConnect);
+        }
+
+        @Override
+        public void onNullBinding(ComponentName name) {
+            safeUnbind();
+            mExecutor.execute(mCallback::onFailedToConnect);
+        }
+
+        private void safeUnbind() {
+            try {
+                mHandler.removeCallbacks(mTimeoutCallback);
+                mContext.unbindService(this);
+            } catch (Exception ex) {
+                Log.w(TAG, "Failed to unbind", ex);
+            }
+        }
+
+        @Override
+        public void onCompleted() {
+            safeUnbind();
+        }
+    }
+}
diff --git a/core/java/android/app/notification.aconfig b/core/java/android/app/notification.aconfig
index f751a23..f05c24f 100644
--- a/core/java/android/app/notification.aconfig
+++ b/core/java/android/app/notification.aconfig
@@ -21,6 +21,13 @@
 }
 
 flag {
+  name: "modes_ui_test"
+  namespace: "systemui"
+  description: "Guards new CTS tests for Modes; dependent on flags modes_api and modes_ui"
+  bug: "360862012"
+}
+
+flag {
   name: "api_tvextender"
   is_exported: true
   namespace: "systemui"
diff --git a/core/java/android/companion/virtual/VirtualDeviceManager.java b/core/java/android/companion/virtual/VirtualDeviceManager.java
index 8b60580..d07fb25 100644
--- a/core/java/android/companion/virtual/VirtualDeviceManager.java
+++ b/core/java/android/companion/virtual/VirtualDeviceManager.java
@@ -531,7 +531,6 @@
      *
      * @hide
      */
-    @FlaggedApi(Flags.FLAG_INTERACTIVE_SCREEN_MIRROR)
     @TestApi
     public boolean isVirtualDeviceOwnedMirrorDisplay(int displayId) {
         if (mService == null) {
diff --git a/core/java/android/companion/virtual/flags.aconfig b/core/java/android/companion/virtual/flags.aconfig
index 91586b6..fc9c94d 100644
--- a/core/java/android/companion/virtual/flags.aconfig
+++ b/core/java/android/companion/virtual/flags.aconfig
@@ -35,13 +35,6 @@
 }
 
 flag {
-  name: "consistent_display_flags"
-  namespace: "virtual_devices"
-  description: "Make virtual display flags consistent with display manager"
-  bug: "300905478"
-}
-
-flag {
   name: "vdm_custom_ime"
   is_exported: true
   namespace: "virtual_devices"
@@ -89,14 +82,6 @@
 }
 
 flag {
-  name: "interactive_screen_mirror"
-  is_exported: true
-  namespace: "virtual_devices"
-  description: "Enable interactive screen mirroring using Virtual Devices"
-  bug: "292212199"
-}
-
-flag {
   name: "virtual_stylus"
   is_exported: true
   namespace: "virtual_devices"
diff --git a/core/java/android/content/pm/TEST_MAPPING b/core/java/android/content/pm/TEST_MAPPING
index 1fab3cf..c7716e5 100644
--- a/core/java/android/content/pm/TEST_MAPPING
+++ b/core/java/android/content/pm/TEST_MAPPING
@@ -173,14 +173,47 @@
             ]
         },
         {
-            "name":"CtsPackageInstallerCUJTestCases",
+            "name": "CtsPackageInstallerCUJInstallationTestCases",
             "options":[
-                {
-                    "exclude-annotation":"androidx.test.filters.FlakyTest"
-                },
-                {
-                    "exclude-annotation":"org.junit.Ignore"
-                }
+               {
+                   "exclude-annotation":"androidx.test.filters.FlakyTest"
+               },
+               {
+                   "exclude-annotation":"org.junit.Ignore"
+               }
+            ]
+        },
+        {
+            "name": "CtsPackageInstallerCUJUninstallationTestCases",
+            "options":[
+               {
+                   "exclude-annotation":"androidx.test.filters.FlakyTest"
+               },
+               {
+                   "exclude-annotation":"org.junit.Ignore"
+               }
+            ]
+        },
+        {
+            "name": "CtsPackageInstallerCUJUpdateOwnerShipTestCases",
+            "options":[
+               {
+                   "exclude-annotation":"androidx.test.filters.FlakyTest"
+               },
+               {
+                   "exclude-annotation":"org.junit.Ignore"
+               }
+            ]
+        },
+        {
+            "name": "CtsPackageInstallerCUJUpdateSelfTestCases",
+            "options":[
+               {
+                   "exclude-annotation":"androidx.test.filters.FlakyTest"
+               },
+               {
+                   "exclude-annotation":"org.junit.Ignore"
+               }
             ]
         }
     ]
diff --git a/core/java/android/hardware/biometrics/BiometricConstants.java b/core/java/android/hardware/biometrics/BiometricConstants.java
index 61d8702..8975191 100644
--- a/core/java/android/hardware/biometrics/BiometricConstants.java
+++ b/core/java/android/hardware/biometrics/BiometricConstants.java
@@ -162,6 +162,13 @@
      * @hide
      */
     int BIOMETRIC_ERROR_POWER_PRESSED = 19;
+
+    /**
+     * Mandatory biometrics is not in effect.
+     * @hide
+     */
+    int BIOMETRIC_ERROR_MANDATORY_NOT_ACTIVE = 20;
+
     /**
      * This constant is only used by SystemUI. It notifies SystemUI that authentication was paused
      * because the authentication attempt was unsuccessful.
diff --git a/core/java/android/hardware/biometrics/BiometricManager.java b/core/java/android/hardware/biometrics/BiometricManager.java
index de1cac4..9bc46b9 100644
--- a/core/java/android/hardware/biometrics/BiometricManager.java
+++ b/core/java/android/hardware/biometrics/BiometricManager.java
@@ -80,6 +80,20 @@
             BiometricConstants.BIOMETRIC_ERROR_HW_NOT_PRESENT;
 
     /**
+     * Lockout error.
+     * @hide
+     */
+    public static final int BIOMETRIC_ERROR_LOCKOUT =
+            BiometricConstants.BIOMETRIC_ERROR_LOCKOUT;
+
+    /**
+     * Mandatory biometrics is not effective.
+     * @hide
+     */
+    public static final int BIOMETRIC_ERROR_MANDATORY_NOT_ACTIVE =
+            BiometricConstants.BIOMETRIC_ERROR_MANDATORY_NOT_ACTIVE;
+
+    /**
      * A security vulnerability has been discovered and the sensor is unavailable until a
      * security update has addressed this issue. This error can be received if for example,
      * authentication was requested with {@link Authenticators#BIOMETRIC_STRONG}, but the
@@ -113,7 +127,9 @@
             BIOMETRIC_ERROR_HW_UNAVAILABLE,
             BIOMETRIC_ERROR_NONE_ENROLLED,
             BIOMETRIC_ERROR_NO_HARDWARE,
-            BIOMETRIC_ERROR_SECURITY_UPDATE_REQUIRED})
+            BIOMETRIC_ERROR_SECURITY_UPDATE_REQUIRED,
+            BIOMETRIC_ERROR_LOCKOUT,
+            BIOMETRIC_ERROR_MANDATORY_NOT_ACTIVE})
     @Retention(RetentionPolicy.SOURCE)
     public @interface BiometricError {}
 
diff --git a/core/java/android/hardware/input/IInputManager.aidl b/core/java/android/hardware/input/IInputManager.aidl
index 98e11375..83f2685 100644
--- a/core/java/android/hardware/input/IInputManager.aidl
+++ b/core/java/android/hardware/input/IInputManager.aidl
@@ -25,7 +25,7 @@
 import android.hardware.input.IInputDeviceBatteryState;
 import android.hardware.input.IKeyboardBacklightListener;
 import android.hardware.input.IKeyboardBacklightState;
-import android.hardware.input.IKeyboardSystemShortcutListener;
+import android.hardware.input.IKeyGestureEventListener;
 import android.hardware.input.IStickyModifierStateListener;
 import android.hardware.input.ITabletModeChangedListener;
 import android.hardware.input.KeyboardLayoutSelectionResult;
@@ -241,13 +241,13 @@
 
     KeyGlyphMap getKeyGlyphMap(int deviceId);
 
-    @EnforcePermission("MONITOR_KEYBOARD_SYSTEM_SHORTCUTS")
+    @EnforcePermission("MANAGE_KEY_GESTURES")
     @JavaPassthrough(annotation="@android.annotation.RequiresPermission(value = "
-            + "android.Manifest.permission.MONITOR_KEYBOARD_SYSTEM_SHORTCUTS)")
-    void registerKeyboardSystemShortcutListener(IKeyboardSystemShortcutListener listener);
+            + "android.Manifest.permission.MANAGE_KEY_GESTURES)")
+    void registerKeyGestureEventListener(IKeyGestureEventListener listener);
 
-    @EnforcePermission("MONITOR_KEYBOARD_SYSTEM_SHORTCUTS")
+    @EnforcePermission("MANAGE_KEY_GESTURES")
     @JavaPassthrough(annotation="@android.annotation.RequiresPermission(value = "
-            + "android.Manifest.permission.MONITOR_KEYBOARD_SYSTEM_SHORTCUTS)")
-    void unregisterKeyboardSystemShortcutListener(IKeyboardSystemShortcutListener listener);
+            + "android.Manifest.permission.MANAGE_KEY_GESTURES)")
+    void unregisterKeyGestureEventListener(IKeyGestureEventListener listener);
 }
diff --git a/core/java/android/hardware/input/IKeyboardSystemShortcutListener.aidl b/core/java/android/hardware/input/IKeyGestureEventListener.aidl
similarity index 71%
rename from core/java/android/hardware/input/IKeyboardSystemShortcutListener.aidl
rename to core/java/android/hardware/input/IKeyGestureEventListener.aidl
index 8d44917..2c430f1 100644
--- a/core/java/android/hardware/input/IKeyboardSystemShortcutListener.aidl
+++ b/core/java/android/hardware/input/IKeyGestureEventListener.aidl
@@ -17,11 +17,10 @@
 package android.hardware.input;
 
 /** @hide */
-oneway interface IKeyboardSystemShortcutListener {
+oneway interface IKeyGestureEventListener {
 
     /**
-     * Called when the keyboard system shortcut is triggered.
+     * Called when a key gesture event occurs.
      */
-    void onKeyboardSystemShortcutTriggered(int deviceId, in int[] keycodes, int modifierState,
-                                           int shortcut);
+    void onKeyGestureEvent(int deviceId, in int[] keycodes, int modifierState, int shortcut);
 }
diff --git a/core/java/android/hardware/input/InputManager.java b/core/java/android/hardware/input/InputManager.java
index 6bc522b..04cfcd8 100644
--- a/core/java/android/hardware/input/InputManager.java
+++ b/core/java/android/hardware/input/InputManager.java
@@ -1378,33 +1378,31 @@
     }
 
     /**
-     * Registers a keyboard system shortcut listener for {@link KeyboardSystemShortcut} being
-     * triggered.
+     * Registers a key gesture event listener for {@link KeyGestureEvent} being triggered.
      *
      * @param executor an executor on which the callback will be called
-     * @param listener the {@link KeyboardSystemShortcutListener}
+     * @param listener the {@link KeyGestureEventListener}
      * @throws IllegalArgumentException if {@code listener} has already been registered previously.
      * @throws NullPointerException     if {@code listener} or {@code executor} is null.
      * @hide
-     * @see #unregisterKeyboardSystemShortcutListener(KeyboardSystemShortcutListener)
+     * @see #unregisterKeyGestureEventListener(KeyGestureEventListener)
      */
-    @RequiresPermission(Manifest.permission.MONITOR_KEYBOARD_SYSTEM_SHORTCUTS)
-    public void registerKeyboardSystemShortcutListener(@NonNull Executor executor,
-            @NonNull KeyboardSystemShortcutListener listener) throws IllegalArgumentException {
-        mGlobal.registerKeyboardSystemShortcutListener(executor, listener);
+    @RequiresPermission(Manifest.permission.MANAGE_KEY_GESTURES)
+    public void registerKeyGestureEventListener(@NonNull Executor executor,
+            @NonNull KeyGestureEventListener listener) throws IllegalArgumentException {
+        mGlobal.registerKeyGestureEventListener(executor, listener);
     }
 
     /**
-     * Unregisters a previously added keyboard system shortcut listener.
+     * Unregisters a previously added key gesture event listener.
      *
-     * @param listener the {@link KeyboardSystemShortcutListener}
+     * @param listener the {@link KeyGestureEventListener}
      * @hide
-     * @see #registerKeyboardSystemShortcutListener(Executor, KeyboardSystemShortcutListener)
+     * @see #registerKeyGestureEventListener(Executor, KeyGestureEventListener)
      */
-    @RequiresPermission(Manifest.permission.MONITOR_KEYBOARD_SYSTEM_SHORTCUTS)
-    public void unregisterKeyboardSystemShortcutListener(
-            @NonNull KeyboardSystemShortcutListener listener) {
-        mGlobal.unregisterKeyboardSystemShortcutListener(listener);
+    @RequiresPermission(Manifest.permission.MANAGE_KEY_GESTURES)
+    public void unregisterKeyGestureEventListener(@NonNull KeyGestureEventListener listener) {
+        mGlobal.unregisterKeyGestureEventListener(listener);
     }
 
     /**
@@ -1510,19 +1508,18 @@
     }
 
     /**
-     * A callback used to be notified about keyboard system shortcuts being triggered.
+     * A callback used to notify about key gesture event on completion.
      *
-     * @see #registerKeyboardSystemShortcutListener(Executor, KeyboardSystemShortcutListener)
-     * @see #unregisterKeyboardSystemShortcutListener(KeyboardSystemShortcutListener)
+     * @see #registerKeyGestureEventListener(Executor, KeyGestureEventListener)
+     * @see #unregisterKeyGestureEventListener(KeyGestureEventListener)
      * @hide
      */
-    public interface KeyboardSystemShortcutListener {
+    public interface KeyGestureEventListener {
         /**
-         * Called when a keyboard system shortcut is triggered.
+         * Called when a key gesture event occurs.
          *
-         * @param systemShortcut the shortcut info about the shortcut that was triggered.
+         * @param event the gesture event that occurred.
          */
-        void onKeyboardSystemShortcutTriggered(int deviceId,
-                @NonNull KeyboardSystemShortcut systemShortcut);
+        void onKeyGestureEvent(@NonNull KeyGestureEvent event);
     }
 }
diff --git a/core/java/android/hardware/input/InputManagerGlobal.java b/core/java/android/hardware/input/InputManagerGlobal.java
index f7fa557..03cf7c5 100644
--- a/core/java/android/hardware/input/InputManagerGlobal.java
+++ b/core/java/android/hardware/input/InputManagerGlobal.java
@@ -25,8 +25,8 @@
 import android.hardware.SensorManager;
 import android.hardware.input.InputManager.InputDeviceBatteryListener;
 import android.hardware.input.InputManager.InputDeviceListener;
+import android.hardware.input.InputManager.KeyGestureEventListener;
 import android.hardware.input.InputManager.KeyboardBacklightListener;
-import android.hardware.input.InputManager.KeyboardSystemShortcutListener;
 import android.hardware.input.InputManager.OnTabletModeChangedListener;
 import android.hardware.input.InputManager.StickyModifierStateListener;
 import android.hardware.lights.Light;
@@ -111,13 +111,13 @@
     @Nullable
     private IStickyModifierStateListener mStickyModifierStateListener;
 
-    private final Object mKeyboardSystemShortcutListenerLock = new Object();
-    @GuardedBy("mKeyboardSystemShortcutListenerLock")
+    private final Object mKeyGestureEventListenerLock = new Object();
+    @GuardedBy("mKeyGestureEventListenerLock")
     @Nullable
-    private ArrayList<KeyboardSystemShortcutListenerDelegate> mKeyboardSystemShortcutListeners;
-    @GuardedBy("mKeyboardSystemShortcutListenerLock")
+    private ArrayList<KeyGestureEventListenerDelegate> mKeyGestureEventListeners;
+    @GuardedBy("mKeyGestureEventListenerLock")
     @Nullable
-    private IKeyboardSystemShortcutListener mKeyboardSystemShortcutListener;
+    private IKeyGestureEventListener mKeyGestureEventListener;
 
     // InputDeviceSensorManager gets notified synchronously from the binder thread when input
     // devices change, so it must be synchronized with the input device listeners.
@@ -1064,94 +1064,92 @@
         }
     }
 
-    private static final class KeyboardSystemShortcutListenerDelegate {
-        final KeyboardSystemShortcutListener mListener;
+    private static final class KeyGestureEventListenerDelegate {
+        final KeyGestureEventListener mListener;
         final Executor mExecutor;
 
-        KeyboardSystemShortcutListenerDelegate(KeyboardSystemShortcutListener listener,
+        KeyGestureEventListenerDelegate(KeyGestureEventListener listener,
                 Executor executor) {
             mListener = listener;
             mExecutor = executor;
         }
 
-        void onKeyboardSystemShortcutTriggered(int deviceId,
-                KeyboardSystemShortcut systemShortcut) {
-            mExecutor.execute(() ->
-                    mListener.onKeyboardSystemShortcutTriggered(deviceId, systemShortcut));
+        void onKeyGestureEvent(KeyGestureEvent event) {
+            mExecutor.execute(() -> mListener.onKeyGestureEvent(event));
         }
     }
 
-    private class LocalKeyboardSystemShortcutListener extends IKeyboardSystemShortcutListener.Stub {
+    private class LocalKeyGestureEventListener extends IKeyGestureEventListener.Stub {
 
         @Override
-        public void onKeyboardSystemShortcutTriggered(int deviceId, int[] keycodes,
-                int modifierState, int shortcut) {
-            synchronized (mKeyboardSystemShortcutListenerLock) {
-                if (mKeyboardSystemShortcutListeners == null) return;
-                final int numListeners = mKeyboardSystemShortcutListeners.size();
+        public void onKeyGestureEvent(int deviceId, int[] keycodes, int modifierState,
+                int gestureType) {
+            synchronized (mKeyGestureEventListenerLock) {
+                if (mKeyGestureEventListeners == null) return;
+                final int numListeners = mKeyGestureEventListeners.size();
                 for (int i = 0; i < numListeners; i++) {
-                    mKeyboardSystemShortcutListeners.get(i)
-                            .onKeyboardSystemShortcutTriggered(deviceId,
-                                    new KeyboardSystemShortcut(keycodes, modifierState, shortcut));
+                    mKeyGestureEventListeners.get(i)
+                            .onKeyGestureEvent(
+                                    new KeyGestureEvent(deviceId, keycodes, modifierState,
+                                            gestureType));
                 }
             }
         }
     }
 
     /**
-     * @see InputManager#registerKeyboardSystemShortcutListener(Executor,
-     * KeyboardSystemShortcutListener)
+     * @see InputManager#registerKeyGestureEventListener(Executor,
+     * KeyGestureEventListener)
      */
-    @RequiresPermission(Manifest.permission.MONITOR_KEYBOARD_SYSTEM_SHORTCUTS)
-    void registerKeyboardSystemShortcutListener(@NonNull Executor executor,
-            @NonNull KeyboardSystemShortcutListener listener) throws IllegalArgumentException {
+    @RequiresPermission(Manifest.permission.MANAGE_KEY_GESTURES)
+    void registerKeyGestureEventListener(@NonNull Executor executor,
+            @NonNull KeyGestureEventListener listener) throws IllegalArgumentException {
         Objects.requireNonNull(executor, "executor should not be null");
         Objects.requireNonNull(listener, "listener should not be null");
 
-        synchronized (mKeyboardSystemShortcutListenerLock) {
-            if (mKeyboardSystemShortcutListener == null) {
-                mKeyboardSystemShortcutListeners = new ArrayList<>();
-                mKeyboardSystemShortcutListener = new LocalKeyboardSystemShortcutListener();
+        synchronized (mKeyGestureEventListenerLock) {
+            if (mKeyGestureEventListener == null) {
+                mKeyGestureEventListeners = new ArrayList<>();
+                mKeyGestureEventListener = new LocalKeyGestureEventListener();
 
                 try {
-                    mIm.registerKeyboardSystemShortcutListener(mKeyboardSystemShortcutListener);
+                    mIm.registerKeyGestureEventListener(mKeyGestureEventListener);
                 } catch (RemoteException e) {
                     throw e.rethrowFromSystemServer();
                 }
             }
-            final int numListeners = mKeyboardSystemShortcutListeners.size();
+            final int numListeners = mKeyGestureEventListeners.size();
             for (int i = 0; i < numListeners; i++) {
-                if (mKeyboardSystemShortcutListeners.get(i).mListener == listener) {
+                if (mKeyGestureEventListeners.get(i).mListener == listener) {
                     throw new IllegalArgumentException("Listener has already been registered!");
                 }
             }
-            KeyboardSystemShortcutListenerDelegate delegate =
-                    new KeyboardSystemShortcutListenerDelegate(listener, executor);
-            mKeyboardSystemShortcutListeners.add(delegate);
+            KeyGestureEventListenerDelegate delegate =
+                    new KeyGestureEventListenerDelegate(listener, executor);
+            mKeyGestureEventListeners.add(delegate);
         }
     }
 
     /**
-     * @see InputManager#unregisterKeyboardSystemShortcutListener(KeyboardSystemShortcutListener)
+     * @see InputManager#unregisterKeyGestureEventListener(KeyGestureEventListener)
      */
-    @RequiresPermission(Manifest.permission.MONITOR_KEYBOARD_SYSTEM_SHORTCUTS)
-    void unregisterKeyboardSystemShortcutListener(
-            @NonNull KeyboardSystemShortcutListener listener) {
+    @RequiresPermission(Manifest.permission.MANAGE_KEY_GESTURES)
+    void unregisterKeyGestureEventListener(@NonNull KeyGestureEventListener listener) {
         Objects.requireNonNull(listener, "listener should not be null");
 
-        synchronized (mKeyboardSystemShortcutListenerLock) {
-            if (mKeyboardSystemShortcutListeners == null) {
+        synchronized (mKeyGestureEventListenerLock) {
+            if (mKeyGestureEventListeners == null) {
                 return;
             }
-            mKeyboardSystemShortcutListeners.removeIf((delegate) -> delegate.mListener == listener);
-            if (mKeyboardSystemShortcutListeners.isEmpty()) {
+            mKeyGestureEventListeners.removeIf((delegate) -> delegate.mListener == listener);
+            if (mKeyGestureEventListeners.isEmpty()) {
                 try {
-                    mIm.unregisterKeyboardSystemShortcutListener(mKeyboardSystemShortcutListener);
+                    mIm.unregisterKeyGestureEventListener(mKeyGestureEventListener);
                 } catch (RemoteException e) {
                     throw e.rethrowFromSystemServer();
                 }
-                mKeyboardSystemShortcutListeners = null;
-                mKeyboardSystemShortcutListener = null;
+                mKeyGestureEventListeners = null;
+                mKeyGestureEventListener = null;
             }
         }
     }
diff --git a/core/java/android/hardware/input/InputSettings.java b/core/java/android/hardware/input/InputSettings.java
index d85e41d..c5d0caf22 100644
--- a/core/java/android/hardware/input/InputSettings.java
+++ b/core/java/android/hardware/input/InputSettings.java
@@ -336,6 +336,39 @@
     }
 
     /**
+     * Returns true if the touchpad visualizer is allowed to appear.
+     *
+     * @param context The application context.
+     * @return Whether it is allowed to show touchpad visualizer or not.
+     *
+     * @hide
+     */
+    public static boolean useTouchpadVisualizer(@NonNull Context context) {
+        if (!isTouchpadVisualizerFeatureFlagEnabled()) {
+            return false;
+        }
+        return Settings.System.getIntForUser(context.getContentResolver(),
+                Settings.System.TOUCHPAD_VISUALIZER, 0, UserHandle.USER_CURRENT) == 1;
+    }
+
+    /**
+     * Sets the touchpad visualizer behaviour.
+     *
+     * @param context The application context.
+     * @param enabled Will enable touchpad visualizer if true, disable it if false
+     *
+     * @hide
+     */
+    @RequiresPermission(Manifest.permission.WRITE_SETTINGS)
+    public static void setTouchpadVisualizer(@NonNull Context context, boolean enabled) {
+        if (!isTouchpadVisualizerFeatureFlagEnabled()) {
+            return;
+        }
+        Settings.System.putIntForUser(context.getContentResolver(),
+                Settings.System.TOUCHPAD_VISUALIZER, enabled ? 1 : 0, UserHandle.USER_CURRENT);
+    }
+
+    /**
      * Returns true if the touchpad should allow tap dragging.
      *
      * The returned value only applies to gesture-compatible touchpads.
diff --git a/core/java/android/hardware/input/KeyGestureEvent.java b/core/java/android/hardware/input/KeyGestureEvent.java
new file mode 100644
index 0000000..7a8dd33
--- /dev/null
+++ b/core/java/android/hardware/input/KeyGestureEvent.java
@@ -0,0 +1,531 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.hardware.input;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+
+import com.android.internal.util.DataClass;
+import com.android.internal.util.FrameworkStatsLog;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * Provides information about the keyboard gesture event being triggered by an external keyboard.
+ *
+ * @hide
+ */
+@DataClass(genToString = true, genEqualsHashCode = true)
+public class KeyGestureEvent {
+
+    private final int mDeviceId;
+    @NonNull
+    private final int[] mKeycodes;
+    private final int mModifierState;
+    @KeyGestureType
+    private final int mKeyGestureType;
+
+
+    public static final int KEY_GESTURE_TYPE_UNSPECIFIED =
+            FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__UNSPECIFIED;
+    public static final int KEY_GESTURE_TYPE_HOME =
+            FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__HOME;
+    public static final int KEY_GESTURE_TYPE_RECENT_APPS =
+            FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__RECENT_APPS;
+    public static final int KEY_GESTURE_TYPE_BACK =
+            FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__BACK;
+    public static final int KEY_GESTURE_TYPE_APP_SWITCH =
+            FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__APP_SWITCH;
+    public static final int KEY_GESTURE_TYPE_LAUNCH_ASSISTANT =
+            FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__LAUNCH_ASSISTANT;
+    public static final int KEY_GESTURE_TYPE_LAUNCH_VOICE_ASSISTANT =
+            FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__LAUNCH_VOICE_ASSISTANT;
+    public static final int KEY_GESTURE_TYPE_LAUNCH_SYSTEM_SETTINGS =
+            FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__LAUNCH_SYSTEM_SETTINGS;
+    public static final int KEY_GESTURE_TYPE_TOGGLE_NOTIFICATION_PANEL =
+            FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__TOGGLE_NOTIFICATION_PANEL;
+    public static final int KEY_GESTURE_TYPE_TOGGLE_TASKBAR =
+            FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__TOGGLE_TASKBAR;
+    public static final int KEY_GESTURE_TYPE_TAKE_SCREENSHOT =
+            FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__TAKE_SCREENSHOT;
+    public static final int KEY_GESTURE_TYPE_OPEN_SHORTCUT_HELPER =
+            FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__OPEN_SHORTCUT_HELPER;
+    public static final int KEY_GESTURE_TYPE_BRIGHTNESS_UP =
+            FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__BRIGHTNESS_UP;
+    public static final int KEY_GESTURE_TYPE_BRIGHTNESS_DOWN =
+            FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__BRIGHTNESS_DOWN;
+    public static final int KEY_GESTURE_TYPE_KEYBOARD_BACKLIGHT_UP =
+            FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__KEYBOARD_BACKLIGHT_UP;
+    public static final int KEY_GESTURE_TYPE_KEYBOARD_BACKLIGHT_DOWN =
+            FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__KEYBOARD_BACKLIGHT_DOWN;
+    public static final int KEY_GESTURE_TYPE_KEYBOARD_BACKLIGHT_TOGGLE =
+            FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__KEYBOARD_BACKLIGHT_TOGGLE;
+    public static final int KEY_GESTURE_TYPE_VOLUME_UP =
+            FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__VOLUME_UP;
+    public static final int KEY_GESTURE_TYPE_VOLUME_DOWN =
+            FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__VOLUME_DOWN;
+    public static final int KEY_GESTURE_TYPE_VOLUME_MUTE =
+            FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__VOLUME_MUTE;
+    public static final int KEY_GESTURE_TYPE_ALL_APPS =
+            FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__ALL_APPS;
+    public static final int KEY_GESTURE_TYPE_LAUNCH_SEARCH =
+            FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__LAUNCH_SEARCH;
+    public static final int KEY_GESTURE_TYPE_LANGUAGE_SWITCH =
+            FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__LANGUAGE_SWITCH;
+    public static final int KEY_GESTURE_TYPE_ACCESSIBILITY_ALL_APPS =
+            FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__ACCESSIBILITY_ALL_APPS;
+    public static final int KEY_GESTURE_TYPE_TOGGLE_CAPS_LOCK =
+            FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__TOGGLE_CAPS_LOCK;
+    public static final int KEY_GESTURE_TYPE_SYSTEM_MUTE =
+            FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__SYSTEM_MUTE;
+    public static final int KEY_GESTURE_TYPE_SPLIT_SCREEN_NAVIGATION =
+            FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__SPLIT_SCREEN_NAVIGATION;
+    public static final int KEY_GESTURE_TYPE_CHANGE_SPLITSCREEN_FOCUS =
+            FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__CHANGE_SPLITSCREEN_FOCUS;
+    public static final int KEY_GESTURE_TYPE_TRIGGER_BUG_REPORT =
+            FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__TRIGGER_BUG_REPORT;
+    public static final int KEY_GESTURE_TYPE_LOCK_SCREEN =
+            FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__LOCK_SCREEN;
+    public static final int KEY_GESTURE_TYPE_OPEN_NOTES =
+            FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__OPEN_NOTES;
+    public static final int KEY_GESTURE_TYPE_TOGGLE_POWER =
+            FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__TOGGLE_POWER;
+    public static final int KEY_GESTURE_TYPE_SYSTEM_NAVIGATION =
+            FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__SYSTEM_NAVIGATION;
+    public static final int KEY_GESTURE_TYPE_SLEEP =
+            FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__SLEEP;
+    public static final int KEY_GESTURE_TYPE_WAKEUP =
+            FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__WAKEUP;
+    public static final int KEY_GESTURE_TYPE_MEDIA_KEY =
+            FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__MEDIA_KEY;
+    public static final int KEY_GESTURE_TYPE_LAUNCH_DEFAULT_BROWSER =
+            FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__LAUNCH_DEFAULT_BROWSER;
+    public static final int KEY_GESTURE_TYPE_LAUNCH_DEFAULT_EMAIL =
+            FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__LAUNCH_DEFAULT_EMAIL;
+    public static final int KEY_GESTURE_TYPE_LAUNCH_DEFAULT_CONTACTS =
+            FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__LAUNCH_DEFAULT_CONTACTS;
+    public static final int KEY_GESTURE_TYPE_LAUNCH_DEFAULT_CALENDAR =
+            FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__LAUNCH_DEFAULT_CALENDAR;
+    public static final int KEY_GESTURE_TYPE_LAUNCH_DEFAULT_CALCULATOR =
+            FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__LAUNCH_DEFAULT_CALCULATOR;
+    public static final int KEY_GESTURE_TYPE_LAUNCH_DEFAULT_MUSIC =
+            FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__LAUNCH_DEFAULT_MUSIC;
+    public static final int KEY_GESTURE_TYPE_LAUNCH_DEFAULT_MAPS =
+            FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__LAUNCH_DEFAULT_MAPS;
+    public static final int KEY_GESTURE_TYPE_LAUNCH_DEFAULT_MESSAGING =
+            FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__LAUNCH_DEFAULT_MESSAGING;
+    public static final int KEY_GESTURE_TYPE_LAUNCH_DEFAULT_GALLERY =
+            FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__LAUNCH_DEFAULT_GALLERY;
+    public static final int KEY_GESTURE_TYPE_LAUNCH_DEFAULT_FILES =
+            FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__LAUNCH_DEFAULT_FILES;
+    public static final int KEY_GESTURE_TYPE_LAUNCH_DEFAULT_WEATHER =
+            FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__LAUNCH_DEFAULT_WEATHER;
+    public static final int KEY_GESTURE_TYPE_LAUNCH_DEFAULT_FITNESS =
+            FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__LAUNCH_DEFAULT_FITNESS;
+    public static final int KEY_GESTURE_TYPE_LAUNCH_APPLICATION_BY_PACKAGE_NAME =
+            FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__LAUNCH_APPLICATION_BY_PACKAGE_NAME;
+    public static final int KEY_GESTURE_TYPE_DESKTOP_MODE =
+            FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__DESKTOP_MODE;
+    public static final int KEY_GESTURE_TYPE_MULTI_WINDOW_NAVIGATION =
+            FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__MULTI_WINDOW_NAVIGATION;
+
+
+
+    // Code below generated by codegen v1.0.23.
+    //
+    // DO NOT MODIFY!
+    // CHECKSTYLE:OFF Generated code
+    //
+    // To regenerate run:
+    // $ codegen $ANDROID_BUILD_TOP/frameworks/base/core/java/android/hardware/input/KeyGestureEvent.java
+    //
+    // To exclude the generated code from IntelliJ auto-formatting enable (one-time):
+    //   Settings > Editor > Code Style > Formatter Control
+    //@formatter:off
+
+
+    @IntDef(prefix = "KEY_GESTURE_TYPE_", value = {
+        KEY_GESTURE_TYPE_UNSPECIFIED,
+        KEY_GESTURE_TYPE_HOME,
+        KEY_GESTURE_TYPE_RECENT_APPS,
+        KEY_GESTURE_TYPE_BACK,
+        KEY_GESTURE_TYPE_APP_SWITCH,
+        KEY_GESTURE_TYPE_LAUNCH_ASSISTANT,
+        KEY_GESTURE_TYPE_LAUNCH_VOICE_ASSISTANT,
+        KEY_GESTURE_TYPE_LAUNCH_SYSTEM_SETTINGS,
+        KEY_GESTURE_TYPE_TOGGLE_NOTIFICATION_PANEL,
+        KEY_GESTURE_TYPE_TOGGLE_TASKBAR,
+        KEY_GESTURE_TYPE_TAKE_SCREENSHOT,
+        KEY_GESTURE_TYPE_OPEN_SHORTCUT_HELPER,
+        KEY_GESTURE_TYPE_BRIGHTNESS_UP,
+        KEY_GESTURE_TYPE_BRIGHTNESS_DOWN,
+        KEY_GESTURE_TYPE_KEYBOARD_BACKLIGHT_UP,
+        KEY_GESTURE_TYPE_KEYBOARD_BACKLIGHT_DOWN,
+        KEY_GESTURE_TYPE_KEYBOARD_BACKLIGHT_TOGGLE,
+        KEY_GESTURE_TYPE_VOLUME_UP,
+        KEY_GESTURE_TYPE_VOLUME_DOWN,
+        KEY_GESTURE_TYPE_VOLUME_MUTE,
+        KEY_GESTURE_TYPE_ALL_APPS,
+        KEY_GESTURE_TYPE_LAUNCH_SEARCH,
+        KEY_GESTURE_TYPE_LANGUAGE_SWITCH,
+        KEY_GESTURE_TYPE_ACCESSIBILITY_ALL_APPS,
+        KEY_GESTURE_TYPE_TOGGLE_CAPS_LOCK,
+        KEY_GESTURE_TYPE_SYSTEM_MUTE,
+        KEY_GESTURE_TYPE_SPLIT_SCREEN_NAVIGATION,
+        KEY_GESTURE_TYPE_CHANGE_SPLITSCREEN_FOCUS,
+        KEY_GESTURE_TYPE_TRIGGER_BUG_REPORT,
+        KEY_GESTURE_TYPE_LOCK_SCREEN,
+        KEY_GESTURE_TYPE_OPEN_NOTES,
+        KEY_GESTURE_TYPE_TOGGLE_POWER,
+        KEY_GESTURE_TYPE_SYSTEM_NAVIGATION,
+        KEY_GESTURE_TYPE_SLEEP,
+        KEY_GESTURE_TYPE_WAKEUP,
+        KEY_GESTURE_TYPE_MEDIA_KEY,
+        KEY_GESTURE_TYPE_LAUNCH_DEFAULT_BROWSER,
+        KEY_GESTURE_TYPE_LAUNCH_DEFAULT_EMAIL,
+        KEY_GESTURE_TYPE_LAUNCH_DEFAULT_CONTACTS,
+        KEY_GESTURE_TYPE_LAUNCH_DEFAULT_CALENDAR,
+        KEY_GESTURE_TYPE_LAUNCH_DEFAULT_CALCULATOR,
+        KEY_GESTURE_TYPE_LAUNCH_DEFAULT_MUSIC,
+        KEY_GESTURE_TYPE_LAUNCH_DEFAULT_MAPS,
+        KEY_GESTURE_TYPE_LAUNCH_DEFAULT_MESSAGING,
+        KEY_GESTURE_TYPE_LAUNCH_DEFAULT_GALLERY,
+        KEY_GESTURE_TYPE_LAUNCH_DEFAULT_FILES,
+        KEY_GESTURE_TYPE_LAUNCH_DEFAULT_WEATHER,
+        KEY_GESTURE_TYPE_LAUNCH_DEFAULT_FITNESS,
+        KEY_GESTURE_TYPE_LAUNCH_APPLICATION_BY_PACKAGE_NAME,
+        KEY_GESTURE_TYPE_DESKTOP_MODE,
+        KEY_GESTURE_TYPE_MULTI_WINDOW_NAVIGATION
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    @DataClass.Generated.Member
+    public @interface KeyGestureType {}
+
+    @DataClass.Generated.Member
+    public static String keyGestureTypeToString(@KeyGestureType int value) {
+        switch (value) {
+            case KEY_GESTURE_TYPE_UNSPECIFIED:
+                    return "KEY_GESTURE_TYPE_UNSPECIFIED";
+            case KEY_GESTURE_TYPE_HOME:
+                    return "KEY_GESTURE_TYPE_HOME";
+            case KEY_GESTURE_TYPE_RECENT_APPS:
+                    return "KEY_GESTURE_TYPE_RECENT_APPS";
+            case KEY_GESTURE_TYPE_BACK:
+                    return "KEY_GESTURE_TYPE_BACK";
+            case KEY_GESTURE_TYPE_APP_SWITCH:
+                    return "KEY_GESTURE_TYPE_APP_SWITCH";
+            case KEY_GESTURE_TYPE_LAUNCH_ASSISTANT:
+                    return "KEY_GESTURE_TYPE_LAUNCH_ASSISTANT";
+            case KEY_GESTURE_TYPE_LAUNCH_VOICE_ASSISTANT:
+                    return "KEY_GESTURE_TYPE_LAUNCH_VOICE_ASSISTANT";
+            case KEY_GESTURE_TYPE_LAUNCH_SYSTEM_SETTINGS:
+                    return "KEY_GESTURE_TYPE_LAUNCH_SYSTEM_SETTINGS";
+            case KEY_GESTURE_TYPE_TOGGLE_NOTIFICATION_PANEL:
+                    return "KEY_GESTURE_TYPE_TOGGLE_NOTIFICATION_PANEL";
+            case KEY_GESTURE_TYPE_TOGGLE_TASKBAR:
+                    return "KEY_GESTURE_TYPE_TOGGLE_TASKBAR";
+            case KEY_GESTURE_TYPE_TAKE_SCREENSHOT:
+                    return "KEY_GESTURE_TYPE_TAKE_SCREENSHOT";
+            case KEY_GESTURE_TYPE_OPEN_SHORTCUT_HELPER:
+                    return "KEY_GESTURE_TYPE_OPEN_SHORTCUT_HELPER";
+            case KEY_GESTURE_TYPE_BRIGHTNESS_UP:
+                    return "KEY_GESTURE_TYPE_BRIGHTNESS_UP";
+            case KEY_GESTURE_TYPE_BRIGHTNESS_DOWN:
+                    return "KEY_GESTURE_TYPE_BRIGHTNESS_DOWN";
+            case KEY_GESTURE_TYPE_KEYBOARD_BACKLIGHT_UP:
+                    return "KEY_GESTURE_TYPE_KEYBOARD_BACKLIGHT_UP";
+            case KEY_GESTURE_TYPE_KEYBOARD_BACKLIGHT_DOWN:
+                    return "KEY_GESTURE_TYPE_KEYBOARD_BACKLIGHT_DOWN";
+            case KEY_GESTURE_TYPE_KEYBOARD_BACKLIGHT_TOGGLE:
+                    return "KEY_GESTURE_TYPE_KEYBOARD_BACKLIGHT_TOGGLE";
+            case KEY_GESTURE_TYPE_VOLUME_UP:
+                    return "KEY_GESTURE_TYPE_VOLUME_UP";
+            case KEY_GESTURE_TYPE_VOLUME_DOWN:
+                    return "KEY_GESTURE_TYPE_VOLUME_DOWN";
+            case KEY_GESTURE_TYPE_VOLUME_MUTE:
+                    return "KEY_GESTURE_TYPE_VOLUME_MUTE";
+            case KEY_GESTURE_TYPE_ALL_APPS:
+                    return "KEY_GESTURE_TYPE_ALL_APPS";
+            case KEY_GESTURE_TYPE_LAUNCH_SEARCH:
+                    return "KEY_GESTURE_TYPE_LAUNCH_SEARCH";
+            case KEY_GESTURE_TYPE_LANGUAGE_SWITCH:
+                    return "KEY_GESTURE_TYPE_LANGUAGE_SWITCH";
+            case KEY_GESTURE_TYPE_ACCESSIBILITY_ALL_APPS:
+                    return "KEY_GESTURE_TYPE_ACCESSIBILITY_ALL_APPS";
+            case KEY_GESTURE_TYPE_TOGGLE_CAPS_LOCK:
+                    return "KEY_GESTURE_TYPE_TOGGLE_CAPS_LOCK";
+            case KEY_GESTURE_TYPE_SYSTEM_MUTE:
+                    return "KEY_GESTURE_TYPE_SYSTEM_MUTE";
+            case KEY_GESTURE_TYPE_SPLIT_SCREEN_NAVIGATION:
+                    return "KEY_GESTURE_TYPE_SPLIT_SCREEN_NAVIGATION";
+            case KEY_GESTURE_TYPE_CHANGE_SPLITSCREEN_FOCUS:
+                    return "KEY_GESTURE_TYPE_CHANGE_SPLITSCREEN_FOCUS";
+            case KEY_GESTURE_TYPE_TRIGGER_BUG_REPORT:
+                    return "KEY_GESTURE_TYPE_TRIGGER_BUG_REPORT";
+            case KEY_GESTURE_TYPE_LOCK_SCREEN:
+                    return "KEY_GESTURE_TYPE_LOCK_SCREEN";
+            case KEY_GESTURE_TYPE_OPEN_NOTES:
+                    return "KEY_GESTURE_TYPE_OPEN_NOTES";
+            case KEY_GESTURE_TYPE_TOGGLE_POWER:
+                    return "KEY_GESTURE_TYPE_TOGGLE_POWER";
+            case KEY_GESTURE_TYPE_SYSTEM_NAVIGATION:
+                    return "KEY_GESTURE_TYPE_SYSTEM_NAVIGATION";
+            case KEY_GESTURE_TYPE_SLEEP:
+                    return "KEY_GESTURE_TYPE_SLEEP";
+            case KEY_GESTURE_TYPE_WAKEUP:
+                    return "KEY_GESTURE_TYPE_WAKEUP";
+            case KEY_GESTURE_TYPE_MEDIA_KEY:
+                    return "KEY_GESTURE_TYPE_MEDIA_KEY";
+            case KEY_GESTURE_TYPE_LAUNCH_DEFAULT_BROWSER:
+                    return "KEY_GESTURE_TYPE_LAUNCH_DEFAULT_BROWSER";
+            case KEY_GESTURE_TYPE_LAUNCH_DEFAULT_EMAIL:
+                    return "KEY_GESTURE_TYPE_LAUNCH_DEFAULT_EMAIL";
+            case KEY_GESTURE_TYPE_LAUNCH_DEFAULT_CONTACTS:
+                    return "KEY_GESTURE_TYPE_LAUNCH_DEFAULT_CONTACTS";
+            case KEY_GESTURE_TYPE_LAUNCH_DEFAULT_CALENDAR:
+                    return "KEY_GESTURE_TYPE_LAUNCH_DEFAULT_CALENDAR";
+            case KEY_GESTURE_TYPE_LAUNCH_DEFAULT_CALCULATOR:
+                    return "KEY_GESTURE_TYPE_LAUNCH_DEFAULT_CALCULATOR";
+            case KEY_GESTURE_TYPE_LAUNCH_DEFAULT_MUSIC:
+                    return "KEY_GESTURE_TYPE_LAUNCH_DEFAULT_MUSIC";
+            case KEY_GESTURE_TYPE_LAUNCH_DEFAULT_MAPS:
+                    return "KEY_GESTURE_TYPE_LAUNCH_DEFAULT_MAPS";
+            case KEY_GESTURE_TYPE_LAUNCH_DEFAULT_MESSAGING:
+                    return "KEY_GESTURE_TYPE_LAUNCH_DEFAULT_MESSAGING";
+            case KEY_GESTURE_TYPE_LAUNCH_DEFAULT_GALLERY:
+                    return "KEY_GESTURE_TYPE_LAUNCH_DEFAULT_GALLERY";
+            case KEY_GESTURE_TYPE_LAUNCH_DEFAULT_FILES:
+                    return "KEY_GESTURE_TYPE_LAUNCH_DEFAULT_FILES";
+            case KEY_GESTURE_TYPE_LAUNCH_DEFAULT_WEATHER:
+                    return "KEY_GESTURE_TYPE_LAUNCH_DEFAULT_WEATHER";
+            case KEY_GESTURE_TYPE_LAUNCH_DEFAULT_FITNESS:
+                    return "KEY_GESTURE_TYPE_LAUNCH_DEFAULT_FITNESS";
+            case KEY_GESTURE_TYPE_LAUNCH_APPLICATION_BY_PACKAGE_NAME:
+                    return "KEY_GESTURE_TYPE_LAUNCH_APPLICATION_BY_PACKAGE_NAME";
+            case KEY_GESTURE_TYPE_DESKTOP_MODE:
+                    return "KEY_GESTURE_TYPE_DESKTOP_MODE";
+            case KEY_GESTURE_TYPE_MULTI_WINDOW_NAVIGATION:
+                    return "KEY_GESTURE_TYPE_MULTI_WINDOW_NAVIGATION";
+            default: return Integer.toHexString(value);
+        }
+    }
+
+    @DataClass.Generated.Member
+    public KeyGestureEvent(
+            int deviceId,
+            @NonNull int[] keycodes,
+            int modifierState,
+            @KeyGestureType int keyGestureType) {
+        this.mDeviceId = deviceId;
+        this.mKeycodes = keycodes;
+        com.android.internal.util.AnnotationValidations.validate(
+                NonNull.class, null, mKeycodes);
+        this.mModifierState = modifierState;
+        this.mKeyGestureType = keyGestureType;
+
+        if (!(mKeyGestureType == KEY_GESTURE_TYPE_UNSPECIFIED)
+                && !(mKeyGestureType == KEY_GESTURE_TYPE_HOME)
+                && !(mKeyGestureType == KEY_GESTURE_TYPE_RECENT_APPS)
+                && !(mKeyGestureType == KEY_GESTURE_TYPE_BACK)
+                && !(mKeyGestureType == KEY_GESTURE_TYPE_APP_SWITCH)
+                && !(mKeyGestureType == KEY_GESTURE_TYPE_LAUNCH_ASSISTANT)
+                && !(mKeyGestureType == KEY_GESTURE_TYPE_LAUNCH_VOICE_ASSISTANT)
+                && !(mKeyGestureType == KEY_GESTURE_TYPE_LAUNCH_SYSTEM_SETTINGS)
+                && !(mKeyGestureType == KEY_GESTURE_TYPE_TOGGLE_NOTIFICATION_PANEL)
+                && !(mKeyGestureType == KEY_GESTURE_TYPE_TOGGLE_TASKBAR)
+                && !(mKeyGestureType == KEY_GESTURE_TYPE_TAKE_SCREENSHOT)
+                && !(mKeyGestureType == KEY_GESTURE_TYPE_OPEN_SHORTCUT_HELPER)
+                && !(mKeyGestureType == KEY_GESTURE_TYPE_BRIGHTNESS_UP)
+                && !(mKeyGestureType == KEY_GESTURE_TYPE_BRIGHTNESS_DOWN)
+                && !(mKeyGestureType == KEY_GESTURE_TYPE_KEYBOARD_BACKLIGHT_UP)
+                && !(mKeyGestureType == KEY_GESTURE_TYPE_KEYBOARD_BACKLIGHT_DOWN)
+                && !(mKeyGestureType == KEY_GESTURE_TYPE_KEYBOARD_BACKLIGHT_TOGGLE)
+                && !(mKeyGestureType == KEY_GESTURE_TYPE_VOLUME_UP)
+                && !(mKeyGestureType == KEY_GESTURE_TYPE_VOLUME_DOWN)
+                && !(mKeyGestureType == KEY_GESTURE_TYPE_VOLUME_MUTE)
+                && !(mKeyGestureType == KEY_GESTURE_TYPE_ALL_APPS)
+                && !(mKeyGestureType == KEY_GESTURE_TYPE_LAUNCH_SEARCH)
+                && !(mKeyGestureType == KEY_GESTURE_TYPE_LANGUAGE_SWITCH)
+                && !(mKeyGestureType == KEY_GESTURE_TYPE_ACCESSIBILITY_ALL_APPS)
+                && !(mKeyGestureType == KEY_GESTURE_TYPE_TOGGLE_CAPS_LOCK)
+                && !(mKeyGestureType == KEY_GESTURE_TYPE_SYSTEM_MUTE)
+                && !(mKeyGestureType == KEY_GESTURE_TYPE_SPLIT_SCREEN_NAVIGATION)
+                && !(mKeyGestureType == KEY_GESTURE_TYPE_CHANGE_SPLITSCREEN_FOCUS)
+                && !(mKeyGestureType == KEY_GESTURE_TYPE_TRIGGER_BUG_REPORT)
+                && !(mKeyGestureType == KEY_GESTURE_TYPE_LOCK_SCREEN)
+                && !(mKeyGestureType == KEY_GESTURE_TYPE_OPEN_NOTES)
+                && !(mKeyGestureType == KEY_GESTURE_TYPE_TOGGLE_POWER)
+                && !(mKeyGestureType == KEY_GESTURE_TYPE_SYSTEM_NAVIGATION)
+                && !(mKeyGestureType == KEY_GESTURE_TYPE_SLEEP)
+                && !(mKeyGestureType == KEY_GESTURE_TYPE_WAKEUP)
+                && !(mKeyGestureType == KEY_GESTURE_TYPE_MEDIA_KEY)
+                && !(mKeyGestureType == KEY_GESTURE_TYPE_LAUNCH_DEFAULT_BROWSER)
+                && !(mKeyGestureType == KEY_GESTURE_TYPE_LAUNCH_DEFAULT_EMAIL)
+                && !(mKeyGestureType == KEY_GESTURE_TYPE_LAUNCH_DEFAULT_CONTACTS)
+                && !(mKeyGestureType == KEY_GESTURE_TYPE_LAUNCH_DEFAULT_CALENDAR)
+                && !(mKeyGestureType == KEY_GESTURE_TYPE_LAUNCH_DEFAULT_CALCULATOR)
+                && !(mKeyGestureType == KEY_GESTURE_TYPE_LAUNCH_DEFAULT_MUSIC)
+                && !(mKeyGestureType == KEY_GESTURE_TYPE_LAUNCH_DEFAULT_MAPS)
+                && !(mKeyGestureType == KEY_GESTURE_TYPE_LAUNCH_DEFAULT_MESSAGING)
+                && !(mKeyGestureType == KEY_GESTURE_TYPE_LAUNCH_DEFAULT_GALLERY)
+                && !(mKeyGestureType == KEY_GESTURE_TYPE_LAUNCH_DEFAULT_FILES)
+                && !(mKeyGestureType == KEY_GESTURE_TYPE_LAUNCH_DEFAULT_WEATHER)
+                && !(mKeyGestureType == KEY_GESTURE_TYPE_LAUNCH_DEFAULT_FITNESS)
+                && !(mKeyGestureType == KEY_GESTURE_TYPE_LAUNCH_APPLICATION_BY_PACKAGE_NAME)
+                && !(mKeyGestureType == KEY_GESTURE_TYPE_DESKTOP_MODE)
+                && !(mKeyGestureType == KEY_GESTURE_TYPE_MULTI_WINDOW_NAVIGATION)) {
+            throw new java.lang.IllegalArgumentException(
+                    "keyGestureType was " + mKeyGestureType + " but must be one of: "
+                            + "KEY_GESTURE_TYPE_UNSPECIFIED(" + KEY_GESTURE_TYPE_UNSPECIFIED + "), "
+                            + "KEY_GESTURE_TYPE_HOME(" + KEY_GESTURE_TYPE_HOME + "), "
+                            + "KEY_GESTURE_TYPE_RECENT_APPS(" + KEY_GESTURE_TYPE_RECENT_APPS + "), "
+                            + "KEY_GESTURE_TYPE_BACK(" + KEY_GESTURE_TYPE_BACK + "), "
+                            + "KEY_GESTURE_TYPE_APP_SWITCH(" + KEY_GESTURE_TYPE_APP_SWITCH + "), "
+                            + "KEY_GESTURE_TYPE_LAUNCH_ASSISTANT(" + KEY_GESTURE_TYPE_LAUNCH_ASSISTANT + "), "
+                            + "KEY_GESTURE_TYPE_LAUNCH_VOICE_ASSISTANT(" + KEY_GESTURE_TYPE_LAUNCH_VOICE_ASSISTANT + "), "
+                            + "KEY_GESTURE_TYPE_LAUNCH_SYSTEM_SETTINGS(" + KEY_GESTURE_TYPE_LAUNCH_SYSTEM_SETTINGS + "), "
+                            + "KEY_GESTURE_TYPE_TOGGLE_NOTIFICATION_PANEL(" + KEY_GESTURE_TYPE_TOGGLE_NOTIFICATION_PANEL + "), "
+                            + "KEY_GESTURE_TYPE_TOGGLE_TASKBAR(" + KEY_GESTURE_TYPE_TOGGLE_TASKBAR + "), "
+                            + "KEY_GESTURE_TYPE_TAKE_SCREENSHOT(" + KEY_GESTURE_TYPE_TAKE_SCREENSHOT + "), "
+                            + "KEY_GESTURE_TYPE_OPEN_SHORTCUT_HELPER(" + KEY_GESTURE_TYPE_OPEN_SHORTCUT_HELPER + "), "
+                            + "KEY_GESTURE_TYPE_BRIGHTNESS_UP(" + KEY_GESTURE_TYPE_BRIGHTNESS_UP + "), "
+                            + "KEY_GESTURE_TYPE_BRIGHTNESS_DOWN(" + KEY_GESTURE_TYPE_BRIGHTNESS_DOWN + "), "
+                            + "KEY_GESTURE_TYPE_KEYBOARD_BACKLIGHT_UP(" + KEY_GESTURE_TYPE_KEYBOARD_BACKLIGHT_UP + "), "
+                            + "KEY_GESTURE_TYPE_KEYBOARD_BACKLIGHT_DOWN(" + KEY_GESTURE_TYPE_KEYBOARD_BACKLIGHT_DOWN + "), "
+                            + "KEY_GESTURE_TYPE_KEYBOARD_BACKLIGHT_TOGGLE(" + KEY_GESTURE_TYPE_KEYBOARD_BACKLIGHT_TOGGLE + "), "
+                            + "KEY_GESTURE_TYPE_VOLUME_UP(" + KEY_GESTURE_TYPE_VOLUME_UP + "), "
+                            + "KEY_GESTURE_TYPE_VOLUME_DOWN(" + KEY_GESTURE_TYPE_VOLUME_DOWN + "), "
+                            + "KEY_GESTURE_TYPE_VOLUME_MUTE(" + KEY_GESTURE_TYPE_VOLUME_MUTE + "), "
+                            + "KEY_GESTURE_TYPE_ALL_APPS(" + KEY_GESTURE_TYPE_ALL_APPS + "), "
+                            + "KEY_GESTURE_TYPE_LAUNCH_SEARCH(" + KEY_GESTURE_TYPE_LAUNCH_SEARCH + "), "
+                            + "KEY_GESTURE_TYPE_LANGUAGE_SWITCH(" + KEY_GESTURE_TYPE_LANGUAGE_SWITCH + "), "
+                            + "KEY_GESTURE_TYPE_ACCESSIBILITY_ALL_APPS(" + KEY_GESTURE_TYPE_ACCESSIBILITY_ALL_APPS + "), "
+                            + "KEY_GESTURE_TYPE_TOGGLE_CAPS_LOCK(" + KEY_GESTURE_TYPE_TOGGLE_CAPS_LOCK + "), "
+                            + "KEY_GESTURE_TYPE_SYSTEM_MUTE(" + KEY_GESTURE_TYPE_SYSTEM_MUTE + "), "
+                            + "KEY_GESTURE_TYPE_SPLIT_SCREEN_NAVIGATION(" + KEY_GESTURE_TYPE_SPLIT_SCREEN_NAVIGATION + "), "
+                            + "KEY_GESTURE_TYPE_CHANGE_SPLITSCREEN_FOCUS(" + KEY_GESTURE_TYPE_CHANGE_SPLITSCREEN_FOCUS + "), "
+                            + "KEY_GESTURE_TYPE_TRIGGER_BUG_REPORT(" + KEY_GESTURE_TYPE_TRIGGER_BUG_REPORT + "), "
+                            + "KEY_GESTURE_TYPE_LOCK_SCREEN(" + KEY_GESTURE_TYPE_LOCK_SCREEN + "), "
+                            + "KEY_GESTURE_TYPE_OPEN_NOTES(" + KEY_GESTURE_TYPE_OPEN_NOTES + "), "
+                            + "KEY_GESTURE_TYPE_TOGGLE_POWER(" + KEY_GESTURE_TYPE_TOGGLE_POWER + "), "
+                            + "KEY_GESTURE_TYPE_SYSTEM_NAVIGATION(" + KEY_GESTURE_TYPE_SYSTEM_NAVIGATION + "), "
+                            + "KEY_GESTURE_TYPE_SLEEP(" + KEY_GESTURE_TYPE_SLEEP + "), "
+                            + "KEY_GESTURE_TYPE_WAKEUP(" + KEY_GESTURE_TYPE_WAKEUP + "), "
+                            + "KEY_GESTURE_TYPE_MEDIA_KEY(" + KEY_GESTURE_TYPE_MEDIA_KEY + "), "
+                            + "KEY_GESTURE_TYPE_LAUNCH_DEFAULT_BROWSER(" + KEY_GESTURE_TYPE_LAUNCH_DEFAULT_BROWSER + "), "
+                            + "KEY_GESTURE_TYPE_LAUNCH_DEFAULT_EMAIL(" + KEY_GESTURE_TYPE_LAUNCH_DEFAULT_EMAIL + "), "
+                            + "KEY_GESTURE_TYPE_LAUNCH_DEFAULT_CONTACTS(" + KEY_GESTURE_TYPE_LAUNCH_DEFAULT_CONTACTS + "), "
+                            + "KEY_GESTURE_TYPE_LAUNCH_DEFAULT_CALENDAR(" + KEY_GESTURE_TYPE_LAUNCH_DEFAULT_CALENDAR + "), "
+                            + "KEY_GESTURE_TYPE_LAUNCH_DEFAULT_CALCULATOR(" + KEY_GESTURE_TYPE_LAUNCH_DEFAULT_CALCULATOR + "), "
+                            + "KEY_GESTURE_TYPE_LAUNCH_DEFAULT_MUSIC(" + KEY_GESTURE_TYPE_LAUNCH_DEFAULT_MUSIC + "), "
+                            + "KEY_GESTURE_TYPE_LAUNCH_DEFAULT_MAPS(" + KEY_GESTURE_TYPE_LAUNCH_DEFAULT_MAPS + "), "
+                            + "KEY_GESTURE_TYPE_LAUNCH_DEFAULT_MESSAGING(" + KEY_GESTURE_TYPE_LAUNCH_DEFAULT_MESSAGING + "), "
+                            + "KEY_GESTURE_TYPE_LAUNCH_DEFAULT_GALLERY(" + KEY_GESTURE_TYPE_LAUNCH_DEFAULT_GALLERY + "), "
+                            + "KEY_GESTURE_TYPE_LAUNCH_DEFAULT_FILES(" + KEY_GESTURE_TYPE_LAUNCH_DEFAULT_FILES + "), "
+                            + "KEY_GESTURE_TYPE_LAUNCH_DEFAULT_WEATHER(" + KEY_GESTURE_TYPE_LAUNCH_DEFAULT_WEATHER + "), "
+                            + "KEY_GESTURE_TYPE_LAUNCH_DEFAULT_FITNESS(" + KEY_GESTURE_TYPE_LAUNCH_DEFAULT_FITNESS + "), "
+                            + "KEY_GESTURE_TYPE_LAUNCH_APPLICATION_BY_PACKAGE_NAME(" + KEY_GESTURE_TYPE_LAUNCH_APPLICATION_BY_PACKAGE_NAME + "), "
+                            + "KEY_GESTURE_TYPE_DESKTOP_MODE(" + KEY_GESTURE_TYPE_DESKTOP_MODE + "), "
+                            + "KEY_GESTURE_TYPE_MULTI_WINDOW_NAVIGATION(" + KEY_GESTURE_TYPE_MULTI_WINDOW_NAVIGATION + ")");
+        }
+
+
+        // onConstructed(); // You can define this method to get a callback
+    }
+
+    @DataClass.Generated.Member
+    public int getDeviceId() {
+        return mDeviceId;
+    }
+
+    @DataClass.Generated.Member
+    public @NonNull int[] getKeycodes() {
+        return mKeycodes;
+    }
+
+    @DataClass.Generated.Member
+    public int getModifierState() {
+        return mModifierState;
+    }
+
+    @DataClass.Generated.Member
+    public @KeyGestureType int getKeyGestureType() {
+        return mKeyGestureType;
+    }
+
+    @Override
+    @DataClass.Generated.Member
+    public String toString() {
+        // You can override field toString logic by defining methods like:
+        // String fieldNameToString() { ... }
+
+        return "KeyGestureEvent { " +
+                "deviceId = " + mDeviceId + ", " +
+                "keycodes = " + java.util.Arrays.toString(mKeycodes) + ", " +
+                "modifierState = " + mModifierState + ", " +
+                "keyGestureType = " + keyGestureTypeToString(mKeyGestureType) +
+        " }";
+    }
+
+    @Override
+    @DataClass.Generated.Member
+    public boolean equals(@Nullable Object o) {
+        // You can override field equality logic by defining either of the methods like:
+        // boolean fieldNameEquals(KeyGestureEvent other) { ... }
+        // boolean fieldNameEquals(FieldType otherValue) { ... }
+
+        if (this == o) return true;
+        if (o == null || getClass() != o.getClass()) return false;
+        @SuppressWarnings("unchecked")
+        KeyGestureEvent that = (KeyGestureEvent) o;
+        //noinspection PointlessBooleanExpression
+        return true
+                && mDeviceId == that.mDeviceId
+                && java.util.Arrays.equals(mKeycodes, that.mKeycodes)
+                && mModifierState == that.mModifierState
+                && mKeyGestureType == that.mKeyGestureType;
+    }
+
+    @Override
+    @DataClass.Generated.Member
+    public int hashCode() {
+        // You can override field hashCode logic by defining methods like:
+        // int fieldNameHashCode() { ... }
+
+        int _hash = 1;
+        _hash = 31 * _hash + mDeviceId;
+        _hash = 31 * _hash + java.util.Arrays.hashCode(mKeycodes);
+        _hash = 31 * _hash + mModifierState;
+        _hash = 31 * _hash + mKeyGestureType;
+        return _hash;
+    }
+
+    @DataClass.Generated(
+            time = 1723409092192L,
+            codegenVersion = "1.0.23",
+            sourceFile = "frameworks/base/core/java/android/hardware/input/KeyGestureEvent.java",
+            inputSignatures = "private final  int mDeviceId\nprivate final @android.annotation.NonNull int[] mKeycodes\nprivate final  int mModifierState\nprivate final @android.hardware.input.KeyGestureEvent.KeyGestureType int mKeyGestureType\npublic static final  int KEY_GESTURE_TYPE_UNSPECIFIED\npublic static final  int KEY_GESTURE_TYPE_HOME\npublic static final  int KEY_GESTURE_TYPE_RECENT_APPS\npublic static final  int KEY_GESTURE_TYPE_BACK\npublic static final  int KEY_GESTURE_TYPE_APP_SWITCH\npublic static final  int KEY_GESTURE_TYPE_LAUNCH_ASSISTANT\npublic static final  int KEY_GESTURE_TYPE_LAUNCH_VOICE_ASSISTANT\npublic static final  int KEY_GESTURE_TYPE_LAUNCH_SYSTEM_SETTINGS\npublic static final  int KEY_GESTURE_TYPE_TOGGLE_NOTIFICATION_PANEL\npublic static final  int KEY_GESTURE_TYPE_TOGGLE_TASKBAR\npublic static final  int KEY_GESTURE_TYPE_TAKE_SCREENSHOT\npublic static final  int KEY_GESTURE_TYPE_OPEN_SHORTCUT_HELPER\npublic static final  int KEY_GESTURE_TYPE_BRIGHTNESS_UP\npublic static final  int KEY_GESTURE_TYPE_BRIGHTNESS_DOWN\npublic static final  int KEY_GESTURE_TYPE_KEYBOARD_BACKLIGHT_UP\npublic static final  int KEY_GESTURE_TYPE_KEYBOARD_BACKLIGHT_DOWN\npublic static final  int KEY_GESTURE_TYPE_KEYBOARD_BACKLIGHT_TOGGLE\npublic static final  int KEY_GESTURE_TYPE_VOLUME_UP\npublic static final  int KEY_GESTURE_TYPE_VOLUME_DOWN\npublic static final  int KEY_GESTURE_TYPE_VOLUME_MUTE\npublic static final  int KEY_GESTURE_TYPE_ALL_APPS\npublic static final  int KEY_GESTURE_TYPE_LAUNCH_SEARCH\npublic static final  int KEY_GESTURE_TYPE_LANGUAGE_SWITCH\npublic static final  int KEY_GESTURE_TYPE_ACCESSIBILITY_ALL_APPS\npublic static final  int KEY_GESTURE_TYPE_TOGGLE_CAPS_LOCK\npublic static final  int KEY_GESTURE_TYPE_SYSTEM_MUTE\npublic static final  int KEY_GESTURE_TYPE_SPLIT_SCREEN_NAVIGATION\npublic static final  int KEY_GESTURE_TYPE_CHANGE_SPLITSCREEN_FOCUS\npublic static final  int KEY_GESTURE_TYPE_TRIGGER_BUG_REPORT\npublic static final  int KEY_GESTURE_TYPE_LOCK_SCREEN\npublic static final  int KEY_GESTURE_TYPE_OPEN_NOTES\npublic static final  int KEY_GESTURE_TYPE_TOGGLE_POWER\npublic static final  int KEY_GESTURE_TYPE_SYSTEM_NAVIGATION\npublic static final  int KEY_GESTURE_TYPE_SLEEP\npublic static final  int KEY_GESTURE_TYPE_WAKEUP\npublic static final  int KEY_GESTURE_TYPE_MEDIA_KEY\npublic static final  int KEY_GESTURE_TYPE_LAUNCH_DEFAULT_BROWSER\npublic static final  int KEY_GESTURE_TYPE_LAUNCH_DEFAULT_EMAIL\npublic static final  int KEY_GESTURE_TYPE_LAUNCH_DEFAULT_CONTACTS\npublic static final  int KEY_GESTURE_TYPE_LAUNCH_DEFAULT_CALENDAR\npublic static final  int KEY_GESTURE_TYPE_LAUNCH_DEFAULT_CALCULATOR\npublic static final  int KEY_GESTURE_TYPE_LAUNCH_DEFAULT_MUSIC\npublic static final  int KEY_GESTURE_TYPE_LAUNCH_DEFAULT_MAPS\npublic static final  int KEY_GESTURE_TYPE_LAUNCH_DEFAULT_MESSAGING\npublic static final  int KEY_GESTURE_TYPE_LAUNCH_DEFAULT_GALLERY\npublic static final  int KEY_GESTURE_TYPE_LAUNCH_DEFAULT_FILES\npublic static final  int KEY_GESTURE_TYPE_LAUNCH_DEFAULT_WEATHER\npublic static final  int KEY_GESTURE_TYPE_LAUNCH_DEFAULT_FITNESS\npublic static final  int KEY_GESTURE_TYPE_LAUNCH_APPLICATION_BY_PACKAGE_NAME\npublic static final  int KEY_GESTURE_TYPE_DESKTOP_MODE\npublic static final  int KEY_GESTURE_TYPE_MULTI_WINDOW_NAVIGATION\nclass KeyGestureEvent extends java.lang.Object implements []\n@com.android.internal.util.DataClass(genToString=true, genEqualsHashCode=true)")
+    @Deprecated
+    private void __metadata() {}
+
+
+    //@formatter:on
+    // End of generated code
+
+}
diff --git a/core/java/android/hardware/input/KeyboardSystemShortcut.java b/core/java/android/hardware/input/KeyboardSystemShortcut.java
deleted file mode 100644
index 89cf877..0000000
--- a/core/java/android/hardware/input/KeyboardSystemShortcut.java
+++ /dev/null
@@ -1,522 +0,0 @@
-/*
- * Copyright 2024 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.hardware.input;
-
-import android.annotation.IntDef;
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-
-import com.android.internal.util.DataClass;
-import com.android.internal.util.FrameworkStatsLog;
-
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-
-/**
- * Provides information about the keyboard shortcut being triggered by an external keyboard.
- *
- * @hide
- */
-@DataClass(genToString = true, genEqualsHashCode = true)
-public class KeyboardSystemShortcut {
-
-    private static final String TAG = "KeyboardSystemShortcut";
-
-    @NonNull
-    private final int[] mKeycodes;
-    private final int mModifierState;
-    @SystemShortcut
-    private final int mSystemShortcut;
-
-
-    public static final int SYSTEM_SHORTCUT_UNSPECIFIED =
-            FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__UNSPECIFIED;
-    public static final int SYSTEM_SHORTCUT_HOME =
-            FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__HOME;
-    public static final int SYSTEM_SHORTCUT_RECENT_APPS =
-            FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__RECENT_APPS;
-    public static final int SYSTEM_SHORTCUT_BACK =
-            FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__BACK;
-    public static final int SYSTEM_SHORTCUT_APP_SWITCH =
-            FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__APP_SWITCH;
-    public static final int SYSTEM_SHORTCUT_LAUNCH_ASSISTANT =
-            FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__LAUNCH_ASSISTANT;
-    public static final int SYSTEM_SHORTCUT_LAUNCH_VOICE_ASSISTANT =
-            FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__LAUNCH_VOICE_ASSISTANT;
-    public static final int SYSTEM_SHORTCUT_LAUNCH_SYSTEM_SETTINGS =
-            FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__LAUNCH_SYSTEM_SETTINGS;
-    public static final int SYSTEM_SHORTCUT_TOGGLE_NOTIFICATION_PANEL =
-            FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__TOGGLE_NOTIFICATION_PANEL;
-    public static final int SYSTEM_SHORTCUT_TOGGLE_TASKBAR =
-            FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__TOGGLE_TASKBAR;
-    public static final int SYSTEM_SHORTCUT_TAKE_SCREENSHOT =
-            FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__TAKE_SCREENSHOT;
-    public static final int SYSTEM_SHORTCUT_OPEN_SHORTCUT_HELPER =
-            FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__OPEN_SHORTCUT_HELPER;
-    public static final int SYSTEM_SHORTCUT_BRIGHTNESS_UP =
-            FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__BRIGHTNESS_UP;
-    public static final int SYSTEM_SHORTCUT_BRIGHTNESS_DOWN =
-            FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__BRIGHTNESS_DOWN;
-    public static final int SYSTEM_SHORTCUT_KEYBOARD_BACKLIGHT_UP =
-            FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__KEYBOARD_BACKLIGHT_UP;
-    public static final int SYSTEM_SHORTCUT_KEYBOARD_BACKLIGHT_DOWN =
-            FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__KEYBOARD_BACKLIGHT_DOWN;
-    public static final int SYSTEM_SHORTCUT_KEYBOARD_BACKLIGHT_TOGGLE =
-            FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__KEYBOARD_BACKLIGHT_TOGGLE;
-    public static final int SYSTEM_SHORTCUT_VOLUME_UP =
-            FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__VOLUME_UP;
-    public static final int SYSTEM_SHORTCUT_VOLUME_DOWN =
-            FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__VOLUME_DOWN;
-    public static final int SYSTEM_SHORTCUT_VOLUME_MUTE =
-            FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__VOLUME_MUTE;
-    public static final int SYSTEM_SHORTCUT_ALL_APPS =
-            FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__ALL_APPS;
-    public static final int SYSTEM_SHORTCUT_LAUNCH_SEARCH =
-            FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__LAUNCH_SEARCH;
-    public static final int SYSTEM_SHORTCUT_LANGUAGE_SWITCH =
-            FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__LANGUAGE_SWITCH;
-    public static final int SYSTEM_SHORTCUT_ACCESSIBILITY_ALL_APPS =
-            FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__ACCESSIBILITY_ALL_APPS;
-    public static final int SYSTEM_SHORTCUT_TOGGLE_CAPS_LOCK =
-            FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__TOGGLE_CAPS_LOCK;
-    public static final int SYSTEM_SHORTCUT_SYSTEM_MUTE =
-            FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__SYSTEM_MUTE;
-    public static final int SYSTEM_SHORTCUT_SPLIT_SCREEN_NAVIGATION =
-            FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__SPLIT_SCREEN_NAVIGATION;
-    public static final int SYSTEM_SHORTCUT_CHANGE_SPLITSCREEN_FOCUS =
-            FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__CHANGE_SPLITSCREEN_FOCUS;
-    public static final int SYSTEM_SHORTCUT_TRIGGER_BUG_REPORT =
-            FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__TRIGGER_BUG_REPORT;
-    public static final int SYSTEM_SHORTCUT_LOCK_SCREEN =
-            FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__LOCK_SCREEN;
-    public static final int SYSTEM_SHORTCUT_OPEN_NOTES =
-            FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__OPEN_NOTES;
-    public static final int SYSTEM_SHORTCUT_TOGGLE_POWER =
-            FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__TOGGLE_POWER;
-    public static final int SYSTEM_SHORTCUT_SYSTEM_NAVIGATION =
-            FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__SYSTEM_NAVIGATION;
-    public static final int SYSTEM_SHORTCUT_SLEEP =
-            FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__SLEEP;
-    public static final int SYSTEM_SHORTCUT_WAKEUP =
-            FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__WAKEUP;
-    public static final int SYSTEM_SHORTCUT_MEDIA_KEY =
-            FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__MEDIA_KEY;
-    public static final int SYSTEM_SHORTCUT_LAUNCH_DEFAULT_BROWSER =
-            FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__LAUNCH_DEFAULT_BROWSER;
-    public static final int SYSTEM_SHORTCUT_LAUNCH_DEFAULT_EMAIL =
-            FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__LAUNCH_DEFAULT_EMAIL;
-    public static final int SYSTEM_SHORTCUT_LAUNCH_DEFAULT_CONTACTS =
-            FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__LAUNCH_DEFAULT_CONTACTS;
-    public static final int SYSTEM_SHORTCUT_LAUNCH_DEFAULT_CALENDAR =
-            FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__LAUNCH_DEFAULT_CALENDAR;
-    public static final int SYSTEM_SHORTCUT_LAUNCH_DEFAULT_CALCULATOR =
-            FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__LAUNCH_DEFAULT_CALCULATOR;
-    public static final int SYSTEM_SHORTCUT_LAUNCH_DEFAULT_MUSIC =
-            FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__LAUNCH_DEFAULT_MUSIC;
-    public static final int SYSTEM_SHORTCUT_LAUNCH_DEFAULT_MAPS =
-            FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__LAUNCH_DEFAULT_MAPS;
-    public static final int SYSTEM_SHORTCUT_LAUNCH_DEFAULT_MESSAGING =
-            FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__LAUNCH_DEFAULT_MESSAGING;
-    public static final int SYSTEM_SHORTCUT_LAUNCH_DEFAULT_GALLERY =
-            FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__LAUNCH_DEFAULT_GALLERY;
-    public static final int SYSTEM_SHORTCUT_LAUNCH_DEFAULT_FILES =
-            FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__LAUNCH_DEFAULT_FILES;
-    public static final int SYSTEM_SHORTCUT_LAUNCH_DEFAULT_WEATHER =
-            FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__LAUNCH_DEFAULT_WEATHER;
-    public static final int SYSTEM_SHORTCUT_LAUNCH_DEFAULT_FITNESS =
-            FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__LAUNCH_DEFAULT_FITNESS;
-    public static final int SYSTEM_SHORTCUT_LAUNCH_APPLICATION_BY_PACKAGE_NAME =
-            FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__LAUNCH_APPLICATION_BY_PACKAGE_NAME;
-    public static final int SYSTEM_SHORTCUT_DESKTOP_MODE =
-            FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__DESKTOP_MODE;
-    public static final int SYSTEM_SHORTCUT_MULTI_WINDOW_NAVIGATION =
-            FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED__KEYBOARD_SYSTEM_EVENT__MULTI_WINDOW_NAVIGATION;
-
-
-
-    // Code below generated by codegen v1.0.23.
-    //
-    // DO NOT MODIFY!
-    // CHECKSTYLE:OFF Generated code
-    //
-    // To regenerate run:
-    // $ codegen $ANDROID_BUILD_TOP/frameworks/base/core/java/android/hardware/input/KeyboardSystemShortcut.java
-    //
-    // To exclude the generated code from IntelliJ auto-formatting enable (one-time):
-    //   Settings > Editor > Code Style > Formatter Control
-    //@formatter:off
-
-
-    @IntDef(prefix = "SYSTEM_SHORTCUT_", value = {
-        SYSTEM_SHORTCUT_UNSPECIFIED,
-        SYSTEM_SHORTCUT_HOME,
-        SYSTEM_SHORTCUT_RECENT_APPS,
-        SYSTEM_SHORTCUT_BACK,
-        SYSTEM_SHORTCUT_APP_SWITCH,
-        SYSTEM_SHORTCUT_LAUNCH_ASSISTANT,
-        SYSTEM_SHORTCUT_LAUNCH_VOICE_ASSISTANT,
-        SYSTEM_SHORTCUT_LAUNCH_SYSTEM_SETTINGS,
-        SYSTEM_SHORTCUT_TOGGLE_NOTIFICATION_PANEL,
-        SYSTEM_SHORTCUT_TOGGLE_TASKBAR,
-        SYSTEM_SHORTCUT_TAKE_SCREENSHOT,
-        SYSTEM_SHORTCUT_OPEN_SHORTCUT_HELPER,
-        SYSTEM_SHORTCUT_BRIGHTNESS_UP,
-        SYSTEM_SHORTCUT_BRIGHTNESS_DOWN,
-        SYSTEM_SHORTCUT_KEYBOARD_BACKLIGHT_UP,
-        SYSTEM_SHORTCUT_KEYBOARD_BACKLIGHT_DOWN,
-        SYSTEM_SHORTCUT_KEYBOARD_BACKLIGHT_TOGGLE,
-        SYSTEM_SHORTCUT_VOLUME_UP,
-        SYSTEM_SHORTCUT_VOLUME_DOWN,
-        SYSTEM_SHORTCUT_VOLUME_MUTE,
-        SYSTEM_SHORTCUT_ALL_APPS,
-        SYSTEM_SHORTCUT_LAUNCH_SEARCH,
-        SYSTEM_SHORTCUT_LANGUAGE_SWITCH,
-        SYSTEM_SHORTCUT_ACCESSIBILITY_ALL_APPS,
-        SYSTEM_SHORTCUT_TOGGLE_CAPS_LOCK,
-        SYSTEM_SHORTCUT_SYSTEM_MUTE,
-        SYSTEM_SHORTCUT_SPLIT_SCREEN_NAVIGATION,
-        SYSTEM_SHORTCUT_CHANGE_SPLITSCREEN_FOCUS,
-        SYSTEM_SHORTCUT_TRIGGER_BUG_REPORT,
-        SYSTEM_SHORTCUT_LOCK_SCREEN,
-        SYSTEM_SHORTCUT_OPEN_NOTES,
-        SYSTEM_SHORTCUT_TOGGLE_POWER,
-        SYSTEM_SHORTCUT_SYSTEM_NAVIGATION,
-        SYSTEM_SHORTCUT_SLEEP,
-        SYSTEM_SHORTCUT_WAKEUP,
-        SYSTEM_SHORTCUT_MEDIA_KEY,
-        SYSTEM_SHORTCUT_LAUNCH_DEFAULT_BROWSER,
-        SYSTEM_SHORTCUT_LAUNCH_DEFAULT_EMAIL,
-        SYSTEM_SHORTCUT_LAUNCH_DEFAULT_CONTACTS,
-        SYSTEM_SHORTCUT_LAUNCH_DEFAULT_CALENDAR,
-        SYSTEM_SHORTCUT_LAUNCH_DEFAULT_CALCULATOR,
-        SYSTEM_SHORTCUT_LAUNCH_DEFAULT_MUSIC,
-        SYSTEM_SHORTCUT_LAUNCH_DEFAULT_MAPS,
-        SYSTEM_SHORTCUT_LAUNCH_DEFAULT_MESSAGING,
-        SYSTEM_SHORTCUT_LAUNCH_DEFAULT_GALLERY,
-        SYSTEM_SHORTCUT_LAUNCH_DEFAULT_FILES,
-        SYSTEM_SHORTCUT_LAUNCH_DEFAULT_WEATHER,
-        SYSTEM_SHORTCUT_LAUNCH_DEFAULT_FITNESS,
-        SYSTEM_SHORTCUT_LAUNCH_APPLICATION_BY_PACKAGE_NAME,
-        SYSTEM_SHORTCUT_DESKTOP_MODE,
-        SYSTEM_SHORTCUT_MULTI_WINDOW_NAVIGATION
-    })
-    @Retention(RetentionPolicy.SOURCE)
-    @DataClass.Generated.Member
-    public @interface SystemShortcut {}
-
-    @DataClass.Generated.Member
-    public static String systemShortcutToString(@SystemShortcut int value) {
-        switch (value) {
-            case SYSTEM_SHORTCUT_UNSPECIFIED:
-                    return "SYSTEM_SHORTCUT_UNSPECIFIED";
-            case SYSTEM_SHORTCUT_HOME:
-                    return "SYSTEM_SHORTCUT_HOME";
-            case SYSTEM_SHORTCUT_RECENT_APPS:
-                    return "SYSTEM_SHORTCUT_RECENT_APPS";
-            case SYSTEM_SHORTCUT_BACK:
-                    return "SYSTEM_SHORTCUT_BACK";
-            case SYSTEM_SHORTCUT_APP_SWITCH:
-                    return "SYSTEM_SHORTCUT_APP_SWITCH";
-            case SYSTEM_SHORTCUT_LAUNCH_ASSISTANT:
-                    return "SYSTEM_SHORTCUT_LAUNCH_ASSISTANT";
-            case SYSTEM_SHORTCUT_LAUNCH_VOICE_ASSISTANT:
-                    return "SYSTEM_SHORTCUT_LAUNCH_VOICE_ASSISTANT";
-            case SYSTEM_SHORTCUT_LAUNCH_SYSTEM_SETTINGS:
-                    return "SYSTEM_SHORTCUT_LAUNCH_SYSTEM_SETTINGS";
-            case SYSTEM_SHORTCUT_TOGGLE_NOTIFICATION_PANEL:
-                    return "SYSTEM_SHORTCUT_TOGGLE_NOTIFICATION_PANEL";
-            case SYSTEM_SHORTCUT_TOGGLE_TASKBAR:
-                    return "SYSTEM_SHORTCUT_TOGGLE_TASKBAR";
-            case SYSTEM_SHORTCUT_TAKE_SCREENSHOT:
-                    return "SYSTEM_SHORTCUT_TAKE_SCREENSHOT";
-            case SYSTEM_SHORTCUT_OPEN_SHORTCUT_HELPER:
-                    return "SYSTEM_SHORTCUT_OPEN_SHORTCUT_HELPER";
-            case SYSTEM_SHORTCUT_BRIGHTNESS_UP:
-                    return "SYSTEM_SHORTCUT_BRIGHTNESS_UP";
-            case SYSTEM_SHORTCUT_BRIGHTNESS_DOWN:
-                    return "SYSTEM_SHORTCUT_BRIGHTNESS_DOWN";
-            case SYSTEM_SHORTCUT_KEYBOARD_BACKLIGHT_UP:
-                    return "SYSTEM_SHORTCUT_KEYBOARD_BACKLIGHT_UP";
-            case SYSTEM_SHORTCUT_KEYBOARD_BACKLIGHT_DOWN:
-                    return "SYSTEM_SHORTCUT_KEYBOARD_BACKLIGHT_DOWN";
-            case SYSTEM_SHORTCUT_KEYBOARD_BACKLIGHT_TOGGLE:
-                    return "SYSTEM_SHORTCUT_KEYBOARD_BACKLIGHT_TOGGLE";
-            case SYSTEM_SHORTCUT_VOLUME_UP:
-                    return "SYSTEM_SHORTCUT_VOLUME_UP";
-            case SYSTEM_SHORTCUT_VOLUME_DOWN:
-                    return "SYSTEM_SHORTCUT_VOLUME_DOWN";
-            case SYSTEM_SHORTCUT_VOLUME_MUTE:
-                    return "SYSTEM_SHORTCUT_VOLUME_MUTE";
-            case SYSTEM_SHORTCUT_ALL_APPS:
-                    return "SYSTEM_SHORTCUT_ALL_APPS";
-            case SYSTEM_SHORTCUT_LAUNCH_SEARCH:
-                    return "SYSTEM_SHORTCUT_LAUNCH_SEARCH";
-            case SYSTEM_SHORTCUT_LANGUAGE_SWITCH:
-                    return "SYSTEM_SHORTCUT_LANGUAGE_SWITCH";
-            case SYSTEM_SHORTCUT_ACCESSIBILITY_ALL_APPS:
-                    return "SYSTEM_SHORTCUT_ACCESSIBILITY_ALL_APPS";
-            case SYSTEM_SHORTCUT_TOGGLE_CAPS_LOCK:
-                    return "SYSTEM_SHORTCUT_TOGGLE_CAPS_LOCK";
-            case SYSTEM_SHORTCUT_SYSTEM_MUTE:
-                    return "SYSTEM_SHORTCUT_SYSTEM_MUTE";
-            case SYSTEM_SHORTCUT_SPLIT_SCREEN_NAVIGATION:
-                    return "SYSTEM_SHORTCUT_SPLIT_SCREEN_NAVIGATION";
-            case SYSTEM_SHORTCUT_CHANGE_SPLITSCREEN_FOCUS:
-                    return "SYSTEM_SHORTCUT_CHANGE_SPLITSCREEN_FOCUS";
-            case SYSTEM_SHORTCUT_TRIGGER_BUG_REPORT:
-                    return "SYSTEM_SHORTCUT_TRIGGER_BUG_REPORT";
-            case SYSTEM_SHORTCUT_LOCK_SCREEN:
-                    return "SYSTEM_SHORTCUT_LOCK_SCREEN";
-            case SYSTEM_SHORTCUT_OPEN_NOTES:
-                    return "SYSTEM_SHORTCUT_OPEN_NOTES";
-            case SYSTEM_SHORTCUT_TOGGLE_POWER:
-                    return "SYSTEM_SHORTCUT_TOGGLE_POWER";
-            case SYSTEM_SHORTCUT_SYSTEM_NAVIGATION:
-                    return "SYSTEM_SHORTCUT_SYSTEM_NAVIGATION";
-            case SYSTEM_SHORTCUT_SLEEP:
-                    return "SYSTEM_SHORTCUT_SLEEP";
-            case SYSTEM_SHORTCUT_WAKEUP:
-                    return "SYSTEM_SHORTCUT_WAKEUP";
-            case SYSTEM_SHORTCUT_MEDIA_KEY:
-                    return "SYSTEM_SHORTCUT_MEDIA_KEY";
-            case SYSTEM_SHORTCUT_LAUNCH_DEFAULT_BROWSER:
-                    return "SYSTEM_SHORTCUT_LAUNCH_DEFAULT_BROWSER";
-            case SYSTEM_SHORTCUT_LAUNCH_DEFAULT_EMAIL:
-                    return "SYSTEM_SHORTCUT_LAUNCH_DEFAULT_EMAIL";
-            case SYSTEM_SHORTCUT_LAUNCH_DEFAULT_CONTACTS:
-                    return "SYSTEM_SHORTCUT_LAUNCH_DEFAULT_CONTACTS";
-            case SYSTEM_SHORTCUT_LAUNCH_DEFAULT_CALENDAR:
-                    return "SYSTEM_SHORTCUT_LAUNCH_DEFAULT_CALENDAR";
-            case SYSTEM_SHORTCUT_LAUNCH_DEFAULT_CALCULATOR:
-                    return "SYSTEM_SHORTCUT_LAUNCH_DEFAULT_CALCULATOR";
-            case SYSTEM_SHORTCUT_LAUNCH_DEFAULT_MUSIC:
-                    return "SYSTEM_SHORTCUT_LAUNCH_DEFAULT_MUSIC";
-            case SYSTEM_SHORTCUT_LAUNCH_DEFAULT_MAPS:
-                    return "SYSTEM_SHORTCUT_LAUNCH_DEFAULT_MAPS";
-            case SYSTEM_SHORTCUT_LAUNCH_DEFAULT_MESSAGING:
-                    return "SYSTEM_SHORTCUT_LAUNCH_DEFAULT_MESSAGING";
-            case SYSTEM_SHORTCUT_LAUNCH_DEFAULT_GALLERY:
-                    return "SYSTEM_SHORTCUT_LAUNCH_DEFAULT_GALLERY";
-            case SYSTEM_SHORTCUT_LAUNCH_DEFAULT_FILES:
-                    return "SYSTEM_SHORTCUT_LAUNCH_DEFAULT_FILES";
-            case SYSTEM_SHORTCUT_LAUNCH_DEFAULT_WEATHER:
-                    return "SYSTEM_SHORTCUT_LAUNCH_DEFAULT_WEATHER";
-            case SYSTEM_SHORTCUT_LAUNCH_DEFAULT_FITNESS:
-                    return "SYSTEM_SHORTCUT_LAUNCH_DEFAULT_FITNESS";
-            case SYSTEM_SHORTCUT_LAUNCH_APPLICATION_BY_PACKAGE_NAME:
-                    return "SYSTEM_SHORTCUT_LAUNCH_APPLICATION_BY_PACKAGE_NAME";
-            case SYSTEM_SHORTCUT_DESKTOP_MODE:
-                    return "SYSTEM_SHORTCUT_DESKTOP_MODE";
-            case SYSTEM_SHORTCUT_MULTI_WINDOW_NAVIGATION:
-                    return "SYSTEM_SHORTCUT_MULTI_WINDOW_NAVIGATION";
-            default: return Integer.toHexString(value);
-        }
-    }
-
-    @DataClass.Generated.Member
-    public KeyboardSystemShortcut(
-            @NonNull int[] keycodes,
-            int modifierState,
-            @SystemShortcut int systemShortcut) {
-        this.mKeycodes = keycodes;
-        com.android.internal.util.AnnotationValidations.validate(
-                NonNull.class, null, mKeycodes);
-        this.mModifierState = modifierState;
-        this.mSystemShortcut = systemShortcut;
-
-        if (!(mSystemShortcut == SYSTEM_SHORTCUT_UNSPECIFIED)
-                && !(mSystemShortcut == SYSTEM_SHORTCUT_HOME)
-                && !(mSystemShortcut == SYSTEM_SHORTCUT_RECENT_APPS)
-                && !(mSystemShortcut == SYSTEM_SHORTCUT_BACK)
-                && !(mSystemShortcut == SYSTEM_SHORTCUT_APP_SWITCH)
-                && !(mSystemShortcut == SYSTEM_SHORTCUT_LAUNCH_ASSISTANT)
-                && !(mSystemShortcut == SYSTEM_SHORTCUT_LAUNCH_VOICE_ASSISTANT)
-                && !(mSystemShortcut == SYSTEM_SHORTCUT_LAUNCH_SYSTEM_SETTINGS)
-                && !(mSystemShortcut == SYSTEM_SHORTCUT_TOGGLE_NOTIFICATION_PANEL)
-                && !(mSystemShortcut == SYSTEM_SHORTCUT_TOGGLE_TASKBAR)
-                && !(mSystemShortcut == SYSTEM_SHORTCUT_TAKE_SCREENSHOT)
-                && !(mSystemShortcut == SYSTEM_SHORTCUT_OPEN_SHORTCUT_HELPER)
-                && !(mSystemShortcut == SYSTEM_SHORTCUT_BRIGHTNESS_UP)
-                && !(mSystemShortcut == SYSTEM_SHORTCUT_BRIGHTNESS_DOWN)
-                && !(mSystemShortcut == SYSTEM_SHORTCUT_KEYBOARD_BACKLIGHT_UP)
-                && !(mSystemShortcut == SYSTEM_SHORTCUT_KEYBOARD_BACKLIGHT_DOWN)
-                && !(mSystemShortcut == SYSTEM_SHORTCUT_KEYBOARD_BACKLIGHT_TOGGLE)
-                && !(mSystemShortcut == SYSTEM_SHORTCUT_VOLUME_UP)
-                && !(mSystemShortcut == SYSTEM_SHORTCUT_VOLUME_DOWN)
-                && !(mSystemShortcut == SYSTEM_SHORTCUT_VOLUME_MUTE)
-                && !(mSystemShortcut == SYSTEM_SHORTCUT_ALL_APPS)
-                && !(mSystemShortcut == SYSTEM_SHORTCUT_LAUNCH_SEARCH)
-                && !(mSystemShortcut == SYSTEM_SHORTCUT_LANGUAGE_SWITCH)
-                && !(mSystemShortcut == SYSTEM_SHORTCUT_ACCESSIBILITY_ALL_APPS)
-                && !(mSystemShortcut == SYSTEM_SHORTCUT_TOGGLE_CAPS_LOCK)
-                && !(mSystemShortcut == SYSTEM_SHORTCUT_SYSTEM_MUTE)
-                && !(mSystemShortcut == SYSTEM_SHORTCUT_SPLIT_SCREEN_NAVIGATION)
-                && !(mSystemShortcut == SYSTEM_SHORTCUT_CHANGE_SPLITSCREEN_FOCUS)
-                && !(mSystemShortcut == SYSTEM_SHORTCUT_TRIGGER_BUG_REPORT)
-                && !(mSystemShortcut == SYSTEM_SHORTCUT_LOCK_SCREEN)
-                && !(mSystemShortcut == SYSTEM_SHORTCUT_OPEN_NOTES)
-                && !(mSystemShortcut == SYSTEM_SHORTCUT_TOGGLE_POWER)
-                && !(mSystemShortcut == SYSTEM_SHORTCUT_SYSTEM_NAVIGATION)
-                && !(mSystemShortcut == SYSTEM_SHORTCUT_SLEEP)
-                && !(mSystemShortcut == SYSTEM_SHORTCUT_WAKEUP)
-                && !(mSystemShortcut == SYSTEM_SHORTCUT_MEDIA_KEY)
-                && !(mSystemShortcut == SYSTEM_SHORTCUT_LAUNCH_DEFAULT_BROWSER)
-                && !(mSystemShortcut == SYSTEM_SHORTCUT_LAUNCH_DEFAULT_EMAIL)
-                && !(mSystemShortcut == SYSTEM_SHORTCUT_LAUNCH_DEFAULT_CONTACTS)
-                && !(mSystemShortcut == SYSTEM_SHORTCUT_LAUNCH_DEFAULT_CALENDAR)
-                && !(mSystemShortcut == SYSTEM_SHORTCUT_LAUNCH_DEFAULT_CALCULATOR)
-                && !(mSystemShortcut == SYSTEM_SHORTCUT_LAUNCH_DEFAULT_MUSIC)
-                && !(mSystemShortcut == SYSTEM_SHORTCUT_LAUNCH_DEFAULT_MAPS)
-                && !(mSystemShortcut == SYSTEM_SHORTCUT_LAUNCH_DEFAULT_MESSAGING)
-                && !(mSystemShortcut == SYSTEM_SHORTCUT_LAUNCH_DEFAULT_GALLERY)
-                && !(mSystemShortcut == SYSTEM_SHORTCUT_LAUNCH_DEFAULT_FILES)
-                && !(mSystemShortcut == SYSTEM_SHORTCUT_LAUNCH_DEFAULT_WEATHER)
-                && !(mSystemShortcut == SYSTEM_SHORTCUT_LAUNCH_DEFAULT_FITNESS)
-                && !(mSystemShortcut == SYSTEM_SHORTCUT_LAUNCH_APPLICATION_BY_PACKAGE_NAME)
-                && !(mSystemShortcut == SYSTEM_SHORTCUT_DESKTOP_MODE)
-                && !(mSystemShortcut == SYSTEM_SHORTCUT_MULTI_WINDOW_NAVIGATION)) {
-            throw new java.lang.IllegalArgumentException(
-                    "systemShortcut was " + mSystemShortcut + " but must be one of: "
-                            + "SYSTEM_SHORTCUT_UNSPECIFIED(" + SYSTEM_SHORTCUT_UNSPECIFIED + "), "
-                            + "SYSTEM_SHORTCUT_HOME(" + SYSTEM_SHORTCUT_HOME + "), "
-                            + "SYSTEM_SHORTCUT_RECENT_APPS(" + SYSTEM_SHORTCUT_RECENT_APPS + "), "
-                            + "SYSTEM_SHORTCUT_BACK(" + SYSTEM_SHORTCUT_BACK + "), "
-                            + "SYSTEM_SHORTCUT_APP_SWITCH(" + SYSTEM_SHORTCUT_APP_SWITCH + "), "
-                            + "SYSTEM_SHORTCUT_LAUNCH_ASSISTANT(" + SYSTEM_SHORTCUT_LAUNCH_ASSISTANT + "), "
-                            + "SYSTEM_SHORTCUT_LAUNCH_VOICE_ASSISTANT(" + SYSTEM_SHORTCUT_LAUNCH_VOICE_ASSISTANT + "), "
-                            + "SYSTEM_SHORTCUT_LAUNCH_SYSTEM_SETTINGS(" + SYSTEM_SHORTCUT_LAUNCH_SYSTEM_SETTINGS + "), "
-                            + "SYSTEM_SHORTCUT_TOGGLE_NOTIFICATION_PANEL(" + SYSTEM_SHORTCUT_TOGGLE_NOTIFICATION_PANEL + "), "
-                            + "SYSTEM_SHORTCUT_TOGGLE_TASKBAR(" + SYSTEM_SHORTCUT_TOGGLE_TASKBAR + "), "
-                            + "SYSTEM_SHORTCUT_TAKE_SCREENSHOT(" + SYSTEM_SHORTCUT_TAKE_SCREENSHOT + "), "
-                            + "SYSTEM_SHORTCUT_OPEN_SHORTCUT_HELPER(" + SYSTEM_SHORTCUT_OPEN_SHORTCUT_HELPER + "), "
-                            + "SYSTEM_SHORTCUT_BRIGHTNESS_UP(" + SYSTEM_SHORTCUT_BRIGHTNESS_UP + "), "
-                            + "SYSTEM_SHORTCUT_BRIGHTNESS_DOWN(" + SYSTEM_SHORTCUT_BRIGHTNESS_DOWN + "), "
-                            + "SYSTEM_SHORTCUT_KEYBOARD_BACKLIGHT_UP(" + SYSTEM_SHORTCUT_KEYBOARD_BACKLIGHT_UP + "), "
-                            + "SYSTEM_SHORTCUT_KEYBOARD_BACKLIGHT_DOWN(" + SYSTEM_SHORTCUT_KEYBOARD_BACKLIGHT_DOWN + "), "
-                            + "SYSTEM_SHORTCUT_KEYBOARD_BACKLIGHT_TOGGLE(" + SYSTEM_SHORTCUT_KEYBOARD_BACKLIGHT_TOGGLE + "), "
-                            + "SYSTEM_SHORTCUT_VOLUME_UP(" + SYSTEM_SHORTCUT_VOLUME_UP + "), "
-                            + "SYSTEM_SHORTCUT_VOLUME_DOWN(" + SYSTEM_SHORTCUT_VOLUME_DOWN + "), "
-                            + "SYSTEM_SHORTCUT_VOLUME_MUTE(" + SYSTEM_SHORTCUT_VOLUME_MUTE + "), "
-                            + "SYSTEM_SHORTCUT_ALL_APPS(" + SYSTEM_SHORTCUT_ALL_APPS + "), "
-                            + "SYSTEM_SHORTCUT_LAUNCH_SEARCH(" + SYSTEM_SHORTCUT_LAUNCH_SEARCH + "), "
-                            + "SYSTEM_SHORTCUT_LANGUAGE_SWITCH(" + SYSTEM_SHORTCUT_LANGUAGE_SWITCH + "), "
-                            + "SYSTEM_SHORTCUT_ACCESSIBILITY_ALL_APPS(" + SYSTEM_SHORTCUT_ACCESSIBILITY_ALL_APPS + "), "
-                            + "SYSTEM_SHORTCUT_TOGGLE_CAPS_LOCK(" + SYSTEM_SHORTCUT_TOGGLE_CAPS_LOCK + "), "
-                            + "SYSTEM_SHORTCUT_SYSTEM_MUTE(" + SYSTEM_SHORTCUT_SYSTEM_MUTE + "), "
-                            + "SYSTEM_SHORTCUT_SPLIT_SCREEN_NAVIGATION(" + SYSTEM_SHORTCUT_SPLIT_SCREEN_NAVIGATION + "), "
-                            + "SYSTEM_SHORTCUT_CHANGE_SPLITSCREEN_FOCUS(" + SYSTEM_SHORTCUT_CHANGE_SPLITSCREEN_FOCUS + "), "
-                            + "SYSTEM_SHORTCUT_TRIGGER_BUG_REPORT(" + SYSTEM_SHORTCUT_TRIGGER_BUG_REPORT + "), "
-                            + "SYSTEM_SHORTCUT_LOCK_SCREEN(" + SYSTEM_SHORTCUT_LOCK_SCREEN + "), "
-                            + "SYSTEM_SHORTCUT_OPEN_NOTES(" + SYSTEM_SHORTCUT_OPEN_NOTES + "), "
-                            + "SYSTEM_SHORTCUT_TOGGLE_POWER(" + SYSTEM_SHORTCUT_TOGGLE_POWER + "), "
-                            + "SYSTEM_SHORTCUT_SYSTEM_NAVIGATION(" + SYSTEM_SHORTCUT_SYSTEM_NAVIGATION + "), "
-                            + "SYSTEM_SHORTCUT_SLEEP(" + SYSTEM_SHORTCUT_SLEEP + "), "
-                            + "SYSTEM_SHORTCUT_WAKEUP(" + SYSTEM_SHORTCUT_WAKEUP + "), "
-                            + "SYSTEM_SHORTCUT_MEDIA_KEY(" + SYSTEM_SHORTCUT_MEDIA_KEY + "), "
-                            + "SYSTEM_SHORTCUT_LAUNCH_DEFAULT_BROWSER(" + SYSTEM_SHORTCUT_LAUNCH_DEFAULT_BROWSER + "), "
-                            + "SYSTEM_SHORTCUT_LAUNCH_DEFAULT_EMAIL(" + SYSTEM_SHORTCUT_LAUNCH_DEFAULT_EMAIL + "), "
-                            + "SYSTEM_SHORTCUT_LAUNCH_DEFAULT_CONTACTS(" + SYSTEM_SHORTCUT_LAUNCH_DEFAULT_CONTACTS + "), "
-                            + "SYSTEM_SHORTCUT_LAUNCH_DEFAULT_CALENDAR(" + SYSTEM_SHORTCUT_LAUNCH_DEFAULT_CALENDAR + "), "
-                            + "SYSTEM_SHORTCUT_LAUNCH_DEFAULT_CALCULATOR(" + SYSTEM_SHORTCUT_LAUNCH_DEFAULT_CALCULATOR + "), "
-                            + "SYSTEM_SHORTCUT_LAUNCH_DEFAULT_MUSIC(" + SYSTEM_SHORTCUT_LAUNCH_DEFAULT_MUSIC + "), "
-                            + "SYSTEM_SHORTCUT_LAUNCH_DEFAULT_MAPS(" + SYSTEM_SHORTCUT_LAUNCH_DEFAULT_MAPS + "), "
-                            + "SYSTEM_SHORTCUT_LAUNCH_DEFAULT_MESSAGING(" + SYSTEM_SHORTCUT_LAUNCH_DEFAULT_MESSAGING + "), "
-                            + "SYSTEM_SHORTCUT_LAUNCH_DEFAULT_GALLERY(" + SYSTEM_SHORTCUT_LAUNCH_DEFAULT_GALLERY + "), "
-                            + "SYSTEM_SHORTCUT_LAUNCH_DEFAULT_FILES(" + SYSTEM_SHORTCUT_LAUNCH_DEFAULT_FILES + "), "
-                            + "SYSTEM_SHORTCUT_LAUNCH_DEFAULT_WEATHER(" + SYSTEM_SHORTCUT_LAUNCH_DEFAULT_WEATHER + "), "
-                            + "SYSTEM_SHORTCUT_LAUNCH_DEFAULT_FITNESS(" + SYSTEM_SHORTCUT_LAUNCH_DEFAULT_FITNESS + "), "
-                            + "SYSTEM_SHORTCUT_LAUNCH_APPLICATION_BY_PACKAGE_NAME(" + SYSTEM_SHORTCUT_LAUNCH_APPLICATION_BY_PACKAGE_NAME + "), "
-                            + "SYSTEM_SHORTCUT_DESKTOP_MODE(" + SYSTEM_SHORTCUT_DESKTOP_MODE + "), "
-                            + "SYSTEM_SHORTCUT_MULTI_WINDOW_NAVIGATION(" + SYSTEM_SHORTCUT_MULTI_WINDOW_NAVIGATION + ")");
-        }
-
-
-        // onConstructed(); // You can define this method to get a callback
-    }
-
-    @DataClass.Generated.Member
-    public @NonNull int[] getKeycodes() {
-        return mKeycodes;
-    }
-
-    @DataClass.Generated.Member
-    public int getModifierState() {
-        return mModifierState;
-    }
-
-    @DataClass.Generated.Member
-    public @SystemShortcut int getSystemShortcut() {
-        return mSystemShortcut;
-    }
-
-    @Override
-    @DataClass.Generated.Member
-    public String toString() {
-        // You can override field toString logic by defining methods like:
-        // String fieldNameToString() { ... }
-
-        return "KeyboardSystemShortcut { " +
-                "keycodes = " + java.util.Arrays.toString(mKeycodes) + ", " +
-                "modifierState = " + mModifierState + ", " +
-                "systemShortcut = " + systemShortcutToString(mSystemShortcut) +
-        " }";
-    }
-
-    @Override
-    @DataClass.Generated.Member
-    public boolean equals(@Nullable Object o) {
-        // You can override field equality logic by defining either of the methods like:
-        // boolean fieldNameEquals(KeyboardSystemShortcut other) { ... }
-        // boolean fieldNameEquals(FieldType otherValue) { ... }
-
-        if (this == o) return true;
-        if (o == null || getClass() != o.getClass()) return false;
-        @SuppressWarnings("unchecked")
-        KeyboardSystemShortcut that = (KeyboardSystemShortcut) o;
-        //noinspection PointlessBooleanExpression
-        return true
-                && java.util.Arrays.equals(mKeycodes, that.mKeycodes)
-                && mModifierState == that.mModifierState
-                && mSystemShortcut == that.mSystemShortcut;
-    }
-
-    @Override
-    @DataClass.Generated.Member
-    public int hashCode() {
-        // You can override field hashCode logic by defining methods like:
-        // int fieldNameHashCode() { ... }
-
-        int _hash = 1;
-        _hash = 31 * _hash + java.util.Arrays.hashCode(mKeycodes);
-        _hash = 31 * _hash + mModifierState;
-        _hash = 31 * _hash + mSystemShortcut;
-        return _hash;
-    }
-
-    @DataClass.Generated(
-            time = 1722890917041L,
-            codegenVersion = "1.0.23",
-            sourceFile = "frameworks/base/core/java/android/hardware/input/KeyboardSystemShortcut.java",
-            inputSignatures = "private static final  java.lang.String TAG\nprivate final @android.annotation.NonNull int[] mKeycodes\nprivate final  int mModifierState\nprivate final @android.hardware.input.KeyboardSystemShortcut.SystemShortcut int mSystemShortcut\npublic static final  int SYSTEM_SHORTCUT_UNSPECIFIED\npublic static final  int SYSTEM_SHORTCUT_HOME\npublic static final  int SYSTEM_SHORTCUT_RECENT_APPS\npublic static final  int SYSTEM_SHORTCUT_BACK\npublic static final  int SYSTEM_SHORTCUT_APP_SWITCH\npublic static final  int SYSTEM_SHORTCUT_LAUNCH_ASSISTANT\npublic static final  int SYSTEM_SHORTCUT_LAUNCH_VOICE_ASSISTANT\npublic static final  int SYSTEM_SHORTCUT_LAUNCH_SYSTEM_SETTINGS\npublic static final  int SYSTEM_SHORTCUT_TOGGLE_NOTIFICATION_PANEL\npublic static final  int SYSTEM_SHORTCUT_TOGGLE_TASKBAR\npublic static final  int SYSTEM_SHORTCUT_TAKE_SCREENSHOT\npublic static final  int SYSTEM_SHORTCUT_OPEN_SHORTCUT_HELPER\npublic static final  int SYSTEM_SHORTCUT_BRIGHTNESS_UP\npublic static final  int SYSTEM_SHORTCUT_BRIGHTNESS_DOWN\npublic static final  int SYSTEM_SHORTCUT_KEYBOARD_BACKLIGHT_UP\npublic static final  int SYSTEM_SHORTCUT_KEYBOARD_BACKLIGHT_DOWN\npublic static final  int SYSTEM_SHORTCUT_KEYBOARD_BACKLIGHT_TOGGLE\npublic static final  int SYSTEM_SHORTCUT_VOLUME_UP\npublic static final  int SYSTEM_SHORTCUT_VOLUME_DOWN\npublic static final  int SYSTEM_SHORTCUT_VOLUME_MUTE\npublic static final  int SYSTEM_SHORTCUT_ALL_APPS\npublic static final  int SYSTEM_SHORTCUT_LAUNCH_SEARCH\npublic static final  int SYSTEM_SHORTCUT_LANGUAGE_SWITCH\npublic static final  int SYSTEM_SHORTCUT_ACCESSIBILITY_ALL_APPS\npublic static final  int SYSTEM_SHORTCUT_TOGGLE_CAPS_LOCK\npublic static final  int SYSTEM_SHORTCUT_SYSTEM_MUTE\npublic static final  int SYSTEM_SHORTCUT_SPLIT_SCREEN_NAVIGATION\npublic static final  int SYSTEM_SHORTCUT_CHANGE_SPLITSCREEN_FOCUS\npublic static final  int SYSTEM_SHORTCUT_TRIGGER_BUG_REPORT\npublic static final  int SYSTEM_SHORTCUT_LOCK_SCREEN\npublic static final  int SYSTEM_SHORTCUT_OPEN_NOTES\npublic static final  int SYSTEM_SHORTCUT_TOGGLE_POWER\npublic static final  int SYSTEM_SHORTCUT_SYSTEM_NAVIGATION\npublic static final  int SYSTEM_SHORTCUT_SLEEP\npublic static final  int SYSTEM_SHORTCUT_WAKEUP\npublic static final  int SYSTEM_SHORTCUT_MEDIA_KEY\npublic static final  int SYSTEM_SHORTCUT_LAUNCH_DEFAULT_BROWSER\npublic static final  int SYSTEM_SHORTCUT_LAUNCH_DEFAULT_EMAIL\npublic static final  int SYSTEM_SHORTCUT_LAUNCH_DEFAULT_CONTACTS\npublic static final  int SYSTEM_SHORTCUT_LAUNCH_DEFAULT_CALENDAR\npublic static final  int SYSTEM_SHORTCUT_LAUNCH_DEFAULT_CALCULATOR\npublic static final  int SYSTEM_SHORTCUT_LAUNCH_DEFAULT_MUSIC\npublic static final  int SYSTEM_SHORTCUT_LAUNCH_DEFAULT_MAPS\npublic static final  int SYSTEM_SHORTCUT_LAUNCH_DEFAULT_MESSAGING\npublic static final  int SYSTEM_SHORTCUT_LAUNCH_DEFAULT_GALLERY\npublic static final  int SYSTEM_SHORTCUT_LAUNCH_DEFAULT_FILES\npublic static final  int SYSTEM_SHORTCUT_LAUNCH_DEFAULT_WEATHER\npublic static final  int SYSTEM_SHORTCUT_LAUNCH_DEFAULT_FITNESS\npublic static final  int SYSTEM_SHORTCUT_LAUNCH_APPLICATION_BY_PACKAGE_NAME\npublic static final  int SYSTEM_SHORTCUT_DESKTOP_MODE\npublic static final  int SYSTEM_SHORTCUT_MULTI_WINDOW_NAVIGATION\nclass KeyboardSystemShortcut extends java.lang.Object implements []\n@com.android.internal.util.DataClass(genToString=true, genEqualsHashCode=true)")
-    @Deprecated
-    private void __metadata() {}
-
-
-    //@formatter:on
-    // End of generated code
-
-}
diff --git a/core/java/android/hardware/input/input_framework.aconfig b/core/java/android/hardware/input/input_framework.aconfig
index 6f1d63d8..83c4de3 100644
--- a/core/java/android/hardware/input/input_framework.aconfig
+++ b/core/java/android/hardware/input/input_framework.aconfig
@@ -92,3 +92,10 @@
   description: "Dump keyboard shortcuts in dumpsys window"
   bug: "351963350"
 }
+
+flag {
+  name: "modifier_shortcut_manager_refactor"
+  namespace: "input"
+  description: "Refactor ModifierShortcutManager internal representation of shortcuts."
+  bug: "358603902"
+}
diff --git a/core/java/android/os/UserManager.java b/core/java/android/os/UserManager.java
index 28f2c25..536eca6 100644
--- a/core/java/android/os/UserManager.java
+++ b/core/java/android/os/UserManager.java
@@ -969,9 +969,7 @@
 
     /**
      * Specifies if a user is disallowed from adding new users. This can only be set by device
-     * owners or profile owners on the primary user. The default value is <code>false</code>.
-     * <p>This restriction has no effect on secondary users and managed profiles since only the
-     * primary user can add other users.
+     * owners or profile owners on the main user. The default value is <code>false</code>.
      * <p> When the device is an organization-owned device provisioned with a managed profile,
      * this restriction will be set as a base restriction which cannot be removed by any admin.
      *
diff --git a/core/java/android/os/VibrationAttributes.java b/core/java/android/os/VibrationAttributes.java
index da863e5..5896227 100644
--- a/core/java/android/os/VibrationAttributes.java
+++ b/core/java/android/os/VibrationAttributes.java
@@ -151,31 +151,6 @@
      */
     public static final int USAGE_MEDIA = 0x10 | USAGE_CLASS_MEDIA;
 
-    /** @hide */
-    @IntDef(prefix = { "CATEGORY_" }, value = {
-            CATEGORY_UNKNOWN,
-            CATEGORY_KEYBOARD,
-    })
-    @Retention(RetentionPolicy.SOURCE)
-    public @interface Category {}
-
-    /**
-     * Category value when the vibration category is unknown.
-     *
-     * @hide
-     */
-    public static final int CATEGORY_UNKNOWN = 0x0;
-
-    /**
-     * Category value for keyboard vibrations.
-     *
-     * <p>Most typical keyboard vibrations are haptic feedback for virtual keyboard key
-     * press/release, for example.
-     *
-     * @hide
-     */
-    public static final int CATEGORY_KEYBOARD = 1;
-
     /**
      * @hide
      */
@@ -252,14 +227,12 @@
     private final int mUsage;
     private final int mFlags;
     private final int mOriginalAudioUsage;
-    private final int mCategory;
 
     private VibrationAttributes(@Usage int usage, @AudioAttributes.AttributeUsage int audioUsage,
-            @Flag int flags, @Category int category) {
+            @Flag int flags) {
         mUsage = usage;
         mOriginalAudioUsage = audioUsage;
         mFlags = flags & FLAG_ALL_SUPPORTED;
-        mCategory = category;
     }
 
     /**
@@ -297,20 +270,6 @@
     }
 
     /**
-     * Return the vibration category.
-     *
-     * <p>Vibration categories describe the source of the vibration, and it can be combined with
-     * the vibration usage to best match to a user setting, e.g. a vibration with usage touch and
-     * category keyboard can be used to control keyboard haptic feedback independently.
-     *
-     * @hide
-     */
-    @Category
-    public int getCategory() {
-        return mCategory;
-    }
-
-    /**
      * Check whether a flag is set
      * @return true if a flag is set and false otherwise
      */
@@ -362,14 +321,12 @@
         dest.writeInt(mUsage);
         dest.writeInt(mOriginalAudioUsage);
         dest.writeInt(mFlags);
-        dest.writeInt(mCategory);
     }
 
     private VibrationAttributes(Parcel src) {
         mUsage = src.readInt();
         mOriginalAudioUsage = src.readInt();
         mFlags = src.readInt();
-        mCategory = src.readInt();
     }
 
     public static final @NonNull Parcelable.Creator<VibrationAttributes>
@@ -392,12 +349,12 @@
         }
         VibrationAttributes rhs = (VibrationAttributes) o;
         return mUsage == rhs.mUsage && mOriginalAudioUsage == rhs.mOriginalAudioUsage
-                && mFlags == rhs.mFlags && mCategory == rhs.mCategory;
+                && mFlags == rhs.mFlags;
     }
 
     @Override
     public int hashCode() {
-        return Objects.hash(mUsage, mOriginalAudioUsage, mFlags, mCategory);
+        return Objects.hash(mUsage, mOriginalAudioUsage, mFlags);
     }
 
     @Override
@@ -405,7 +362,6 @@
         return "VibrationAttributes{"
                 + "mUsage=" + usageToString()
                 + ", mAudioUsage= " + AudioAttributes.usageToString(mOriginalAudioUsage)
-                + ", mCategory=" + categoryToString()
                 + ", mFlags=" + mFlags
                 + '}';
     }
@@ -445,23 +401,6 @@
         }
     }
 
-    /** @hide */
-    public String categoryToString() {
-        return categoryToString(mCategory);
-    }
-
-    /** @hide */
-    public static String categoryToString(@Category int category) {
-        switch (category) {
-            case CATEGORY_UNKNOWN:
-                return "UNKNOWN";
-            case CATEGORY_KEYBOARD:
-                return "KEYBOARD";
-            default:
-                return "unknown category " + category;
-        }
-    }
-
     /**
      * Builder class for {@link VibrationAttributes} objects.
      * By default, all information is set to UNKNOWN.
@@ -471,7 +410,6 @@
         private int mUsage = USAGE_UNKNOWN;
         private int mOriginalAudioUsage = AudioAttributes.USAGE_UNKNOWN;
         private int mFlags = 0x0;
-        private int mCategory = CATEGORY_UNKNOWN;
 
         /**
          * Constructs a new Builder with the defaults.
@@ -487,7 +425,6 @@
                 mUsage = vib.mUsage;
                 mOriginalAudioUsage = vib.mOriginalAudioUsage;
                 mFlags = vib.mFlags;
-                mCategory = vib.mCategory;
             }
         }
 
@@ -554,7 +491,7 @@
          */
         public @NonNull VibrationAttributes build() {
             VibrationAttributes ans = new VibrationAttributes(
-                    mUsage, mOriginalAudioUsage, mFlags, mCategory);
+                    mUsage, mOriginalAudioUsage, mFlags);
             return ans;
         }
 
@@ -570,19 +507,6 @@
         }
 
         /**
-         * Sets the attribute describing the category of the corresponding vibration.
-         *
-         * @param category The category for the vibration
-         * @return the same Builder instance.
-         *
-         * @hide
-         */
-        public @NonNull Builder setCategory(@Category int category) {
-            mCategory = category;
-            return this;
-        }
-
-        /**
          * Sets only the flags specified in the bitmask, leaving the other supported flag values
          * unchanged in the builder.
          *
diff --git a/core/java/android/os/VibrationEffect.java b/core/java/android/os/VibrationEffect.java
index e68b746..f02d4a9 100644
--- a/core/java/android/os/VibrationEffect.java
+++ b/core/java/android/os/VibrationEffect.java
@@ -346,7 +346,7 @@
     @RequiresPermission(android.Manifest.permission.VIBRATE_VENDOR_EFFECTS)
     public static VibrationEffect createVendorEffect(@NonNull PersistableBundle effect) {
         VibrationEffect vendorEffect = new VendorEffect(effect, VendorEffect.DEFAULT_STRENGTH,
-                VendorEffect.DEFAULT_SCALE);
+                VendorEffect.DEFAULT_SCALE, VendorEffect.DEFAULT_SCALE);
         vendorEffect.validate();
         return vendorEffect;
     }
@@ -623,7 +623,7 @@
      * @hide
      */
     @NonNull
-    public abstract VibrationEffect scaleLinearly(float scaleFactor);
+    public abstract VibrationEffect applyAdaptiveScale(float scaleFactor);
 
     /**
      * Ensures that the effect is repeating indefinitely or not. This is a lossy operation and
@@ -948,7 +948,7 @@
         /** @hide */
         @NonNull
         @Override
-        public Composed scaleLinearly(float scaleFactor) {
+        public Composed applyAdaptiveScale(float scaleFactor) {
             return applyToSegments(VibrationEffectSegment::scaleLinearly, scaleFactor);
         }
 
@@ -1100,21 +1100,23 @@
 
         private final PersistableBundle mVendorData;
         private final int mEffectStrength;
-        private final float mLinearScale;
+        private final float mScale;
+        private final float mAdaptiveScale;
 
         /** @hide */
         VendorEffect(@NonNull Parcel in) {
             this(Objects.requireNonNull(
                     in.readPersistableBundle(VibrationEffect.class.getClassLoader())),
-                    in.readInt(), in.readFloat());
+                    in.readInt(), in.readFloat(), in.readFloat());
         }
 
         /** @hide */
         public VendorEffect(@NonNull PersistableBundle vendorData, int effectStrength,
-                float linearScale) {
+                float scale, float adaptiveScale) {
             mVendorData = vendorData;
             mEffectStrength = effectStrength;
-            mLinearScale = linearScale;
+            mScale = scale;
+            mAdaptiveScale = adaptiveScale;
         }
 
         @NonNull
@@ -1126,8 +1128,12 @@
             return mEffectStrength;
         }
 
-        public float getLinearScale() {
-            return mLinearScale;
+        public float getScale() {
+            return mScale;
+        }
+
+        public float getAdaptiveScale() {
+            return mAdaptiveScale;
         }
 
         /** @hide */
@@ -1175,7 +1181,8 @@
             if (mEffectStrength == effectStrength) {
                 return this;
             }
-            VendorEffect updated = new VendorEffect(mVendorData, effectStrength, mLinearScale);
+            VendorEffect updated = new VendorEffect(mVendorData, effectStrength, mScale,
+                    mAdaptiveScale);
             updated.validate();
             return updated;
         }
@@ -1184,18 +1191,24 @@
         @NonNull
         @Override
         public VendorEffect scale(float scaleFactor) {
-            // Vendor effect strength cannot be scaled with this method.
-            return this;
+            if (Float.compare(mScale, scaleFactor) == 0) {
+                return this;
+            }
+            VendorEffect updated = new VendorEffect(mVendorData, mEffectStrength, scaleFactor,
+                    mAdaptiveScale);
+            updated.validate();
+            return updated;
         }
 
         /** @hide */
         @NonNull
         @Override
-        public VibrationEffect scaleLinearly(float scaleFactor) {
-            if (Float.compare(mLinearScale, scaleFactor) == 0) {
+        public VibrationEffect applyAdaptiveScale(float scaleFactor) {
+            if (Float.compare(mAdaptiveScale, scaleFactor) == 0) {
                 return this;
             }
-            VendorEffect updated = new VendorEffect(mVendorData, mEffectStrength, scaleFactor);
+            VendorEffect updated = new VendorEffect(mVendorData, mEffectStrength, mScale,
+                    scaleFactor);
             updated.validate();
             return updated;
         }
@@ -1216,29 +1229,31 @@
                 return false;
             }
             return mEffectStrength == other.mEffectStrength
-                    && (Float.compare(mLinearScale, other.mLinearScale) == 0)
+                    && (Float.compare(mScale, other.mScale) == 0)
+                    && (Float.compare(mAdaptiveScale, other.mAdaptiveScale) == 0)
                     && isPersistableBundleEquals(mVendorData, other.mVendorData);
         }
 
         @Override
         public int hashCode() {
             // PersistableBundle does not implement hashCode, so use its size as a shortcut.
-            return Objects.hash(mVendorData.size(), mEffectStrength, mLinearScale);
+            return Objects.hash(mVendorData.size(), mEffectStrength, mScale, mAdaptiveScale);
         }
 
         @Override
         public String toString() {
             return String.format(Locale.ROOT,
-                    "VendorEffect{vendorData=%s, strength=%s, scale=%.2f}",
-                    mVendorData, effectStrengthToString(mEffectStrength), mLinearScale);
+                    "VendorEffect{vendorData=%s, strength=%s, scale=%.2f, adaptiveScale=%.2f}",
+                    mVendorData, effectStrengthToString(mEffectStrength), mScale, mAdaptiveScale);
         }
 
         /** @hide */
         @Override
         public String toDebugString() {
-            return String.format(Locale.ROOT, "vendorEffect=%s, strength=%s, scale=%.2f",
+            return String.format(Locale.ROOT,
+                    "vendorEffect=%s, strength=%s, scale=%.2f, adaptiveScale=%.2f",
                     mVendorData.toShortString(), effectStrengthToString(mEffectStrength),
-                    mLinearScale);
+                    mScale, mAdaptiveScale);
         }
 
         @Override
@@ -1251,7 +1266,8 @@
             out.writeInt(PARCEL_TOKEN_VENDOR_EFFECT);
             out.writePersistableBundle(mVendorData);
             out.writeInt(mEffectStrength);
-            out.writeFloat(mLinearScale);
+            out.writeFloat(mScale);
+            out.writeFloat(mAdaptiveScale);
         }
 
         /**
diff --git a/core/java/android/os/vibrator/flags.aconfig b/core/java/android/os/vibrator/flags.aconfig
index 53a1a67d..e3b1221 100644
--- a/core/java/android/os/vibrator/flags.aconfig
+++ b/core/java/android/os/vibrator/flags.aconfig
@@ -113,3 +113,14 @@
         purpose: PURPOSE_FEATURE
     }
 }
+
+flag {
+    namespace: "haptics"
+    name: "normalized_pwle_effects"
+    is_exported: true
+    description: "Enables functionality to create PWLE effects using advanced and simple APIs"
+    bug: "341052318"
+    metadata {
+        purpose: PURPOSE_FEATURE
+    }
+}
\ No newline at end of file
diff --git a/core/java/android/permission/flags.aconfig b/core/java/android/permission/flags.aconfig
index 3fe063d..5174005 100644
--- a/core/java/android/permission/flags.aconfig
+++ b/core/java/android/permission/flags.aconfig
@@ -217,9 +217,27 @@
 }
 
 flag {
+    name: "check_op_validate_package"
+    namespace: "permissions"
+    description: "Validate package/uid match in checkOp similar to noteOp"
+    bug: "294609684"
+}
+
+flag {
     name: "location_bypass_privacy_dashboard_enabled"
     is_exported: true
     namespace: "permissions"
     description: "Show access entry of location bypass permission in the Privacy Dashboard"
     bug: "325536053"
 }
+
+flag {
+    name: "dont_remove_existing_uid_states"
+    is_fixed_read_only: true
+    namespace: "permissions"
+    description: "Double check if the uid still exists before attempting to remove its appops state"
+    bug: "353474742"
+    metadata {
+        purpose: PURPOSE_BUGFIX
+    }
+}
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index 7ca40ea..184bac4 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -14953,6 +14953,7 @@
          *
          * @hide
          */
+        @Readable
         public static final String MUTE_ALARM_STREAM_WITH_RINGER_MODE =
                 "mute_alarm_stream_with_ringer_mode";
 
diff --git a/core/java/android/service/dreams/DreamService.java b/core/java/android/service/dreams/DreamService.java
index 9c281f3..0242de0 100644
--- a/core/java/android/service/dreams/DreamService.java
+++ b/core/java/android/service/dreams/DreamService.java
@@ -1363,6 +1363,9 @@
      * Tells the dream to come to the front (which in turn tells the overlay to come to the front).
      */
     private void comeToFront() {
+        if (mOverlayConnection == null) {
+            return;
+        }
         mOverlayConnection.addConsumer(overlay -> {
             try {
                 overlay.comeToFront();
diff --git a/core/java/android/service/wallpaper/WallpaperService.java b/core/java/android/service/wallpaper/WallpaperService.java
index 66d08f9..1734223 100644
--- a/core/java/android/service/wallpaper/WallpaperService.java
+++ b/core/java/android/service/wallpaper/WallpaperService.java
@@ -27,6 +27,7 @@
 import static android.view.WindowManager.LayoutParams.TYPE_WALLPAPER;
 
 import static com.android.window.flags.Flags.FLAG_OFFLOAD_COLOR_EXTRACTION;
+import static com.android.window.flags.Flags.noDuplicateSurfaceDestroyedEvents;
 import static com.android.window.flags.Flags.noConsecutiveVisibilityEvents;
 import static com.android.window.flags.Flags.noVisibilityEventOnDisplayStateChange;
 import static com.android.window.flags.Flags.offloadColorExtraction;
@@ -255,6 +256,7 @@
          */
         private boolean mIsScreenTurningOn;
         boolean mReportedVisible;
+        boolean mReportedSurfaceCreated;
         boolean mDestroyed;
         // Set to true after receiving WallpaperManager#COMMAND_FREEZE. It's reset back to false
         // after receiving WallpaperManager#COMMAND_UNFREEZE. COMMAND_FREEZE is fully applied once
@@ -1076,6 +1078,9 @@
             out.print(prefix); out.print("mDisplay="); out.println(mDisplay);
             out.print(prefix); out.print("mCreated="); out.print(mCreated);
                     out.print(" mSurfaceCreated="); out.print(mSurfaceCreated);
+                    if (noDuplicateSurfaceDestroyedEvents()) {
+                        out.print(" mReportedSurfaceCreated="); out.print(mReportedSurfaceCreated);
+                    }
                     out.print(" mIsCreating="); out.print(mIsCreating);
                     out.print(" mDrawingAllowed="); out.println(mDrawingAllowed);
             out.print(prefix); out.print("mWidth="); out.print(mWidth);
@@ -1381,6 +1386,7 @@
                         if (surfaceCreating) {
                             mIsCreating = true;
                             didSurface = true;
+                            mReportedSurfaceCreated = true;
                             if (DEBUG) Log.v(TAG, "onSurfaceCreated("
                                     + mSurfaceHolder + "): " + this);
                             Trace.beginSection("WPMS.Engine.onSurfaceCreated");
@@ -2264,8 +2270,10 @@
         }
 
         void reportSurfaceDestroyed() {
-            if (mSurfaceCreated) {
+            if ((!noDuplicateSurfaceDestroyedEvents() && mSurfaceCreated)
+                    || (noDuplicateSurfaceDestroyedEvents() && mReportedSurfaceCreated)) {
                 mSurfaceCreated = false;
+                mReportedSurfaceCreated = false;
                 mSurfaceHolder.ungetCallbacks();
                 SurfaceHolder.Callback callbacks[] = mSurfaceHolder.getCallbacks();
                 if (callbacks != null) {
diff --git a/core/java/android/text/Selection.java b/core/java/android/text/Selection.java
index 6a54d23..711578c 100644
--- a/core/java/android/text/Selection.java
+++ b/core/java/android/text/Selection.java
@@ -350,7 +350,7 @@
     private static final char PARAGRAPH_SEPARATOR = '\n';
 
     /**
-     * Move the cusrot to the closest paragraph start offset.
+     * Move the cursor to the closest paragraph start offset.
      *
      * @param text the spannable text
      * @param layout layout to be used for drawing.
diff --git a/core/java/android/text/flags/flags.aconfig b/core/java/android/text/flags/flags.aconfig
index 3d190fe..1c3d738 100644
--- a/core/java/android/text/flags/flags.aconfig
+++ b/core/java/android/text/flags/flags.aconfig
@@ -267,3 +267,23 @@
     purpose: PURPOSE_BUGFIX
   }
 }
+
+flag {
+  name: "typeface_cache_for_var_settings"
+  namespace: "text"
+  description: "Cache Typeface instance for font variation settings."
+  bug: "355462362"
+  metadata {
+    purpose: PURPOSE_BUGFIX
+  }
+}
+
+flag {
+  name: "context_menu_hide_unavailable_items"
+  namespace: "text"
+  description: "Hide rather than disable unavailable Editor context menu items."
+  bug: "345709107"
+  metadata {
+    purpose: PURPOSE_BUGFIX
+  }
+}
diff --git a/core/java/android/view/HandwritingInitiator.java b/core/java/android/view/HandwritingInitiator.java
index ccec89b..8912035 100644
--- a/core/java/android/view/HandwritingInitiator.java
+++ b/core/java/android/view/HandwritingInitiator.java
@@ -350,12 +350,13 @@
             return;
         }
 
+        final View focusedView = getFocusedView();
+
         if (!view.isAutoHandwritingEnabled()) {
-            clearFocusedView(view);
+            clearFocusedView(focusedView);
             return;
         }
 
-        final View focusedView = getFocusedView();
         if (focusedView == view) {
             return;
         }
diff --git a/core/java/android/view/SurfaceView.java b/core/java/android/view/SurfaceView.java
index 42d66ce..f7745d1 100644
--- a/core/java/android/view/SurfaceView.java
+++ b/core/java/android/view/SurfaceView.java
@@ -325,17 +325,62 @@
 
     private String mTag = TAG;
 
-    private final ISurfaceControlViewHostParent mSurfaceControlViewHostParent =
-            new ISurfaceControlViewHostParent.Stub() {
+    private static class SurfaceControlViewHostParent extends ISurfaceControlViewHostParent.Stub {
+
+        /**
+         * mSurfaceView is set in {@link #attach} and cleared in {@link #detach} to prevent
+         * temporary memory leaks. The remote process's ISurfaceControlViewHostParent binder
+         * reference extends this object's lifetime. If mSurfaceView is not cleared in
+         * {@link #detach}, then the SurfaceView and anything it references will not be promptly
+         * garbage collected.
+         */
+        @Nullable
+        private SurfaceView mSurfaceView;
+
+        void attach(SurfaceView sv) {
+            synchronized (this) {
+                try {
+                    sv.mSurfacePackage.getRemoteInterface().attachParentInterface(this);
+                    mSurfaceView = sv;
+                } catch (RemoteException e) {
+                    Log.d(TAG, "Failed to attach parent interface to SCVH. Likely SCVH is alraedy "
+                            + "dead.");
+                }
+            }
+        }
+
+        void detach() {
+            synchronized (this) {
+                if (mSurfaceView == null) {
+                    return;
+                }
+                try {
+                    mSurfaceView.mSurfacePackage.getRemoteInterface().attachParentInterface(null);
+                } catch (RemoteException e) {
+                    Log.d(TAG, "Failed to remove parent interface from SCVH. Likely SCVH is "
+                            + "already dead");
+                }
+                mSurfaceView = null;
+            }
+        }
+
         @Override
         public void updateParams(WindowManager.LayoutParams[] childAttrs) {
-            mEmbeddedWindowParams.clear();
-            mEmbeddedWindowParams.addAll(Arrays.asList(childAttrs));
+            SurfaceView sv;
+            synchronized (this) {
+                sv = mSurfaceView;
+            }
+            if (sv == null) {
+                return;
+            }
 
-            if (isAttachedToWindow()) {
-                runOnUiThread(() -> {
-                    if (mParent != null) {
-                        mParent.recomputeViewAttributes(SurfaceView.this);
+            sv.mEmbeddedWindowParams.clear();
+            sv.mEmbeddedWindowParams.addAll(Arrays.asList(childAttrs));
+
+            if (sv.isAttachedToWindow()) {
+                sv.runOnUiThread(() -> {
+                    if (sv.mParent != null) {
+                        sv.mParent.recomputeViewAttributes(sv);
                     }
                 });
             }
@@ -343,34 +388,45 @@
 
         @Override
         public void forwardBackKeyToParent(@NonNull KeyEvent keyEvent) {
-                runOnUiThread(() -> {
-                    if (!isAttachedToWindow() || keyEvent.getKeyCode() != KeyEvent.KEYCODE_BACK) {
-                        return;
-                    }
-                    final ViewRootImpl vri = getViewRootImpl();
-                    if (vri == null) {
-                        return;
-                    }
-                    final InputManager inputManager = mContext.getSystemService(InputManager.class);
-                    if (inputManager == null) {
-                        return;
-                    }
-                    // Check that the event was created recently.
-                    final long timeDiff = SystemClock.uptimeMillis() - keyEvent.getEventTime();
-                    if (timeDiff > FORWARD_BACK_KEY_TOLERANCE_MS) {
-                        Log.e(TAG, "Ignore the input event that exceed the tolerance time, "
-                                + "exceed " + timeDiff + "ms");
-                        return;
-                    }
-                    if (inputManager.verifyInputEvent(keyEvent) == null) {
-                        Log.e(TAG, "Received invalid input event");
-                        return;
-                    }
-                    vri.enqueueInputEvent(keyEvent, null /* receiver */, 0 /* flags */,
-                            true /* processImmediately */);
-                });
+            SurfaceView sv;
+            synchronized (this) {
+                sv = mSurfaceView;
+            }
+            if (sv == null) {
+                return;
+            }
+
+            sv.runOnUiThread(() -> {
+                if (!sv.isAttachedToWindow() || keyEvent.getKeyCode() != KeyEvent.KEYCODE_BACK) {
+                    return;
+                }
+                final ViewRootImpl vri = sv.getViewRootImpl();
+                if (vri == null) {
+                    return;
+                }
+                final InputManager inputManager = sv.mContext.getSystemService(InputManager.class);
+                if (inputManager == null) {
+                    return;
+                }
+                // Check that the event was created recently.
+                final long timeDiff = SystemClock.uptimeMillis() - keyEvent.getEventTime();
+                if (timeDiff > FORWARD_BACK_KEY_TOLERANCE_MS) {
+                    Log.e(TAG, "Ignore the input event that exceed the tolerance time, "
+                            + "exceed " + timeDiff + "ms");
+                    return;
+                }
+                if (inputManager.verifyInputEvent(keyEvent) == null) {
+                    Log.e(TAG, "Received invalid input event");
+                    return;
+                }
+                vri.enqueueInputEvent(keyEvent, null /* receiver */, 0 /* flags */,
+                        true /* processImmediately */);
+            });
         }
-    };
+    }
+
+    private final SurfaceControlViewHostParent mSurfaceControlViewHostParent =
+            new SurfaceControlViewHostParent();
 
     private final boolean mRtDrivenClipping = Flags.clipSurfaceviews();
 
@@ -930,13 +986,8 @@
             }
 
             if (mSurfacePackage != null) {
-                try {
-                    mSurfacePackage.getRemoteInterface().attachParentInterface(null);
-                    mEmbeddedWindowParams.clear();
-                } catch (RemoteException e) {
-                    Log.d(TAG, "Failed to remove parent interface from SCVH. Likely SCVH is "
-                            + "already dead");
-                }
+                mSurfaceControlViewHostParent.detach();
+                mEmbeddedWindowParams.clear();
                 if (releaseSurfacePackage) {
                     mSurfacePackage.release();
                     mSurfacePackage = null;
@@ -2067,12 +2118,7 @@
             applyTransactionOnVriDraw(transaction);
         }
         mSurfacePackage = p;
-        try {
-            mSurfacePackage.getRemoteInterface().attachParentInterface(
-                    mSurfaceControlViewHostParent);
-        } catch (RemoteException e) {
-            Log.d(TAG, "Failed to attach parent interface to SCVH. Likely SCVH is already dead.");
-        }
+        mSurfaceControlViewHostParent.attach(this);
 
         if (isFocused()) {
             requestEmbeddedFocus(true);
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index 3088fd3..e81f32e 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -33989,7 +33989,7 @@
                 || mLastFrameTop != mTop)
                 && viewRootImpl.shouldCheckFrameRateCategory()
                 && parent instanceof View
-                && ((View) parent).mFrameContentVelocity <= 0
+                && ((View) parent).getFrameContentVelocity() <= 0
                 && !isInputMethodWindowType) {
 
             return FRAME_RATE_CATEGORY_HIGH_HINT | FRAME_RATE_CATEGORY_REASON_BOOST;
diff --git a/core/java/android/view/ViewOverlay.java b/core/java/android/view/ViewOverlay.java
index 02f7e95..2786c84 100644
--- a/core/java/android/view/ViewOverlay.java
+++ b/core/java/android/view/ViewOverlay.java
@@ -15,7 +15,10 @@
  */
 package android.view;
 
+import static android.view.flags.Flags.FLAG_VIEW_VELOCITY_API;
+
 import android.animation.LayoutTransition;
+import android.annotation.FlaggedApi;
 import android.annotation.NonNull;
 import android.compat.annotation.UnsupportedAppUsage;
 import android.content.Context;
@@ -365,6 +368,18 @@
             }
             return null;
         }
+
+        /**
+         * @hide
+         */
+        @Override
+        @FlaggedApi(FLAG_VIEW_VELOCITY_API)
+        public float getFrameContentVelocity() {
+            if (mHostView != null) {
+                return mHostView.getFrameContentVelocity();
+            }
+            return super.getFrameContentVelocity();
+        }
     }
 
 }
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index e97f603..0e02627 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -304,6 +304,7 @@
 import java.util.Queue;
 import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.Executor;
+import java.util.concurrent.atomic.AtomicBoolean;
 import java.util.function.Consumer;
 import java.util.function.Predicate;
 /**
@@ -1201,8 +1202,7 @@
     private String mLargestViewTraceName;
 
     private final boolean mAppStartInfoTimestampsFlagValue;
-    @GuardedBy("this")
-    private boolean mAppStartTimestampsSent = false;
+    private AtomicBoolean mAppStartTimestampsSent = new AtomicBoolean(false);
     private boolean mAppStartTrackingStarted = false;
     private long mRenderThreadDrawStartTimeNs = -1;
     private long mFirstFramePresentedTimeNs = -1;
@@ -2647,7 +2647,7 @@
                 destroySurface();
 
                 // Reset so they can be sent again for warm starts.
-                mAppStartTimestampsSent = false;
+                mAppStartTimestampsSent.set(false);
                 mAppStartTrackingStarted = false;
                 mRenderThreadDrawStartTimeNs = -1;
                 mFirstFramePresentedTimeNs = -1;
@@ -4503,42 +4503,29 @@
     }
 
     private void maybeSendAppStartTimes() {
-        synchronized (this) {
-            if (mAppStartTimestampsSent) {
-                // Don't send timestamps more than once.
-                return;
-            }
-
-            // If we already have {@link mRenderThreadDrawStartTimeNs} then pass it through, if not
-            // post to main thread and check if we have it there.
-            if (mRenderThreadDrawStartTimeNs != -1) {
-                sendAppStartTimesLocked();
-            } else {
-                mHandler.post(new Runnable() {
-                    @Override
-                    public void run() {
-                        synchronized (ViewRootImpl.this) {
-                            if (mRenderThreadDrawStartTimeNs == -1) {
-                                return;
-                            }
-                            sendAppStartTimesLocked();
-                        }
-                    }
-                });
-            }
+        if (mAppStartTimestampsSent.get()) {
+            // Don't send timestamps more than once.
+            return;
         }
-    }
 
-    @GuardedBy("this")
-    private void sendAppStartTimesLocked() {
-        try {
-            ActivityManager.getService().reportStartInfoViewTimestamps(
-                    mRenderThreadDrawStartTimeNs, mFirstFramePresentedTimeNs);
-            mAppStartTimestampsSent = true;
-        } catch (RemoteException e) {
-            // Ignore, timestamps may be lost.
-            if (DBG) Log.d(TAG, "Exception attempting to report start timestamps.", e);
-        }
+        // Post to main thread
+        mHandler.post(new Runnable() {
+            @Override
+            public void run() {
+                if (mRenderThreadDrawStartTimeNs == -1) {
+                    return;
+                }
+
+                try {
+                    ActivityManager.getService().reportStartInfoViewTimestamps(
+                            mRenderThreadDrawStartTimeNs, mFirstFramePresentedTimeNs);
+                    mAppStartTimestampsSent.set(true);
+                } catch (RemoteException e) {
+                    // Ignore, timestamps may be lost.
+                    if (DBG) Log.d(TAG, "Exception attempting to report start timestamps.", e);
+                }
+            }
+        });
     }
 
     /**
@@ -13025,7 +13012,7 @@
 
     private boolean shouldSetFrameRateCategory() {
         // use toolkitSetFrameRate flag to gate the change
-        return shouldEnableDvrr() && mSurface.isValid() && shouldEnableDvrr();
+        return shouldEnableDvrr() && mSurface.isValid();
     }
 
     private boolean shouldSetFrameRate() {
diff --git a/core/java/android/view/WindowManager.java b/core/java/android/view/WindowManager.java
index 017e004..67a207e 100644
--- a/core/java/android/view/WindowManager.java
+++ b/core/java/android/view/WindowManager.java
@@ -3462,6 +3462,15 @@
         public static final int PRIVATE_FLAG_NOT_MAGNIFIABLE = 1 << 22;
 
         /**
+         * Indicates that the window should receive key events including Action/Meta key.
+         * They will not be intercepted as usual and instead will be passed to the window with other
+         * key events.
+         * TODO(b/358569822) Remove this once we have nicer API for listening to shortcuts
+         * @hide
+         */
+        public static final int PRIVATE_FLAG_ALLOW_ACTION_KEY_EVENTS = 1 << 23;
+
+        /**
          * Flag to indicate that the window is color space agnostic, and the color can be
          * interpreted to any color space.
          * @hide
diff --git a/core/java/android/view/contentprotection/OWNERS b/core/java/android/view/contentprotection/OWNERS
index b3583a7..48052c6 100644
--- a/core/java/android/view/contentprotection/OWNERS
+++ b/core/java/android/view/contentprotection/OWNERS
@@ -1,4 +1,6 @@
-# Bug component: 544200
+# Bug component: 1040349
 
-include /core/java/android/view/contentcapture/OWNERS
+njagar@google.com
+williamluh@google.com
+aaronjosephs@google.com
 
diff --git a/core/java/android/view/inputmethod/InputConnection.java b/core/java/android/view/inputmethod/InputConnection.java
index 1840bcb..4742f1e 100644
--- a/core/java/android/view/inputmethod/InputConnection.java
+++ b/core/java/android/view/inputmethod/InputConnection.java
@@ -625,8 +625,7 @@
      *        the text you are providing so it is not possible to correctly
      *        specify locations there.
      * @param textAttribute The extra information about the text.
-     * @return true on success, false if the input connection is no longer
-     *
+     * @return true on success, false if the input connection is no longer valid.
      */
     default boolean setComposingText(@NonNull CharSequence text, int newCursorPosition,
             @Nullable TextAttribute textAttribute) {
@@ -753,7 +752,7 @@
      *        you are providing so it is not possible to correctly specify
      *        locations there.
      * @param textAttribute The extra information about the text.
-     * @return true on success, false if the input connection is no longer
+     * @return true on success, false if the input connection is no longer valid.
      */
     default boolean commitText(@NonNull CharSequence text, int newCursorPosition,
             @Nullable TextAttribute textAttribute) {
diff --git a/core/java/android/view/inputmethod/InputMethodInfo.java b/core/java/android/view/inputmethod/InputMethodInfo.java
index 0e66f7a..806a593 100644
--- a/core/java/android/view/inputmethod/InputMethodInfo.java
+++ b/core/java/android/view/inputmethod/InputMethodInfo.java
@@ -878,15 +878,18 @@
 
     /**
      * Returns {@link Intent} for IME language settings activity with
-     * {@link Intent#getAction() Intent action} {@link #ACTION_IME_LANGUAGE_SETTINGS},
-     * else <code>null</code> if
-     * {@link android.R.styleable#InputMethod_languageSettingsActivity} is not defined.
+     * {@link Intent#getAction() Intent action} {@link #ACTION_IME_LANGUAGE_SETTINGS}. If
+     * {@link android.R.styleable#InputMethod_languageSettingsActivity} is not defined, tries to
+     * fall back to the IME general settings activity. If
+     * {@link android.R.styleable#InputMethod_settingsActivity} is also not defined,
+     * returns {code null}.
      *
      * <p>To launch IME language settings, use this method to get the {@link Intent} to launch
      * the IME language settings activity.</p>
      * <p>e.g.<pre><code>startActivity(createImeLanguageSettingsActivityIntent());</code></pre></p>
      *
      * @attr ref R.styleable#InputMethod_languageSettingsActivity
+     * @attr ref R.styleable#InputMethod_settingsActivity
      */
     @FlaggedApi(android.view.inputmethod.Flags.FLAG_IME_SWITCHER_REVAMP_API)
     @Nullable
diff --git a/core/java/android/widget/Editor.java b/core/java/android/widget/Editor.java
index d28c953..03a2672 100644
--- a/core/java/android/widget/Editor.java
+++ b/core/java/android/widget/Editor.java
@@ -474,7 +474,11 @@
     private final AccessibilitySmartActions mA11ySmartActions;
     private InsertModeController mInsertModeController;
 
-    Editor(TextView textView) {
+    /**
+     * @hide
+     */
+    @VisibleForTesting
+    public Editor(TextView textView) {
         mTextView = textView;
         // Synchronize the filter list, which places the undo input filter at the end.
         mTextView.setFilters(mTextView.getFilters());
@@ -3206,16 +3210,6 @@
             }
         }
 
-        final int menuItemOrderUndo = 2;
-        final int menuItemOrderRedo = 3;
-        final int menuItemOrderCut = 4;
-        final int menuItemOrderCopy = 5;
-        final int menuItemOrderPaste = 6;
-        final int menuItemOrderPasteAsPlainText = 7;
-        final int menuItemOrderSelectAll = 8;
-        final int menuItemOrderShare = 9;
-        final int menuItemOrderAutofill = 10;
-
         menu.setOptionalIconsVisible(true);
         menu.setGroupDividerEnabled(true);
 
@@ -3224,7 +3218,18 @@
         final int keyboard = mTextView.getResources().getConfiguration().keyboard;
         menu.setQwertyMode(keyboard == Configuration.KEYBOARD_QWERTY);
 
-        final TypedArray a = mTextView.getContext().obtainStyledAttributes(new int[] {
+        setTextContextMenuItems(menu);
+
+        mPreserveSelection = true;
+
+        // No-op for the old context menu because it doesn't have icons.
+        adjustIconSpacing(menu);
+    }
+
+    /** @hide */
+    @VisibleForTesting
+    public void setTextContextMenuItems(ContextMenu menu) {
+        final TypedArray a = mTextView.getContext().obtainStyledAttributes(new int[]{
                 // TODO: Make Undo/Redo be public attribute.
                 com.android.internal.R.attr.actionModeUndoDrawable,
                 com.android.internal.R.attr.actionModeRedoDrawable,
@@ -3235,6 +3240,16 @@
                 android.R.attr.actionModeShareDrawable,
         });
 
+        final int menuItemOrderUndo = 2;
+        final int menuItemOrderRedo = 3;
+        final int menuItemOrderCut = 4;
+        final int menuItemOrderCopy = 5;
+        final int menuItemOrderPaste = 6;
+        final int menuItemOrderPasteAsPlainText = 7;
+        final int menuItemOrderSelectAll = 8;
+        final int menuItemOrderShare = 9;
+        final int menuItemOrderAutofill = 10;
+
         menu.add(CONTEXT_MENU_GROUP_UNDO_REDO, TextView.ID_UNDO, menuItemOrderUndo,
                 com.android.internal.R.string.undo)
                 .setAlphabeticShortcut('z')
@@ -3291,12 +3306,7 @@
                 .setEnabled(mTextView.canRequestAutofill()
                         && (selected == null || selected.isEmpty()))
                 .setOnMenuItemClickListener(mOnContextMenuItemClickListener);
-
-        mPreserveSelection = true;
         a.recycle();
-
-        // No-op for the old context menu because it doesn't have icons.
-        adjustIconSpacing(menu);
     }
 
     /**
diff --git a/core/java/android/widget/RemoteViews.java b/core/java/android/widget/RemoteViews.java
index eb35817..1922327 100644
--- a/core/java/android/widget/RemoteViews.java
+++ b/core/java/android/widget/RemoteViews.java
@@ -83,6 +83,7 @@
 import android.os.Process;
 import android.os.RemoteException;
 import android.os.StrictMode;
+import android.os.Trace;
 import android.os.UserHandle;
 import android.system.Os;
 import android.text.TextUtils;
@@ -6246,6 +6247,18 @@
 
     private View inflateView(Context context, RemoteViews rv, @Nullable ViewGroup parent,
             @StyleRes int applyThemeResId, @Nullable ColorResources colorResources) {
+        try {
+            Trace.beginSection(rv.hasDrawInstructions()
+                    ? "RemoteViews#inflateViewWithDrawInstructions"
+                    : "RemoteViews#inflateView");
+            return inflateViewInternal(context, rv, parent, applyThemeResId, colorResources);
+        } finally {
+            Trace.endSection();
+        }
+    }
+
+    private View inflateViewInternal(Context context, RemoteViews rv, @Nullable ViewGroup parent,
+            @StyleRes int applyThemeResId, @Nullable ColorResources colorResources) {
         // RemoteViews may be built by an application installed in another
         // user. So build a context that loads resources from that user but
         // still returns the current users userId so settings like data / time formats
@@ -6384,7 +6397,7 @@
 
         private View mResult;
         private ViewTree mTree;
-        private Action[] mActions;
+        private List<Action> mActions;
         private Exception mError;
 
         private AsyncApplyTask(
@@ -6411,11 +6424,20 @@
 
                 if (mRV.mActions != null) {
                     int count = mRV.mActions.size();
-                    mActions = new Action[count];
-                    for (int i = 0; i < count && !isCancelled(); i++) {
-                        // TODO: check if isCancelled in nested views.
-                        mActions[i] = mRV.mActions.get(i)
-                                .initActionAsync(mTree, mParent, mApplyParams);
+                    mActions = new ArrayList<>(count);
+                    try {
+                        Trace.beginSection(hasDrawInstructions()
+                                ? "RemoteViews#initActionAsyncWithDrawInstructions"
+                                : "RemoteViews#initActionAsync");
+                        for (Action action : mRV.mActions) {
+                            if (isCancelled()) {
+                                break;
+                            }
+                            // TODO: check if isCancelled in nested views.
+                            mActions.add(action.initActionAsync(mTree, mParent, mApplyParams));
+                        }
+                    } finally {
+                        Trace.endSection();
                     }
                 } else {
                     mActions = null;
@@ -6437,14 +6459,7 @@
 
                 try {
                     if (mActions != null) {
-
-                        ActionApplyParams applyParams = mApplyParams.clone();
-                        if (applyParams.handler == null) {
-                            applyParams.handler = DEFAULT_INTERACTION_HANDLER;
-                        }
-                        for (Action a : mActions) {
-                            a.apply(viewTree.mRoot, mParent, applyParams);
-                        }
+                        mRV.performApply(viewTree.mRoot, mParent, mApplyParams, mActions);
                     }
                     // If the parent of the view is has is a root, resolve the recycling.
                     if (mTopLevel && mResult instanceof ViewGroup) {
@@ -6620,6 +6635,11 @@
     }
 
     private void performApply(View v, ViewGroup parent, ActionApplyParams params) {
+        performApply(v, parent, params, mActions);
+    }
+
+    private void performApply(
+            View v, ViewGroup parent, ActionApplyParams params, List<Action> actions) {
         params = params.clone();
         if (params.handler == null) {
             params.handler = DEFAULT_INTERACTION_HANDLER;
@@ -6630,8 +6650,15 @@
         }
         if (mActions != null) {
             final int count = mActions.size();
-            for (int i = 0; i < count; i++) {
-                mActions.get(i).apply(v, parent, params);
+            try {
+                Trace.beginSection(hasDrawInstructions()
+                        ? "RemoteViews#applyActionsWithDrawInstructions"
+                        : "RemoteViews#applyActions");
+                for (int i = 0; i < count; i++) {
+                    mActions.get(i).apply(v, parent, params);
+                }
+            } finally {
+                Trace.endSection();
             }
         }
     }
diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java
index 61ecc62..72b268b 100644
--- a/core/java/android/widget/TextView.java
+++ b/core/java/android/widget/TextView.java
@@ -12255,7 +12255,11 @@
         return selectionMin >= 0 && selectionMax > 0 && selectionMin != selectionMax;
     }
 
-    String getSelectedText() {
+    /**
+     * @hide
+     */
+    @VisibleForTesting
+    public String getSelectedText() {
         if (!hasSelection()) {
             return null;
         }
@@ -14080,7 +14084,11 @@
         structure.setInputType(getInputType());
     }
 
-    boolean canRequestAutofill() {
+    /**
+     * @hide
+     */
+    @VisibleForTesting
+    public boolean canRequestAutofill() {
         if (!isAutofillable()) {
             return false;
         }
diff --git a/core/java/android/window/TransitionRequestInfo.java b/core/java/android/window/TransitionRequestInfo.java
index 253337b..fe936f7 100644
--- a/core/java/android/window/TransitionRequestInfo.java
+++ b/core/java/android/window/TransitionRequestInfo.java
@@ -115,8 +115,11 @@
     @DataClass(genToString = true, genSetters = true, genBuilder = false, genConstructor = false)
     public static final class DisplayChange implements Parcelable {
         private final int mDisplayId;
+
+        // If non-null, these bounds changes should ignore any potential rotation changes.
         @Nullable private Rect mStartAbsBounds = null;
         @Nullable private Rect mEndAbsBounds = null;
+
         private int mStartRotation = WindowConfiguration.ROTATION_UNDEFINED;
         private int mEndRotation = WindowConfiguration.ROTATION_UNDEFINED;
         private boolean mPhysicalDisplayChanged = false;
diff --git a/core/java/android/window/flags/wallpaper_manager.aconfig b/core/java/android/window/flags/wallpaper_manager.aconfig
index 01c78a0..8c6721a 100644
--- a/core/java/android/window/flags/wallpaper_manager.aconfig
+++ b/core/java/android/window/flags/wallpaper_manager.aconfig
@@ -39,3 +39,13 @@
   description: "Prevent the system from sending visibility event on display state change."
   bug: "331725519"
 }
+
+flag {
+  name: "no_duplicate_surface_destroyed_events"
+  namespace: "systemui"
+  description: "Prevent the system from sending onSurfaceDestroyed() twice."
+  bug: "344461715"
+  metadata {
+    purpose: PURPOSE_BUGFIX
+  }
+}
diff --git a/core/java/android/window/flags/windowing_frontend.aconfig b/core/java/android/window/flags/windowing_frontend.aconfig
index 0cb96df..61ee13a 100644
--- a/core/java/android/window/flags/windowing_frontend.aconfig
+++ b/core/java/android/window/flags/windowing_frontend.aconfig
@@ -91,6 +91,14 @@
 }
 
 flag {
+  name: "transit_tracker_plumbing"
+  namespace: "windowing_frontend"
+  description: "Plumb and collect on transition tracking object instead of singleton"
+  bug: "325114242"
+  is_fixed_read_only: true
+}
+
+flag {
   name: "transit_ready_tracking"
   namespace: "windowing_frontend"
   description: "Enable accurate transition readiness tracking"
diff --git a/core/java/com/android/internal/app/LocalePickerWithRegion.java b/core/java/com/android/internal/app/LocalePickerWithRegion.java
index 27eebbe..ce17d78 100644
--- a/core/java/com/android/internal/app/LocalePickerWithRegion.java
+++ b/core/java/com/android/internal/app/LocalePickerWithRegion.java
@@ -74,6 +74,7 @@
          * @param locale    the locale picked.
          */
         void onLocaleSelected(LocaleStore.LocaleInfo locale);
+        default void onParentLocaleSelected(LocaleStore.LocaleInfo locale) {}
     }
 
     /**
@@ -292,7 +293,7 @@
                         mListener, locale, mTranslatedOnly /* translate only */,
                         mOnActionExpandListener, this.mLocalePickerCollector);
             }
-
+            mListener.onParentLocaleSelected(locale);
             if (selector != null) {
                 getFragmentManager().beginTransaction()
                         .setTransition(FragmentTransaction.TRANSIT_FRAGMENT_OPEN)
diff --git a/core/java/com/android/internal/jank/InteractionJankMonitor.java b/core/java/com/android/internal/jank/InteractionJankMonitor.java
index 33610a0..c7e1fba 100644
--- a/core/java/com/android/internal/jank/InteractionJankMonitor.java
+++ b/core/java/com/android/internal/jank/InteractionJankMonitor.java
@@ -396,6 +396,9 @@
         int cujType = conf.mCujType;
         if (!shouldMonitor()) {
             return false;
+        } else if (!conf.hasValidView()) {
+            Log.w(TAG, "The view has since become invalid, aborting the CUJ.");
+            return false;
         }
 
         RunningTracker tracker = putTrackerIfNoCurrent(cujType, () ->
diff --git a/core/java/com/android/internal/pm/pkg/component/ParsedProviderImpl.java b/core/java/com/android/internal/pm/pkg/component/ParsedProviderImpl.java
index 987fd41..ec5ff4c 100644
--- a/core/java/com/android/internal/pm/pkg/component/ParsedProviderImpl.java
+++ b/core/java/com/android/internal/pm/pkg/component/ParsedProviderImpl.java
@@ -174,7 +174,7 @@
     // CHECKSTYLE:OFF Generated code
     //
     // To regenerate run:
-    // $ codegen $ANDROID_BUILD_TOP/frameworks/base/services/core/java/com/android/server/pm/pkg/component/ParsedProviderImpl.java
+    // $ codegen $ANDROID_BUILD_TOP/frameworks/base/core/java/com/android/internal/pm/pkg/component/ParsedProviderImpl.java
     //
     // To exclude the generated code from IntelliJ auto-formatting enable (one-time):
     //   Settings > Editor > Code Style > Formatter Control
@@ -298,9 +298,9 @@
     }
 
     @DataClass.Generated(
-            time = 1642560323360L,
+            time = 1723882842941L,
             codegenVersion = "1.0.23",
-            sourceFile = "frameworks/base/services/core/java/com/android/server/pm/pkg/component/ParsedProviderImpl.java",
+            sourceFile = "frameworks/base/core/java/com/android/internal/pm/pkg/component/ParsedProviderImpl.java",
             inputSignatures = "private @android.annotation.Nullable @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForInternedString.class) java.lang.String authority\nprivate  boolean syncable\nprivate @android.annotation.Nullable @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForInternedString.class) java.lang.String readPermission\nprivate @android.annotation.Nullable @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForInternedString.class) java.lang.String writePermission\nprivate  boolean grantUriPermissions\nprivate  boolean forceUriPermissions\nprivate  boolean multiProcess\nprivate  int initOrder\nprivate @android.annotation.NonNull java.util.List<android.os.PatternMatcher> uriPermissionPatterns\nprivate @android.annotation.NonNull java.util.List<android.content.pm.PathPermission> pathPermissions\npublic static final @android.annotation.NonNull android.os.Parcelable.Creator<com.android.internal.pm.pkg.component.ParsedProviderImpl> CREATOR\npublic  com.android.internal.pm.pkg.component.ParsedProviderImpl setReadPermission(java.lang.String)\npublic  com.android.internal.pm.pkg.component.ParsedProviderImpl setWritePermission(java.lang.String)\npublic @android.annotation.NonNull com.android.internal.pm.pkg.component.ParsedProviderImpl addUriPermissionPattern(android.os.PatternMatcher)\npublic @android.annotation.NonNull com.android.internal.pm.pkg.component.ParsedProviderImpl addPathPermission(android.content.pm.PathPermission)\npublic  java.lang.String toString()\npublic @java.lang.Override int describeContents()\npublic @java.lang.Override void writeToParcel(android.os.Parcel,int)\nclass ParsedProviderImpl extends com.android.internal.pm.pkg.component.ParsedMainComponentImpl implements [com.android.internal.pm.pkg.component.ParsedProvider, android.os.Parcelable]\n@com.android.internal.util.DataClass(genSetters=true, genGetters=true, genParcelable=false, genBuilder=false)")
     @Deprecated
     private void __metadata() {}
diff --git a/core/java/com/android/internal/policy/PhoneWindow.java b/core/java/com/android/internal/policy/PhoneWindow.java
index ec004d0..0d0207f 100644
--- a/core/java/com/android/internal/policy/PhoneWindow.java
+++ b/core/java/com/android/internal/policy/PhoneWindow.java
@@ -418,6 +418,7 @@
             mElevation = preservedWindow.getElevation();
             mLoadElevation = false;
             mForceDecorInstall = true;
+            mDecorFitsSystemWindows = preservedWindow.decorFitsSystemWindows();
             setSystemBarAppearance(preservedWindow.getSystemBarAppearance());
             // If we're preserving window, carry over the app token from the preserved
             // window, as we'll be skipping the addView in handleResumeActivity(), and
diff --git a/core/java/com/android/internal/protolog/LegacyProtoLogImpl.java b/core/java/com/android/internal/protolog/LegacyProtoLogImpl.java
index 572a599..2feb3d5 100644
--- a/core/java/com/android/internal/protolog/LegacyProtoLogImpl.java
+++ b/core/java/com/android/internal/protolog/LegacyProtoLogImpl.java
@@ -48,6 +48,7 @@
 import java.io.IOException;
 import java.io.PrintWriter;
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.Map;
 import java.util.TreeMap;
 import java.util.stream.Collectors;
@@ -418,11 +419,18 @@
         return group.isLogToLogcat() || (group.isLogToProto() && isProtoEnabled());
     }
 
-    @Override
     public void registerGroups(IProtoLogGroup... protoLogGroups) {
         for (IProtoLogGroup group : protoLogGroups) {
             mLogGroups.put(group.name(), group);
         }
+
+        final var hasGroupsLoggingToLogcat = Arrays.stream(protoLogGroups)
+                .anyMatch(IProtoLogGroup::isLogToLogcat);
+
+        final ILogger logger = (msg) -> Slog.i(TAG, msg);
+        if (hasGroupsLoggingToLogcat) {
+            mViewerConfig.loadViewerConfig(logger, mLegacyViewerConfigFilename);
+        }
     }
 }
 
diff --git a/core/java/com/android/internal/protolog/LogcatOnlyProtoLogImpl.java b/core/java/com/android/internal/protolog/LogcatOnlyProtoLogImpl.java
index ebdad6d..e8d5195 100644
--- a/core/java/com/android/internal/protolog/LogcatOnlyProtoLogImpl.java
+++ b/core/java/com/android/internal/protolog/LogcatOnlyProtoLogImpl.java
@@ -79,9 +79,4 @@
     public boolean isEnabled(IProtoLogGroup group, LogLevel level) {
         return true;
     }
-
-    @Override
-    public void registerGroups(IProtoLogGroup... protoLogGroups) {
-        // Does nothing
-    }
 }
diff --git a/core/java/com/android/internal/protolog/PerfettoProtoLogImpl.java b/core/java/com/android/internal/protolog/PerfettoProtoLogImpl.java
index cb20ceb..78b5cfe 100644
--- a/core/java/com/android/internal/protolog/PerfettoProtoLogImpl.java
+++ b/core/java/com/android/internal/protolog/PerfettoProtoLogImpl.java
@@ -16,6 +16,7 @@
 
 package com.android.internal.protolog;
 
+import static android.content.Context.PROTOLOG_SERVICE;
 import static android.internal.perfetto.protos.InternedDataOuterClass.InternedData.PROTOLOG_STACKTRACE;
 import static android.internal.perfetto.protos.InternedDataOuterClass.InternedData.PROTOLOG_STRING_ARGS;
 import static android.internal.perfetto.protos.ProfileCommon.InternedString.IID;
@@ -46,6 +47,8 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.internal.perfetto.protos.Protolog.ProtoLogViewerConfig.MessageData;
+import android.os.RemoteException;
+import android.os.ServiceManager;
 import android.os.ShellCommand;
 import android.os.SystemClock;
 import android.text.TextUtils;
@@ -76,6 +79,7 @@
 import java.util.Arrays;
 import java.util.List;
 import java.util.Map;
+import java.util.Objects;
 import java.util.Set;
 import java.util.TreeMap;
 import java.util.UUID;
@@ -85,6 +89,7 @@
 import java.util.concurrent.atomic.AtomicInteger;
 import java.util.concurrent.locks.Lock;
 import java.util.concurrent.locks.ReentrantLock;
+import java.util.stream.Stream;
 
 /**
  * A service for the ProtoLog logging system.
@@ -103,45 +108,69 @@
     private final ProtoLogViewerConfigReader mViewerConfigReader;
     @Nullable
     private final ViewerConfigInputStreamProvider mViewerConfigInputStreamProvider;
+    @NonNull
     private final TreeMap<String, IProtoLogGroup> mLogGroups = new TreeMap<>();
+    @NonNull
     private final Runnable mCacheUpdater;
 
+    @Nullable // null when the flag android.tracing.client_side_proto_logging is not flipped
+    private final IProtoLogService mProtoLogService;
+
+    @NonNull
     private final int[] mDefaultLogLevelCounts = new int[LogLevel.values().length];
+    @NonNull
     private final Map<String, int[]> mLogLevelCounts = new ArrayMap<>();
+    @NonNull
     private final Map<String, Integer> mCollectStackTraceGroupCounts = new ArrayMap<>();
 
     private final Lock mBackgroundServiceLock = new ReentrantLock();
     private ExecutorService mBackgroundLoggingService = Executors.newSingleThreadExecutor();
 
-    public PerfettoProtoLogImpl(@NonNull String viewerConfigFilePath, Runnable cacheUpdater) {
-        this(() -> {
-            try {
-                return new ProtoInputStream(new FileInputStream(viewerConfigFilePath));
-            } catch (FileNotFoundException e) {
-                throw new RuntimeException("Failed to load viewer config file " + viewerConfigFilePath, e);
-            }
-        }, cacheUpdater);
+    public PerfettoProtoLogImpl(@NonNull IProtoLogGroup[] groups) {
+        this(null, null, null, () -> {}, groups);
     }
 
-    public PerfettoProtoLogImpl() {
-        this(null, null, () -> {});
+    public PerfettoProtoLogImpl(@NonNull Runnable cacheUpdater, @NonNull IProtoLogGroup[] groups) {
+        this(null, null, null, cacheUpdater, groups);
     }
 
     public PerfettoProtoLogImpl(
-            @NonNull ViewerConfigInputStreamProvider viewerConfigInputStreamProvider,
-            Runnable cacheUpdater
-    ) {
-        this(viewerConfigInputStreamProvider,
-                new ProtoLogViewerConfigReader(viewerConfigInputStreamProvider),
-                cacheUpdater);
+            @NonNull String viewerConfigFilePath,
+            @NonNull Runnable cacheUpdater,
+            @NonNull IProtoLogGroup[] groups) {
+        this(viewerConfigFilePath,
+                null,
+                new ProtoLogViewerConfigReader(() -> {
+                    try {
+                        return new ProtoInputStream(new FileInputStream(viewerConfigFilePath));
+                    } catch (FileNotFoundException e) {
+                        throw new RuntimeException(
+                                "Failed to load viewer config file " + viewerConfigFilePath, e);
+                    }
+                }),
+                cacheUpdater, groups);
     }
 
     @VisibleForTesting
     public PerfettoProtoLogImpl(
             @Nullable ViewerConfigInputStreamProvider viewerConfigInputStreamProvider,
             @Nullable ProtoLogViewerConfigReader viewerConfigReader,
-            Runnable cacheUpdater
-    ) {
+            @NonNull Runnable cacheUpdater,
+            @NonNull IProtoLogGroup[] groups) {
+        this(null, viewerConfigInputStreamProvider, viewerConfigReader, cacheUpdater, groups);
+    }
+
+    private PerfettoProtoLogImpl(
+            @Nullable String viewerConfigFilePath,
+            @Nullable ViewerConfigInputStreamProvider viewerConfigInputStreamProvider,
+            @Nullable ProtoLogViewerConfigReader viewerConfigReader,
+            @NonNull Runnable cacheUpdater,
+            @NonNull IProtoLogGroup[] groups) {
+        if (viewerConfigFilePath != null && viewerConfigInputStreamProvider != null) {
+            throw new RuntimeException("Only one of viewerConfigFilePath and "
+                    + "viewerConfigInputStreamProvider can be set");
+        }
+
         Producer.init(InitArguments.DEFAULTS);
         DataSourceParams params =
                 new DataSourceParams.Builder()
@@ -153,6 +182,35 @@
         this.mViewerConfigInputStreamProvider = viewerConfigInputStreamProvider;
         this.mViewerConfigReader = viewerConfigReader;
         this.mCacheUpdater = cacheUpdater;
+
+        registerGroupsLocally(groups);
+
+        if (android.tracing.Flags.clientSideProtoLogging()) {
+            mProtoLogService =
+                    IProtoLogService.Stub.asInterface(ServiceManager.getService(PROTOLOG_SERVICE));
+            Objects.requireNonNull(mProtoLogService,
+                    "ServiceManager returned a null ProtoLog service");
+
+            try {
+                var args = new ProtoLogService.RegisterClientArgs();
+
+                if (viewerConfigFilePath != null) {
+                    args.setViewerConfigFile(viewerConfigFilePath);
+                }
+
+                final var groupArgs = Stream.of(groups)
+                        .map(group -> new ProtoLogService.RegisterClientArgs.GroupConfig(
+                                group.name(), group.isLogToLogcat()))
+                        .toArray(ProtoLogService.RegisterClientArgs.GroupConfig[]::new);
+                args.setGroups(groupArgs);
+
+                mProtoLogService.registerClient(this, args);
+            } catch (RemoteException e) {
+                throw new RuntimeException("Failed to register ProtoLog client");
+            }
+        } else {
+            mProtoLogService = null;
+        }
     }
 
     /**
@@ -248,19 +306,22 @@
                 || group.isLogToLogcat();
     }
 
-    @Override
-    public void registerGroups(IProtoLogGroup... protoLogGroups) {
+    private void registerGroupsLocally(@NonNull IProtoLogGroup[] protoLogGroups) {
+        final var groupsLoggingToLogcat = new ArrayList<String>();
         for (IProtoLogGroup protoLogGroup : protoLogGroups) {
             mLogGroups.put(protoLogGroup.name(), protoLogGroup);
+
+            if (protoLogGroup.isLogToLogcat()) {
+                groupsLoggingToLogcat.add(protoLogGroup.name());
+            }
         }
 
-        final String[] groupsLoggingToLogcat = Arrays.stream(protoLogGroups)
-                .filter(IProtoLogGroup::isLogToLogcat)
-                .map(IProtoLogGroup::name)
-                .toArray(String[]::new);
-
         if (mViewerConfigReader != null) {
-            mViewerConfigReader.loadViewerConfig(groupsLoggingToLogcat);
+            // Load in background to avoid delay in boot process.
+            // The caveat is that any log message that is also logged to logcat will not be
+            // successfully decoded until this completes.
+            mBackgroundLoggingService.execute(() -> mViewerConfigReader
+                    .loadViewerConfig(groupsLoggingToLogcat.toArray(new String[0])));
         }
     }
 
@@ -355,7 +416,9 @@
             Log.e(LOG_TAG, "Failed to wait for tracing to finish", e);
         }
 
-        dumpViewerConfig();
+        if (!android.tracing.Flags.clientSideProtoLogging()) {
+            dumpViewerConfig();
+        }
 
         Log.d(LOG_TAG, "Finished onTracingFlush");
     }
@@ -451,7 +514,8 @@
                     os.write(GROUP_ID, pis.readInt(GROUP_ID));
                     break;
                 case (int) LOCATION:
-                    os.write(LOCATION, pis.readInt(LOCATION));
+                    os.write(LOCATION, pis.readString(LOCATION));
+                    break;
                 default:
                     throw new RuntimeException(
                             "Unexpected field id " + pis.getFieldNumber());
@@ -690,7 +754,7 @@
         return UUID.nameUUIDFromBytes(fullStringIdentifier.getBytes()).getMostSignificantBits();
     }
 
-    private static final int STACK_SIZE_TO_PROTO_LOG_ENTRY_CALL = 12;
+    private static final int STACK_SIZE_TO_PROTO_LOG_ENTRY_CALL = 6;
 
     private String collectStackTrace() {
         StackTraceElement[] stackTrace =  Thread.currentThread().getStackTrace();
diff --git a/core/java/com/android/internal/protolog/ProtoLog.java b/core/java/com/android/internal/protolog/ProtoLog.java
index 87678e5..f9b9894 100644
--- a/core/java/com/android/internal/protolog/ProtoLog.java
+++ b/core/java/com/android/internal/protolog/ProtoLog.java
@@ -50,6 +50,24 @@
     private static IProtoLog sProtoLogInstance;
 
     /**
+     * Initialize ProtoLog in this process.
+     * <p>
+     * This method MUST be called before any protologging is performed in this process.
+     * Ensure that all groups that will be used for protologging are registered.
+     *
+     * @param groups The ProtoLog groups that will be used in the process.
+     */
+    public static void init(IProtoLogGroup... groups) {
+        if (android.tracing.Flags.perfettoProtologTracing()) {
+            sProtoLogInstance = new PerfettoProtoLogImpl(groups);
+        } else {
+            // The first call to ProtoLog is likely to flip REQUIRE_PROTOLOGTOOL, which is when this
+            // static block will be executed before REQUIRE_PROTOLOGTOOL is actually set.
+            sProtoLogInstance = new LogcatOnlyProtoLogImpl();
+        }
+    }
+
+    /**
      * DEBUG level log.
      *
      * @param group         {@code IProtoLogGroup} controlling this log call.
@@ -150,14 +168,6 @@
         return sProtoLogInstance;
     }
 
-    /**
-     * Registers available protolog groups. A group must be registered before it can be used.
-     * @param protoLogGroups The groups to register for use in protolog.
-     */
-    public static void registerGroups(IProtoLogGroup... protoLogGroups) {
-        sProtoLogInstance.registerGroups(protoLogGroups);
-    }
-
     private static void logStringMessage(LogLevel logLevel, IProtoLogGroup group,
             String stringMessage, Object... args) {
         if (sProtoLogInstance == null) {
@@ -169,14 +179,4 @@
             sProtoLogInstance.log(logLevel, group, stringMessage, args);
         }
     }
-
-    static {
-        if (android.tracing.Flags.perfettoProtologTracing()) {
-            sProtoLogInstance = new PerfettoProtoLogImpl();
-        } else {
-            // The first call to ProtoLog is likely to flip REQUIRE_PROTOLOGTOOL, which is when this
-            // static block will be executed before REQUIRE_PROTOLOGTOOL is actually set.
-            sProtoLogInstance = new LogcatOnlyProtoLogImpl();
-        }
-    }
 }
diff --git a/core/java/com/android/internal/protolog/ProtoLogImpl.java b/core/java/com/android/internal/protolog/ProtoLogImpl.java
index 77ca7ce..da6d8cf 100644
--- a/core/java/com/android/internal/protolog/ProtoLogImpl.java
+++ b/core/java/com/android/internal/protolog/ProtoLogImpl.java
@@ -93,35 +93,30 @@
     }
 
     /**
-     * Registers available protolog groups. A group must be registered before it can be used.
-     * @param protoLogGroups The groups to register for use in protolog.
-     */
-    public static void registerGroups(IProtoLogGroup... protoLogGroups) {
-        getSingleInstance().registerGroups(protoLogGroups);
-    }
-
-    /**
      * Returns the single instance of the ProtoLogImpl singleton class.
      */
     public static synchronized IProtoLog getSingleInstance() {
         if (sServiceInstance == null) {
+            final var groups = sLogGroups.values().toArray(new IProtoLogGroup[0]);
+
             if (android.tracing.Flags.perfettoProtologTracing()) {
                 File f = new File(sViewerConfigPath);
                 if (!ProtoLog.REQUIRE_PROTOLOGTOOL && !f.exists()) {
                     // TODO(b/353530422): Remove - temporary fix to unblock b/352290057
-                    // In so tests the viewer config file might not exist in which we don't
+                    // In some tests the viewer config file might not exist in which we don't
                     // want to provide config path to the user
-                    sServiceInstance = new PerfettoProtoLogImpl(null, null, sCacheUpdater);
+                    sServiceInstance = new PerfettoProtoLogImpl(sCacheUpdater, groups);
                 } else {
-                    sServiceInstance = new PerfettoProtoLogImpl(sViewerConfigPath, sCacheUpdater);
+                    sServiceInstance =
+                            new PerfettoProtoLogImpl(sViewerConfigPath, sCacheUpdater, groups);
                 }
             } else {
-                sServiceInstance = new LegacyProtoLogImpl(
+                var protologImpl = new LegacyProtoLogImpl(
                         sLegacyOutputFilePath, sLegacyViewerConfigPath, sCacheUpdater);
+                protologImpl.registerGroups(groups);
+                sServiceInstance = protologImpl;
             }
 
-            IProtoLogGroup[] groups = sLogGroups.values().toArray(new IProtoLogGroup[0]);
-            sServiceInstance.registerGroups(groups);
             sCacheUpdater.run();
         }
         return sServiceInstance;
diff --git a/core/java/com/android/internal/protolog/common/IProtoLog.java b/core/java/com/android/internal/protolog/common/IProtoLog.java
index f5695ac..d5c2ac1 100644
--- a/core/java/com/android/internal/protolog/common/IProtoLog.java
+++ b/core/java/com/android/internal/protolog/common/IProtoLog.java
@@ -68,10 +68,4 @@
      * @return If we need to log this group and level to either ProtoLog or Logcat.
      */
     boolean isEnabled(IProtoLogGroup group, LogLevel level);
-
-    /**
-     * Registers available protolog groups. A group must be registered before it can be used.
-     * @param protoLogGroups The groups to register for use in protolog.
-     */
-    void registerGroups(IProtoLogGroup... protoLogGroups);
 }
diff --git a/core/java/com/android/internal/util/RateLimitingCache.java b/core/java/com/android/internal/util/RateLimitingCache.java
new file mode 100644
index 0000000..9916076
--- /dev/null
+++ b/core/java/com/android/internal/util/RateLimitingCache.java
@@ -0,0 +1,135 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.util;
+
+import android.os.SystemClock;
+
+/**
+ * A speed/rate limiting cache that's used to cache a value to be returned as long as period hasn't
+ * elapsed and then fetches a new value after period has elapsed. Use this when AIDL calls are
+ * expensive but the value returned by those APIs don't change often enough (or the recency doesn't
+ * matter as much), to incur the cost every time. This class maintains the last fetch time and
+ * fetches a new value when period has passed. Do not use this for API calls that have side-effects.
+ * <p>
+ * By passing in an optional <code>count</code> during creation, this can be used as a rate
+ * limiter that allows up to <code>count</code> calls per period to be passed on to the query
+ * and then the cached value is returned for the remainder of the period. It uses a simple fixed
+ * window method to track rate. Use a window and count appropriate for bursts of calls and for
+ * high latency/cost of the AIDL call.
+ *
+ * @param <Value> The type of the return value
+ * @hide
+ */
+@android.ravenwood.annotation.RavenwoodKeepWholeClass
+public class RateLimitingCache<Value> {
+
+    private volatile Value mCurrentValue;
+    private volatile long mLastTimestamp; // Can be last fetch time or window start of fetch time
+    private final long mPeriodMillis; // window size
+    private final int mLimit; // max per window
+    private int mCount = 0; // current count within window
+    private long mRandomOffset; // random offset to avoid batching of AIDL calls at window boundary
+
+    /**
+     * The interface to fetch the actual value, if the cache is null or expired.
+     * @hide
+     * @param <V> The return value type
+     */
+    public interface ValueFetcher<V> {
+        /** Called when the cache needs to be updated.
+         * @return the latest value fetched from the source
+         */
+        V fetchValue();
+    }
+
+    /**
+     * Create a speed limiting cache that returns the same value until periodMillis has passed
+     * and then fetches a new value via the {@link ValueFetcher}.
+     *
+     * @param periodMillis time to wait before fetching a new value. Use a negative period to
+     *                     indicate the value never changes and is fetched only once and
+     *                     cached. A value of 0 will mean always fetch a new value.
+     */
+    public RateLimitingCache(long periodMillis) {
+        this(periodMillis, 1);
+    }
+
+    /**
+     * Create a rate-limiting cache that allows up to <code>count</code> number of AIDL calls per
+     * period before it starts returning a cached value. The count resets when the next period
+     * begins.
+     *
+     * @param periodMillis the window of time in which <code>count</code> calls will fetch the
+     *                     newest value from the AIDL call.
+     * @param count how many times during the period it's ok to forward the request to the fetcher
+     *              in the {@link #get(ValueFetcher)} method.
+     */
+    public RateLimitingCache(long periodMillis, int count) {
+        mPeriodMillis = periodMillis;
+        mLimit = count;
+        if (mLimit > 1 && periodMillis > 1) {
+            mRandomOffset = (long) (Math.random() * (periodMillis / 2));
+        }
+    }
+
+    /**
+     * Returns the current time in <code>elapsedRealtime</code>. Can be overridden to use
+     * a different timebase that is monotonically increasing; for example, uptimeMillis()
+     * @return a monotonically increasing time in milliseconds
+     */
+    protected long getTime() {
+        return SystemClock.elapsedRealtime();
+    }
+
+    /**
+     * Returns either the cached value, if called more frequently than the specific rate, or
+     * a new value is fetched and cached. Warning: if the caller is likely to mutate the returned
+     * object, override this method and make a clone before returning it.
+     * @return the cached or latest value
+     */
+    public Value get(ValueFetcher<Value> query) {
+        // If the value never changes
+        if (mPeriodMillis < 0 && mLastTimestamp != 0) {
+            return mCurrentValue;
+        }
+
+        synchronized (this) {
+            // Get the current time and add a random offset to avoid colliding with other
+            // caches with similar harmonic window boundaries
+            final long now = getTime() + mRandomOffset;
+            final boolean newWindow = now - mLastTimestamp >= mPeriodMillis;
+            if (newWindow || mCount < mLimit) {
+                // Fetch a new value
+                mCurrentValue = query.fetchValue();
+
+                // If rate limiting, set timestamp to start of this window
+                if (mLimit > 1) {
+                    mLastTimestamp = now - (now % mPeriodMillis);
+                } else {
+                    mLastTimestamp = now;
+                }
+
+                if (newWindow) {
+                    mCount = 1;
+                } else {
+                    mCount++;
+                }
+            }
+            return mCurrentValue;
+        }
+    }
+}
diff --git a/core/jni/android_database_SQLiteRawStatement.cpp b/core/jni/android_database_SQLiteRawStatement.cpp
index 9614864..85a6bdf 100644
--- a/core/jni/android_database_SQLiteRawStatement.cpp
+++ b/core/jni/android_database_SQLiteRawStatement.cpp
@@ -72,14 +72,17 @@
 
 
 // This throws a SQLiteBindOrColumnIndexOutOfRangeException if the column index is out
-// of bounds.
-static void throwIfInvalidColumn(JNIEnv *env, jlong stmtPtr, jint col) {
+// of bounds.  It returns true if an exception was thrown.
+static bool throwIfInvalidColumn(JNIEnv *env, jlong stmtPtr, jint col) {
     if (col < 0 || col >= sqlite3_data_count(stmt(stmtPtr))) {
         int count = sqlite3_data_count(stmt(stmtPtr));
         std::string message = android::base::StringPrintf(
             "column index %d out of bounds [0,%d]", col, count - 1);
         char const * errmsg = sqlite3_errstr(SQLITE_RANGE);
         throw_sqlite3_exception(env, SQLITE_RANGE, errmsg, message.c_str());
+        return true;
+    } else {
+        return false;
     }
 }
 
@@ -216,12 +219,16 @@
 
 
 static jint columnType(JNIEnv* env, jclass, jlong stmtPtr, jint col) {
-    throwIfInvalidColumn(env, stmtPtr, col);
+    if (throwIfInvalidColumn(env, stmtPtr, col)) {
+        return 0;
+    }
     return sqlite3_column_type(stmt(stmtPtr), col);
 }
 
 static jstring columnName(JNIEnv* env, jclass, jlong stmtPtr, jint col) {
-    throwIfInvalidColumn(env, stmtPtr, col);
+    if (throwIfInvalidColumn(env, stmtPtr, col)) {
+        return nullptr;
+    }
     const jchar* name = static_cast<const jchar*>(sqlite3_column_name16(stmt(stmtPtr), col));
     if (name == nullptr) {
         throw_sqlite3_exception(env, db(stmtPtr), "error fetching columnName()");
@@ -232,14 +239,18 @@
 }
 
 static jint columnBytes(JNIEnv* env, jclass, jlong stmtPtr, jint col) {
-    throwIfInvalidColumn(env, stmtPtr, col);
+    if (throwIfInvalidColumn(env, stmtPtr, col)) {
+        return 0;
+    }
     int r = sqlite3_column_bytes16(stmt(stmtPtr), col);
     throwIfError(env, stmtPtr);
     return r;
 }
 
 static jbyteArray columnBlob(JNIEnv* env, jclass, jlong stmtPtr, jint col) {
-    throwIfInvalidColumn(env, stmtPtr, col);
+    if (throwIfInvalidColumn(env, stmtPtr, col)) {
+        return nullptr;
+    }
     const void* blob = sqlite3_column_blob(stmt(stmtPtr), col);
     if (blob == nullptr) {
         if (throwIfError(env, stmtPtr)) {
@@ -262,7 +273,9 @@
 
 static int columnBuffer(JNIEnv* env, jclass, jlong stmtPtr, jint col,
         jbyteArray buffer, jint offset, jint length, jint srcOffset) {
-    throwIfInvalidColumn(env, stmtPtr, col);
+    if (throwIfInvalidColumn(env, stmtPtr, col)) {
+        return 0;
+    }
     const void* blob = sqlite3_column_blob(stmt(stmtPtr), col);
     if (blob == nullptr) {
         throwIfError(env, stmtPtr);
@@ -281,22 +294,30 @@
 }
 
 static jdouble columnDouble(JNIEnv* env, jclass, jlong stmtPtr, jint col) {
-    throwIfInvalidColumn(env, stmtPtr, col);
+    if (throwIfInvalidColumn(env, stmtPtr, col)) {
+        return 0;
+    }
     return sqlite3_column_double(stmt(stmtPtr), col);
 }
 
 static jint columnInt(JNIEnv* env, jclass, jlong stmtPtr, jint col) {
-    throwIfInvalidColumn(env, stmtPtr, col);
+    if (throwIfInvalidColumn(env, stmtPtr, col)) {
+        return 0;
+    }
     return sqlite3_column_int(stmt(stmtPtr), col);
 }
 
 static jlong columnLong(JNIEnv* env, jclass, jlong stmtPtr, jint col) {
-    throwIfInvalidColumn(env, stmtPtr, col);
+    if (throwIfInvalidColumn(env, stmtPtr, col)) {
+        return 0;
+    }
     return sqlite3_column_int64(stmt(stmtPtr), col);
 }
 
 static jstring columnText(JNIEnv* env, jclass, jlong stmtPtr, jint col) {
-    throwIfInvalidColumn(env, stmtPtr, col);
+    if (throwIfInvalidColumn(env, stmtPtr, col)) {
+        return nullptr;
+    }
     const jchar* text = static_cast<const jchar*>(sqlite3_column_text16(stmt(stmtPtr), col));
     if (text == nullptr) {
         throwIfError(env, stmtPtr);
diff --git a/core/jni/android_os_SELinux.cpp b/core/jni/android_os_SELinux.cpp
index 84ca1ba..7a4670f4 100644
--- a/core/jni/android_os_SELinux.cpp
+++ b/core/jni/android_os_SELinux.cpp
@@ -53,7 +53,7 @@
 }
 
 struct SecurityContext_Delete {
-    void operator()(security_context_t p) const {
+    void operator()(char* p) const {
         freecon(p);
     }
 };
@@ -111,7 +111,7 @@
         return NULL;
     }
 
-    security_context_t tmp = NULL;
+    char* tmp = NULL;
     if (selabel_lookup(selabel_handle, &tmp, path_c_str, S_IFREG) != 0) {
       ALOGE("fileSelabelLookup => selabel_lookup for %s failed: %d", path_c_str, errno);
       return NULL;
@@ -138,7 +138,7 @@
         return NULL;
     }
 
-    security_context_t tmp = NULL;
+    char* tmp = NULL;
     int ret;
     if (isSocket) {
         ret = getpeercon(fd, &tmp);
@@ -184,7 +184,7 @@
  * Function: setFSCreateCon
  * Purpose: set security context used for creating a new file system object
  * Parameters:
- *       context: security_context_t representing the new context of a file system object,
+ *       context: char* representing the new context of a file system object,
  *                set to NULL to return to the default policy behavior
  * Returns: true on success, false on error
  * Exception: none
@@ -267,7 +267,7 @@
         return NULL;
     }
 
-    security_context_t tmp = NULL;
+    char* tmp = NULL;
     int ret = getfilecon(path.c_str(), &tmp);
     Unique_SecurityContext context(tmp);
 
@@ -293,7 +293,7 @@
         return NULL;
     }
 
-    security_context_t tmp = NULL;
+    char* tmp = NULL;
     int ret = getcon(&tmp);
     Unique_SecurityContext context(tmp);
 
@@ -320,7 +320,7 @@
         return NULL;
     }
 
-    security_context_t tmp = NULL;
+    char* tmp = NULL;
     int ret = getpidcon(static_cast<pid_t>(pid), &tmp);
     Unique_SecurityContext context(tmp);
 
diff --git a/core/jni/android_window_ScreenCapture.cpp b/core/jni/android_window_ScreenCapture.cpp
index 1031542..b1a2cea 100644
--- a/core/jni/android_window_ScreenCapture.cpp
+++ b/core/jni/android_window_ScreenCapture.cpp
@@ -19,6 +19,7 @@
 
 #include <android/gui/BnScreenCaptureListener.h>
 #include <android_runtime/android_hardware_HardwareBuffer.h>
+#include <gui/AidlStatusUtil.h>
 #include <gui/SurfaceComposerClient.h>
 #include <jni.h>
 #include <nativehelper/JNIHelp.h>
@@ -141,12 +142,13 @@
 };
 
 static void getCaptureArgs(JNIEnv* env, jobject captureArgsObject, CaptureArgs& captureArgs) {
-    captureArgs.pixelFormat = static_cast<ui::PixelFormat>(
+    captureArgs.pixelFormat = static_cast<int32_t>(
             env->GetIntField(captureArgsObject, gCaptureArgsClassInfo.pixelFormat));
-    captureArgs.sourceCrop =
+    const auto sourceCrop =
             JNICommon::rectFromObj(env,
                                    env->GetObjectField(captureArgsObject,
                                                        gCaptureArgsClassInfo.sourceCrop));
+    captureArgs.sourceCrop = gui::aidl_utils::toARect(sourceCrop);
     captureArgs.frameScaleX =
             env->GetFloatField(captureArgsObject, gCaptureArgsClassInfo.frameScaleX);
     captureArgs.frameScaleY =
@@ -172,7 +174,7 @@
                 jniThrowNullPointerException(env, "Exclude layer is null");
                 return;
             }
-            captureArgs.excludeHandles.emplace(excludeObject->getHandle());
+            captureArgs.excludeHandles.emplace_back(excludeObject->getHandle());
         }
     }
     captureArgs.hintForSeamlessTransition =
@@ -182,18 +184,18 @@
 
 static DisplayCaptureArgs displayCaptureArgsFromObject(JNIEnv* env,
                                                        jobject displayCaptureArgsObject) {
-    DisplayCaptureArgs captureArgs;
-    getCaptureArgs(env, displayCaptureArgsObject, captureArgs);
+    DisplayCaptureArgs displayCaptureArgs;
+    getCaptureArgs(env, displayCaptureArgsObject, displayCaptureArgs.captureArgs);
 
-    captureArgs.displayToken =
+    displayCaptureArgs.displayToken =
             ibinderForJavaObject(env,
                                  env->GetObjectField(displayCaptureArgsObject,
                                                      gDisplayCaptureArgsClassInfo.displayToken));
-    captureArgs.width =
+    displayCaptureArgs.width =
             env->GetIntField(displayCaptureArgsObject, gDisplayCaptureArgsClassInfo.width);
-    captureArgs.height =
+    displayCaptureArgs.height =
             env->GetIntField(displayCaptureArgsObject, gDisplayCaptureArgsClassInfo.height);
-    return captureArgs;
+    return displayCaptureArgs;
 }
 
 static jint nativeCaptureDisplay(JNIEnv* env, jclass clazz, jobject displayCaptureArgsObject,
@@ -212,8 +214,8 @@
 
 static jint nativeCaptureLayers(JNIEnv* env, jclass clazz, jobject layerCaptureArgsObject,
                                 jlong screenCaptureListenerObject, jboolean sync) {
-    LayerCaptureArgs captureArgs;
-    getCaptureArgs(env, layerCaptureArgsObject, captureArgs);
+    LayerCaptureArgs layerCaptureArgs;
+    getCaptureArgs(env, layerCaptureArgsObject, layerCaptureArgs.captureArgs);
 
     SurfaceControl* layer = reinterpret_cast<SurfaceControl*>(
             env->GetLongField(layerCaptureArgsObject, gLayerCaptureArgsClassInfo.layer));
@@ -221,13 +223,13 @@
         return BAD_VALUE;
     }
 
-    captureArgs.layerHandle = layer->getHandle();
-    captureArgs.childrenOnly =
+    layerCaptureArgs.layerHandle = layer->getHandle();
+    layerCaptureArgs.childrenOnly =
             env->GetBooleanField(layerCaptureArgsObject, gLayerCaptureArgsClassInfo.childrenOnly);
 
     sp<gui::IScreenCaptureListener> captureListener =
             reinterpret_cast<gui::IScreenCaptureListener*>(screenCaptureListenerObject);
-    return ScreenshotClient::captureLayers(captureArgs, captureListener, sync);
+    return ScreenshotClient::captureLayers(layerCaptureArgs, captureListener, sync);
 }
 
 static jlong nativeCreateScreenCaptureListener(JNIEnv* env, jclass clazz, jobject consumerObj) {
diff --git a/core/jni/com_android_internal_os_Zygote.cpp b/core/jni/com_android_internal_os_Zygote.cpp
index 9112d37..284c299 100644
--- a/core/jni/com_android_internal_os_Zygote.cpp
+++ b/core/jni/com_android_internal_os_Zygote.cpp
@@ -19,14 +19,6 @@
 
 #include "com_android_internal_os_Zygote.h"
 
-#include <async_safe/log.h>
-
-// sys/mount.h has to come before linux/fs.h due to redefinition of MS_RDONLY, MS_BIND, etc
-#include <sys/mount.h>
-#include <linux/fs.h>
-#include <sys/types.h>
-#include <dirent.h>
-
 #include <algorithm>
 #include <array>
 #include <atomic>
@@ -41,19 +33,18 @@
 
 #include <android/fdsan.h>
 #include <arpa/inet.h>
+#include <dirent.h>
 #include <fcntl.h>
 #include <grp.h>
 #include <inttypes.h>
 #include <malloc.h>
 #include <mntent.h>
-#include <paths.h>
 #include <signal.h>
 #include <stdio.h>
 #include <stdlib.h>
-#include <sys/auxv.h>
 #include <sys/capability.h>
-#include <sys/cdefs.h>
 #include <sys/eventfd.h>
+#include <sys/mount.h>
 #include <sys/personality.h>
 #include <sys/prctl.h>
 #include <sys/resource.h>
@@ -66,6 +57,7 @@
 #include <sys/wait.h>
 #include <unistd.h>
 
+#include <async_safe/log.h>
 #include <android-base/file.h>
 #include <android-base/logging.h>
 #include <android-base/properties.h>
diff --git a/core/res/Android.bp b/core/res/Android.bp
index e900eb2..bcc0a97 100644
--- a/core/res/Android.bp
+++ b/core/res/Android.bp
@@ -156,6 +156,7 @@
     generate_product_characteristics_rro: true,
 
     flags_packages: [
+        "android.app.appfunctions.flags-aconfig",
         "android.app.contextualsearch.flags-aconfig",
         "android.content.pm.flags-aconfig",
         "android.provider.flags-aconfig",
@@ -164,6 +165,7 @@
         "com.android.window.flags.window-aconfig",
         "android.permission.flags-aconfig",
         "android.os.flags-aconfig",
+        "android.os.vibrator.flags-aconfig",
         "android.media.tv.flags-aconfig",
     ],
 }
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 7aeabee..160f651 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -2617,7 +2617,8 @@
          @hide
     -->
     <permission android:name="android.permission.VIBRATE_VENDOR_EFFECTS"
-        android:protectionLevel="signature|privileged" />
+        android:protectionLevel="signature|privileged"
+        android:featureFlag="android.os.vibrator.vendor_vibration_effects" />
 
     <!-- @SystemApi Allows access to the vibrator state.
          <p>Protection level: signature
@@ -8010,6 +8011,41 @@
     <permission android:name="android.permission.EXECUTE_APP_ACTION"
                 android:protectionLevel="internal|role" />
 
+    <!-- Must be required by an {@link android.app.appfunctions.AppFunctionService},
+         to ensure that only the system can bind to it.
+         <p>Protection level: signature
+         @FlaggedApi("android.app.appfunctions.flags.enable_app_function_manager")  -->
+    <permission android:name="android.permission.BIND_APP_FUNCTION_SERVICE"
+        android:featureFlag="android.app.appfunctions.flags.enable_app_function_manager"
+        android:protectionLevel="signature" />
+
+    <!-- @SystemApi Allows a trusted application to perform actions on behalf of users inside of
+         applications with privacy guarantees from the system.
+         <p>This permission is currently only granted to system packages in the
+         {@link android.app.role.SYSTEM_UI_INTELLIGENCE} role which complies with privacy
+         requirements outlined in the Android CDD section "9.8.6 Content Capture".
+         <p>Apps are not able to opt-out from caller having this permission.
+         <p>Protection level: internal|role
+         @hide
+         @FlaggedApi("android.app.appfunctions.flags.enable_app_function_manager")  -->
+    <permission android:name="android.permission.EXECUTE_APP_FUNCTIONS_TRUSTED"
+        android:featureFlag="android.app.appfunctions.flags.enable_app_function_manager"
+        android:protectionLevel="internal|role" />
+
+    <!-- @SystemApi Allows an application to perform actions on behalf of users inside of
+         applications.
+         <p>This permission is currently only granted to preinstalled / system apps having the
+         {@link android.app.role.ASSISTANT} role.
+         <p>Apps contributing app functions can opt to disallow callers with this permission,
+         limiting to only callers with {@link android.permission.EXECUTE_APP_FUNCTIONS_TRUSTED}
+         instead.
+         <p>Protection level: internal|role
+         @hide
+         @FlaggedApi("android.app.appfunctions.flags.enable_app_function_manager")  -->
+    <permission android:name="android.permission.EXECUTE_APP_FUNCTIONS"
+        android:featureFlag="android.app.appfunctions.flags.enable_app_function_manager"
+        android:protectionLevel="internal|role" />
+
     <!-- Allows an application to display its suggestions using the autofill framework.
          <p>For now, this permission is only granted to the Browser application.
          <p>Protection level: internal|role
@@ -8132,10 +8168,10 @@
     <permission android:name="android.permission.MONITOR_STICKY_MODIFIER_STATE"
                 android:protectionLevel="signature" />
 
-    <!-- Allows low-level access to monitor keyboard system shortcuts
+    <!-- Allows low-level access to manage key gestures.
          <p>Not for use by third-party applications.
          @hide -->
-    <permission android:name="android.permission.MONITOR_KEYBOARD_SYSTEM_SHORTCUTS"
+    <permission android:name="android.permission.MANAGE_KEY_GESTURES"
                 android:protectionLevel="signature" />
 
     <uses-permission android:name="android.permission.HANDLE_QUERY_PACKAGE_RESTART" />
diff --git a/core/res/res/drawable-car/car_activity_resolver_list_background.xml b/core/res/res/drawable-car/car_activity_resolver_list_background.xml
deleted file mode 100644
index dbbadd8..0000000
--- a/core/res/res/drawable-car/car_activity_resolver_list_background.xml
+++ /dev/null
@@ -1,20 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!-- Copyright (C) 2024 The Android Open Source Project
-
-     Licensed under the Apache License, Version 2.0 (the "License");
-     you may not use this file except in compliance with the License.
-     You may obtain a copy of the License at
-
-          http://www.apache.org/licenses/LICENSE-2.0
-
-     Unless required by applicable law or agreed to in writing, software
-     distributed under the License is distributed on an "AS IS" BASIS,
-     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-     See the License for the specific language governing permissions and
-     limitations under the License.
--->
-<shape xmlns:android="http://schemas.android.com/apk/res/android"
-    android:shape="rectangle">
-    <solid android:color="?attr/colorBackgroundFloating" />
-    <corners android:radius="@dimen/car_activity_resolver_corner_radius" />
-</shape>
\ No newline at end of file
diff --git a/core/res/res/drawable/ic_zen_mode_icon_animal_paw.xml b/core/res/res/drawable/ic_zen_mode_icon_animal_paw.xml
new file mode 100644
index 0000000..31004ec
--- /dev/null
+++ b/core/res/res/drawable/ic_zen_mode_icon_animal_paw.xml
@@ -0,0 +1,25 @@
+<!--
+Copyright (C) 2024 The Android Open Source Project
+
+   Licensed under the Apache License, Version 2.0 (the "License");
+    you may not use this file except in compliance with the License.
+    You may obtain a copy of the License at
+
+         http://www.apache.org/licenses/LICENSE-2.0
+
+    Unless required by applicable law or agreed to in writing, software
+    distributed under the License is distributed on an "AS IS" BASIS,
+    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+    See the License for the specific language governing permissions and
+    limitations under the License.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24dp"
+    android:height="24dp"
+    android:tint="?android:attr/colorControlNormal"
+    android:viewportHeight="960"
+    android:viewportWidth="960">
+    <path
+        android:fillColor="@android:color/white"
+        android:pathData="M180,485Q138,485 109,456Q80,427 80,385Q80,343 109,314Q138,285 180,285Q222,285 251,314Q280,343 280,385Q280,427 251,456Q222,485 180,485ZM360,325Q318,325 289,296Q260,267 260,225Q260,183 289,154Q318,125 360,125Q402,125 431,154Q460,183 460,225Q460,267 431,296Q402,325 360,325ZM600,325Q558,325 529,296Q500,267 500,225Q500,183 529,154Q558,125 600,125Q642,125 671,154Q700,183 700,225Q700,267 671,296Q642,325 600,325ZM780,485Q738,485 709,456Q680,427 680,385Q680,343 709,314Q738,285 780,285Q822,285 851,314Q880,343 880,385Q880,427 851,456Q822,485 780,485ZM266,885Q221,885 190.5,850.5Q160,816 160,769Q160,717 195.5,678Q231,639 266,601Q295,570 316,533.5Q337,497 366,465Q388,439 417,422Q446,405 480,405Q514,405 543,421Q572,437 594,463Q622,495 643.5,532Q665,569 694,601Q729,639 764.5,678Q800,717 800,769Q800,816 769.5,850.5Q739,885 694,885Q640,885 587,876Q534,867 480,867Q426,867 373,876Q320,885 266,885Z" />
+</vector>
\ No newline at end of file
diff --git a/core/res/res/drawable/ic_zen_mode_icon_apartment_building.xml b/core/res/res/drawable/ic_zen_mode_icon_apartment_building.xml
new file mode 100644
index 0000000..30f01fa
--- /dev/null
+++ b/core/res/res/drawable/ic_zen_mode_icon_apartment_building.xml
@@ -0,0 +1,25 @@
+<!--
+Copyright (C) 2024 The Android Open Source Project
+
+   Licensed under the Apache License, Version 2.0 (the "License");
+    you may not use this file except in compliance with the License.
+    You may obtain a copy of the License at
+
+         http://www.apache.org/licenses/LICENSE-2.0
+
+    Unless required by applicable law or agreed to in writing, software
+    distributed under the License is distributed on an "AS IS" BASIS,
+    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+    See the License for the specific language governing permissions and
+    limitations under the License.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24dp"
+    android:height="24dp"
+    android:tint="?android:attr/colorControlNormal"
+    android:viewportHeight="960"
+    android:viewportWidth="960">
+    <path
+        android:fillColor="@android:color/white"
+        android:pathData="M120,840L120,280L280,280L280,120L680,120L680,440L840,440L840,840L520,840L520,680L440,680L440,840L120,840ZM200,760L280,760L280,680L200,680L200,760ZM200,600L280,600L280,520L200,520L200,600ZM200,440L280,440L280,360L200,360L200,440ZM360,600L440,600L440,520L360,520L360,600ZM360,440L440,440L440,360L360,360L360,440ZM360,280L440,280L440,200L360,200L360,280ZM520,600L600,600L600,520L520,520L520,600ZM520,440L600,440L600,360L520,360L520,440ZM520,280L600,280L600,200L520,200L520,280ZM680,760L760,760L760,680L680,680L680,760ZM680,600L760,600L760,520L680,520L680,600Z" />
+</vector>
\ No newline at end of file
diff --git a/core/res/res/drawable/ic_zen_mode_icon_book.xml b/core/res/res/drawable/ic_zen_mode_icon_book.xml
new file mode 100644
index 0000000..c30d222
--- /dev/null
+++ b/core/res/res/drawable/ic_zen_mode_icon_book.xml
@@ -0,0 +1,26 @@
+<!--
+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.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24dp"
+    android:height="24dp"
+    android:tint="?android:attr/colorControlNormal"
+    android:viewportHeight="960"
+    android:viewportWidth="960"
+    android:autoMirrored="true">
+    <path
+        android:fillColor="@android:color/white"
+        android:pathData="M560,396L560,328Q593,314 627.5,307Q662,300 700,300Q726,300 751,304Q776,308 800,314L800,378Q776,369 751.5,364.5Q727,360 700,360Q662,360 627,369.5Q592,379 560,396ZM560,616L560,548Q593,534 627.5,527Q662,520 700,520Q726,520 751,524Q776,528 800,534L800,598Q776,589 751.5,584.5Q727,580 700,580Q662,580 627,589Q592,598 560,616ZM560,506L560,438Q593,424 627.5,417Q662,410 700,410Q726,410 751,414Q776,418 800,424L800,488Q776,479 751.5,474.5Q727,470 700,470Q662,470 627,479.5Q592,489 560,506ZM260,640Q307,640 351.5,650.5Q396,661 440,682L440,288Q399,264 353,252Q307,240 260,240Q224,240 188.5,247Q153,254 120,268Q120,268 120,268Q120,268 120,268L120,664Q120,664 120,664Q120,664 120,664Q155,652 189.5,646Q224,640 260,640ZM520,682Q564,661 608.5,650.5Q653,640 700,640Q736,640 770.5,646Q805,652 840,664Q840,664 840,664Q840,664 840,664L840,268Q840,268 840,268Q840,268 840,268Q807,254 771.5,247Q736,240 700,240Q653,240 607,252Q561,264 520,288L520,682ZM480,800Q432,762 376,741Q320,720 260,720Q218,720 177.5,731Q137,742 100,762Q79,773 59.5,761Q40,749 40,726L40,244Q40,233 45.5,223Q51,213 62,208Q108,184 158,172Q208,160 260,160Q318,160 373.5,175Q429,190 480,220Q531,190 586.5,175Q642,160 700,160Q752,160 802,172Q852,184 898,208Q909,213 914.5,223Q920,233 920,244L920,726Q920,749 900.5,761Q881,773 860,762Q823,742 782.5,731Q742,720 700,720Q640,720 584,741Q528,762 480,800ZM280,466Q280,466 280,466Q280,466 280,466L280,466Q280,466 280,466Q280,466 280,466Q280,466 280,466Q280,466 280,466Q280,466 280,466Q280,466 280,466L280,466Q280,466 280,466Q280,466 280,466Q280,466 280,466Q280,466 280,466Z" />
+</vector>
\ No newline at end of file
diff --git a/core/res/res/drawable/ic_zen_mode_icon_child.xml b/core/res/res/drawable/ic_zen_mode_icon_child.xml
new file mode 100644
index 0000000..d11772a
--- /dev/null
+++ b/core/res/res/drawable/ic_zen_mode_icon_child.xml
@@ -0,0 +1,25 @@
+<!--
+Copyright (C) 2024 The Android Open Source Project
+
+   Licensed under the Apache License, Version 2.0 (the "License");
+    you may not use this file except in compliance with the License.
+    You may obtain a copy of the License at
+
+         http://www.apache.org/licenses/LICENSE-2.0
+
+    Unless required by applicable law or agreed to in writing, software
+    distributed under the License is distributed on an "AS IS" BASIS,
+    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+    See the License for the specific language governing permissions and
+    limitations under the License.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24dp"
+    android:height="24dp"
+    android:tint="?android:attr/colorControlNormal"
+    android:viewportHeight="960"
+    android:viewportWidth="960">
+    <path
+        android:fillColor="@android:color/white"
+        android:pathData="M580,470Q559,470 544.5,455.5Q530,441 530,420Q530,399 544.5,384.5Q559,370 580,370Q601,370 615.5,384.5Q630,399 630,420Q630,441 615.5,455.5Q601,470 580,470ZM380,470Q359,470 344.5,455.5Q330,441 330,420Q330,399 344.5,384.5Q359,370 380,370Q401,370 415.5,384.5Q430,399 430,420Q430,441 415.5,455.5Q401,470 380,470ZM480,680Q420,680 371.5,647Q323,614 300,560L660,560Q637,614 588.5,647Q540,680 480,680ZM480,840Q405,840 339.5,811.5Q274,783 225.5,734.5Q177,686 148.5,620.5Q120,555 120,480Q120,405 148.5,339.5Q177,274 225.5,225.5Q274,177 339.5,148.5Q405,120 480,120Q555,120 620.5,148.5Q686,177 734.5,225.5Q783,274 811.5,339.5Q840,405 840,480Q840,555 811.5,620.5Q783,686 734.5,734.5Q686,783 620.5,811.5Q555,840 480,840ZM480,760Q596,760 678,678Q760,596 760,480Q760,364 678,282Q596,200 480,200Q474,200 468,200Q462,200 456,202Q450,208 448,215Q446,222 446,230Q446,251 460.5,265.5Q475,280 496,280Q505,280 512.5,277Q520,274 528,274Q540,274 548,283Q556,292 556,304Q556,327 534.5,333.5Q513,340 496,340Q451,340 418.5,307.5Q386,275 386,230Q386,227 386,224Q386,221 387,216Q304,246 252,317Q200,388 200,480Q200,596 282,678Q364,760 480,760ZM480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Z" />
+</vector>
\ No newline at end of file
diff --git a/core/res/res/drawable/ic_zen_mode_icon_classical_building.xml b/core/res/res/drawable/ic_zen_mode_icon_classical_building.xml
new file mode 100644
index 0000000..2675126
--- /dev/null
+++ b/core/res/res/drawable/ic_zen_mode_icon_classical_building.xml
@@ -0,0 +1,25 @@
+<!--
+Copyright (C) 2024 The Android Open Source Project
+
+   Licensed under the Apache License, Version 2.0 (the "License");
+    you may not use this file except in compliance with the License.
+    You may obtain a copy of the License at
+
+         http://www.apache.org/licenses/LICENSE-2.0
+
+    Unless required by applicable law or agreed to in writing, software
+    distributed under the License is distributed on an "AS IS" BASIS,
+    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+    See the License for the specific language governing permissions and
+    limitations under the License.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24dp"
+    android:height="24dp"
+    android:tint="?android:attr/colorControlNormal"
+    android:viewportHeight="960"
+    android:viewportWidth="960">
+    <path
+        android:fillColor="@android:color/white"
+        android:pathData="M200,680L200,400L280,400L280,680L200,680ZM440,680L440,400L520,400L520,680L440,680ZM80,840L80,760L880,760L880,840L80,840ZM680,680L680,400L760,400L760,680L680,680ZM80,320L80,240L480,40L880,240L880,320L80,320ZM258,240L480,240L702,240L258,240ZM258,240L702,240L480,130L258,240Z" />
+</vector>
\ No newline at end of file
diff --git a/core/res/res/drawable/ic_zen_mode_icon_croissant.xml b/core/res/res/drawable/ic_zen_mode_icon_croissant.xml
new file mode 100644
index 0000000..199343d
--- /dev/null
+++ b/core/res/res/drawable/ic_zen_mode_icon_croissant.xml
@@ -0,0 +1,25 @@
+<!--
+Copyright (C) 2024 The Android Open Source Project
+
+   Licensed under the Apache License, Version 2.0 (the "License");
+    you may not use this file except in compliance with the License.
+    You may obtain a copy of the License at
+
+         http://www.apache.org/licenses/LICENSE-2.0
+
+    Unless required by applicable law or agreed to in writing, software
+    distributed under the License is distributed on an "AS IS" BASIS,
+    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+    See the License for the specific language governing permissions and
+    limitations under the License.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24dp"
+    android:height="24dp"
+    android:tint="?android:attr/colorControlNormal"
+    android:viewportHeight="960"
+    android:viewportWidth="960">
+    <path
+        android:fillColor="@android:color/white"
+        android:pathData="M804,678Q821,687 834,674Q847,661 838,644L780,536L738,644L804,678ZM604,640L652,640L748,402Q751,394 746.5,388.5Q742,383 736,380L656,348Q647,345 638.5,350Q630,355 628,364L604,640ZM308,640L356,640L332,364Q330,353 321.5,349Q313,345 304,348L224,380Q216,383 212.5,388.5Q209,394 212,402L308,640ZM156,678L222,644L180,536L122,644Q113,661 126,674Q139,687 156,678ZM436,640L524,640L554,302Q556,293 549.5,286.5Q543,280 534,280L426,280Q418,280 411.5,286.5Q405,293 406,302L436,640ZM138,760Q96,760 68,728.5Q40,697 40,654Q40,642 43.5,630.5Q47,619 52,608L140,440Q126,400 141,361Q156,322 194,306L274,274Q288,269 302,267Q316,265 330,268Q344,239 369,219.5Q394,200 426,200L534,200Q566,200 591,219.5Q616,239 630,268Q644,266 658,267.5Q672,269 686,274L766,306Q806,322 822,361Q838,400 820,438L908,606Q914,617 917,629Q920,641 920,654Q920,699 889.5,729.5Q859,760 814,760Q803,760 792,757.5Q781,755 770,750L708,720L250,720L194,750Q181,757 166.5,758.5Q152,760 138,760ZM480,480Q480,480 480,480Q480,480 480,480L480,480L480,480L480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480L480,480Q480,480 480,480Q480,480 480,480L480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480L480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480L480,480Q480,480 480,480Q480,480 480,480L480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Z" />
+</vector>
\ No newline at end of file
diff --git a/core/res/res/drawable/ic_zen_mode_icon_fork_and_knife.xml b/core/res/res/drawable/ic_zen_mode_icon_fork_and_knife.xml
new file mode 100644
index 0000000..1fa7379
--- /dev/null
+++ b/core/res/res/drawable/ic_zen_mode_icon_fork_and_knife.xml
@@ -0,0 +1,25 @@
+<!--
+Copyright (C) 2024 The Android Open Source Project
+
+   Licensed under the Apache License, Version 2.0 (the "License");
+    you may not use this file except in compliance with the License.
+    You may obtain a copy of the License at
+
+         http://www.apache.org/licenses/LICENSE-2.0
+
+    Unless required by applicable law or agreed to in writing, software
+    distributed under the License is distributed on an "AS IS" BASIS,
+    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+    See the License for the specific language governing permissions and
+    limitations under the License.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24dp"
+    android:height="24dp"
+    android:tint="?android:attr/colorControlNormal"
+    android:viewportHeight="960"
+    android:viewportWidth="960">
+    <path
+        android:fillColor="@android:color/white"
+        android:pathData="M280,880L280,514Q229,500 194.5,458Q160,416 160,360L160,80L240,80L240,360L280,360L280,80L360,80L360,360L400,360L400,80L480,80L480,360Q480,416 445.5,458Q411,500 360,514L360,880L280,880ZM680,880L680,560L560,560L560,280Q560,197 618.5,138.5Q677,80 760,80L760,880L680,880Z" />
+</vector>
\ No newline at end of file
diff --git a/core/res/res/drawable/ic_zen_mode_icon_group_of_people.xml b/core/res/res/drawable/ic_zen_mode_icon_group_of_people.xml
new file mode 100644
index 0000000..c6194d5
--- /dev/null
+++ b/core/res/res/drawable/ic_zen_mode_icon_group_of_people.xml
@@ -0,0 +1,25 @@
+<!--
+Copyright (C) 2024 The Android Open Source Project
+
+   Licensed under the Apache License, Version 2.0 (the "License");
+    you may not use this file except in compliance with the License.
+    You may obtain a copy of the License at
+
+         http://www.apache.org/licenses/LICENSE-2.0
+
+    Unless required by applicable law or agreed to in writing, software
+    distributed under the License is distributed on an "AS IS" BASIS,
+    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+    See the License for the specific language governing permissions and
+    limitations under the License.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24dp"
+    android:height="24dp"
+    android:tint="?android:attr/colorControlNormal"
+    android:viewportHeight="960"
+    android:viewportWidth="960">
+    <path
+        android:fillColor="@android:color/white"
+        android:pathData="M0,720L0,657Q0,614 44,587Q88,560 160,560Q173,560 185,560.5Q197,561 208,563Q194,584 187,607Q180,630 180,655L180,720L0,720ZM240,720L240,655Q240,623 257.5,596.5Q275,570 307,550Q339,530 383.5,520Q428,510 480,510Q533,510 577.5,520Q622,530 654,550Q686,570 703,596.5Q720,623 720,655L720,720L240,720ZM780,720L780,655Q780,629 773.5,606Q767,583 754,563Q765,561 776.5,560.5Q788,560 800,560Q872,560 916,586.5Q960,613 960,657L960,720L780,720ZM325,640L636,640L636,640Q626,620 580.5,605Q535,590 480,590Q425,590 379.5,605Q334,620 325,640ZM160,520Q127,520 103.5,496.5Q80,473 80,440Q80,406 103.5,383Q127,360 160,360Q194,360 217,383Q240,406 240,440Q240,473 217,496.5Q194,520 160,520ZM800,520Q767,520 743.5,496.5Q720,473 720,440Q720,406 743.5,383Q767,360 800,360Q834,360 857,383Q880,406 880,440Q880,473 857,496.5Q834,520 800,520ZM480,480Q430,480 395,445Q360,410 360,360Q360,309 395,274.5Q430,240 480,240Q531,240 565.5,274.5Q600,309 600,360Q600,410 565.5,445Q531,480 480,480ZM480,400Q497,400 508.5,388.5Q520,377 520,360Q520,343 508.5,331.5Q497,320 480,320Q463,320 451.5,331.5Q440,343 440,360Q440,377 451.5,388.5Q463,400 480,400ZM481,640L481,640Q481,640 481,640Q481,640 481,640Q481,640 481,640Q481,640 481,640L481,640ZM480,360Q480,360 480,360Q480,360 480,360Q480,360 480,360Q480,360 480,360Q480,360 480,360Q480,360 480,360Q480,360 480,360Q480,360 480,360Z" />
+</vector>
\ No newline at end of file
diff --git a/core/res/res/drawable/ic_zen_mode_icon_headphones.xml b/core/res/res/drawable/ic_zen_mode_icon_headphones.xml
new file mode 100644
index 0000000..f0bc7a2
--- /dev/null
+++ b/core/res/res/drawable/ic_zen_mode_icon_headphones.xml
@@ -0,0 +1,25 @@
+<!--
+Copyright (C) 2024 The Android Open Source Project
+
+   Licensed under the Apache License, Version 2.0 (the "License");
+    you may not use this file except in compliance with the License.
+    You may obtain a copy of the License at
+
+         http://www.apache.org/licenses/LICENSE-2.0
+
+    Unless required by applicable law or agreed to in writing, software
+    distributed under the License is distributed on an "AS IS" BASIS,
+    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+    See the License for the specific language governing permissions and
+    limitations under the License.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24dp"
+    android:height="24dp"
+    android:tint="?android:attr/colorControlNormal"
+    android:viewportHeight="960"
+    android:viewportWidth="960">
+    <path
+        android:fillColor="@android:color/white"
+        android:pathData="M360,840L200,840Q167,840 143.5,816.5Q120,793 120,760L120,480Q120,405 148.5,339.5Q177,274 225.5,225.5Q274,177 339.5,148.5Q405,120 480,120Q555,120 620.5,148.5Q686,177 734.5,225.5Q783,274 811.5,339.5Q840,405 840,480L840,760Q840,793 816.5,816.5Q793,840 760,840L600,840L600,520L760,520L760,480Q760,363 678.5,281.5Q597,200 480,200Q363,200 281.5,281.5Q200,363 200,480L200,520L360,520L360,840ZM280,600L200,600L200,760Q200,760 200,760Q200,760 200,760L280,760L280,600ZM680,600L680,760L760,760Q760,760 760,760Q760,760 760,760L760,600L680,600ZM280,600L280,600L200,600Q200,600 200,600Q200,600 200,600L200,600L280,600ZM680,600L760,600L760,600Q760,600 760,600Q760,600 760,600L680,600Z" />
+</vector>
\ No newline at end of file
diff --git a/core/res/res/drawable/ic_zen_mode_icon_heart.xml b/core/res/res/drawable/ic_zen_mode_icon_heart.xml
new file mode 100644
index 0000000..c9b1577
--- /dev/null
+++ b/core/res/res/drawable/ic_zen_mode_icon_heart.xml
@@ -0,0 +1,25 @@
+<!--
+Copyright (C) 2024 The Android Open Source Project
+
+   Licensed under the Apache License, Version 2.0 (the "License");
+    you may not use this file except in compliance with the License.
+    You may obtain a copy of the License at
+
+         http://www.apache.org/licenses/LICENSE-2.0
+
+    Unless required by applicable law or agreed to in writing, software
+    distributed under the License is distributed on an "AS IS" BASIS,
+    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+    See the License for the specific language governing permissions and
+    limitations under the License.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24dp"
+    android:height="24dp"
+    android:tint="?android:attr/colorControlNormal"
+    android:viewportHeight="960"
+    android:viewportWidth="960">
+    <path
+        android:fillColor="@android:color/white"
+        android:pathData="M480,840L422,788Q321,697 255,631Q189,565 150,512.5Q111,460 95.5,416Q80,372 80,326Q80,232 143,169Q206,106 300,106Q352,106 399,128Q446,150 480,190Q514,150 561,128Q608,106 660,106Q754,106 817,169Q880,232 880,326Q880,372 864.5,416Q849,460 810,512.5Q771,565 705,631Q639,697 538,788L480,840ZM480,732Q576,646 638,584.5Q700,523 736,477.5Q772,432 786,396.5Q800,361 800,326Q800,266 760,226Q720,186 660,186Q613,186 573,212.5Q533,239 518,280L518,280L442,280L442,280Q427,239 387,212.5Q347,186 300,186Q240,186 200,226Q160,266 160,326Q160,361 174,396.5Q188,432 224,477.5Q260,523 322,584.5Q384,646 480,732ZM480,459Q480,459 480,459Q480,459 480,459Q480,459 480,459Q480,459 480,459Q480,459 480,459Q480,459 480,459Q480,459 480,459Q480,459 480,459L480,459L480,459L480,459Q480,459 480,459Q480,459 480,459Q480,459 480,459Q480,459 480,459Q480,459 480,459Q480,459 480,459Q480,459 480,459Q480,459 480,459Z" />
+</vector>
\ No newline at end of file
diff --git a/core/res/res/drawable/ic_zen_mode_icon_house.xml b/core/res/res/drawable/ic_zen_mode_icon_house.xml
new file mode 100644
index 0000000..e25d194
--- /dev/null
+++ b/core/res/res/drawable/ic_zen_mode_icon_house.xml
@@ -0,0 +1,25 @@
+<!--
+Copyright (C) 2024 The Android Open Source Project
+
+   Licensed under the Apache License, Version 2.0 (the "License");
+    you may not use this file except in compliance with the License.
+    You may obtain a copy of the License at
+
+         http://www.apache.org/licenses/LICENSE-2.0
+
+    Unless required by applicable law or agreed to in writing, software
+    distributed under the License is distributed on an "AS IS" BASIS,
+    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+    See the License for the specific language governing permissions and
+    limitations under the License.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24dp"
+    android:height="24dp"
+    android:tint="?android:attr/colorControlNormal"
+    android:viewportHeight="960"
+    android:viewportWidth="960">
+    <path
+        android:fillColor="@android:color/white"
+        android:pathData="M160,840L160,465L88,520L40,456L160,364L160,240L240,240L240,303L480,120L920,456L872,519L800,465L800,840L160,840ZM240,760L440,760L440,600L520,600L520,760L720,760L720,404L480,221L240,404L240,760ZM160,200Q160,150 195,115Q230,80 280,80Q297,80 308.5,68.5Q320,57 320,40L400,40Q400,90 365,125Q330,160 280,160Q263,160 251.5,171.5Q240,183 240,200L160,200ZM240,760L440,760L440,760L520,760L520,760L720,760L720,760L480,760L240,760Z" />
+</vector>
\ No newline at end of file
diff --git a/core/res/res/drawable/ic_zen_mode_icon_lightbulb.xml b/core/res/res/drawable/ic_zen_mode_icon_lightbulb.xml
new file mode 100644
index 0000000..602e60d
--- /dev/null
+++ b/core/res/res/drawable/ic_zen_mode_icon_lightbulb.xml
@@ -0,0 +1,25 @@
+<!--
+Copyright (C) 2024 The Android Open Source Project
+
+   Licensed under the Apache License, Version 2.0 (the "License");
+    you may not use this file except in compliance with the License.
+    You may obtain a copy of the License at
+
+         http://www.apache.org/licenses/LICENSE-2.0
+
+    Unless required by applicable law or agreed to in writing, software
+    distributed under the License is distributed on an "AS IS" BASIS,
+    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+    See the License for the specific language governing permissions and
+    limitations under the License.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24dp"
+    android:height="24dp"
+    android:tint="?android:attr/colorControlNormal"
+    android:viewportHeight="960"
+    android:viewportWidth="960">
+    <path
+        android:fillColor="@android:color/white"
+        android:pathData="M480,880Q454,880 433,867.5Q412,855 400,834L400,834Q367,834 343.5,810.5Q320,787 320,754L320,612Q261,573 225.5,509Q190,445 190,370Q190,249 274.5,164.5Q359,80 480,80Q601,80 685.5,164.5Q770,249 770,370Q770,447 734.5,510Q699,573 640,612L640,754Q640,787 616.5,810.5Q593,834 560,834L560,834Q548,855 527,867.5Q506,880 480,880ZM400,754L560,754L560,718L400,718L400,754ZM400,678L560,678L560,640L400,640L400,678ZM392,560L450,560L450,452L362,364L404,322L480,398L556,322L598,364L510,452L510,560L568,560Q622,534 656,483.5Q690,433 690,370Q690,282 629,221Q568,160 480,160Q392,160 331,221Q270,282 270,370Q270,433 304,483.5Q338,534 392,560ZM480,398L480,398L480,398L480,398L480,398L480,398L480,398L480,398L480,398ZM480,360Q480,360 480,360Q480,360 480,360Q480,360 480,360Q480,360 480,360Q480,360 480,360Q480,360 480,360Q480,360 480,360Q480,360 480,360L480,360L480,360L480,360L480,360L480,360L480,360L480,360L480,360L480,360Z" />
+</vector>
\ No newline at end of file
diff --git a/core/res/res/drawable/ic_zen_mode_icon_lotus_flower.xml b/core/res/res/drawable/ic_zen_mode_icon_lotus_flower.xml
new file mode 100644
index 0000000..c1afd44
--- /dev/null
+++ b/core/res/res/drawable/ic_zen_mode_icon_lotus_flower.xml
@@ -0,0 +1,25 @@
+<!--
+Copyright (C) 2024 The Android Open Source Project
+
+   Licensed under the Apache License, Version 2.0 (the "License");
+    you may not use this file except in compliance with the License.
+    You may obtain a copy of the License at
+
+         http://www.apache.org/licenses/LICENSE-2.0
+
+    Unless required by applicable law or agreed to in writing, software
+    distributed under the License is distributed on an "AS IS" BASIS,
+    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+    See the License for the specific language governing permissions and
+    limitations under the License.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24dp"
+    android:height="24dp"
+    android:tint="?android:attr/colorControlNormal"
+    android:viewportHeight="960"
+    android:viewportWidth="960">
+    <path
+        android:fillColor="@android:color/white"
+        android:pathData="M480,880Q407,871 335,840.5Q263,810 206.5,753Q150,696 115,609Q80,522 80,400L80,360L120,360Q171,360 225,373Q279,386 326,412Q338,326 380.5,235.5Q423,145 480,80Q537,145 579.5,235.5Q622,326 634,412Q681,386 735,373Q789,360 840,360L880,360L880,400Q880,522 845,609Q810,696 753.5,753Q697,810 625.5,840.5Q554,871 480,880ZM478,798Q467,632 379.5,547Q292,462 162,442Q173,613 263.5,697Q354,781 478,798ZM480,544Q495,522 516.5,498.5Q538,475 558,458Q556,401 535.5,339Q515,277 480,218Q445,277 424.5,339Q404,401 402,458Q422,475 444,498.5Q466,522 480,544ZM558,780Q595,768 635,745Q675,722 709.5,682.5Q744,643 768.5,584Q793,525 798,442Q704,456 633,504.5Q562,553 524,628Q536,660 544.5,698Q553,736 558,780ZM480,544Q480,544 480,544Q480,544 480,544Q480,544 480,544Q480,544 480,544Q480,544 480,544Q480,544 480,544Q480,544 480,544Q480,544 480,544ZM558,780Q558,780 558,780Q558,780 558,780Q558,780 558,780Q558,780 558,780Q558,780 558,780Q558,780 558,780Q558,780 558,780Q558,780 558,780ZM478,798Q478,798 478,798Q478,798 478,798Q478,798 478,798Q478,798 478,798ZM524,628L524,628Q524,628 524,628Q524,628 524,628L524,628L524,628L524,628Q524,628 524,628Q524,628 524,628L524,628Q524,628 524,628Q524,628 524,628ZM480,880L480,880Q480,880 480,880Q480,880 480,880Q480,880 480,880Q480,880 480,880Q480,880 480,880Q480,880 480,880Q480,880 480,880Q480,880 480,880Q480,880 480,880Q480,880 480,880L480,880Q480,880 480,880Q480,880 480,880L480,880Q480,880 480,880Q480,880 480,880L480,880Z" />
+</vector>
\ No newline at end of file
diff --git a/core/res/res/drawable/ic_zen_mode_icon_palette.xml b/core/res/res/drawable/ic_zen_mode_icon_palette.xml
new file mode 100644
index 0000000..f31704d
--- /dev/null
+++ b/core/res/res/drawable/ic_zen_mode_icon_palette.xml
@@ -0,0 +1,25 @@
+<!--
+Copyright (C) 2024 The Android Open Source Project
+
+   Licensed under the Apache License, Version 2.0 (the "License");
+    you may not use this file except in compliance with the License.
+    You may obtain a copy of the License at
+
+         http://www.apache.org/licenses/LICENSE-2.0
+
+    Unless required by applicable law or agreed to in writing, software
+    distributed under the License is distributed on an "AS IS" BASIS,
+    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+    See the License for the specific language governing permissions and
+    limitations under the License.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24dp"
+    android:height="24dp"
+    android:tint="?android:attr/colorControlNormal"
+    android:viewportHeight="960"
+    android:viewportWidth="960">
+    <path
+        android:fillColor="@android:color/white"
+        android:pathData="M480,880Q398,880 325,848.5Q252,817 197.5,762.5Q143,708 111.5,635Q80,562 80,480Q80,397 112.5,324Q145,251 200.5,197Q256,143 330,111.5Q404,80 488,80Q568,80 639,107.5Q710,135 763.5,183.5Q817,232 848.5,298.5Q880,365 880,442Q880,557 810,618.5Q740,680 640,680L566,680Q557,680 553.5,685Q550,690 550,696Q550,708 565,730.5Q580,753 580,782Q580,832 552.5,856Q525,880 480,880ZM480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480L480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480ZM260,520Q286,520 303,503Q320,486 320,460Q320,434 303,417Q286,400 260,400Q234,400 217,417Q200,434 200,460Q200,486 217,503Q234,520 260,520ZM380,360Q406,360 423,343Q440,326 440,300Q440,274 423,257Q406,240 380,240Q354,240 337,257Q320,274 320,300Q320,326 337,343Q354,360 380,360ZM580,360Q606,360 623,343Q640,326 640,300Q640,274 623,257Q606,240 580,240Q554,240 537,257Q520,274 520,300Q520,326 537,343Q554,360 580,360ZM700,520Q726,520 743,503Q760,486 760,460Q760,434 743,417Q726,400 700,400Q674,400 657,417Q640,434 640,460Q640,486 657,503Q674,520 700,520ZM480,800Q489,800 494.5,795Q500,790 500,782Q500,768 485,749Q470,730 470,692Q470,650 499,625Q528,600 570,600L640,600Q706,600 753,561.5Q800,523 800,442Q800,321 707.5,240.5Q615,160 488,160Q352,160 256,253Q160,346 160,480Q160,613 253.5,706.5Q347,800 480,800Z" />
+</vector>
\ No newline at end of file
diff --git a/core/res/res/drawable/ic_zen_mode_icon_running.xml b/core/res/res/drawable/ic_zen_mode_icon_running.xml
new file mode 100644
index 0000000..472b04e
--- /dev/null
+++ b/core/res/res/drawable/ic_zen_mode_icon_running.xml
@@ -0,0 +1,26 @@
+<!--
+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.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24dp"
+    android:height="24dp"
+    android:tint="?android:attr/colorControlNormal"
+    android:viewportHeight="960"
+    android:viewportWidth="960"
+    android:autoMirrored="true">
+    <path
+        android:fillColor="@android:color/white"
+        android:pathData="M520,920L520,680L436,600L396,776L120,720L136,640L328,680L392,356L320,384L320,520L240,520L240,332L398,264Q433,249 449.5,244.5Q466,240 480,240Q501,240 519,251Q537,262 548,280L588,344Q614,386 658.5,413Q703,440 760,440L760,520Q694,520 636.5,492.5Q579,465 540,420L516,540L600,620L600,920L520,920ZM540,220Q507,220 483.5,196.5Q460,173 460,140Q460,107 483.5,83.5Q507,60 540,60Q573,60 596.5,83.5Q620,107 620,140Q620,173 596.5,196.5Q573,220 540,220Z" />
+</vector>
\ No newline at end of file
diff --git a/core/res/res/drawable/ic_zen_mode_icon_shopping_cart.xml b/core/res/res/drawable/ic_zen_mode_icon_shopping_cart.xml
new file mode 100644
index 0000000..92cec4d
--- /dev/null
+++ b/core/res/res/drawable/ic_zen_mode_icon_shopping_cart.xml
@@ -0,0 +1,25 @@
+<!--
+Copyright (C) 2024 The Android Open Source Project
+
+   Licensed under the Apache License, Version 2.0 (the "License");
+    you may not use this file except in compliance with the License.
+    You may obtain a copy of the License at
+
+         http://www.apache.org/licenses/LICENSE-2.0
+
+    Unless required by applicable law or agreed to in writing, software
+    distributed under the License is distributed on an "AS IS" BASIS,
+    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+    See the License for the specific language governing permissions and
+    limitations under the License.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24dp"
+    android:height="24dp"
+    android:tint="?android:attr/colorControlNormal"
+    android:viewportHeight="960"
+    android:viewportWidth="960">
+    <path
+        android:fillColor="@android:color/white"
+        android:pathData="M280,880Q247,880 223.5,856.5Q200,833 200,800Q200,767 223.5,743.5Q247,720 280,720Q313,720 336.5,743.5Q360,767 360,800Q360,833 336.5,856.5Q313,880 280,880ZM680,880Q647,880 623.5,856.5Q600,833 600,800Q600,767 623.5,743.5Q647,720 680,720Q713,720 736.5,743.5Q760,767 760,800Q760,833 736.5,856.5Q713,880 680,880ZM246,240L342,440L622,440Q622,440 622,440Q622,440 622,440L732,240Q732,240 732,240Q732,240 732,240L246,240ZM208,160L798,160Q821,160 833,180.5Q845,201 834,222L692,478Q681,498 662.5,509Q644,520 622,520L324,520L280,600Q280,600 280,600Q280,600 280,600L760,600L760,680L280,680Q235,680 212,640.5Q189,601 210,562L264,464L120,160L40,160L40,80L170,80L208,160ZM342,440L342,440L622,440Q622,440 622,440Q622,440 622,440L622,440L342,440Z" />
+</vector>
\ No newline at end of file
diff --git a/core/res/res/drawable/ic_zen_mode_icon_snowflake.xml b/core/res/res/drawable/ic_zen_mode_icon_snowflake.xml
new file mode 100644
index 0000000..1746e20
--- /dev/null
+++ b/core/res/res/drawable/ic_zen_mode_icon_snowflake.xml
@@ -0,0 +1,25 @@
+<!--
+Copyright (C) 2024 The Android Open Source Project
+
+   Licensed under the Apache License, Version 2.0 (the "License");
+    you may not use this file except in compliance with the License.
+    You may obtain a copy of the License at
+
+         http://www.apache.org/licenses/LICENSE-2.0
+
+    Unless required by applicable law or agreed to in writing, software
+    distributed under the License is distributed on an "AS IS" BASIS,
+    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+    See the License for the specific language governing permissions and
+    limitations under the License.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24dp"
+    android:height="24dp"
+    android:tint="?android:attr/colorControlNormal"
+    android:viewportHeight="960"
+    android:viewportWidth="960">
+    <path
+        android:fillColor="@android:color/white"
+        android:pathData="M440,880L440,714L310,842L254,786L440,600L440,520L360,520L174,706L118,650L246,520L80,520L80,440L246,440L118,310L174,254L360,440L440,440L440,360L254,174L310,118L440,246L440,80L520,80L520,246L650,118L706,174L520,360L520,440L600,440L786,254L842,310L714,440L880,440L880,520L714,520L842,650L786,706L600,520L520,520L520,600L706,786L650,842L520,714L520,880L440,880Z" />
+</vector>
\ No newline at end of file
diff --git a/core/res/res/drawable/ic_zen_mode_icon_speech_bubble.xml b/core/res/res/drawable/ic_zen_mode_icon_speech_bubble.xml
new file mode 100644
index 0000000..4be98ab
--- /dev/null
+++ b/core/res/res/drawable/ic_zen_mode_icon_speech_bubble.xml
@@ -0,0 +1,25 @@
+<!--
+Copyright (C) 2024 The Android Open Source Project
+
+   Licensed under the Apache License, Version 2.0 (the "License");
+    you may not use this file except in compliance with the License.
+    You may obtain a copy of the License at
+
+         http://www.apache.org/licenses/LICENSE-2.0
+
+    Unless required by applicable law or agreed to in writing, software
+    distributed under the License is distributed on an "AS IS" BASIS,
+    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+    See the License for the specific language governing permissions and
+    limitations under the License.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24dp"
+    android:height="24dp"
+    android:tint="?android:attr/colorControlNormal"
+    android:viewportHeight="960"
+    android:viewportWidth="960">
+    <path
+        android:fillColor="@android:color/white"
+        android:pathData="M80,880L80,160Q80,127 103.5,103.5Q127,80 160,80L800,80Q833,80 856.5,103.5Q880,127 880,160L880,640Q880,673 856.5,696.5Q833,720 800,720L240,720L80,880ZM206,640L800,640Q800,640 800,640Q800,640 800,640L800,160Q800,160 800,160Q800,160 800,160L160,160Q160,160 160,160Q160,160 160,160L160,685L206,640ZM160,640L160,640L160,160Q160,160 160,160Q160,160 160,160L160,160Q160,160 160,160Q160,160 160,160L160,640Q160,640 160,640Q160,640 160,640Z" />
+</vector>
\ No newline at end of file
diff --git a/core/res/res/drawable/ic_zen_mode_icon_train.xml b/core/res/res/drawable/ic_zen_mode_icon_train.xml
new file mode 100644
index 0000000..b6f3445
--- /dev/null
+++ b/core/res/res/drawable/ic_zen_mode_icon_train.xml
@@ -0,0 +1,25 @@
+<!--
+Copyright (C) 2024 The Android Open Source Project
+
+   Licensed under the Apache License, Version 2.0 (the "License");
+    you may not use this file except in compliance with the License.
+    You may obtain a copy of the License at
+
+         http://www.apache.org/licenses/LICENSE-2.0
+
+    Unless required by applicable law or agreed to in writing, software
+    distributed under the License is distributed on an "AS IS" BASIS,
+    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+    See the License for the specific language governing permissions and
+    limitations under the License.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24dp"
+    android:height="24dp"
+    android:tint="?android:attr/colorControlNormal"
+    android:viewportHeight="960"
+    android:viewportWidth="960">
+    <path
+        android:fillColor="@android:color/white"
+        android:pathData="M160,620L160,240Q160,187 187.5,155.5Q215,124 260,107.5Q305,91 362.5,85.5Q420,80 480,80Q546,80 604.5,85.5Q663,91 706.5,107.5Q750,124 775,155.5Q800,187 800,240L800,620Q800,679 759.5,719.5Q719,760 660,760L720,820L720,840L640,840L560,760L400,760L320,840L240,840L240,820L300,760Q241,760 200.5,719.5Q160,679 160,620ZM480,160Q374,160 325,172.5Q276,185 258,200L706,200Q691,183 641.5,171.5Q592,160 480,160ZM240,400L440,400L440,280L240,280L240,400ZM660,480L300,480Q274,480 257,480Q240,480 240,480L240,480L720,480L720,480Q720,480 703,480Q686,480 660,480ZM520,400L720,400L720,280L520,280L520,400ZM340,640Q366,640 383,623Q400,606 400,580Q400,554 383,537Q366,520 340,520Q314,520 297,537Q280,554 280,580Q280,606 297,623Q314,640 340,640ZM620,640Q646,640 663,623Q680,606 680,580Q680,554 663,537Q646,520 620,520Q594,520 577,537Q560,554 560,580Q560,606 577,623Q594,640 620,640ZM300,680L660,680Q686,680 703,663Q720,646 720,620L720,480L240,480L240,620Q240,646 257,663Q274,680 300,680ZM480,200Q592,200 641.5,200Q691,200 706,200L258,200Q276,200 325,200Q374,200 480,200Z" />
+</vector>
\ No newline at end of file
diff --git a/core/res/res/drawable/ic_zen_mode_icon_tv.xml b/core/res/res/drawable/ic_zen_mode_icon_tv.xml
new file mode 100644
index 0000000..eaa920a
--- /dev/null
+++ b/core/res/res/drawable/ic_zen_mode_icon_tv.xml
@@ -0,0 +1,25 @@
+<!--
+Copyright (C) 2024 The Android Open Source Project
+
+   Licensed under the Apache License, Version 2.0 (the "License");
+    you may not use this file except in compliance with the License.
+    You may obtain a copy of the License at
+
+         http://www.apache.org/licenses/LICENSE-2.0
+
+    Unless required by applicable law or agreed to in writing, software
+    distributed under the License is distributed on an "AS IS" BASIS,
+    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+    See the License for the specific language governing permissions and
+    limitations under the License.
+-->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24dp"
+    android:height="24dp"
+    android:tint="?android:attr/colorControlNormal"
+    android:viewportHeight="960"
+    android:viewportWidth="960">
+    <path
+        android:fillColor="@android:color/white"
+        android:pathData="M320,840L320,760L160,760Q127,760 103.5,736.5Q80,713 80,680L80,200Q80,167 103.5,143.5Q127,120 160,120L800,120Q833,120 856.5,143.5Q880,167 880,200L880,680Q880,713 856.5,736.5Q833,760 800,760L640,760L640,840L320,840ZM160,680L800,680Q800,680 800,680Q800,680 800,680L800,200Q800,200 800,200Q800,200 800,200L160,200Q160,200 160,200Q160,200 160,200L160,680Q160,680 160,680Q160,680 160,680ZM160,680Q160,680 160,680Q160,680 160,680L160,200Q160,200 160,200Q160,200 160,200L160,200Q160,200 160,200Q160,200 160,200L160,680Q160,680 160,680Q160,680 160,680Z" />
+</vector>
\ No newline at end of file
diff --git a/core/res/res/drawable/ic_zen_mode_type_unknown.xml b/core/res/res/drawable/ic_zen_mode_type_unknown.xml
index c1afd44..04df5f9 100644
--- a/core/res/res/drawable/ic_zen_mode_type_unknown.xml
+++ b/core/res/res/drawable/ic_zen_mode_type_unknown.xml
@@ -21,5 +21,5 @@
     android:viewportWidth="960">
     <path
         android:fillColor="@android:color/white"
-        android:pathData="M480,880Q407,871 335,840.5Q263,810 206.5,753Q150,696 115,609Q80,522 80,400L80,360L120,360Q171,360 225,373Q279,386 326,412Q338,326 380.5,235.5Q423,145 480,80Q537,145 579.5,235.5Q622,326 634,412Q681,386 735,373Q789,360 840,360L880,360L880,400Q880,522 845,609Q810,696 753.5,753Q697,810 625.5,840.5Q554,871 480,880ZM478,798Q467,632 379.5,547Q292,462 162,442Q173,613 263.5,697Q354,781 478,798ZM480,544Q495,522 516.5,498.5Q538,475 558,458Q556,401 535.5,339Q515,277 480,218Q445,277 424.5,339Q404,401 402,458Q422,475 444,498.5Q466,522 480,544ZM558,780Q595,768 635,745Q675,722 709.5,682.5Q744,643 768.5,584Q793,525 798,442Q704,456 633,504.5Q562,553 524,628Q536,660 544.5,698Q553,736 558,780ZM480,544Q480,544 480,544Q480,544 480,544Q480,544 480,544Q480,544 480,544Q480,544 480,544Q480,544 480,544Q480,544 480,544Q480,544 480,544ZM558,780Q558,780 558,780Q558,780 558,780Q558,780 558,780Q558,780 558,780Q558,780 558,780Q558,780 558,780Q558,780 558,780Q558,780 558,780ZM478,798Q478,798 478,798Q478,798 478,798Q478,798 478,798Q478,798 478,798ZM524,628L524,628Q524,628 524,628Q524,628 524,628L524,628L524,628L524,628Q524,628 524,628Q524,628 524,628L524,628Q524,628 524,628Q524,628 524,628ZM480,880L480,880Q480,880 480,880Q480,880 480,880Q480,880 480,880Q480,880 480,880Q480,880 480,880Q480,880 480,880Q480,880 480,880Q480,880 480,880Q480,880 480,880Q480,880 480,880L480,880Q480,880 480,880Q480,880 480,880L480,880Q480,880 480,880Q480,880 480,880L480,880Z" />
+        android:pathData="M368,640L480,556L590,640L548,504L660,416L524,416L480,280L436,416L300,416L410,504L368,640ZM160,800Q127,800 103.5,776.5Q80,753 80,720L80,585Q80,574 87,566Q94,558 105,556Q129,548 144.5,527Q160,506 160,480Q160,454 144.5,433Q129,412 105,404Q94,402 87,394Q80,386 80,375L80,240Q80,207 103.5,183.5Q127,160 160,160L800,160Q833,160 856.5,183.5Q880,207 880,240L880,375Q880,386 873,394Q866,402 855,404Q831,412 815.5,433Q800,454 800,480Q800,506 815.5,527Q831,548 855,556Q866,558 873,566Q880,574 880,585L880,720Q880,753 856.5,776.5Q833,800 800,800L160,800ZM160,720L800,720Q800,720 800,720Q800,720 800,720L800,618Q763,596 741.5,559.5Q720,523 720,480Q720,437 741.5,400.5Q763,364 800,342L800,240Q800,240 800,240Q800,240 800,240L160,240Q160,240 160,240Q160,240 160,240L160,342Q197,364 218.5,400.5Q240,437 240,480Q240,523 218.5,559.5Q197,596 160,618L160,720Q160,720 160,720Q160,720 160,720ZM480,480Q480,480 480,480Q480,480 480,480L480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480L480,480Q480,480 480,480Q480,480 480,480L480,480Q480,480 480,480Q480,480 480,480L480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480L480,480Q480,480 480,480Q480,480 480,480Z" />
 </vector>
\ No newline at end of file
diff --git a/core/res/res/layout-car/car_resolver_list.xml b/core/res/res/layout-car/car_resolver_list.xml
deleted file mode 100644
index 08c9861..0000000
--- a/core/res/res/layout-car/car_resolver_list.xml
+++ /dev/null
@@ -1,127 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-/*
-* Copyright 2019, The Android Open Source Project
-*
-* Licensed under the Apache License, Version 2.0 (the "License");
-* you may not use this file except in compliance with the License.
-* You may obtain a copy of the License at
-*
-*     http://www.apache.org/licenses/LICENSE-2.0
-*
-* Unless required by applicable law or agreed to in writing, software
-* distributed under the License is distributed on an "AS IS" BASIS,
-* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-* See the License for the specific language governing permissions and
-* limitations under the License.
-*/
--->
-<com.android.internal.widget.ResolverDrawerLayout
-    xmlns:android="http://schemas.android.com/apk/res/android"
-    android:layout_width="@dimen/car_activity_resolver_width"
-    android:layout_height="wrap_content"
-    android:layout_gravity="center"
-    android:id="@id/contentPanel">
-
-    <LinearLayout
-        android:layout_width="match_parent"
-        android:layout_height="wrap_content"
-        android:orientation="vertical"
-        android:background="@drawable/car_activity_resolver_list_background">
-
-        <LinearLayout
-            android:layout_width="match_parent"
-            android:layout_height="wrap_content"
-            android:background="@drawable/car_activity_resolver_list_background"
-            android:orientation="horizontal"
-            android:paddingVertical="@dimen/car_padding_4"
-            android:paddingHorizontal="@dimen/car_padding_4" >
-            <TextView
-                android:id="@+id/profile_button"
-                android:layout_width="wrap_content"
-                android:layout_height="wrap_content"
-                android:visibility="gone" />
-
-            <TextView
-                android:id="@+id/title"
-                android:layout_width="wrap_content"
-                android:layout_height="wrap_content"
-                android:layout_gravity="start"
-                android:textAppearance="@android:style/TextAppearance.DeviceDefault.DialogWindowTitle" />
-        </LinearLayout>
-
-        <FrameLayout
-            android:id="@+id/stub"
-            android:visibility="gone"
-            android:layout_width="match_parent"
-            android:layout_height="wrap_content"/>
-
-        <TabHost
-            android:id="@+id/profile_tabhost"
-            android:layout_width="match_parent"
-            android:layout_height="wrap_content"
-            android:layout_alignParentTop="true"
-            android:layout_centerHorizontal="true"
-            android:background="?android:attr/colorBackgroundFloating">
-            <LinearLayout
-                android:orientation="vertical"
-                android:layout_width="match_parent"
-                android:layout_height="wrap_content">
-                <TabWidget
-                    android:id="@android:id/tabs"
-                    android:visibility="gone"
-                    android:layout_width="match_parent"
-                    android:layout_height="wrap_content">
-                </TabWidget>
-                <View
-                    android:id="@+id/resolver_tab_divider"
-                    android:visibility="gone"
-                    android:layout_width="match_parent"
-                    android:layout_height="wrap_content" />
-                <FrameLayout
-                    android:id="@android:id/tabcontent"
-                    android:layout_width="match_parent"
-                    android:layout_height="wrap_content">
-                    <com.android.internal.app.ResolverViewPager
-                        android:id="@+id/profile_pager"
-                        android:layout_width="match_parent"
-                        android:layout_height="wrap_content"/>
-                </FrameLayout>
-            </LinearLayout>
-        </TabHost>
-
-        <LinearLayout
-            android:id="@+id/button_bar"
-            android:visibility="gone"
-            android:layout_width="match_parent"
-            android:layout_height="wrap_content"
-            android:layout_marginVertical="@dimen/car_padding_4"
-            android:layout_marginHorizontal="@dimen/car_padding_4"
-            android:padding="0dp"
-            android:gravity="center"
-            android:background="@drawable/car_activity_resolver_list_background"
-            android:orientation="vertical">
-
-            <Button
-                android:id="@+id/button_once"
-                android:layout_width="match_parent"
-                android:layout_height="@dimen/car_button_height"
-                android:enabled="false"
-                android:layout_gravity="center"
-                android:layout_marginBottom="@dimen/car_padding_2"
-                android:text="@string/activity_resolver_use_once"
-                android:onClick="onButtonClick"/>
-
-            <Button
-                android:id="@+id/button_always"
-                android:layout_width="match_parent"
-                android:layout_height="@dimen/car_button_height"
-                android:enabled="false"
-                android:layout_gravity="center"
-                android:text="@string/activity_resolver_use_always"
-                android:onClick="onButtonClick"/>
-        </LinearLayout>
-
-    </LinearLayout>
-
-</com.android.internal.widget.ResolverDrawerLayout>
\ No newline at end of file
diff --git a/core/res/res/layout-car/car_resolver_list_with_default.xml b/core/res/res/layout-car/car_resolver_list_with_default.xml
deleted file mode 100644
index 08cc7ff..0000000
--- a/core/res/res/layout-car/car_resolver_list_with_default.xml
+++ /dev/null
@@ -1,159 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-/*
-* Copyright 2019, The Android Open Source Project
-*
-* Licensed under the Apache License, Version 2.0 (the "License");
-* you may not use this file except in compliance with the License.
-* You may obtain a copy of the License at
-*
-*     http://www.apache.org/licenses/LICENSE-2.0
-*
-* Unless required by applicable law or agreed to in writing, software
-* distributed under the License is distributed on an "AS IS" BASIS,
-* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-* See the License for the specific language governing permissions and
-* limitations under the License.
-*/
--->
-<com.android.internal.widget.ResolverDrawerLayout
-    xmlns:android="http://schemas.android.com/apk/res/android"
-    android:layout_width="@dimen/car_activity_resolver_width"
-    android:layout_height="wrap_content"
-    android:layout_gravity="center"
-    android:id="@id/contentPanel">
-
-    <LinearLayout
-        android:layout_width="match_parent"
-        android:layout_height="wrap_content"
-        android:orientation="vertical"
-        android:layout_gravity="center"
-        android:background="@drawable/car_activity_resolver_list_background">
-
-        <FrameLayout
-            android:id="@+id/stub"
-            android:layout_width="match_parent"
-            android:layout_height="wrap_content"
-            android:background="@drawable/car_activity_resolver_list_background"/>
-
-
-        <LinearLayout
-            android:layout_width="match_parent"
-            android:layout_height="wrap_content"
-            android:minHeight="@dimen/car_activity_resolver_list_item_height"
-            android:orientation="horizontal">
-
-            <RadioButton
-                android:layout_width="wrap_content"
-                android:layout_height="wrap_content"
-                android:focusable="false"
-                android:clickable="false"
-                android:layout_marginStart="?attr/listPreferredItemPaddingStart"
-                android:layout_gravity="start|center_vertical"
-                android:checked="true"/>
-
-            <ImageView
-                android:id="@+id/icon"
-                android:layout_width="@dimen/car_icon_size"
-                android:layout_height="@dimen/car_icon_size"
-                android:layout_gravity="start|center_vertical"
-                android:layout_marginStart="@dimen/car_padding_4"
-                android:src="@drawable/resolver_icon_placeholder"
-                android:scaleType="fitCenter"/>
-
-            <TextView
-                android:id="@+id/title"
-                android:layout_width="wrap_content"
-                android:layout_height="wrap_content"
-                android:layout_marginHorizontal="?attr/listPreferredItemPaddingStart"
-                style="?android:attr/textAppearanceListItem"
-                android:layout_gravity="start|center_vertical" />
-
-            <LinearLayout
-                android:id="@+id/profile_button"
-                android:visibility="gone"
-                android:layout_width="wrap_content"
-                android:layout_height="wrap_content">
-
-                <ImageView
-                    android:id="@+id/icon"
-                    android:visibility="gone"
-                    android:layout_width="wrap_content"
-                    android:layout_height="wrap_content" />
-
-                <TextView
-                    android:id="@id/text1"
-                    android:visibility="gone"
-                    android:layout_width="wrap_content"
-                    android:layout_height="wrap_content" />
-            </LinearLayout>
-        </LinearLayout>
-
-        <TabHost
-            android:id="@+id/profile_tabhost"
-            android:layout_width="match_parent"
-            android:layout_height="wrap_content"
-            android:layout_alignParentTop="true"
-            android:layout_centerHorizontal="true"
-            android:background="?attr/colorBackgroundFloating">
-            <LinearLayout
-                android:orientation="vertical"
-                android:layout_width="match_parent"
-                android:layout_height="wrap_content">
-                <TabWidget
-                    android:id="@android:id/tabs"
-                    android:layout_width="match_parent"
-                    android:layout_height="wrap_content"
-                    android:visibility="gone">
-                </TabWidget>
-                <View
-                    android:id="@+id/resolver_tab_divider"
-                    android:visibility="gone"
-                    android:layout_width="match_parent"
-                    android:layout_height="wrap_content" />
-                <FrameLayout
-                    android:id="@android:id/tabcontent"
-                    android:layout_width="match_parent"
-                    android:layout_height="wrap_content">
-                    <com.android.internal.app.ResolverViewPager
-                        android:id="@+id/profile_pager"
-                        android:layout_width="match_parent"
-                        android:layout_height="wrap_content">
-                    </com.android.internal.app.ResolverViewPager>
-                </FrameLayout>
-            </LinearLayout>
-        </TabHost>
-
-        <LinearLayout
-            android:id="@+id/button_bar"
-            android:visibility="gone"
-            android:layout_width="match_parent"
-            android:layout_height="wrap_content"
-            android:layout_marginVertical="@dimen/car_padding_4"
-            android:layout_marginHorizontal="@dimen/car_padding_4"
-            android:gravity="center"
-            android:background="@drawable/car_activity_resolver_list_background"
-            android:orientation="vertical">
-
-            <Button
-                android:id="@+id/button_once"
-                android:layout_width="match_parent"
-                android:layout_height="@dimen/car_button_height"
-                android:enabled="false"
-                android:layout_gravity="center"
-                android:layout_marginBottom="@dimen/car_padding_2"
-                android:text="@string/activity_resolver_use_once"
-                android:onClick="onButtonClick"/>
-
-            <Button
-                android:id="@+id/button_always"
-                android:layout_width="match_parent"
-                android:layout_height="@dimen/car_button_height"
-                android:enabled="false"
-                android:layout_gravity="center"
-                android:text="@string/activity_resolver_use_always"
-                android:onClick="onButtonClick"/>
-        </LinearLayout>
-    </LinearLayout>
-
-</com.android.internal.widget.ResolverDrawerLayout>
diff --git a/core/res/res/layout/input_method_switch_dialog_new.xml b/core/res/res/layout/input_method_switch_dialog_new.xml
index 610a212..058fe3f 100644
--- a/core/res/res/layout/input_method_switch_dialog_new.xml
+++ b/core/res/res/layout/input_method_switch_dialog_new.xml
@@ -71,9 +71,10 @@
             android:layout_height="wrap_content"
             android:background="@drawable/input_method_switch_button"
             android:layout_gravity="end"
-            android:text="@string/input_method_language_settings"
+            android:text="@string/input_method_switcher_settings_button"
             android:fontFamily="google-sans-text"
             android:textAppearance="?attr/textAppearance"
+            android:contentDescription="@string/input_method_language_settings"
             android:visibility="gone"/>
 
     </LinearLayout>
diff --git a/core/res/res/values/config_telephony.xml b/core/res/res/values/config_telephony.xml
index 61c7a8c..383033d 100644
--- a/core/res/res/values/config_telephony.xml
+++ b/core/res/res/values/config_telephony.xml
@@ -237,6 +237,13 @@
     <integer name="config_emergency_call_wait_for_connection_timeout_millis">20000</integer>
     <java-symbol type="integer" name="config_emergency_call_wait_for_connection_timeout_millis" />
 
+    <!-- The time duration in millis after which Telephony will stop waiting for the OFF state
+         from cellular modem, consider the request to power off cellular modem as failed, and thus
+         treat the cellular modem state as ON.
+     -->
+    <integer name="config_satellite_wait_for_cellular_modem_off_timeout_millis">120000</integer>
+    <java-symbol type="integer" name="config_satellite_wait_for_cellular_modem_off_timeout_millis" />
+
     <!-- Indicates the data limit in bytes that can be used for bootstrap sim until factory reset.
          -1 means unlimited. -->
     <integer name="config_esim_bootstrap_data_limit_bytes">-1</integer>
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index cb58339..f404666 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -3280,6 +3280,9 @@
     <!-- Accessibility text for the long click action on the switch input method button. This will
          be used following "Double-tap and hold to..." [CHAR LIMIT=NONE] -->
     <string name="input_method_ime_switch_long_click_action_desc">Open input method picker</string>
+    <!-- Button to access the language settings of the current input method
+         from the Input Method Switcher menu. [CHAR LIMIT=50]-->
+    <string name="input_method_switcher_settings_button">Settings</string>
 
     <!-- If the device is getting low on internal storage, a notification is shown to the user.  This is the title of that notification. -->
     <string name="low_internal_storage_view_title">Storage space running out</string>
@@ -3883,7 +3886,8 @@
 
     <!-- Title of the pop-up dialog in which the user switches keyboard, also known as input method. -->
     <string name="select_input_method">Choose input method</string>
-    <!-- Button to access the language settings of the current input method. [CHAR LIMIT=50]-->
+    <!-- Content description of the button to access the language settings of the current input method
+         from the Input Method Switcher menu, for accessibility (not shown on the screen). [CHAR LIMIT=NONE]-->
     <string name="input_method_language_settings">Language Settings</string>
     <!-- Summary text of a toggle switch to enable/disable use of the IME while a physical
          keyboard is connected -->
diff --git a/core/res/res/values/themes_device_defaults.xml b/core/res/res/values/themes_device_defaults.xml
index f5c6738..452ae04 100644
--- a/core/res/res/values/themes_device_defaults.xml
+++ b/core/res/res/values/themes_device_defaults.xml
@@ -212,30 +212,30 @@
 
         <!-- Color palette -->
         <item name="colorPrimary">@color/primary_device_default_dark</item>
-        <item name="colorPrimaryDark">@color/primary_dark_device_default_dark</item>
+        <item name="colorPrimaryDark">@color/primary_device_default_dark</item>
         <item name="colorAccent">@color/accent_device_default_dark</item>
-        <item name="colorAccentPrimary">@color/accent_primary_device_default</item>
-        <item name="colorAccentSecondary">@color/accent_secondary_device_default</item>
-        <item name="colorAccentTertiary">@color/accent_tertiary_device_default</item>
-        <item name="colorAccentPrimaryVariant">@color/accent_primary_variant_dark_device_default</item>
-        <item name="colorAccentSecondaryVariant">@color/accent_secondary_variant_dark_device_default</item>
-        <item name="colorAccentTertiaryVariant">@color/accent_tertiary_variant_dark_device_default</item>
-        <item name="colorSurface">@color/surface_dark</item>
-        <item name="colorSurfaceHighlight">@color/surface_highlight_dark</item>
-        <item name="colorSurfaceVariant">@color/surface_variant_dark</item>
-        <item name="colorSurfaceHeader">@color/surface_header_dark</item>
+        <item name="colorAccentPrimary">@color/system_primary_dark</item>
+        <item name="colorAccentSecondary">@color/system_secondary_dark</item>
+        <item name="colorAccentTertiary">@color/system_tertiary_dark</item>
+        <item name="colorAccentPrimaryVariant">@color/system_primary_container_dark</item>
+        <item name="colorAccentSecondaryVariant">@color/system_secondary_container_dark</item>
+        <item name="colorAccentTertiaryVariant">@color/system_tertiary_container_dark</item>
+        <item name="colorSurface">@color/system_surface_container_dark</item>
+        <item name="colorSurfaceHighlight">@color/system_surface_bright_dark</item>
+        <item name="colorSurfaceVariant">@color/system_surface_container_high_dark</item>
+        <item name="colorSurfaceHeader">@color/system_surface_container_highest_dark</item>
         <item name="colorError">@color/error_color_device_default_dark</item>
-        <item name="colorBackground">@color/background_device_default_dark</item>
+        <item name="colorBackground">@color/system_surface_container_dark</item>
         <item name="colorBackgroundFloating">@color/background_floating_device_default_dark</item>
         <item name="colorPopupBackground">?attr/colorBackgroundFloating</item>
         <item name="panelColorBackground">?attr/colorBackgroundFloating</item>
-        <item name="textColorPrimary">@color/text_color_primary_device_default_dark</item>
-        <item name="textColorSecondary">@color/text_color_secondary_device_default_dark</item>
-        <item name="textColorTertiary">@color/text_color_tertiary_device_default_dark</item>
-        <item name="textColorPrimaryInverse">@color/text_color_primary_device_default_light</item>
-        <item name="textColorSecondaryInverse">@color/text_color_secondary_device_default_light</item>
-        <item name="textColorTertiaryInverse">@color/text_color_tertiary_device_default_light</item>
-        <item name="textColorOnAccent">@color/text_color_on_accent_device_default</item>
+        <item name="textColorPrimary">@color/system_on_surface_dark</item>
+        <item name="textColorSecondary">@color/system_on_surface_variant_dark</item>
+        <item name="textColorTertiary">@color/system_outline_dark</item>
+        <item name="textColorPrimaryInverse">@color/system_on_surface_light</item>
+        <item name="textColorSecondaryInverse">@color/system_on_surface_variant_light</item>
+        <item name="textColorTertiaryInverse">@color/system_outline_light</item>
+        <item name="textColorOnAccent">@color/system_on_primary_dark</item>
         <item name="colorForeground">@color/foreground_device_default_dark</item>
         <item name="colorForegroundInverse">@color/foreground_device_default_light</item>
 
@@ -314,28 +314,28 @@
     <style name="Theme.DeviceDefault.NoActionBar" parent="Theme.Material.NoActionBar">
         <!-- Color palette -->
         <item name="colorPrimary">@color/primary_device_default_dark</item>
-        <item name="colorPrimaryDark">@color/primary_dark_device_default_dark</item>
+        <item name="colorPrimaryDark">@color/primary_device_default_dark</item>
         <item name="colorAccent">@color/accent_device_default_dark</item>
-        <item name="colorAccentPrimary">@color/accent_primary_device_default</item>
-        <item name="colorAccentSecondary">@color/accent_secondary_device_default</item>
-        <item name="colorAccentTertiary">@color/accent_tertiary_device_default</item>
-        <item name="colorAccentPrimaryVariant">@color/accent_primary_variant_dark_device_default</item>
-        <item name="colorAccentSecondaryVariant">@color/accent_secondary_variant_dark_device_default</item>
-        <item name="colorAccentTertiaryVariant">@color/accent_tertiary_variant_dark_device_default</item>
-        <item name="colorSurface">@color/surface_dark</item>
-        <item name="colorSurfaceHighlight">@color/surface_highlight_dark</item>
-        <item name="colorSurfaceVariant">@color/surface_variant_dark</item>
-        <item name="colorSurfaceHeader">@color/surface_header_dark</item>
+        <item name="colorAccentPrimary">@color/system_primary_dark</item>
+        <item name="colorAccentSecondary">@color/system_secondary_dark</item>
+        <item name="colorAccentTertiary">@color/system_tertiary_dark</item>
+        <item name="colorAccentPrimaryVariant">@color/system_primary_container_dark</item>
+        <item name="colorAccentSecondaryVariant">@color/system_secondary_container_dark</item>
+        <item name="colorAccentTertiaryVariant">@color/system_tertiary_container_dark</item>
+        <item name="colorSurface">@color/system_surface_container_dark</item>
+        <item name="colorSurfaceHighlight">@color/system_surface_bright_dark</item>
+        <item name="colorSurfaceVariant">@color/system_surface_container_high_dark</item>
+        <item name="colorSurfaceHeader">@color/system_surface_container_highest_dark</item>
         <item name="colorError">@color/error_color_device_default_dark</item>
-        <item name="colorBackground">@color/background_device_default_dark</item>
+        <item name="colorBackground">@color/system_surface_container_dark</item>
         <item name="colorBackgroundFloating">@color/background_floating_device_default_dark</item>
-        <item name="textColorPrimary">@color/text_color_primary_device_default_dark</item>
-        <item name="textColorSecondary">@color/text_color_secondary_device_default_dark</item>
-        <item name="textColorTertiary">@color/text_color_tertiary_device_default_dark</item>
-        <item name="textColorPrimaryInverse">@color/text_color_primary_device_default_light</item>
-        <item name="textColorSecondaryInverse">@color/text_color_secondary_device_default_light</item>
-        <item name="textColorTertiaryInverse">@color/text_color_tertiary_device_default_light</item>
-        <item name="textColorOnAccent">@color/text_color_on_accent_device_default</item>
+        <item name="textColorPrimary">@color/system_on_surface_dark</item>
+        <item name="textColorSecondary">@color/system_on_surface_variant_dark</item>
+        <item name="textColorTertiary">@color/system_outline_dark</item>
+        <item name="textColorPrimaryInverse">@color/system_on_surface_light</item>
+        <item name="textColorSecondaryInverse">@color/system_on_surface_variant_light</item>
+        <item name="textColorTertiaryInverse">@color/system_outline_light</item>
+        <item name="textColorOnAccent">@color/system_on_primary_dark</item>
         <item name="colorForeground">@color/foreground_device_default_dark</item>
         <item name="colorForegroundInverse">@color/foreground_device_default_light</item>
 
@@ -431,28 +431,28 @@
     <style name="Theme.DeviceDefault.NoActionBar.Fullscreen" parent="Theme.Material.NoActionBar.Fullscreen">
         <!-- Color palette -->
         <item name="colorPrimary">@color/primary_device_default_dark</item>
-        <item name="colorPrimaryDark">@color/primary_dark_device_default_dark</item>
+        <item name="colorPrimaryDark">@color/primary_device_default_dark</item>
         <item name="colorAccent">@color/accent_device_default_dark</item>
-        <item name="colorAccentPrimary">@color/accent_primary_device_default</item>
-        <item name="colorAccentSecondary">@color/accent_secondary_device_default</item>
-        <item name="colorAccentTertiary">@color/accent_tertiary_device_default</item>
-        <item name="colorAccentPrimaryVariant">@color/accent_primary_variant_dark_device_default</item>
-        <item name="colorAccentSecondaryVariant">@color/accent_secondary_variant_dark_device_default</item>
-        <item name="colorAccentTertiaryVariant">@color/accent_tertiary_variant_dark_device_default</item>
-        <item name="colorSurface">@color/surface_dark</item>
-        <item name="colorSurfaceHighlight">@color/surface_highlight_dark</item>
-        <item name="colorSurfaceVariant">@color/surface_variant_dark</item>
-        <item name="colorSurfaceHeader">@color/surface_header_dark</item>
+        <item name="colorAccentPrimary">@color/system_primary_dark</item>
+        <item name="colorAccentSecondary">@color/system_secondary_dark</item>
+        <item name="colorAccentTertiary">@color/system_tertiary_dark</item>
+        <item name="colorAccentPrimaryVariant">@color/system_primary_container_dark</item>
+        <item name="colorAccentSecondaryVariant">@color/system_secondary_container_dark</item>
+        <item name="colorAccentTertiaryVariant">@color/system_tertiary_container_dark</item>
+        <item name="colorSurface">@color/system_surface_container_dark</item>
+        <item name="colorSurfaceHighlight">@color/system_surface_bright_dark</item>
+        <item name="colorSurfaceVariant">@color/system_surface_container_high_dark</item>
+        <item name="colorSurfaceHeader">@color/system_surface_container_highest_dark</item>
         <item name="colorError">@color/error_color_device_default_dark</item>
-        <item name="colorBackground">@color/background_device_default_dark</item>
+        <item name="colorBackground">@color/system_surface_container_dark</item>
         <item name="colorBackgroundFloating">@color/background_floating_device_default_dark</item>
-        <item name="textColorPrimary">@color/text_color_primary_device_default_dark</item>
-        <item name="textColorSecondary">@color/text_color_secondary_device_default_dark</item>
-        <item name="textColorTertiary">@color/text_color_tertiary_device_default_dark</item>
-        <item name="textColorPrimaryInverse">@color/text_color_primary_device_default_light</item>
-        <item name="textColorSecondaryInverse">@color/text_color_secondary_device_default_light</item>
-        <item name="textColorTertiaryInverse">@color/text_color_tertiary_device_default_light</item>
-        <item name="textColorOnAccent">@color/text_color_on_accent_device_default</item>
+        <item name="textColorPrimary">@color/system_on_surface_dark</item>
+        <item name="textColorSecondary">@color/system_on_surface_variant_dark</item>
+        <item name="textColorTertiary">@color/system_outline_dark</item>
+        <item name="textColorPrimaryInverse">@color/system_on_surface_light</item>
+        <item name="textColorSecondaryInverse">@color/system_on_surface_variant_light</item>
+        <item name="textColorTertiaryInverse">@color/system_outline_light</item>
+        <item name="textColorOnAccent">@color/system_on_primary_dark</item>
         <item name="colorForeground">@color/foreground_device_default_dark</item>
         <item name="colorForegroundInverse">@color/foreground_device_default_light</item>
 
@@ -550,28 +550,28 @@
     <style name="Theme.DeviceDefault.NoActionBar.Overscan" parent="Theme.Material.NoActionBar.Overscan">
         <!-- Color palette -->
         <item name="colorPrimary">@color/primary_device_default_dark</item>
-        <item name="colorPrimaryDark">@color/primary_dark_device_default_dark</item>
+        <item name="colorPrimaryDark">@color/primary_device_default_dark</item>
         <item name="colorAccent">@color/accent_device_default_dark</item>
-        <item name="colorAccentPrimary">@color/accent_primary_device_default</item>
-        <item name="colorAccentSecondary">@color/accent_secondary_device_default</item>
-        <item name="colorAccentTertiary">@color/accent_tertiary_device_default</item>
-        <item name="colorAccentPrimaryVariant">@color/accent_primary_variant_dark_device_default</item>
-        <item name="colorAccentSecondaryVariant">@color/accent_secondary_variant_dark_device_default</item>
-        <item name="colorAccentTertiaryVariant">@color/accent_tertiary_variant_dark_device_default</item>
-        <item name="colorSurface">@color/surface_dark</item>
-        <item name="colorSurfaceHighlight">@color/surface_highlight_dark</item>
-        <item name="colorSurfaceVariant">@color/surface_variant_dark</item>
-        <item name="colorSurfaceHeader">@color/surface_header_dark</item>
+        <item name="colorAccentPrimary">@color/system_primary_dark</item>
+        <item name="colorAccentSecondary">@color/system_secondary_dark</item>
+        <item name="colorAccentTertiary">@color/system_tertiary_dark</item>
+        <item name="colorAccentPrimaryVariant">@color/system_primary_container_dark</item>
+        <item name="colorAccentSecondaryVariant">@color/system_secondary_container_dark</item>
+        <item name="colorAccentTertiaryVariant">@color/system_tertiary_container_dark</item>
+        <item name="colorSurface">@color/system_surface_container_dark</item>
+        <item name="colorSurfaceHighlight">@color/system_surface_bright_dark</item>
+        <item name="colorSurfaceVariant">@color/system_surface_container_high_dark</item>
+        <item name="colorSurfaceHeader">@color/system_surface_container_highest_dark</item>
         <item name="colorError">@color/error_color_device_default_dark</item>
-        <item name="colorBackground">@color/background_device_default_dark</item>
+        <item name="colorBackground">@color/system_surface_container_dark</item>
         <item name="colorBackgroundFloating">@color/background_floating_device_default_dark</item>
-        <item name="textColorPrimary">@color/text_color_primary_device_default_dark</item>
-        <item name="textColorSecondary">@color/text_color_secondary_device_default_dark</item>
-        <item name="textColorTertiary">@color/text_color_tertiary_device_default_dark</item>
-        <item name="textColorPrimaryInverse">@color/text_color_primary_device_default_light</item>
-        <item name="textColorSecondaryInverse">@color/text_color_secondary_device_default_light</item>
-        <item name="textColorTertiaryInverse">@color/text_color_tertiary_device_default_light</item>
-        <item name="textColorOnAccent">@color/text_color_on_accent_device_default</item>
+        <item name="textColorPrimary">@color/system_on_surface_dark</item>
+        <item name="textColorSecondary">@color/system_on_surface_variant_dark</item>
+        <item name="textColorTertiary">@color/system_outline_dark</item>
+        <item name="textColorPrimaryInverse">@color/system_on_surface_light</item>
+        <item name="textColorSecondaryInverse">@color/system_on_surface_variant_light</item>
+        <item name="textColorTertiaryInverse">@color/system_outline_light</item>
+        <item name="textColorOnAccent">@color/system_on_primary_dark</item>
         <item name="colorForeground">@color/foreground_device_default_dark</item>
         <item name="colorForegroundInverse">@color/foreground_device_default_light</item>
 
@@ -668,28 +668,28 @@
     <style name="Theme.DeviceDefault.NoActionBar.TranslucentDecor" parent="Theme.Material.NoActionBar.TranslucentDecor">
         <!-- Color palette -->
         <item name="colorPrimary">@color/primary_device_default_dark</item>
-        <item name="colorPrimaryDark">@color/primary_dark_device_default_dark</item>
+        <item name="colorPrimaryDark">@color/primary_device_default_dark</item>
         <item name="colorAccent">@color/accent_device_default_dark</item>
-        <item name="colorAccentPrimary">@color/accent_primary_device_default</item>
-        <item name="colorAccentSecondary">@color/accent_secondary_device_default</item>
-        <item name="colorAccentTertiary">@color/accent_tertiary_device_default</item>
-        <item name="colorAccentPrimaryVariant">@color/accent_primary_variant_dark_device_default</item>
-        <item name="colorAccentSecondaryVariant">@color/accent_secondary_variant_dark_device_default</item>
-        <item name="colorAccentTertiaryVariant">@color/accent_tertiary_variant_dark_device_default</item>
-        <item name="colorSurface">@color/surface_dark</item>
-        <item name="colorSurfaceHighlight">@color/surface_highlight_dark</item>
-        <item name="colorSurfaceVariant">@color/surface_variant_dark</item>
-        <item name="colorSurfaceHeader">@color/surface_header_dark</item>
+        <item name="colorAccentPrimary">@color/system_primary_dark</item>
+        <item name="colorAccentSecondary">@color/system_secondary_dark</item>
+        <item name="colorAccentTertiary">@color/system_tertiary_dark</item>
+        <item name="colorAccentPrimaryVariant">@color/system_primary_container_dark</item>
+        <item name="colorAccentSecondaryVariant">@color/system_secondary_container_dark</item>
+        <item name="colorAccentTertiaryVariant">@color/system_tertiary_container_dark</item>
+        <item name="colorSurface">@color/system_surface_container_dark</item>
+        <item name="colorSurfaceHighlight">@color/system_surface_bright_dark</item>
+        <item name="colorSurfaceVariant">@color/system_surface_container_high_dark</item>
+        <item name="colorSurfaceHeader">@color/system_surface_container_highest_dark</item>
         <item name="colorError">@color/error_color_device_default_dark</item>
-        <item name="colorBackground">@color/background_device_default_dark</item>
+        <item name="colorBackground">@color/system_surface_container_dark</item>
         <item name="colorBackgroundFloating">@color/background_floating_device_default_dark</item>
-        <item name="textColorPrimary">@color/text_color_primary_device_default_dark</item>
-        <item name="textColorSecondary">@color/text_color_secondary_device_default_dark</item>
-        <item name="textColorTertiary">@color/text_color_tertiary_device_default_dark</item>
-        <item name="textColorPrimaryInverse">@color/text_color_primary_device_default_light</item>
-        <item name="textColorSecondaryInverse">@color/text_color_secondary_device_default_light</item>
-        <item name="textColorTertiaryInverse">@color/text_color_tertiary_device_default_light</item>
-        <item name="textColorOnAccent">@color/text_color_on_accent_device_default</item>
+        <item name="textColorPrimary">@color/system_on_surface_dark</item>
+        <item name="textColorSecondary">@color/system_on_surface_variant_dark</item>
+        <item name="textColorTertiary">@color/system_outline_dark</item>
+        <item name="textColorPrimaryInverse">@color/system_on_surface_light</item>
+        <item name="textColorSecondaryInverse">@color/system_on_surface_variant_light</item>
+        <item name="textColorTertiaryInverse">@color/system_outline_light</item>
+        <item name="textColorOnAccent">@color/system_on_primary_dark</item>
         <item name="colorForeground">@color/foreground_device_default_dark</item>
         <item name="colorForegroundInverse">@color/foreground_device_default_light</item>
 
@@ -801,28 +801,28 @@
 
         <!-- Color palette -->
         <item name="colorPrimary">@color/primary_device_default_dark</item>
-        <item name="colorPrimaryDark">@color/primary_dark_device_default_dark</item>
+        <item name="colorPrimaryDark">@color/primary_device_default_dark</item>
         <item name="colorAccent">@color/accent_device_default_dark</item>
-        <item name="colorAccentPrimary">@color/accent_primary_device_default</item>
-        <item name="colorAccentSecondary">@color/accent_secondary_device_default</item>
-        <item name="colorAccentTertiary">@color/accent_tertiary_device_default</item>
-        <item name="colorAccentPrimaryVariant">@color/accent_primary_variant_dark_device_default</item>
-        <item name="colorAccentSecondaryVariant">@color/accent_secondary_variant_dark_device_default</item>
-        <item name="colorAccentTertiaryVariant">@color/accent_tertiary_variant_dark_device_default</item>
-        <item name="colorSurface">@color/surface_dark</item>
-        <item name="colorSurfaceHighlight">@color/surface_highlight_dark</item>
-        <item name="colorSurfaceVariant">@color/surface_variant_dark</item>
-        <item name="colorSurfaceHeader">@color/surface_header_dark</item>
+        <item name="colorAccentPrimary">@color/system_primary_dark</item>
+        <item name="colorAccentSecondary">@color/system_secondary_dark</item>
+        <item name="colorAccentTertiary">@color/system_tertiary_dark</item>
+        <item name="colorAccentPrimaryVariant">@color/system_primary_container_dark</item>
+        <item name="colorAccentSecondaryVariant">@color/system_secondary_container_dark</item>
+        <item name="colorAccentTertiaryVariant">@color/system_tertiary_container_dark</item>
+        <item name="colorSurface">@color/system_surface_container_dark</item>
+        <item name="colorSurfaceHighlight">@color/system_surface_bright_dark</item>
+        <item name="colorSurfaceVariant">@color/system_surface_container_high_dark</item>
+        <item name="colorSurfaceHeader">@color/system_surface_container_highest_dark</item>
         <item name="colorError">@color/error_color_device_default_dark</item>
-        <item name="colorBackground">@color/background_device_default_dark</item>
+        <item name="colorBackground">@color/system_surface_container_dark</item>
         <item name="colorBackgroundFloating">@color/background_floating_device_default_dark</item>
-        <item name="textColorPrimary">@color/text_color_primary_device_default_dark</item>
-        <item name="textColorSecondary">@color/text_color_secondary_device_default_dark</item>
-        <item name="textColorTertiary">@color/text_color_tertiary_device_default_dark</item>
-        <item name="textColorPrimaryInverse">@color/text_color_primary_device_default_light</item>
-        <item name="textColorSecondaryInverse">@color/text_color_secondary_device_default_light</item>
-        <item name="textColorTertiaryInverse">@color/text_color_tertiary_device_default_light</item>
-        <item name="textColorOnAccent">@color/text_color_on_accent_device_default</item>
+        <item name="textColorPrimary">@color/system_on_surface_dark</item>
+        <item name="textColorSecondary">@color/system_on_surface_variant_dark</item>
+        <item name="textColorTertiary">@color/system_outline_dark</item>
+        <item name="textColorPrimaryInverse">@color/system_on_surface_light</item>
+        <item name="textColorSecondaryInverse">@color/system_on_surface_variant_light</item>
+        <item name="textColorTertiaryInverse">@color/system_outline_light</item>
+        <item name="textColorOnAccent">@color/system_on_primary_dark</item>
         <item name="colorForeground">@color/foreground_device_default_dark</item>
         <item name="colorForegroundInverse">@color/foreground_device_default_light</item>
 
@@ -911,28 +911,28 @@
     <style name="Theme.DeviceDefault.Dialog.MinWidth" parent="Theme.Material.Dialog.MinWidth">
         <!-- Color palette -->
         <item name="colorPrimary">@color/primary_device_default_dark</item>
-        <item name="colorPrimaryDark">@color/primary_dark_device_default_dark</item>
+        <item name="colorPrimaryDark">@color/primary_device_default_dark</item>
         <item name="colorAccent">@color/accent_device_default_dark</item>
-        <item name="colorAccentPrimary">@color/accent_primary_device_default</item>
-        <item name="colorAccentSecondary">@color/accent_secondary_device_default</item>
-        <item name="colorAccentTertiary">@color/accent_tertiary_device_default</item>
-        <item name="colorAccentPrimaryVariant">@color/accent_primary_variant_dark_device_default</item>
-        <item name="colorAccentSecondaryVariant">@color/accent_secondary_variant_dark_device_default</item>
-        <item name="colorAccentTertiaryVariant">@color/accent_tertiary_variant_dark_device_default</item>
-        <item name="colorSurface">@color/surface_dark</item>
-        <item name="colorSurfaceHighlight">@color/surface_highlight_dark</item>
-        <item name="colorSurfaceVariant">@color/surface_variant_dark</item>
-        <item name="colorSurfaceHeader">@color/surface_header_dark</item>
+        <item name="colorAccentPrimary">@color/system_primary_dark</item>
+        <item name="colorAccentSecondary">@color/system_secondary_dark</item>
+        <item name="colorAccentTertiary">@color/system_tertiary_dark</item>
+        <item name="colorAccentPrimaryVariant">@color/system_primary_container_dark</item>
+        <item name="colorAccentSecondaryVariant">@color/system_secondary_container_dark</item>
+        <item name="colorAccentTertiaryVariant">@color/system_tertiary_container_dark</item>
+        <item name="colorSurface">@color/system_surface_container_dark</item>
+        <item name="colorSurfaceHighlight">@color/system_surface_bright_dark</item>
+        <item name="colorSurfaceVariant">@color/system_surface_container_high_dark</item>
+        <item name="colorSurfaceHeader">@color/system_surface_container_highest_dark</item>
         <item name="colorError">@color/error_color_device_default_dark</item>
-        <item name="colorBackground">@color/background_device_default_dark</item>
+        <item name="colorBackground">@color/system_surface_container_dark</item>
         <item name="colorBackgroundFloating">@color/background_floating_device_default_dark</item>
-        <item name="textColorPrimary">@color/text_color_primary_device_default_dark</item>
-        <item name="textColorSecondary">@color/text_color_secondary_device_default_dark</item>
-        <item name="textColorTertiary">@color/text_color_tertiary_device_default_dark</item>
-        <item name="textColorPrimaryInverse">@color/text_color_primary_device_default_light</item>
-        <item name="textColorSecondaryInverse">@color/text_color_secondary_device_default_light</item>
-        <item name="textColorTertiaryInverse">@color/text_color_tertiary_device_default_light</item>
-        <item name="textColorOnAccent">@color/text_color_on_accent_device_default</item>
+        <item name="textColorPrimary">@color/system_on_surface_dark</item>
+        <item name="textColorSecondary">@color/system_on_surface_variant_dark</item>
+        <item name="textColorTertiary">@color/system_outline_dark</item>
+        <item name="textColorPrimaryInverse">@color/system_on_surface_light</item>
+        <item name="textColorSecondaryInverse">@color/system_on_surface_variant_light</item>
+        <item name="textColorTertiaryInverse">@color/system_outline_light</item>
+        <item name="textColorOnAccent">@color/system_on_primary_dark</item>
         <item name="colorForeground">@color/foreground_device_default_dark</item>
         <item name="colorForegroundInverse">@color/foreground_device_default_light</item>
 
@@ -1027,28 +1027,28 @@
     <style name="Theme.DeviceDefault.Dialog.NoActionBar" parent="Theme.Material.Dialog.NoActionBar">
         <!-- Color palette -->
         <item name="colorPrimary">@color/primary_device_default_dark</item>
-        <item name="colorPrimaryDark">@color/primary_dark_device_default_dark</item>
+        <item name="colorPrimaryDark">@color/primary_device_default_dark</item>
         <item name="colorAccent">@color/accent_device_default_dark</item>
-        <item name="colorAccentPrimary">@color/accent_primary_device_default</item>
-        <item name="colorAccentSecondary">@color/accent_secondary_device_default</item>
-        <item name="colorAccentTertiary">@color/accent_tertiary_device_default</item>
-        <item name="colorAccentPrimaryVariant">@color/accent_primary_variant_dark_device_default</item>
-        <item name="colorAccentSecondaryVariant">@color/accent_secondary_variant_dark_device_default</item>
-        <item name="colorAccentTertiaryVariant">@color/accent_tertiary_variant_dark_device_default</item>
-        <item name="colorSurface">@color/surface_dark</item>
-        <item name="colorSurfaceHighlight">@color/surface_highlight_dark</item>
-        <item name="colorSurfaceVariant">@color/surface_variant_dark</item>
-        <item name="colorSurfaceHeader">@color/surface_header_dark</item>
+        <item name="colorAccentPrimary">@color/system_primary_dark</item>
+        <item name="colorAccentSecondary">@color/system_secondary_dark</item>
+        <item name="colorAccentTertiary">@color/system_tertiary_dark</item>
+        <item name="colorAccentPrimaryVariant">@color/system_primary_container_dark</item>
+        <item name="colorAccentSecondaryVariant">@color/system_secondary_container_dark</item>
+        <item name="colorAccentTertiaryVariant">@color/system_tertiary_container_dark</item>
+        <item name="colorSurface">@color/system_surface_container_dark</item>
+        <item name="colorSurfaceHighlight">@color/system_surface_bright_dark</item>
+        <item name="colorSurfaceVariant">@color/system_surface_container_high_dark</item>
+        <item name="colorSurfaceHeader">@color/system_surface_container_highest_dark</item>
         <item name="colorError">@color/error_color_device_default_dark</item>
-        <item name="colorBackground">@color/background_device_default_dark</item>
+        <item name="colorBackground">@color/system_surface_container_dark</item>
         <item name="colorBackgroundFloating">@color/background_floating_device_default_dark</item>
-        <item name="textColorPrimary">@color/text_color_primary_device_default_dark</item>
-        <item name="textColorSecondary">@color/text_color_secondary_device_default_dark</item>
-        <item name="textColorTertiary">@color/text_color_tertiary_device_default_dark</item>
-        <item name="textColorPrimaryInverse">@color/text_color_primary_device_default_light</item>
-        <item name="textColorSecondaryInverse">@color/text_color_secondary_device_default_light</item>
-        <item name="textColorTertiaryInverse">@color/text_color_tertiary_device_default_light</item>
-        <item name="textColorOnAccent">@color/text_color_on_accent_device_default</item>
+        <item name="textColorPrimary">@color/system_on_surface_dark</item>
+        <item name="textColorSecondary">@color/system_on_surface_variant_dark</item>
+        <item name="textColorTertiary">@color/system_outline_dark</item>
+        <item name="textColorPrimaryInverse">@color/system_on_surface_light</item>
+        <item name="textColorSecondaryInverse">@color/system_on_surface_variant_light</item>
+        <item name="textColorTertiaryInverse">@color/system_outline_light</item>
+        <item name="textColorOnAccent">@color/system_on_primary_dark</item>
         <item name="colorForeground">@color/foreground_device_default_dark</item>
         <item name="colorForegroundInverse">@color/foreground_device_default_light</item>
 
@@ -1144,28 +1144,28 @@
     <style name="Theme.DeviceDefault.Dialog.NoActionBar.MinWidth" parent="Theme.Material.Dialog.NoActionBar.MinWidth">
         <!-- Color palette -->
         <item name="colorPrimary">@color/primary_device_default_dark</item>
-        <item name="colorPrimaryDark">@color/primary_dark_device_default_dark</item>
+        <item name="colorPrimaryDark">@color/primary_device_default_dark</item>
         <item name="colorAccent">@color/accent_device_default_dark</item>
-        <item name="colorAccentPrimary">@color/accent_primary_device_default</item>
-        <item name="colorAccentSecondary">@color/accent_secondary_device_default</item>
-        <item name="colorAccentTertiary">@color/accent_tertiary_device_default</item>
-        <item name="colorAccentPrimaryVariant">@color/accent_primary_variant_dark_device_default</item>
-        <item name="colorAccentSecondaryVariant">@color/accent_secondary_variant_dark_device_default</item>
-        <item name="colorAccentTertiaryVariant">@color/accent_tertiary_variant_dark_device_default</item>
-        <item name="colorSurface">@color/surface_dark</item>
-        <item name="colorSurfaceHighlight">@color/surface_highlight_dark</item>
-        <item name="colorSurfaceVariant">@color/surface_variant_dark</item>
-        <item name="colorSurfaceHeader">@color/surface_header_dark</item>
+        <item name="colorAccentPrimary">@color/system_primary_dark</item>
+        <item name="colorAccentSecondary">@color/system_secondary_dark</item>
+        <item name="colorAccentTertiary">@color/system_tertiary_dark</item>
+        <item name="colorAccentPrimaryVariant">@color/system_primary_container_dark</item>
+        <item name="colorAccentSecondaryVariant">@color/system_secondary_container_dark</item>
+        <item name="colorAccentTertiaryVariant">@color/system_tertiary_container_dark</item>
+        <item name="colorSurface">@color/system_surface_container_dark</item>
+        <item name="colorSurfaceHighlight">@color/system_surface_bright_dark</item>
+        <item name="colorSurfaceVariant">@color/system_surface_container_high_dark</item>
+        <item name="colorSurfaceHeader">@color/system_surface_container_highest_dark</item>
         <item name="colorError">@color/error_color_device_default_dark</item>
-        <item name="colorBackground">@color/background_device_default_dark</item>
+        <item name="colorBackground">@color/system_surface_container_dark</item>
         <item name="colorBackgroundFloating">@color/background_floating_device_default_dark</item>
-        <item name="textColorPrimary">@color/text_color_primary_device_default_dark</item>
-        <item name="textColorSecondary">@color/text_color_secondary_device_default_dark</item>
-        <item name="textColorTertiary">@color/text_color_tertiary_device_default_dark</item>
-        <item name="textColorPrimaryInverse">@color/text_color_primary_device_default_light</item>
-        <item name="textColorSecondaryInverse">@color/text_color_secondary_device_default_light</item>
-        <item name="textColorTertiaryInverse">@color/text_color_tertiary_device_default_light</item>
-        <item name="textColorOnAccent">@color/text_color_on_accent_device_default</item>
+        <item name="textColorPrimary">@color/system_on_surface_dark</item>
+        <item name="textColorSecondary">@color/system_on_surface_variant_dark</item>
+        <item name="textColorTertiary">@color/system_outline_dark</item>
+        <item name="textColorPrimaryInverse">@color/system_on_surface_light</item>
+        <item name="textColorSecondaryInverse">@color/system_on_surface_variant_light</item>
+        <item name="textColorTertiaryInverse">@color/system_outline_light</item>
+        <item name="textColorOnAccent">@color/system_on_primary_dark</item>
         <item name="colorForeground">@color/foreground_device_default_dark</item>
         <item name="colorForegroundInverse">@color/foreground_device_default_light</item>
 
@@ -1277,28 +1277,28 @@
     <style name="Theme.DeviceDefault.DialogWhenLarge" parent="Theme.Material.DialogWhenLarge">
         <!-- Color palette -->
         <item name="colorPrimary">@color/primary_device_default_dark</item>
-        <item name="colorPrimaryDark">@color/primary_dark_device_default_dark</item>
+        <item name="colorPrimaryDark">@color/primary_device_default_dark</item>
         <item name="colorAccent">@color/accent_device_default_dark</item>
-        <item name="colorAccentPrimary">@color/accent_primary_device_default</item>
-        <item name="colorAccentSecondary">@color/accent_secondary_device_default</item>
-        <item name="colorAccentTertiary">@color/accent_tertiary_device_default</item>
-        <item name="colorAccentPrimaryVariant">@color/accent_primary_variant_dark_device_default</item>
-        <item name="colorAccentSecondaryVariant">@color/accent_secondary_variant_dark_device_default</item>
-        <item name="colorAccentTertiaryVariant">@color/accent_tertiary_variant_dark_device_default</item>
-        <item name="colorSurface">@color/surface_dark</item>
-        <item name="colorSurfaceHighlight">@color/surface_highlight_dark</item>
-        <item name="colorSurfaceVariant">@color/surface_variant_dark</item>
-        <item name="colorSurfaceHeader">@color/surface_header_dark</item>
+        <item name="colorAccentPrimary">@color/system_primary_dark</item>
+        <item name="colorAccentSecondary">@color/system_secondary_dark</item>
+        <item name="colorAccentTertiary">@color/system_tertiary_dark</item>
+        <item name="colorAccentPrimaryVariant">@color/system_primary_container_dark</item>
+        <item name="colorAccentSecondaryVariant">@color/system_secondary_container_dark</item>
+        <item name="colorAccentTertiaryVariant">@color/system_tertiary_container_dark</item>
+        <item name="colorSurface">@color/system_surface_container_dark</item>
+        <item name="colorSurfaceHighlight">@color/system_surface_bright_dark</item>
+        <item name="colorSurfaceVariant">@color/system_surface_container_high_dark</item>
+        <item name="colorSurfaceHeader">@color/system_surface_container_highest_dark</item>
         <item name="colorError">@color/error_color_device_default_dark</item>
-        <item name="colorBackground">@color/background_device_default_dark</item>
+        <item name="colorBackground">@color/system_surface_container_dark</item>
         <item name="colorBackgroundFloating">@color/background_floating_device_default_dark</item>
-        <item name="textColorPrimary">@color/text_color_primary_device_default_dark</item>
-        <item name="textColorSecondary">@color/text_color_secondary_device_default_dark</item>
-        <item name="textColorTertiary">@color/text_color_tertiary_device_default_dark</item>
-        <item name="textColorPrimaryInverse">@color/text_color_primary_device_default_light</item>
-        <item name="textColorSecondaryInverse">@color/text_color_secondary_device_default_light</item>
-        <item name="textColorTertiaryInverse">@color/text_color_tertiary_device_default_light</item>
-        <item name="textColorOnAccent">@color/text_color_on_accent_device_default</item>
+        <item name="textColorPrimary">@color/system_on_surface_dark</item>
+        <item name="textColorSecondary">@color/system_on_surface_variant_dark</item>
+        <item name="textColorTertiary">@color/system_outline_dark</item>
+        <item name="textColorPrimaryInverse">@color/system_on_surface_light</item>
+        <item name="textColorSecondaryInverse">@color/system_on_surface_variant_light</item>
+        <item name="textColorTertiaryInverse">@color/system_outline_light</item>
+        <item name="textColorOnAccent">@color/system_on_primary_dark</item>
         <item name="colorForeground">@color/foreground_device_default_dark</item>
         <item name="colorForegroundInverse">@color/foreground_device_default_light</item>
 
@@ -1395,28 +1395,28 @@
     <style name="Theme.DeviceDefault.DialogWhenLarge.NoActionBar" parent="Theme.Material.DialogWhenLarge.NoActionBar">
         <!-- Color palette -->
         <item name="colorPrimary">@color/primary_device_default_dark</item>
-        <item name="colorPrimaryDark">@color/primary_dark_device_default_dark</item>
+        <item name="colorPrimaryDark">@color/primary_device_default_dark</item>
         <item name="colorAccent">@color/accent_device_default_dark</item>
-        <item name="colorAccentPrimary">@color/accent_primary_device_default</item>
-        <item name="colorAccentSecondary">@color/accent_secondary_device_default</item>
-        <item name="colorAccentTertiary">@color/accent_tertiary_device_default</item>
-        <item name="colorAccentPrimaryVariant">@color/accent_primary_variant_dark_device_default</item>
-        <item name="colorAccentSecondaryVariant">@color/accent_secondary_variant_dark_device_default</item>
-        <item name="colorAccentTertiaryVariant">@color/accent_tertiary_variant_dark_device_default</item>
-        <item name="colorSurface">@color/surface_dark</item>
-        <item name="colorSurfaceHighlight">@color/surface_highlight_dark</item>
-        <item name="colorSurfaceVariant">@color/surface_variant_dark</item>
-        <item name="colorSurfaceHeader">@color/surface_header_dark</item>
+        <item name="colorAccentPrimary">@color/system_primary_dark</item>
+        <item name="colorAccentSecondary">@color/system_secondary_dark</item>
+        <item name="colorAccentTertiary">@color/system_tertiary_dark</item>
+        <item name="colorAccentPrimaryVariant">@color/system_primary_container_dark</item>
+        <item name="colorAccentSecondaryVariant">@color/system_secondary_container_dark</item>
+        <item name="colorAccentTertiaryVariant">@color/system_tertiary_container_dark</item>
+        <item name="colorSurface">@color/system_surface_container_dark</item>
+        <item name="colorSurfaceHighlight">@color/system_surface_bright_dark</item>
+        <item name="colorSurfaceVariant">@color/system_surface_container_high_dark</item>
+        <item name="colorSurfaceHeader">@color/system_surface_container_highest_dark</item>
         <item name="colorError">@color/error_color_device_default_dark</item>
-        <item name="colorBackground">@color/background_device_default_dark</item>
+        <item name="colorBackground">@color/system_surface_container_dark</item>
         <item name="colorBackgroundFloating">@color/background_floating_device_default_dark</item>
-        <item name="textColorPrimary">@color/text_color_primary_device_default_dark</item>
-        <item name="textColorSecondary">@color/text_color_secondary_device_default_dark</item>
-        <item name="textColorTertiary">@color/text_color_tertiary_device_default_dark</item>
-        <item name="textColorPrimaryInverse">@color/text_color_primary_device_default_light</item>
-        <item name="textColorSecondaryInverse">@color/text_color_secondary_device_default_light</item>
-        <item name="textColorTertiaryInverse">@color/text_color_tertiary_device_default_light</item>
-        <item name="textColorOnAccent">@color/text_color_on_accent_device_default</item>
+        <item name="textColorPrimary">@color/system_on_surface_dark</item>
+        <item name="textColorSecondary">@color/system_on_surface_variant_dark</item>
+        <item name="textColorTertiary">@color/system_outline_dark</item>
+        <item name="textColorPrimaryInverse">@color/system_on_surface_light</item>
+        <item name="textColorSecondaryInverse">@color/system_on_surface_variant_light</item>
+        <item name="textColorTertiaryInverse">@color/system_outline_light</item>
+        <item name="textColorOnAccent">@color/system_on_primary_dark</item>
         <item name="colorForeground">@color/foreground_device_default_dark</item>
         <item name="colorForegroundInverse">@color/foreground_device_default_light</item>
 
@@ -1511,28 +1511,28 @@
     <style name="Theme.DeviceDefault.Dialog.Presentation" parent="Theme.Material.Dialog.Presentation">
         <!-- Color palette -->
         <item name="colorPrimary">@color/primary_device_default_dark</item>
-        <item name="colorPrimaryDark">@color/primary_dark_device_default_dark</item>
+        <item name="colorPrimaryDark">@color/primary_device_default_dark</item>
         <item name="colorAccent">@color/accent_device_default_dark</item>
-        <item name="colorAccentPrimary">@color/accent_primary_device_default</item>
-        <item name="colorAccentSecondary">@color/accent_secondary_device_default</item>
-        <item name="colorAccentTertiary">@color/accent_tertiary_device_default</item>
-        <item name="colorAccentPrimaryVariant">@color/accent_primary_variant_dark_device_default</item>
-        <item name="colorAccentSecondaryVariant">@color/accent_secondary_variant_dark_device_default</item>
-        <item name="colorAccentTertiaryVariant">@color/accent_tertiary_variant_dark_device_default</item>
-        <item name="colorSurface">@color/surface_dark</item>
-        <item name="colorSurfaceHighlight">@color/surface_highlight_dark</item>
-        <item name="colorSurfaceVariant">@color/surface_variant_dark</item>
-        <item name="colorSurfaceHeader">@color/surface_header_dark</item>
+        <item name="colorAccentPrimary">@color/system_primary_dark</item>
+        <item name="colorAccentSecondary">@color/system_secondary_dark</item>
+        <item name="colorAccentTertiary">@color/system_tertiary_dark</item>
+        <item name="colorAccentPrimaryVariant">@color/system_primary_container_dark</item>
+        <item name="colorAccentSecondaryVariant">@color/system_secondary_container_dark</item>
+        <item name="colorAccentTertiaryVariant">@color/system_tertiary_container_dark</item>
+        <item name="colorSurface">@color/system_surface_container_dark</item>
+        <item name="colorSurfaceHighlight">@color/system_surface_bright_dark</item>
+        <item name="colorSurfaceVariant">@color/system_surface_container_high_dark</item>
+        <item name="colorSurfaceHeader">@color/system_surface_container_highest_dark</item>
         <item name="colorError">@color/error_color_device_default_dark</item>
-        <item name="colorBackground">@color/background_device_default_dark</item>
+        <item name="colorBackground">@color/system_surface_container_dark</item>
         <item name="colorBackgroundFloating">@color/background_floating_device_default_dark</item>
-        <item name="textColorPrimary">@color/text_color_primary_device_default_dark</item>
-        <item name="textColorSecondary">@color/text_color_secondary_device_default_dark</item>
-        <item name="textColorTertiary">@color/text_color_tertiary_device_default_dark</item>
-        <item name="textColorPrimaryInverse">@color/text_color_primary_device_default_light</item>
-        <item name="textColorSecondaryInverse">@color/text_color_secondary_device_default_light</item>
-        <item name="textColorTertiaryInverse">@color/text_color_tertiary_device_default_light</item>
-        <item name="textColorOnAccent">@color/text_color_on_accent_device_default</item>
+        <item name="textColorPrimary">@color/system_on_surface_dark</item>
+        <item name="textColorSecondary">@color/system_on_surface_variant_dark</item>
+        <item name="textColorTertiary">@color/system_outline_dark</item>
+        <item name="textColorPrimaryInverse">@color/system_on_surface_light</item>
+        <item name="textColorSecondaryInverse">@color/system_on_surface_variant_light</item>
+        <item name="textColorTertiaryInverse">@color/system_outline_light</item>
+        <item name="textColorOnAccent">@color/system_on_primary_dark</item>
         <item name="colorForeground">@color/foreground_device_default_dark</item>
         <item name="colorForegroundInverse">@color/foreground_device_default_light</item>
 
@@ -1629,28 +1629,28 @@
     <style name="Theme.DeviceDefault.Panel" parent="Theme.Material.Panel">
         <!-- Color palette -->
         <item name="colorPrimary">@color/primary_device_default_dark</item>
-        <item name="colorPrimaryDark">@color/primary_dark_device_default_dark</item>
+        <item name="colorPrimaryDark">@color/primary_device_default_dark</item>
         <item name="colorAccent">@color/accent_device_default_dark</item>
-        <item name="colorAccentPrimary">@color/accent_primary_device_default</item>
-        <item name="colorAccentSecondary">@color/accent_secondary_device_default</item>
-        <item name="colorAccentTertiary">@color/accent_tertiary_device_default</item>
-        <item name="colorAccentPrimaryVariant">@color/accent_primary_variant_dark_device_default</item>
-        <item name="colorAccentSecondaryVariant">@color/accent_secondary_variant_dark_device_default</item>
-        <item name="colorAccentTertiaryVariant">@color/accent_tertiary_variant_dark_device_default</item>
-        <item name="colorSurface">@color/surface_dark</item>
-        <item name="colorSurfaceHighlight">@color/surface_highlight_dark</item>
-        <item name="colorSurfaceVariant">@color/surface_variant_dark</item>
-        <item name="colorSurfaceHeader">@color/surface_header_dark</item>
+        <item name="colorAccentPrimary">@color/system_primary_dark</item>
+        <item name="colorAccentSecondary">@color/system_secondary_dark</item>
+        <item name="colorAccentTertiary">@color/system_tertiary_dark</item>
+        <item name="colorAccentPrimaryVariant">@color/system_primary_container_dark</item>
+        <item name="colorAccentSecondaryVariant">@color/system_secondary_container_dark</item>
+        <item name="colorAccentTertiaryVariant">@color/system_tertiary_container_dark</item>
+        <item name="colorSurface">@color/system_surface_container_dark</item>
+        <item name="colorSurfaceHighlight">@color/system_surface_bright_dark</item>
+        <item name="colorSurfaceVariant">@color/system_surface_container_high_dark</item>
+        <item name="colorSurfaceHeader">@color/system_surface_container_highest_dark</item>
         <item name="colorError">@color/error_color_device_default_dark</item>
-        <item name="colorBackground">@color/background_device_default_dark</item>
+        <item name="colorBackground">@color/system_surface_container_dark</item>
         <item name="colorBackgroundFloating">@color/background_floating_device_default_dark</item>
-        <item name="textColorPrimary">@color/text_color_primary_device_default_dark</item>
-        <item name="textColorSecondary">@color/text_color_secondary_device_default_dark</item>
-        <item name="textColorTertiary">@color/text_color_tertiary_device_default_dark</item>
-        <item name="textColorPrimaryInverse">@color/text_color_primary_device_default_light</item>
-        <item name="textColorSecondaryInverse">@color/text_color_secondary_device_default_light</item>
-        <item name="textColorTertiaryInverse">@color/text_color_tertiary_device_default_light</item>
-        <item name="textColorOnAccent">@color/text_color_on_accent_device_default</item>
+        <item name="textColorPrimary">@color/system_on_surface_dark</item>
+        <item name="textColorSecondary">@color/system_on_surface_variant_dark</item>
+        <item name="textColorTertiary">@color/system_outline_dark</item>
+        <item name="textColorPrimaryInverse">@color/system_on_surface_light</item>
+        <item name="textColorSecondaryInverse">@color/system_on_surface_variant_light</item>
+        <item name="textColorTertiaryInverse">@color/system_outline_light</item>
+        <item name="textColorOnAccent">@color/system_on_primary_dark</item>
         <item name="colorForeground">@color/foreground_device_default_dark</item>
         <item name="colorForegroundInverse">@color/foreground_device_default_light</item>
 
@@ -1746,28 +1746,28 @@
     <style name="Theme.DeviceDefault.Wallpaper" parent="Theme.Material.Wallpaper">
         <!-- Color palette -->
         <item name="colorPrimary">@color/primary_device_default_dark</item>
-        <item name="colorPrimaryDark">@color/primary_dark_device_default_dark</item>
+        <item name="colorPrimaryDark">@color/primary_device_default_dark</item>
         <item name="colorAccent">@color/accent_device_default_dark</item>
-        <item name="colorAccentPrimary">@color/accent_primary_device_default</item>
-        <item name="colorAccentSecondary">@color/accent_secondary_device_default</item>
-        <item name="colorAccentTertiary">@color/accent_tertiary_device_default</item>
-        <item name="colorAccentPrimaryVariant">@color/accent_primary_variant_dark_device_default</item>
-        <item name="colorAccentSecondaryVariant">@color/accent_secondary_variant_dark_device_default</item>
-        <item name="colorAccentTertiaryVariant">@color/accent_tertiary_variant_dark_device_default</item>
-        <item name="colorSurface">@color/surface_dark</item>
-        <item name="colorSurfaceHighlight">@color/surface_highlight_dark</item>
-        <item name="colorSurfaceVariant">@color/surface_variant_dark</item>
-        <item name="colorSurfaceHeader">@color/surface_header_dark</item>
+        <item name="colorAccentPrimary">@color/system_primary_dark</item>
+        <item name="colorAccentSecondary">@color/system_secondary_dark</item>
+        <item name="colorAccentTertiary">@color/system_tertiary_dark</item>
+        <item name="colorAccentPrimaryVariant">@color/system_primary_container_dark</item>
+        <item name="colorAccentSecondaryVariant">@color/system_secondary_container_dark</item>
+        <item name="colorAccentTertiaryVariant">@color/system_tertiary_container_dark</item>
+        <item name="colorSurface">@color/system_surface_container_dark</item>
+        <item name="colorSurfaceHighlight">@color/system_surface_bright_dark</item>
+        <item name="colorSurfaceVariant">@color/system_surface_container_high_dark</item>
+        <item name="colorSurfaceHeader">@color/system_surface_container_highest_dark</item>
         <item name="colorError">@color/error_color_device_default_dark</item>
-        <item name="colorBackground">@color/background_device_default_dark</item>
+        <item name="colorBackground">@color/system_surface_container_dark</item>
         <item name="colorBackgroundFloating">@color/background_floating_device_default_dark</item>
-        <item name="textColorPrimary">@color/text_color_primary_device_default_dark</item>
-        <item name="textColorSecondary">@color/text_color_secondary_device_default_dark</item>
-        <item name="textColorTertiary">@color/text_color_tertiary_device_default_dark</item>
-        <item name="textColorPrimaryInverse">@color/text_color_primary_device_default_light</item>
-        <item name="textColorSecondaryInverse">@color/text_color_secondary_device_default_light</item>
-        <item name="textColorTertiaryInverse">@color/text_color_tertiary_device_default_light</item>
-        <item name="textColorOnAccent">@color/text_color_on_accent_device_default</item>
+        <item name="textColorPrimary">@color/system_on_surface_dark</item>
+        <item name="textColorSecondary">@color/system_on_surface_variant_dark</item>
+        <item name="textColorTertiary">@color/system_outline_dark</item>
+        <item name="textColorPrimaryInverse">@color/system_on_surface_light</item>
+        <item name="textColorSecondaryInverse">@color/system_on_surface_variant_light</item>
+        <item name="textColorTertiaryInverse">@color/system_outline_light</item>
+        <item name="textColorOnAccent">@color/system_on_primary_dark</item>
         <item name="colorForeground">@color/foreground_device_default_dark</item>
         <item name="colorForegroundInverse">@color/foreground_device_default_light</item>
 
@@ -1863,28 +1863,28 @@
     <style name="Theme.DeviceDefault.Wallpaper.NoTitleBar" parent="Theme.Material.Wallpaper.NoTitleBar">
         <!-- Color palette -->
         <item name="colorPrimary">@color/primary_device_default_dark</item>
-        <item name="colorPrimaryDark">@color/primary_dark_device_default_dark</item>
+        <item name="colorPrimaryDark">@color/primary_device_default_dark</item>
         <item name="colorAccent">@color/accent_device_default_dark</item>
-        <item name="colorAccentPrimary">@color/accent_primary_device_default</item>
-        <item name="colorAccentSecondary">@color/accent_secondary_device_default</item>
-        <item name="colorAccentTertiary">@color/accent_tertiary_device_default</item>
-        <item name="colorAccentPrimaryVariant">@color/accent_primary_variant_dark_device_default</item>
-        <item name="colorAccentSecondaryVariant">@color/accent_secondary_variant_dark_device_default</item>
-        <item name="colorAccentTertiaryVariant">@color/accent_tertiary_variant_dark_device_default</item>
-        <item name="colorSurface">@color/surface_dark</item>
-        <item name="colorSurfaceHighlight">@color/surface_highlight_dark</item>
-        <item name="colorSurfaceVariant">@color/surface_variant_dark</item>
-        <item name="colorSurfaceHeader">@color/surface_header_dark</item>
+        <item name="colorAccentPrimary">@color/system_primary_dark</item>
+        <item name="colorAccentSecondary">@color/system_secondary_dark</item>
+        <item name="colorAccentTertiary">@color/system_tertiary_dark</item>
+        <item name="colorAccentPrimaryVariant">@color/system_primary_container_dark</item>
+        <item name="colorAccentSecondaryVariant">@color/system_secondary_container_dark</item>
+        <item name="colorAccentTertiaryVariant">@color/system_tertiary_container_dark</item>
+        <item name="colorSurface">@color/system_surface_container_dark</item>
+        <item name="colorSurfaceHighlight">@color/system_surface_bright_dark</item>
+        <item name="colorSurfaceVariant">@color/system_surface_container_high_dark</item>
+        <item name="colorSurfaceHeader">@color/system_surface_container_highest_dark</item>
         <item name="colorError">@color/error_color_device_default_dark</item>
-        <item name="colorBackground">@color/background_device_default_dark</item>
+        <item name="colorBackground">@color/system_surface_container_dark</item>
         <item name="colorBackgroundFloating">@color/background_floating_device_default_dark</item>
-        <item name="textColorPrimary">@color/text_color_primary_device_default_dark</item>
-        <item name="textColorSecondary">@color/text_color_secondary_device_default_dark</item>
-        <item name="textColorTertiary">@color/text_color_tertiary_device_default_dark</item>
-        <item name="textColorPrimaryInverse">@color/text_color_primary_device_default_light</item>
-        <item name="textColorSecondaryInverse">@color/text_color_secondary_device_default_light</item>
-        <item name="textColorTertiaryInverse">@color/text_color_tertiary_device_default_light</item>
-        <item name="textColorOnAccent">@color/text_color_on_accent_device_default</item>
+        <item name="textColorPrimary">@color/system_on_surface_dark</item>
+        <item name="textColorSecondary">@color/system_on_surface_variant_dark</item>
+        <item name="textColorTertiary">@color/system_outline_dark</item>
+        <item name="textColorPrimaryInverse">@color/system_on_surface_light</item>
+        <item name="textColorSecondaryInverse">@color/system_on_surface_variant_light</item>
+        <item name="textColorTertiaryInverse">@color/system_outline_light</item>
+        <item name="textColorOnAccent">@color/system_on_primary_dark</item>
         <item name="colorForeground">@color/foreground_device_default_dark</item>
         <item name="colorForegroundInverse">@color/foreground_device_default_light</item>
 
@@ -1980,28 +1980,28 @@
     <style name="Theme.DeviceDefault.InputMethod" parent="Theme.Material.InputMethod">
         <!-- Color palette -->
         <item name="colorPrimary">@color/primary_device_default_light</item>
-        <item name="colorPrimaryDark">@color/primary_dark_device_default_light</item>
+        <item name="colorPrimaryDark">@color/primary_device_default_light</item>
         <item name="colorAccent">@color/accent_device_default_light</item>
-        <item name="colorAccentPrimary">@color/accent_primary_device_default</item>
-        <item name="colorAccentSecondary">@color/accent_secondary_device_default</item>
-        <item name="colorAccentTertiary">@color/accent_tertiary_device_default</item>
-        <item name="colorAccentPrimaryVariant">@color/accent_primary_variant_light_device_default</item>
-        <item name="colorAccentSecondaryVariant">@color/accent_secondary_variant_light_device_default</item>
-        <item name="colorAccentTertiaryVariant">@color/accent_tertiary_variant_light_device_default</item>
-        <item name="colorSurface">@color/surface_light</item>
-        <item name="colorSurfaceHighlight">@color/surface_highlight_light</item>
-        <item name="colorSurfaceVariant">@color/surface_variant_light</item>
-        <item name="colorSurfaceHeader">@color/surface_header_light</item>
+        <item name="colorAccentPrimary">@color/system_primary_dark</item>
+        <item name="colorAccentSecondary">@color/system_secondary_dark</item>
+        <item name="colorAccentTertiary">@color/system_tertiary_dark</item>
+        <item name="colorAccentPrimaryVariant">@color/system_primary_container_light</item>
+        <item name="colorAccentSecondaryVariant">@color/system_secondary_container_light</item>
+        <item name="colorAccentTertiaryVariant">@color/system_tertiary_container_light</item>
+        <item name="colorSurface">@color/system_surface_container_light</item>
+        <item name="colorSurfaceHighlight">@color/system_surface_bright_light</item>
+        <item name="colorSurfaceVariant">@color/system_surface_container_high_light</item>
+        <item name="colorSurfaceHeader">@color/system_surface_container_highest_light</item>
         <item name="colorError">@color/error_color_device_default_light</item>
-        <item name="colorBackground">@color/background_device_default_light</item>
+        <item name="colorBackground">@color/system_surface_container_light</item>
         <item name="colorBackgroundFloating">@color/background_floating_device_default_light</item>
-        <item name="textColorPrimary">@color/text_color_primary_device_default_light</item>
-        <item name="textColorSecondary">@color/text_color_secondary_device_default_light</item>
-        <item name="textColorTertiary">@color/text_color_tertiary_device_default_light</item>
-        <item name="textColorPrimaryInverse">@color/text_color_primary_device_default_dark</item>
-        <item name="textColorSecondaryInverse">@color/text_color_secondary_device_default_dark</item>
-        <item name="textColorTertiaryInverse">@color/text_color_tertiary_device_default_dark</item>
-        <item name="textColorOnAccent">@color/text_color_on_accent_device_default</item>
+        <item name="textColorPrimary">@color/system_on_surface_light</item>
+        <item name="textColorSecondary">@color/system_on_surface_variant_light</item>
+        <item name="textColorTertiary">@color/system_outline_light</item>
+        <item name="textColorPrimaryInverse">@color/system_on_surface_dark</item>
+        <item name="textColorSecondaryInverse">@color/system_on_surface_variant_dark</item>
+        <item name="textColorTertiaryInverse">@color/system_outline_dark</item>
+        <item name="textColorOnAccent">@color/system_on_primary_dark</item>
         <item name="colorForeground">@color/foreground_device_default_light</item>
         <item name="colorForegroundInverse">@color/foreground_device_default_dark</item>
 
@@ -2097,28 +2097,28 @@
     <style name="Theme.DeviceDefault.VoiceInteractionSession" parent="Theme.Material.VoiceInteractionSession">
         <!-- Color palette -->
         <item name="colorPrimary">@color/primary_device_default_light</item>
-        <item name="colorPrimaryDark">@color/primary_dark_device_default_light</item>
+        <item name="colorPrimaryDark">@color/primary_device_default_light</item>
         <item name="colorAccent">@color/accent_device_default_light</item>
-        <item name="colorAccentPrimary">@color/accent_primary_device_default</item>
-        <item name="colorAccentSecondary">@color/accent_secondary_device_default</item>
-        <item name="colorAccentTertiary">@color/accent_tertiary_device_default</item>
-        <item name="colorAccentPrimaryVariant">@color/accent_primary_variant_light_device_default</item>
-        <item name="colorAccentSecondaryVariant">@color/accent_secondary_variant_light_device_default</item>
-        <item name="colorAccentTertiaryVariant">@color/accent_tertiary_variant_light_device_default</item>
-        <item name="colorSurface">@color/surface_light</item>
-        <item name="colorSurfaceHighlight">@color/surface_highlight_light</item>
-        <item name="colorSurfaceVariant">@color/surface_variant_light</item>
-        <item name="colorSurfaceHeader">@color/surface_header_light</item>
+        <item name="colorAccentPrimary">@color/system_primary_dark</item>
+        <item name="colorAccentSecondary">@color/system_secondary_dark</item>
+        <item name="colorAccentTertiary">@color/system_tertiary_dark</item>
+        <item name="colorAccentPrimaryVariant">@color/system_primary_container_light</item>
+        <item name="colorAccentSecondaryVariant">@color/system_secondary_container_light</item>
+        <item name="colorAccentTertiaryVariant">@color/system_tertiary_container_light</item>
+        <item name="colorSurface">@color/system_surface_container_light</item>
+        <item name="colorSurfaceHighlight">@color/system_surface_bright_light</item>
+        <item name="colorSurfaceVariant">@color/system_surface_container_high_light</item>
+        <item name="colorSurfaceHeader">@color/system_surface_container_highest_light</item>
         <item name="colorError">@color/error_color_device_default_light</item>
-        <item name="colorBackground">@color/background_device_default_light</item>
+        <item name="colorBackground">@color/system_surface_container_light</item>
         <item name="colorBackgroundFloating">@color/background_floating_device_default_light</item>
-        <item name="textColorPrimary">@color/text_color_primary_device_default_light</item>
-        <item name="textColorSecondary">@color/text_color_secondary_device_default_light</item>
-        <item name="textColorTertiary">@color/text_color_tertiary_device_default_light</item>
-        <item name="textColorPrimaryInverse">@color/text_color_primary_device_default_dark</item>
-        <item name="textColorSecondaryInverse">@color/text_color_secondary_device_default_dark</item>
-        <item name="textColorTertiaryInverse">@color/text_color_tertiary_device_default_dark</item>
-        <item name="textColorOnAccent">@color/text_color_on_accent_device_default</item>
+        <item name="textColorPrimary">@color/system_on_surface_light</item>
+        <item name="textColorSecondary">@color/system_on_surface_variant_light</item>
+        <item name="textColorTertiary">@color/system_outline_light</item>
+        <item name="textColorPrimaryInverse">@color/system_on_surface_dark</item>
+        <item name="textColorSecondaryInverse">@color/system_on_surface_variant_dark</item>
+        <item name="textColorTertiaryInverse">@color/system_outline_dark</item>
+        <item name="textColorOnAccent">@color/system_on_primary_dark</item>
         <item name="colorForeground">@color/foreground_device_default_light</item>
         <item name="colorForegroundInverse">@color/foreground_device_default_dark</item>
 
@@ -2218,28 +2218,28 @@
 
         <!-- Color palette -->
         <item name="colorPrimary">@color/primary_device_default_dark</item>
-        <item name="colorPrimaryDark">@color/primary_dark_device_default_dark</item>
+        <item name="colorPrimaryDark">@color/primary_device_default_dark</item>
         <item name="colorAccent">@color/accent_device_default_dark</item>
-        <item name="colorAccentPrimary">@color/accent_primary_device_default</item>
-        <item name="colorAccentSecondary">@color/accent_secondary_device_default</item>
-        <item name="colorAccentTertiary">@color/accent_tertiary_device_default</item>
-        <item name="colorAccentPrimaryVariant">@color/accent_primary_variant_dark_device_default</item>
-        <item name="colorAccentSecondaryVariant">@color/accent_secondary_variant_dark_device_default</item>
-        <item name="colorAccentTertiaryVariant">@color/accent_tertiary_variant_dark_device_default</item>
-        <item name="colorSurface">@color/surface_dark</item>
-        <item name="colorSurfaceHighlight">@color/surface_highlight_dark</item>
-        <item name="colorSurfaceVariant">@color/surface_variant_dark</item>
-        <item name="colorSurfaceHeader">@color/surface_header_dark</item>
+        <item name="colorAccentPrimary">@color/system_primary_dark</item>
+        <item name="colorAccentSecondary">@color/system_secondary_dark</item>
+        <item name="colorAccentTertiary">@color/system_tertiary_dark</item>
+        <item name="colorAccentPrimaryVariant">@color/system_primary_container_dark</item>
+        <item name="colorAccentSecondaryVariant">@color/system_secondary_container_dark</item>
+        <item name="colorAccentTertiaryVariant">@color/system_tertiary_container_dark</item>
+        <item name="colorSurface">@color/system_surface_container_dark</item>
+        <item name="colorSurfaceHighlight">@color/system_surface_bright_dark</item>
+        <item name="colorSurfaceVariant">@color/system_surface_container_high_dark</item>
+        <item name="colorSurfaceHeader">@color/system_surface_container_highest_dark</item>
         <item name="colorError">@color/error_color_device_default_dark</item>
-        <item name="colorBackground">@color/background_device_default_dark</item>
+        <item name="colorBackground">@color/system_surface_container_dark</item>
         <item name="colorBackgroundFloating">@color/background_floating_device_default_dark</item>
-        <item name="textColorPrimary">@color/text_color_primary_device_default_dark</item>
-        <item name="textColorSecondary">@color/text_color_secondary_device_default_dark</item>
-        <item name="textColorTertiary">@color/text_color_tertiary_device_default_dark</item>
-        <item name="textColorPrimaryInverse">@color/text_color_primary_device_default_light</item>
-        <item name="textColorSecondaryInverse">@color/text_color_secondary_device_default_light</item>
-        <item name="textColorTertiaryInverse">@color/text_color_tertiary_device_default_light</item>
-        <item name="textColorOnAccent">@color/text_color_on_accent_device_default</item>
+        <item name="textColorPrimary">@color/system_on_surface_dark</item>
+        <item name="textColorSecondary">@color/system_on_surface_variant_dark</item>
+        <item name="textColorTertiary">@color/system_outline_dark</item>
+        <item name="textColorPrimaryInverse">@color/system_on_surface_light</item>
+        <item name="textColorSecondaryInverse">@color/system_on_surface_variant_light</item>
+        <item name="textColorTertiaryInverse">@color/system_outline_light</item>
+        <item name="textColorOnAccent">@color/system_on_primary_dark</item>
         <item name="colorForeground">@color/foreground_device_default_dark</item>
         <item name="colorForegroundInverse">@color/foreground_device_default_light</item>
 
@@ -2336,28 +2336,28 @@
     <style name="Theme.DeviceDefault.SearchBar" parent="Theme.Material.SearchBar">
         <!-- Color palette -->
         <item name="colorPrimary">@color/primary_device_default_dark</item>
-        <item name="colorPrimaryDark">@color/primary_dark_device_default_dark</item>
+        <item name="colorPrimaryDark">@color/primary_device_default_dark</item>
         <item name="colorAccent">@color/accent_device_default_dark</item>
-        <item name="colorAccentPrimary">@color/accent_primary_device_default</item>
-        <item name="colorAccentSecondary">@color/accent_secondary_device_default</item>
-        <item name="colorAccentTertiary">@color/accent_tertiary_device_default</item>
-        <item name="colorAccentPrimaryVariant">@color/accent_primary_variant_dark_device_default</item>
-        <item name="colorAccentSecondaryVariant">@color/accent_secondary_variant_dark_device_default</item>
-        <item name="colorAccentTertiaryVariant">@color/accent_tertiary_variant_dark_device_default</item>
-        <item name="colorSurface">@color/surface_dark</item>
-        <item name="colorSurfaceHighlight">@color/surface_highlight_dark</item>
-        <item name="colorSurfaceVariant">@color/surface_variant_dark</item>
-        <item name="colorSurfaceHeader">@color/surface_header_dark</item>
+        <item name="colorAccentPrimary">@color/system_primary_dark</item>
+        <item name="colorAccentSecondary">@color/system_secondary_dark</item>
+        <item name="colorAccentTertiary">@color/system_tertiary_dark</item>
+        <item name="colorAccentPrimaryVariant">@color/system_primary_container_dark</item>
+        <item name="colorAccentSecondaryVariant">@color/system_secondary_container_dark</item>
+        <item name="colorAccentTertiaryVariant">@color/system_tertiary_container_dark</item>
+        <item name="colorSurface">@color/system_surface_container_dark</item>
+        <item name="colorSurfaceHighlight">@color/system_surface_bright_dark</item>
+        <item name="colorSurfaceVariant">@color/system_surface_container_high_dark</item>
+        <item name="colorSurfaceHeader">@color/system_surface_container_highest_dark</item>
         <item name="colorError">@color/error_color_device_default_dark</item>
-        <item name="colorBackground">@color/background_device_default_dark</item>
+        <item name="colorBackground">@color/system_surface_container_dark</item>
         <item name="colorBackgroundFloating">@color/background_floating_device_default_dark</item>
-        <item name="textColorPrimary">@color/text_color_primary_device_default_dark</item>
-        <item name="textColorSecondary">@color/text_color_secondary_device_default_dark</item>
-        <item name="textColorTertiary">@color/text_color_tertiary_device_default_dark</item>
-        <item name="textColorPrimaryInverse">@color/text_color_primary_device_default_light</item>
-        <item name="textColorSecondaryInverse">@color/text_color_secondary_device_default_light</item>
-        <item name="textColorTertiaryInverse">@color/text_color_tertiary_device_default_light</item>
-        <item name="textColorOnAccent">@color/text_color_on_accent_device_default</item>
+        <item name="textColorPrimary">@color/system_on_surface_dark</item>
+        <item name="textColorSecondary">@color/system_on_surface_variant_dark</item>
+        <item name="textColorTertiary">@color/system_outline_dark</item>
+        <item name="textColorPrimaryInverse">@color/system_on_surface_light</item>
+        <item name="textColorSecondaryInverse">@color/system_on_surface_variant_light</item>
+        <item name="textColorTertiaryInverse">@color/system_outline_light</item>
+        <item name="textColorOnAccent">@color/system_on_primary_dark</item>
         <item name="colorForeground">@color/foreground_device_default_dark</item>
         <item name="colorForegroundInverse">@color/foreground_device_default_light</item>
 
@@ -2451,28 +2451,28 @@
     <style name="Theme.DeviceDefault.Dialog.NoFrame" parent="Theme.Material.Dialog.NoFrame">
         <!-- Color palette -->
         <item name="colorPrimary">@color/primary_device_default_dark</item>
-        <item name="colorPrimaryDark">@color/primary_dark_device_default_dark</item>
+        <item name="colorPrimaryDark">@color/primary_device_default_dark</item>
         <item name="colorAccent">@color/accent_device_default_dark</item>
-        <item name="colorAccentPrimary">@color/accent_primary_device_default</item>
-        <item name="colorAccentSecondary">@color/accent_secondary_device_default</item>
-        <item name="colorAccentTertiary">@color/accent_tertiary_device_default</item>
-        <item name="colorAccentPrimaryVariant">@color/accent_primary_variant_dark_device_default</item>
-        <item name="colorAccentSecondaryVariant">@color/accent_secondary_variant_dark_device_default</item>
-        <item name="colorAccentTertiaryVariant">@color/accent_tertiary_variant_dark_device_default</item>
-        <item name="colorSurface">@color/surface_dark</item>
-        <item name="colorSurfaceHighlight">@color/surface_highlight_dark</item>
-        <item name="colorSurfaceVariant">@color/surface_variant_dark</item>
-        <item name="colorSurfaceHeader">@color/surface_header_dark</item>
+        <item name="colorAccentPrimary">@color/system_primary_dark</item>
+        <item name="colorAccentSecondary">@color/system_secondary_dark</item>
+        <item name="colorAccentTertiary">@color/system_tertiary_dark</item>
+        <item name="colorAccentPrimaryVariant">@color/system_primary_container_dark</item>
+        <item name="colorAccentSecondaryVariant">@color/system_secondary_container_dark</item>
+        <item name="colorAccentTertiaryVariant">@color/system_tertiary_container_dark</item>
+        <item name="colorSurface">@color/system_surface_container_dark</item>
+        <item name="colorSurfaceHighlight">@color/system_surface_bright_dark</item>
+        <item name="colorSurfaceVariant">@color/system_surface_container_high_dark</item>
+        <item name="colorSurfaceHeader">@color/system_surface_container_highest_dark</item>
         <item name="colorError">@color/error_color_device_default_dark</item>
-        <item name="colorBackground">@color/background_device_default_dark</item>
+        <item name="colorBackground">@color/system_surface_container_dark</item>
         <item name="colorBackgroundFloating">@color/background_floating_device_default_dark</item>
-        <item name="textColorPrimary">@color/text_color_primary_device_default_dark</item>
-        <item name="textColorSecondary">@color/text_color_secondary_device_default_dark</item>
-        <item name="textColorTertiary">@color/text_color_tertiary_device_default_dark</item>
-        <item name="textColorPrimaryInverse">@color/text_color_primary_device_default_light</item>
-        <item name="textColorSecondaryInverse">@color/text_color_secondary_device_default_light</item>
-        <item name="textColorTertiaryInverse">@color/text_color_tertiary_device_default_light</item>
-        <item name="textColorOnAccent">@color/text_color_on_accent_device_default</item>
+        <item name="textColorPrimary">@color/system_on_surface_dark</item>
+        <item name="textColorSecondary">@color/system_on_surface_variant_dark</item>
+        <item name="textColorTertiary">@color/system_outline_dark</item>
+        <item name="textColorPrimaryInverse">@color/system_on_surface_light</item>
+        <item name="textColorSecondaryInverse">@color/system_on_surface_variant_light</item>
+        <item name="textColorTertiaryInverse">@color/system_outline_light</item>
+        <item name="textColorOnAccent">@color/system_on_primary_dark</item>
         <item name="colorForeground">@color/foreground_device_default_dark</item>
         <item name="colorForegroundInverse">@color/foreground_device_default_light</item>
 
@@ -2720,28 +2720,28 @@
 
         <!-- Color palette -->
         <item name="colorPrimary">@color/primary_device_default_light</item>
-        <item name="colorPrimaryDark">@color/primary_dark_device_default_light</item>
+        <item name="colorPrimaryDark">@color/primary_device_default_light</item>
         <item name="colorAccent">@color/accent_device_default_light</item>
-        <item name="colorAccentPrimary">@color/accent_primary_device_default</item>
-        <item name="colorAccentSecondary">@color/accent_secondary_device_default</item>
-        <item name="colorAccentTertiary">@color/accent_tertiary_device_default</item>
-        <item name="colorAccentPrimaryVariant">@color/accent_primary_variant_light_device_default</item>
-        <item name="colorAccentSecondaryVariant">@color/accent_secondary_variant_light_device_default</item>
-        <item name="colorAccentTertiaryVariant">@color/accent_tertiary_variant_light_device_default</item>
-        <item name="colorSurface">@color/surface_light</item>
-        <item name="colorSurfaceHighlight">@color/surface_highlight_light</item>
-        <item name="colorSurfaceVariant">@color/surface_variant_light</item>
-        <item name="colorSurfaceHeader">@color/surface_header_light</item>
+        <item name="colorAccentPrimary">@color/system_primary_dark</item>
+        <item name="colorAccentSecondary">@color/system_secondary_dark</item>
+        <item name="colorAccentTertiary">@color/system_tertiary_dark</item>
+        <item name="colorAccentPrimaryVariant">@color/system_primary_container_light</item>
+        <item name="colorAccentSecondaryVariant">@color/system_secondary_container_light</item>
+        <item name="colorAccentTertiaryVariant">@color/system_tertiary_container_light</item>
+        <item name="colorSurface">@color/system_surface_container_light</item>
+        <item name="colorSurfaceHighlight">@color/system_surface_bright_light</item>
+        <item name="colorSurfaceVariant">@color/system_surface_container_high_light</item>
+        <item name="colorSurfaceHeader">@color/system_surface_container_highest_light</item>
         <item name="colorError">@color/error_color_device_default_light</item>
-        <item name="colorBackground">@color/background_device_default_light</item>
+        <item name="colorBackground">@color/system_surface_container_light</item>
         <item name="colorBackgroundFloating">@color/background_floating_device_default_light</item>
-        <item name="textColorPrimary">@color/text_color_primary_device_default_light</item>
-        <item name="textColorSecondary">@color/text_color_secondary_device_default_light</item>
-        <item name="textColorTertiary">@color/text_color_tertiary_device_default_light</item>
-        <item name="textColorPrimaryInverse">@color/text_color_primary_device_default_dark</item>
-        <item name="textColorSecondaryInverse">@color/text_color_secondary_device_default_dark</item>
-        <item name="textColorTertiaryInverse">@color/text_color_tertiary_device_default_dark</item>
-        <item name="textColorOnAccent">@color/text_color_on_accent_device_default</item>
+        <item name="textColorPrimary">@color/system_on_surface_light</item>
+        <item name="textColorSecondary">@color/system_on_surface_variant_light</item>
+        <item name="textColorTertiary">@color/system_outline_light</item>
+        <item name="textColorPrimaryInverse">@color/system_on_surface_dark</item>
+        <item name="textColorSecondaryInverse">@color/system_on_surface_variant_dark</item>
+        <item name="textColorTertiaryInverse">@color/system_outline_dark</item>
+        <item name="textColorOnAccent">@color/system_on_primary_dark</item>
         <item name="colorForeground">@color/foreground_device_default_light</item>
         <item name="colorForegroundInverse">@color/foreground_device_default_dark</item>
         <item name="colorPopupBackground">?attr/colorBackgroundFloating</item>
@@ -2821,28 +2821,28 @@
     <style name="Theme.DeviceDefault.Light.DarkActionBar" parent="Theme.Material.Light.DarkActionBar">
         <!-- Color palette -->
         <item name="colorPrimary">@color/primary_device_default_dark</item>
-        <item name="colorPrimaryDark">@color/primary_dark_device_default_dark</item>
+        <item name="colorPrimaryDark">@color/primary_device_default_dark</item>
         <item name="colorAccent">@color/accent_device_default_light</item>
-        <item name="colorAccentPrimary">@color/accent_primary_device_default</item>
-        <item name="colorAccentSecondary">@color/accent_secondary_device_default</item>
-        <item name="colorAccentTertiary">@color/accent_tertiary_device_default</item>
-        <item name="colorAccentPrimaryVariant">@color/accent_primary_variant_light_device_default</item>
-        <item name="colorAccentSecondaryVariant">@color/accent_secondary_variant_light_device_default</item>
-        <item name="colorAccentTertiaryVariant">@color/accent_tertiary_variant_light_device_default</item>
-        <item name="colorSurface">@color/surface_light</item>
-        <item name="colorSurfaceHighlight">@color/surface_highlight_light</item>
-        <item name="colorSurfaceVariant">@color/surface_variant_light</item>
-        <item name="colorSurfaceHeader">@color/surface_header_light</item>
+        <item name="colorAccentPrimary">@color/system_primary_dark</item>
+        <item name="colorAccentSecondary">@color/system_secondary_dark</item>
+        <item name="colorAccentTertiary">@color/system_tertiary_dark</item>
+        <item name="colorAccentPrimaryVariant">@color/system_primary_container_light</item>
+        <item name="colorAccentSecondaryVariant">@color/system_secondary_container_light</item>
+        <item name="colorAccentTertiaryVariant">@color/system_tertiary_container_light</item>
+        <item name="colorSurface">@color/system_surface_container_light</item>
+        <item name="colorSurfaceHighlight">@color/system_surface_bright_light</item>
+        <item name="colorSurfaceVariant">@color/system_surface_container_high_light</item>
+        <item name="colorSurfaceHeader">@color/system_surface_container_highest_light</item>
         <item name="colorError">@color/error_color_device_default_light</item>
-        <item name="colorBackground">@color/background_device_default_light</item>
+        <item name="colorBackground">@color/system_surface_container_light</item>
         <item name="colorBackgroundFloating">@color/background_floating_device_default_light</item>
-        <item name="textColorPrimary">@color/text_color_primary_device_default_light</item>
-        <item name="textColorSecondary">@color/text_color_secondary_device_default_light</item>
-        <item name="textColorTertiary">@color/text_color_tertiary_device_default_light</item>
-        <item name="textColorPrimaryInverse">@color/text_color_primary_device_default_dark</item>
-        <item name="textColorSecondaryInverse">@color/text_color_secondary_device_default_dark</item>
-        <item name="textColorTertiaryInverse">@color/text_color_tertiary_device_default_dark</item>
-        <item name="textColorOnAccent">@color/text_color_on_accent_device_default</item>
+        <item name="textColorPrimary">@color/system_on_surface_light</item>
+        <item name="textColorSecondary">@color/system_on_surface_variant_light</item>
+        <item name="textColorTertiary">@color/system_outline_light</item>
+        <item name="textColorPrimaryInverse">@color/system_on_surface_dark</item>
+        <item name="textColorSecondaryInverse">@color/system_on_surface_variant_dark</item>
+        <item name="textColorTertiaryInverse">@color/system_outline_dark</item>
+        <item name="textColorOnAccent">@color/system_on_primary_dark</item>
         <item name="colorForeground">@color/foreground_device_default_light</item>
         <item name="colorForegroundInverse">@color/foreground_device_default_dark</item>
 
@@ -2937,28 +2937,28 @@
     <style name="Theme.DeviceDefault.Light.NoActionBar" parent="Theme.Material.Light.NoActionBar">
         <!-- Color palette -->
         <item name="colorPrimary">@color/primary_device_default_light</item>
-        <item name="colorPrimaryDark">@color/primary_dark_device_default_light</item>
+        <item name="colorPrimaryDark">@color/primary_device_default_light</item>
         <item name="colorAccent">@color/accent_device_default_light</item>
-        <item name="colorAccentPrimary">@color/accent_primary_device_default</item>
-        <item name="colorAccentSecondary">@color/accent_secondary_device_default</item>
-        <item name="colorAccentTertiary">@color/accent_tertiary_device_default</item>
-        <item name="colorAccentPrimaryVariant">@color/accent_primary_variant_light_device_default</item>
-        <item name="colorAccentSecondaryVariant">@color/accent_secondary_variant_light_device_default</item>
-        <item name="colorAccentTertiaryVariant">@color/accent_tertiary_variant_light_device_default</item>
-        <item name="colorSurface">@color/surface_light</item>
-        <item name="colorSurfaceHighlight">@color/surface_highlight_light</item>
-        <item name="colorSurfaceVariant">@color/surface_variant_light</item>
-        <item name="colorSurfaceHeader">@color/surface_header_light</item>
+        <item name="colorAccentPrimary">@color/system_primary_dark</item>
+        <item name="colorAccentSecondary">@color/system_secondary_dark</item>
+        <item name="colorAccentTertiary">@color/system_tertiary_dark</item>
+        <item name="colorAccentPrimaryVariant">@color/system_primary_container_light</item>
+        <item name="colorAccentSecondaryVariant">@color/system_secondary_container_light</item>
+        <item name="colorAccentTertiaryVariant">@color/system_tertiary_container_light</item>
+        <item name="colorSurface">@color/system_surface_container_light</item>
+        <item name="colorSurfaceHighlight">@color/system_surface_bright_light</item>
+        <item name="colorSurfaceVariant">@color/system_surface_container_high_light</item>
+        <item name="colorSurfaceHeader">@color/system_surface_container_highest_light</item>
         <item name="colorError">@color/error_color_device_default_light</item>
-        <item name="colorBackground">@color/background_device_default_light</item>
+        <item name="colorBackground">@color/system_surface_container_light</item>
         <item name="colorBackgroundFloating">@color/background_floating_device_default_light</item>
-        <item name="textColorPrimary">@color/text_color_primary_device_default_light</item>
-        <item name="textColorSecondary">@color/text_color_secondary_device_default_light</item>
-        <item name="textColorTertiary">@color/text_color_tertiary_device_default_light</item>
-        <item name="textColorPrimaryInverse">@color/text_color_primary_device_default_dark</item>
-        <item name="textColorSecondaryInverse">@color/text_color_secondary_device_default_dark</item>
-        <item name="textColorTertiaryInverse">@color/text_color_tertiary_device_default_dark</item>
-        <item name="textColorOnAccent">@color/text_color_on_accent_device_default</item>
+        <item name="textColorPrimary">@color/system_on_surface_light</item>
+        <item name="textColorSecondary">@color/system_on_surface_variant_light</item>
+        <item name="textColorTertiary">@color/system_outline_light</item>
+        <item name="textColorPrimaryInverse">@color/system_on_surface_dark</item>
+        <item name="textColorSecondaryInverse">@color/system_on_surface_variant_dark</item>
+        <item name="textColorTertiaryInverse">@color/system_outline_dark</item>
+        <item name="textColorOnAccent">@color/system_on_primary_dark</item>
         <item name="colorForeground">@color/foreground_device_default_light</item>
         <item name="colorForegroundInverse">@color/foreground_device_default_dark</item>
 
@@ -3054,28 +3054,28 @@
     <style name="Theme.DeviceDefault.Light.NoActionBar.Fullscreen" parent="Theme.Material.Light.NoActionBar.Fullscreen">
         <!-- Color palette -->
         <item name="colorPrimary">@color/primary_device_default_light</item>
-        <item name="colorPrimaryDark">@color/primary_dark_device_default_light</item>
+        <item name="colorPrimaryDark">@color/primary_device_default_light</item>
         <item name="colorAccent">@color/accent_device_default_light</item>
-        <item name="colorAccentPrimary">@color/accent_primary_device_default</item>
-        <item name="colorAccentSecondary">@color/accent_secondary_device_default</item>
-        <item name="colorAccentTertiary">@color/accent_tertiary_device_default</item>
-        <item name="colorAccentPrimaryVariant">@color/accent_primary_variant_light_device_default</item>
-        <item name="colorAccentSecondaryVariant">@color/accent_secondary_variant_light_device_default</item>
-        <item name="colorAccentTertiaryVariant">@color/accent_tertiary_variant_light_device_default</item>
-        <item name="colorSurface">@color/surface_light</item>
-        <item name="colorSurfaceHighlight">@color/surface_highlight_light</item>
-        <item name="colorSurfaceVariant">@color/surface_variant_light</item>
-        <item name="colorSurfaceHeader">@color/surface_header_light</item>
+        <item name="colorAccentPrimary">@color/system_primary_dark</item>
+        <item name="colorAccentSecondary">@color/system_secondary_dark</item>
+        <item name="colorAccentTertiary">@color/system_tertiary_dark</item>
+        <item name="colorAccentPrimaryVariant">@color/system_primary_container_light</item>
+        <item name="colorAccentSecondaryVariant">@color/system_secondary_container_light</item>
+        <item name="colorAccentTertiaryVariant">@color/system_tertiary_container_light</item>
+        <item name="colorSurface">@color/system_surface_container_light</item>
+        <item name="colorSurfaceHighlight">@color/system_surface_bright_light</item>
+        <item name="colorSurfaceVariant">@color/system_surface_container_high_light</item>
+        <item name="colorSurfaceHeader">@color/system_surface_container_highest_light</item>
         <item name="colorError">@color/error_color_device_default_light</item>
-        <item name="colorBackground">@color/background_device_default_light</item>
+        <item name="colorBackground">@color/system_surface_container_light</item>
         <item name="colorBackgroundFloating">@color/background_floating_device_default_light</item>
-        <item name="textColorPrimary">@color/text_color_primary_device_default_light</item>
-        <item name="textColorSecondary">@color/text_color_secondary_device_default_light</item>
-        <item name="textColorTertiary">@color/text_color_tertiary_device_default_light</item>
-        <item name="textColorPrimaryInverse">@color/text_color_primary_device_default_dark</item>
-        <item name="textColorSecondaryInverse">@color/text_color_secondary_device_default_dark</item>
-        <item name="textColorTertiaryInverse">@color/text_color_tertiary_device_default_dark</item>
-        <item name="textColorOnAccent">@color/text_color_on_accent_device_default</item>
+        <item name="textColorPrimary">@color/system_on_surface_light</item>
+        <item name="textColorSecondary">@color/system_on_surface_variant_light</item>
+        <item name="textColorTertiary">@color/system_outline_light</item>
+        <item name="textColorPrimaryInverse">@color/system_on_surface_dark</item>
+        <item name="textColorSecondaryInverse">@color/system_on_surface_variant_dark</item>
+        <item name="textColorTertiaryInverse">@color/system_outline_dark</item>
+        <item name="textColorOnAccent">@color/system_on_primary_dark</item>
         <item name="colorForeground">@color/foreground_device_default_light</item>
         <item name="colorForegroundInverse">@color/foreground_device_default_dark</item>
 
@@ -3173,28 +3173,28 @@
     <style name="Theme.DeviceDefault.Light.NoActionBar.Overscan" parent="Theme.Material.Light.NoActionBar.Overscan">
         <!-- Color palette -->
         <item name="colorPrimary">@color/primary_device_default_light</item>
-        <item name="colorPrimaryDark">@color/primary_dark_device_default_light</item>
+        <item name="colorPrimaryDark">@color/primary_device_default_light</item>
         <item name="colorAccent">@color/accent_device_default_light</item>
-        <item name="colorAccentPrimary">@color/accent_primary_device_default</item>
-        <item name="colorAccentSecondary">@color/accent_secondary_device_default</item>
-        <item name="colorAccentTertiary">@color/accent_tertiary_device_default</item>
-        <item name="colorAccentPrimaryVariant">@color/accent_primary_variant_light_device_default</item>
-        <item name="colorAccentSecondaryVariant">@color/accent_secondary_variant_light_device_default</item>
-        <item name="colorAccentTertiaryVariant">@color/accent_tertiary_variant_light_device_default</item>
-        <item name="colorSurface">@color/surface_light</item>
-        <item name="colorSurfaceHighlight">@color/surface_highlight_light</item>
-        <item name="colorSurfaceVariant">@color/surface_variant_light</item>
-        <item name="colorSurfaceHeader">@color/surface_header_light</item>
+        <item name="colorAccentPrimary">@color/system_primary_dark</item>
+        <item name="colorAccentSecondary">@color/system_secondary_dark</item>
+        <item name="colorAccentTertiary">@color/system_tertiary_dark</item>
+        <item name="colorAccentPrimaryVariant">@color/system_primary_container_light</item>
+        <item name="colorAccentSecondaryVariant">@color/system_secondary_container_light</item>
+        <item name="colorAccentTertiaryVariant">@color/system_tertiary_container_light</item>
+        <item name="colorSurface">@color/system_surface_container_light</item>
+        <item name="colorSurfaceHighlight">@color/system_surface_bright_light</item>
+        <item name="colorSurfaceVariant">@color/system_surface_container_high_light</item>
+        <item name="colorSurfaceHeader">@color/system_surface_container_highest_light</item>
         <item name="colorError">@color/error_color_device_default_light</item>
-        <item name="colorBackground">@color/background_device_default_light</item>
+        <item name="colorBackground">@color/system_surface_container_light</item>
         <item name="colorBackgroundFloating">@color/background_floating_device_default_light</item>
-        <item name="textColorPrimary">@color/text_color_primary_device_default_light</item>
-        <item name="textColorSecondary">@color/text_color_secondary_device_default_light</item>
-        <item name="textColorTertiary">@color/text_color_tertiary_device_default_light</item>
-        <item name="textColorPrimaryInverse">@color/text_color_primary_device_default_dark</item>
-        <item name="textColorSecondaryInverse">@color/text_color_secondary_device_default_dark</item>
-        <item name="textColorTertiaryInverse">@color/text_color_tertiary_device_default_dark</item>
-        <item name="textColorOnAccent">@color/text_color_on_accent_device_default</item>
+        <item name="textColorPrimary">@color/system_on_surface_light</item>
+        <item name="textColorSecondary">@color/system_on_surface_variant_light</item>
+        <item name="textColorTertiary">@color/system_outline_light</item>
+        <item name="textColorPrimaryInverse">@color/system_on_surface_dark</item>
+        <item name="textColorSecondaryInverse">@color/system_on_surface_variant_dark</item>
+        <item name="textColorTertiaryInverse">@color/system_outline_dark</item>
+        <item name="textColorOnAccent">@color/system_on_primary_dark</item>
         <item name="colorForeground">@color/foreground_device_default_light</item>
         <item name="colorForegroundInverse">@color/foreground_device_default_dark</item>
 
@@ -3291,28 +3291,28 @@
     <style name="Theme.DeviceDefault.Light.NoActionBar.TranslucentDecor" parent="Theme.Material.Light.NoActionBar.TranslucentDecor">
         <!-- Color palette -->
         <item name="colorPrimary">@color/primary_device_default_light</item>
-        <item name="colorPrimaryDark">@color/primary_dark_device_default_light</item>
+        <item name="colorPrimaryDark">@color/primary_device_default_light</item>
         <item name="colorAccent">@color/accent_device_default_light</item>
-        <item name="colorAccentPrimary">@color/accent_primary_device_default</item>
-        <item name="colorAccentSecondary">@color/accent_secondary_device_default</item>
-        <item name="colorAccentTertiary">@color/accent_tertiary_device_default</item>
-        <item name="colorAccentPrimaryVariant">@color/accent_primary_variant_light_device_default</item>
-        <item name="colorAccentSecondaryVariant">@color/accent_secondary_variant_light_device_default</item>
-        <item name="colorAccentTertiaryVariant">@color/accent_tertiary_variant_light_device_default</item>
-        <item name="colorSurface">@color/surface_light</item>
-        <item name="colorSurfaceHighlight">@color/surface_highlight_light</item>
-        <item name="colorSurfaceVariant">@color/surface_variant_light</item>
-        <item name="colorSurfaceHeader">@color/surface_header_light</item>
+        <item name="colorAccentPrimary">@color/system_primary_dark</item>
+        <item name="colorAccentSecondary">@color/system_secondary_dark</item>
+        <item name="colorAccentTertiary">@color/system_tertiary_dark</item>
+        <item name="colorAccentPrimaryVariant">@color/system_primary_container_light</item>
+        <item name="colorAccentSecondaryVariant">@color/system_secondary_container_light</item>
+        <item name="colorAccentTertiaryVariant">@color/system_tertiary_container_light</item>
+        <item name="colorSurface">@color/system_surface_container_light</item>
+        <item name="colorSurfaceHighlight">@color/system_surface_bright_light</item>
+        <item name="colorSurfaceVariant">@color/system_surface_container_high_light</item>
+        <item name="colorSurfaceHeader">@color/system_surface_container_highest_light</item>
         <item name="colorError">@color/error_color_device_default_light</item>
-        <item name="colorBackground">@color/background_device_default_light</item>
+        <item name="colorBackground">@color/system_surface_container_light</item>
         <item name="colorBackgroundFloating">@color/background_floating_device_default_light</item>
-        <item name="textColorPrimary">@color/text_color_primary_device_default_light</item>
-        <item name="textColorSecondary">@color/text_color_secondary_device_default_light</item>
-        <item name="textColorTertiary">@color/text_color_tertiary_device_default_light</item>
-        <item name="textColorPrimaryInverse">@color/text_color_primary_device_default_dark</item>
-        <item name="textColorSecondaryInverse">@color/text_color_secondary_device_default_dark</item>
-        <item name="textColorTertiaryInverse">@color/text_color_tertiary_device_default_dark</item>
-        <item name="textColorOnAccent">@color/text_color_on_accent_device_default</item>
+        <item name="textColorPrimary">@color/system_on_surface_light</item>
+        <item name="textColorSecondary">@color/system_on_surface_variant_light</item>
+        <item name="textColorTertiary">@color/system_outline_light</item>
+        <item name="textColorPrimaryInverse">@color/system_on_surface_dark</item>
+        <item name="textColorSecondaryInverse">@color/system_on_surface_variant_dark</item>
+        <item name="textColorTertiaryInverse">@color/system_outline_dark</item>
+        <item name="textColorOnAccent">@color/system_on_primary_dark</item>
         <item name="colorForeground">@color/foreground_device_default_light</item>
         <item name="colorForegroundInverse">@color/foreground_device_default_dark</item>
 
@@ -3426,28 +3426,28 @@
 
         <!-- Color palette -->
         <item name="colorPrimary">@color/primary_device_default_light</item>
-        <item name="colorPrimaryDark">@color/primary_dark_device_default_light</item>
+        <item name="colorPrimaryDark">@color/primary_device_default_light</item>
         <item name="colorAccent">@color/accent_device_default_light</item>
-        <item name="colorAccentPrimary">@color/accent_primary_device_default</item>
-        <item name="colorAccentSecondary">@color/accent_secondary_device_default</item>
-        <item name="colorAccentTertiary">@color/accent_tertiary_device_default</item>
-        <item name="colorAccentPrimaryVariant">@color/accent_primary_variant_light_device_default</item>
-        <item name="colorAccentSecondaryVariant">@color/accent_secondary_variant_light_device_default</item>
-        <item name="colorAccentTertiaryVariant">@color/accent_tertiary_variant_light_device_default</item>
-        <item name="colorSurface">@color/surface_light</item>
-        <item name="colorSurfaceHighlight">@color/surface_highlight_light</item>
-        <item name="colorSurfaceVariant">@color/surface_variant_light</item>
-        <item name="colorSurfaceHeader">@color/surface_header_light</item>
+        <item name="colorAccentPrimary">@color/system_primary_dark</item>
+        <item name="colorAccentSecondary">@color/system_secondary_dark</item>
+        <item name="colorAccentTertiary">@color/system_tertiary_dark</item>
+        <item name="colorAccentPrimaryVariant">@color/system_primary_container_light</item>
+        <item name="colorAccentSecondaryVariant">@color/system_secondary_container_light</item>
+        <item name="colorAccentTertiaryVariant">@color/system_tertiary_container_light</item>
+        <item name="colorSurface">@color/system_surface_container_light</item>
+        <item name="colorSurfaceHighlight">@color/system_surface_bright_light</item>
+        <item name="colorSurfaceVariant">@color/system_surface_container_high_light</item>
+        <item name="colorSurfaceHeader">@color/system_surface_container_highest_light</item>
         <item name="colorError">@color/error_color_device_default_light</item>
-        <item name="colorBackground">@color/background_device_default_light</item>
+        <item name="colorBackground">@color/system_surface_container_light</item>
         <item name="colorBackgroundFloating">@color/background_floating_device_default_light</item>
-        <item name="textColorPrimary">@color/text_color_primary_device_default_light</item>
-        <item name="textColorSecondary">@color/text_color_secondary_device_default_light</item>
-        <item name="textColorTertiary">@color/text_color_tertiary_device_default_light</item>
-        <item name="textColorPrimaryInverse">@color/text_color_primary_device_default_dark</item>
-        <item name="textColorSecondaryInverse">@color/text_color_secondary_device_default_dark</item>
-        <item name="textColorTertiaryInverse">@color/text_color_tertiary_device_default_dark</item>
-        <item name="textColorOnAccent">@color/text_color_on_accent_device_default</item>
+        <item name="textColorPrimary">@color/system_on_surface_light</item>
+        <item name="textColorSecondary">@color/system_on_surface_variant_light</item>
+        <item name="textColorTertiary">@color/system_outline_light</item>
+        <item name="textColorPrimaryInverse">@color/system_on_surface_dark</item>
+        <item name="textColorSecondaryInverse">@color/system_on_surface_variant_dark</item>
+        <item name="textColorTertiaryInverse">@color/system_outline_dark</item>
+        <item name="textColorOnAccent">@color/system_on_primary_dark</item>
         <item name="colorForeground">@color/foreground_device_default_light</item>
         <item name="colorForegroundInverse">@color/foreground_device_default_dark</item>
 
@@ -3535,28 +3535,28 @@
 
         <!-- Color palette -->
         <item name="colorPrimary">@color/primary_device_default_light</item>
-        <item name="colorPrimaryDark">@color/primary_dark_device_default_light</item>
+        <item name="colorPrimaryDark">@color/primary_device_default_light</item>
         <item name="colorAccent">@color/accent_device_default_light</item>
-        <item name="colorAccentPrimary">@color/accent_primary_device_default</item>
-        <item name="colorAccentSecondary">@color/accent_secondary_device_default</item>
-        <item name="colorAccentTertiary">@color/accent_tertiary_device_default</item>
-        <item name="colorAccentPrimaryVariant">@color/accent_primary_variant_light_device_default</item>
-        <item name="colorAccentSecondaryVariant">@color/accent_secondary_variant_light_device_default</item>
-        <item name="colorAccentTertiaryVariant">@color/accent_tertiary_variant_light_device_default</item>
-        <item name="colorSurface">@color/surface_light</item>
-        <item name="colorSurfaceHighlight">@color/surface_highlight_light</item>
-        <item name="colorSurfaceVariant">@color/surface_variant_light</item>
-        <item name="colorSurfaceHeader">@color/surface_header_light</item>
+        <item name="colorAccentPrimary">@color/system_primary_dark</item>
+        <item name="colorAccentSecondary">@color/system_secondary_dark</item>
+        <item name="colorAccentTertiary">@color/system_tertiary_dark</item>
+        <item name="colorAccentPrimaryVariant">@color/system_primary_container_light</item>
+        <item name="colorAccentSecondaryVariant">@color/system_secondary_container_light</item>
+        <item name="colorAccentTertiaryVariant">@color/system_tertiary_container_light</item>
+        <item name="colorSurface">@color/system_surface_container_light</item>
+        <item name="colorSurfaceHighlight">@color/system_surface_bright_light</item>
+        <item name="colorSurfaceVariant">@color/system_surface_container_high_light</item>
+        <item name="colorSurfaceHeader">@color/system_surface_container_highest_light</item>
         <item name="colorError">@color/error_color_device_default_light</item>
-        <item name="colorBackground">@color/background_device_default_light</item>
+        <item name="colorBackground">@color/system_surface_container_light</item>
         <item name="colorBackgroundFloating">@color/background_floating_device_default_light</item>
-        <item name="textColorPrimary">@color/text_color_primary_device_default_light</item>
-        <item name="textColorSecondary">@color/text_color_secondary_device_default_light</item>
-        <item name="textColorTertiary">@color/text_color_tertiary_device_default_light</item>
-        <item name="textColorPrimaryInverse">@color/text_color_primary_device_default_dark</item>
-        <item name="textColorSecondaryInverse">@color/text_color_secondary_device_default_dark</item>
-        <item name="textColorTertiaryInverse">@color/text_color_tertiary_device_default_dark</item>
-        <item name="textColorOnAccent">@color/text_color_on_accent_device_default</item>
+        <item name="textColorPrimary">@color/system_on_surface_light</item>
+        <item name="textColorSecondary">@color/system_on_surface_variant_light</item>
+        <item name="textColorTertiary">@color/system_outline_light</item>
+        <item name="textColorPrimaryInverse">@color/system_on_surface_dark</item>
+        <item name="textColorSecondaryInverse">@color/system_on_surface_variant_dark</item>
+        <item name="textColorTertiaryInverse">@color/system_outline_dark</item>
+        <item name="textColorOnAccent">@color/system_on_primary_dark</item>
         <item name="colorForeground">@color/foreground_device_default_light</item>
         <item name="colorForegroundInverse">@color/foreground_device_default_dark</item>
 
@@ -3654,28 +3654,28 @@
 
         <!-- Color palette -->
         <item name="colorPrimary">@color/primary_device_default_light</item>
-        <item name="colorPrimaryDark">@color/primary_dark_device_default_light</item>
+        <item name="colorPrimaryDark">@color/primary_device_default_light</item>
         <item name="colorAccent">@color/accent_device_default_light</item>
-        <item name="colorAccentPrimary">@color/accent_primary_device_default</item>
-        <item name="colorAccentSecondary">@color/accent_secondary_device_default</item>
-        <item name="colorAccentTertiary">@color/accent_tertiary_device_default</item>
-        <item name="colorAccentPrimaryVariant">@color/accent_primary_variant_light_device_default</item>
-        <item name="colorAccentSecondaryVariant">@color/accent_secondary_variant_light_device_default</item>
-        <item name="colorAccentTertiaryVariant">@color/accent_tertiary_variant_light_device_default</item>
-        <item name="colorSurface">@color/surface_light</item>
-        <item name="colorSurfaceHighlight">@color/surface_highlight_light</item>
-        <item name="colorSurfaceVariant">@color/surface_variant_light</item>
-        <item name="colorSurfaceHeader">@color/surface_header_light</item>
+        <item name="colorAccentPrimary">@color/system_primary_dark</item>
+        <item name="colorAccentSecondary">@color/system_secondary_dark</item>
+        <item name="colorAccentTertiary">@color/system_tertiary_dark</item>
+        <item name="colorAccentPrimaryVariant">@color/system_primary_container_light</item>
+        <item name="colorAccentSecondaryVariant">@color/system_secondary_container_light</item>
+        <item name="colorAccentTertiaryVariant">@color/system_tertiary_container_light</item>
+        <item name="colorSurface">@color/system_surface_container_light</item>
+        <item name="colorSurfaceHighlight">@color/system_surface_bright_light</item>
+        <item name="colorSurfaceVariant">@color/system_surface_container_high_light</item>
+        <item name="colorSurfaceHeader">@color/system_surface_container_highest_light</item>
         <item name="colorError">@color/error_color_device_default_light</item>
-        <item name="colorBackground">@color/background_device_default_light</item>
+        <item name="colorBackground">@color/system_surface_container_light</item>
         <item name="colorBackgroundFloating">@color/background_floating_device_default_light</item>
-        <item name="textColorPrimary">@color/text_color_primary_device_default_light</item>
-        <item name="textColorSecondary">@color/text_color_secondary_device_default_light</item>
-        <item name="textColorTertiary">@color/text_color_tertiary_device_default_light</item>
-        <item name="textColorPrimaryInverse">@color/text_color_primary_device_default_dark</item>
-        <item name="textColorSecondaryInverse">@color/text_color_secondary_device_default_dark</item>
-        <item name="textColorTertiaryInverse">@color/text_color_tertiary_device_default_dark</item>
-        <item name="textColorOnAccent">@color/text_color_on_accent_device_default</item>
+        <item name="textColorPrimary">@color/system_on_surface_light</item>
+        <item name="textColorSecondary">@color/system_on_surface_variant_light</item>
+        <item name="textColorTertiary">@color/system_outline_light</item>
+        <item name="textColorPrimaryInverse">@color/system_on_surface_dark</item>
+        <item name="textColorSecondaryInverse">@color/system_on_surface_variant_dark</item>
+        <item name="textColorTertiaryInverse">@color/system_outline_dark</item>
+        <item name="textColorOnAccent">@color/system_on_primary_dark</item>
         <item name="colorForeground">@color/foreground_device_default_light</item>
         <item name="colorForegroundInverse">@color/foreground_device_default_dark</item>
 
@@ -3774,28 +3774,28 @@
 
         <!-- Color palette -->
         <item name="colorPrimary">@color/primary_device_default_light</item>
-        <item name="colorPrimaryDark">@color/primary_dark_device_default_light</item>
+        <item name="colorPrimaryDark">@color/primary_device_default_light</item>
         <item name="colorAccent">@color/accent_device_default_light</item>
-        <item name="colorAccentPrimary">@color/accent_primary_device_default</item>
-        <item name="colorAccentSecondary">@color/accent_secondary_device_default</item>
-        <item name="colorAccentTertiary">@color/accent_tertiary_device_default</item>
-        <item name="colorAccentPrimaryVariant">@color/accent_primary_variant_light_device_default</item>
-        <item name="colorAccentSecondaryVariant">@color/accent_secondary_variant_light_device_default</item>
-        <item name="colorAccentTertiaryVariant">@color/accent_tertiary_variant_light_device_default</item>
-        <item name="colorSurface">@color/surface_light</item>
-        <item name="colorSurfaceHighlight">@color/surface_highlight_light</item>
-        <item name="colorSurfaceVariant">@color/surface_variant_light</item>
-        <item name="colorSurfaceHeader">@color/surface_header_light</item>
+        <item name="colorAccentPrimary">@color/system_primary_dark</item>
+        <item name="colorAccentSecondary">@color/system_secondary_dark</item>
+        <item name="colorAccentTertiary">@color/system_tertiary_dark</item>
+        <item name="colorAccentPrimaryVariant">@color/system_primary_container_light</item>
+        <item name="colorAccentSecondaryVariant">@color/system_secondary_container_light</item>
+        <item name="colorAccentTertiaryVariant">@color/system_tertiary_container_light</item>
+        <item name="colorSurface">@color/system_surface_container_light</item>
+        <item name="colorSurfaceHighlight">@color/system_surface_bright_light</item>
+        <item name="colorSurfaceVariant">@color/system_surface_container_high_light</item>
+        <item name="colorSurfaceHeader">@color/system_surface_container_highest_light</item>
         <item name="colorError">@color/error_color_device_default_light</item>
-        <item name="colorBackground">@color/background_device_default_light</item>
+        <item name="colorBackground">@color/system_surface_container_light</item>
         <item name="colorBackgroundFloating">@color/background_floating_device_default_light</item>
-        <item name="textColorPrimary">@color/text_color_primary_device_default_light</item>
-        <item name="textColorSecondary">@color/text_color_secondary_device_default_light</item>
-        <item name="textColorTertiary">@color/text_color_tertiary_device_default_light</item>
-        <item name="textColorPrimaryInverse">@color/text_color_primary_device_default_dark</item>
-        <item name="textColorSecondaryInverse">@color/text_color_secondary_device_default_dark</item>
-        <item name="textColorTertiaryInverse">@color/text_color_tertiary_device_default_dark</item>
-        <item name="textColorOnAccent">@color/text_color_on_accent_device_default</item>
+        <item name="textColorPrimary">@color/system_on_surface_light</item>
+        <item name="textColorSecondary">@color/system_on_surface_variant_light</item>
+        <item name="textColorTertiary">@color/system_outline_light</item>
+        <item name="textColorPrimaryInverse">@color/system_on_surface_dark</item>
+        <item name="textColorSecondaryInverse">@color/system_on_surface_variant_dark</item>
+        <item name="textColorTertiaryInverse">@color/system_outline_dark</item>
+        <item name="textColorOnAccent">@color/system_on_primary_dark</item>
         <item name="colorForeground">@color/foreground_device_default_light</item>
         <item name="colorForegroundInverse">@color/foreground_device_default_dark</item>
 
@@ -3895,26 +3895,26 @@
 
         <!-- Color palette -->
         <item name="colorPrimary">@color/primary_device_default_light</item>
-        <item name="colorPrimaryDark">@color/primary_dark_device_default_light</item>
+        <item name="colorPrimaryDark">@color/primary_device_default_light</item>
         <item name="colorAccent">@color/accent_device_default_light</item>
-        <item name="colorAccentPrimary">@color/accent_primary_device_default</item>
-        <item name="colorAccentSecondary">@color/accent_secondary_device_default</item>
-        <item name="colorAccentTertiary">@color/accent_tertiary_device_default</item>
-        <item name="colorAccentPrimaryVariant">@color/accent_primary_variant_light_device_default</item>
-        <item name="colorAccentSecondaryVariant">@color/accent_secondary_variant_light_device_default</item>
-        <item name="colorAccentTertiaryVariant">@color/accent_tertiary_variant_light_device_default</item>
-        <item name="colorSurface">@color/surface_light</item>
-        <item name="colorSurfaceHighlight">@color/surface_highlight_light</item>
-        <item name="colorSurfaceVariant">@color/surface_variant_light</item>
-        <item name="colorSurfaceHeader">@color/surface_header_light</item>
+        <item name="colorAccentPrimary">@color/system_primary_dark</item>
+        <item name="colorAccentSecondary">@color/system_secondary_dark</item>
+        <item name="colorAccentTertiary">@color/system_tertiary_dark</item>
+        <item name="colorAccentPrimaryVariant">@color/system_primary_container_light</item>
+        <item name="colorAccentSecondaryVariant">@color/system_secondary_container_light</item>
+        <item name="colorAccentTertiaryVariant">@color/system_tertiary_container_light</item>
+        <item name="colorSurface">@color/system_surface_container_light</item>
+        <item name="colorSurfaceHighlight">@color/system_surface_bright_light</item>
+        <item name="colorSurfaceVariant">@color/system_surface_container_high_light</item>
+        <item name="colorSurfaceHeader">@color/system_surface_container_highest_light</item>
         <item name="colorError">@color/error_color_device_default_light</item>
-        <item name="textColorPrimary">@color/text_color_primary_device_default_light</item>
-        <item name="textColorSecondary">@color/text_color_secondary_device_default_light</item>
-        <item name="textColorTertiary">@color/text_color_tertiary_device_default_light</item>
-        <item name="textColorPrimaryInverse">@color/text_color_primary_device_default_dark</item>
-        <item name="textColorSecondaryInverse">@color/text_color_secondary_device_default_dark</item>
-        <item name="textColorTertiaryInverse">@color/text_color_tertiary_device_default_dark</item>
-        <item name="textColorOnAccent">@color/text_color_on_accent_device_default</item>
+        <item name="textColorPrimary">@color/system_on_surface_light</item>
+        <item name="textColorSecondary">@color/system_on_surface_variant_light</item>
+        <item name="textColorTertiary">@color/system_outline_light</item>
+        <item name="textColorPrimaryInverse">@color/system_on_surface_dark</item>
+        <item name="textColorSecondaryInverse">@color/system_on_surface_variant_dark</item>
+        <item name="textColorTertiaryInverse">@color/system_outline_dark</item>
+        <item name="textColorOnAccent">@color/system_on_primary_dark</item>
         <item name="colorForeground">@color/foreground_device_default_light</item>
         <item name="colorForegroundInverse">@color/foreground_device_default_dark</item>
 
@@ -3996,26 +3996,26 @@
 
         <!-- Color palette -->
         <item name="colorPrimary">@color/primary_device_default_light</item>
-        <item name="colorPrimaryDark">@color/primary_dark_device_default_light</item>
+        <item name="colorPrimaryDark">@color/primary_device_default_light</item>
         <item name="colorAccent">@color/accent_device_default_light</item>
-        <item name="colorAccentPrimary">@color/accent_primary_device_default</item>
-        <item name="colorAccentSecondary">@color/accent_secondary_device_default</item>
-        <item name="colorAccentTertiary">@color/accent_tertiary_device_default</item>
-        <item name="colorAccentPrimaryVariant">@color/accent_primary_variant_light_device_default</item>
-        <item name="colorAccentSecondaryVariant">@color/accent_secondary_variant_light_device_default</item>
-        <item name="colorAccentTertiaryVariant">@color/accent_tertiary_variant_light_device_default</item>
-        <item name="colorSurface">@color/surface_light</item>
-        <item name="colorSurfaceHighlight">@color/surface_highlight_light</item>
-        <item name="colorSurfaceVariant">@color/surface_variant_light</item>
-        <item name="colorSurfaceHeader">@color/surface_header_light</item>
+        <item name="colorAccentPrimary">@color/system_primary_dark</item>
+        <item name="colorAccentSecondary">@color/system_secondary_dark</item>
+        <item name="colorAccentTertiary">@color/system_tertiary_dark</item>
+        <item name="colorAccentPrimaryVariant">@color/system_primary_container_light</item>
+        <item name="colorAccentSecondaryVariant">@color/system_secondary_container_light</item>
+        <item name="colorAccentTertiaryVariant">@color/system_tertiary_container_light</item>
+        <item name="colorSurface">@color/system_surface_container_light</item>
+        <item name="colorSurfaceHighlight">@color/system_surface_bright_light</item>
+        <item name="colorSurfaceVariant">@color/system_surface_container_high_light</item>
+        <item name="colorSurfaceHeader">@color/system_surface_container_highest_light</item>
         <item name="colorError">@color/error_color_device_default_light</item>
-        <item name="textColorPrimary">@color/text_color_primary_device_default_light</item>
-        <item name="textColorSecondary">@color/text_color_secondary_device_default_light</item>
-        <item name="textColorTertiary">@color/text_color_tertiary_device_default_light</item>
-        <item name="textColorPrimaryInverse">@color/text_color_primary_device_default_dark</item>
-        <item name="textColorSecondaryInverse">@color/text_color_secondary_device_default_dark</item>
-        <item name="textColorTertiaryInverse">@color/text_color_tertiary_device_default_dark</item>
-        <item name="textColorOnAccent">@color/text_color_on_accent_device_default</item>
+        <item name="textColorPrimary">@color/system_on_surface_light</item>
+        <item name="textColorSecondary">@color/system_on_surface_variant_light</item>
+        <item name="textColorTertiary">@color/system_outline_light</item>
+        <item name="textColorPrimaryInverse">@color/system_on_surface_dark</item>
+        <item name="textColorSecondaryInverse">@color/system_on_surface_variant_dark</item>
+        <item name="textColorTertiaryInverse">@color/system_outline_dark</item>
+        <item name="textColorOnAccent">@color/system_on_primary_dark</item>
         <item name="colorForeground">@color/foreground_device_default_light</item>
         <item name="colorForegroundInverse">@color/foreground_device_default_dark</item>
 
@@ -4096,28 +4096,28 @@
 
         <!-- Color palette -->
         <item name="colorPrimary">@color/primary_device_default_light</item>
-        <item name="colorPrimaryDark">@color/primary_dark_device_default_light</item>
+        <item name="colorPrimaryDark">@color/primary_device_default_light</item>
         <item name="colorAccent">@color/accent_device_default_light</item>
-        <item name="colorAccentPrimary">@color/accent_primary_device_default</item>
-        <item name="colorAccentSecondary">@color/accent_secondary_device_default</item>
-        <item name="colorAccentTertiary">@color/accent_tertiary_device_default</item>
-        <item name="colorAccentPrimaryVariant">@color/accent_primary_variant_light_device_default</item>
-        <item name="colorAccentSecondaryVariant">@color/accent_secondary_variant_light_device_default</item>
-        <item name="colorAccentTertiaryVariant">@color/accent_tertiary_variant_light_device_default</item>
-        <item name="colorSurface">@color/surface_light</item>
-        <item name="colorSurfaceHighlight">@color/surface_highlight_light</item>
-        <item name="colorSurfaceVariant">@color/surface_variant_light</item>
-        <item name="colorSurfaceHeader">@color/surface_header_light</item>
+        <item name="colorAccentPrimary">@color/system_primary_dark</item>
+        <item name="colorAccentSecondary">@color/system_secondary_dark</item>
+        <item name="colorAccentTertiary">@color/system_tertiary_dark</item>
+        <item name="colorAccentPrimaryVariant">@color/system_primary_container_light</item>
+        <item name="colorAccentSecondaryVariant">@color/system_secondary_container_light</item>
+        <item name="colorAccentTertiaryVariant">@color/system_tertiary_container_light</item>
+        <item name="colorSurface">@color/system_surface_container_light</item>
+        <item name="colorSurfaceHighlight">@color/system_surface_bright_light</item>
+        <item name="colorSurfaceVariant">@color/system_surface_container_high_light</item>
+        <item name="colorSurfaceHeader">@color/system_surface_container_highest_light</item>
         <item name="colorError">@color/error_color_device_default_light</item>
-        <item name="colorBackground">@color/background_device_default_light</item>
+        <item name="colorBackground">@color/system_surface_container_light</item>
         <item name="colorBackgroundFloating">@color/background_floating_device_default_light</item>
-        <item name="textColorPrimary">@color/text_color_primary_device_default_light</item>
-        <item name="textColorSecondary">@color/text_color_secondary_device_default_light</item>
-        <item name="textColorTertiary">@color/text_color_tertiary_device_default_light</item>
-        <item name="textColorPrimaryInverse">@color/text_color_primary_device_default_dark</item>
-        <item name="textColorSecondaryInverse">@color/text_color_secondary_device_default_dark</item>
-        <item name="textColorTertiaryInverse">@color/text_color_tertiary_device_default_dark</item>
-        <item name="textColorOnAccent">@color/text_color_on_accent_device_default</item>
+        <item name="textColorPrimary">@color/system_on_surface_light</item>
+        <item name="textColorSecondary">@color/system_on_surface_variant_light</item>
+        <item name="textColorTertiary">@color/system_outline_light</item>
+        <item name="textColorPrimaryInverse">@color/system_on_surface_dark</item>
+        <item name="textColorSecondaryInverse">@color/system_on_surface_variant_dark</item>
+        <item name="textColorTertiaryInverse">@color/system_outline_dark</item>
+        <item name="textColorOnAccent">@color/system_on_primary_dark</item>
         <item name="colorForeground">@color/foreground_device_default_light</item>
         <item name="colorForegroundInverse">@color/foreground_device_default_dark</item>
 
@@ -4217,28 +4217,28 @@
 
         <!-- Color palette -->
         <item name="colorPrimary">@color/primary_device_default_light</item>
-        <item name="colorPrimaryDark">@color/primary_dark_device_default_light</item>
+        <item name="colorPrimaryDark">@color/primary_device_default_light</item>
         <item name="colorAccent">@color/accent_device_default_light</item>
-        <item name="colorAccentPrimary">@color/accent_primary_device_default</item>
-        <item name="colorAccentSecondary">@color/accent_secondary_device_default</item>
-        <item name="colorAccentTertiary">@color/accent_tertiary_device_default</item>
-        <item name="colorAccentPrimaryVariant">@color/accent_primary_variant_light_device_default</item>
-        <item name="colorAccentSecondaryVariant">@color/accent_secondary_variant_light_device_default</item>
-        <item name="colorAccentTertiaryVariant">@color/accent_tertiary_variant_light_device_default</item>
-        <item name="colorSurface">@color/surface_light</item>
-        <item name="colorSurfaceHighlight">@color/surface_highlight_light</item>
-        <item name="colorSurfaceVariant">@color/surface_variant_light</item>
-        <item name="colorSurfaceHeader">@color/surface_header_light</item>
+        <item name="colorAccentPrimary">@color/system_primary_dark</item>
+        <item name="colorAccentSecondary">@color/system_secondary_dark</item>
+        <item name="colorAccentTertiary">@color/system_tertiary_dark</item>
+        <item name="colorAccentPrimaryVariant">@color/system_primary_container_light</item>
+        <item name="colorAccentSecondaryVariant">@color/system_secondary_container_light</item>
+        <item name="colorAccentTertiaryVariant">@color/system_tertiary_container_light</item>
+        <item name="colorSurface">@color/system_surface_container_light</item>
+        <item name="colorSurfaceHighlight">@color/system_surface_bright_light</item>
+        <item name="colorSurfaceVariant">@color/system_surface_container_high_light</item>
+        <item name="colorSurfaceHeader">@color/system_surface_container_highest_light</item>
         <item name="colorError">@color/error_color_device_default_light</item>
-        <item name="colorBackground">@color/background_device_default_light</item>
+        <item name="colorBackground">@color/system_surface_container_light</item>
         <item name="colorBackgroundFloating">@color/background_floating_device_default_light</item>
-        <item name="textColorPrimary">@color/text_color_primary_device_default_light</item>
-        <item name="textColorSecondary">@color/text_color_secondary_device_default_light</item>
-        <item name="textColorTertiary">@color/text_color_tertiary_device_default_light</item>
-        <item name="textColorPrimaryInverse">@color/text_color_primary_device_default_dark</item>
-        <item name="textColorSecondaryInverse">@color/text_color_secondary_device_default_dark</item>
-        <item name="textColorTertiaryInverse">@color/text_color_tertiary_device_default_dark</item>
-        <item name="textColorOnAccent">@color/text_color_on_accent_device_default</item>
+        <item name="textColorPrimary">@color/system_on_surface_light</item>
+        <item name="textColorSecondary">@color/system_on_surface_variant_light</item>
+        <item name="textColorTertiary">@color/system_outline_light</item>
+        <item name="textColorPrimaryInverse">@color/system_on_surface_dark</item>
+        <item name="textColorSecondaryInverse">@color/system_on_surface_variant_dark</item>
+        <item name="textColorTertiaryInverse">@color/system_outline_dark</item>
+        <item name="textColorOnAccent">@color/system_on_primary_dark</item>
         <item name="colorForeground">@color/foreground_device_default_light</item>
         <item name="colorForegroundInverse">@color/foreground_device_default_dark</item>
 
@@ -4336,28 +4336,28 @@
 
         <!-- Color palette -->
         <item name="colorPrimary">@color/primary_device_default_light</item>
-        <item name="colorPrimaryDark">@color/primary_dark_device_default_light</item>
+        <item name="colorPrimaryDark">@color/primary_device_default_light</item>
         <item name="colorAccent">@color/accent_device_default_light</item>
-        <item name="colorAccentPrimary">@color/accent_primary_device_default</item>
-        <item name="colorAccentSecondary">@color/accent_secondary_device_default</item>
-        <item name="colorAccentTertiary">@color/accent_tertiary_device_default</item>
-        <item name="colorAccentPrimaryVariant">@color/accent_primary_variant_light_device_default</item>
-        <item name="colorAccentSecondaryVariant">@color/accent_secondary_variant_light_device_default</item>
-        <item name="colorAccentTertiaryVariant">@color/accent_tertiary_variant_light_device_default</item>
-        <item name="colorSurface">@color/surface_light</item>
-        <item name="colorSurfaceHighlight">@color/surface_highlight_light</item>
-        <item name="colorSurfaceVariant">@color/surface_variant_light</item>
-        <item name="colorSurfaceHeader">@color/surface_header_light</item>
+        <item name="colorAccentPrimary">@color/system_primary_dark</item>
+        <item name="colorAccentSecondary">@color/system_secondary_dark</item>
+        <item name="colorAccentTertiary">@color/system_tertiary_dark</item>
+        <item name="colorAccentPrimaryVariant">@color/system_primary_container_light</item>
+        <item name="colorAccentSecondaryVariant">@color/system_secondary_container_light</item>
+        <item name="colorAccentTertiaryVariant">@color/system_tertiary_container_light</item>
+        <item name="colorSurface">@color/system_surface_container_light</item>
+        <item name="colorSurfaceHighlight">@color/system_surface_bright_light</item>
+        <item name="colorSurfaceVariant">@color/system_surface_container_high_light</item>
+        <item name="colorSurfaceHeader">@color/system_surface_container_highest_light</item>
         <item name="colorError">@color/error_color_device_default_light</item>
-        <item name="colorBackground">@color/background_device_default_light</item>
+        <item name="colorBackground">@color/system_surface_container_light</item>
         <item name="colorBackgroundFloating">@color/background_floating_device_default_light</item>
-        <item name="textColorPrimary">@color/text_color_primary_device_default_light</item>
-        <item name="textColorSecondary">@color/text_color_secondary_device_default_light</item>
-        <item name="textColorTertiary">@color/text_color_tertiary_device_default_light</item>
-        <item name="textColorPrimaryInverse">@color/text_color_primary_device_default_dark</item>
-        <item name="textColorSecondaryInverse">@color/text_color_secondary_device_default_dark</item>
-        <item name="textColorTertiaryInverse">@color/text_color_tertiary_device_default_dark</item>
-        <item name="textColorOnAccent">@color/text_color_on_accent_device_default</item>
+        <item name="textColorPrimary">@color/system_on_surface_light</item>
+        <item name="textColorSecondary">@color/system_on_surface_variant_light</item>
+        <item name="textColorTertiary">@color/system_outline_light</item>
+        <item name="textColorPrimaryInverse">@color/system_on_surface_dark</item>
+        <item name="textColorSecondaryInverse">@color/system_on_surface_variant_dark</item>
+        <item name="textColorTertiaryInverse">@color/system_outline_dark</item>
+        <item name="textColorOnAccent">@color/system_on_primary_dark</item>
         <item name="colorForeground">@color/foreground_device_default_light</item>
         <item name="colorForegroundInverse">@color/foreground_device_default_dark</item>
 
@@ -4454,28 +4454,28 @@
     <style name="Theme.DeviceDefault.Light.Panel" parent="Theme.Material.Light.Panel">
         <!-- Color palette -->
         <item name="colorPrimary">@color/primary_device_default_light</item>
-        <item name="colorPrimaryDark">@color/primary_dark_device_default_light</item>
+        <item name="colorPrimaryDark">@color/primary_device_default_light</item>
         <item name="colorAccent">@color/accent_device_default_light</item>
-        <item name="colorAccentPrimary">@color/accent_primary_device_default</item>
-        <item name="colorAccentSecondary">@color/accent_secondary_device_default</item>
-        <item name="colorAccentTertiary">@color/accent_tertiary_device_default</item>
-        <item name="colorAccentPrimaryVariant">@color/accent_primary_variant_light_device_default</item>
-        <item name="colorAccentSecondaryVariant">@color/accent_secondary_variant_light_device_default</item>
-        <item name="colorAccentTertiaryVariant">@color/accent_tertiary_variant_light_device_default</item>
-        <item name="colorSurface">@color/surface_light</item>
-        <item name="colorSurfaceHighlight">@color/surface_highlight_light</item>
-        <item name="colorSurfaceVariant">@color/surface_variant_light</item>
-        <item name="colorSurfaceHeader">@color/surface_header_light</item>
+        <item name="colorAccentPrimary">@color/system_primary_dark</item>
+        <item name="colorAccentSecondary">@color/system_secondary_dark</item>
+        <item name="colorAccentTertiary">@color/system_tertiary_dark</item>
+        <item name="colorAccentPrimaryVariant">@color/system_primary_container_light</item>
+        <item name="colorAccentSecondaryVariant">@color/system_secondary_container_light</item>
+        <item name="colorAccentTertiaryVariant">@color/system_tertiary_container_light</item>
+        <item name="colorSurface">@color/system_surface_container_light</item>
+        <item name="colorSurfaceHighlight">@color/system_surface_bright_light</item>
+        <item name="colorSurfaceVariant">@color/system_surface_container_high_light</item>
+        <item name="colorSurfaceHeader">@color/system_surface_container_highest_light</item>
         <item name="colorError">@color/error_color_device_default_light</item>
-        <item name="colorBackground">@color/background_device_default_light</item>
+        <item name="colorBackground">@color/system_surface_container_light</item>
         <item name="colorBackgroundFloating">@color/background_floating_device_default_light</item>
-        <item name="textColorPrimary">@color/text_color_primary_device_default_light</item>
-        <item name="textColorSecondary">@color/text_color_secondary_device_default_light</item>
-        <item name="textColorTertiary">@color/text_color_tertiary_device_default_light</item>
-        <item name="textColorPrimaryInverse">@color/text_color_primary_device_default_dark</item>
-        <item name="textColorSecondaryInverse">@color/text_color_secondary_device_default_dark</item>
-        <item name="textColorTertiaryInverse">@color/text_color_tertiary_device_default_dark</item>
-        <item name="textColorOnAccent">@color/text_color_on_accent_device_default</item>
+        <item name="textColorPrimary">@color/system_on_surface_light</item>
+        <item name="textColorSecondary">@color/system_on_surface_variant_light</item>
+        <item name="textColorTertiary">@color/system_outline_light</item>
+        <item name="textColorPrimaryInverse">@color/system_on_surface_dark</item>
+        <item name="textColorSecondaryInverse">@color/system_on_surface_variant_dark</item>
+        <item name="textColorTertiaryInverse">@color/system_outline_dark</item>
+        <item name="textColorOnAccent">@color/system_on_primary_dark</item>
         <item name="colorForeground">@color/foreground_device_default_light</item>
         <item name="colorForegroundInverse">@color/foreground_device_default_dark</item>
 
@@ -4571,28 +4571,28 @@
 
         <!-- Color palette -->
         <item name="colorPrimary">@color/primary_device_default_light</item>
-        <item name="colorPrimaryDark">@color/primary_dark_device_default_light</item>
+        <item name="colorPrimaryDark">@color/primary_device_default_light</item>
         <item name="colorAccent">@color/accent_device_default_light</item>
-        <item name="colorAccentPrimary">@color/accent_primary_device_default</item>
-        <item name="colorAccentSecondary">@color/accent_secondary_device_default</item>
-        <item name="colorAccentTertiary">@color/accent_tertiary_device_default</item>
-        <item name="colorAccentPrimaryVariant">@color/accent_primary_variant_light_device_default</item>
-        <item name="colorAccentSecondaryVariant">@color/accent_secondary_variant_light_device_default</item>
-        <item name="colorAccentTertiaryVariant">@color/accent_tertiary_variant_light_device_default</item>
-        <item name="colorSurface">@color/surface_light</item>
-        <item name="colorSurfaceHighlight">@color/surface_highlight_light</item>
-        <item name="colorSurfaceVariant">@color/surface_variant_light</item>
-        <item name="colorSurfaceHeader">@color/surface_header_light</item>
+        <item name="colorAccentPrimary">@color/system_primary_dark</item>
+        <item name="colorAccentSecondary">@color/system_secondary_dark</item>
+        <item name="colorAccentTertiary">@color/system_tertiary_dark</item>
+        <item name="colorAccentPrimaryVariant">@color/system_primary_container_light</item>
+        <item name="colorAccentSecondaryVariant">@color/system_secondary_container_light</item>
+        <item name="colorAccentTertiaryVariant">@color/system_tertiary_container_light</item>
+        <item name="colorSurface">@color/system_surface_container_light</item>
+        <item name="colorSurfaceHighlight">@color/system_surface_bright_light</item>
+        <item name="colorSurfaceVariant">@color/system_surface_container_high_light</item>
+        <item name="colorSurfaceHeader">@color/system_surface_container_highest_light</item>
         <item name="colorError">@color/error_color_device_default_light</item>
-        <item name="colorBackground">@color/background_device_default_light</item>
+        <item name="colorBackground">@color/system_surface_container_light</item>
         <item name="colorBackgroundFloating">@color/background_floating_device_default_light</item>
-        <item name="textColorPrimary">@color/text_color_primary_device_default_light</item>
-        <item name="textColorSecondary">@color/text_color_secondary_device_default_light</item>
-        <item name="textColorTertiary">@color/text_color_tertiary_device_default_light</item>
-        <item name="textColorPrimaryInverse">@color/text_color_primary_device_default_dark</item>
-        <item name="textColorSecondaryInverse">@color/text_color_secondary_device_default_dark</item>
-        <item name="textColorTertiaryInverse">@color/text_color_tertiary_device_default_dark</item>
-        <item name="textColorOnAccent">@color/text_color_on_accent_device_default</item>
+        <item name="textColorPrimary">@color/system_on_surface_light</item>
+        <item name="textColorSecondary">@color/system_on_surface_variant_light</item>
+        <item name="textColorTertiary">@color/system_outline_light</item>
+        <item name="textColorPrimaryInverse">@color/system_on_surface_dark</item>
+        <item name="textColorSecondaryInverse">@color/system_on_surface_variant_dark</item>
+        <item name="textColorTertiaryInverse">@color/system_outline_dark</item>
+        <item name="textColorOnAccent">@color/system_on_primary_dark</item>
         <item name="colorForeground">@color/foreground_device_default_light</item>
         <item name="colorForegroundInverse">@color/foreground_device_default_dark</item>
 
@@ -4688,28 +4688,28 @@
     <style name="Theme.DeviceDefault.Light.SearchBar" parent="Theme.Material.Light.SearchBar">
         <!-- Color palette -->
         <item name="colorPrimary">@color/primary_device_default_light</item>
-        <item name="colorPrimaryDark">@color/primary_dark_device_default_light</item>
+        <item name="colorPrimaryDark">@color/primary_device_default_light</item>
         <item name="colorAccent">@color/accent_device_default_light</item>
-        <item name="colorAccentPrimary">@color/accent_primary_device_default</item>
-        <item name="colorAccentSecondary">@color/accent_secondary_device_default</item>
-        <item name="colorAccentTertiary">@color/accent_tertiary_device_default</item>
-        <item name="colorAccentPrimaryVariant">@color/accent_primary_variant_light_device_default</item>
-        <item name="colorAccentSecondaryVariant">@color/accent_secondary_variant_light_device_default</item>
-        <item name="colorAccentTertiaryVariant">@color/accent_tertiary_variant_light_device_default</item>
-        <item name="colorSurface">@color/surface_light</item>
-        <item name="colorSurfaceHighlight">@color/surface_highlight_light</item>
-        <item name="colorSurfaceVariant">@color/surface_variant_light</item>
-        <item name="colorSurfaceHeader">@color/surface_header_light</item>
+        <item name="colorAccentPrimary">@color/system_primary_dark</item>
+        <item name="colorAccentSecondary">@color/system_secondary_dark</item>
+        <item name="colorAccentTertiary">@color/system_tertiary_dark</item>
+        <item name="colorAccentPrimaryVariant">@color/system_primary_container_light</item>
+        <item name="colorAccentSecondaryVariant">@color/system_secondary_container_light</item>
+        <item name="colorAccentTertiaryVariant">@color/system_tertiary_container_light</item>
+        <item name="colorSurface">@color/system_surface_container_light</item>
+        <item name="colorSurfaceHighlight">@color/system_surface_bright_light</item>
+        <item name="colorSurfaceVariant">@color/system_surface_container_high_light</item>
+        <item name="colorSurfaceHeader">@color/system_surface_container_highest_light</item>
         <item name="colorError">@color/error_color_device_default_light</item>
-        <item name="colorBackground">@color/background_device_default_light</item>
+        <item name="colorBackground">@color/system_surface_container_light</item>
         <item name="colorBackgroundFloating">@color/background_floating_device_default_light</item>
-        <item name="textColorPrimary">@color/text_color_primary_device_default_light</item>
-        <item name="textColorSecondary">@color/text_color_secondary_device_default_light</item>
-        <item name="textColorTertiary">@color/text_color_tertiary_device_default_light</item>
-        <item name="textColorPrimaryInverse">@color/text_color_primary_device_default_dark</item>
-        <item name="textColorSecondaryInverse">@color/text_color_secondary_device_default_dark</item>
-        <item name="textColorTertiaryInverse">@color/text_color_tertiary_device_default_dark</item>
-        <item name="textColorOnAccent">@color/text_color_on_accent_device_default</item>
+        <item name="textColorPrimary">@color/system_on_surface_light</item>
+        <item name="textColorSecondary">@color/system_on_surface_variant_light</item>
+        <item name="textColorTertiary">@color/system_outline_light</item>
+        <item name="textColorPrimaryInverse">@color/system_on_surface_dark</item>
+        <item name="textColorSecondaryInverse">@color/system_on_surface_variant_dark</item>
+        <item name="textColorTertiaryInverse">@color/system_outline_dark</item>
+        <item name="textColorOnAccent">@color/system_on_primary_dark</item>
         <item name="colorForeground">@color/foreground_device_default_light</item>
         <item name="colorForegroundInverse">@color/foreground_device_default_dark</item>
 
@@ -4803,28 +4803,28 @@
     <style name="Theme.DeviceDefault.Light.Voice" parent="Theme.Material.Light.Voice">
         <!-- Color palette -->
         <item name="colorPrimary">@color/primary_device_default_light</item>
-        <item name="colorPrimaryDark">@color/primary_dark_device_default_light</item>
+        <item name="colorPrimaryDark">@color/primary_device_default_light</item>
         <item name="colorAccent">@color/accent_device_default_light</item>
-        <item name="colorAccentPrimary">@color/accent_primary_device_default</item>
-        <item name="colorAccentSecondary">@color/accent_secondary_device_default</item>
-        <item name="colorAccentTertiary">@color/accent_tertiary_device_default</item>
-        <item name="colorAccentPrimaryVariant">@color/accent_primary_variant_light_device_default</item>
-        <item name="colorAccentSecondaryVariant">@color/accent_secondary_variant_light_device_default</item>
-        <item name="colorAccentTertiaryVariant">@color/accent_tertiary_variant_light_device_default</item>
-        <item name="colorSurface">@color/surface_light</item>
-        <item name="colorSurfaceHighlight">@color/surface_highlight_light</item>
-        <item name="colorSurfaceVariant">@color/surface_variant_light</item>
-        <item name="colorSurfaceHeader">@color/surface_header_light</item>
+        <item name="colorAccentPrimary">@color/system_primary_dark</item>
+        <item name="colorAccentSecondary">@color/system_secondary_dark</item>
+        <item name="colorAccentTertiary">@color/system_tertiary_dark</item>
+        <item name="colorAccentPrimaryVariant">@color/system_primary_container_light</item>
+        <item name="colorAccentSecondaryVariant">@color/system_secondary_container_light</item>
+        <item name="colorAccentTertiaryVariant">@color/system_tertiary_container_light</item>
+        <item name="colorSurface">@color/system_surface_container_light</item>
+        <item name="colorSurfaceHighlight">@color/system_surface_bright_light</item>
+        <item name="colorSurfaceVariant">@color/system_surface_container_high_light</item>
+        <item name="colorSurfaceHeader">@color/system_surface_container_highest_light</item>
         <item name="colorError">@color/error_color_device_default_light</item>
-        <item name="colorBackground">@color/background_device_default_light</item>
+        <item name="colorBackground">@color/system_surface_container_light</item>
         <item name="colorBackgroundFloating">@color/background_floating_device_default_light</item>
-        <item name="textColorPrimary">@color/text_color_primary_device_default_light</item>
-        <item name="textColorSecondary">@color/text_color_secondary_device_default_light</item>
-        <item name="textColorTertiary">@color/text_color_tertiary_device_default_light</item>
-        <item name="textColorPrimaryInverse">@color/text_color_primary_device_default_dark</item>
-        <item name="textColorSecondaryInverse">@color/text_color_secondary_device_default_dark</item>
-        <item name="textColorTertiaryInverse">@color/text_color_tertiary_device_default_dark</item>
-        <item name="textColorOnAccent">@color/text_color_on_accent_device_default</item>
+        <item name="textColorPrimary">@color/system_on_surface_light</item>
+        <item name="textColorSecondary">@color/system_on_surface_variant_light</item>
+        <item name="textColorTertiary">@color/system_outline_light</item>
+        <item name="textColorPrimaryInverse">@color/system_on_surface_dark</item>
+        <item name="textColorSecondaryInverse">@color/system_on_surface_variant_dark</item>
+        <item name="textColorTertiaryInverse">@color/system_outline_dark</item>
+        <item name="textColorOnAccent">@color/system_on_primary_dark</item>
         <item name="colorForeground">@color/foreground_device_default_light</item>
         <item name="colorForegroundInverse">@color/foreground_device_default_dark</item>
 
@@ -4932,21 +4932,21 @@
         <item name="popupTheme">@style/ThemeOverlay.DeviceDefault.Popup.Light</item>
 
         <!-- Color palette -->
-        <item name="colorBackground">@color/background_device_default_light</item>
+        <item name="colorBackground">@color/system_surface_container_light</item>
         <item name="colorPrimary">@color/primary_device_default_settings_light</item>
         <item name="colorPrimaryDark">@color/primary_dark_device_default_settings_light</item>
         <item name="colorSecondary">@color/secondary_device_default_settings_light</item>
         <item name="colorAccent">@color/accent_device_default_light</item>
-        <item name="colorAccentPrimary">@color/accent_primary_device_default</item>
-        <item name="colorAccentSecondary">@color/accent_secondary_device_default</item>
-        <item name="colorAccentTertiary">@color/accent_tertiary_device_default</item>
-        <item name="colorAccentPrimaryVariant">@color/accent_primary_variant_light_device_default</item>
-        <item name="colorAccentSecondaryVariant">@color/accent_secondary_variant_light_device_default</item>
-        <item name="colorAccentTertiaryVariant">@color/accent_tertiary_variant_light_device_default</item>
-        <item name="colorSurface">@color/surface_light</item>
-        <item name="colorSurfaceHighlight">@color/surface_highlight_light</item>
-        <item name="colorSurfaceVariant">@color/surface_variant_light</item>
-        <item name="colorSurfaceHeader">@color/surface_header_light</item>
+        <item name="colorAccentPrimary">@color/system_primary_dark</item>
+        <item name="colorAccentSecondary">@color/system_secondary_dark</item>
+        <item name="colorAccentTertiary">@color/system_tertiary_dark</item>
+        <item name="colorAccentPrimaryVariant">@color/system_primary_container_light</item>
+        <item name="colorAccentSecondaryVariant">@color/system_secondary_container_light</item>
+        <item name="colorAccentTertiaryVariant">@color/system_tertiary_container_light</item>
+        <item name="colorSurface">@color/system_surface_container_light</item>
+        <item name="colorSurfaceHighlight">@color/system_surface_bright_light</item>
+        <item name="colorSurfaceVariant">@color/system_surface_container_high_light</item>
+        <item name="colorSurfaceHeader">@color/system_surface_container_highest_light</item>
         <item name="colorError">@color/error_color_device_default_light</item>
         <item name="colorEdgeEffect">@color/edge_effect_device_default_light</item>
 
@@ -5044,16 +5044,16 @@
         <item name="colorPrimaryDark">@color/primary_dark_device_default_settings_light</item>
         <item name="colorSecondary">@color/secondary_device_default_settings_light</item>
         <item name="colorAccent">@color/accent_device_default_light</item>
-        <item name="colorAccentPrimary">@color/accent_primary_device_default</item>
-        <item name="colorAccentSecondary">@color/accent_secondary_device_default</item>
-        <item name="colorAccentTertiary">@color/accent_tertiary_device_default</item>
-        <item name="colorAccentPrimaryVariant">@color/accent_primary_variant_light_device_default</item>
-        <item name="colorAccentSecondaryVariant">@color/accent_secondary_variant_light_device_default</item>
-        <item name="colorAccentTertiaryVariant">@color/accent_tertiary_variant_light_device_default</item>
-        <item name="colorSurface">@color/surface_light</item>
-        <item name="colorSurfaceHighlight">@color/surface_highlight_light</item>
-        <item name="colorSurfaceVariant">@color/surface_variant_light</item>
-        <item name="colorSurfaceHeader">@color/surface_header_light</item>
+        <item name="colorAccentPrimary">@color/system_primary_dark</item>
+        <item name="colorAccentSecondary">@color/system_secondary_dark</item>
+        <item name="colorAccentTertiary">@color/system_tertiary_dark</item>
+        <item name="colorAccentPrimaryVariant">@color/system_primary_container_light</item>
+        <item name="colorAccentSecondaryVariant">@color/system_secondary_container_light</item>
+        <item name="colorAccentTertiaryVariant">@color/system_tertiary_container_light</item>
+        <item name="colorSurface">@color/system_surface_container_light</item>
+        <item name="colorSurfaceHighlight">@color/system_surface_bright_light</item>
+        <item name="colorSurfaceVariant">@color/system_surface_container_high_light</item>
+        <item name="colorSurfaceHeader">@color/system_surface_container_highest_light</item>
         <item name="colorError">@color/error_color_device_default_light</item>
         <item name="colorControlNormal">?attr/textColorPrimary</item>
         <item name="alertDialogTheme">@style/Theme.DeviceDefault.Light.Dialog.Alert</item>
@@ -5148,16 +5148,16 @@
         <item name="colorPrimaryDark">@color/primary_dark_device_default_settings_light</item>
         <item name="colorSecondary">@color/secondary_device_default_settings_light</item>
         <item name="colorAccent">@color/accent_device_default_light</item>
-        <item name="colorAccentPrimary">@color/accent_primary_device_default</item>
-        <item name="colorAccentSecondary">@color/accent_secondary_device_default</item>
-        <item name="colorAccentTertiary">@color/accent_tertiary_device_default</item>
-        <item name="colorAccentPrimaryVariant">@color/accent_primary_variant_light_device_default</item>
-        <item name="colorAccentSecondaryVariant">@color/accent_secondary_variant_light_device_default</item>
-        <item name="colorAccentTertiaryVariant">@color/accent_tertiary_variant_light_device_default</item>
-        <item name="colorSurface">@color/surface_light</item>
-        <item name="colorSurfaceHighlight">@color/surface_highlight_light</item>
-        <item name="colorSurfaceVariant">@color/surface_variant_light</item>
-        <item name="colorSurfaceHeader">@color/surface_header_light</item>
+        <item name="colorAccentPrimary">@color/system_primary_dark</item>
+        <item name="colorAccentSecondary">@color/system_secondary_dark</item>
+        <item name="colorAccentTertiary">@color/system_tertiary_dark</item>
+        <item name="colorAccentPrimaryVariant">@color/system_primary_container_light</item>
+        <item name="colorAccentSecondaryVariant">@color/system_secondary_container_light</item>
+        <item name="colorAccentTertiaryVariant">@color/system_tertiary_container_light</item>
+        <item name="colorSurface">@color/system_surface_container_light</item>
+        <item name="colorSurfaceHighlight">@color/system_surface_bright_light</item>
+        <item name="colorSurfaceVariant">@color/system_surface_container_high_light</item>
+        <item name="colorSurfaceHeader">@color/system_surface_container_highest_light</item>
         <item name="colorError">@color/error_color_device_default_light</item>
         <item name="alertDialogTheme">@style/Theme.DeviceDefault.Light.Dialog.Alert</item>
 
@@ -5245,26 +5245,26 @@
         <item name="colorPrimaryDark">@color/primary_dark_device_default_settings</item>
         <item name="colorSecondary">@color/secondary_device_default_settings</item>
         <item name="colorAccent">@color/accent_device_default_dark</item>
-        <item name="colorAccentPrimary">@color/accent_primary_device_default</item>
-        <item name="colorAccentSecondary">@color/accent_secondary_device_default</item>
-        <item name="colorAccentTertiary">@color/accent_tertiary_device_default</item>
-        <item name="colorAccentPrimaryVariant">@color/accent_primary_variant_dark_device_default</item>
-        <item name="colorAccentSecondaryVariant">@color/accent_secondary_variant_dark_device_default</item>
-        <item name="colorAccentTertiaryVariant">@color/accent_tertiary_variant_dark_device_default</item>
-        <item name="colorSurface">@color/surface_dark</item>
-        <item name="colorSurfaceHighlight">@color/surface_highlight_dark</item>
-        <item name="colorSurfaceVariant">@color/surface_variant_dark</item>
-        <item name="colorSurfaceHeader">@color/surface_header_dark</item>
+        <item name="colorAccentPrimary">@color/system_primary_dark</item>
+        <item name="colorAccentSecondary">@color/system_secondary_dark</item>
+        <item name="colorAccentTertiary">@color/system_tertiary_dark</item>
+        <item name="colorAccentPrimaryVariant">@color/system_primary_container_dark</item>
+        <item name="colorAccentSecondaryVariant">@color/system_secondary_container_dark</item>
+        <item name="colorAccentTertiaryVariant">@color/system_tertiary_container_dark</item>
+        <item name="colorSurface">@color/system_surface_container_dark</item>
+        <item name="colorSurfaceHighlight">@color/system_surface_bright_dark</item>
+        <item name="colorSurfaceVariant">@color/system_surface_container_high_dark</item>
+        <item name="colorSurfaceHeader">@color/system_surface_container_highest_dark</item>
         <item name="colorError">@color/error_color_device_default_dark</item>
-        <item name="colorBackground">@color/background_device_default_dark</item>
+        <item name="colorBackground">@color/system_surface_container_dark</item>
         <item name="colorBackgroundFloating">@color/background_floating_device_default_dark</item>
-        <item name="textColorPrimary">@color/text_color_primary_device_default_dark</item>
-        <item name="textColorSecondary">@color/text_color_secondary_device_default_dark</item>
-        <item name="textColorTertiary">@color/text_color_tertiary_device_default_dark</item>
-        <item name="textColorPrimaryInverse">@color/text_color_primary_device_default_light</item>
-        <item name="textColorSecondaryInverse">@color/text_color_secondary_device_default_light</item>
-        <item name="textColorTertiaryInverse">@color/text_color_tertiary_device_default_light</item>
-        <item name="textColorOnAccent">@color/text_color_on_accent_device_default</item>
+        <item name="textColorPrimary">@color/system_on_surface_dark</item>
+        <item name="textColorSecondary">@color/system_on_surface_variant_dark</item>
+        <item name="textColorTertiary">@color/system_outline_dark</item>
+        <item name="textColorPrimaryInverse">@color/system_on_surface_light</item>
+        <item name="textColorSecondaryInverse">@color/system_on_surface_variant_light</item>
+        <item name="textColorTertiaryInverse">@color/system_outline_light</item>
+        <item name="textColorOnAccent">@color/system_on_primary_dark</item>
         <item name="colorForeground">@color/foreground_device_default_dark</item>
         <item name="colorForegroundInverse">@color/foreground_device_default_light</item>
 
@@ -5361,26 +5361,26 @@
         <item name="colorPrimaryDark">@color/primary_dark_device_default_settings</item>
         <item name="colorSecondary">@color/secondary_device_default_settings</item>
         <item name="colorAccent">@color/accent_device_default_light</item>
-        <item name="colorAccentPrimary">@color/accent_primary_device_default</item>
-        <item name="colorAccentSecondary">@color/accent_secondary_device_default</item>
-        <item name="colorAccentTertiary">@color/accent_tertiary_device_default</item>
-        <item name="colorAccentPrimaryVariant">@color/accent_primary_variant_light_device_default</item>
-        <item name="colorAccentSecondaryVariant">@color/accent_secondary_variant_light_device_default</item>
-        <item name="colorAccentTertiaryVariant">@color/accent_tertiary_variant_light_device_default</item>
-        <item name="colorSurface">@color/surface_light</item>
-        <item name="colorSurfaceHighlight">@color/surface_highlight_light</item>
-        <item name="colorSurfaceVariant">@color/surface_variant_light</item>
-        <item name="colorSurfaceHeader">@color/surface_header_light</item>
+        <item name="colorAccentPrimary">@color/system_primary_dark</item>
+        <item name="colorAccentSecondary">@color/system_secondary_dark</item>
+        <item name="colorAccentTertiary">@color/system_tertiary_dark</item>
+        <item name="colorAccentPrimaryVariant">@color/system_primary_container_light</item>
+        <item name="colorAccentSecondaryVariant">@color/system_secondary_container_light</item>
+        <item name="colorAccentTertiaryVariant">@color/system_tertiary_container_light</item>
+        <item name="colorSurface">@color/system_surface_container_light</item>
+        <item name="colorSurfaceHighlight">@color/system_surface_bright_light</item>
+        <item name="colorSurfaceVariant">@color/system_surface_container_high_light</item>
+        <item name="colorSurfaceHeader">@color/system_surface_container_highest_light</item>
         <item name="colorError">@color/error_color_device_default_light</item>
-        <item name="colorBackground">@color/background_device_default_light</item>
+        <item name="colorBackground">@color/system_surface_container_light</item>
         <item name="colorBackgroundFloating">@color/background_floating_device_default_light</item>
-        <item name="textColorPrimary">@color/text_color_primary_device_default_light</item>
-        <item name="textColorSecondary">@color/text_color_secondary_device_default_light</item>
-        <item name="textColorTertiary">@color/text_color_tertiary_device_default_light</item>
-        <item name="textColorPrimaryInverse">@color/text_color_primary_device_default_dark</item>
-        <item name="textColorSecondaryInverse">@color/text_color_secondary_device_default_dark</item>
-        <item name="textColorTertiaryInverse">@color/text_color_tertiary_device_default_dark</item>
-        <item name="textColorOnAccent">@color/text_color_on_accent_device_default</item>
+        <item name="textColorPrimary">@color/system_on_surface_light</item>
+        <item name="textColorSecondary">@color/system_on_surface_variant_light</item>
+        <item name="textColorTertiary">@color/system_outline_light</item>
+        <item name="textColorPrimaryInverse">@color/system_on_surface_dark</item>
+        <item name="textColorSecondaryInverse">@color/system_on_surface_variant_dark</item>
+        <item name="textColorTertiaryInverse">@color/system_outline_dark</item>
+        <item name="textColorOnAccent">@color/system_on_primary_dark</item>
         <item name="colorForeground">@color/foreground_device_default_light</item>
         <item name="colorForegroundInverse">@color/foreground_device_default_dark</item>
 
@@ -5487,26 +5487,26 @@
         <item name="colorPrimaryDark">@color/primary_dark_device_default_settings</item>
         <item name="colorSecondary">@color/secondary_device_default_settings</item>
         <item name="colorAccent">@color/accent_device_default_light</item>
-        <item name="colorAccentPrimary">@color/accent_primary_device_default</item>
-        <item name="colorAccentSecondary">@color/accent_secondary_device_default</item>
-        <item name="colorAccentTertiary">@color/accent_tertiary_device_default</item>
-        <item name="colorAccentPrimaryVariant">@color/accent_primary_variant_light_device_default</item>
-        <item name="colorAccentSecondaryVariant">@color/accent_secondary_variant_light_device_default</item>
-        <item name="colorAccentTertiaryVariant">@color/accent_tertiary_variant_light_device_default</item>
-        <item name="colorSurface">@color/surface_light</item>
-        <item name="colorSurfaceHighlight">@color/surface_highlight_light</item>
-        <item name="colorSurfaceVariant">@color/surface_variant_light</item>
-        <item name="colorSurfaceHeader">@color/surface_header_light</item>
+        <item name="colorAccentPrimary">@color/system_primary_dark</item>
+        <item name="colorAccentSecondary">@color/system_secondary_dark</item>
+        <item name="colorAccentTertiary">@color/system_tertiary_dark</item>
+        <item name="colorAccentPrimaryVariant">@color/system_primary_container_light</item>
+        <item name="colorAccentSecondaryVariant">@color/system_secondary_container_light</item>
+        <item name="colorAccentTertiaryVariant">@color/system_tertiary_container_light</item>
+        <item name="colorSurface">@color/system_surface_container_light</item>
+        <item name="colorSurfaceHighlight">@color/system_surface_bright_light</item>
+        <item name="colorSurfaceVariant">@color/system_surface_container_high_light</item>
+        <item name="colorSurfaceHeader">@color/system_surface_container_highest_light</item>
         <item name="colorError">@color/error_color_device_default_light</item>
-        <item name="colorBackground">@color/background_device_default_light</item>
+        <item name="colorBackground">@color/system_surface_container_light</item>
         <item name="colorBackgroundFloating">@color/background_floating_device_default_light</item>
-        <item name="textColorPrimary">@color/text_color_primary_device_default_light</item>
-        <item name="textColorSecondary">@color/text_color_secondary_device_default_light</item>
-        <item name="textColorTertiary">@color/text_color_tertiary_device_default_light</item>
-        <item name="textColorPrimaryInverse">@color/text_color_primary_device_default_dark</item>
-        <item name="textColorSecondaryInverse">@color/text_color_secondary_device_default_dark</item>
-        <item name="textColorTertiaryInverse">@color/text_color_tertiary_device_default_dark</item>
-        <item name="textColorOnAccent">@color/text_color_on_accent_device_default</item>
+        <item name="textColorPrimary">@color/system_on_surface_light</item>
+        <item name="textColorSecondary">@color/system_on_surface_variant_light</item>
+        <item name="textColorTertiary">@color/system_outline_light</item>
+        <item name="textColorPrimaryInverse">@color/system_on_surface_dark</item>
+        <item name="textColorSecondaryInverse">@color/system_on_surface_variant_dark</item>
+        <item name="textColorTertiaryInverse">@color/system_outline_dark</item>
+        <item name="textColorOnAccent">@color/system_on_primary_dark</item>
         <item name="colorForeground">@color/foreground_device_default_light</item>
         <item name="colorForegroundInverse">@color/foreground_device_default_dark</item>
 
@@ -5606,26 +5606,26 @@
         <item name="colorPrimaryDark">@color/primary_dark_device_default_settings</item>
         <item name="colorSecondary">@color/secondary_device_default_settings</item>
         <item name="colorAccent">@color/accent_device_default_light</item>
-        <item name="colorAccentPrimary">@color/accent_primary_device_default</item>
-        <item name="colorAccentSecondary">@color/accent_secondary_device_default</item>
-        <item name="colorAccentTertiary">@color/accent_tertiary_device_default</item>
-        <item name="colorAccentPrimaryVariant">@color/accent_primary_variant_light_device_default</item>
-        <item name="colorAccentSecondaryVariant">@color/accent_secondary_variant_light_device_default</item>
-        <item name="colorAccentTertiaryVariant">@color/accent_tertiary_variant_light_device_default</item>
-        <item name="colorSurface">@color/surface_light</item>
-        <item name="colorSurfaceHighlight">@color/surface_highlight_light</item>
-        <item name="colorSurfaceVariant">@color/surface_variant_light</item>
-        <item name="colorSurfaceHeader">@color/surface_header_light</item>
+        <item name="colorAccentPrimary">@color/system_primary_dark</item>
+        <item name="colorAccentSecondary">@color/system_secondary_dark</item>
+        <item name="colorAccentTertiary">@color/system_tertiary_dark</item>
+        <item name="colorAccentPrimaryVariant">@color/system_primary_container_light</item>
+        <item name="colorAccentSecondaryVariant">@color/system_secondary_container_light</item>
+        <item name="colorAccentTertiaryVariant">@color/system_tertiary_container_light</item>
+        <item name="colorSurface">@color/system_surface_container_light</item>
+        <item name="colorSurfaceHighlight">@color/system_surface_bright_light</item>
+        <item name="colorSurfaceVariant">@color/system_surface_container_high_light</item>
+        <item name="colorSurfaceHeader">@color/system_surface_container_highest_light</item>
         <item name="colorError">@color/error_color_device_default_light</item>
-        <item name="colorBackground">@color/background_device_default_light</item>
+        <item name="colorBackground">@color/system_surface_container_light</item>
         <item name="colorBackgroundFloating">@color/background_floating_device_default_light</item>
-        <item name="textColorPrimary">@color/text_color_primary_device_default_light</item>
-        <item name="textColorSecondary">@color/text_color_secondary_device_default_light</item>
-        <item name="textColorTertiary">@color/text_color_tertiary_device_default_light</item>
-        <item name="textColorPrimaryInverse">@color/text_color_primary_device_default_dark</item>
-        <item name="textColorSecondaryInverse">@color/text_color_secondary_device_default_dark</item>
-        <item name="textColorTertiaryInverse">@color/text_color_tertiary_device_default_dark</item>
-        <item name="textColorOnAccent">@color/text_color_on_accent_device_default</item>
+        <item name="textColorPrimary">@color/system_on_surface_light</item>
+        <item name="textColorSecondary">@color/system_on_surface_variant_light</item>
+        <item name="textColorTertiary">@color/system_outline_light</item>
+        <item name="textColorPrimaryInverse">@color/system_on_surface_dark</item>
+        <item name="textColorSecondaryInverse">@color/system_on_surface_variant_dark</item>
+        <item name="textColorTertiaryInverse">@color/system_outline_dark</item>
+        <item name="textColorOnAccent">@color/system_on_primary_dark</item>
         <item name="colorForeground">@color/foreground_device_default_light</item>
         <item name="colorForegroundInverse">@color/foreground_device_default_dark</item>
 
@@ -5788,9 +5788,9 @@
 
     <style name="ThemeOverlay.DeviceDefault.Accent">
         <item name="colorAccent">@color/accent_device_default_dark</item>
-        <item name="colorAccentPrimary">@color/accent_primary_device_default</item>
-        <item name="colorAccentSecondary">@color/accent_secondary_device_default</item>
-        <item name="colorAccentTertiary">@color/accent_tertiary_device_default</item>
+        <item name="colorAccentPrimary">@color/system_primary_dark</item>
+        <item name="colorAccentSecondary">@color/system_secondary_dark</item>
+        <item name="colorAccentTertiary">@color/system_tertiary_dark</item>
 
         <item name="materialColorOnSecondaryFixedVariant">@color/system_on_secondary_fixed_variant</item>
         <item name="materialColorOnTertiaryFixedVariant">@color/system_on_tertiary_fixed_variant</item>
@@ -5863,9 +5863,9 @@
 
     <style name="ThemeOverlay.DeviceDefault.Accent.Light">
         <item name="colorAccent">@color/accent_device_default_light</item>
-        <item name="colorAccentPrimary">@color/accent_primary_device_default</item>
-        <item name="colorAccentSecondary">@color/accent_secondary_device_default</item>
-        <item name="colorAccentTertiary">@color/accent_tertiary_device_default</item>
+        <item name="colorAccentPrimary">@color/system_primary_dark</item>
+        <item name="colorAccentSecondary">@color/system_secondary_dark</item>
+        <item name="colorAccentTertiary">@color/system_tertiary_dark</item>
 
         <item name="materialColorOnSecondaryFixedVariant">@color/system_on_secondary_fixed_variant</item>
         <item name="materialColorOnTertiaryFixedVariant">@color/system_on_tertiary_fixed_variant</item>
@@ -5938,13 +5938,13 @@
 
     <!-- Theme overlay that replaces colorAccent with the colorAccent from {@link #Theme_DeviceDefault_DayNight}. -->
     <style name="ThemeOverlay.DeviceDefault.Accent.DayNight"
-           parent="@style/ThemeOverlay.DeviceDefault.Accent.Light" />
+        parent="@style/ThemeOverlay.DeviceDefault.Accent.Light" />
 
     <style name="ThemeOverlay.DeviceDefault.Dark.ActionBar.Accent" parent="ThemeOverlay.Material.Dark.ActionBar">
         <item name="colorAccent">@color/accent_device_default_dark</item>
-        <item name="colorAccentPrimary">@color/accent_primary_device_default</item>
-        <item name="colorAccentSecondary">@color/accent_secondary_device_default</item>
-        <item name="colorAccentTertiary">@color/accent_tertiary_device_default</item>
+        <item name="colorAccentPrimary">@color/system_primary_dark</item>
+        <item name="colorAccentSecondary">@color/system_secondary_dark</item>
+        <item name="colorAccentTertiary">@color/system_tertiary_dark</item>
 
         <item name="materialColorOnSecondaryFixedVariant">@color/system_on_secondary_fixed_variant</item>
         <item name="materialColorOnTertiaryFixedVariant">@color/system_on_tertiary_fixed_variant</item>
@@ -6016,7 +6016,7 @@
     </style>
 
     <style name="Theme.DeviceDefault.Light.Dialog.Alert.UserSwitchingDialog" parent="Theme.DeviceDefault.NoActionBar.Fullscreen">
-        <item name="colorBackground">@color/background_device_default_light</item>
+        <item name="colorBackground">@color/system_surface_container_light</item>
         <item name="colorBackgroundFloating">@color/background_device_default_light</item>
         <item name="layout_gravity">center</item>
         <item name="windowAnimationStyle">@style/Animation.DeviceDefault.Dialog</item>
diff --git a/core/tests/coretests/src/android/database/sqlite/SQLiteRawStatementTest.java b/core/tests/coretests/src/android/database/sqlite/SQLiteRawStatementTest.java
index 832ebe5..6dad3b7 100644
--- a/core/tests/coretests/src/android/database/sqlite/SQLiteRawStatementTest.java
+++ b/core/tests/coretests/src/android/database/sqlite/SQLiteRawStatementTest.java
@@ -999,4 +999,31 @@
             mDatabase.endTransaction();
         }
     }
+
+    /**
+     * This test verifies that the JNI exception thrown because of a bad column is actually thrown
+     * and does not crash the VM.
+     */
+    @Test
+    public void testJniExceptions() {
+        // Create the t1 table.
+        mDatabase.beginTransaction();
+        try {
+            mDatabase.execSQL("CREATE TABLE t1 (i int, j int);");
+            mDatabase.setTransactionSuccessful();
+        } finally {
+            mDatabase.endTransaction();
+        }
+
+        mDatabase.beginTransactionReadOnly();
+        try (SQLiteRawStatement s = mDatabase.createRawStatement("SELECT * from t1")) {
+            s.step();
+            s.getColumnText(5); // out-of-range column
+            fail("JNI exception not thrown");
+        } catch (SQLiteBindOrColumnIndexOutOfRangeException e) {
+            // Passing case.
+        } finally {
+            mDatabase.endTransaction();
+        }
+    }
 }
diff --git a/core/tests/coretests/src/android/graphics/PaintFontVariationTest.java b/core/tests/coretests/src/android/graphics/PaintFontVariationTest.java
new file mode 100644
index 0000000..8a54e5b
--- /dev/null
+++ b/core/tests/coretests/src/android/graphics/PaintFontVariationTest.java
@@ -0,0 +1,74 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.graphics;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.platform.test.annotations.RequiresFlagsEnabled;
+import android.platform.test.flag.junit.CheckFlagsRule;
+import android.platform.test.flag.junit.DeviceFlagsValueProvider;
+import android.test.InstrumentationTestCase;
+
+import androidx.test.filters.SmallTest;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.text.flags.Flags;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/**
+ * PaintTest tests {@link Paint}.
+ */
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+public class PaintFontVariationTest extends InstrumentationTestCase {
+    @Rule
+    public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule();
+
+    @RequiresFlagsEnabled(Flags.FLAG_TYPEFACE_CACHE_FOR_VAR_SETTINGS)
+    @Test
+    public void testDerivedFromSameTypeface() {
+        final Paint p = new Paint();
+
+        p.setTypeface(Typeface.SANS_SERIF);
+        assertThat(p.setFontVariationSettings("'wght' 450")).isTrue();
+        Typeface first = p.getTypeface();
+
+        p.setTypeface(Typeface.SANS_SERIF);
+        assertThat(p.setFontVariationSettings("'wght' 480")).isTrue();
+        Typeface second = p.getTypeface();
+
+        assertThat(first.getDerivedFrom()).isSameInstanceAs(second.getDerivedFrom());
+    }
+
+    @RequiresFlagsEnabled(Flags.FLAG_TYPEFACE_CACHE_FOR_VAR_SETTINGS)
+    @Test
+    public void testDerivedFromChained() {
+        final Paint p = new Paint();
+
+        p.setTypeface(Typeface.SANS_SERIF);
+        assertThat(p.setFontVariationSettings("'wght' 450")).isTrue();
+        Typeface first = p.getTypeface();
+
+        assertThat(p.setFontVariationSettings("'wght' 480")).isTrue();
+        Typeface second = p.getTypeface();
+
+        assertThat(first.getDerivedFrom()).isSameInstanceAs(second.getDerivedFrom());
+    }
+}
diff --git a/core/tests/coretests/src/android/graphics/PaintTest.java b/core/tests/coretests/src/android/graphics/PaintTest.java
index 0dec756..878ba70 100644
--- a/core/tests/coretests/src/android/graphics/PaintTest.java
+++ b/core/tests/coretests/src/android/graphics/PaintTest.java
@@ -16,13 +16,22 @@
 
 package android.graphics;
 
+import static com.google.common.truth.Truth.assertThat;
+
 import static org.junit.Assert.assertNotEquals;
 
+import android.platform.test.annotations.RequiresFlagsEnabled;
+import android.platform.test.flag.junit.CheckFlagsRule;
+import android.platform.test.flag.junit.DeviceFlagsValueProvider;
 import android.test.InstrumentationTestCase;
 import android.text.TextUtils;
 
 import androidx.test.filters.SmallTest;
 
+import com.android.text.flags.Flags;
+
+import org.junit.Rule;
+
 import java.util.Arrays;
 import java.util.HashSet;
 
@@ -30,6 +39,9 @@
  * PaintTest tests {@link Paint}.
  */
 public class PaintTest extends InstrumentationTestCase {
+    @Rule
+    public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule();
+
     private static final String FONT_PATH = "fonts/HintedAdvanceWidthTest-Regular.ttf";
 
     static void assertEquals(String message, float[] expected, float[] actual) {
@@ -403,4 +415,33 @@
         assertEquals(6, getClusterCount(p, rtlStr + ltrStr));
         assertEquals(9, getClusterCount(p, ltrStr + rtlStr + ltrStr));
     }
+
+    @RequiresFlagsEnabled(Flags.FLAG_TYPEFACE_CACHE_FOR_VAR_SETTINGS)
+    public void testDerivedFromSameTypeface() {
+        final Paint p = new Paint();
+
+        p.setTypeface(Typeface.SANS_SERIF);
+        assertThat(p.setFontVariationSettings("'wght' 450")).isTrue();
+        Typeface first = p.getTypeface();
+
+        p.setTypeface(Typeface.SANS_SERIF);
+        assertThat(p.setFontVariationSettings("'wght' 480")).isTrue();
+        Typeface second = p.getTypeface();
+
+        assertThat(first.getDerivedFrom()).isSameInstanceAs(second.getDerivedFrom());
+    }
+
+    @RequiresFlagsEnabled(Flags.FLAG_TYPEFACE_CACHE_FOR_VAR_SETTINGS)
+    public void testDerivedFromChained() {
+        final Paint p = new Paint();
+
+        p.setTypeface(Typeface.SANS_SERIF);
+        assertThat(p.setFontVariationSettings("'wght' 450")).isTrue();
+        Typeface first = p.getTypeface();
+
+        assertThat(p.setFontVariationSettings("'wght' 480")).isTrue();
+        Typeface second = p.getTypeface();
+
+        assertThat(first.getDerivedFrom()).isSameInstanceAs(second.getDerivedFrom());
+    }
 }
diff --git a/core/tests/coretests/src/android/os/VibrationAttributesTest.java b/core/tests/coretests/src/android/os/VibrationAttributesTest.java
index d8142c8..5bdae0e 100644
--- a/core/tests/coretests/src/android/os/VibrationAttributesTest.java
+++ b/core/tests/coretests/src/android/os/VibrationAttributesTest.java
@@ -28,11 +28,9 @@
     @Test
     public void testSimple() throws Exception {
         final VibrationAttributes attr = new VibrationAttributes.Builder()
-                .setCategory(VibrationAttributes.CATEGORY_KEYBOARD)
                 .setUsage(VibrationAttributes.USAGE_ALARM)
                 .build();
 
-        assertEquals(VibrationAttributes.CATEGORY_KEYBOARD, attr.getCategory());
         assertEquals(VibrationAttributes.USAGE_ALARM, attr.getUsage());
     }
 }
diff --git a/core/tests/coretests/src/android/view/ViewFrameRateTest.java b/core/tests/coretests/src/android/view/ViewFrameRateTest.java
index b8ff595..c631c6f 100644
--- a/core/tests/coretests/src/android/view/ViewFrameRateTest.java
+++ b/core/tests/coretests/src/android/view/ViewFrameRateTest.java
@@ -994,6 +994,35 @@
                 mViewRoot.getLastPreferredFrameRateCategory());
     }
 
+    /**
+     * If a View is an instance of ViewGroupOverlay,
+     * we obtain the velocity from its hostView.
+     */
+    @Test
+    @RequiresFlagsEnabled(FLAG_VIEW_VELOCITY_API)
+    public void overlayViewGroupVelocity() throws Throwable {
+        if (!ViewProperties.vrr_enabled().orElse(true)) {
+            return;
+        }
+
+        FrameLayout host = new FrameLayout(mActivity);
+        View childView = new View(mActivity);
+        float velocity = 1000;
+
+        mActivityRule.runOnUiThread(() -> {
+            ViewGroup.LayoutParams fullSize = new ViewGroup.LayoutParams(
+                    ViewGroup.LayoutParams.MATCH_PARENT,
+                    ViewGroup.LayoutParams.MATCH_PARENT);
+            mActivity.setContentView(host, fullSize);
+            host.setFrameContentVelocity(velocity);
+            ViewGroupOverlay overlay = host.getOverlay();
+            overlay.add(childView);
+            assertEquals(velocity, host.getFrameContentVelocity());
+            assertEquals(host.getFrameContentVelocity(),
+                    ((View) childView.getParent()).getFrameContentVelocity());
+        });
+    }
+
     private void runAfterDraw(@NonNull Runnable runnable) {
         Handler handler = new Handler(Looper.getMainLooper());
         mAfterDrawLatch = new CountDownLatch(1);
diff --git a/core/tests/coretests/src/android/view/contentprotection/OWNERS b/core/tests/coretests/src/android/view/contentprotection/OWNERS
index b3583a7..3d09da3 100644
--- a/core/tests/coretests/src/android/view/contentprotection/OWNERS
+++ b/core/tests/coretests/src/android/view/contentprotection/OWNERS
@@ -1,4 +1,4 @@
-# Bug component: 544200
+# Bug component: 1040349
 
-include /core/java/android/view/contentcapture/OWNERS
+include /core/java/android/view/contentprotection/OWNERS
 
diff --git a/core/tests/coretests/src/android/widget/TextViewContextMenuTest.java b/core/tests/coretests/src/android/widget/TextViewContextMenuTest.java
index f9da832..b11307e 100644
--- a/core/tests/coretests/src/android/widget/TextViewContextMenuTest.java
+++ b/core/tests/coretests/src/android/widget/TextViewContextMenuTest.java
@@ -23,7 +23,9 @@
 import static org.mockito.ArgumentMatchers.anyChar;
 import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.spy;
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.verifyNoMoreInteractions;
@@ -251,8 +253,9 @@
         when(menu.add(anyInt(), eq(TextView.ID_AUTOFILL), anyInt(), anyInt()))
                 .thenReturn(mockAutofillMenuItem);
 
-        EditText et = mActivity.findViewById(R.id.editText);
-        et.setText("Test");
+        EditText et = spy(mActivity.findViewById(R.id.editText));
+        doReturn(true).when(et).canRequestAutofill();
+        doReturn(null).when(et).getSelectedText();
 
         Editor editor = et.getEditorForTesting();
         editor.onCreateContextMenu(menu);
@@ -271,11 +274,11 @@
         when(menu.add(anyInt(), eq(TextView.ID_AUTOFILL), anyInt(), anyInt()))
                 .thenReturn(mockAutofillMenuItem);
 
-        EditText et = mActivity.findViewById(R.id.editText);
-        et.setText("Test");
-        et.selectAll();
-        Editor editor = et.getEditorForTesting();
-        editor.onCreateContextMenu(menu);
+        EditText et = spy(mActivity.findViewById(R.id.editText));
+        doReturn(true).when(et).canRequestAutofill();
+        doReturn("test").when(et).getSelectedText();
+        Editor editor = new Editor(et);
+        editor.setTextContextMenuItems(menu);
 
         verify(menu).add(anyInt(), eq(TextView.ID_AUTOFILL), anyInt(), anyInt());
         verify(mockAutofillMenuItem).setEnabled(false);
diff --git a/core/tests/coretests/src/com/android/internal/util/RateLimitingCacheTest.java b/core/tests/coretests/src/com/android/internal/util/RateLimitingCacheTest.java
new file mode 100644
index 0000000..7541a84
--- /dev/null
+++ b/core/tests/coretests/src/com/android/internal/util/RateLimitingCacheTest.java
@@ -0,0 +1,158 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.util;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import android.os.SystemClock;
+
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+/**
+ * Test the RateLimitingCache class.
+ */
+@RunWith(AndroidJUnit4.class)
+public class RateLimitingCacheTest {
+
+    private int mCounter = 0;
+
+    @Before
+    public void before() {
+        mCounter = -1;
+    }
+
+    RateLimitingCache.ValueFetcher<Integer> mFetcher = () -> {
+        return ++mCounter;
+    };
+
+    /**
+     * Test zero period passed into RateLimitingCache. A new value should be returned for each
+     * time the cache's get() is invoked.
+     */
+    @Test
+    public void testTtl_Zero() {
+        RateLimitingCache<Integer> s = new RateLimitingCache<>(0);
+
+        int first = s.get(mFetcher);
+        assertEquals(first, 0);
+        int second = s.get(mFetcher);
+        assertEquals(second, 1);
+        SystemClock.sleep(20);
+        int third = s.get(mFetcher);
+        assertEquals(third, 2);
+    }
+
+    /**
+     * Test a period of 100ms passed into RateLimitingCache. A new value should not be fetched
+     * any more frequently than every 100ms.
+     */
+    @Test
+    public void testTtl_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);
+        SystemClock.sleep(150);
+        int third = s.get(mFetcher);
+        // Changed by now
+        assertEquals(third, 1);
+        int fourth = s.get(mFetcher);
+        // Too early to change again
+        assertEquals(fourth, 1);
+    }
+
+    /**
+     * Test a negative period passed into RateLimitingCache. A new value should only be fetched the
+     * first call to get().
+     */
+    @Test
+    public void testTtl_Negative() {
+        RateLimitingCache<Integer> s = new RateLimitingCache<>(-1);
+
+        int first = s.get(mFetcher);
+        assertEquals(first, 0);
+        SystemClock.sleep(200);
+        // Should return the original value every time
+        int second = s.get(mFetcher);
+        assertEquals(second, 0);
+    }
+
+    /**
+     * Test making tons of calls to the speed-limiter and make sure number of fetches does not
+     * exceed expected number of fetches.
+     */
+    @Test
+    public void testTtl_Spam() {
+        RateLimitingCache<Integer> s = new RateLimitingCache<>(100);
+        assertCount(s, 1000, 7, 15);
+    }
+
+    /**
+     * Test rate-limiting across multiple periods and make sure the expected number of fetches is
+     * within the specified rate.
+     */
+    @Test
+    public void testRate_10hz() {
+        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);
+    }
+
+    /**
+     * Helper to make repeated calls every 5 millis to verify the number of expected fetches for
+     * the given parameters.
+     * @param cache the cache object
+     * @param period the period for which to make get() calls
+     * @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(RateLimitingCache<Integer> cache, long period,
+            int minCount, int maxCount) {
+        long startTime = SystemClock.elapsedRealtime();
+        while (SystemClock.elapsedRealtime() < startTime + period) {
+            int value = cache.get(mFetcher);
+            SystemClock.sleep(5);
+        }
+        int latest = cache.get(mFetcher);
+        assertTrue("Latest should be between " + minCount + " and " + maxCount
+                        + " but is " + latest, latest <= maxCount && latest >= minCount);
+    }
+}
diff --git a/core/tests/resourceflaggingtests/src/com/android/resourceflaggingtests/ResourceFlaggingTest.java b/core/tests/resourceflaggingtests/src/com/android/resourceflaggingtests/ResourceFlaggingTest.java
index c1e3578..471b402 100644
--- a/core/tests/resourceflaggingtests/src/com/android/resourceflaggingtests/ResourceFlaggingTest.java
+++ b/core/tests/resourceflaggingtests/src/com/android/resourceflaggingtests/ResourceFlaggingTest.java
@@ -68,6 +68,16 @@
         assertThat(getBoolean("res3")).isTrue();
     }
 
+    @Test
+    public void testFlagDisabledStringArrayElement() {
+        assertThat(getStringArray("strarr1")).isEqualTo(new String[]{"one", "two", "three"});
+    }
+
+    @Test
+    public void testFlagDisabledIntArrayElement() {
+        assertThat(getIntArray("intarr1")).isEqualTo(new int[]{1, 2, 3});
+    }
+
     private boolean getBoolean(String name) {
         int resId = mResources.getIdentifier(
                 name,
@@ -77,13 +87,22 @@
         return mResources.getBoolean(resId);
     }
 
-    private String getString(String name) {
+    private String[] getStringArray(String name) {
         int resId = mResources.getIdentifier(
                 name,
-                "string",
+                "array",
                 "com.android.intenal.flaggedresources");
         assertThat(resId).isNotEqualTo(0);
-        return mResources.getString(resId);
+        return mResources.getStringArray(resId);
+    }
+
+    private int[] getIntArray(String name) {
+        int resId = mResources.getIdentifier(
+                name,
+                "array",
+                "com.android.intenal.flaggedresources");
+        assertThat(resId).isNotEqualTo(0);
+        return mResources.getIntArray(resId);
     }
 
     private String extractApkAndGetPath(int id) throws Exception {
diff --git a/core/tests/vibrator/src/android/os/VibrationEffectTest.java b/core/tests/vibrator/src/android/os/VibrationEffectTest.java
index bd3d944..4f76dd6 100644
--- a/core/tests/vibrator/src/android/os/VibrationEffectTest.java
+++ b/core/tests/vibrator/src/android/os/VibrationEffectTest.java
@@ -60,7 +60,7 @@
 
 @RunWith(MockitoJUnitRunner.class)
 public class VibrationEffectTest {
-
+    private static final float TOLERANCE = 1e-2f;
     private static final String RINGTONE_URI_1 = "content://test/system/ringtone_1";
     private static final String RINGTONE_URI_2 = "content://test/system/ringtone_2";
     private static final String RINGTONE_URI_3 = "content://test/system/ringtone_3";
@@ -709,7 +709,7 @@
     @Test
     public void testScaleWaveform() {
         VibrationEffect scaledUp = TEST_WAVEFORM.scale(1.5f);
-        assertEquals(1f, getStepSegment(scaledUp, 0).getAmplitude(), 1e-5f);
+        assertEquals(1f, getStepSegment(scaledUp, 0).getAmplitude(), TOLERANCE);
 
         VibrationEffect scaledDown = TEST_WAVEFORM.scale(0.5f);
         assertTrue(1f > getStepSegment(scaledDown, 0).getAmplitude());
@@ -731,11 +731,11 @@
     public void testScaleVendorEffect() {
         VibrationEffect effect = VibrationEffect.createVendorEffect(createNonEmptyBundle());
 
-        VibrationEffect scaledUp = effect.scale(1.5f);
-        assertEquals(effect, scaledUp);
+        VibrationEffect.VendorEffect scaledUp = (VibrationEffect.VendorEffect) effect.scale(1.5f);
+        assertEquals(1.5f, scaledUp.getScale());
 
-        VibrationEffect scaledDown = effect.scale(0.5f);
-        assertEquals(effect, scaledDown);
+        VibrationEffect.VendorEffect scaledDown = (VibrationEffect.VendorEffect) effect.scale(0.5f);
+        assertEquals(0.5f, scaledDown.getScale());
     }
 
     @Test
@@ -755,6 +755,70 @@
     }
 
     @Test
+    public void testApplyAdaptiveScaleOneShot() {
+        VibrationEffect oneShot = VibrationEffect.createOneShot(TEST_TIMING, /* amplitude= */ 100);
+
+        VibrationEffect scaledUp = oneShot.applyAdaptiveScale(1.5f);
+        assertThat(getStepSegment(scaledUp, 0).getAmplitude()).isWithin(TOLERANCE).of(150 / 255f);
+
+        VibrationEffect scaledDown = oneShot.applyAdaptiveScale(0.5f);
+        assertThat(getStepSegment(scaledDown, 0).getAmplitude()).isWithin(TOLERANCE).of(50 / 255f);
+    }
+
+    @Test
+    public void testApplyAdaptiveScaleWaveform() {
+        VibrationEffect waveform = VibrationEffect.createWaveform(
+                new long[] { 100, 100 }, new int[] { 10, 0 }, -1);
+
+        VibrationEffect scaledUp = waveform.applyAdaptiveScale(1.5f);
+        assertThat(getStepSegment(scaledUp, 0).getAmplitude()).isWithin(TOLERANCE).of(15 / 255f);
+
+        VibrationEffect scaledDown = waveform.applyAdaptiveScale(0.5f);
+        assertThat(getStepSegment(scaledDown, 0).getAmplitude()).isWithin(TOLERANCE).of(5 / 255f);
+    }
+
+    @Test
+    public void testApplyAdaptiveScalePrebaked() {
+        VibrationEffect effect = VibrationEffect.get(VibrationEffect.EFFECT_CLICK);
+
+        VibrationEffect scaledUp = effect.applyAdaptiveScale(1.5f);
+        assertEquals(effect, scaledUp);
+
+        VibrationEffect scaledDown = effect.applyAdaptiveScale(0.5f);
+        assertEquals(effect, scaledDown);
+    }
+
+    @Test
+    @RequiresFlagsEnabled(android.os.vibrator.Flags.FLAG_VENDOR_VIBRATION_EFFECTS)
+    public void testApplyAdaptiveScaleVendorEffect() {
+        VibrationEffect effect = VibrationEffect.createVendorEffect(createNonEmptyBundle());
+
+        VibrationEffect.VendorEffect scaledUp =
+                (VibrationEffect.VendorEffect) effect.applyAdaptiveScale(1.5f);
+        assertEquals(1.5f, scaledUp.getAdaptiveScale());
+
+        VibrationEffect.VendorEffect scaledDown =
+                (VibrationEffect.VendorEffect) effect.applyAdaptiveScale(0.5f);
+        assertEquals(0.5f, scaledDown.getAdaptiveScale());
+    }
+
+    @Test
+    public void testApplyAdaptiveScaleComposed() {
+        VibrationEffect effect = VibrationEffect.startComposition()
+                .addPrimitive(VibrationEffect.Composition.PRIMITIVE_CLICK, 0.5f, 1)
+                .addEffect(VibrationEffect.createOneShot(TEST_TIMING, /* amplitude= */ 100))
+                .compose();
+
+        VibrationEffect scaledUp = effect.applyAdaptiveScale(1.5f);
+        assertThat(getPrimitiveSegment(scaledUp, 0).getScale()).isWithin(TOLERANCE).of(0.75f);
+        assertThat(getStepSegment(scaledUp, 1).getAmplitude()).isWithin(TOLERANCE).of(150 / 255f);
+
+        VibrationEffect scaledDown = effect.applyAdaptiveScale(0.5f);
+        assertThat(getPrimitiveSegment(scaledDown, 0).getScale()).isWithin(TOLERANCE).of(0.25f);
+        assertThat(getStepSegment(scaledDown, 1).getAmplitude()).isWithin(TOLERANCE).of(50 / 255f);
+    }
+
+    @Test
     public void testApplyEffectStrengthToOneShotWaveformAndPrimitives() {
         VibrationEffect oneShot = VibrationEffect.createOneShot(100, 100);
         VibrationEffect waveform = VibrationEffect.createWaveform(new long[] { 10, 20 }, 0);
diff --git a/data/etc/privapp-permissions-platform.xml b/data/etc/privapp-permissions-platform.xml
index 1fe6ca7..9a55b80 100644
--- a/data/etc/privapp-permissions-platform.xml
+++ b/data/etc/privapp-permissions-platform.xml
@@ -592,6 +592,10 @@
         <permission name="android.permission.INTERACT_ACROSS_USERS" />
     </privapp-permissions>
 
+    <privapp-permissions package="com.android.providers.tv">
+        <permission name="android.permission.INTERACT_ACROSS_USERS"/>
+    </privapp-permissions>
+
     <privapp-permissions package="com.android.tv">
         <permission name="android.permission.CHANGE_HDMI_CEC_ACTIVE_SOURCE"/>
         <permission name="android.permission.DVB_DEVICE"/>
diff --git a/graphics/java/android/graphics/Typeface.java b/graphics/java/android/graphics/Typeface.java
index fd78816..889a7785 100644
--- a/graphics/java/android/graphics/Typeface.java
+++ b/graphics/java/android/graphics/Typeface.java
@@ -56,6 +56,7 @@
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.util.Preconditions;
+import com.android.text.flags.Flags;
 
 import dalvik.annotation.optimization.CriticalNative;
 import dalvik.annotation.optimization.FastNative;
@@ -74,6 +75,7 @@
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collections;
+import java.util.Comparator;
 import java.util.List;
 import java.util.Map;
 import java.util.Objects;
@@ -143,6 +145,23 @@
     private static final LruCache<String, Typeface> sDynamicTypefaceCache = new LruCache<>(16);
     private static final Object sDynamicCacheLock = new Object();
 
+    private static final LruCache<Long, LruCache<String, Typeface>> sVariableCache =
+            new LruCache<>(16);
+    private static final Object sVariableCacheLock = new Object();
+
+    /** @hide */
+    @VisibleForTesting
+    public static void clearTypefaceCachesForTestingPurpose() {
+        synchronized (sWeightCacheLock) {
+            sWeightTypefaceCache.clear();
+        }
+        synchronized (sDynamicCacheLock) {
+            sDynamicTypefaceCache.evictAll();
+        }
+        synchronized (sVariableCacheLock) {
+            sVariableCache.evictAll();
+        }
+    }
 
     @GuardedBy("SYSTEM_FONT_MAP_LOCK")
     static Typeface sDefaultTypeface;
@@ -195,6 +214,8 @@
     @UnsupportedAppUsage
     public final long native_instance;
 
+    private final Typeface mDerivedFrom;
+
     private final String mSystemFontFamilyName;
 
     private final Runnable mCleaner;
@@ -274,6 +295,18 @@
     }
 
     /**
+     * Returns the Typeface used for creating this Typeface.
+     *
+     * Maybe null if this is not derived from other Typeface.
+     * TODO(b/357707916): Make this public API.
+     * @hide
+     */
+    @VisibleForTesting
+    public final @Nullable Typeface getDerivedFrom() {
+        return mDerivedFrom;
+    }
+
+    /**
      * Returns the system font family name if the typeface was created from a system font family,
      * otherwise returns null.
      */
@@ -1021,9 +1054,51 @@
         return typeface;
     }
 
-    /** @hide */
+    private static String axesToVarKey(@NonNull List<FontVariationAxis> axes) {
+        // The given list can be mutated because it is allocated in Paint#setFontVariationSettings.
+        // Currently, Paint#setFontVariationSettings is the only code path reaches this method.
+        axes.sort(Comparator.comparingInt(FontVariationAxis::getOpenTypeTagValue));
+        StringBuilder sb = new StringBuilder();
+        for (int i = 0; i < axes.size(); ++i) {
+            final FontVariationAxis fva = axes.get(i);
+            sb.append(fva.getTag());
+            sb.append(fva.getStyleValue());
+        }
+        return sb.toString();
+    }
+
+    /**
+     * TODO(b/357707916): Make this public API.
+     * @hide
+     */
     public static Typeface createFromTypefaceWithVariation(@Nullable Typeface family,
             @NonNull List<FontVariationAxis> axes) {
+        if (Flags.typefaceCacheForVarSettings()) {
+            final Typeface target = (family == null) ? Typeface.DEFAULT : family;
+            final Typeface base = (target.mDerivedFrom == null) ? target : target.mDerivedFrom;
+
+            final String key = axesToVarKey(axes);
+
+            synchronized (sVariableCacheLock) {
+                LruCache<String, Typeface> innerCache = sVariableCache.get(base.native_instance);
+                if (innerCache == null) {
+                    // Cache up to 16 var instance per root Typeface
+                    innerCache = new LruCache<>(16);
+                    sVariableCache.put(base.native_instance, innerCache);
+                } else {
+                    Typeface cached = innerCache.get(key);
+                    if (cached != null) {
+                        return cached;
+                    }
+                }
+                Typeface typeface = new Typeface(
+                        nativeCreateFromTypefaceWithVariation(base.native_instance, axes),
+                        base.getSystemFontFamilyName(), base);
+                innerCache.put(key, typeface);
+                return typeface;
+            }
+        }
+
         final Typeface base = family == null ? Typeface.DEFAULT : family;
         Typeface typeface = new Typeface(
                 nativeCreateFromTypefaceWithVariation(base.native_instance, axes),
@@ -1184,11 +1259,19 @@
     // don't allow clients to call this directly
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
     private Typeface(long ni) {
-        this(ni, null);
+        this(ni, null, null);
+    }
+
+
+    // don't allow clients to call this directly
+    // This is kept for robolectric.
+    private Typeface(long ni, @Nullable String systemFontFamilyName) {
+        this(ni, systemFontFamilyName, null);
     }
 
     // don't allow clients to call this directly
-    private Typeface(long ni, @Nullable String systemFontFamilyName) {
+    private Typeface(long ni, @Nullable String systemFontFamilyName,
+            @Nullable Typeface derivedFrom) {
         if (ni == 0) {
             throw new RuntimeException("native typeface cannot be made");
         }
@@ -1198,6 +1281,7 @@
         mStyle = nativeGetStyle(ni);
         mWeight = nativeGetWeight(ni);
         mSystemFontFamilyName = systemFontFamilyName;
+        mDerivedFrom = derivedFrom;
     }
 
     /**
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/BackupHelper.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/BackupHelper.java
new file mode 100644
index 0000000..69a68c8
--- /dev/null
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/BackupHelper.java
@@ -0,0 +1,101 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.window.extensions.embedding;
+
+import android.os.Build;
+import android.os.Bundle;
+import android.os.Looper;
+import android.os.MessageQueue;
+import android.util.Log;
+
+import androidx.annotation.NonNull;
+
+import java.util.List;
+
+/**
+ * Helper class to back up and restore the TaskFragmentOrganizer state, in order to resume
+ * organizing the TaskFragments if the app process is restarted.
+ */
+@SuppressWarnings("GuardedBy")
+class BackupHelper {
+    private static final String TAG = "BackupHelper";
+    private static final boolean DEBUG = Build.isDebuggable();
+
+    private static final String KEY_TASK_CONTAINERS = "KEY_TASK_CONTAINERS";
+    @NonNull
+    private final SplitController mController;
+    @NonNull
+    private final BackupIdler mBackupIdler = new BackupIdler();
+    private boolean mBackupIdlerScheduled;
+
+    BackupHelper(@NonNull SplitController splitController, @NonNull Bundle savedState) {
+        mController = splitController;
+
+        if (!savedState.isEmpty()) {
+            restoreState(savedState);
+        }
+    }
+
+    /**
+     * Schedules a back-up request. It is no-op if there was a request scheduled and not yet
+     * completed.
+     */
+    void scheduleBackup() {
+        if (!mBackupIdlerScheduled) {
+            mBackupIdlerScheduled = true;
+            Looper.myQueue().addIdleHandler(mBackupIdler);
+        }
+    }
+
+    final class BackupIdler implements MessageQueue.IdleHandler {
+        @Override
+        public boolean queueIdle() {
+            synchronized (mController.mLock) {
+                mBackupIdlerScheduled = false;
+                startBackup();
+            }
+            return false;
+        }
+    }
+
+    private void startBackup() {
+        final List<TaskContainer> taskContainers = mController.getTaskContainers();
+        if (taskContainers.isEmpty()) {
+            Log.w(TAG, "No task-container to back up");
+            return;
+        }
+
+        if (DEBUG) Log.d(TAG, "Start to back up " + taskContainers);
+        final Bundle state = new Bundle();
+        state.setClassLoader(TaskContainer.class.getClassLoader());
+        state.putParcelableList(KEY_TASK_CONTAINERS, taskContainers);
+        mController.setSavedState(state);
+    }
+
+    private void restoreState(@NonNull Bundle savedState) {
+        if (savedState.isEmpty()) {
+            return;
+        }
+
+        final List<TaskContainer> taskContainers = savedState.getParcelableArrayList(
+                KEY_TASK_CONTAINERS, TaskContainer.class);
+        for (TaskContainer taskContainer : taskContainers) {
+            if (DEBUG) Log.d(TAG, "restore task " + taskContainer.getTaskId());
+            // TODO(b/289875940): implement the TaskContainer restoration.
+        }
+    }
+}
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java
index 26d180c..bb384c5 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java
@@ -2536,6 +2536,21 @@
         return mTaskContainers.get(taskId);
     }
 
+    @NonNull
+    @GuardedBy("mLock")
+    List<TaskContainer> getTaskContainers() {
+        final ArrayList<TaskContainer> taskContainers = new ArrayList<>();
+        for (int i = mTaskContainers.size() - 1; i >= 0; i--) {
+            taskContainers.add(mTaskContainers.valueAt(i));
+        }
+        return taskContainers;
+    }
+
+    @GuardedBy("mLock")
+    void setSavedState(@NonNull Bundle savedState) {
+        mPresenter.setSavedState(savedState);
+    }
+
     @GuardedBy("mLock")
     void addTaskContainer(int taskId, TaskContainer taskContainer) {
         mTaskContainers.put(taskId, taskContainer);
@@ -2829,6 +2844,12 @@
         return getActiveSplitForContainer(container) != null;
     }
 
+    void scheduleBackup() {
+        synchronized (mLock) {
+            mPresenter.scheduleBackup();
+        }
+    }
+
     private final class LifecycleCallbacks extends EmptyLifecycleCallbacksAdapter {
 
         @Override
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java
index 99716e7..fb8efc4 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java
@@ -158,6 +158,8 @@
 
     private final WindowLayoutComponentImpl mWindowLayoutComponent;
     private final SplitController mController;
+    @NonNull
+    private final BackupHelper mBackupHelper;
 
     SplitPresenter(@NonNull Executor executor,
             @NonNull WindowLayoutComponentImpl windowLayoutComponent,
@@ -165,7 +167,18 @@
         super(executor, controller);
         mWindowLayoutComponent = windowLayoutComponent;
         mController = controller;
-        registerOrganizer();
+        final Bundle outSavedState = new Bundle();
+        if (Flags.aeBackStackRestore()) {
+            outSavedState.setClassLoader(TaskContainer.class.getClassLoader());
+            registerOrganizer(false /* isSystemOrganizer */, outSavedState);
+        } else {
+            registerOrganizer();
+        }
+        mBackupHelper = new BackupHelper(controller, outSavedState);
+    }
+
+    void scheduleBackup() {
+        mBackupHelper.scheduleBackup();
     }
 
     /**
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskContainer.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskContainer.java
index 20ad53e..5795e8d 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskContainer.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskContainer.java
@@ -32,6 +32,8 @@
 import android.content.res.Configuration;
 import android.graphics.Rect;
 import android.os.IBinder;
+import android.os.Parcel;
+import android.os.Parcelable;
 import android.util.ArraySet;
 import android.util.DisplayMetrics;
 import android.util.Log;
@@ -48,12 +50,14 @@
 import androidx.window.extensions.embedding.SplitAttributes.SplitType.ExpandContainersSplitType;
 import androidx.window.extensions.embedding.SplitAttributes.SplitType.RatioSplitType;
 
+import com.android.window.flags.Flags;
+
 import java.util.ArrayList;
 import java.util.List;
 import java.util.Set;
 
 /** Represents TaskFragments and split pairs below a Task. */
-class TaskContainer {
+class TaskContainer implements Parcelable {
     private static final String TAG = TaskContainer.class.getSimpleName();
 
     /** The unique task id. */
@@ -80,6 +84,9 @@
     @NonNull
     private TaskFragmentParentInfo mInfo;
 
+    @NonNull
+    private SplitController mSplitController;
+
     /**
      * TaskFragments that the organizer has requested to be closed. They should be removed when
      * the organizer receives
@@ -116,12 +123,14 @@
     /**
      * The {@link TaskContainer} constructor
      *
-     * @param taskId         The ID of the Task, which must match {@link Activity#getTaskId()} with
-     *                       {@code activityInTask}.
-     * @param activityInTask The {@link Activity} in the Task with {@code taskId}. It is used to
-     *                       initialize the {@link TaskContainer} properties.
+     * @param taskId          The ID of the Task, which must match {@link Activity#getTaskId()} with
+     *                        {@code activityInTask}.
+     * @param activityInTask  The {@link Activity} in the Task with {@code taskId}. It is used to
+     *                        initialize the {@link TaskContainer} properties.
+     * @param splitController The {@link SplitController}.
      */
-    TaskContainer(int taskId, @NonNull Activity activityInTask) {
+    TaskContainer(int taskId, @NonNull Activity activityInTask,
+            @Nullable SplitController splitController) {
         if (taskId == INVALID_TASK_ID) {
             throw new IllegalArgumentException("Invalid Task id");
         }
@@ -136,6 +145,7 @@
                 true /* visible */,
                 true /* hasDirectActivity */,
                 null /* decorSurface */);
+        mSplitController = splitController;
     }
 
     int getTaskId() {
@@ -571,6 +581,12 @@
         // Update overlay container after split pin container since the overlay should be on top of
         // pin container.
         updateAlwaysOnTopOverlayIfNecessary();
+
+        // TODO(b/289875940): Making backup-restore as an opt-in solution, before the flag goes
+        //  to next-food.
+        if (Flags.aeBackStackRestore()) {
+            mSplitController.scheduleBackup();
+        }
     }
 
     private void updateAlwaysOnTopOverlayIfNecessary() {
@@ -664,6 +680,34 @@
         return activityStacks;
     }
 
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(Parcel dest, int flags) {
+        dest.writeInt(mTaskId);
+        // TODO(b/289875940)
+    }
+
+    protected TaskContainer(Parcel in) {
+        mTaskId = in.readInt();
+        // TODO(b/289875940)
+    }
+
+    public static final Creator<TaskContainer> CREATOR = new Creator<>() {
+        @Override
+        public TaskContainer createFromParcel(Parcel in) {
+            return new TaskContainer(in);
+        }
+
+        @Override
+        public TaskContainer[] newArray(int size) {
+            return new TaskContainer[size];
+        }
+    };
+
     /** A wrapper class which contains the information of {@link TaskContainer} */
     static final class TaskProperties {
         private final int mDisplayId;
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java
index ee3e6f3..dc6506b 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java
@@ -1203,7 +1203,7 @@
 
             if (taskContainer == null) {
                 // Adding a TaskContainer if no existed one.
-                taskContainer = new TaskContainer(mTaskId, mActivityInTask);
+                taskContainer = new TaskContainer(mTaskId, mActivityInTask, mSplitController);
                 mSplitController.addTaskContainer(mTaskId, taskContainer);
             }
 
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/sidecar/SampleSidecarImpl.java b/libs/WindowManager/Jetpack/src/androidx/window/sidecar/SampleSidecarImpl.java
deleted file mode 100644
index 60bc7be..0000000
--- a/libs/WindowManager/Jetpack/src/androidx/window/sidecar/SampleSidecarImpl.java
+++ /dev/null
@@ -1,109 +0,0 @@
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package androidx.window.sidecar;
-
-import android.annotation.Nullable;
-import android.app.Activity;
-import android.app.Application;
-import android.content.Context;
-import android.hardware.devicestate.DeviceStateManager;
-import android.os.Bundle;
-import android.os.IBinder;
-
-import androidx.annotation.NonNull;
-import androidx.window.common.BaseDataProducer;
-import androidx.window.common.DeviceStateManagerFoldingFeatureProducer;
-import androidx.window.common.EmptyLifecycleCallbacksAdapter;
-import androidx.window.common.RawFoldingFeatureProducer;
-import androidx.window.common.layout.CommonFoldingFeature;
-
-import java.util.ArrayList;
-import java.util.List;
-
-/**
- * Reference implementation of androidx.window.sidecar OEM interface for use with
- * WindowManager Jetpack.
- */
-class SampleSidecarImpl extends StubSidecar {
-    private List<CommonFoldingFeature> mStoredFeatures = new ArrayList<>();
-
-    SampleSidecarImpl(Context context) {
-        ((Application) context.getApplicationContext())
-                .registerActivityLifecycleCallbacks(new NotifyOnConfigurationChanged());
-        RawFoldingFeatureProducer settingsFeatureProducer = new RawFoldingFeatureProducer(context);
-        BaseDataProducer<List<CommonFoldingFeature>> foldingFeatureProducer =
-                new DeviceStateManagerFoldingFeatureProducer(context,
-                        settingsFeatureProducer,
-                        context.getSystemService(DeviceStateManager.class));
-
-        foldingFeatureProducer.addDataChangedCallback(this::onDisplayFeaturesChanged);
-    }
-
-    private void setStoredFeatures(List<CommonFoldingFeature> storedFeatures) {
-        mStoredFeatures = storedFeatures;
-    }
-
-    private void onDisplayFeaturesChanged(List<CommonFoldingFeature> storedFeatures) {
-        setStoredFeatures(storedFeatures);
-        updateDeviceState(getDeviceState());
-        for (IBinder windowToken : getWindowsListeningForLayoutChanges()) {
-            SidecarWindowLayoutInfo newLayout = getWindowLayoutInfo(windowToken);
-            updateWindowLayout(windowToken, newLayout);
-        }
-    }
-
-    @NonNull
-    @Override
-    public SidecarDeviceState getDeviceState() {
-        return SidecarHelper.calculateDeviceState(mStoredFeatures);
-    }
-
-    @NonNull
-    @Override
-    public SidecarWindowLayoutInfo getWindowLayoutInfo(@NonNull IBinder windowToken) {
-        return SidecarHelper.calculateWindowLayoutInfo(windowToken, mStoredFeatures);
-    }
-
-    @Override
-    protected void onListenersChanged() {
-        if (hasListeners()) {
-            onDisplayFeaturesChanged(mStoredFeatures);
-        }
-    }
-
-    private final class NotifyOnConfigurationChanged extends EmptyLifecycleCallbacksAdapter {
-        @Override
-        public void onActivityCreated(@NonNull Activity activity,
-                @Nullable Bundle savedInstanceState) {
-            super.onActivityCreated(activity, savedInstanceState);
-            onDisplayFeaturesChangedForActivity(activity);
-        }
-
-        @Override
-        public void onActivityConfigurationChanged(@NonNull Activity activity) {
-            super.onActivityConfigurationChanged(activity);
-            onDisplayFeaturesChangedForActivity(activity);
-        }
-
-        private void onDisplayFeaturesChangedForActivity(@NonNull Activity activity) {
-            IBinder token = activity.getWindow().getAttributes().token;
-            if (token == null || mWindowLayoutChangeListenerTokens.contains(token)) {
-                onDisplayFeaturesChanged(mStoredFeatures);
-            }
-        }
-    }
-}
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/sidecar/SidecarImpl.java b/libs/WindowManager/Jetpack/src/androidx/window/sidecar/SidecarImpl.java
new file mode 100644
index 0000000..a1de206
--- /dev/null
+++ b/libs/WindowManager/Jetpack/src/androidx/window/sidecar/SidecarImpl.java
@@ -0,0 +1,174 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.window.sidecar;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.app.Activity;
+import android.app.Application;
+import android.content.Context;
+import android.hardware.devicestate.DeviceStateManager;
+import android.os.Bundle;
+import android.os.IBinder;
+import android.util.ArraySet;
+import android.util.Log;
+
+import androidx.window.common.BaseDataProducer;
+import androidx.window.common.DeviceStateManagerFoldingFeatureProducer;
+import androidx.window.common.EmptyLifecycleCallbacksAdapter;
+import androidx.window.common.RawFoldingFeatureProducer;
+import androidx.window.common.layout.CommonFoldingFeature;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Objects;
+import java.util.Set;
+
+/**
+ * Basic implementation of the {@link SidecarInterface}. An OEM can choose to use it as the base
+ * class for their implementation.
+ */
+class SidecarImpl implements SidecarInterface {
+
+    private static final String TAG = "WindowManagerSidecar";
+
+    @Nullable
+    private SidecarCallback mSidecarCallback;
+    private final ArraySet<IBinder> mWindowLayoutChangeListenerTokens = new ArraySet<>();
+    private boolean mDeviceStateChangeListenerRegistered;
+    @NonNull
+    private List<CommonFoldingFeature> mStoredFeatures = new ArrayList<>();
+
+    SidecarImpl(Context context) {
+        ((Application) context.getApplicationContext())
+                .registerActivityLifecycleCallbacks(new SidecarImpl.NotifyOnConfigurationChanged());
+        RawFoldingFeatureProducer settingsFeatureProducer = new RawFoldingFeatureProducer(context);
+        BaseDataProducer<List<CommonFoldingFeature>> foldingFeatureProducer =
+                new DeviceStateManagerFoldingFeatureProducer(context,
+                        settingsFeatureProducer,
+                        context.getSystemService(DeviceStateManager.class));
+
+        foldingFeatureProducer.addDataChangedCallback(this::onDisplayFeaturesChanged);
+    }
+
+    @NonNull
+    @Override
+    public SidecarDeviceState getDeviceState() {
+        return SidecarHelper.calculateDeviceState(mStoredFeatures);
+    }
+
+    @NonNull
+    @Override
+    public SidecarWindowLayoutInfo getWindowLayoutInfo(@NonNull IBinder windowToken) {
+        return SidecarHelper.calculateWindowLayoutInfo(windowToken, mStoredFeatures);
+    }
+
+    @Override
+    public void setSidecarCallback(@NonNull SidecarCallback sidecarCallback) {
+        mSidecarCallback = sidecarCallback;
+    }
+
+    @Override
+    public void onWindowLayoutChangeListenerAdded(@NonNull IBinder iBinder) {
+        mWindowLayoutChangeListenerTokens.add(iBinder);
+        onListenersChanged();
+    }
+
+    @Override
+    public void onWindowLayoutChangeListenerRemoved(@NonNull IBinder iBinder) {
+        mWindowLayoutChangeListenerTokens.remove(iBinder);
+        onListenersChanged();
+    }
+
+    @Override
+    public void onDeviceStateListenersChanged(boolean isEmpty) {
+        mDeviceStateChangeListenerRegistered = !isEmpty;
+        onListenersChanged();
+    }
+
+    private void setStoredFeatures(@NonNull List<CommonFoldingFeature> storedFeatures) {
+        mStoredFeatures = Objects.requireNonNull(storedFeatures);
+    }
+
+    private void onDisplayFeaturesChanged(@NonNull List<CommonFoldingFeature> storedFeatures) {
+        setStoredFeatures(storedFeatures);
+        updateDeviceState(getDeviceState());
+        for (IBinder windowToken : getWindowsListeningForLayoutChanges()) {
+            SidecarWindowLayoutInfo newLayout = getWindowLayoutInfo(windowToken);
+            updateWindowLayout(windowToken, newLayout);
+        }
+    }
+
+    void updateDeviceState(@NonNull SidecarDeviceState newState) {
+        if (mSidecarCallback != null) {
+            try {
+                mSidecarCallback.onDeviceStateChanged(newState);
+            } catch (AbstractMethodError e) {
+                Log.e(TAG, "App is using an outdated Window Jetpack library", e);
+            }
+        }
+    }
+
+    void updateWindowLayout(@NonNull IBinder windowToken,
+            @NonNull SidecarWindowLayoutInfo newLayout) {
+        if (mSidecarCallback != null) {
+            try {
+                mSidecarCallback.onWindowLayoutChanged(windowToken, newLayout);
+            } catch (AbstractMethodError e) {
+                Log.e(TAG, "App is using an outdated Window Jetpack library", e);
+            }
+        }
+    }
+
+    @NonNull
+    private Set<IBinder> getWindowsListeningForLayoutChanges() {
+        return mWindowLayoutChangeListenerTokens;
+    }
+
+    protected boolean hasListeners() {
+        return !mWindowLayoutChangeListenerTokens.isEmpty() || mDeviceStateChangeListenerRegistered;
+    }
+
+    private void onListenersChanged() {
+        if (hasListeners()) {
+            onDisplayFeaturesChanged(mStoredFeatures);
+        }
+    }
+
+
+    private final class NotifyOnConfigurationChanged extends EmptyLifecycleCallbacksAdapter {
+        @Override
+        public void onActivityCreated(@NonNull Activity activity,
+                @Nullable Bundle savedInstanceState) {
+            super.onActivityCreated(activity, savedInstanceState);
+            onDisplayFeaturesChangedForActivity(activity);
+        }
+
+        @Override
+        public void onActivityConfigurationChanged(@NonNull Activity activity) {
+            super.onActivityConfigurationChanged(activity);
+            onDisplayFeaturesChangedForActivity(activity);
+        }
+
+        private void onDisplayFeaturesChangedForActivity(@NonNull Activity activity) {
+            IBinder token = activity.getWindow().getAttributes().token;
+            if (token == null || mWindowLayoutChangeListenerTokens.contains(token)) {
+                onDisplayFeaturesChanged(mStoredFeatures);
+            }
+        }
+    }
+}
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/sidecar/SidecarProvider.java b/libs/WindowManager/Jetpack/src/androidx/window/sidecar/SidecarProvider.java
index 686a31b..1e306fc 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/sidecar/SidecarProvider.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/sidecar/SidecarProvider.java
@@ -36,7 +36,7 @@
     @Nullable
     public static SidecarInterface getSidecarImpl(Context context) {
         return isWindowExtensionsEnabled()
-                ? new SampleSidecarImpl(context.getApplicationContext())
+                ? new SidecarImpl(context.getApplicationContext())
                 : null;
     }
 
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/sidecar/StubSidecar.java b/libs/WindowManager/Jetpack/src/androidx/window/sidecar/StubSidecar.java
deleted file mode 100644
index 46c1f3b..0000000
--- a/libs/WindowManager/Jetpack/src/androidx/window/sidecar/StubSidecar.java
+++ /dev/null
@@ -1,96 +0,0 @@
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package androidx.window.sidecar;
-
-import android.os.IBinder;
-import android.util.Log;
-
-import androidx.annotation.NonNull;
-
-import java.util.HashSet;
-import java.util.Set;
-
-/**
- * Basic implementation of the {@link SidecarInterface}. An OEM can choose to use it as the base
- * class for their implementation.
- */
-abstract class StubSidecar implements SidecarInterface {
-
-    private static final String TAG = "WindowManagerSidecar";
-
-    private SidecarCallback mSidecarCallback;
-    final Set<IBinder> mWindowLayoutChangeListenerTokens = new HashSet<>();
-    private boolean mDeviceStateChangeListenerRegistered;
-
-    StubSidecar() {
-    }
-
-    @Override
-    public void setSidecarCallback(@NonNull SidecarCallback sidecarCallback) {
-        this.mSidecarCallback = sidecarCallback;
-    }
-
-    @Override
-    public void onWindowLayoutChangeListenerAdded(@NonNull IBinder iBinder) {
-        this.mWindowLayoutChangeListenerTokens.add(iBinder);
-        this.onListenersChanged();
-    }
-
-    @Override
-    public void onWindowLayoutChangeListenerRemoved(@NonNull IBinder iBinder) {
-        this.mWindowLayoutChangeListenerTokens.remove(iBinder);
-        this.onListenersChanged();
-    }
-
-    @Override
-    public void onDeviceStateListenersChanged(boolean isEmpty) {
-        this.mDeviceStateChangeListenerRegistered = !isEmpty;
-        this.onListenersChanged();
-    }
-
-    void updateDeviceState(SidecarDeviceState newState) {
-        if (this.mSidecarCallback != null) {
-            try {
-                mSidecarCallback.onDeviceStateChanged(newState);
-            } catch (AbstractMethodError e) {
-                Log.e(TAG, "App is using an outdated Window Jetpack library", e);
-            }
-        }
-    }
-
-    void updateWindowLayout(@NonNull IBinder windowToken,
-            @NonNull SidecarWindowLayoutInfo newLayout) {
-        if (this.mSidecarCallback != null) {
-            try {
-                mSidecarCallback.onWindowLayoutChanged(windowToken, newLayout);
-            } catch (AbstractMethodError e) {
-                Log.e(TAG, "App is using an outdated Window Jetpack library", e);
-            }
-        }
-    }
-
-    @NonNull
-    Set<IBinder> getWindowsListeningForLayoutChanges() {
-        return mWindowLayoutChangeListenerTokens;
-    }
-
-    protected boolean hasListeners() {
-        return !mWindowLayoutChangeListenerTokens.isEmpty() || mDeviceStateChangeListenerRegistered;
-    }
-
-    protected abstract void onListenersChanged();
-}
diff --git a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/EmbeddingTestUtils.java b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/EmbeddingTestUtils.java
index 7dc78fd..5c85778 100644
--- a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/EmbeddingTestUtils.java
+++ b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/EmbeddingTestUtils.java
@@ -222,7 +222,7 @@
         doReturn(resources).when(activity).getResources();
         doReturn(DEFAULT_DISPLAY).when(activity).getDisplayId();
 
-        return new TaskContainer(TASK_ID, activity);
+        return new TaskContainer(TASK_ID, activity, mock(SplitController.class));
     }
 
     static TaskContainer createTestTaskContainer(@NonNull SplitController controller) {
diff --git a/libs/WindowManager/Shell/Android.bp b/libs/WindowManager/Shell/Android.bp
index 1c3d9c3..1a3aa8e 100644
--- a/libs/WindowManager/Shell/Android.bp
+++ b/libs/WindowManager/Shell/Android.bp
@@ -44,18 +44,10 @@
 filegroup {
     name: "wm_shell_util-sources",
     srcs: [
-        "src/com/android/wm/shell/animation/Interpolators.java",
         "src/com/android/wm/shell/common/bubbles/*.kt",
         "src/com/android/wm/shell/common/bubbles/*.java",
-        "src/com/android/wm/shell/common/magnetictarget/MagnetizedObject.kt",
-        "src/com/android/wm/shell/common/split/SplitScreenConstants.java",
-        "src/com/android/wm/shell/common/TransactionPool.java",
-        "src/com/android/wm/shell/common/TriangleShape.java",
         "src/com/android/wm/shell/common/desktopmode/*.kt",
-        "src/com/android/wm/shell/draganddrop/DragAndDropConstants.java",
         "src/com/android/wm/shell/pip/PipContentOverlay.java",
-        "src/com/android/wm/shell/startingsurface/SplashScreenExitAnimationUtils.java",
-        "src/com/android/wm/shell/sysui/ShellSharedConstants.java",
         "src/com/android/wm/shell/util/**/*.java",
     ],
     path: "src",
diff --git a/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/BubblePositionerTest.kt b/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/BubblePositionerTest.kt
index ae60d8b..4b97451 100644
--- a/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/BubblePositionerTest.kt
+++ b/libs/WindowManager/Shell/multivalentTests/src/com/android/wm/shell/bubbles/BubblePositionerTest.kt
@@ -268,7 +268,8 @@
             )
         positioner.update(deviceConfig)
         val intent = Intent(Intent.ACTION_VIEW).setPackage(context.packageName)
-        val bubble = Bubble.createAppBubble(intent, UserHandle(1), null, directExecutor())
+        val bubble =
+            Bubble.createAppBubble(intent, UserHandle(1), null, directExecutor(), directExecutor())
 
         assertThat(positioner.getExpandedViewHeight(bubble)).isEqualTo(MAX_HEIGHT)
     }
@@ -294,6 +295,7 @@
                 0 /* taskId */,
                 null /* locus */,
                 true /* isDismissable */,
+                directExecutor(),
                 directExecutor()
             ) {}
 
@@ -322,6 +324,7 @@
                 0 /* taskId */,
                 null /* locus */,
                 true /* isDismissable */,
+                directExecutor(),
                 directExecutor()
             ) {}
 
@@ -416,7 +419,8 @@
         positioner.update(deviceConfig)
 
         val intent = Intent(Intent.ACTION_VIEW).setPackage(context.packageName)
-        val bubble = Bubble.createAppBubble(intent, UserHandle(1), null, directExecutor())
+        val bubble =
+            Bubble.createAppBubble(intent, UserHandle(1), null, directExecutor(), directExecutor())
 
         // This bubble will have max height so it'll always be top aligned
         assertThat(positioner.getExpandedViewY(bubble, 0f /* bubblePosition */))
@@ -433,7 +437,8 @@
         positioner.update(deviceConfig)
 
         val intent = Intent(Intent.ACTION_VIEW).setPackage(context.packageName)
-        val bubble = Bubble.createAppBubble(intent, UserHandle(1), null, directExecutor())
+        val bubble =
+            Bubble.createAppBubble(intent, UserHandle(1), null, directExecutor(), directExecutor())
 
         // Always top aligned in phone portrait
         assertThat(positioner.getExpandedViewY(bubble, 0f /* bubblePosition */))
@@ -452,7 +457,8 @@
         positioner.update(deviceConfig)
 
         val intent = Intent(Intent.ACTION_VIEW).setPackage(context.packageName)
-        val bubble = Bubble.createAppBubble(intent, UserHandle(1), null, directExecutor())
+        val bubble =
+            Bubble.createAppBubble(intent, UserHandle(1), null, directExecutor(), directExecutor())
 
         // This bubble will have max height which is always top aligned on small tablets
         assertThat(positioner.getExpandedViewY(bubble, 0f /* bubblePosition */))
@@ -470,7 +476,8 @@
         positioner.update(deviceConfig)
 
         val intent = Intent(Intent.ACTION_VIEW).setPackage(context.packageName)
-        val bubble = Bubble.createAppBubble(intent, UserHandle(1), null, directExecutor())
+        val bubble =
+            Bubble.createAppBubble(intent, UserHandle(1), null, directExecutor(), directExecutor())
 
         // This bubble will have max height which is always top aligned on small tablets
         assertThat(positioner.getExpandedViewY(bubble, 0f /* bubblePosition */))
@@ -489,7 +496,8 @@
         positioner.update(deviceConfig)
 
         val intent = Intent(Intent.ACTION_VIEW).setPackage(context.packageName)
-        val bubble = Bubble.createAppBubble(intent, UserHandle(1), null, directExecutor())
+        val bubble =
+            Bubble.createAppBubble(intent, UserHandle(1), null, directExecutor(), directExecutor())
 
         // This bubble will have max height which is always top aligned on landscape, large tablet
         assertThat(positioner.getExpandedViewY(bubble, 0f /* bubblePosition */))
@@ -507,7 +515,8 @@
         positioner.update(deviceConfig)
 
         val intent = Intent(Intent.ACTION_VIEW).setPackage(context.packageName)
-        val bubble = Bubble.createAppBubble(intent, UserHandle(1), null, directExecutor())
+        val bubble =
+            Bubble.createAppBubble(intent, UserHandle(1), null, directExecutor(), directExecutor())
 
         val manageButtonHeight =
             context.resources.getDimensionPixelSize(R.dimen.bubble_manage_button_height)
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 84f7bb2..faadf1d 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
@@ -102,6 +102,7 @@
                 BubbleLogger(UiEventLoggerFake()),
                 positioner,
                 BubbleEducationController(context),
+                shellExecutor,
                 shellExecutor
             )
         bubbleStackViewManager = FakeBubbleStackViewManager()
@@ -364,6 +365,7 @@
                 /* taskId= */ 0,
                 "locus",
                 /* isDismissable= */ true,
+                directExecutor(),
                 directExecutor()
             ) {}
         inflateBubble(bubble)
@@ -373,7 +375,8 @@
     private fun createAndInflateBubble(): Bubble {
         val intent = Intent(Intent.ACTION_VIEW).setPackage(context.packageName)
         val icon = Icon.createWithResource(context.resources, R.drawable.bubble_ic_overflow_button)
-        val bubble = Bubble.createAppBubble(intent, UserHandle(1), icon, directExecutor())
+        val bubble =
+            Bubble.createAppBubble(intent, UserHandle(1), icon, directExecutor(), directExecutor())
         inflateBubble(bubble)
         return bubble
     }
diff --git a/libs/WindowManager/Shell/res/drawable/decor_maximize_button_dark.xml b/libs/WindowManager/Shell/res/drawable/decor_maximize_button_dark.xml
index ab4e29a..7b33534 100644
--- a/libs/WindowManager/Shell/res/drawable/decor_maximize_button_dark.xml
+++ b/libs/WindowManager/Shell/res/drawable/decor_maximize_button_dark.xml
@@ -17,19 +17,16 @@
 <vector xmlns:android="http://schemas.android.com/apk/res/android"
         android:width="32.0dp"
         android:height="32.0dp"
-        android:viewportWidth="32.0"
-        android:viewportHeight="32.0"
-        android:tint="@color/decor_button_dark_color">
+        android:viewportWidth="24.0"
+        android:viewportHeight="24.0">
     <group android:scaleX="0.5"
             android:scaleY="0.5"
-            android:translateX="8.0"
-            android:translateY="8.0" >
+            android:translateX="6.0"
+            android:translateY="6.0" >
         <path
-            android:fillColor="@android:color/white"
-            android:pathData="M2.0,4.0l0.0,16.0l28.0,0.0L30.0,4.0L2.0,4.0zM26.0,16.0L6.0,16.0L6.0,8.0l20.0,0.0L26.0,16.0z"/>
-        <path
-            android:fillColor="@android:color/white"
-            android:pathData="M2.0,24.0l28.0,0.0l0.0,4.0l-28.0,0.0z"/>
+            android:fillColor="@android:color/black"
+            android:fillType="evenOdd"
+            android:pathData="M23.0,1.0v22.0H1V1h22zm-3,19H4V4h16v16z"/>
     </group>
 </vector>
 
diff --git a/libs/WindowManager/Shell/res/drawable/decor_restore_button_dark.xml b/libs/WindowManager/Shell/res/drawable/decor_restore_button_dark.xml
new file mode 100644
index 0000000..91c8f54
--- /dev/null
+++ b/libs/WindowManager/Shell/res/drawable/decor_restore_button_dark.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2024 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+        android:width="32.0dp"
+        android:height="32.0dp"
+        android:viewportWidth="24.0"
+        android:viewportHeight="24.0">
+    <group android:scaleX="0.5"
+            android:scaleY="0.5"
+            android:translateX="6.0"
+            android:translateY="6.0" >
+        <path
+            android:fillColor="@android:color/black"
+            android:fillType="evenOdd"
+            android:pathData="M23,16H8V1h15v15zm-12,-3V4h9v9h-9zM4,8H1v15h15v-3H4V8z"/>
+    </group>
+</vector>
diff --git a/libs/WindowManager/Shell/res/layout/compat_ui_restart_button_layout.xml b/libs/WindowManager/Shell/res/layout/compat_ui_restart_button_layout.xml
new file mode 100644
index 0000000..d00c69c
--- /dev/null
+++ b/libs/WindowManager/Shell/res/layout/compat_ui_restart_button_layout.xml
@@ -0,0 +1,41 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2024 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+<LinearLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="wrap_content"
+    android:layout_height="wrap_content"
+    android:orientation="vertical"
+    android:gravity="bottom|end">
+
+    <include android:id="@+id/size_compat_hint"
+        android:visibility="gone"
+        android:layout_width="@dimen/compat_hint_width"
+        android:layout_height="wrap_content"
+        layout="@layout/compat_mode_hint"/>
+
+    <ImageButton
+        android:id="@+id/size_compat_restart_button"
+        android:visibility="gone"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_marginEnd="@dimen/compat_button_margin"
+        android:layout_marginBottom="@dimen/compat_button_margin"
+        android:src="@drawable/size_compat_restart_button_ripple"
+        android:background="@android:color/transparent"
+        android:contentDescription="@string/restart_button_description"/>
+
+</LinearLayout>
diff --git a/libs/WindowManager/Shell/res/values/dimen.xml b/libs/WindowManager/Shell/res/values/dimen.xml
index 8627c0b..08a746f 100644
--- a/libs/WindowManager/Shell/res/values/dimen.xml
+++ b/libs/WindowManager/Shell/res/values/dimen.xml
@@ -92,8 +92,11 @@
     <dimen name="docked_divider_handle_width">16dp</dimen>
     <dimen name="docked_divider_handle_height">2dp</dimen>
 
-    <dimen name="split_divider_handle_region_width">96dp</dimen>
+    <dimen name="split_divider_handle_region_width">80dp</dimen>
     <dimen name="split_divider_handle_region_height">48dp</dimen>
+    <!-- The divider touch zone height is intentionally halved in portrait to avoid colliding
+         with the app handle.-->
+    <dimen name="desktop_mode_portrait_split_divider_handle_region_height">24dp</dimen>
 
     <!-- Divider handle size for split screen -->
     <dimen name="split_divider_handle_width">40dp</dimen>
@@ -539,20 +542,23 @@
     <!-- The size of the icon shown in the resize veil. -->
     <dimen name="desktop_mode_resize_veil_icon_size">96dp</dimen>
 
-    <!-- The with of the border around the app task for edge resizing, when
+    <!-- The width of the border outside the app task eligible for edge resizing, when
          enable_windowing_edge_drag_resize is enabled. -->
-    <dimen name="desktop_mode_edge_handle">12dp</dimen>
+    <dimen name="freeform_edge_handle_outset">10dp</dimen>
+
+    <!-- The size of the border inside the app task eligible for edge resizing, when
+         enable_windowing_edge_drag_resize is enabled. -->
+    <dimen name="freeform_edge_handle_inset">2dp</dimen>
 
     <!-- The original width of the border around the app task for edge resizing, when
          enable_windowing_edge_drag_resize is disabled. -->
     <dimen name="freeform_resize_handle">15dp</dimen>
 
     <!-- The size of the corner region for drag resizing with touch, when a larger touch region is
-         appropriate. Applied when enable_windowing_edge_drag_resize is enabled. -->
+         appropriate. -->
     <dimen name="desktop_mode_corner_resize_large">48dp</dimen>
 
-    <!-- The original size of the corner region for darg resizing, when
-         enable_windowing_edge_drag_resize is disabled. -->
+    <!-- The size of the corner region for drag resizing with a cursor or a stylus. -->
     <dimen name="freeform_resize_corner">44dp</dimen>
 
     <!-- The thickness in dp for all desktop drag transition regions. -->
diff --git a/libs/WindowManager/Shell/res/values/strings.xml b/libs/WindowManager/Shell/res/values/strings.xml
index 6a62d7a3..36d0a3c 100644
--- a/libs/WindowManager/Shell/res/values/strings.xml
+++ b/libs/WindowManager/Shell/res/values/strings.xml
@@ -302,4 +302,6 @@
     <string name="desktop_mode_maximize_menu_maximize_text">Maximize Screen</string>
     <!-- Maximize menu snap buttons string. -->
     <string name="desktop_mode_maximize_menu_snap_text">Snap Screen</string>
+    <!-- Snap resizing non-resizable string. -->
+    <string name="desktop_mode_non_resizable_snap_text">This app can\'t be resized</string>
 </resources>
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/sysui/ShellSharedConstants.java b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/ShellSharedConstants.java
similarity index 95%
rename from libs/WindowManager/Shell/src/com/android/wm/shell/sysui/ShellSharedConstants.java
rename to libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/ShellSharedConstants.java
index c886cc9..8f7a2e5 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/sysui/ShellSharedConstants.java
+++ b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/ShellSharedConstants.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2022 The Android Open Source Project
+ * Copyright (C) 2024 The Android Open Source Project
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.wm.shell.sysui;
+package com.android.wm.shell.shared;
 
 /**
  * General shell-related constants that are shared with users of the library.
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/TransactionPool.java b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/TransactionPool.java
similarity index 93%
rename from libs/WindowManager/Shell/src/com/android/wm/shell/common/TransactionPool.java
rename to libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/TransactionPool.java
index 4c34566..0c5d88d 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/TransactionPool.java
+++ b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/TransactionPool.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2020 The Android Open Source Project
+ * Copyright (C) 2024 The Android Open Source Project
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.wm.shell.common;
+package com.android.wm.shell.shared;
 
 import android.util.Pools;
 import android.view.SurfaceControl;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/TriangleShape.java b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/TriangleShape.java
similarity index 96%
rename from libs/WindowManager/Shell/src/com/android/wm/shell/common/TriangleShape.java
rename to libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/TriangleShape.java
index 7079190..0ca5327 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/TriangleShape.java
+++ b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/TriangleShape.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2020 The Android Open Source Project
+ * Copyright (C) 2024 The Android Open Source Project
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.wm.shell.common;
+package com.android.wm.shell.shared;
 
 import android.graphics.Outline;
 import android.graphics.Path;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/animation/Interpolators.java b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/animation/Interpolators.java
similarity index 97%
rename from libs/WindowManager/Shell/src/com/android/wm/shell/animation/Interpolators.java
rename to libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/animation/Interpolators.java
index ce0bf8b..f45dc3a 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/animation/Interpolators.java
+++ b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/animation/Interpolators.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2020 The Android Open Source Project
+ * Copyright (C) 2024 The Android Open Source Project
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.wm.shell.animation;
+package com.android.wm.shell.shared.animation;
 
 import android.graphics.Path;
 import android.view.animation.BackGestureInterpolator;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropConstants.java b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/draganddrop/DragAndDropConstants.java
similarity index 89%
rename from libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropConstants.java
rename to libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/draganddrop/DragAndDropConstants.java
index 20da54e..4127adc 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropConstants.java
+++ b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/draganddrop/DragAndDropConstants.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2023 The Android Open Source Project
+ * Copyright (C) 2024 The Android Open Source Project
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.wm.shell.draganddrop;
+package com.android.wm.shell.shared.draganddrop;
 
 /** Constants that can be used by both Shell and other users of the library, e.g. Launcher */
 public class DragAndDropConstants {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/magnetictarget/MagnetizedObject.kt b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/magnetictarget/MagnetizedObject.kt
similarity index 99%
rename from libs/WindowManager/Shell/src/com/android/wm/shell/common/magnetictarget/MagnetizedObject.kt
rename to libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/magnetictarget/MagnetizedObject.kt
index 123d4dc..efdc6f8 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/magnetictarget/MagnetizedObject.kt
+++ b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/magnetictarget/MagnetizedObject.kt
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2020 The Android Open Source Project
+ * Copyright (C) 2024 The Android Open Source Project
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -13,7 +13,8 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package com.android.wm.shell.common.magnetictarget
+
+package com.android.wm.shell.shared.magnetictarget
 
 import android.annotation.SuppressLint
 import android.content.Context
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitScreenConstants.java b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/split/SplitScreenConstants.java
similarity index 97%
rename from libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitScreenConstants.java
rename to libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/split/SplitScreenConstants.java
index 8c06de7..498dc8b 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitScreenConstants.java
+++ b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/split/SplitScreenConstants.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2021 The Android Open Source Project
+ * Copyright (C) 2024 The Android Open Source Project
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -13,7 +13,7 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package com.android.wm.shell.common.split;
+package com.android.wm.shell.shared.split;
 
 import static android.app.WindowConfiguration.ACTIVITY_TYPE_STANDARD;
 import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
@@ -30,7 +30,7 @@
     /** Duration used for every split fade-in or fade-out. */
     public static final int FADE_DURATION = 133;
     /** Duration where we keep an app veiled to allow it to redraw itself behind the scenes. */
-    public static final int VEIL_DELAY_DURATION = 400;
+    public static final int VEIL_DELAY_DURATION = 300;
 
     /** Key for passing in widget intents when invoking split from launcher workspace. */
     public static final String KEY_EXTRA_WIDGET_INTENT = "key_extra_widget_intent";
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashScreenExitAnimationUtils.java b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/startingsurface/SplashScreenExitAnimationUtils.java
similarity index 98%
rename from libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashScreenExitAnimationUtils.java
rename to libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/startingsurface/SplashScreenExitAnimationUtils.java
index ea8c0eb..da9bf7a 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashScreenExitAnimationUtils.java
+++ b/libs/WindowManager/Shell/shared/src/com/android/wm/shell/shared/startingsurface/SplashScreenExitAnimationUtils.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2022 The Android Open Source Project
+ * Copyright (C) 2024 The Android Open Source Project
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -13,7 +13,7 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package com.android.wm.shell.startingsurface;
+package com.android.wm.shell.shared.startingsurface;
 
 import static android.view.Choreographer.CALLBACK_COMMIT;
 
@@ -45,8 +45,8 @@
 import android.view.animation.PathInterpolator;
 import android.window.SplashScreenView;
 
-import com.android.wm.shell.animation.Interpolators;
-import com.android.wm.shell.common.TransactionPool;
+import com.android.wm.shell.shared.TransactionPool;
+import com.android.wm.shell.shared.animation.Interpolators;
 
 /**
  * Utilities for creating the splash screen window animations.
@@ -88,7 +88,7 @@
      * Creates and starts the animator to fade out the icon, reveal the app, and shift up main
      * window with rounded corner radius.
      */
-    static void startAnimations(@ExitAnimationType int animationType,
+    public static void startAnimations(@ExitAnimationType int animationType,
             ViewGroup splashScreenView, SurfaceControl firstWindowSurface,
             int mainWindowShiftLength, TransactionPool transactionPool, Rect firstWindowFrame,
             int animationDuration, int iconFadeOutDuration, float iconStartAlpha,
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/animation/FlingAnimationUtils.java b/libs/WindowManager/Shell/src/com/android/wm/shell/animation/FlingAnimationUtils.java
index 26edd7d..be1f71e 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/animation/FlingAnimationUtils.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/animation/FlingAnimationUtils.java
@@ -23,6 +23,8 @@
 import android.view.animation.Interpolator;
 import android.view.animation.PathInterpolator;
 
+import com.android.wm.shell.shared.animation.Interpolators;
+
 import javax.inject.Inject;
 
 /**
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationBackground.java b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationBackground.java
index d754d04..26f7b36 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationBackground.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationBackground.java
@@ -20,6 +20,7 @@
 import static android.view.WindowInsetsController.APPEARANCE_LIGHT_STATUS_BARS;
 
 import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.graphics.Color;
 import android.graphics.Rect;
 import android.view.SurfaceControl;
@@ -59,6 +60,23 @@
      */
     public void ensureBackground(Rect startRect, int color,
             @NonNull SurfaceControl.Transaction transaction, int statusbarHeight) {
+        ensureBackground(startRect, color, transaction, statusbarHeight,
+                null /* cropBounds */, 0 /* cornerRadius */);
+    }
+
+    /**
+     * Ensures the back animation background color layer is present.
+     *
+     * @param startRect The start bounds of the closing target.
+     * @param color The background color.
+     * @param transaction The animation transaction.
+     * @param statusbarHeight The height of the statusbar (in px).
+     * @param cropBounds The crop bounds of the surface, set to non-empty to show wallpaper.
+     * @param cornerRadius The radius of corner, only work when cropBounds is not empty.
+     */
+    public void ensureBackground(Rect startRect, int color,
+            @NonNull SurfaceControl.Transaction transaction, int statusbarHeight,
+            @Nullable Rect cropBounds, float cornerRadius) {
         if (mBackgroundSurface != null) {
             return;
         }
@@ -78,6 +96,10 @@
         transaction.setColor(mBackgroundSurface, colorComponents)
                 .setLayer(mBackgroundSurface, BACKGROUND_LAYER)
                 .show(mBackgroundSurface);
+        if (cropBounds != null && !cropBounds.isEmpty()) {
+            transaction.setCrop(mBackgroundSurface, cropBounds)
+                    .setCornerRadius(mBackgroundSurface, cornerRadius);
+        }
         mStartBounds = startRect;
         mIsRequestingStatusBarAppearance = false;
         mStatusbarHeight = statusbarHeight;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java
index a0a9451..d7da051 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java
@@ -29,7 +29,7 @@
 import static com.android.window.flags.Flags.migratePredictiveBackTransition;
 import static com.android.window.flags.Flags.predictiveBackSystemAnims;
 import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_BACK_PREVIEW;
-import static com.android.wm.shell.sysui.ShellSharedConstants.KEY_EXTRA_SHELL_BACK_ANIMATION;
+import static com.android.wm.shell.shared.ShellSharedConstants.KEY_EXTRA_SHELL_BACK_ANIMATION;
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
@@ -1313,15 +1313,18 @@
                         info.getChanges().remove(j);
                     }
                 }
-                tmpSize = info.getChanges().size();
-                for (int i = 0; i < tmpSize; ++i) {
-                    final TransitionInfo.Change change = init.getChanges().get(i);
-                    if (moveToTop) {
-                        if (isSameChangeTarget(openComponent, openTaskId, change)) {
-                            change.setFlags(change.getFlags() | FLAG_MOVED_TO_TOP);
+                // Ignore merge if there is no close target
+                if (!info.getChanges().isEmpty()) {
+                    tmpSize = init.getChanges().size();
+                    for (int i = 0; i < tmpSize; ++i) {
+                        final TransitionInfo.Change change = init.getChanges().get(i);
+                        if (moveToTop) {
+                            if (isSameChangeTarget(openComponent, openTaskId, change)) {
+                                change.setFlags(change.getFlags() | FLAG_MOVED_TO_TOP);
+                            }
                         }
+                        info.getChanges().add(i, change);
                     }
-                    info.getChanges().add(i, change);
                 }
             } else {
                 // Open transition, the transition info should be:
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossActivityBackAnimation.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossActivityBackAnimation.kt
index c7e8df9..32e809a 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossActivityBackAnimation.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossActivityBackAnimation.kt
@@ -51,8 +51,8 @@
 import com.android.internal.protolog.ProtoLog
 import com.android.wm.shell.R
 import com.android.wm.shell.RootTaskDisplayAreaOrganizer
-import com.android.wm.shell.animation.Interpolators
 import com.android.wm.shell.protolog.ShellProtoLogGroup
+import com.android.wm.shell.shared.animation.Interpolators
 import kotlin.math.abs
 import kotlin.math.max
 import kotlin.math.min
@@ -189,10 +189,13 @@
         preparePreCommitEnteringRectMovement()
 
         background.ensureBackground(
-            closingTarget!!.windowConfiguration.bounds,
-            getBackgroundColor(),
-            transaction,
-            statusbarHeight
+                closingTarget!!.windowConfiguration.bounds,
+                getBackgroundColor(),
+                transaction,
+                statusbarHeight,
+                if (closingTarget!!.windowConfiguration.tasksAreFloating())
+                    closingTarget!!.localBounds else null,
+                cornerRadius
         )
         ensureScrimLayer()
         if (isLetterboxed && enteringHasSameLetterbox) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossTaskBackAnimation.java b/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossTaskBackAnimation.java
index e2b0513..3fcceca 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossTaskBackAnimation.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/CrossTaskBackAnimation.java
@@ -51,7 +51,7 @@
 import com.android.internal.policy.SystemBarUtils;
 import com.android.internal.protolog.ProtoLog;
 import com.android.wm.shell.R;
-import com.android.wm.shell.animation.Interpolators;
+import com.android.wm.shell.shared.animation.Interpolators;
 
 import javax.inject.Inject;
 
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/DefaultCrossActivityBackAnimation.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/back/DefaultCrossActivityBackAnimation.kt
index c747e1e..66d8a5f 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/back/DefaultCrossActivityBackAnimation.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/DefaultCrossActivityBackAnimation.kt
@@ -20,7 +20,7 @@
 import android.window.BackEvent
 import com.android.wm.shell.R
 import com.android.wm.shell.RootTaskDisplayAreaOrganizer
-import com.android.wm.shell.animation.Interpolators
+import com.android.wm.shell.shared.animation.Interpolators
 import javax.inject.Inject
 import kotlin.math.max
 
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BadgedImageView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BadgedImageView.java
index dc511be..c1dadad 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BadgedImageView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BadgedImageView.java
@@ -37,7 +37,7 @@
 import com.android.launcher3.icons.DotRenderer;
 import com.android.launcher3.icons.IconNormalizer;
 import com.android.wm.shell.R;
-import com.android.wm.shell.animation.Interpolators;
+import com.android.wm.shell.shared.animation.Interpolators;
 
 import java.util.EnumSet;
 
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubble.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubble.java
index 5cd2cb7..021d3c3 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubble.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/Bubble.java
@@ -54,6 +54,8 @@
 import com.android.wm.shell.bubbles.bar.BubbleBarExpandedView;
 import com.android.wm.shell.bubbles.bar.BubbleBarLayerView;
 import com.android.wm.shell.common.bubbles.BubbleInfo;
+import com.android.wm.shell.shared.annotations.ShellBackgroundThread;
+import com.android.wm.shell.shared.annotations.ShellMainThread;
 
 import java.io.PrintWriter;
 import java.util.List;
@@ -79,6 +81,7 @@
     private final LocusId mLocusId;
 
     private final Executor mMainExecutor;
+    private final Executor mBgExecutor;
 
     private long mLastUpdated;
     private long mLastAccessed;
@@ -111,7 +114,10 @@
     @Nullable
     private BubbleTaskView mBubbleTaskView;
 
+    @Nullable
     private BubbleViewInfoTask mInflationTask;
+    @Nullable
+    private BubbleViewInfoTaskLegacy mInflationTaskLegacy;
     private boolean mInflateSynchronously;
     private boolean mPendingIntentCanceled;
     private boolean mIsImportantConversation;
@@ -203,7 +209,9 @@
     @VisibleForTesting(visibility = PRIVATE)
     public Bubble(@NonNull final String key, @NonNull final ShortcutInfo shortcutInfo,
             final int desiredHeight, final int desiredHeightResId, @Nullable final String title,
-            int taskId, @Nullable final String locus, boolean isDismissable, Executor mainExecutor,
+            int taskId, @Nullable final String locus, boolean isDismissable,
+            @ShellMainThread Executor mainExecutor,
+            @ShellBackgroundThread Executor bgExecutor,
             final Bubbles.BubbleMetadataFlagListener listener) {
         Objects.requireNonNull(key);
         Objects.requireNonNull(shortcutInfo);
@@ -222,6 +230,7 @@
         mTitle = title;
         mShowBubbleUpdateDot = false;
         mMainExecutor = mainExecutor;
+        mBgExecutor = bgExecutor;
         mTaskId = taskId;
         mBubbleMetadataFlagListener = listener;
         mIsAppBubble = false;
@@ -233,7 +242,8 @@
             @Nullable Icon icon,
             boolean isAppBubble,
             String key,
-            Executor mainExecutor) {
+            @ShellMainThread Executor mainExecutor,
+            @ShellBackgroundThread Executor bgExecutor) {
         mGroupKey = null;
         mLocusId = null;
         mFlags = 0;
@@ -243,13 +253,15 @@
         mKey = key;
         mShowBubbleUpdateDot = false;
         mMainExecutor = mainExecutor;
+        mBgExecutor = bgExecutor;
         mTaskId = INVALID_TASK_ID;
         mAppIntent = intent;
         mDesiredHeight = Integer.MAX_VALUE;
         mPackageName = intent.getPackage();
     }
 
-    private Bubble(ShortcutInfo info, Executor mainExecutor) {
+    private Bubble(ShortcutInfo info, @ShellMainThread Executor mainExecutor,
+            @ShellBackgroundThread Executor bgExecutor) {
         mGroupKey = null;
         mLocusId = null;
         mFlags = 0;
@@ -259,6 +271,7 @@
         mKey = getBubbleKeyForShortcut(info);
         mShowBubbleUpdateDot = false;
         mMainExecutor = mainExecutor;
+        mBgExecutor = bgExecutor;
         mTaskId = INVALID_TASK_ID;
         mAppIntent = null;
         mDesiredHeight = Integer.MAX_VALUE;
@@ -267,24 +280,21 @@
     }
 
     /** Creates an app bubble. */
-    public static Bubble createAppBubble(
-            Intent intent,
-            UserHandle user,
-            @Nullable Icon icon,
-            Executor mainExecutor) {
+    public static Bubble createAppBubble(Intent intent, UserHandle user, @Nullable Icon icon,
+            @ShellMainThread Executor mainExecutor, @ShellBackgroundThread Executor bgExecutor) {
         return new Bubble(intent,
                 user,
                 icon,
                 /* isAppBubble= */ true,
                 /* key= */ getAppBubbleKeyForApp(intent.getPackage(), user),
-                mainExecutor);
+                mainExecutor, bgExecutor);
     }
 
     /** Creates a shortcut bubble. */
     public static Bubble createShortcutBubble(
             ShortcutInfo info,
-            Executor mainExecutor) {
-        return new Bubble(info, mainExecutor);
+            @ShellMainThread Executor mainExecutor, @ShellBackgroundThread Executor bgExecutor) {
+        return new Bubble(info, mainExecutor, bgExecutor);
     }
 
     /**
@@ -309,7 +319,7 @@
     public Bubble(@NonNull final BubbleEntry entry,
             final Bubbles.BubbleMetadataFlagListener listener,
             final Bubbles.PendingIntentCanceledListener intentCancelListener,
-            Executor mainExecutor) {
+            @ShellMainThread Executor mainExecutor, @ShellBackgroundThread Executor bgExecutor) {
         mIsAppBubble = false;
         mKey = entry.getKey();
         mGroupKey = entry.getGroupKey();
@@ -324,6 +334,7 @@
             });
         };
         mMainExecutor = mainExecutor;
+        mBgExecutor = bgExecutor;
         mTaskId = INVALID_TASK_ID;
         setEntry(entry);
     }
@@ -557,40 +568,70 @@
             @Nullable BubbleBarLayerView layerView,
             BubbleIconFactory iconFactory,
             boolean skipInflation) {
-        if (isBubbleLoading()) {
-            mInflationTask.cancel(true /* mayInterruptIfRunning */);
-        }
-        mInflationTask = new BubbleViewInfoTask(this,
-                context,
-                expandedViewManager,
-                taskViewFactory,
-                positioner,
-                stackView,
-                layerView,
-                iconFactory,
-                skipInflation,
-                callback,
-                mMainExecutor);
-        if (mInflateSynchronously) {
-            mInflationTask.onPostExecute(mInflationTask.doInBackground());
+        if (Flags.bubbleViewInfoExecutors()) {
+            if (mInflationTask != null && mInflationTask.getStatus() != FINISHED) {
+                mInflationTask.cancel(true /* mayInterruptIfRunning */);
+            }
+            // TODO(b/353894869): switch to executors
+            mInflationTask = new BubbleViewInfoTask(this,
+                    context,
+                    expandedViewManager,
+                    taskViewFactory,
+                    positioner,
+                    stackView,
+                    layerView,
+                    iconFactory,
+                    skipInflation,
+                    callback,
+                    mMainExecutor);
+            if (mInflateSynchronously) {
+                mInflationTask.onPostExecute(mInflationTask.doInBackground());
+            } else {
+                mInflationTask.execute();
+            }
         } else {
-            mInflationTask.execute();
+            if (mInflationTaskLegacy != null && mInflationTaskLegacy.getStatus() != FINISHED) {
+                mInflationTaskLegacy.cancel(true /* mayInterruptIfRunning */);
+            }
+            mInflationTaskLegacy = new BubbleViewInfoTaskLegacy(this,
+                    context,
+                    expandedViewManager,
+                    taskViewFactory,
+                    positioner,
+                    stackView,
+                    layerView,
+                    iconFactory,
+                    skipInflation,
+                    bubble -> {
+                        if (callback != null) {
+                            callback.onBubbleViewsReady(bubble);
+                        }
+                    },
+                    mMainExecutor);
+            if (mInflateSynchronously) {
+                mInflationTaskLegacy.onPostExecute(mInflationTaskLegacy.doInBackground());
+            } else {
+                mInflationTaskLegacy.execute();
+            }
         }
     }
 
-    private boolean isBubbleLoading() {
-        return mInflationTask != null && mInflationTask.getStatus() != FINISHED;
-    }
-
     boolean isInflated() {
         return (mIconView != null && mExpandedView != null) || mBubbleBarExpandedView != null;
     }
 
     void stopInflation() {
-        if (mInflationTask == null) {
-            return;
+        if (Flags.bubbleViewInfoExecutors()) {
+            if (mInflationTask == null) {
+                return;
+            }
+            mInflationTask.cancel(true /* mayInterruptIfRunning */);
+        } else {
+            if (mInflationTaskLegacy == null) {
+                return;
+            }
+            mInflationTaskLegacy.cancel(true /* mayInterruptIfRunning */);
         }
-        mInflationTask.cancel(true /* mayInterruptIfRunning */);
     }
 
     void setViewInfo(BubbleViewInfoTask.BubbleViewInfo info) {
@@ -626,6 +667,42 @@
     }
 
     /**
+     * @deprecated {@link BubbleViewInfoTaskLegacy} is deprecated.
+     */
+    @Deprecated
+    void setViewInfoLegacy(BubbleViewInfoTaskLegacy.BubbleViewInfo info) {
+        if (!isInflated()) {
+            mIconView = info.imageView;
+            mExpandedView = info.expandedView;
+            mBubbleBarExpandedView = info.bubbleBarExpandedView;
+        }
+
+        mShortcutInfo = info.shortcutInfo;
+        mAppName = info.appName;
+        if (mTitle == null) {
+            mTitle = mAppName;
+        }
+        mFlyoutMessage = info.flyoutMessage;
+
+        mBadgeBitmap = info.badgeBitmap;
+        mRawBadgeBitmap = info.rawBadgeBitmap;
+        mBubbleBitmap = info.bubbleBitmap;
+
+        mDotColor = info.dotColor;
+        mDotPath = info.dotPath;
+
+        if (mExpandedView != null) {
+            mExpandedView.update(this /* bubble */);
+        }
+        if (mBubbleBarExpandedView != null) {
+            mBubbleBarExpandedView.update(this /* bubble */);
+        }
+        if (mIconView != null) {
+            mIconView.setRenderedBubble(this /* bubble */);
+        }
+    }
+
+    /**
      * Set visibility of bubble in the expanded state.
      *
      * <p>Note that this contents visibility doesn't affect visibility at {@link android.view.View},
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java
index b28b218..3dc33c2 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java
@@ -35,7 +35,7 @@
 import static com.android.wm.shell.bubbles.Bubbles.DISMISS_SHORTCUT_REMOVED;
 import static com.android.wm.shell.bubbles.Bubbles.DISMISS_USER_CHANGED;
 import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_BUBBLES;
-import static com.android.wm.shell.sysui.ShellSharedConstants.KEY_EXTRA_SHELL_BUBBLES;
+import static com.android.wm.shell.shared.ShellSharedConstants.KEY_EXTRA_SHELL_BUBBLES;
 
 import android.annotation.BinderThread;
 import android.annotation.NonNull;
@@ -1495,7 +1495,7 @@
                 b.setAppBubbleIntent(intent);
             } else {
                 // App bubble does not exist, lets add and expand it
-                b = Bubble.createAppBubble(intent, user, icon, mMainExecutor);
+                b = Bubble.createAppBubble(intent, user, icon, mMainExecutor, mBackgroundExecutor);
             }
             ProtoLog.d(WM_SHELL_BUBBLES, "inflateAndAdd %s", appBubbleKey);
             b.setShouldAutoExpand(true);
@@ -1997,10 +1997,15 @@
 
         @Override
         public void expansionChanged(boolean isExpanded) {
-            // in bubble bar mode, let the request to show the expanded view come from launcher.
-            // only collapse here if we're collapsing.
-            if (mLayerView != null && !isExpanded) {
-                mLayerView.collapse();
+            if (mLayerView != null) {
+                if (!isExpanded) {
+                    mLayerView.collapse();
+                } else {
+                    BubbleViewProvider selectedBubble = mBubbleData.getSelectedBubble();
+                    if (selectedBubble != null) {
+                        mLayerView.showExpandedView(selectedBubble);
+                    }
+                }
             }
         }
 
@@ -2146,13 +2151,6 @@
         }
     };
 
-    private void showExpandedViewForBubbleBar() {
-        BubbleViewProvider selectedBubble = mBubbleData.getSelectedBubble();
-        if (selectedBubble != null) {
-            mLayerView.showExpandedView(selectedBubble);
-        }
-    }
-
     private void updateOverflowButtonDot() {
         BubbleOverflow overflow = mBubbleData.getOverflow();
         if (overflow == null) return;
@@ -2219,7 +2217,6 @@
         // And since all children are removed, remove the summary.
         removeCallback.accept(-1);
 
-        // TODO: (b/145659174) remove references to mSuppressedGroupKeys once fully migrated
         mBubbleData.addSummaryToSuppress(summary.getStatusBarNotification().getGroupKey(),
                 summary.getKey());
     }
@@ -2535,15 +2532,6 @@
                 if (mLayerView != null) mLayerView.updateExpandedView();
             });
         }
-
-        @Override
-        public void showExpandedView() {
-            mMainExecutor.execute(() -> {
-                if (mLayerView != null) {
-                    showExpandedViewForBubbleBar();
-                }
-            });
-        }
     }
 
     private class BubblesImpl implements Bubbles {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleData.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleData.java
index 3c6c6fa..4ad1802 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleData.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleData.java
@@ -43,6 +43,8 @@
 import com.android.wm.shell.bubbles.Bubbles.DismissReason;
 import com.android.wm.shell.common.bubbles.BubbleBarUpdate;
 import com.android.wm.shell.common.bubbles.RemovedBubble;
+import com.android.wm.shell.shared.annotations.ShellBackgroundThread;
+import com.android.wm.shell.shared.annotations.ShellMainThread;
 
 import java.io.PrintWriter;
 import java.util.ArrayList;
@@ -201,6 +203,7 @@
     private final BubblePositioner mPositioner;
     private final BubbleEducationController mEducationController;
     private final Executor mMainExecutor;
+    private final Executor mBgExecutor;
     /** Bubbles that are actively in the stack. */
     private final List<Bubble> mBubbles;
     /** Bubbles that aged out to overflow. */
@@ -246,12 +249,14 @@
     private HashMap<String, String> mSuppressedGroupKeys = new HashMap<>();
 
     public BubbleData(Context context, BubbleLogger bubbleLogger, BubblePositioner positioner,
-            BubbleEducationController educationController, Executor mainExecutor) {
+            BubbleEducationController educationController, @ShellMainThread Executor mainExecutor,
+            @ShellBackgroundThread Executor bgExecutor) {
         mContext = context;
         mLogger = bubbleLogger;
         mPositioner = positioner;
         mEducationController = educationController;
         mMainExecutor = mainExecutor;
+        mBgExecutor = bgExecutor;
         mOverflow = new BubbleOverflow(context, positioner);
         mBubbles = new ArrayList<>();
         mOverflowBubbles = new ArrayList<>();
@@ -431,7 +436,8 @@
                     bubbleToReturn = new Bubble(entry,
                             mBubbleMetadataFlagListener,
                             mCancelledListener,
-                            mMainExecutor);
+                            mMainExecutor,
+                            mBgExecutor);
                 } else {
                     // If there's no entry it must be a persisted bubble
                     bubbleToReturn = persistedBubble;
@@ -450,7 +456,7 @@
         String bubbleKey = Bubble.getBubbleKeyForShortcut(info);
         Bubble bubbleToReturn = findAndRemoveBubbleFromOverflow(bubbleKey);
         if (bubbleToReturn == null) {
-            bubbleToReturn = Bubble.createShortcutBubble(info, mMainExecutor);
+            bubbleToReturn = Bubble.createShortcutBubble(info, mMainExecutor, mBgExecutor);
         }
         return bubbleToReturn;
     }
@@ -461,7 +467,7 @@
                 user);
         Bubble bubbleToReturn = findAndRemoveBubbleFromOverflow(bubbleKey);
         if (bubbleToReturn == null) {
-            bubbleToReturn = Bubble.createAppBubble(intent, user, null, mMainExecutor);
+            bubbleToReturn = Bubble.createAppBubble(intent, user, null, mMainExecutor, mBgExecutor);
         }
         return bubbleToReturn;
     }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleDataRepository.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleDataRepository.kt
index df12999..818ba45 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleDataRepository.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleDataRepository.kt
@@ -31,6 +31,9 @@
 import com.android.wm.shell.bubbles.storage.BubblePersistentRepository
 import com.android.wm.shell.bubbles.storage.BubbleVolatileRepository
 import com.android.wm.shell.common.ShellExecutor
+import com.android.wm.shell.shared.annotations.ShellBackgroundThread
+import com.android.wm.shell.shared.annotations.ShellMainThread
+import java.util.concurrent.Executor
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.Dispatchers
 import kotlinx.coroutines.Job
@@ -41,7 +44,8 @@
 
 class BubbleDataRepository(
     private val launcherApps: LauncherApps,
-    private val mainExecutor: ShellExecutor,
+    @ShellMainThread private val mainExecutor: ShellExecutor,
+    @ShellBackgroundThread private val bgExecutor: Executor,
     private val persistentRepository: BubblePersistentRepository,
 ) {
     private val volatileRepository = BubbleVolatileRepository(launcherApps)
@@ -259,8 +263,8 @@
                                 entity.locus,
                                 entity.isDismissable,
                                 mainExecutor,
-                                bubbleMetadataFlagListener
-                        )
+                                bgExecutor,
+                                bubbleMetadataFlagListener)
                     }
         }
         mainExecutor.execute { cb(bubbles) }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleExpandedView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleExpandedView.java
index c9fcd58..5295526 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleExpandedView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleExpandedView.java
@@ -71,7 +71,7 @@
 import com.android.wm.shell.Flags;
 import com.android.wm.shell.R;
 import com.android.wm.shell.common.AlphaOptimizedButton;
-import com.android.wm.shell.common.TriangleShape;
+import com.android.wm.shell.shared.TriangleShape;
 import com.android.wm.shell.taskview.TaskView;
 
 import java.io.PrintWriter;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleFlyoutView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleFlyoutView.java
index 42de401..1711dca 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleFlyoutView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleFlyoutView.java
@@ -19,8 +19,8 @@
 import static android.graphics.Paint.ANTI_ALIAS_FLAG;
 import static android.graphics.Paint.FILTER_BITMAP_FLAG;
 
-import static com.android.wm.shell.animation.Interpolators.ALPHA_IN;
-import static com.android.wm.shell.animation.Interpolators.ALPHA_OUT;
+import static com.android.wm.shell.shared.animation.Interpolators.ALPHA_IN;
+import static com.android.wm.shell.shared.animation.Interpolators.ALPHA_OUT;
 
 import android.animation.ArgbEvaluator;
 import android.content.Context;
@@ -50,7 +50,7 @@
 import androidx.annotation.Nullable;
 
 import com.android.wm.shell.R;
-import com.android.wm.shell.common.TriangleShape;
+import com.android.wm.shell.shared.TriangleShape;
 
 /**
  * Flyout view that appears as a 'chat bubble' alongside the bubble stack. The flyout can visually
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java
index 322b01e..53bbf88 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java
@@ -19,8 +19,6 @@
 import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
 import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT;
 
-import static com.android.wm.shell.animation.Interpolators.ALPHA_IN;
-import static com.android.wm.shell.animation.Interpolators.ALPHA_OUT;
 import static com.android.wm.shell.bubbles.BubbleDebugConfig.TAG_BUBBLES;
 import static com.android.wm.shell.bubbles.BubbleDebugConfig.TAG_WITH_CLASS_NAME;
 import static com.android.wm.shell.bubbles.BubblePositioner.NUM_VISIBLE_WHEN_RESTING;
@@ -28,6 +26,8 @@
 import static com.android.wm.shell.bubbles.BubblePositioner.StackPinnedEdge.RIGHT;
 import static com.android.wm.shell.common.bubbles.BubbleConstants.BUBBLE_EXPANDED_SCRIM_ALPHA;
 import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_BUBBLES;
+import static com.android.wm.shell.shared.animation.Interpolators.ALPHA_IN;
+import static com.android.wm.shell.shared.animation.Interpolators.ALPHA_OUT;
 
 import android.animation.Animator;
 import android.animation.AnimatorListenerAdapter;
@@ -82,7 +82,6 @@
 import com.android.internal.util.FrameworkStatsLog;
 import com.android.wm.shell.Flags;
 import com.android.wm.shell.R;
-import com.android.wm.shell.animation.Interpolators;
 import com.android.wm.shell.bubbles.BubblesNavBarMotionEventHandler.MotionEventListener;
 import com.android.wm.shell.bubbles.animation.AnimatableScaleMatrix;
 import com.android.wm.shell.bubbles.animation.ExpandedAnimationController;
@@ -94,8 +93,9 @@
 import com.android.wm.shell.common.ShellExecutor;
 import com.android.wm.shell.common.bubbles.DismissView;
 import com.android.wm.shell.common.bubbles.RelativeTouchListener;
-import com.android.wm.shell.common.magnetictarget.MagnetizedObject;
+import com.android.wm.shell.shared.animation.Interpolators;
 import com.android.wm.shell.shared.animation.PhysicsAnimator;
+import com.android.wm.shell.shared.magnetictarget.MagnetizedObject;
 
 import java.io.PrintWriter;
 import java.math.BigDecimal;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleViewInfoTask.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleViewInfoTask.java
index 69119cf..03a2efd 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleViewInfoTask.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleViewInfoTask.java
@@ -54,6 +54,7 @@
 /**
  * Simple task to inflate views & load necessary info to display a bubble.
  */
+// TODO(b/353894869): switch to executors
 public class BubbleViewInfoTask extends AsyncTask<Void, Void, BubbleViewInfoTask.BubbleViewInfo> {
     private static final String TAG = TAG_WITH_CLASS_NAME ? "BubbleViewInfoTask" : TAG_BUBBLES;
 
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleViewInfoTaskLegacy.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleViewInfoTaskLegacy.java
new file mode 100644
index 0000000..5cfebf8
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleViewInfoTaskLegacy.java
@@ -0,0 +1,346 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.bubbles;
+
+import static com.android.wm.shell.bubbles.BadgedImageView.DEFAULT_PATH_SIZE;
+import static com.android.wm.shell.bubbles.BadgedImageView.WHITE_SCRIM_ALPHA;
+import static com.android.wm.shell.bubbles.BubbleDebugConfig.TAG_BUBBLES;
+import static com.android.wm.shell.bubbles.BubbleDebugConfig.TAG_WITH_CLASS_NAME;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.ShortcutInfo;
+import android.graphics.Bitmap;
+import android.graphics.Color;
+import android.graphics.Matrix;
+import android.graphics.Path;
+import android.graphics.drawable.Drawable;
+import android.graphics.drawable.Icon;
+import android.os.AsyncTask;
+import android.util.Log;
+import android.util.PathParser;
+import android.view.LayoutInflater;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.graphics.ColorUtils;
+import com.android.launcher3.icons.BitmapInfo;
+import com.android.launcher3.icons.BubbleIconFactory;
+import com.android.wm.shell.R;
+import com.android.wm.shell.bubbles.bar.BubbleBarExpandedView;
+import com.android.wm.shell.bubbles.bar.BubbleBarLayerView;
+
+import java.lang.ref.WeakReference;
+import java.util.Objects;
+import java.util.concurrent.Executor;
+
+/**
+ * Simple task to inflate views & load necessary info to display a bubble.
+ *
+ * @deprecated Deprecated since this is using an AsyncTask. Use {@link BubbleViewInfoTask} instead.
+ */
+@Deprecated
+// TODO(b/353894869): remove once flag for loading view info with executors rolls out
+public class BubbleViewInfoTaskLegacy extends
+        AsyncTask<Void, Void, BubbleViewInfoTaskLegacy.BubbleViewInfo> {
+    private static final String TAG =
+            TAG_WITH_CLASS_NAME ? "BubbleViewInfoTaskLegacy" : TAG_BUBBLES;
+
+
+    /**
+     * Callback to find out when the bubble has been inflated & necessary data loaded.
+     */
+    public interface Callback {
+        /**
+         * Called when data has been loaded for the bubble.
+         */
+        void onBubbleViewsReady(Bubble bubble);
+    }
+
+    private Bubble mBubble;
+    private WeakReference<Context> mContext;
+    private WeakReference<BubbleExpandedViewManager> mExpandedViewManager;
+    private WeakReference<BubbleTaskViewFactory> mTaskViewFactory;
+    private WeakReference<BubblePositioner> mPositioner;
+    private WeakReference<BubbleStackView> mStackView;
+    private WeakReference<BubbleBarLayerView> mLayerView;
+    private BubbleIconFactory mIconFactory;
+    private boolean mSkipInflation;
+    private Callback mCallback;
+    private Executor mMainExecutor;
+
+    /**
+     * Creates a task to load information for the provided {@link Bubble}. Once all info
+     * is loaded, {@link Callback} is notified.
+     */
+    BubbleViewInfoTaskLegacy(Bubble b,
+            Context context,
+            BubbleExpandedViewManager expandedViewManager,
+            BubbleTaskViewFactory taskViewFactory,
+            BubblePositioner positioner,
+            @Nullable BubbleStackView stackView,
+            @Nullable BubbleBarLayerView layerView,
+            BubbleIconFactory factory,
+            boolean skipInflation,
+            Callback c,
+            Executor mainExecutor) {
+        mBubble = b;
+        mContext = new WeakReference<>(context);
+        mExpandedViewManager = new WeakReference<>(expandedViewManager);
+        mTaskViewFactory = new WeakReference<>(taskViewFactory);
+        mPositioner = new WeakReference<>(positioner);
+        mStackView = new WeakReference<>(stackView);
+        mLayerView = new WeakReference<>(layerView);
+        mIconFactory = factory;
+        mSkipInflation = skipInflation;
+        mCallback = c;
+        mMainExecutor = mainExecutor;
+    }
+
+    @Override
+    protected BubbleViewInfo doInBackground(Void... voids) {
+        if (!verifyState()) {
+            // If we're in an inconsistent state, then switched modes and should just bail now.
+            return null;
+        }
+        if (mLayerView.get() != null) {
+            return BubbleViewInfo.populateForBubbleBar(mContext.get(), mExpandedViewManager.get(),
+                    mTaskViewFactory.get(), mPositioner.get(), mLayerView.get(), mIconFactory,
+                    mBubble, mSkipInflation);
+        } else {
+            return BubbleViewInfo.populate(mContext.get(), mExpandedViewManager.get(),
+                    mTaskViewFactory.get(), mPositioner.get(), mStackView.get(), mIconFactory,
+                    mBubble, mSkipInflation);
+        }
+    }
+
+    @Override
+    protected void onPostExecute(BubbleViewInfo viewInfo) {
+        if (isCancelled() || viewInfo == null) {
+            return;
+        }
+
+        mMainExecutor.execute(() -> {
+            if (!verifyState()) {
+                return;
+            }
+            mBubble.setViewInfoLegacy(viewInfo);
+            if (mCallback != null) {
+                mCallback.onBubbleViewsReady(mBubble);
+            }
+        });
+    }
+
+    private boolean verifyState() {
+        if (mExpandedViewManager.get().isShowingAsBubbleBar()) {
+            return mLayerView.get() != null;
+        } else {
+            return mStackView.get() != null;
+        }
+    }
+
+    /**
+     * Info necessary to render a bubble.
+     */
+    @VisibleForTesting
+    public static class BubbleViewInfo {
+        // TODO(b/273312602): for foldables it might make sense to populate all of the views
+
+        // Always populated
+        ShortcutInfo shortcutInfo;
+        String appName;
+        Bitmap rawBadgeBitmap;
+
+        // Only populated when showing in taskbar
+        @Nullable BubbleBarExpandedView bubbleBarExpandedView;
+
+        // These are only populated when not showing in taskbar
+        @Nullable BadgedImageView imageView;
+        @Nullable BubbleExpandedView expandedView;
+        int dotColor;
+        Path dotPath;
+        @Nullable Bubble.FlyoutMessage flyoutMessage;
+        Bitmap bubbleBitmap;
+        Bitmap badgeBitmap;
+
+        @Nullable
+        public static BubbleViewInfo populateForBubbleBar(Context c,
+                BubbleExpandedViewManager expandedViewManager,
+                BubbleTaskViewFactory taskViewFactory,
+                BubblePositioner positioner,
+                BubbleBarLayerView layerView,
+                BubbleIconFactory iconFactory,
+                Bubble b,
+                boolean skipInflation) {
+            BubbleViewInfo info = new BubbleViewInfo();
+
+            if (!skipInflation && !b.isInflated()) {
+                BubbleTaskView bubbleTaskView = b.getOrCreateBubbleTaskView(taskViewFactory);
+                LayoutInflater inflater = LayoutInflater.from(c);
+                info.bubbleBarExpandedView = (BubbleBarExpandedView) inflater.inflate(
+                        R.layout.bubble_bar_expanded_view, layerView, false /* attachToRoot */);
+                info.bubbleBarExpandedView.initialize(
+                        expandedViewManager, positioner, false /* isOverflow */, bubbleTaskView);
+            }
+
+            if (!populateCommonInfo(info, c, b, iconFactory)) {
+                // if we failed to update common fields return null
+                return null;
+            }
+
+            return info;
+        }
+
+        @VisibleForTesting
+        @Nullable
+        public static BubbleViewInfo populate(Context c,
+                BubbleExpandedViewManager expandedViewManager,
+                BubbleTaskViewFactory taskViewFactory,
+                BubblePositioner positioner,
+                BubbleStackView stackView,
+                BubbleIconFactory iconFactory,
+                Bubble b,
+                boolean skipInflation) {
+            BubbleViewInfo info = new BubbleViewInfo();
+
+            // View inflation: only should do this once per bubble
+            if (!skipInflation && !b.isInflated()) {
+                LayoutInflater inflater = LayoutInflater.from(c);
+                info.imageView = (BadgedImageView) inflater.inflate(
+                        R.layout.bubble_view, stackView, false /* attachToRoot */);
+                info.imageView.initialize(positioner);
+
+                BubbleTaskView bubbleTaskView = b.getOrCreateBubbleTaskView(taskViewFactory);
+                info.expandedView = (BubbleExpandedView) inflater.inflate(
+                        R.layout.bubble_expanded_view, stackView, false /* attachToRoot */);
+                info.expandedView.initialize(
+                        expandedViewManager, stackView, positioner, false /* isOverflow */,
+                        bubbleTaskView);
+            }
+
+            if (!populateCommonInfo(info, c, b, iconFactory)) {
+                // if we failed to update common fields return null
+                return null;
+            }
+
+            // Flyout
+            info.flyoutMessage = b.getFlyoutMessage();
+            if (info.flyoutMessage != null) {
+                info.flyoutMessage.senderAvatar =
+                        loadSenderAvatar(c, info.flyoutMessage.senderIcon);
+            }
+            return info;
+        }
+    }
+
+    /**
+     * Modifies the given {@code info} object and populates common fields in it.
+     *
+     * <p>This method returns {@code true} if the update was successful and {@code false} otherwise.
+     * Callers should assume that the info object is unusable if the update was unsuccessful.
+     */
+    private static boolean populateCommonInfo(
+            BubbleViewInfo info, Context c, Bubble b, BubbleIconFactory iconFactory) {
+        if (b.getShortcutInfo() != null) {
+            info.shortcutInfo = b.getShortcutInfo();
+        }
+
+        // App name & app icon
+        PackageManager pm = BubbleController.getPackageManagerForUser(c,
+                b.getUser().getIdentifier());
+        ApplicationInfo appInfo;
+        Drawable badgedIcon;
+        Drawable appIcon;
+        try {
+            appInfo = pm.getApplicationInfo(
+                    b.getPackageName(),
+                    PackageManager.MATCH_UNINSTALLED_PACKAGES
+                            | PackageManager.MATCH_DISABLED_COMPONENTS
+                            | PackageManager.MATCH_DIRECT_BOOT_UNAWARE
+                            | PackageManager.MATCH_DIRECT_BOOT_AWARE);
+            if (appInfo != null) {
+                info.appName = String.valueOf(pm.getApplicationLabel(appInfo));
+            }
+            appIcon = pm.getApplicationIcon(b.getPackageName());
+            badgedIcon = pm.getUserBadgedIcon(appIcon, b.getUser());
+        } catch (PackageManager.NameNotFoundException exception) {
+            // If we can't find package... don't think we should show the bubble.
+            Log.w(TAG, "Unable to find package: " + b.getPackageName());
+            return false;
+        }
+
+        Drawable bubbleDrawable = null;
+        try {
+            // Badged bubble image
+            bubbleDrawable = iconFactory.getBubbleDrawable(c, info.shortcutInfo,
+                    b.getIcon());
+        } catch (Exception e) {
+            // If we can't create the icon we'll default to the app icon
+            Log.w(TAG, "Exception creating icon for the bubble: " + b.getKey());
+        }
+
+        if (bubbleDrawable == null) {
+            // Default to app icon
+            bubbleDrawable = appIcon;
+        }
+
+        BitmapInfo badgeBitmapInfo = iconFactory.getBadgeBitmap(badgedIcon,
+                b.isImportantConversation());
+        info.badgeBitmap = badgeBitmapInfo.icon;
+        // Raw badge bitmap never includes the important conversation ring
+        info.rawBadgeBitmap = b.isImportantConversation()
+                ? iconFactory.getBadgeBitmap(badgedIcon, false).icon
+                : badgeBitmapInfo.icon;
+
+        float[] bubbleBitmapScale = new float[1];
+        info.bubbleBitmap = iconFactory.getBubbleBitmap(bubbleDrawable, bubbleBitmapScale);
+
+        // Dot color & placement
+        Path iconPath = PathParser.createPathFromPathData(
+                c.getResources().getString(com.android.internal.R.string.config_icon_mask));
+        Matrix matrix = new Matrix();
+        float scale = bubbleBitmapScale[0];
+        float radius = DEFAULT_PATH_SIZE / 2f;
+        matrix.setScale(scale /* x scale */, scale /* y scale */, radius /* pivot x */,
+                radius /* pivot y */);
+        iconPath.transform(matrix);
+        info.dotPath = iconPath;
+        info.dotColor = ColorUtils.blendARGB(badgeBitmapInfo.color,
+                Color.WHITE, WHITE_SCRIM_ALPHA);
+        return true;
+    }
+
+    @Nullable
+    static Drawable loadSenderAvatar(@NonNull final Context context, @Nullable final Icon icon) {
+        Objects.requireNonNull(context);
+        if (icon == null) return null;
+        try {
+            if (icon.getType() == Icon.TYPE_URI
+                    || icon.getType() == Icon.TYPE_URI_ADAPTIVE_BITMAP) {
+                context.grantUriPermission(context.getPackageName(),
+                        icon.getUri(), Intent.FLAG_GRANT_READ_URI_PERMISSION);
+            }
+            return icon.loadDrawable(context);
+        } catch (Exception e) {
+            Log.w(TAG, "loadSenderAvatar failed: " + e.getMessage());
+            return null;
+        }
+    }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/IBubbles.aidl b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/IBubbles.aidl
index 5779a8f..5c78974 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/IBubbles.aidl
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/IBubbles.aidl
@@ -53,6 +53,4 @@
     oneway void showShortcutBubble(in ShortcutInfo info) = 12;
 
     oneway void showAppBubble(in Intent intent) = 13;
-
-    oneway void showExpandedView() = 14;
 }
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/ManageEducationView.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/ManageEducationView.kt
index da71b1c..39a2a7b 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/ManageEducationView.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/ManageEducationView.kt
@@ -27,7 +27,7 @@
 import android.widget.LinearLayout
 import com.android.internal.R.color.system_neutral1_900
 import com.android.wm.shell.R
-import com.android.wm.shell.animation.Interpolators
+import com.android.wm.shell.shared.animation.Interpolators
 
 /**
  * User education view to highlight the manage button that allows a user to configure the settings
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/StackEducationView.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/StackEducationView.kt
index c4108c4..1660619 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/StackEducationView.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/StackEducationView.kt
@@ -26,7 +26,7 @@
 import android.widget.TextView
 import com.android.internal.util.ContrastColorUtil
 import com.android.wm.shell.R
-import com.android.wm.shell.animation.Interpolators
+import com.android.wm.shell.shared.animation.Interpolators
 
 /**
  * User education view to highlight the collapsed stack of bubbles. Shown only the first time a user
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/ExpandedAnimationController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/ExpandedAnimationController.java
index f925eae..8f0dfb9 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/ExpandedAnimationController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/ExpandedAnimationController.java
@@ -33,13 +33,13 @@
 import androidx.dynamicanimation.animation.SpringForce;
 
 import com.android.wm.shell.R;
-import com.android.wm.shell.animation.Interpolators;
 import com.android.wm.shell.bubbles.BadgedImageView;
 import com.android.wm.shell.bubbles.BubbleOverflow;
 import com.android.wm.shell.bubbles.BubblePositioner;
 import com.android.wm.shell.bubbles.BubbleStackView;
-import com.android.wm.shell.common.magnetictarget.MagnetizedObject;
+import com.android.wm.shell.shared.animation.Interpolators;
 import com.android.wm.shell.shared.animation.PhysicsAnimator;
+import com.android.wm.shell.shared.magnetictarget.MagnetizedObject;
 
 import com.google.android.collect.Sets;
 
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/ExpandedViewAnimationControllerImpl.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/ExpandedViewAnimationControllerImpl.java
index fbef6b5..7cb537a 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/ExpandedViewAnimationControllerImpl.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/ExpandedViewAnimationControllerImpl.java
@@ -40,9 +40,9 @@
 
 import com.android.internal.protolog.ProtoLog;
 import com.android.wm.shell.animation.FlingAnimationUtils;
-import com.android.wm.shell.animation.Interpolators;
 import com.android.wm.shell.bubbles.BubbleExpandedView;
 import com.android.wm.shell.bubbles.BubblePositioner;
+import com.android.wm.shell.shared.animation.Interpolators;
 
 import java.util.ArrayList;
 import java.util.List;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/StackAnimationController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/StackAnimationController.java
index 47d4d07..91585dc 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/StackAnimationController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/animation/StackAnimationController.java
@@ -42,8 +42,8 @@
 import com.android.wm.shell.bubbles.BubblePositioner;
 import com.android.wm.shell.bubbles.BubbleStackView;
 import com.android.wm.shell.common.FloatingContentCoordinator;
-import com.android.wm.shell.common.magnetictarget.MagnetizedObject;
 import com.android.wm.shell.shared.animation.PhysicsAnimator;
+import com.android.wm.shell.shared.magnetictarget.MagnetizedObject;
 
 import com.google.android.collect.Sets;
 
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarAnimationHelper.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarAnimationHelper.java
index 8e58db1..565fde0 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarAnimationHelper.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarAnimationHelper.java
@@ -24,9 +24,9 @@
 import static android.view.View.X;
 import static android.view.View.Y;
 
-import static com.android.wm.shell.animation.Interpolators.EMPHASIZED;
-import static com.android.wm.shell.animation.Interpolators.EMPHASIZED_DECELERATE;
 import static com.android.wm.shell.bubbles.bar.BubbleBarExpandedView.CORNER_RADIUS;
+import static com.android.wm.shell.shared.animation.Interpolators.EMPHASIZED;
+import static com.android.wm.shell.shared.animation.Interpolators.EMPHASIZED_DECELERATE;
 
 import android.animation.Animator;
 import android.animation.AnimatorListenerAdapter;
@@ -42,13 +42,13 @@
 
 import androidx.annotation.Nullable;
 
-import com.android.wm.shell.animation.Interpolators;
 import com.android.wm.shell.bubbles.BubbleOverflow;
 import com.android.wm.shell.bubbles.BubblePositioner;
 import com.android.wm.shell.bubbles.BubbleViewProvider;
 import com.android.wm.shell.bubbles.animation.AnimatableScaleMatrix;
-import com.android.wm.shell.common.magnetictarget.MagnetizedObject.MagneticTarget;
+import com.android.wm.shell.shared.animation.Interpolators;
 import com.android.wm.shell.shared.animation.PhysicsAnimator;
+import com.android.wm.shell.shared.magnetictarget.MagnetizedObject.MagneticTarget;
 
 /**
  * Helper class to animate a {@link BubbleBarExpandedView} on a bubble.
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedViewDragController.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedViewDragController.kt
index d45ed0d..eeb5c94 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedViewDragController.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedViewDragController.kt
@@ -22,7 +22,7 @@
 import com.android.wm.shell.bubbles.BubblePositioner
 import com.android.wm.shell.common.bubbles.DismissView
 import com.android.wm.shell.common.bubbles.RelativeTouchListener
-import com.android.wm.shell.common.magnetictarget.MagnetizedObject
+import com.android.wm.shell.shared.magnetictarget.MagnetizedObject
 
 /** Controller for handling drag interactions with [BubbleBarExpandedView] */
 @SuppressLint("ClickableViewAccessibility")
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerView.java
index 9fa85cf..ac42453 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerView.java
@@ -16,8 +16,8 @@
 
 package com.android.wm.shell.bubbles.bar;
 
-import static com.android.wm.shell.animation.Interpolators.ALPHA_IN;
-import static com.android.wm.shell.animation.Interpolators.ALPHA_OUT;
+import static com.android.wm.shell.shared.animation.Interpolators.ALPHA_IN;
+import static com.android.wm.shell.shared.animation.Interpolators.ALPHA_OUT;
 import static com.android.wm.shell.bubbles.Bubbles.DISMISS_USER_GESTURE;
 
 import android.annotation.Nullable;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayController.java
index dcbc72a..7c51a69 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayController.java
@@ -23,6 +23,7 @@
 import android.hardware.display.DisplayManager;
 import android.os.RemoteException;
 import android.util.ArraySet;
+import android.util.Size;
 import android.util.Slog;
 import android.util.SparseArray;
 import android.view.Display;
@@ -193,8 +194,8 @@
 
 
     /** Called when a display rotate requested. */
-    public void onDisplayRotateRequested(WindowContainerTransaction wct, int displayId,
-            int fromRotation, int toRotation) {
+    public void onDisplayChangeRequested(WindowContainerTransaction wct, int displayId,
+            Rect startAbsBounds, Rect endAbsBounds, int fromRotation, int toRotation) {
         synchronized (mDisplays) {
             final DisplayRecord dr = mDisplays.get(displayId);
             if (dr == null) {
@@ -203,6 +204,13 @@
             }
 
             if (dr.mDisplayLayout != null) {
+                if (endAbsBounds != null) {
+                    // If there is a change in the display dimensions update the layout as well;
+                    // note that endAbsBounds should ignore any potential rotation changes, so
+                    // we still need to rotate the layout after if needed.
+                    dr.mDisplayLayout.resizeTo(dr.mContext.getResources(),
+                            new Size(endAbsBounds.width(), endAbsBounds.height()));
+                }
                 dr.mDisplayLayout.rotateTo(dr.mContext.getResources(), toRotation);
             }
 
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayImeController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayImeController.java
index 3fa51a9..5b01a0d 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayImeController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayImeController.java
@@ -52,6 +52,7 @@
 import androidx.annotation.VisibleForTesting;
 
 import com.android.internal.inputmethod.SoftInputShowHideReason;
+import com.android.wm.shell.shared.TransactionPool;
 import com.android.wm.shell.sysui.ShellInit;
 
 import java.util.ArrayList;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayLayout.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayLayout.java
index 84e32a2..b6a1686 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayLayout.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/DisplayLayout.java
@@ -35,6 +35,7 @@
 import android.os.SystemProperties;
 import android.provider.Settings;
 import android.util.DisplayMetrics;
+import android.util.Size;
 import android.view.Display;
 import android.view.DisplayCutout;
 import android.view.DisplayInfo;
@@ -244,6 +245,16 @@
         recalcInsets(res);
     }
 
+    /**
+     * Update the dimensions of this layout.
+     */
+    public void resizeTo(Resources res, Size displaySize) {
+        mWidth = displaySize.getWidth();
+        mHeight = displaySize.getHeight();
+
+        recalcInsets(res);
+    }
+
     /** Get this layout's non-decor insets. */
     public Rect nonDecorInsets() {
         return mNonDecorInsets;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/SyncTransactionQueue.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/SyncTransactionQueue.java
index f792392..bcd40a9 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/SyncTransactionQueue.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/SyncTransactionQueue.java
@@ -29,6 +29,7 @@
 import android.window.WindowOrganizer;
 
 import com.android.internal.protolog.ProtoLog;
+import com.android.wm.shell.shared.TransactionPool;
 import com.android.wm.shell.transition.LegacyTransitions;
 
 import java.util.ArrayList;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/DividerHandleView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/DividerHandleView.java
index 999da24..bdbd4cf 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/DividerHandleView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/DividerHandleView.java
@@ -32,7 +32,7 @@
 import android.view.View;
 
 import com.android.wm.shell.R;
-import com.android.wm.shell.animation.Interpolators;
+import com.android.wm.shell.shared.animation.Interpolators;
 
 /**
  * View for the handle in the docked stack divider.
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/DividerSnapAlgorithm.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/DividerSnapAlgorithm.java
index 2e1789a..8156a9c 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/DividerSnapAlgorithm.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/DividerSnapAlgorithm.java
@@ -19,14 +19,14 @@
 import static android.view.WindowManager.DOCKED_LEFT;
 import static android.view.WindowManager.DOCKED_RIGHT;
 
-import static com.android.wm.shell.common.split.SplitScreenConstants.SNAP_TO_30_70;
-import static com.android.wm.shell.common.split.SplitScreenConstants.SNAP_TO_50_50;
-import static com.android.wm.shell.common.split.SplitScreenConstants.SNAP_TO_70_30;
-import static com.android.wm.shell.common.split.SplitScreenConstants.SNAP_TO_END_AND_DISMISS;
-import static com.android.wm.shell.common.split.SplitScreenConstants.SNAP_TO_MINIMIZE;
-import static com.android.wm.shell.common.split.SplitScreenConstants.SNAP_TO_NONE;
-import static com.android.wm.shell.common.split.SplitScreenConstants.SNAP_TO_START_AND_DISMISS;
-import static com.android.wm.shell.common.split.SplitScreenConstants.SnapPosition;
+import static com.android.wm.shell.shared.split.SplitScreenConstants.SNAP_TO_30_70;
+import static com.android.wm.shell.shared.split.SplitScreenConstants.SNAP_TO_50_50;
+import static com.android.wm.shell.shared.split.SplitScreenConstants.SNAP_TO_70_30;
+import static com.android.wm.shell.shared.split.SplitScreenConstants.SNAP_TO_END_AND_DISMISS;
+import static com.android.wm.shell.shared.split.SplitScreenConstants.SNAP_TO_MINIMIZE;
+import static com.android.wm.shell.shared.split.SplitScreenConstants.SNAP_TO_NONE;
+import static com.android.wm.shell.shared.split.SplitScreenConstants.SNAP_TO_START_AND_DISMISS;
+import static com.android.wm.shell.shared.split.SplitScreenConstants.SnapPosition;
 
 import android.content.res.Resources;
 import android.graphics.Rect;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/DividerView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/DividerView.java
index 1bc1795..e7848e2 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/DividerView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/DividerView.java
@@ -56,8 +56,9 @@
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.protolog.ProtoLog;
 import com.android.wm.shell.R;
-import com.android.wm.shell.animation.Interpolators;
 import com.android.wm.shell.protolog.ShellProtoLogGroup;
+import com.android.wm.shell.shared.animation.Interpolators;
+import com.android.wm.shell.shared.desktopmode.DesktopModeStatus;
 
 /**
  * Divider for multi window splits.
@@ -228,7 +229,9 @@
                 : R.dimen.split_divider_handle_region_width);
         mHandleRegionHeight = getResources().getDimensionPixelSize(isLeftRightSplit
                 ? R.dimen.split_divider_handle_region_width
-                : R.dimen.split_divider_handle_region_height);
+                : DesktopModeStatus.canEnterDesktopMode(mContext)
+                        ? R.dimen.desktop_mode_portrait_split_divider_handle_region_height
+                        : R.dimen.split_divider_handle_region_height);
     }
 
     void onInsetsChanged(InsetsState insetsState, boolean animate) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitDecorManager.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitDecorManager.java
index e2988bc..7175e36 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitDecorManager.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitDecorManager.java
@@ -25,8 +25,8 @@
 
 import static com.android.wm.shell.common.split.SplitLayout.BEHIND_APP_VEIL_LAYER;
 import static com.android.wm.shell.common.split.SplitLayout.FRONT_APP_VEIL_LAYER;
-import static com.android.wm.shell.common.split.SplitScreenConstants.FADE_DURATION;
-import static com.android.wm.shell.common.split.SplitScreenConstants.VEIL_DELAY_DURATION;
+import static com.android.wm.shell.shared.split.SplitScreenConstants.FADE_DURATION;
+import static com.android.wm.shell.shared.split.SplitScreenConstants.VEIL_DELAY_DURATION;
 
 import android.animation.Animator;
 import android.animation.AnimatorListenerAdapter;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java
index 0e050694..2a934cb 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java
@@ -26,13 +26,15 @@
 
 import static com.android.internal.jank.InteractionJankMonitor.CUJ_SPLIT_SCREEN_DOUBLE_TAP_DIVIDER;
 import static com.android.internal.jank.InteractionJankMonitor.CUJ_SPLIT_SCREEN_RESIZE;
-import static com.android.wm.shell.animation.Interpolators.DIM_INTERPOLATOR;
-import static com.android.wm.shell.animation.Interpolators.SLOWDOWN_INTERPOLATOR;
-import static com.android.wm.shell.common.split.SplitScreenConstants.SNAP_TO_END_AND_DISMISS;
-import static com.android.wm.shell.common.split.SplitScreenConstants.SNAP_TO_START_AND_DISMISS;
-import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_BOTTOM_OR_RIGHT;
-import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_TOP_OR_LEFT;
-import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_UNDEFINED;
+import static com.android.wm.shell.shared.animation.Interpolators.DIM_INTERPOLATOR;
+import static com.android.wm.shell.shared.animation.Interpolators.EMPHASIZED;
+import static com.android.wm.shell.shared.animation.Interpolators.LINEAR;
+import static com.android.wm.shell.shared.animation.Interpolators.SLOWDOWN_INTERPOLATOR;
+import static com.android.wm.shell.shared.split.SplitScreenConstants.SNAP_TO_END_AND_DISMISS;
+import static com.android.wm.shell.shared.split.SplitScreenConstants.SNAP_TO_START_AND_DISMISS;
+import static com.android.wm.shell.shared.split.SplitScreenConstants.SPLIT_POSITION_BOTTOM_OR_RIGHT;
+import static com.android.wm.shell.shared.split.SplitScreenConstants.SPLIT_POSITION_TOP_OR_LEFT;
+import static com.android.wm.shell.shared.split.SplitScreenConstants.SPLIT_POSITION_UNDEFINED;
 import static com.android.wm.shell.splitscreen.SplitScreenController.EXIT_REASON_DRAG_DIVIDER;
 
 import android.animation.Animator;
@@ -65,15 +67,15 @@
 import com.android.internal.protolog.ProtoLog;
 import com.android.wm.shell.R;
 import com.android.wm.shell.ShellTaskOrganizer;
-import com.android.wm.shell.animation.Interpolators;
 import com.android.wm.shell.common.DisplayController;
 import com.android.wm.shell.common.DisplayImeController;
 import com.android.wm.shell.common.DisplayInsetsController;
 import com.android.wm.shell.common.DisplayLayout;
 import com.android.wm.shell.common.pip.PipUtils;
-import com.android.wm.shell.common.split.SplitScreenConstants.PersistentSnapPosition;
-import com.android.wm.shell.common.split.SplitScreenConstants.SnapPosition;
-import com.android.wm.shell.common.split.SplitScreenConstants.SplitPosition;
+import com.android.wm.shell.shared.animation.Interpolators;
+import com.android.wm.shell.shared.split.SplitScreenConstants.PersistentSnapPosition;
+import com.android.wm.shell.shared.split.SplitScreenConstants.SnapPosition;
+import com.android.wm.shell.shared.split.SplitScreenConstants.SplitPosition;
 import com.android.wm.shell.protolog.ShellProtoLogGroup;
 import com.android.wm.shell.splitscreen.StageTaskListener;
 
@@ -813,7 +815,9 @@
         float growPortion = 1 - shrinkPortion;
 
         ValueAnimator animator = ValueAnimator.ofFloat(0, 1);
-        animator.setInterpolator(Interpolators.EMPHASIZED);
+        // Set the base animation to proceed linearly. Each component of the animation (movement,
+        // shrinking, growing) overrides it with a different interpolator later.
+        animator.setInterpolator(LINEAR);
         animator.addUpdateListener(animation -> {
             if (leash == null) return;
             if (roundCorners) {
@@ -822,10 +826,11 @@
             }
 
             final float progress = (float) animation.getAnimatedValue();
-            float instantaneousX = tempStart.left + progress * diffX;
-            float instantaneousY = tempStart.top + progress * diffY;
-            int width = (int) (tempStart.width() + progress * diffWidth);
-            int height = (int) (tempStart.height() + progress * diffHeight);
+            final float moveProgress = EMPHASIZED.getInterpolation(progress);
+            float instantaneousX = tempStart.left + moveProgress * diffX;
+            float instantaneousY = tempStart.top + moveProgress * diffY;
+            int width = (int) (tempStart.width() + moveProgress * diffWidth);
+            int height = (int) (tempStart.height() + moveProgress * diffHeight);
 
             if (isGoingBehind) {
                 float shrinkDiffX; // the position adjustments needed for this frame
@@ -897,8 +902,8 @@
                             taskInfo, mTempRect, t, isGoingBehind, leash, 0, 0);
                 }
             } else {
-                final int diffOffsetX = (int) (progress * offsetX);
-                final int diffOffsetY = (int) (progress * offsetY);
+                final int diffOffsetX = (int) (moveProgress * offsetX);
+                final int diffOffsetY = (int) (moveProgress * offsetY);
                 t.setPosition(leash, instantaneousX + diffOffsetX, instantaneousY + diffOffsetY);
                 mTempRect.set(0, 0, width, height);
                 mTempRect.offsetTo(-diffOffsetX, -diffOffsetY);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitScreenUtils.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitScreenUtils.java
index f9259e7..bdbcb46 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitScreenUtils.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitScreenUtils.java
@@ -16,34 +16,25 @@
 
 package com.android.wm.shell.common.split;
 
-import static android.content.pm.LauncherApps.ShortcutQuery.FLAG_MATCH_ALL_KINDS_WITH_ALL_PINNED;
-
-import static com.android.wm.shell.common.split.SplitScreenConstants.CONTROLLED_ACTIVITY_TYPES;
-import static com.android.wm.shell.common.split.SplitScreenConstants.CONTROLLED_WINDOWING_MODES;
-import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_BOTTOM_OR_RIGHT;
-import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_TOP_OR_LEFT;
-import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_UNDEFINED;
+import static com.android.wm.shell.shared.split.SplitScreenConstants.CONTROLLED_ACTIVITY_TYPES;
+import static com.android.wm.shell.shared.split.SplitScreenConstants.CONTROLLED_WINDOWING_MODES;
+import static com.android.wm.shell.shared.split.SplitScreenConstants.SPLIT_POSITION_BOTTOM_OR_RIGHT;
+import static com.android.wm.shell.shared.split.SplitScreenConstants.SPLIT_POSITION_TOP_OR_LEFT;
+import static com.android.wm.shell.shared.split.SplitScreenConstants.SPLIT_POSITION_UNDEFINED;
 
 import android.app.ActivityManager;
 import android.app.PendingIntent;
-import android.content.ComponentName;
 import android.content.Intent;
-import android.content.pm.LauncherApps;
-import android.content.pm.ShortcutInfo;
 import android.content.res.Configuration;
 import android.content.res.Resources;
 import android.graphics.Rect;
-import android.os.UserHandle;
 
-import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 
 import com.android.internal.util.ArrayUtils;
 import com.android.wm.shell.Flags;
 import com.android.wm.shell.ShellTaskOrganizer;
-
-import java.util.Arrays;
-import java.util.List;
+import com.android.wm.shell.shared.split.SplitScreenConstants;
 
 /** Helper utility class for split screen components to use. */
 public class SplitScreenUtils {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/api/CompatUIComponent.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/api/CompatUIComponent.kt
index 9ee50ac..831b331 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/api/CompatUIComponent.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/api/CompatUIComponent.kt
@@ -16,28 +16,222 @@
 
 package com.android.wm.shell.compatui.api
 
-import android.util.Log
+import android.content.Context
+import android.content.res.Configuration
+import android.graphics.PixelFormat
+import android.graphics.Point
+import android.os.Binder
+import android.view.IWindow
+import android.view.SurfaceControl
+import android.view.SurfaceControlViewHost
+import android.view.SurfaceSession
+import android.view.View
+import android.view.WindowManager
+import android.view.WindowlessWindowManager
+import com.android.wm.shell.common.DisplayLayout
+import com.android.wm.shell.common.SyncTransactionQueue
 
 /**
  * The component created after a {@link CompatUISpec} definition
  */
 class CompatUIComponent(
     private val spec: CompatUISpec,
-    private val id: String
+    private val id: String,
+    private var context: Context,
+    private val state: CompatUIState,
+    private var compatUIInfo: CompatUIInfo,
+    private val syncQueue: SyncTransactionQueue,
+    private var displayLayout: DisplayLayout?
+) : WindowlessWindowManager(
+    compatUIInfo.taskInfo.configuration,
+    /* rootSurface */
+    null,
+    /* hostInputToken */
+    null
 ) {
 
+    private val tag
+        get() = "CompatUI {id = $id}"
+
+    private var leash: SurfaceControl? = null
+
+    private var layout: View? = null
+
+    /**
+     * Utility class for adding and releasing a View hierarchy for this [ ] to `mLeash`.
+     */
+    protected var viewHost: SurfaceControlViewHost? = null
+
+    override fun setConfiguration(configuration: Configuration?) {
+        super.setConfiguration(configuration)
+        configuration?.let {
+            context = context.createConfigurationContext(it)
+        }
+    }
+
     /**
      * Invoked every time a new CompatUIInfo comes from core
      * @param newInfo The new CompatUIInfo object
-     * @param sharedState The state shared between all the component
      */
-    fun update(newInfo: CompatUIInfo, state: CompatUIState) {
-        // TODO(b/322817374): To be removed when the implementation is provided.
-        Log.d("CompatUIComponent", "update() newInfo: $newInfo state:$state")
+    fun update(newInfo: CompatUIInfo) {
+        updateComponentState(newInfo, state.stateForComponent(id))
+        updateUI(state)
     }
 
     fun release() {
-        // TODO(b/322817374): To be removed when the implementation is provided.
-        Log.d("CompatUIComponent", "release()")
+        spec.log("$tag releasing.....")
+        // Implementation empty
+        // Hiding before releasing to avoid flickering when transitioning to the Home screen.
+        layout?.visibility = View.GONE
+        layout = null
+        spec.layout.viewReleaser()
+        spec.log("$tag layout releaser invoked!")
+        viewHost?.release()
+        viewHost = null
+        leash?.run {
+            val localLeash: SurfaceControl = this
+            syncQueue.runInSync { t: SurfaceControl.Transaction ->
+                t.remove(
+                    localLeash
+                )
+            }
+            leash = null
+            spec.log("$tag leash removed")
+        }
+        spec.log("$tag released")
     }
-}
\ No newline at end of file
+
+    override fun getParentSurface(
+        window: IWindow,
+        attrs: WindowManager.LayoutParams
+    ): SurfaceControl? {
+        val className = javaClass.simpleName
+        val builder = SurfaceControl.Builder(SurfaceSession())
+                .setContainerLayer()
+                .setName(className + "Leash")
+                .setHidden(false)
+                .setCallsite("$className#attachToParentSurface")
+        attachToParentSurface(builder)
+        leash = builder.build()
+        initSurface(leash)
+        return leash
+    }
+
+    fun attachToParentSurface(builder: SurfaceControl.Builder) {
+        compatUIInfo.listener?.attachChildSurfaceToTask(compatUIInfo.taskInfo.taskId, builder)
+    }
+
+    fun initLayout(newCompatUIInfo: CompatUIInfo) {
+        compatUIInfo = newCompatUIInfo
+        spec.log("$tag updating...")
+        check(viewHost == null) { "A UI has already been created with this window manager." }
+        val componentState: CompatUIComponentState? = state.stateForComponent(id)
+        spec.log("$tag state: $componentState")
+        // We inflate the layout
+        layout = spec.layout.viewBuilder(context, compatUIInfo, componentState)
+        spec.log("$tag layout: $layout")
+        viewHost = createSurfaceViewHost().apply {
+            spec.log("$tag adding view $layout to host $this")
+            setView(layout!!, getWindowLayoutParams())
+        }
+        updateSurfacePosition()
+    }
+
+    /** Creates a [SurfaceControlViewHost] for this window manager.  */
+    fun createSurfaceViewHost(): SurfaceControlViewHost =
+        SurfaceControlViewHost(context, context.display, this, javaClass.simpleName)
+
+    fun relayout() {
+        spec.log("$tag relayout...")
+        viewHost?.run {
+            relayout(getWindowLayoutParams())
+            updateSurfacePosition()
+        }
+    }
+
+    protected fun updateSurfacePosition() {
+        spec.log("$tag updateSurfacePosition on layout $layout")
+        layout?.let {
+            updateSurfacePosition(
+                spec.layout.positionFactory(
+                    it,
+                    compatUIInfo,
+                    state.sharedState,
+                    state.stateForComponent(id)
+                )
+            )
+        }
+    }
+
+    protected fun getWindowLayoutParams(width: Int, height: Int): WindowManager.LayoutParams {
+        // Cannot be wrap_content as this determines the actual window size
+        val winParams =
+            WindowManager.LayoutParams(
+                width,
+                height,
+                WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY,
+                spec.layout.layoutParamFlags,
+                PixelFormat.TRANSLUCENT
+            )
+        winParams.token = Binder()
+        winParams.title = javaClass.simpleName + compatUIInfo.taskInfo.taskId
+        winParams.privateFlags =
+            winParams.privateFlags or (WindowManager.LayoutParams.PRIVATE_FLAG_NO_MOVE_ANIMATION
+                    or WindowManager.LayoutParams.PRIVATE_FLAG_TRUSTED_OVERLAY)
+        spec.log("$tag getWindowLayoutParams $winParams")
+        return winParams
+    }
+
+    /** Gets the layout params.  */
+    protected fun getWindowLayoutParams(): WindowManager.LayoutParams =
+        layout?.run {
+            measure(View.MeasureSpec.UNSPECIFIED, View.MeasureSpec.UNSPECIFIED)
+            spec.log(
+                "$tag getWindowLayoutParams size: ${measuredWidth}x$measuredHeight"
+            )
+            return getWindowLayoutParams(measuredWidth, measuredHeight)
+        } ?: WindowManager.LayoutParams()
+
+    protected fun updateSurfacePosition(position: Point) {
+        spec.log("$tag updateSurfacePosition on leash $leash")
+        leash?.run {
+            syncQueue.runInSync { t: SurfaceControl.Transaction ->
+                if (!isValid) {
+                    spec.log("$tag The leash has been released.")
+                    return@runInSync
+                }
+                spec.log("$tag settings position  $position")
+                t.setPosition(this, position.x.toFloat(), position.y.toFloat())
+            }
+        }
+    }
+
+    private fun updateComponentState(
+        newInfo: CompatUIInfo,
+        componentState: CompatUIComponentState?
+    ) {
+        spec.log("$tag component state updating.... $componentState")
+        compatUIInfo = newInfo
+    }
+
+    private fun updateUI(state: CompatUIState) {
+        spec.log("$tag updating ui")
+        setConfiguration(compatUIInfo.taskInfo.configuration)
+        val componentState: CompatUIComponentState? = state.stateForComponent(id)
+        layout?.run {
+            spec.log("$tag viewBinder execution...")
+            spec.layout.viewBinder(this, compatUIInfo, state.sharedState, componentState)
+            relayout()
+        }
+    }
+
+    private fun initSurface(leash: SurfaceControl?) {
+        syncQueue.runInSync { t: SurfaceControl.Transaction ->
+            if (leash == null || !leash.isValid) {
+                spec.log("$tag The leash has been released.")
+                return@runInSync
+            }
+            t.setLayer(leash, spec.layout.zOrder)
+        }
+    }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/api/CompatUIComponentFactory.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/api/CompatUIComponentFactory.kt
new file mode 100644
index 0000000..55821ff
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/api/CompatUIComponentFactory.kt
@@ -0,0 +1,30 @@
+/*
+ * 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.wm.shell.compatui.api
+
+/**
+ * Abstracts the component responsible for the creation of a component
+ */
+interface CompatUIComponentFactory {
+
+    fun create(
+        spec: CompatUISpec,
+        compId: String,
+        state: CompatUIState,
+        compatUIInfo: CompatUIInfo,
+    ): CompatUIComponent
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/api/CompatUIComponentState.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/api/CompatUIComponentState.kt
index dcaea00..ec21924 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/api/CompatUIComponentState.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/api/CompatUIComponentState.kt
@@ -18,7 +18,6 @@
 
 /**
  * Abstraction of all the component specific state. Each
- * component can create its own state implementing this
- * tagging interface.
+ * component can create its own state implementing this interface.
  */
-interface CompatUIComponentState
\ No newline at end of file
+interface CompatUIComponentState
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/api/CompatUISpec.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/api/CompatUISpec.kt
index 022906c..de400f4 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/api/CompatUISpec.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/api/CompatUISpec.kt
@@ -16,6 +16,11 @@
 
 package com.android.wm.shell.compatui.api
 
+import android.content.Context
+import android.graphics.Point
+import android.view.View
+import android.view.WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
+import android.view.WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
 import com.android.internal.protolog.ProtoLog
 import com.android.wm.shell.protolog.ShellProtoLogGroup
 
@@ -39,6 +44,28 @@
 )
 
 /**
+ * Layout configuration
+ */
+data class CompatUILayout(
+    val zOrder: Int = 0,
+    val layoutParamFlags: Int = FLAG_NOT_FOCUSABLE or FLAG_NOT_TOUCH_MODAL,
+    val viewBuilder: (Context, CompatUIInfo, CompatUIComponentState?) -> View,
+    val viewBinder: (
+        View,
+        CompatUIInfo,
+        CompatUISharedState,
+        CompatUIComponentState?
+    ) -> Unit = { _, _, _, _ -> },
+    val positionFactory: (
+        View,
+        CompatUIInfo,
+        CompatUISharedState,
+        CompatUIComponentState?
+    ) -> Point,
+    val viewReleaser: () -> Unit = {}
+)
+
+/**
  * Describes each compat ui component to the framework.
  */
 class CompatUISpec(
@@ -47,5 +74,7 @@
     // unique component identifier in the system.
     val name: String,
     // The lifecycle definition
-    val lifecycle: CompatUILifecyclePredicates
+    val lifecycle: CompatUILifecyclePredicates,
+    // The layout definition
+    val layout: CompatUILayout
 )
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/components/RestartButtonSpec.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/components/RestartButtonSpec.kt
new file mode 100644
index 0000000..e18cc0e
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/components/RestartButtonSpec.kt
@@ -0,0 +1,58 @@
+/*
+ * 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.wm.shell.compatui.components
+
+import android.annotation.SuppressLint
+import android.graphics.Point
+import android.view.LayoutInflater
+import android.view.View
+import android.window.TaskConstants
+import com.android.wm.shell.R
+import com.android.wm.shell.compatui.api.CompatUILayout
+import com.android.wm.shell.compatui.api.CompatUILifecyclePredicates
+import com.android.wm.shell.compatui.api.CompatUISpec
+
+/**
+ * CompatUISpec for the Restart Button
+ */
+@SuppressLint("InflateParams")
+val RestartButtonSpec = CompatUISpec(
+    name = "restartButton",
+    lifecycle = CompatUILifecyclePredicates(
+        creationPredicate = { info, _ ->
+            info.taskInfo.appCompatTaskInfo.isTopActivityInSizeCompat
+        },
+        removalPredicate = { info, _, _ ->
+            !info.taskInfo.appCompatTaskInfo.isTopActivityInSizeCompat
+        }
+    ),
+    layout = CompatUILayout(
+        zOrder = TaskConstants.TASK_CHILD_LAYER_COMPAT_UI + 10,
+        viewBuilder = { ctx, _, _ ->
+            LayoutInflater.from(ctx).inflate(
+                R.layout.compat_ui_restart_button_layout,
+                null
+            )
+        },
+        viewBinder = { view, _, _, _ ->
+            view.visibility = View.VISIBLE
+            view.findViewById<View>(R.id.size_compat_restart_button)?.visibility = View.VISIBLE
+        },
+        // TODO(b/360288344): Calculate right position from stable bounds
+        positionFactory = { _, _, _, _ -> Point(500, 500) }
+    )
+)
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/impl/DefaultCompatUIComponentFactory.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/impl/DefaultCompatUIComponentFactory.kt
new file mode 100644
index 0000000..4eea6a3
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/impl/DefaultCompatUIComponentFactory.kt
@@ -0,0 +1,51 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.compatui.impl
+
+import android.content.Context
+import com.android.wm.shell.common.DisplayController
+import com.android.wm.shell.common.SyncTransactionQueue
+import com.android.wm.shell.compatui.api.CompatUIComponent
+import com.android.wm.shell.compatui.api.CompatUIComponentFactory
+import com.android.wm.shell.compatui.api.CompatUIInfo
+import com.android.wm.shell.compatui.api.CompatUISpec
+import com.android.wm.shell.compatui.api.CompatUIState
+
+/**
+ * Default {@link CompatUIComponentFactory } implementation
+ */
+class DefaultCompatUIComponentFactory(
+    private val context: Context,
+    private val syncQueue: SyncTransactionQueue,
+    private val displayController: DisplayController
+) : CompatUIComponentFactory {
+    override fun create(
+        spec: CompatUISpec,
+        compId: String,
+        state: CompatUIState,
+        compatUIInfo: CompatUIInfo
+    ): CompatUIComponent =
+        CompatUIComponent(
+            spec,
+            compId,
+            context,
+            state,
+            compatUIInfo,
+            syncQueue,
+            displayController.getDisplayLayout(compatUIInfo.taskInfo.displayId)
+        )
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/impl/DefaultCompatUIHandler.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/impl/DefaultCompatUIHandler.kt
index a7d1b42..02db85a 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/impl/DefaultCompatUIHandler.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/impl/DefaultCompatUIHandler.kt
@@ -16,7 +16,8 @@
 
 package com.android.wm.shell.compatui.impl
 
-import com.android.wm.shell.compatui.api.CompatUIComponent
+import com.android.wm.shell.common.ShellExecutor
+import com.android.wm.shell.compatui.api.CompatUIComponentFactory
 import com.android.wm.shell.compatui.api.CompatUIComponentIdGenerator
 import com.android.wm.shell.compatui.api.CompatUIEvent
 import com.android.wm.shell.compatui.api.CompatUIHandler
@@ -24,7 +25,6 @@
 import com.android.wm.shell.compatui.api.CompatUIRepository
 import com.android.wm.shell.compatui.api.CompatUIState
 import java.util.function.Consumer
-import java.util.function.IntSupplier
 
 /**
  * Default implementation of {@link CompatUIHandler} to handle CompatUI components
@@ -32,7 +32,9 @@
 class DefaultCompatUIHandler(
     private val compatUIRepository: CompatUIRepository,
     private val compatUIState: CompatUIState,
-    private val componentIdGenerator: CompatUIComponentIdGenerator
+    private val componentIdGenerator: CompatUIComponentIdGenerator,
+    private val componentFactory: CompatUIComponentFactory,
+    private val executor: ShellExecutor
 ) : CompatUIHandler {
 
     private var compatUIEventSender: Consumer<CompatUIEvent>? = null
@@ -41,23 +43,36 @@
         compatUIRepository.iterateOn { spec ->
             // We get the identifier for the component depending on the task and spec
             val componentId = componentIdGenerator.generateId(compatUIInfo, spec)
-            // We check in the state if the component already exists
-            var comp = compatUIState.getUIComponent(componentId)
-            if (comp == null) {
+            spec.log("Evaluating component $componentId")
+            // We check in the state if the component does not yet exist
+            var component = compatUIState.getUIComponent(componentId)
+            if (component == null) {
+                spec.log("Component $componentId not present")
                 // We evaluate the predicate
                 if (spec.lifecycle.creationPredicate(compatUIInfo, compatUIState.sharedState)) {
+                    spec.log("Component $componentId should be created")
                     // We create the component and store in the
                     // global state
-                    comp = CompatUIComponent(spec, componentId)
+                    component =
+                        componentFactory.create(spec, componentId, compatUIState, compatUIInfo)
+                    spec.log("Component $componentId created $component")
                     // We initialize the state for the component
                     val compState = spec.lifecycle.stateBuilder(
                         compatUIInfo,
                         compatUIState.sharedState
                     )
-                    compatUIState.registerUIComponent(componentId, comp, compState)
+                    spec.log("Component $componentId initial state $compState")
+                    compatUIState.registerUIComponent(componentId, component, compState)
+                    spec.log("Component $componentId registered")
+                    // We initialize the layout for the component
+                    component.initLayout(compatUIInfo)
+                    spec.log("Component $componentId layout created")
                     // Now we can invoke the update passing the shared state and
                     // the state specific to the component
-                    comp.update(compatUIInfo, compatUIState)
+                    executor.execute {
+                        component.update(compatUIInfo)
+                        spec.log("Component $componentId updated with $compatUIInfo")
+                    }
                 }
             } else {
                 // The component is present. We check if we need to remove it
@@ -66,13 +81,18 @@
                         compatUIState.sharedState,
                         compatUIState.stateForComponent(componentId)
                     )) {
+                    spec.log("Component $componentId should be removed")
                     // We clean the component
-                    comp.release()
-                    // We remove the component
+                    component.release()
+                    spec.log("Component $componentId released")
                     compatUIState.unregisterUIComponent(componentId)
+                    spec.log("Component $componentId removed from registry")
                 } else {
-                    // The component exists so we need to invoke the update methods
-                    comp.update(compatUIInfo, compatUIState)
+                    executor.execute {
+                        // The component exists so we need to invoke the update methods
+                        component.update(compatUIInfo)
+                        spec.log("Component $componentId updated with $compatUIInfo")
+                    }
                 }
             }
         }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/TvWMShellModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/TvWMShellModule.java
index 0110937..33e4fd8 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/TvWMShellModule.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/TvWMShellModule.java
@@ -30,9 +30,9 @@
 import com.android.wm.shell.common.ShellExecutor;
 import com.android.wm.shell.common.SyncTransactionQueue;
 import com.android.wm.shell.common.SystemWindows;
-import com.android.wm.shell.common.TransactionPool;
 import com.android.wm.shell.dagger.pip.TvPipModule;
 import com.android.wm.shell.recents.RecentTasksController;
+import com.android.wm.shell.shared.TransactionPool;
 import com.android.wm.shell.shared.annotations.ShellMainThread;
 import com.android.wm.shell.splitscreen.SplitScreenController;
 import com.android.wm.shell.splitscreen.tv.TvSplitScreenController;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java
index 04cd225..98536bf 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java
@@ -62,7 +62,6 @@
 import com.android.wm.shell.common.SystemWindows;
 import com.android.wm.shell.common.TabletopModeController;
 import com.android.wm.shell.common.TaskStackListenerImpl;
-import com.android.wm.shell.common.TransactionPool;
 import com.android.wm.shell.common.pip.PhonePipKeepClearAlgorithm;
 import com.android.wm.shell.common.pip.PhoneSizeSpecSource;
 import com.android.wm.shell.common.pip.PipBoundsAlgorithm;
@@ -77,10 +76,13 @@
 import com.android.wm.shell.compatui.CompatUIController;
 import com.android.wm.shell.compatui.CompatUIShellCommandHandler;
 import com.android.wm.shell.compatui.CompatUIStatusManager;
+import com.android.wm.shell.compatui.api.CompatUIComponentFactory;
 import com.android.wm.shell.compatui.api.CompatUIComponentIdGenerator;
 import com.android.wm.shell.compatui.api.CompatUIHandler;
 import com.android.wm.shell.compatui.api.CompatUIRepository;
 import com.android.wm.shell.compatui.api.CompatUIState;
+import com.android.wm.shell.compatui.components.RestartButtonSpecKt;
+import com.android.wm.shell.compatui.impl.DefaultCompatUIComponentFactory;
 import com.android.wm.shell.compatui.impl.DefaultCompatUIHandler;
 import com.android.wm.shell.compatui.impl.DefaultCompatUIRepository;
 import com.android.wm.shell.compatui.impl.DefaultComponentIdGenerator;
@@ -102,6 +104,7 @@
 import com.android.wm.shell.recents.RecentsTransitionHandler;
 import com.android.wm.shell.recents.TaskStackTransitionObserver;
 import com.android.wm.shell.shared.ShellTransitions;
+import com.android.wm.shell.shared.TransactionPool;
 import com.android.wm.shell.shared.annotations.ShellAnimationThread;
 import com.android.wm.shell.shared.annotations.ShellBackgroundThread;
 import com.android.wm.shell.shared.annotations.ShellMainThread;
@@ -260,13 +263,15 @@
             CompatUIRepository compatUIRepository,
             @NonNull CompatUIState compatUIState,
             @NonNull CompatUIComponentIdGenerator componentIdGenerator,
+            @NonNull CompatUIComponentFactory compatUIComponentFactory,
             CompatUIStatusManager compatUIStatusManager) {
         if (!context.getResources().getBoolean(R.bool.config_enableCompatUIController)) {
             return Optional.empty();
         }
         if (Flags.appCompatUiFramework()) {
-            return Optional.of(new DefaultCompatUIHandler(compatUIRepository, compatUIState,
-                    componentIdGenerator));
+            return Optional.of(
+                    new DefaultCompatUIHandler(compatUIRepository, compatUIState,
+                            componentIdGenerator, compatUIComponentFactory, mainExecutor));
         }
         return Optional.of(
                 new CompatUIController(
@@ -308,6 +313,15 @@
 
     @WMSingleton
     @Provides
+    static CompatUIComponentFactory provideCompatUIComponentFactory(
+            @NonNull Context context,
+            @NonNull SyncTransactionQueue syncQueue,
+            @NonNull DisplayController displayController) {
+        return new DefaultCompatUIComponentFactory(context, syncQueue, displayController);
+    }
+
+    @WMSingleton
+    @Provides
     static CompatUIComponentIdGenerator provideCompatUIComponentIdGenerator() {
         return new DefaultComponentIdGenerator();
     }
@@ -315,7 +329,10 @@
     @WMSingleton
     @Provides
     static CompatUIRepository provideCompatUIRepository() {
-        return new DefaultCompatUIRepository();
+        // TODO(b/360288344) Integrate Dagger Multibinding
+        final CompatUIRepository repository = new DefaultCompatUIRepository();
+        repository.addSpec(RestartButtonSpecKt.getRestartButtonSpec());
+        return repository;
     }
 
     @WMSingleton
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 955361f..ce054a8 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
@@ -56,10 +56,10 @@
 import com.android.wm.shell.common.ShellExecutor;
 import com.android.wm.shell.common.SyncTransactionQueue;
 import com.android.wm.shell.common.TaskStackListenerImpl;
-import com.android.wm.shell.common.TransactionPool;
 import com.android.wm.shell.dagger.back.ShellBackAnimationModule;
 import com.android.wm.shell.dagger.pip.PipModule;
 import com.android.wm.shell.desktopmode.DefaultDragToDesktopTransitionHandler;
+import com.android.wm.shell.desktopmode.DesktopModeDragAndDropTransitionHandler;
 import com.android.wm.shell.desktopmode.DesktopModeEventLogger;
 import com.android.wm.shell.desktopmode.DesktopModeLoggerTransitionObserver;
 import com.android.wm.shell.desktopmode.DesktopModeTaskRepository;
@@ -84,6 +84,7 @@
 import com.android.wm.shell.pip.PipTransitionController;
 import com.android.wm.shell.recents.RecentTasksController;
 import com.android.wm.shell.recents.RecentsTransitionHandler;
+import com.android.wm.shell.shared.TransactionPool;
 import com.android.wm.shell.shared.annotations.ShellAnimationThread;
 import com.android.wm.shell.shared.annotations.ShellBackgroundThread;
 import com.android.wm.shell.shared.annotations.ShellMainThread;
@@ -164,8 +165,10 @@
             BubbleLogger logger,
             BubblePositioner positioner,
             BubbleEducationController educationController,
-            @ShellMainThread ShellExecutor mainExecutor) {
-        return new BubbleData(context, logger, positioner, educationController, mainExecutor);
+            @ShellMainThread ShellExecutor mainExecutor,
+            @ShellBackgroundThread ShellExecutor bgExecutor) {
+        return new BubbleData(context, logger, positioner, educationController, mainExecutor,
+                bgExecutor);
     }
 
     // Note: Handler needed for LauncherApps.register
@@ -198,7 +201,7 @@
             IWindowManager wmService) {
         return new BubbleController(context, shellInit, shellCommandHandler, shellController, data,
                 null /* synchronizer */, floatingContentCoordinator,
-                new BubbleDataRepository(launcherApps, mainExecutor,
+                new BubbleDataRepository(launcherApps, mainExecutor, bgExecutor,
                         new BubblePersistentRepository(context)),
                 statusBarService, windowManager, windowManagerShellWrapper, userManager,
                 launcherApps, logger, taskStackListener, organizer, positioner, displayController,
@@ -301,6 +304,7 @@
             ShellInit shellInit,
             ShellTaskOrganizer shellTaskOrganizer,
             Optional<DesktopModeTaskRepository> desktopModeTaskRepository,
+            LaunchAdjacentController launchAdjacentController,
             WindowDecorViewModel windowDecorViewModel) {
         // TODO(b/238217847): Temporarily add this check here until we can remove the dynamic
         //                    override for this controller from the base module
@@ -308,7 +312,7 @@
                 ? shellInit
                 : null;
         return new FreeformTaskListener(context, init, shellTaskOrganizer,
-                desktopModeTaskRepository, windowDecorViewModel);
+                desktopModeTaskRepository, launchAdjacentController, windowDecorViewModel);
     }
 
     @WMSingleton
@@ -556,6 +560,7 @@
             ReturnToDragStartAnimator returnToDragStartAnimator,
             EnterDesktopTaskTransitionHandler enterDesktopTransitionHandler,
             ExitDesktopTaskTransitionHandler exitDesktopTransitionHandler,
+            DesktopModeDragAndDropTransitionHandler desktopModeDragAndDropTransitionHandler,
             ToggleResizeDesktopTaskTransitionHandler toggleResizeDesktopTaskTransitionHandler,
             DragToDesktopTransitionHandler dragToDesktopTransitionHandler,
             @DynamicOverride DesktopModeTaskRepository desktopModeTaskRepository,
@@ -571,7 +576,8 @@
                 displayController, shellTaskOrganizer, syncQueue, rootTaskDisplayAreaOrganizer,
                 dragAndDropController, transitions, keyguardManager,
                 returnToDragStartAnimator, enterDesktopTransitionHandler,
-                exitDesktopTransitionHandler, toggleResizeDesktopTaskTransitionHandler,
+                exitDesktopTransitionHandler, desktopModeDragAndDropTransitionHandler,
+                toggleResizeDesktopTaskTransitionHandler,
                 dragToDesktopTransitionHandler, desktopModeTaskRepository,
                 desktopModeLoggerTransitionObserver, launchAdjacentController,
                 recentsTransitionHandler, multiInstanceHelper, mainExecutor, desktopTasksLimiter,
@@ -606,8 +612,8 @@
     @WMSingleton
     @Provides
     static ReturnToDragStartAnimator provideReturnToDragStartAnimator(
-            InteractionJankMonitor interactionJankMonitor) {
-        return new ReturnToDragStartAnimator(interactionJankMonitor);
+            Context context, InteractionJankMonitor interactionJankMonitor) {
+        return new ReturnToDragStartAnimator(context, interactionJankMonitor);
     }
 
 
@@ -653,6 +659,14 @@
 
     @WMSingleton
     @Provides
+    static DesktopModeDragAndDropTransitionHandler provideDesktopModeDragAndDropTransitionHandler(
+            Transitions transitions
+    ) {
+        return new DesktopModeDragAndDropTransitionHandler(transitions);
+    }
+
+    @WMSingleton
+    @Provides
     @DynamicOverride
     static DesktopModeTaskRepository provideDesktopModeTaskRepository() {
         return new DesktopModeTaskRepository();
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip2Module.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip2Module.java
index 06c1e68..51ce2c6 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip2Module.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip2Module.java
@@ -45,6 +45,7 @@
 import com.android.wm.shell.pip2.phone.PipTouchHandler;
 import com.android.wm.shell.pip2.phone.PipTransition;
 import com.android.wm.shell.pip2.phone.PipTransitionState;
+import com.android.wm.shell.pip2.phone.PipUiStateChangeController;
 import com.android.wm.shell.shared.annotations.ShellMainThread;
 import com.android.wm.shell.sysui.ShellCommandHandler;
 import com.android.wm.shell.sysui.ShellController;
@@ -73,10 +74,11 @@
             Optional<PipController> pipController,
             PipTouchHandler pipTouchHandler,
             @NonNull PipScheduler pipScheduler,
-            @NonNull PipTransitionState pipStackListenerController) {
+            @NonNull PipTransitionState pipStackListenerController,
+            @NonNull PipUiStateChangeController pipUiStateChangeController) {
         return new PipTransition(context, shellInit, shellTaskOrganizer, transitions,
                 pipBoundsState, null, pipBoundsAlgorithm, pipScheduler,
-                pipStackListenerController);
+                pipStackListenerController, pipUiStateChangeController);
     }
 
     @WMSingleton
@@ -181,4 +183,11 @@
     static PipTransitionState providePipTransitionState(@ShellMainThread Handler handler) {
         return new PipTransitionState(handler);
     }
+
+    @WMSingleton
+    @Provides
+    static PipUiStateChangeController providePipUiStateChangeController(
+            PipTransitionState pipTransitionState) {
+        return new PipUiStateChangeController(pipTransitionState);
+    }
 }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeDragAndDropTransitionHandler.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeDragAndDropTransitionHandler.kt
new file mode 100644
index 0000000..a7a4a10
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeDragAndDropTransitionHandler.kt
@@ -0,0 +1,111 @@
+/*
+ * 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.wm.shell.desktopmode
+
+import android.animation.Animator
+import android.animation.AnimatorListenerAdapter
+import android.animation.ValueAnimator
+import android.os.IBinder
+import android.view.SurfaceControl
+import android.view.WindowManager.TRANSIT_OPEN
+import android.window.TransitionInfo
+import android.window.TransitionRequestInfo
+import android.window.WindowContainerTransaction
+import com.android.wm.shell.transition.Transitions
+import com.android.wm.shell.transition.Transitions.TransitionFinishCallback
+
+/**
+ * Transition handler for drag-and-drop (i.e., tab tear) transitions that occur in desktop mode.
+ */
+class DesktopModeDragAndDropTransitionHandler(private val transitions: Transitions) :
+    Transitions.TransitionHandler {
+    private val pendingTransitionTokens: MutableList<IBinder> = mutableListOf()
+
+    /**
+     * Begin a transition when a [android.app.PendingIntent] is dropped without a window to
+     * accept it.
+     */
+    fun handleDropEvent(wct: WindowContainerTransaction): IBinder {
+        val token = transitions.startTransition(TRANSIT_OPEN, wct, this)
+        pendingTransitionTokens.add(token)
+        return token
+    }
+
+    override fun startAnimation(
+        transition: IBinder,
+        info: TransitionInfo,
+        startTransaction: SurfaceControl.Transaction,
+        finishTransaction: SurfaceControl.Transaction,
+        finishCallback: TransitionFinishCallback
+    ): Boolean {
+        if (!pendingTransitionTokens.contains(transition)) return false
+        val change = findRelevantChange(info)
+        val leash = change.leash
+        val endBounds = change.endAbsBounds
+        startTransaction.hide(leash)
+            .setWindowCrop(leash, endBounds.width(), endBounds.height())
+            .apply()
+        val animator = ValueAnimator()
+        animator.setFloatValues(0f, 1f)
+        animator.setDuration(FADE_IN_ANIMATION_DURATION)
+        val t = SurfaceControl.Transaction()
+        animator.addListener(object : AnimatorListenerAdapter() {
+            override fun onAnimationStart(animation: Animator) {
+                t.show(leash)
+                t.apply()
+            }
+
+            override fun onAnimationEnd(animation: Animator) {
+                finishCallback.onTransitionFinished(null)
+            }
+        })
+        animator.addUpdateListener { animation: ValueAnimator ->
+            t.setAlpha(leash, animation.animatedFraction)
+            t.apply()
+        }
+        animator.start()
+        pendingTransitionTokens.remove(transition)
+        return true
+    }
+
+    private fun findRelevantChange(info: TransitionInfo): TransitionInfo.Change {
+        val matchingChanges =
+            info.changes.filter { c ->
+                isValidTaskChange(c) && c.mode == TRANSIT_OPEN
+            }
+        if (matchingChanges.size != 1) {
+            throw IllegalStateException(
+                "Expected 1 relevant change but found: ${matchingChanges.size}"
+            )
+        }
+        return matchingChanges.first()
+    }
+
+    private fun isValidTaskChange(change: TransitionInfo.Change): Boolean {
+        return change.taskInfo != null && change.taskInfo?.taskId != -1
+    }
+
+    override fun handleRequest(
+        transition: IBinder,
+        request: TransitionRequestInfo
+    ): WindowContainerTransaction? {
+        return null
+    }
+
+    companion object {
+        const val FADE_IN_ANIMATION_DURATION = 300L
+    }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeEventLogger.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeEventLogger.kt
index 05c9d02..b6f2a25 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeEventLogger.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeEventLogger.kt
@@ -16,6 +16,7 @@
 
 package com.android.wm.shell.desktopmode
 
+import com.android.internal.annotations.VisibleForTesting
 import com.android.internal.protolog.ProtoLog
 import com.android.internal.util.FrameworkStatsLog
 import com.android.wm.shell.protolog.ShellProtoLogGroup
@@ -128,7 +129,10 @@
             /* task_y */
             taskUpdate.taskY,
             /* session_id */
-            sessionId
+            sessionId,
+            taskUpdate.minimizeReason?.reason ?: UNSET_MINIMIZE_REASON,
+            taskUpdate.unminimizeReason?.reason ?: UNSET_UNMINIMIZE_REASON,
+
         )
     }
 
@@ -142,6 +146,8 @@
          * @property taskWidth width of the task in px
          * @property taskX x-coordinate of the top-left corner
          * @property taskY y-coordinate of the top-left corner
+         * @property minimizeReason the reason the task was minimized
+         * @property unminimizeEvent the reason the task was unminimized
          *
          */
         data class TaskUpdate(
@@ -151,8 +157,52 @@
             val taskWidth: Int,
             val taskX: Int,
             val taskY: Int,
+            val minimizeReason: MinimizeReason? = null,
+            val unminimizeReason: UnminimizeReason? = null,
         )
 
+        // Default value used when the task was not minimized.
+        @VisibleForTesting
+        const val UNSET_MINIMIZE_REASON =
+            FrameworkStatsLog.DESKTOP_MODE_SESSION_TASK_UPDATE__MINIMIZE_REASON__UNSET_MINIMIZE
+
+        /** The reason a task was minimized. */
+        enum class MinimizeReason (val reason: Int) {
+            TASK_LIMIT(
+                FrameworkStatsLog
+                    .DESKTOP_MODE_SESSION_TASK_UPDATE__MINIMIZE_REASON__MINIMIZE_TASK_LIMIT
+            ),
+            MINIMIZE_BUTTON( // TODO(b/356843241): use this enum value
+                FrameworkStatsLog
+                    .DESKTOP_MODE_SESSION_TASK_UPDATE__MINIMIZE_REASON__MINIMIZE_BUTTON
+            ),
+        }
+
+        // Default value used when the task was not unminimized.
+        @VisibleForTesting
+        const val UNSET_UNMINIMIZE_REASON =
+            FrameworkStatsLog.DESKTOP_MODE_SESSION_TASK_UPDATE__UNMINIMIZE_REASON__UNSET_UNMINIMIZE
+
+        /** The reason a task was unminimized. */
+        enum class UnminimizeReason (val reason: Int) {
+            UNKNOWN(
+                FrameworkStatsLog
+                    .DESKTOP_MODE_SESSION_TASK_UPDATE__UNMINIMIZE_REASON__UNMINIMIZE_UNKNOWN
+            ),
+            TASKBAR_TAP(
+                FrameworkStatsLog
+                    .DESKTOP_MODE_SESSION_TASK_UPDATE__UNMINIMIZE_REASON__UNMINIMIZE_TASKBAR_TAP
+            ),
+            ALT_TAB(
+                FrameworkStatsLog
+                    .DESKTOP_MODE_SESSION_TASK_UPDATE__UNMINIMIZE_REASON__UNMINIMIZE_ALT_TAB
+            ),
+            TASK_LAUNCH(
+                FrameworkStatsLog
+                    .DESKTOP_MODE_SESSION_TASK_UPDATE__UNMINIMIZE_REASON__UNMINIMIZE_TASK_LAUNCH
+            ),
+        }
+
         /**
          * Enum EnterReason mapped to the EnterReason definition in
          * stats/atoms/desktopmode/desktopmode_extensions_atoms.proto
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeUtils.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeUtils.kt
index 6c03dc3..b68b436 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeUtils.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeUtils.kt
@@ -66,7 +66,7 @@
     val initialSize: Size =
         when (taskInfo.configuration.orientation) {
             ORIENTATION_LANDSCAPE -> {
-                if (taskInfo.isResizeable) {
+                if (taskInfo.canChangeAspectRatio) {
                     if (isFixedOrientationPortrait(topActivityInfo.screenOrientation)) {
                         // For portrait resizeable activities, respect apps fullscreen width but
                         // apply ideal size height.
@@ -85,7 +85,7 @@
             ORIENTATION_PORTRAIT -> {
                 val customPortraitWidthForLandscapeApp =
                     screenBounds.width() - (DESKTOP_MODE_LANDSCAPE_APP_PADDING * 2)
-                if (taskInfo.isResizeable) {
+                if (taskInfo.canChangeAspectRatio) {
                     if (isFixedOrientationLandscape(topActivityInfo.screenOrientation)) {
                         // For landscape resizeable activities, respect apps fullscreen height and
                         // apply custom app width.
@@ -189,6 +189,13 @@
     }
 
 /**
+ * Whether the activity's aspect ratio can be changed or if it should be maintained as if it was
+ * unresizeable.
+ */
+private val TaskInfo.canChangeAspectRatio: Boolean
+    get() = isResizeable && !appCompatTaskInfo.hasMinAspectRatioOverride()
+
+/**
  * Adjusts bounds to be positioned in the middle of the area provided, not necessarily the
  * entire screen, as area can be offset by left and top start.
  */
@@ -204,7 +211,7 @@
     return Rect(newLeft, newTop, newRight, newBottom)
 }
 
-fun TaskInfo.hasPortraitTopActivity(): Boolean {
+private fun TaskInfo.hasPortraitTopActivity(): Boolean {
     val topActivityScreenOrientation =
         topActivityInfo?.screenOrientation ?: SCREEN_ORIENTATION_UNSPECIFIED
     val appBounds = configuration.windowConfiguration.appBounds
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeVisualIndicator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeVisualIndicator.java
index 09f9139..bfc0ee8 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeVisualIndicator.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeVisualIndicator.java
@@ -16,7 +16,6 @@
 
 package com.android.wm.shell.desktopmode;
 
-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_NOT_FOCUSABLE;
@@ -29,7 +28,6 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.app.ActivityManager;
-import android.app.WindowConfiguration;
 import android.content.Context;
 import android.content.res.Resources;
 import android.graphics.PixelFormat;
@@ -70,6 +68,37 @@
         TO_SPLIT_RIGHT_INDICATOR
     }
 
+    /**
+     * The conditions surrounding the drag event that led to the indicator's creation.
+     */
+    public enum DragStartState {
+        /** The indicator is resulting from a freeform task drag. */
+        FROM_FREEFORM,
+        /** The indicator is resulting from a split screen task drag */
+        FROM_SPLIT,
+        /** The indicator is resulting from a fullscreen task drag */
+        FROM_FULLSCREEN,
+        /** The indicator is resulting from an Intent generated during a drag-and-drop event */
+        DRAGGED_INTENT;
+
+        /**
+         * Get the {@link DragStartState} of a drag event based on the windowing mode of the task.
+         * Note that DRAGGED_INTENT will be specified by the caller if needed and not returned
+         * here.
+         */
+        public static DesktopModeVisualIndicator.DragStartState getDragStartState(
+                ActivityManager.RunningTaskInfo taskInfo
+        ) {
+            if (taskInfo.getWindowingMode() == WINDOWING_MODE_FULLSCREEN) {
+                return FROM_FULLSCREEN;
+            } else if (taskInfo.getWindowingMode() == WINDOWING_MODE_MULTI_WINDOW) {
+                return FROM_SPLIT;
+            } else if (taskInfo.isFreeform()) {
+                return FROM_FREEFORM;
+            } else return null;
+        }
+    }
+
     private final Context mContext;
     private final DisplayController mDisplayController;
     private final RootTaskDisplayAreaOrganizer mRootTdaOrganizer;
@@ -82,11 +111,13 @@
 
     private View mView;
     private IndicatorType mCurrentType;
+    private DragStartState mDragStartState;
 
     public DesktopModeVisualIndicator(SyncTransactionQueue syncQueue,
             ActivityManager.RunningTaskInfo taskInfo, DisplayController displayController,
             Context context, SurfaceControl taskSurface,
-            RootTaskDisplayAreaOrganizer taskDisplayAreaOrganizer) {
+            RootTaskDisplayAreaOrganizer taskDisplayAreaOrganizer,
+            DragStartState dragStartState) {
         mSyncQueue = syncQueue;
         mTaskInfo = taskInfo;
         mDisplayController = displayController;
@@ -94,6 +125,7 @@
         mTaskSurface = taskSurface;
         mRootTdaOrganizer = taskDisplayAreaOrganizer;
         mCurrentType = IndicatorType.NO_INDICATOR;
+        mDragStartState = dragStartState;
     }
 
     /**
@@ -101,7 +133,7 @@
      * display, including no visible indicator.
      */
     @NonNull
-    IndicatorType updateIndicatorType(PointF inputCoordinates, int windowingMode) {
+    IndicatorType updateIndicatorType(PointF inputCoordinates) {
         final DisplayLayout layout = mDisplayController.getDisplayLayout(mTaskInfo.displayId);
         // If we are in freeform, we don't want a visible indicator in the "freeform" drag zone.
         IndicatorType result = IndicatorType.NO_INDICATOR;
@@ -111,14 +143,13 @@
         // account for the possibility of the task going off the top of the screen by captionHeight
         final int captionHeight = mContext.getResources().getDimensionPixelSize(
                 com.android.wm.shell.R.dimen.desktop_mode_freeform_decor_caption_height);
-        final Region fullscreenRegion = calculateFullscreenRegion(layout, windowingMode,
+        final Region fullscreenRegion = calculateFullscreenRegion(layout, captionHeight);
+        final Region splitLeftRegion = calculateSplitLeftRegion(layout, transitionAreaWidth,
                 captionHeight);
-        final Region splitLeftRegion = calculateSplitLeftRegion(layout, windowingMode,
-                transitionAreaWidth, captionHeight);
-        final Region splitRightRegion = calculateSplitRightRegion(layout, windowingMode,
-                transitionAreaWidth, captionHeight);
-        final Region toDesktopRegion = calculateToDesktopRegion(layout, windowingMode,
-                splitLeftRegion, splitRightRegion, fullscreenRegion);
+        final Region splitRightRegion = calculateSplitRightRegion(layout, transitionAreaWidth,
+                captionHeight);
+        final Region toDesktopRegion = calculateToDesktopRegion(layout, splitLeftRegion,
+                splitRightRegion, fullscreenRegion);
         if (fullscreenRegion.contains((int) inputCoordinates.x, (int) inputCoordinates.y)) {
             result = IndicatorType.TO_FULLSCREEN_INDICATOR;
         }
@@ -131,20 +162,22 @@
         if (toDesktopRegion.contains((int) inputCoordinates.x, (int) inputCoordinates.y)) {
             result = IndicatorType.TO_DESKTOP_INDICATOR;
         }
-        transitionIndicator(result);
+        if (mDragStartState != DragStartState.DRAGGED_INTENT) {
+            transitionIndicator(result);
+        }
         return result;
     }
 
     @VisibleForTesting
-    Region calculateFullscreenRegion(DisplayLayout layout,
-            @WindowConfiguration.WindowingMode int windowingMode, int captionHeight) {
+    Region calculateFullscreenRegion(DisplayLayout layout, int captionHeight) {
         final Region region = new Region();
-        int transitionHeight = windowingMode == WINDOWING_MODE_FREEFORM
+        int transitionHeight = mDragStartState == DragStartState.FROM_FREEFORM
+                || mDragStartState == DragStartState.DRAGGED_INTENT
                 ? mContext.getResources().getDimensionPixelSize(
                 com.android.wm.shell.R.dimen.desktop_mode_transition_region_thickness)
                 : 2 * layout.stableInsets().top;
         // A Rect at the top of the screen that takes up the center 40%.
-        if (windowingMode == WINDOWING_MODE_FREEFORM) {
+        if (mDragStartState == DragStartState.FROM_FREEFORM) {
             final float toFullscreenScale = mContext.getResources().getFloat(
                     R.dimen.desktop_mode_fullscreen_region_scale);
             final float toFullscreenWidth = (layout.width() * toFullscreenScale);
@@ -153,9 +186,11 @@
                     (int) ((layout.width() / 2f) + (toFullscreenWidth / 2f)),
                     transitionHeight));
         }
-        // A screen-wide Rect if the task is in fullscreen or split.
-        if (windowingMode == WINDOWING_MODE_FULLSCREEN
-                || windowingMode == WINDOWING_MODE_MULTI_WINDOW) {
+        // A screen-wide Rect if the task is in fullscreen, split, or a dragged intent.
+        if (mDragStartState == DragStartState.FROM_FULLSCREEN
+                || mDragStartState == DragStartState.FROM_SPLIT
+                || mDragStartState == DragStartState.DRAGGED_INTENT
+        ) {
             region.union(new Rect(0,
                     -captionHeight,
                     layout.width(),
@@ -166,12 +201,11 @@
 
     @VisibleForTesting
     Region calculateToDesktopRegion(DisplayLayout layout,
-            @WindowConfiguration.WindowingMode int windowingMode,
             Region splitLeftRegion, Region splitRightRegion,
             Region toFullscreenRegion) {
         final Region region = new Region();
         // If in desktop, we need no region. Otherwise it's the same for all windowing modes.
-        if (windowingMode != WINDOWING_MODE_FREEFORM) {
+        if (mDragStartState != DragStartState.FROM_FREEFORM) {
             region.union(new Rect(0, 0, layout.width(), layout.height()));
             region.op(splitLeftRegion, Region.Op.DIFFERENCE);
             region.op(splitRightRegion, Region.Op.DIFFERENCE);
@@ -182,11 +216,10 @@
 
     @VisibleForTesting
     Region calculateSplitLeftRegion(DisplayLayout layout,
-            @WindowConfiguration.WindowingMode int windowingMode,
             int transitionEdgeWidth, int captionHeight) {
         final Region region = new Region();
         // In freeform, keep the top corners clear.
-        int transitionHeight = windowingMode == WINDOWING_MODE_FREEFORM
+        int transitionHeight = mDragStartState == DragStartState.FROM_FREEFORM
                 ? mContext.getResources().getDimensionPixelSize(
                 com.android.wm.shell.R.dimen.desktop_mode_split_from_desktop_height) :
                 -captionHeight;
@@ -196,11 +229,10 @@
 
     @VisibleForTesting
     Region calculateSplitRightRegion(DisplayLayout layout,
-            @WindowConfiguration.WindowingMode int windowingMode,
             int transitionEdgeWidth, int captionHeight) {
         final Region region = new Region();
         // In freeform, keep the top corners clear.
-        int transitionHeight = windowingMode == WINDOWING_MODE_FREEFORM
+        int transitionHeight = mDragStartState == DragStartState.FROM_FREEFORM
                 ? mContext.getResources().getDimensionPixelSize(
                 com.android.wm.shell.R.dimen.desktop_mode_split_from_desktop_height) :
                 -captionHeight;
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 456d92a..33794d2 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
@@ -34,10 +34,12 @@
 import android.graphics.PointF
 import android.graphics.Rect
 import android.graphics.Region
+import android.os.Binder
 import android.os.IBinder
 import android.os.SystemProperties
 import android.util.Size
 import android.view.Display.DEFAULT_DISPLAY
+import android.view.DragEvent
 import android.view.SurfaceControl
 import android.view.WindowManager.TRANSIT_CHANGE
 import android.view.WindowManager.TRANSIT_NONE
@@ -67,10 +69,9 @@
 import com.android.wm.shell.common.SingleInstanceRemoteListener
 import com.android.wm.shell.common.SyncTransactionQueue
 import com.android.wm.shell.common.desktopmode.DesktopModeTransitionSource
-import com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_BOTTOM_OR_RIGHT
-import com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_TOP_OR_LEFT
 import com.android.wm.shell.compatui.isTopActivityExemptFromDesktopWindowing
 import com.android.wm.shell.desktopmode.DesktopModeTaskRepository.VisibleTasksListener
+import com.android.wm.shell.desktopmode.DesktopModeVisualIndicator.DragStartState
 import com.android.wm.shell.desktopmode.DesktopModeVisualIndicator.IndicatorType
 import com.android.wm.shell.desktopmode.DragToDesktopTransitionHandler.DragToDesktopStateListener
 import com.android.wm.shell.draganddrop.DragAndDropController
@@ -79,6 +80,7 @@
 import com.android.wm.shell.recents.RecentsTransitionHandler
 import com.android.wm.shell.recents.RecentsTransitionStateListener
 import com.android.wm.shell.shared.TransitionUtil
+import com.android.wm.shell.shared.ShellSharedConstants
 import com.android.wm.shell.shared.annotations.ExternalThread
 import com.android.wm.shell.shared.annotations.ShellMainThread
 import com.android.wm.shell.shared.desktopmode.DesktopModeFlags
@@ -86,12 +88,13 @@
 import com.android.wm.shell.shared.desktopmode.DesktopModeStatus
 import com.android.wm.shell.shared.desktopmode.DesktopModeStatus.DESKTOP_DENSITY_OVERRIDE
 import com.android.wm.shell.shared.desktopmode.DesktopModeStatus.useDesktopOverrideDensity
+import com.android.wm.shell.shared.split.SplitScreenConstants.SPLIT_POSITION_BOTTOM_OR_RIGHT
+import com.android.wm.shell.shared.split.SplitScreenConstants.SPLIT_POSITION_TOP_OR_LEFT
 import com.android.wm.shell.splitscreen.SplitScreenController
 import com.android.wm.shell.splitscreen.SplitScreenController.EXIT_REASON_DESKTOP_MODE
 import com.android.wm.shell.sysui.ShellCommandHandler
 import com.android.wm.shell.sysui.ShellController
 import com.android.wm.shell.sysui.ShellInit
-import com.android.wm.shell.sysui.ShellSharedConstants
 import com.android.wm.shell.transition.OneShotRemoteHandler
 import com.android.wm.shell.transition.Transitions
 import com.android.wm.shell.windowdecor.DragPositioningCallbackUtility
@@ -121,6 +124,7 @@
     private val returnToDragStartAnimator: ReturnToDragStartAnimator,
     private val enterDesktopTaskTransitionHandler: EnterDesktopTaskTransitionHandler,
     private val exitDesktopTaskTransitionHandler: ExitDesktopTaskTransitionHandler,
+    private val desktopModeDragAndDropTransitionHandler: DesktopModeDragAndDropTransitionHandler,
     private val toggleResizeDesktopTaskTransitionHandler: ToggleResizeDesktopTaskTransitionHandler,
     private val dragToDesktopTransitionHandler: DragToDesktopTransitionHandler,
     private val taskRepository: DesktopModeTaskRepository,
@@ -146,12 +150,6 @@
             visualIndicator?.releaseVisualIndicator(t)
             visualIndicator = null
         }
-    private val taskVisibilityListener =
-        object : VisibleTasksListener {
-            override fun onTasksVisibilityChanged(displayId: Int, visibleTasksCount: Int) {
-                launchAdjacentController.launchAdjacentEnabled = visibleTasksCount == 0
-            }
-        }
     private val dragToDesktopStateListener =
         object : DragToDesktopStateListener {
             override fun onCommitToDesktopAnimationStart(tx: SurfaceControl.Transaction) {
@@ -176,6 +174,9 @@
 
     private var recentsAnimationRunning = false
     private lateinit var splitScreenController: SplitScreenController
+    // Launch cookie used to identify a drag and drop transition to fullscreen after it has begun.
+    // Used to prevent handleRequest from moving the new fullscreen task to freeform.
+    private var dragAndDropFullscreenCookie: Binder? = null
 
     init {
         desktopMode = DesktopModeImpl()
@@ -194,7 +195,6 @@
             this
         )
         transitions.addHandler(this)
-        taskRepository.addVisibleTasksListener(taskVisibilityListener, mainExecutor)
         dragToDesktopTransitionHandler.dragToDesktopStateListener = dragToDesktopStateListener
         recentsTransitionHandler.addTransitionStateListener(
             object : RecentsTransitionStateListener {
@@ -858,6 +858,7 @@
         val triggerTask = request.triggerTask
         var shouldHandleMidRecentsFreeformLaunch =
             recentsAnimationRunning && isFreeformRelaunch(triggerTask, request)
+        val isDragAndDropFullscreenTransition = taskContainsDragAndDropCookie(triggerTask)
         val shouldHandleRequest =
             when {
                 // Handle freeform relaunch during recents animation
@@ -866,6 +867,13 @@
                     reason = "recents animation is running"
                     false
                 }
+                // Don't handle request if this was a tear to fullscreen transition.
+                // handleFullscreenTaskLaunch moves fullscreen intents to freeform;
+                // this is an exception to the rule
+                isDragAndDropFullscreenTransition -> {
+                    dragAndDropFullscreenCookie = null
+                    false
+                }
                 // Handle task closing for the last window if wallpaper is available
                 shouldHandleTaskClosing(request) -> true
                 // Only handle open or to front transitions
@@ -884,8 +892,7 @@
                     false
                 }
                 // Only handle fullscreen or freeform tasks
-                triggerTask.windowingMode != WINDOWING_MODE_FULLSCREEN &&
-                    triggerTask.windowingMode != WINDOWING_MODE_FREEFORM -> {
+                !triggerTask.isFullscreen && !triggerTask.isFreeform -> {
                     reason = "windowingMode not handled (${triggerTask.windowingMode})"
                     false
                 }
@@ -920,6 +927,9 @@
         return result
     }
 
+    private fun taskContainsDragAndDropCookie(taskInfo: RunningTaskInfo?) =
+        taskInfo?.launchCookies?.any { it == dragAndDropFullscreenCookie } ?: false
+
     /**
      * Applies the proper surface states (rounded corners) to tasks when desktop mode is active.
      * This is intended to be used when desktop mode is part of another animation but isn't, itself,
@@ -1108,16 +1118,21 @@
     /** Handle task closing by removing wallpaper activity if it's the last active task */
     private fun handleTaskClosing(task: RunningTaskInfo): WindowContainerTransaction? {
         logV("handleTaskClosing")
+        if (!isDesktopModeShowing(task.displayId))
+            return null
+
         val wct = WindowContainerTransaction()
         if (taskRepository.isOnlyVisibleNonClosingTask(task.taskId)
-            && taskRepository.wallpaperActivityToken != null) {
+            && taskRepository.wallpaperActivityToken != null
+        ) {
             // Remove wallpaper activity when the last active task is removed
             removeWallpaperActivity(wct)
         }
         taskRepository.addClosingTask(task.displayId, task.taskId)
         // If a CLOSE or TO_BACK is triggered on a desktop task, remove the task.
         if (DesktopModeFlags.BACK_NAVIGATION.isEnabled(context) &&
-            taskRepository.isVisibleTask(task.taskId)) {
+            taskRepository.isVisibleTask(task.taskId)
+        ) {
             wct.removeTask(task.token)
         }
         return if (wct.isEmpty) null else wct
@@ -1314,15 +1329,17 @@
         taskBounds: Rect
     ) {
         if (taskInfo.windowingMode != WINDOWING_MODE_FREEFORM) return
-        updateVisualIndicator(taskInfo, taskSurface, inputX, taskBounds.top.toFloat())
+        updateVisualIndicator(taskInfo, taskSurface, inputX, taskBounds.top.toFloat(),
+            DragStartState.FROM_FREEFORM)
     }
 
     fun updateVisualIndicator(
         taskInfo: RunningTaskInfo,
-        taskSurface: SurfaceControl,
+        taskSurface: SurfaceControl?,
         inputX: Float,
-        taskTop: Float
-    ): IndicatorType {
+        taskTop: Float,
+        dragStartState: DragStartState
+    ): DesktopModeVisualIndicator.IndicatorType {
         // If the visual indicator does not exist, create it.
         val indicator =
             visualIndicator
@@ -1332,10 +1349,11 @@
                     displayController,
                     context,
                     taskSurface,
-                    rootTaskDisplayAreaOrganizer
+                    rootTaskDisplayAreaOrganizer,
+                    dragStartState
                 )
         if (visualIndicator == null) visualIndicator = indicator
-        return indicator.updateIndicatorType(PointF(inputX, taskTop), taskInfo.windowingMode)
+        return indicator.updateIndicatorType(PointF(inputX, taskTop))
     }
 
     /**
@@ -1368,7 +1386,6 @@
         val indicatorType =
             indicator.updateIndicatorType(
                 PointF(inputCoordinate.x, currentDragBounds.top.toFloat()),
-                taskInfo.windowingMode
             )
         when (indicatorType) {
             IndicatorType.TO_FULLSCREEN_INDICATOR -> {
@@ -1429,7 +1446,7 @@
         // End the drag_hold CUJ interaction.
         interactionJankMonitor.end(CUJ_DESKTOP_MODE_ENTER_APP_HANDLE_DRAG_HOLD)
         val indicator = getVisualIndicator() ?: return IndicatorType.NO_INDICATOR
-        val indicatorType = indicator.updateIndicatorType(inputCoordinates, taskInfo.windowingMode)
+        val indicatorType = indicator.updateIndicatorType(inputCoordinates)
         when (indicatorType) {
             IndicatorType.TO_DESKTOP_INDICATOR -> {
                 // Start a new jank interaction for the drag release to desktop window animation.
@@ -1481,9 +1498,10 @@
         taskRepository.setExclusionRegionListener(listener, callbackExecutor)
     }
 
+    // TODO(b/358114479): Move this implementation into a separate class.
     override fun onUnhandledDrag(
         launchIntent: PendingIntent,
-        dragSurface: SurfaceControl,
+        dragEvent: DragEvent,
         onFinishCallback: Consumer<Boolean>
     ): Boolean {
         // TODO(b/320797628): Pass through which display we are dropping onto
@@ -1491,7 +1509,6 @@
             // Not currently in desktop mode, ignore the drop
             return false
         }
-
         val launchComponent = getComponent(launchIntent)
         if (!multiInstanceHelper.supportsMultiInstanceSplit(launchComponent)) {
             // TODO(b/320797628): Should only return early if there is an existing running task, and
@@ -1499,20 +1516,69 @@
             logV("Dropped intent does not support multi-instance")
             return false
         }
-
+        val taskInfo = getFocusedFreeformTask(DEFAULT_DISPLAY) ?: return false
+        // TODO(b/358114479): Update drag and drop handling to give us visibility into when another
+        //  window will accept a drag event. This way, we can hide the indicator when we won't
+        //  be handling the transition here, allowing us to display the indicator accurately.
+        //  For now, we create the indicator only on drag end and immediately dispose it.
+        val indicatorType = updateVisualIndicator(taskInfo, dragEvent.dragSurface,
+            dragEvent.x, dragEvent.y,
+            DragStartState.DRAGGED_INTENT)
+        releaseVisualIndicator()
+        val windowingMode = when (indicatorType) {
+            IndicatorType.TO_FULLSCREEN_INDICATOR -> {
+                WINDOWING_MODE_FULLSCREEN
+            }
+            IndicatorType.TO_SPLIT_LEFT_INDICATOR,
+            IndicatorType.TO_SPLIT_RIGHT_INDICATOR,
+            IndicatorType.TO_DESKTOP_INDICATOR
+            -> {
+                WINDOWING_MODE_FREEFORM
+            }
+            else -> error("Invalid indicator type: $indicatorType")
+        }
+        val displayLayout = displayController.getDisplayLayout(DEFAULT_DISPLAY) ?: return false
+        val newWindowBounds = Rect()
+        when (indicatorType) {
+            IndicatorType.TO_DESKTOP_INDICATOR -> {
+                // Use default bounds, but with the top-center at the drop point.
+                newWindowBounds.set(getDefaultDesktopTaskBounds(displayLayout))
+                newWindowBounds.offsetTo(
+                    dragEvent.x.toInt() - (newWindowBounds.width() / 2),
+                    dragEvent.y.toInt()
+                )
+            }
+            IndicatorType.TO_SPLIT_RIGHT_INDICATOR -> {
+                newWindowBounds.set(getSnapBounds(taskInfo, SnapPosition.RIGHT))
+            }
+            IndicatorType.TO_SPLIT_LEFT_INDICATOR -> {
+                newWindowBounds.set(getSnapBounds(taskInfo, SnapPosition.LEFT))
+            }
+            else -> {
+                // Use empty bounds for the fullscreen case.
+            }
+        }
         // Start a new transition to launch the app
         val opts =
             ActivityOptions.makeBasic().apply {
-                launchWindowingMode = WINDOWING_MODE_FREEFORM
+                launchWindowingMode = windowingMode
+                launchBounds = newWindowBounds
+                pendingIntentBackgroundActivityStartMode =
+                    ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOW_ALWAYS
                 pendingIntentLaunchFlags =
                     Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_MULTIPLE_TASK
-                setPendingIntentBackgroundActivityStartMode(
-                    ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_DENIED
-                )
             }
+        if (windowingMode == WINDOWING_MODE_FULLSCREEN) {
+            dragAndDropFullscreenCookie = Binder()
+            opts.launchCookie = dragAndDropFullscreenCookie
+        }
         val wct = WindowContainerTransaction()
         wct.sendPendingIntent(launchIntent, null, opts.toBundle())
-        transitions.startTransition(TRANSIT_OPEN, wct, null /* handler */)
+        if (windowingMode == WINDOWING_MODE_FREEFORM) {
+            desktopModeDragAndDropTransitionHandler.handleDropEvent(wct)
+        } else {
+            transitions.startTransition(TRANSIT_OPEN, wct, null)
+        }
 
         // Report that this is handled by the listener
         onFinishCallback.accept(true)
@@ -1520,7 +1586,7 @@
         // We've assumed responsibility of cleaning up the drag surface, so do that now
         // TODO(b/320797628): Do an actual animation here for the drag surface
         val t = SurfaceControl.Transaction()
-        t.remove(dragSurface)
+        t.remove(dragEvent.dragSurface)
         t.apply()
         return true
     }
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 9874f4c..1a103d3 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
@@ -35,13 +35,13 @@
 import com.android.internal.protolog.ProtoLog
 import com.android.wm.shell.RootTaskDisplayAreaOrganizer
 import com.android.wm.shell.animation.FloatProperties
-import com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_BOTTOM_OR_RIGHT
-import com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_TOP_OR_LEFT
-import com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_UNDEFINED
-import com.android.wm.shell.common.split.SplitScreenConstants.SplitPosition
 import com.android.wm.shell.protolog.ShellProtoLogGroup
 import com.android.wm.shell.shared.TransitionUtil
 import com.android.wm.shell.shared.animation.PhysicsAnimator
+import com.android.wm.shell.shared.split.SplitScreenConstants.SPLIT_POSITION_BOTTOM_OR_RIGHT
+import com.android.wm.shell.shared.split.SplitScreenConstants.SPLIT_POSITION_TOP_OR_LEFT
+import com.android.wm.shell.shared.split.SplitScreenConstants.SPLIT_POSITION_UNDEFINED
+import com.android.wm.shell.shared.split.SplitScreenConstants.SplitPosition
 import com.android.wm.shell.splitscreen.SplitScreenController
 import com.android.wm.shell.transition.Transitions
 import com.android.wm.shell.transition.Transitions.TRANSIT_DESKTOP_MODE_CANCEL_DRAG_TO_DESKTOP
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/ReturnToDragStartAnimator.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/ReturnToDragStartAnimator.kt
index be67a40..24a7d77 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/ReturnToDragStartAnimator.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/ReturnToDragStartAnimator.kt
@@ -19,23 +19,27 @@
 import android.animation.Animator
 import android.animation.RectEvaluator
 import android.animation.ValueAnimator
+import android.content.Context
 import android.graphics.Rect
 import android.view.SurfaceControl
+import android.widget.Toast
 import androidx.core.animation.addListener
 import com.android.internal.jank.InteractionJankMonitor
+import com.android.wm.shell.R
 import com.android.wm.shell.windowdecor.OnTaskRepositionAnimationListener
 import java.util.function.Supplier
 
 /** Animates the task surface moving from its current drag position to its pre-drag position. */
 class ReturnToDragStartAnimator(
+    private val context: Context,
     private val transactionSupplier: Supplier<SurfaceControl.Transaction>,
     private val interactionJankMonitor: InteractionJankMonitor
 ) {
     private var boundsAnimator: Animator? = null
     private lateinit var taskRepositionAnimationListener: OnTaskRepositionAnimationListener
 
-    constructor(interactionJankMonitor: InteractionJankMonitor) :
-            this(Supplier { SurfaceControl.Transaction() }, interactionJankMonitor)
+    constructor(context: Context, interactionJankMonitor: InteractionJankMonitor) :
+            this(context, Supplier { SurfaceControl.Transaction() }, interactionJankMonitor)
 
     /** Sets a listener for the start and end of the reposition animation. */
     fun setTaskRepositionAnimationListener(listener: OnTaskRepositionAnimationListener) {
@@ -76,7 +80,11 @@
                                 .apply()
                             taskRepositionAnimationListener.onAnimationEnd(taskId)
                             boundsAnimator = null
-                            // TODO(b/354658237) - show toast with relevant string
+                            Toast.makeText(
+                                context,
+                                R.string.desktop_mode_non_resizable_snap_text,
+                                Toast.LENGTH_SHORT
+                            ).show()
                             // TODO(b/339582583) - add Jank CUJ using interactionJankMonitor
                         }
                     )
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropController.java
index e00353d..cf02fb5 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropController.java
@@ -32,7 +32,7 @@
 import static android.view.WindowManager.LayoutParams.SYSTEM_FLAG_SHOW_FOR_ALL_USERS;
 import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
 
-import static com.android.wm.shell.sysui.ShellSharedConstants.KEY_EXTRA_SHELL_DRAG_AND_DROP;
+import static com.android.wm.shell.shared.ShellSharedConstants.KEY_EXTRA_SHELL_DRAG_AND_DROP;
 
 import android.app.ActivityManager;
 import android.app.ActivityTaskManager;
@@ -70,6 +70,7 @@
 import com.android.wm.shell.common.ShellExecutor;
 import com.android.wm.shell.protolog.ShellProtoLogGroup;
 import com.android.wm.shell.shared.annotations.ExternalMainThread;
+import com.android.wm.shell.shared.desktopmode.DesktopModeStatus;
 import com.android.wm.shell.splitscreen.SplitScreenController;
 import com.android.wm.shell.sysui.ShellCommandHandler;
 import com.android.wm.shell.sysui.ShellController;
@@ -127,7 +128,7 @@
          * drag.
          */
         default boolean onUnhandledDrag(@NonNull PendingIntent launchIntent,
-                @NonNull SurfaceControl dragSurface,
+                @NonNull DragEvent dragEvent,
                 @NonNull Consumer<Boolean> onFinishCallback) {
             return false;
         }
@@ -329,9 +330,18 @@
             return false;
         }
 
+        DragSession dragSession = null;
         if (event.getAction() == ACTION_DRAG_STARTED) {
             mActiveDragDisplay = displayId;
-            pd.isHandlingDrag = DragUtils.canHandleDrag(event);
+            dragSession = new DragSession(ActivityTaskManager.getInstance(),
+                    mDisplayController.getDisplayLayout(displayId), event.getClipData(),
+                    event.getDragFlags());
+            dragSession.initialize();
+            final ActivityManager.RunningTaskInfo taskInfo = dragSession.runningTaskInfo;
+            // Desktop tasks will have their own drag handling.
+            final boolean isDesktopDrag = taskInfo != null && taskInfo.isFreeform()
+                    && DesktopModeStatus.canEnterDesktopMode(mContext);
+            pd.isHandlingDrag = DragUtils.canHandleDrag(event) && !isDesktopDrag;
             ProtoLog.v(ShellProtoLogGroup.WM_SHELL_DRAG_AND_DROP,
                     "Clip description: handlingDrag=%b itemCount=%d mimeTypes=%s flags=%s",
                     pd.isHandlingDrag, event.getClipData().getItemCount(),
@@ -349,10 +359,7 @@
                     Slog.w(TAG, "Unexpected drag start during an active drag");
                     return false;
                 }
-                pd.dragSession = new DragSession(ActivityTaskManager.getInstance(),
-                        mDisplayController.getDisplayLayout(displayId), event.getClipData(),
-                        event.getDragFlags());
-                pd.dragSession.initialize();
+                pd.dragSession = dragSession;
                 pd.activeDragCount++;
                 pd.dragLayout.prepare(pd.dragSession, mLogger.logStart(pd.dragSession));
                 if (pd.dragSession.hideDragSourceTaskId != -1) {
@@ -437,7 +444,7 @@
         }
 
         final boolean handled = notifyListeners(
-                l -> l.onUnhandledDrag(launchIntent, dragEvent.getDragSurface(), onFinishCallback));
+                l -> l.onUnhandledDrag(launchIntent, dragEvent, onFinishCallback));
         if (!handled) {
             // Nobody handled this, we still have to notify WM
             onFinishCallback.accept(false);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropPolicy.java b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropPolicy.java
index 7e03624..6fec0c1 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropPolicy.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropPolicy.java
@@ -32,15 +32,15 @@
 import static android.content.Intent.FLAG_ACTIVITY_MULTIPLE_TASK;
 import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
 
-import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_BOTTOM_OR_RIGHT;
-import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_TOP_OR_LEFT;
-import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_UNDEFINED;
-import static com.android.wm.shell.draganddrop.DragAndDropConstants.EXTRA_DISALLOW_HIT_REGION;
 import static com.android.wm.shell.draganddrop.DragAndDropPolicy.Target.TYPE_FULLSCREEN;
 import static com.android.wm.shell.draganddrop.DragAndDropPolicy.Target.TYPE_SPLIT_BOTTOM;
 import static com.android.wm.shell.draganddrop.DragAndDropPolicy.Target.TYPE_SPLIT_LEFT;
 import static com.android.wm.shell.draganddrop.DragAndDropPolicy.Target.TYPE_SPLIT_RIGHT;
 import static com.android.wm.shell.draganddrop.DragAndDropPolicy.Target.TYPE_SPLIT_TOP;
+import static com.android.wm.shell.shared.draganddrop.DragAndDropConstants.EXTRA_DISALLOW_HIT_REGION;
+import static com.android.wm.shell.shared.split.SplitScreenConstants.SPLIT_POSITION_BOTTOM_OR_RIGHT;
+import static com.android.wm.shell.shared.split.SplitScreenConstants.SPLIT_POSITION_TOP_OR_LEFT;
+import static com.android.wm.shell.shared.split.SplitScreenConstants.SPLIT_POSITION_UNDEFINED;
 
 import android.app.ActivityOptions;
 import android.app.ActivityTaskManager;
@@ -69,8 +69,8 @@
 import com.android.internal.logging.InstanceId;
 import com.android.internal.protolog.ProtoLog;
 import com.android.wm.shell.R;
-import com.android.wm.shell.common.split.SplitScreenConstants.SplitPosition;
 import com.android.wm.shell.protolog.ShellProtoLogGroup;
+import com.android.wm.shell.shared.split.SplitScreenConstants.SplitPosition;
 import com.android.wm.shell.splitscreen.SplitScreenController;
 
 import java.lang.annotation.Retention;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragLayout.java b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragLayout.java
index d03a561..3fecbe7 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragLayout.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragLayout.java
@@ -21,15 +21,14 @@
 import static android.content.pm.ActivityInfo.CONFIG_ASSETS_PATHS;
 import static android.content.pm.ActivityInfo.CONFIG_UI_MODE;
 import static android.view.ViewGroup.LayoutParams.MATCH_PARENT;
-import static android.view.ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_FRAME;
 import static android.view.ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_REGION;
 
-import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_BOTTOM_OR_RIGHT;
-import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_TOP_OR_LEFT;
 import static com.android.wm.shell.draganddrop.DragAndDropPolicy.Target.TYPE_SPLIT_BOTTOM;
 import static com.android.wm.shell.draganddrop.DragAndDropPolicy.Target.TYPE_SPLIT_LEFT;
 import static com.android.wm.shell.draganddrop.DragAndDropPolicy.Target.TYPE_SPLIT_RIGHT;
 import static com.android.wm.shell.draganddrop.DragAndDropPolicy.Target.TYPE_SPLIT_TOP;
+import static com.android.wm.shell.shared.split.SplitScreenConstants.SPLIT_POSITION_BOTTOM_OR_RIGHT;
+import static com.android.wm.shell.shared.split.SplitScreenConstants.SPLIT_POSITION_TOP_OR_LEFT;
 
 import android.animation.Animator;
 import android.animation.AnimatorListenerAdapter;
@@ -61,9 +60,9 @@
 import com.android.internal.protolog.ProtoLog;
 import com.android.launcher3.icons.IconProvider;
 import com.android.wm.shell.R;
-import com.android.wm.shell.animation.Interpolators;
 import com.android.wm.shell.common.split.SplitScreenUtils;
 import com.android.wm.shell.protolog.ShellProtoLogGroup;
+import com.android.wm.shell.shared.animation.Interpolators;
 import com.android.wm.shell.splitscreen.SplitScreenController;
 
 import java.io.PrintWriter;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DropZoneView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DropZoneView.java
index 18cd2d8..f9749ec 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DropZoneView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DropZoneView.java
@@ -16,10 +16,9 @@
 
 package com.android.wm.shell.draganddrop;
 
-import static com.android.wm.shell.animation.Interpolators.FAST_OUT_SLOW_IN;
+import static com.android.wm.shell.shared.animation.Interpolators.FAST_OUT_SLOW_IN;
 
 import android.animation.Animator;
-import android.animation.AnimatorListenerAdapter;
 import android.animation.ObjectAnimator;
 import android.content.Context;
 import android.graphics.Canvas;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskListener.java b/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskListener.java
index 456767a..83cc18b 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskListener.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/FreeformTaskListener.java
@@ -27,6 +27,7 @@
 
 import com.android.internal.protolog.ProtoLog;
 import com.android.wm.shell.ShellTaskOrganizer;
+import com.android.wm.shell.common.LaunchAdjacentController;
 import com.android.wm.shell.desktopmode.DesktopModeTaskRepository;
 import com.android.wm.shell.protolog.ShellProtoLogGroup;
 import com.android.wm.shell.shared.desktopmode.DesktopModeStatus;
@@ -49,6 +50,7 @@
     private final ShellTaskOrganizer mShellTaskOrganizer;
     private final Optional<DesktopModeTaskRepository> mDesktopModeTaskRepository;
     private final WindowDecorViewModel mWindowDecorationViewModel;
+    private final LaunchAdjacentController mLaunchAdjacentController;
 
     private final SparseArray<State> mTasks = new SparseArray<>();
 
@@ -62,11 +64,13 @@
             ShellInit shellInit,
             ShellTaskOrganizer shellTaskOrganizer,
             Optional<DesktopModeTaskRepository> desktopModeTaskRepository,
+            LaunchAdjacentController launchAdjacentController,
             WindowDecorViewModel windowDecorationViewModel) {
         mContext = context;
         mShellTaskOrganizer = shellTaskOrganizer;
         mWindowDecorationViewModel = windowDecorationViewModel;
         mDesktopModeTaskRepository = desktopModeTaskRepository;
+        mLaunchAdjacentController = launchAdjacentController;
         if (shellInit != null) {
             shellInit.addInitCallback(this::onInit, this);
         }
@@ -106,6 +110,7 @@
                 }
             });
         }
+        updateLaunchAdjacentController();
     }
 
     @Override
@@ -123,6 +128,7 @@
         if (!Transitions.ENABLE_SHELL_TRANSITIONS) {
             mWindowDecorationViewModel.destroyWindowDecoration(taskInfo);
         }
+        updateLaunchAdjacentController();
     }
 
     @Override
@@ -144,6 +150,17 @@
                         taskInfo.isVisible);
             });
         }
+        updateLaunchAdjacentController();
+    }
+
+    private void updateLaunchAdjacentController() {
+        for (int i = 0; i < mTasks.size(); i++) {
+            if (mTasks.valueAt(i).mTaskInfo.isVisible) {
+                mLaunchAdjacentController.setLaunchAdjacentEnabled(false);
+                return;
+            }
+        }
+        mLaunchAdjacentController.setLaunchAdjacentEnabled(true);
     }
 
     @Override
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedController.java
index 962309f..1827923 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedController.java
@@ -23,7 +23,7 @@
 import static com.android.wm.shell.onehanded.OneHandedState.STATE_ENTERING;
 import static com.android.wm.shell.onehanded.OneHandedState.STATE_EXITING;
 import static com.android.wm.shell.onehanded.OneHandedState.STATE_NONE;
-import static com.android.wm.shell.sysui.ShellSharedConstants.KEY_EXTRA_SHELL_ONE_HANDED;
+import static com.android.wm.shell.shared.ShellSharedConstants.KEY_EXTRA_SHELL_ONE_HANDED;
 
 import android.annotation.BinderThread;
 import android.content.ComponentName;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipAnimationController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipAnimationController.java
index 852382d..b0c896f 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipAnimationController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipAnimationController.java
@@ -40,9 +40,9 @@
 import com.android.internal.graphics.SfVsyncFrameCallbackProvider;
 import com.android.internal.protolog.ProtoLog;
 import com.android.launcher3.icons.IconProvider;
-import com.android.wm.shell.animation.Interpolators;
 import com.android.wm.shell.common.pip.PipUtils;
 import com.android.wm.shell.protolog.ShellProtoLogGroup;
+import com.android.wm.shell.shared.animation.Interpolators;
 import com.android.wm.shell.transition.Transitions;
 
 import java.lang.annotation.Retention;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java
index 86777df..b3beb4a 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java
@@ -25,8 +25,6 @@
 
 import static com.android.wm.shell.ShellTaskOrganizer.TASK_LISTENER_TYPE_PIP;
 import static com.android.wm.shell.ShellTaskOrganizer.taskListenerTypeToString;
-import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_TOP_OR_LEFT;
-import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_UNDEFINED;
 import static com.android.wm.shell.pip.PipAnimationController.ANIM_TYPE_ALPHA;
 import static com.android.wm.shell.pip.PipAnimationController.ANIM_TYPE_BOUNDS;
 import static com.android.wm.shell.pip.PipAnimationController.FRACTION_START;
@@ -42,6 +40,8 @@
 import static com.android.wm.shell.pip.PipAnimationController.isInPipDirection;
 import static com.android.wm.shell.pip.PipAnimationController.isOutPipDirection;
 import static com.android.wm.shell.pip.PipAnimationController.isRemovePipDirection;
+import static com.android.wm.shell.shared.split.SplitScreenConstants.SPLIT_POSITION_TOP_OR_LEFT;
+import static com.android.wm.shell.shared.split.SplitScreenConstants.SPLIT_POSITION_UNDEFINED;
 import static com.android.wm.shell.transition.Transitions.ENABLE_SHELL_TRANSITIONS;
 import static com.android.wm.shell.transition.Transitions.TRANSIT_EXIT_PIP;
 import static com.android.wm.shell.transition.Transitions.TRANSIT_EXIT_PIP_TO_SPLIT;
@@ -76,7 +76,6 @@
 import com.android.internal.protolog.ProtoLog;
 import com.android.wm.shell.R;
 import com.android.wm.shell.ShellTaskOrganizer;
-import com.android.wm.shell.animation.Interpolators;
 import com.android.wm.shell.common.DisplayController;
 import com.android.wm.shell.common.ScreenshotUtils;
 import com.android.wm.shell.common.ShellExecutor;
@@ -90,6 +89,7 @@
 import com.android.wm.shell.common.pip.PipUtils;
 import com.android.wm.shell.pip.phone.PipMotionHelper;
 import com.android.wm.shell.protolog.ShellProtoLogGroup;
+import com.android.wm.shell.shared.animation.Interpolators;
 import com.android.wm.shell.shared.annotations.ShellMainThread;
 import com.android.wm.shell.splitscreen.SplitScreenController;
 import com.android.wm.shell.transition.Transitions;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java
index da6221e..5ec0c11 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipController.java
@@ -32,7 +32,7 @@
 import static com.android.wm.shell.pip.PipAnimationController.TRANSITION_DIRECTION_TO_PIP;
 import static com.android.wm.shell.pip.PipAnimationController.TRANSITION_DIRECTION_USER_RESIZE;
 import static com.android.wm.shell.pip.PipAnimationController.isOutPipDirection;
-import static com.android.wm.shell.sysui.ShellSharedConstants.KEY_EXTRA_SHELL_PIP;
+import static com.android.wm.shell.shared.ShellSharedConstants.KEY_EXTRA_SHELL_PIP;
 
 import android.app.ActivityManager;
 import android.app.ActivityTaskManager;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipDismissTargetHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipDismissTargetHandler.java
index f929389..0d2b8e7 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipDismissTargetHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipDismissTargetHandler.java
@@ -37,8 +37,8 @@
 import com.android.wm.shell.common.ShellExecutor;
 import com.android.wm.shell.common.bubbles.DismissCircleView;
 import com.android.wm.shell.common.bubbles.DismissView;
-import com.android.wm.shell.common.magnetictarget.MagnetizedObject;
 import com.android.wm.shell.common.pip.PipUiEventLogger;
+import com.android.wm.shell.shared.magnetictarget.MagnetizedObject;
 
 import kotlin.Unit;
 
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMenuView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMenuView.java
index 0d7f7f6..c8b52c6 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMenuView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMenuView.java
@@ -63,11 +63,11 @@
 
 import com.android.internal.protolog.ProtoLog;
 import com.android.wm.shell.R;
-import com.android.wm.shell.animation.Interpolators;
 import com.android.wm.shell.common.ShellExecutor;
 import com.android.wm.shell.common.pip.PipUiEventLogger;
 import com.android.wm.shell.common.pip.PipUtils;
 import com.android.wm.shell.protolog.ShellProtoLogGroup;
+import com.android.wm.shell.shared.animation.Interpolators;
 
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMotionHelper.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMotionHelper.java
index 999ab95..82fbfad 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMotionHelper.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/phone/PipMotionHelper.java
@@ -39,7 +39,6 @@
 import com.android.wm.shell.animation.FloatProperties;
 import com.android.wm.shell.common.FloatingContentCoordinator;
 import com.android.wm.shell.common.ShellExecutor;
-import com.android.wm.shell.common.magnetictarget.MagnetizedObject;
 import com.android.wm.shell.common.pip.PipAppOpsListener;
 import com.android.wm.shell.common.pip.PipBoundsState;
 import com.android.wm.shell.common.pip.PipPerfHintController;
@@ -49,6 +48,7 @@
 import com.android.wm.shell.protolog.ShellProtoLogGroup;
 import com.android.wm.shell.shared.animation.PhysicsAnimator;
 import com.android.wm.shell.shared.annotations.ShellMainThread;
+import com.android.wm.shell.shared.magnetictarget.MagnetizedObject;
 
 import kotlin.Unit;
 import kotlin.jvm.functions.Function0;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipController.java
index dc21f82..e9c4c14 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipController.java
@@ -19,7 +19,7 @@
 import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
 import static android.content.pm.PackageManager.FEATURE_PICTURE_IN_PICTURE;
 
-import static com.android.wm.shell.sysui.ShellSharedConstants.KEY_EXTRA_SHELL_PIP;
+import static com.android.wm.shell.shared.ShellSharedConstants.KEY_EXTRA_SHELL_PIP;
 
 import android.app.ActivityManager;
 import android.app.PictureInPictureParams;
@@ -29,7 +29,6 @@
 import android.content.res.Configuration;
 import android.graphics.Rect;
 import android.os.Bundle;
-import android.view.InsetsState;
 import android.view.SurfaceControl;
 import android.window.DisplayAreaInfo;
 import android.window.WindowContainerTransaction;
@@ -200,17 +199,8 @@
         DisplayLayout layout = new DisplayLayout(mContext, mContext.getDisplay());
         mPipDisplayLayoutState.setDisplayLayout(layout);
 
-        mDisplayController.addDisplayWindowListener(this);
         mDisplayController.addDisplayChangingController(this);
         mDisplayInsetsController.addInsetsChangedListener(mPipDisplayLayoutState.getDisplayId(),
-                new DisplayInsetsController.OnInsetsChangedListener() {
-                    @Override
-                    public void insetsChanged(InsetsState insetsState) {
-                        setDisplayLayout(mDisplayController
-                                        .getDisplayLayout(mPipDisplayLayoutState.getDisplayId()));
-                    }
-                });
-        mDisplayInsetsController.addInsetsChangedListener(mPipDisplayLayoutState.getDisplayId(),
                 new ImeListener(mDisplayController, mPipDisplayLayoutState.getDisplayId()) {
                     @Override
                     public void onImeVisibilityChanged(boolean imeVisible, int imeHeight) {
@@ -285,34 +275,37 @@
         setDisplayLayout(mDisplayController.getDisplayLayout(displayId));
     }
 
-    @Override
-    public void onDisplayConfigurationChanged(int displayId, Configuration newConfig) {
-        if (displayId != mPipDisplayLayoutState.getDisplayId()) {
-            return;
-        }
-        setDisplayLayout(mDisplayController.getDisplayLayout(displayId));
-    }
-
     /**
      * A callback for any observed transition that contains a display change in its
-     * {@link android.window.TransitionRequestInfo} with a non-zero rotation delta.
+     * {@link android.window.TransitionRequestInfo},
      */
     @Override
     public void onDisplayChange(int displayId, int fromRotation, int toRotation,
             @Nullable DisplayAreaInfo newDisplayAreaInfo, WindowContainerTransaction t) {
+        if (displayId != mPipDisplayLayoutState.getDisplayId()) {
+            return;
+        }
+        final float snapFraction = mPipBoundsAlgorithm.getSnapFraction(mPipBoundsState.getBounds());
+        final float boundsScale = mPipBoundsState.getBoundsScale();
+
+        // Update the display layout caches even if we are not in PiP.
+        setDisplayLayout(mDisplayController.getDisplayLayout(displayId));
+
         if (!mPipTransitionState.isInPip()) {
             return;
         }
 
-        // Calculate the snap fraction pre-rotation.
-        float snapFraction = mPipBoundsAlgorithm.getSnapFraction(mPipBoundsState.getBounds());
+        mPipTouchHandler.updateMinMaxSize(mPipBoundsState.getAspectRatio());
 
-        // Update the caches to reflect the new display layout and movement bounds.
-        mPipDisplayLayoutState.rotateTo(toRotation);
+        // Update the caches to reflect the new display layout in the movement bounds;
+        // temporarily update bounds to be at the top left for the movement bounds calculation.
+        Rect toBounds = new Rect(0, 0,
+                (int) Math.ceil(mPipBoundsState.getMaxSize().x * boundsScale),
+                (int) Math.ceil(mPipBoundsState.getMaxSize().y * boundsScale));
+        mPipBoundsState.setBounds(toBounds);
         mPipTouchHandler.updateMovementBounds();
 
-        // The policy is to keep PiP width, height and snap fraction invariant.
-        Rect toBounds = mPipBoundsState.getBounds();
+        // The policy is to keep PiP snap fraction invariant.
         mPipBoundsAlgorithm.applySnapFraction(toBounds, snapFraction);
         mPipBoundsState.setBounds(toBounds);
         t.setBounds(mPipTransitionState.mPipTaskToken, toBounds);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipDismissTargetHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipDismissTargetHandler.java
index e7e7970..e04178e 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipDismissTargetHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipDismissTargetHandler.java
@@ -37,8 +37,8 @@
 import com.android.wm.shell.common.ShellExecutor;
 import com.android.wm.shell.common.bubbles.DismissCircleView;
 import com.android.wm.shell.common.bubbles.DismissView;
-import com.android.wm.shell.common.magnetictarget.MagnetizedObject;
 import com.android.wm.shell.common.pip.PipUiEventLogger;
+import com.android.wm.shell.shared.magnetictarget.MagnetizedObject;
 
 import kotlin.Unit;
 
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipMenuView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipMenuView.java
index c54e4cd..a29104c 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipMenuView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipMenuView.java
@@ -61,11 +61,11 @@
 
 import com.android.internal.protolog.ProtoLog;
 import com.android.wm.shell.R;
-import com.android.wm.shell.animation.Interpolators;
 import com.android.wm.shell.common.ShellExecutor;
 import com.android.wm.shell.common.pip.PipUiEventLogger;
 import com.android.wm.shell.common.pip.PipUtils;
 import com.android.wm.shell.protolog.ShellProtoLogGroup;
+import com.android.wm.shell.shared.animation.Interpolators;
 
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipMotionHelper.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipMotionHelper.java
index 83253c6..218d456 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipMotionHelper.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipMotionHelper.java
@@ -42,7 +42,6 @@
 import com.android.wm.shell.R;
 import com.android.wm.shell.animation.FloatProperties;
 import com.android.wm.shell.common.FloatingContentCoordinator;
-import com.android.wm.shell.common.magnetictarget.MagnetizedObject;
 import com.android.wm.shell.common.pip.PipAppOpsListener;
 import com.android.wm.shell.common.pip.PipBoundsAlgorithm;
 import com.android.wm.shell.common.pip.PipBoundsState;
@@ -51,6 +50,7 @@
 import com.android.wm.shell.pip2.animation.PipResizeAnimator;
 import com.android.wm.shell.protolog.ShellProtoLogGroup;
 import com.android.wm.shell.shared.animation.PhysicsAnimator;
+import com.android.wm.shell.shared.magnetictarget.MagnetizedObject;
 
 import kotlin.Unit;
 import kotlin.jvm.functions.Function0;
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 846fa62..ed18712 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
@@ -119,7 +119,8 @@
             PipMenuController pipMenuController,
             PipBoundsAlgorithm pipBoundsAlgorithm,
             PipScheduler pipScheduler,
-            PipTransitionState pipTransitionState) {
+            PipTransitionState pipTransitionState,
+            PipUiStateChangeController pipUiStateChangeController) {
         super(shellInit, shellTaskOrganizer, transitions, pipBoundsState, pipMenuController,
                 pipBoundsAlgorithm);
 
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipUiStateChangeController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipUiStateChangeController.java
new file mode 100644
index 0000000..224016e
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipUiStateChangeController.java
@@ -0,0 +1,83 @@
+/*
+ * 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.wm.shell.pip2.phone;
+
+import android.app.ActivityTaskManager;
+import android.app.Flags;
+import android.app.PictureInPictureUiState;
+import android.os.Bundle;
+import android.os.RemoteException;
+
+import androidx.annotation.Nullable;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.protolog.ProtoLog;
+import com.android.wm.shell.protolog.ShellProtoLogGroup;
+
+import java.util.function.Consumer;
+
+/**
+ * Controller class manages the {@link android.app.PictureInPictureUiState} callbacks sent to app.
+ */
+public class PipUiStateChangeController implements
+        PipTransitionState.PipTransitionStateChangedListener {
+
+    private final PipTransitionState mPipTransitionState;
+
+    private Consumer<PictureInPictureUiState> mPictureInPictureUiStateConsumer;
+
+    public PipUiStateChangeController(PipTransitionState pipTransitionState) {
+        mPipTransitionState = pipTransitionState;
+        mPipTransitionState.addPipTransitionStateChangedListener(this);
+        mPictureInPictureUiStateConsumer = pictureInPictureUiState -> {
+            try {
+                ActivityTaskManager.getService().onPictureInPictureUiStateChanged(
+                        pictureInPictureUiState);
+            } catch (RemoteException | IllegalStateException e) {
+                ProtoLog.e(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+                        "Failed to send PictureInPictureUiState.");
+            }
+        };
+    }
+
+    @Override
+    public void onPipTransitionStateChanged(@PipTransitionState.TransitionState int oldState,
+            @PipTransitionState.TransitionState int newState, @Nullable Bundle extra) {
+        if (newState == PipTransitionState.SWIPING_TO_PIP) {
+            onIsTransitioningToPipUiStateChange(true /* isTransitioningToPip */);
+        } else if (newState == PipTransitionState.ENTERING_PIP
+                && !mPipTransitionState.isInSwipePipToHomeTransition()) {
+            onIsTransitioningToPipUiStateChange(true /* isTransitioningToPip */);
+        } else if (newState == PipTransitionState.ENTERED_PIP) {
+            onIsTransitioningToPipUiStateChange(false /* isTransitioningToPip */);
+        }
+    }
+
+    @VisibleForTesting
+    void setPictureInPictureUiStateConsumer(Consumer<PictureInPictureUiState> consumer) {
+        mPictureInPictureUiStateConsumer = consumer;
+    }
+
+    private void onIsTransitioningToPipUiStateChange(boolean isTransitioningToPip) {
+        if (Flags.enablePipUiStateCallbackOnEntering()
+                && mPictureInPictureUiStateConsumer != null) {
+            mPictureInPictureUiStateConsumer.accept(new PictureInPictureUiState.Builder()
+                    .setTransitioningToPip(isTransitioningToPip)
+                    .build());
+        }
+    }
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java
index da7e03f..2f0af855 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentTasksController.java
@@ -19,7 +19,7 @@
 import static android.app.ActivityTaskManager.INVALID_TASK_ID;
 import static android.content.pm.PackageManager.FEATURE_PC;
 
-import static com.android.wm.shell.sysui.ShellSharedConstants.KEY_EXTRA_SHELL_RECENT_TASKS;
+import static com.android.wm.shell.shared.ShellSharedConstants.KEY_EXTRA_SHELL_RECENT_TASKS;
 
 import android.app.ActivityManager;
 import android.app.ActivityTaskManager;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java
index ad3f4f8..7a9eb1c 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java
@@ -29,7 +29,7 @@
 import static android.window.TransitionInfo.FLAG_MOVED_TO_TOP;
 import static android.window.TransitionInfo.FLAG_TRANSLUCENT;
 
-import static com.android.wm.shell.sysui.ShellSharedConstants.KEY_EXTRA_SHELL_CAN_HAND_OFF_ANIMATION;
+import static com.android.wm.shell.shared.ShellSharedConstants.KEY_EXTRA_SHELL_CAN_HAND_OFF_ANIMATION;
 import static com.android.wm.shell.util.SplitBounds.KEY_EXTRA_SPLIT_BOUNDS;
 
 import android.annotation.Nullable;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreen.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreen.java
index 06c57bd..a6233dc9 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreen.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreen.java
@@ -25,9 +25,9 @@
 import android.window.RemoteTransition;
 
 import com.android.internal.logging.InstanceId;
-import com.android.wm.shell.common.split.SplitScreenConstants.PersistentSnapPosition;
-import com.android.wm.shell.common.split.SplitScreenConstants.SplitPosition;
 import com.android.wm.shell.shared.annotations.ExternalThread;
+import com.android.wm.shell.shared.split.SplitScreenConstants.PersistentSnapPosition;
+import com.android.wm.shell.shared.split.SplitScreenConstants.SplitPosition;
 
 import java.util.concurrent.Executor;
 
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java
index 83f827a..7e165af 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenController.java
@@ -27,16 +27,16 @@
 import static com.android.wm.shell.common.MultiInstanceHelper.getComponent;
 import static com.android.wm.shell.common.MultiInstanceHelper.getShortcutComponent;
 import static com.android.wm.shell.common.MultiInstanceHelper.samePackage;
-import static com.android.wm.shell.common.split.SplitScreenConstants.KEY_EXTRA_WIDGET_INTENT;
-import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_BOTTOM_OR_RIGHT;
-import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_TOP_OR_LEFT;
-import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_UNDEFINED;
 import static com.android.wm.shell.common.split.SplitScreenUtils.isValidToSplit;
 import static com.android.wm.shell.common.split.SplitScreenUtils.reverseSplitPosition;
 import static com.android.wm.shell.common.split.SplitScreenUtils.splitFailureMessage;
 import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN;
+import static com.android.wm.shell.shared.ShellSharedConstants.KEY_EXTRA_SHELL_SPLIT_SCREEN;
+import static com.android.wm.shell.shared.split.SplitScreenConstants.KEY_EXTRA_WIDGET_INTENT;
+import static com.android.wm.shell.shared.split.SplitScreenConstants.SPLIT_POSITION_BOTTOM_OR_RIGHT;
+import static com.android.wm.shell.shared.split.SplitScreenConstants.SPLIT_POSITION_TOP_OR_LEFT;
+import static com.android.wm.shell.shared.split.SplitScreenConstants.SPLIT_POSITION_UNDEFINED;
 import static com.android.wm.shell.splitscreen.SplitScreen.STAGE_TYPE_UNDEFINED;
-import static com.android.wm.shell.sysui.ShellSharedConstants.KEY_EXTRA_SHELL_SPLIT_SCREEN;
 
 import android.app.ActivityManager;
 import android.app.ActivityOptions;
@@ -90,16 +90,16 @@
 import com.android.wm.shell.common.ShellExecutor;
 import com.android.wm.shell.common.SingleInstanceRemoteListener;
 import com.android.wm.shell.common.SyncTransactionQueue;
-import com.android.wm.shell.common.TransactionPool;
-import com.android.wm.shell.common.split.SplitScreenConstants.PersistentSnapPosition;
-import com.android.wm.shell.common.split.SplitScreenConstants.SplitPosition;
 import com.android.wm.shell.common.split.SplitScreenUtils;
 import com.android.wm.shell.desktopmode.DesktopTasksController;
 import com.android.wm.shell.draganddrop.DragAndDropController;
 import com.android.wm.shell.draganddrop.DragAndDropPolicy;
 import com.android.wm.shell.protolog.ShellProtoLogGroup;
 import com.android.wm.shell.recents.RecentTasksController;
+import com.android.wm.shell.shared.TransactionPool;
 import com.android.wm.shell.shared.annotations.ExternalThread;
+import com.android.wm.shell.shared.split.SplitScreenConstants.PersistentSnapPosition;
+import com.android.wm.shell.shared.split.SplitScreenConstants.SplitPosition;
 import com.android.wm.shell.splitscreen.SplitScreen.StageType;
 import com.android.wm.shell.sysui.KeyguardChangeListener;
 import com.android.wm.shell.sysui.ShellCommandHandler;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenShellCommandHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenShellCommandHandler.java
index af11ebc..e1b474d 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenShellCommandHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenShellCommandHandler.java
@@ -16,7 +16,7 @@
 
 package com.android.wm.shell.splitscreen;
 
-import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_BOTTOM_OR_RIGHT;
+import static com.android.wm.shell.shared.split.SplitScreenConstants.SPLIT_POSITION_BOTTOM_OR_RIGHT;
 import static com.android.wm.shell.splitscreen.SplitScreenController.EXIT_REASON_UNKNOWN;
 
 import com.android.wm.shell.sysui.ShellCommandHandler;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenTransitions.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenTransitions.java
index c1f60383..84004941 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenTransitions.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitScreenTransitions.java
@@ -21,12 +21,12 @@
 import static android.view.WindowManager.TRANSIT_OPEN;
 import static android.view.WindowManager.TRANSIT_TO_BACK;
 
-import static com.android.wm.shell.animation.Interpolators.ALPHA_IN;
-import static com.android.wm.shell.animation.Interpolators.ALPHA_OUT;
-import static com.android.wm.shell.common.split.SplitScreenConstants.FADE_DURATION;
-import static com.android.wm.shell.common.split.SplitScreenConstants.FLAG_IS_DIVIDER_BAR;
 import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN;
 import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_TRANSITIONS;
+import static com.android.wm.shell.shared.animation.Interpolators.ALPHA_IN;
+import static com.android.wm.shell.shared.animation.Interpolators.ALPHA_OUT;
+import static com.android.wm.shell.shared.split.SplitScreenConstants.FADE_DURATION;
+import static com.android.wm.shell.shared.split.SplitScreenConstants.FLAG_IS_DIVIDER_BAR;
 import static com.android.wm.shell.splitscreen.SplitScreen.stageTypeToString;
 import static com.android.wm.shell.splitscreen.SplitScreenController.EXIT_REASON_DRAG_DIVIDER;
 import static com.android.wm.shell.splitscreen.SplitScreenController.exitReasonToString;
@@ -47,9 +47,9 @@
 import android.window.WindowContainerTransaction;
 
 import com.android.internal.protolog.ProtoLog;
-import com.android.wm.shell.common.TransactionPool;
 import com.android.wm.shell.common.split.SplitDecorManager;
 import com.android.wm.shell.protolog.ShellProtoLogGroup;
+import com.android.wm.shell.shared.TransactionPool;
 import com.android.wm.shell.shared.TransitionUtil;
 import com.android.wm.shell.transition.OneShotRemoteHandler;
 import com.android.wm.shell.transition.Transitions;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitscreenEventLogger.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitscreenEventLogger.java
index a0bf843..27ded57 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitscreenEventLogger.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/SplitscreenEventLogger.java
@@ -32,8 +32,8 @@
 import static com.android.internal.util.FrameworkStatsLog.SPLITSCREEN_UICHANGED__EXIT_REASON__SCREEN_LOCKED;
 import static com.android.internal.util.FrameworkStatsLog.SPLITSCREEN_UICHANGED__EXIT_REASON__SCREEN_LOCKED_SHOW_ON_TOP;
 import static com.android.internal.util.FrameworkStatsLog.SPLITSCREEN_UICHANGED__EXIT_REASON__UNKNOWN_EXIT;
-import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_TOP_OR_LEFT;
-import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_UNDEFINED;
+import static com.android.wm.shell.shared.split.SplitScreenConstants.SPLIT_POSITION_TOP_OR_LEFT;
+import static com.android.wm.shell.shared.split.SplitScreenConstants.SPLIT_POSITION_UNDEFINED;
 import static com.android.wm.shell.splitscreen.SplitScreenController.ENTER_REASON_DRAG;
 import static com.android.wm.shell.splitscreen.SplitScreenController.ENTER_REASON_LAUNCHER;
 import static com.android.wm.shell.splitscreen.SplitScreenController.ENTER_REASON_MULTI_INSTANCE;
@@ -58,7 +58,7 @@
 import com.android.internal.logging.InstanceId;
 import com.android.internal.logging.InstanceIdSequence;
 import com.android.internal.util.FrameworkStatsLog;
-import com.android.wm.shell.common.split.SplitScreenConstants.SplitPosition;
+import com.android.wm.shell.shared.split.SplitScreenConstants.SplitPosition;
 import com.android.wm.shell.splitscreen.SplitScreenController.ExitReason;
 
 /**
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 9bf5159..0b5c751 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
@@ -34,16 +34,16 @@
 import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_REORDER;
 
 import static com.android.wm.shell.common.split.SplitLayout.PARALLAX_ALIGN_CENTER;
-import static com.android.wm.shell.common.split.SplitScreenConstants.FLAG_IS_DIVIDER_BAR;
-import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_BOTTOM_OR_RIGHT;
-import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_TOP_OR_LEFT;
-import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_UNDEFINED;
-import static com.android.wm.shell.common.split.SplitScreenConstants.splitPositionToString;
 import static com.android.wm.shell.common.split.SplitScreenUtils.reverseSplitPosition;
 import static com.android.wm.shell.common.split.SplitScreenUtils.splitFailureMessage;
 import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN;
 import static com.android.wm.shell.shared.TransitionUtil.isClosingType;
 import static com.android.wm.shell.shared.TransitionUtil.isOpeningType;
+import static com.android.wm.shell.shared.split.SplitScreenConstants.FLAG_IS_DIVIDER_BAR;
+import static com.android.wm.shell.shared.split.SplitScreenConstants.SPLIT_POSITION_BOTTOM_OR_RIGHT;
+import static com.android.wm.shell.shared.split.SplitScreenConstants.SPLIT_POSITION_TOP_OR_LEFT;
+import static com.android.wm.shell.shared.split.SplitScreenConstants.SPLIT_POSITION_UNDEFINED;
+import static com.android.wm.shell.shared.split.SplitScreenConstants.splitPositionToString;
 import static com.android.wm.shell.splitscreen.SplitScreen.STAGE_TYPE_MAIN;
 import static com.android.wm.shell.splitscreen.SplitScreen.STAGE_TYPE_SIDE;
 import static com.android.wm.shell.splitscreen.SplitScreen.STAGE_TYPE_UNDEFINED;
@@ -125,16 +125,16 @@
 import com.android.wm.shell.common.LaunchAdjacentController;
 import com.android.wm.shell.common.ShellExecutor;
 import com.android.wm.shell.common.SyncTransactionQueue;
-import com.android.wm.shell.common.TransactionPool;
 import com.android.wm.shell.common.split.SplitDecorManager;
 import com.android.wm.shell.common.split.SplitLayout;
-import com.android.wm.shell.common.split.SplitScreenConstants.PersistentSnapPosition;
-import com.android.wm.shell.common.split.SplitScreenConstants.SplitPosition;
 import com.android.wm.shell.common.split.SplitScreenUtils;
 import com.android.wm.shell.common.split.SplitWindowManager;
 import com.android.wm.shell.protolog.ShellProtoLogGroup;
 import com.android.wm.shell.recents.RecentTasksController;
+import com.android.wm.shell.shared.TransactionPool;
 import com.android.wm.shell.shared.TransitionUtil;
+import com.android.wm.shell.shared.split.SplitScreenConstants.PersistentSnapPosition;
+import com.android.wm.shell.shared.split.SplitScreenConstants.SplitPosition;
 import com.android.wm.shell.splitscreen.SplitScreen.StageType;
 import com.android.wm.shell.splitscreen.SplitScreenController.ExitReason;
 import com.android.wm.shell.transition.DefaultMixedHandler;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageTaskListener.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageTaskListener.java
index f19eb3f..99f3832 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageTaskListener.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageTaskListener.java
@@ -22,9 +22,9 @@
 import static android.content.res.Configuration.SMALLEST_SCREEN_WIDTH_DP_UNDEFINED;
 import static android.view.RemoteAnimationTarget.MODE_OPENING;
 
-import static com.android.wm.shell.common.split.SplitScreenConstants.CONTROLLED_ACTIVITY_TYPES;
-import static com.android.wm.shell.common.split.SplitScreenConstants.CONTROLLED_WINDOWING_MODES;
-import static com.android.wm.shell.common.split.SplitScreenConstants.CONTROLLED_WINDOWING_MODES_WHEN_ACTIVE;
+import static com.android.wm.shell.shared.split.SplitScreenConstants.CONTROLLED_ACTIVITY_TYPES;
+import static com.android.wm.shell.shared.split.SplitScreenConstants.CONTROLLED_WINDOWING_MODES;
+import static com.android.wm.shell.shared.split.SplitScreenConstants.CONTROLLED_WINDOWING_MODES_WHEN_ACTIVE;
 import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN;
 import static com.android.wm.shell.transition.Transitions.ENABLE_SHELL_TRANSITIONS;
 
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/tv/TvSplitMenuController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/tv/TvSplitMenuController.java
index e0f6394..bb2f60b 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/tv/TvSplitMenuController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/tv/TvSplitMenuController.java
@@ -39,8 +39,8 @@
 import com.android.internal.protolog.ProtoLog;
 import com.android.wm.shell.R;
 import com.android.wm.shell.common.SystemWindows;
-import com.android.wm.shell.common.split.SplitScreenConstants;
 import com.android.wm.shell.protolog.ShellProtoLogGroup;
+import com.android.wm.shell.shared.split.SplitScreenConstants;
 
 /**
  * Handles the interaction logic with the {@link TvSplitMenuView}.
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/tv/TvSplitMenuView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/tv/TvSplitMenuView.java
index 88e9757..b758b53 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/tv/TvSplitMenuView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/tv/TvSplitMenuView.java
@@ -19,8 +19,8 @@
 import static android.view.KeyEvent.ACTION_DOWN;
 import static android.view.KeyEvent.KEYCODE_BACK;
 
-import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_BOTTOM_OR_RIGHT;
-import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_TOP_OR_LEFT;
+import static com.android.wm.shell.shared.split.SplitScreenConstants.SPLIT_POSITION_BOTTOM_OR_RIGHT;
+import static com.android.wm.shell.shared.split.SplitScreenConstants.SPLIT_POSITION_TOP_OR_LEFT;
 
 import android.content.Context;
 import android.util.AttributeSet;
@@ -31,7 +31,7 @@
 import androidx.annotation.Nullable;
 
 import com.android.wm.shell.R;
-import com.android.wm.shell.common.split.SplitScreenConstants;
+import com.android.wm.shell.shared.split.SplitScreenConstants;
 
 /**
  * A View for the Menu Window.
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/tv/TvSplitScreenController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/tv/TvSplitScreenController.java
index b65e978..3468156 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/tv/TvSplitScreenController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/tv/TvSplitScreenController.java
@@ -32,8 +32,8 @@
 import com.android.wm.shell.common.ShellExecutor;
 import com.android.wm.shell.common.SyncTransactionQueue;
 import com.android.wm.shell.common.SystemWindows;
-import com.android.wm.shell.common.TransactionPool;
 import com.android.wm.shell.recents.RecentTasksController;
+import com.android.wm.shell.shared.TransactionPool;
 import com.android.wm.shell.splitscreen.SplitScreenController;
 import com.android.wm.shell.splitscreen.StageCoordinator;
 import com.android.wm.shell.sysui.ShellCommandHandler;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/tv/TvStageCoordinator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/tv/TvStageCoordinator.java
index 81ca48f..4451ee8 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/tv/TvStageCoordinator.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/tv/TvStageCoordinator.java
@@ -28,9 +28,9 @@
 import com.android.wm.shell.common.ShellExecutor;
 import com.android.wm.shell.common.SyncTransactionQueue;
 import com.android.wm.shell.common.SystemWindows;
-import com.android.wm.shell.common.TransactionPool;
-import com.android.wm.shell.common.split.SplitScreenConstants;
 import com.android.wm.shell.recents.RecentTasksController;
+import com.android.wm.shell.shared.TransactionPool;
+import com.android.wm.shell.shared.split.SplitScreenConstants;
 import com.android.wm.shell.splitscreen.StageCoordinator;
 import com.android.wm.shell.transition.Transitions;
 
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashScreenExitAnimation.java b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashScreenExitAnimation.java
index edb5aba..42b8b73 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashScreenExitAnimation.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashScreenExitAnimation.java
@@ -29,7 +29,8 @@
 
 import com.android.internal.jank.InteractionJankMonitor;
 import com.android.wm.shell.R;
-import com.android.wm.shell.common.TransactionPool;
+import com.android.wm.shell.shared.TransactionPool;
+import com.android.wm.shell.shared.startingsurface.SplashScreenExitAnimationUtils;
 
 /**
  * Default animation for exiting the splash screen window.
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenContentDrawer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenContentDrawer.java
index 759f97f..b18feefe 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenContentDrawer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashscreenContentDrawer.java
@@ -78,8 +78,8 @@
 import com.android.internal.protolog.ProtoLog;
 import com.android.launcher3.icons.BaseIconFactory;
 import com.android.launcher3.icons.IconProvider;
-import com.android.wm.shell.common.TransactionPool;
 import com.android.wm.shell.protolog.ShellProtoLogGroup;
+import com.android.wm.shell.shared.TransactionPool;
 
 import java.util.List;
 import java.util.function.Consumer;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawer.java
index 97a695f..fac3592 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawer.java
@@ -46,8 +46,8 @@
 import com.android.internal.protolog.ProtoLog;
 import com.android.launcher3.icons.IconProvider;
 import com.android.wm.shell.common.ShellExecutor;
-import com.android.wm.shell.common.TransactionPool;
 import com.android.wm.shell.protolog.ShellProtoLogGroup;
+import com.android.wm.shell.shared.TransactionPool;
 import com.android.wm.shell.shared.annotations.ShellSplashscreenThread;
 
 /**
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingWindowController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingWindowController.java
index fa084c58..7cb8e8a 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingWindowController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/StartingWindowController.java
@@ -23,7 +23,7 @@
 import static android.window.StartingWindowInfo.STARTING_WINDOW_TYPE_SPLASH_SCREEN;
 import static android.window.StartingWindowInfo.STARTING_WINDOW_TYPE_WINDOWLESS;
 
-import static com.android.wm.shell.sysui.ShellSharedConstants.KEY_EXTRA_SHELL_STARTING_WINDOW;
+import static com.android.wm.shell.shared.ShellSharedConstants.KEY_EXTRA_SHELL_STARTING_WINDOW;
 
 import android.app.ActivityManager.RunningTaskInfo;
 import android.app.TaskInfo;
@@ -48,7 +48,7 @@
 import com.android.wm.shell.common.RemoteCallable;
 import com.android.wm.shell.common.ShellExecutor;
 import com.android.wm.shell.common.SingleInstanceRemoteListener;
-import com.android.wm.shell.common.TransactionPool;
+import com.android.wm.shell.shared.TransactionPool;
 import com.android.wm.shell.sysui.ShellController;
 import com.android.wm.shell.sysui.ShellInit;
 
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/WindowlessSnapshotWindowCreator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/WindowlessSnapshotWindowCreator.java
index bad5baf..2a22d4d 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/WindowlessSnapshotWindowCreator.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/WindowlessSnapshotWindowCreator.java
@@ -36,7 +36,7 @@
 import android.window.TaskSnapshot;
 
 import com.android.wm.shell.common.ShellExecutor;
-import com.android.wm.shell.common.TransactionPool;
+import com.android.wm.shell.shared.TransactionPool;
 
 class WindowlessSnapshotWindowCreator {
     private static final int DEFAULT_FADEOUT_DURATION = 233;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/WindowlessSplashWindowCreator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/WindowlessSplashWindowCreator.java
index 1a38449..e1d7600 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/WindowlessSplashWindowCreator.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/WindowlessSplashWindowCreator.java
@@ -36,7 +36,7 @@
 import android.window.StartingWindowRemovalInfo;
 
 import com.android.wm.shell.common.ShellExecutor;
-import com.android.wm.shell.common.TransactionPool;
+import com.android.wm.shell.shared.TransactionPool;
 
 class WindowlessSplashWindowCreator extends AbsSplashWindowCreator {
 
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/sysui/ShellInit.java b/libs/WindowManager/Shell/src/com/android/wm/shell/sysui/ShellInit.java
index 287e779..1140c82 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/sysui/ShellInit.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/sysui/ShellInit.java
@@ -48,7 +48,7 @@
 
     public ShellInit(ShellExecutor mainExecutor) {
         mMainExecutor = mainExecutor;
-        ProtoLog.registerGroups(ShellProtoLogGroup.values());
+        ProtoLog.init(ShellProtoLogGroup.values());
     }
 
     /**
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 de6887a2..a9a4e10 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
@@ -18,8 +18,8 @@
 
 import static android.app.ActivityOptions.ANIM_CLIP_REVEAL;
 import static android.app.ActivityOptions.ANIM_CUSTOM;
-import static android.app.ActivityOptions.ANIM_NONE;
 import static android.app.ActivityOptions.ANIM_FROM_STYLE;
+import static android.app.ActivityOptions.ANIM_NONE;
 import static android.app.ActivityOptions.ANIM_OPEN_CROSS_PROFILE_APPS;
 import static android.app.ActivityOptions.ANIM_SCALE_UP;
 import static android.app.ActivityOptions.ANIM_SCENE_TRANSITION;
@@ -112,8 +112,8 @@
 import com.android.wm.shell.common.DisplayController;
 import com.android.wm.shell.common.DisplayLayout;
 import com.android.wm.shell.common.ShellExecutor;
-import com.android.wm.shell.common.TransactionPool;
 import com.android.wm.shell.protolog.ShellProtoLogGroup;
+import com.android.wm.shell.shared.TransactionPool;
 import com.android.wm.shell.shared.TransitionUtil;
 import com.android.wm.shell.sysui.ShellInit;
 
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/MixedTransitionHelper.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/MixedTransitionHelper.java
index 8cc7f21..30d7245 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/MixedTransitionHelper.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/MixedTransitionHelper.java
@@ -20,8 +20,8 @@
 import static android.view.WindowManager.TRANSIT_TO_BACK;
 import static android.window.TransitionInfo.FLAG_IS_WALLPAPER;
 
-import static com.android.wm.shell.common.split.SplitScreenConstants.FLAG_IS_DIVIDER_BAR;
-import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_UNDEFINED;
+import static com.android.wm.shell.shared.split.SplitScreenConstants.FLAG_IS_DIVIDER_BAR;
+import static com.android.wm.shell.shared.split.SplitScreenConstants.SPLIT_POSITION_UNDEFINED;
 import static com.android.wm.shell.pip.PipAnimationController.ANIM_TYPE_ALPHA;
 import static com.android.wm.shell.shared.TransitionUtil.isOpeningMode;
 import static com.android.wm.shell.splitscreen.SplitScreen.STAGE_TYPE_MAIN;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/OneShotRemoteHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/OneShotRemoteHandler.java
index b7b42c7..209fc39 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/OneShotRemoteHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/OneShotRemoteHandler.java
@@ -114,7 +114,6 @@
                 t.clear();
                 mMainExecutor.execute(() -> {
                     finishCallback.onTransitionFinished(wct);
-                    mRemote = null;
                 });
             }
         };
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/RecentsMixedTransition.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/RecentsMixedTransition.java
index 391c5fe..fd4d568 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/RecentsMixedTransition.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/RecentsMixedTransition.java
@@ -18,7 +18,7 @@
 
 import static android.view.WindowManager.TRANSIT_FLAG_KEYGUARD_UNOCCLUDING;
 
-import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_UNDEFINED;
+import static com.android.wm.shell.shared.split.SplitScreenConstants.SPLIT_POSITION_UNDEFINED;
 import static com.android.wm.shell.transition.DefaultMixedHandler.handoverTransitionLeashes;
 import static com.android.wm.shell.transition.MixedTransitionHelper.animateEnterPipFromSplit;
 import static com.android.wm.shell.transition.MixedTransitionHelper.animateKeyguard;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/ScreenRotationAnimation.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/ScreenRotationAnimation.java
index 1958825..0bf9d36 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/ScreenRotationAnimation.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/ScreenRotationAnimation.java
@@ -47,7 +47,7 @@
 import com.android.internal.R;
 import com.android.internal.policy.TransitionAnimation;
 import com.android.wm.shell.common.ShellExecutor;
-import com.android.wm.shell.common.TransactionPool;
+import com.android.wm.shell.shared.TransactionPool;
 
 import java.util.ArrayList;
 
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 c850ff8..f0d3668 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
@@ -38,12 +38,12 @@
 import static android.window.TransitionInfo.FLAG_NO_ANIMATION;
 import static android.window.TransitionInfo.FLAG_STARTING_WINDOW_TRANSFER_RECIPIENT;
 
-import static com.android.window.flags.Flags.ensureWallpaperInTransitions;
 import static com.android.systemui.shared.Flags.returnAnimationFrameworkLibrary;
+import static com.android.window.flags.Flags.ensureWallpaperInTransitions;
 import static com.android.window.flags.Flags.migratePredictiveBackTransition;
+import static com.android.wm.shell.shared.ShellSharedConstants.KEY_EXTRA_SHELL_SHELL_TRANSITIONS;
 import static com.android.wm.shell.shared.TransitionUtil.isClosingType;
 import static com.android.wm.shell.shared.TransitionUtil.isOpeningType;
-import static com.android.wm.shell.sysui.ShellSharedConstants.KEY_EXTRA_SHELL_SHELL_TRANSITIONS;
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
@@ -85,12 +85,12 @@
 import com.android.wm.shell.common.ExternalInterfaceBinder;
 import com.android.wm.shell.common.RemoteCallable;
 import com.android.wm.shell.common.ShellExecutor;
-import com.android.wm.shell.common.TransactionPool;
 import com.android.wm.shell.keyguard.KeyguardTransitionHandler;
 import com.android.wm.shell.protolog.ShellProtoLogGroup;
 import com.android.wm.shell.shared.IHomeTransitionListener;
 import com.android.wm.shell.shared.IShellTransitions;
 import com.android.wm.shell.shared.ShellTransitions;
+import com.android.wm.shell.shared.TransactionPool;
 import com.android.wm.shell.shared.TransitionUtil;
 import com.android.wm.shell.shared.annotations.ExternalThread;
 import com.android.wm.shell.sysui.ShellCommandHandler;
@@ -717,7 +717,11 @@
                 Log.e(TAG, "Got duplicate transitionReady for " + transitionToken);
                 // The transition is already somewhere else in the pipeline, so just return here.
                 t.apply();
-                existing.mFinishT.merge(finishT);
+                if (existing.mFinishT != null) {
+                    existing.mFinishT.merge(finishT);
+                } else {
+                    existing.mFinishT = finishT;
+                }
                 return;
             }
             // This usually means the system is in a bad state and may not recover; however,
@@ -1183,12 +1187,15 @@
             }
             if (request.getDisplayChange() != null) {
                 TransitionRequestInfo.DisplayChange change = request.getDisplayChange();
-                if (change.getEndRotation() != change.getStartRotation()) {
-                    // Is a rotation, so dispatch to all displayChange listeners
+                if (change.getStartRotation() != change.getEndRotation()
+                        || (change.getStartAbsBounds() != null
+                        && !change.getStartAbsBounds().equals(change.getEndAbsBounds()))) {
+                    // Is a display change, so dispatch to all displayChange listeners
                     if (wct == null) {
                         wct = new WindowContainerTransaction();
                     }
-                    mDisplayController.onDisplayRotateRequested(wct, change.getDisplayId(),
+                    mDisplayController.onDisplayChangeRequested(wct, change.getDisplayId(),
+                            change.getStartAbsBounds(), change.getEndAbsBounds(),
                             change.getStartRotation(), change.getEndRotation());
                 }
             }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/UnfoldAnimationController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/UnfoldAnimationController.java
index e6d35e8..2ca749c 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/UnfoldAnimationController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/UnfoldAnimationController.java
@@ -23,7 +23,7 @@
 import android.view.SurfaceControl;
 
 import com.android.wm.shell.common.ShellExecutor;
-import com.android.wm.shell.common.TransactionPool;
+import com.android.wm.shell.shared.TransactionPool;
 import com.android.wm.shell.sysui.ShellInit;
 import com.android.wm.shell.unfold.ShellUnfoldProgressProvider.UnfoldListener;
 import com.android.wm.shell.unfold.animation.UnfoldTaskAnimator;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/UnfoldTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/UnfoldTransitionHandler.java
index 88bfebf..f783b45 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/UnfoldTransitionHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/UnfoldTransitionHandler.java
@@ -34,7 +34,7 @@
 import androidx.annotation.Nullable;
 
 import com.android.internal.protolog.ProtoLog;
-import com.android.wm.shell.common.TransactionPool;
+import com.android.wm.shell.shared.TransactionPool;
 import com.android.wm.shell.shared.TransitionUtil;
 import com.android.wm.shell.sysui.ShellInit;
 import com.android.wm.shell.transition.Transitions;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/animation/SplitTaskUnfoldAnimator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/animation/SplitTaskUnfoldAnimator.java
index bb5d546..d28287d 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/animation/SplitTaskUnfoldAnimator.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/unfold/animation/SplitTaskUnfoldAnimator.java
@@ -19,8 +19,8 @@
 import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
 import static android.view.Display.DEFAULT_DISPLAY;
 
-import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_TOP_OR_LEFT;
-import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_UNDEFINED;
+import static com.android.wm.shell.shared.split.SplitScreenConstants.SPLIT_POSITION_TOP_OR_LEFT;
+import static com.android.wm.shell.shared.split.SplitScreenConstants.SPLIT_POSITION_UNDEFINED;
 import static com.android.wm.shell.splitscreen.SplitScreen.STAGE_TYPE_MAIN;
 import static com.android.wm.shell.splitscreen.SplitScreen.STAGE_TYPE_UNDEFINED;
 
@@ -41,7 +41,7 @@
 
 import com.android.internal.policy.ScreenDecorationsUtils;
 import com.android.wm.shell.common.DisplayInsetsController;
-import com.android.wm.shell.common.split.SplitScreenConstants.SplitPosition;
+import com.android.wm.shell.shared.split.SplitScreenConstants.SplitPosition;
 import com.android.wm.shell.splitscreen.SplitScreen;
 import com.android.wm.shell.splitscreen.SplitScreen.SplitScreenListener;
 import com.android.wm.shell.splitscreen.SplitScreenController;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/util/SplitBounds.java b/libs/WindowManager/Shell/src/com/android/wm/shell/util/SplitBounds.java
index 3e06d2d..88b7528 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/util/SplitBounds.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/util/SplitBounds.java
@@ -19,7 +19,7 @@
 import android.os.Parcel;
 import android.os.Parcelable;
 
-import com.android.wm.shell.common.split.SplitScreenConstants.PersistentSnapPosition;
+import com.android.wm.shell.shared.split.SplitScreenConstants.PersistentSnapPosition;
 
 import java.util.Objects;
 
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java
index 401b78d..231570f 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecoration.java
@@ -19,6 +19,7 @@
 import static com.android.wm.shell.windowdecor.DragResizeWindowGeometry.getFineResizeCornerSize;
 import static com.android.wm.shell.windowdecor.DragResizeWindowGeometry.getLargeResizeCornerSize;
 import static com.android.wm.shell.windowdecor.DragResizeWindowGeometry.getResizeEdgeHandleSize;
+import static com.android.wm.shell.windowdecor.DragResizeWindowGeometry.getResizeHandleEdgeInset;
 
 import android.annotation.NonNull;
 import android.annotation.SuppressLint;
@@ -260,6 +261,8 @@
             setupRootView();
         }
 
+        bindData(mResult.mRootView, taskInfo);
+
         if (!isDragResizeable) {
             closeDragResizeListener();
             return;
@@ -286,7 +289,8 @@
         final Resources res = mResult.mRootView.getResources();
         mDragResizeListener.setGeometry(new DragResizeWindowGeometry(0 /* taskCornerRadius */,
                 new Size(mResult.mWidth, mResult.mHeight), getResizeEdgeHandleSize(mContext, res),
-                getFineResizeCornerSize(res), getLargeResizeCornerSize(res)), touchSlop);
+                getResizeHandleEdgeInset(res), getFineResizeCornerSize(res),
+                getLargeResizeCornerSize(res)), touchSlop);
     }
 
     /**
@@ -305,6 +309,14 @@
         maximize.setOnClickListener(mOnCaptionButtonClickListener);
     }
 
+    private void bindData(View rootView, RunningTaskInfo taskInfo) {
+        final boolean isFullscreen =
+                taskInfo.getWindowingMode() == WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
+        rootView.findViewById(R.id.maximize_window)
+                .setBackgroundResource(isFullscreen ? R.drawable.decor_restore_button_dark
+                        : R.drawable.decor_maximize_button_dark);
+    }
+
     void setCaptionColor(int captionColor) {
         if (mResult.mRootView == null) {
             return;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java
index 8c8f205..457b511 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorViewModel.java
@@ -34,13 +34,13 @@
 import static android.view.WindowInsets.Type.statusBars;
 
 import static com.android.internal.jank.Cuj.CUJ_DESKTOP_MODE_ENTER_MODE_APP_HANDLE_MENU;
-import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_BOTTOM_OR_RIGHT;
-import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_TOP_OR_LEFT;
 import static com.android.wm.shell.compatui.AppCompatUtils.isTopActivityExemptFromDesktopWindowing;
 import static com.android.wm.shell.desktopmode.DesktopModeVisualIndicator.IndicatorType.TO_FULLSCREEN_INDICATOR;
 import static com.android.wm.shell.desktopmode.DesktopModeVisualIndicator.IndicatorType.TO_SPLIT_LEFT_INDICATOR;
 import static com.android.wm.shell.desktopmode.DesktopModeVisualIndicator.IndicatorType.TO_SPLIT_RIGHT_INDICATOR;
 import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_DESKTOP_MODE;
+import static com.android.wm.shell.shared.split.SplitScreenConstants.SPLIT_POSITION_BOTTOM_OR_RIGHT;
+import static com.android.wm.shell.shared.split.SplitScreenConstants.SPLIT_POSITION_TOP_OR_LEFT;
 import static com.android.wm.shell.splitscreen.SplitScreen.STAGE_TYPE_UNDEFINED;
 
 import android.annotation.NonNull;
@@ -74,6 +74,7 @@
 import android.view.SurfaceControl;
 import android.view.SurfaceControl.Transaction;
 import android.view.View;
+import android.widget.Toast;
 import android.window.WindowContainerToken;
 import android.window.WindowContainerTransaction;
 
@@ -96,7 +97,6 @@
 import com.android.wm.shell.common.ShellExecutor;
 import com.android.wm.shell.common.SyncTransactionQueue;
 import com.android.wm.shell.common.desktopmode.DesktopModeTransitionSource;
-import com.android.wm.shell.common.split.SplitScreenConstants.SplitPosition;
 import com.android.wm.shell.desktopmode.DesktopModeVisualIndicator;
 import com.android.wm.shell.desktopmode.DesktopTasksController;
 import com.android.wm.shell.desktopmode.DesktopTasksController.SnapPosition;
@@ -105,6 +105,7 @@
 import com.android.wm.shell.shared.annotations.ShellBackgroundThread;
 import com.android.wm.shell.shared.desktopmode.DesktopModeFlags;
 import com.android.wm.shell.shared.desktopmode.DesktopModeStatus;
+import com.android.wm.shell.shared.split.SplitScreenConstants.SplitPosition;
 import com.android.wm.shell.splitscreen.SplitScreen;
 import com.android.wm.shell.splitscreen.SplitScreen.StageType;
 import com.android.wm.shell.splitscreen.SplitScreenController;
@@ -469,7 +470,8 @@
 
         if (!decoration.mTaskInfo.isResizeable
                 && DesktopModeFlags.DISABLE_SNAP_RESIZE.isEnabled(mContext)) {
-            //TODO(b/354658237) - show toast with relevant string
+            Toast.makeText(mContext,
+                    R.string.desktop_mode_non_resizable_snap_text, Toast.LENGTH_SHORT).show();
         } else {
             mDesktopTasksController.snapToHalfScreen(decoration.mTaskInfo,
                     decoration.mTaskInfo.configuration.windowConfiguration.getBounds(),
@@ -1029,7 +1031,7 @@
                 }
                 final boolean shouldStartTransitionDrag =
                         relevantDecor.checkTouchEventInFocusedCaptionHandle(ev)
-                        || Flags.enableAdditionalWindowsAboveStatusBar();
+                                || Flags.enableAdditionalWindowsAboveStatusBar();
                 if (dragFromStatusBarAllowed && shouldStartTransitionDrag) {
                     mTransitionDragActive = true;
                 }
@@ -1037,8 +1039,13 @@
             }
             case MotionEvent.ACTION_UP: {
                 if (mTransitionDragActive) {
+                    final DesktopModeVisualIndicator.DragStartState dragStartState =
+                            DesktopModeVisualIndicator.DragStartState
+                                    .getDragStartState(relevantDecor.mTaskInfo);
+                    if (dragStartState == null) return;
                     mDesktopTasksController.updateVisualIndicator(relevantDecor.mTaskInfo,
-                            relevantDecor.mTaskSurface, ev.getRawX(), ev.getRawY());
+                            relevantDecor.mTaskSurface, ev.getRawX(), ev.getRawY(),
+                            dragStartState);
                     mTransitionDragActive = false;
                     if (mMoveToDesktopAnimator != null) {
                         // Though this isn't a hover event, we need to update handle's hover state
@@ -1078,10 +1085,15 @@
                             && mMoveToDesktopAnimator == null) {
                         return;
                     }
+                    final DesktopModeVisualIndicator.DragStartState dragStartState =
+                            DesktopModeVisualIndicator.DragStartState
+                                    .getDragStartState(relevantDecor.mTaskInfo);
+                    if (dragStartState == null) return;
                     final DesktopModeVisualIndicator.IndicatorType indicatorType =
                             mDesktopTasksController.updateVisualIndicator(
                                     relevantDecor.mTaskInfo,
-                                    relevantDecor.mTaskSurface, ev.getRawX(), ev.getRawY());
+                                    relevantDecor.mTaskSurface, ev.getRawX(), ev.getRawY(),
+                                    dragStartState);
                     if (indicatorType != TO_FULLSCREEN_INDICATOR) {
                         if (mMoveToDesktopAnimator == null) {
                             mMoveToDesktopAnimator = new MoveToDesktopAnimator(
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java
index 538d0fb..75a6cd7 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecoration.java
@@ -27,10 +27,11 @@
 
 import static com.android.launcher3.icons.BaseIconFactory.MODE_DEFAULT;
 import static com.android.wm.shell.common.desktopmode.DesktopModeTransitionSource.APP_HANDLE_MENU_BUTTON;
-import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_BOTTOM_OR_RIGHT;
+import static com.android.wm.shell.shared.split.SplitScreenConstants.SPLIT_POSITION_BOTTOM_OR_RIGHT;
 import static com.android.wm.shell.windowdecor.DragResizeWindowGeometry.getFineResizeCornerSize;
 import static com.android.wm.shell.windowdecor.DragResizeWindowGeometry.getLargeResizeCornerSize;
 import static com.android.wm.shell.windowdecor.DragResizeWindowGeometry.getResizeEdgeHandleSize;
+import static com.android.wm.shell.windowdecor.DragResizeWindowGeometry.getResizeHandleEdgeInset;
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
@@ -515,8 +516,8 @@
         if (mDragResizeListener.setGeometry(
                 new DragResizeWindowGeometry(mRelayoutParams.mCornerRadius,
                         new Size(mResult.mWidth, mResult.mHeight),
-                        getResizeEdgeHandleSize(mContext, res), getFineResizeCornerSize(res),
-                        getLargeResizeCornerSize(res)), touchSlop)
+                        getResizeEdgeHandleSize(mContext, res), getResizeHandleEdgeInset(res),
+                        getFineResizeCornerSize(res), getLargeResizeCornerSize(res)), touchSlop)
                 || !mTaskInfo.positionInParent.equals(mPositionInParent)) {
             updateExclusionRegion();
         }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragPositioningCallbackUtility.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragPositioningCallbackUtility.java
index 0f2de70..cb9781e 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragPositioningCallbackUtility.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragPositioningCallbackUtility.java
@@ -82,32 +82,26 @@
         final int oldRight = repositionTaskBounds.right;
         final int oldBottom = repositionTaskBounds.bottom;
 
-
         repositionTaskBounds.set(taskBoundsAtDragStart);
 
         // Make sure the new resizing destination in any direction falls within the stable bounds.
-        // If not, set the bounds back to the old location that was valid to avoid conflicts with
-        // some regions such as the gesture area.
         if ((ctrlType & CTRL_TYPE_LEFT) != 0) {
-            final int candidateLeft = repositionTaskBounds.left + (int) delta.x;
-            repositionTaskBounds.left = (candidateLeft > stableBounds.left)
-                    ? candidateLeft : oldLeft;
+            repositionTaskBounds.left = Math.max(repositionTaskBounds.left + (int) delta.x,
+                    stableBounds.left);
         }
         if ((ctrlType & CTRL_TYPE_RIGHT) != 0) {
-            final int candidateRight = repositionTaskBounds.right + (int) delta.x;
-            repositionTaskBounds.right = (candidateRight < stableBounds.right)
-                    ? candidateRight : oldRight;
+            repositionTaskBounds.right = Math.min(repositionTaskBounds.right + (int) delta.x,
+                    stableBounds.right);
         }
         if ((ctrlType & CTRL_TYPE_TOP) != 0) {
-            final int candidateTop = repositionTaskBounds.top + (int) delta.y;
-            repositionTaskBounds.top = (candidateTop > stableBounds.top)
-                    ? candidateTop : oldTop;
+            repositionTaskBounds.top = Math.max(repositionTaskBounds.top + (int) delta.y,
+                    stableBounds.top);
         }
         if ((ctrlType & CTRL_TYPE_BOTTOM) != 0) {
-            final int candidateBottom = repositionTaskBounds.bottom + (int) delta.y;
-            repositionTaskBounds.bottom = (candidateBottom < stableBounds.bottom)
-                    ? candidateBottom : oldBottom;
+            repositionTaskBounds.bottom = Math.min(repositionTaskBounds.bottom + (int) delta.y,
+                    stableBounds.bottom);
         }
+
         // If width or height are negative or exceeding the width or height constraints, revert the
         // respective bounds to use previous bound dimensions.
         if (isExceedingWidthConstraint(repositionTaskBounds, stableBounds, displayController,
@@ -120,14 +114,12 @@
             repositionTaskBounds.top = oldTop;
             repositionTaskBounds.bottom = oldBottom;
         }
-        // If there are no changes to the bounds after checking new bounds against minimum width
-        // and height, do not set bounds and return false
-        if (oldLeft == repositionTaskBounds.left && oldTop == repositionTaskBounds.top
-                && oldRight == repositionTaskBounds.right
-                && oldBottom == repositionTaskBounds.bottom) {
-            return false;
-        }
-        return true;
+
+        // If there are no changes to the bounds after checking new bounds against minimum and
+        // maximum width and height, do not set bounds and return false
+        return oldLeft != repositionTaskBounds.left || oldTop != repositionTaskBounds.top
+                || oldRight != repositionTaskBounds.right
+                || oldBottom != repositionTaskBounds.bottom;
     }
 
     /**
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeWindowGeometry.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeWindowGeometry.java
index 014d61d..6dedf6d 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeWindowGeometry.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/DragResizeWindowGeometry.java
@@ -16,6 +16,7 @@
 
 package com.android.wm.shell.windowdecor;
 
+import static android.view.InputDevice.SOURCE_MOUSE;
 import static android.view.InputDevice.SOURCE_TOUCHSCREEN;
 
 import static com.android.wm.shell.shared.desktopmode.DesktopModeFlags.EDGE_DRAG_RESIZE;
@@ -44,27 +45,32 @@
 final class DragResizeWindowGeometry {
     private final int mTaskCornerRadius;
     private final Size mTaskSize;
-    // The size of the handle applied to the edges of the window, for the user to drag resize.
-    private final int mResizeHandleThickness;
+    // The size of the handle outside the task window applied to the edges of the window, for the
+    // user to drag resize.
+    private final int mResizeHandleEdgeOutset;
+    // The size of the handle inside the task window applied to the edges of the window, for the
+    // user to drag resize.
+    private final int mResizeHandleEdgeInset;
     // The task corners to permit drag resizing with a course input, such as touch.
-
     private final @NonNull TaskCorners mLargeTaskCorners;
     // The task corners to permit drag resizing with a fine input, such as stylus or cursor.
     private final @NonNull TaskCorners mFineTaskCorners;
     // The bounds for each edge drag region, which can resize the task in one direction.
-    private final @NonNull TaskEdges mTaskEdges;
+    final @NonNull TaskEdges mTaskEdges;
 
     DragResizeWindowGeometry(int taskCornerRadius, @NonNull Size taskSize,
-            int resizeHandleThickness, int fineCornerSize, int largeCornerSize) {
+            int resizeHandleEdgeOutset, int resizeHandleEdgeInset, int fineCornerSize,
+            int largeCornerSize) {
         mTaskCornerRadius = taskCornerRadius;
         mTaskSize = taskSize;
-        mResizeHandleThickness = resizeHandleThickness;
+        mResizeHandleEdgeOutset = resizeHandleEdgeOutset;
+        mResizeHandleEdgeInset = resizeHandleEdgeInset;
 
         mLargeTaskCorners = new TaskCorners(mTaskSize, largeCornerSize);
         mFineTaskCorners = new TaskCorners(mTaskSize, fineCornerSize);
 
         // Save touch areas for each edge.
-        mTaskEdges = new TaskEdges(mTaskSize, mResizeHandleThickness);
+        mTaskEdges = new TaskEdges(mTaskSize, mResizeHandleEdgeOutset, mResizeHandleEdgeInset);
     }
 
     /**
@@ -72,11 +78,18 @@
      */
     static int getResizeEdgeHandleSize(@NonNull Context context, @NonNull Resources res) {
         return EDGE_DRAG_RESIZE.isEnabled(context)
-                ? res.getDimensionPixelSize(R.dimen.desktop_mode_edge_handle)
+                ? res.getDimensionPixelSize(R.dimen.freeform_edge_handle_outset)
                 : res.getDimensionPixelSize(R.dimen.freeform_resize_handle);
     }
 
     /**
+     * Returns the resource value to use for the edge resize handle inside the task bounds.
+     */
+    static int getResizeHandleEdgeInset(@NonNull Resources res) {
+        return res.getDimensionPixelSize(R.dimen.freeform_edge_handle_inset);
+    }
+
+    /**
      * Returns the resource value to use for course input, such as touch, that benefits from a large
      * square on each of the window's corners.
      */
@@ -95,7 +108,8 @@
     /**
      * Returns the size of the task this geometry is calculated for.
      */
-    @NonNull Size getTaskSize() {
+    @NonNull
+    Size getTaskSize() {
         // Safe to return directly since size is immutable.
         return mTaskSize;
     }
@@ -153,7 +167,10 @@
     static boolean isEdgeResizePermitted(@NonNull Context context, @NonNull MotionEvent e) {
         if (EDGE_DRAG_RESIZE.isEnabled(context)) {
             return e.getToolType(0) == MotionEvent.TOOL_TYPE_STYLUS
-                    || e.getToolType(0) == MotionEvent.TOOL_TYPE_MOUSE;
+                    || e.getToolType(0) == MotionEvent.TOOL_TYPE_MOUSE
+                    // Touchpad input
+                    || (e.isFromSource(SOURCE_MOUSE)
+                        && e.getToolType(0) == MotionEvent.TOOL_TYPE_FINGER);
         } else {
             return e.getToolType(0) == MotionEvent.TOOL_TYPE_MOUSE;
         }
@@ -217,13 +234,15 @@
             ctrlType |= CTRL_TYPE_BOTTOM;
         }
         // If the touch is within one of the four corners, check if it is within the bounds of the
-        // // handle.
+        // handle.
         if ((ctrlType & (CTRL_TYPE_LEFT | CTRL_TYPE_RIGHT)) != 0
                 && (ctrlType & (CTRL_TYPE_TOP | CTRL_TYPE_BOTTOM)) != 0) {
             return checkDistanceFromCenter(ctrlType, x, y);
         }
-        // Otherwise, we should make sure we don't resize tasks inside task bounds.
-        return (x < 0 || y < 0 || x >= mTaskSize.getWidth() || y >= mTaskSize.getHeight())
+        // Allow a small resize handle inside the task bounds defined by the edge inset.
+        return (x <= mResizeHandleEdgeInset || y <= mResizeHandleEdgeInset
+                || x >= mTaskSize.getWidth() - mResizeHandleEdgeInset
+                || y >= mTaskSize.getHeight() - mResizeHandleEdgeInset)
                 ? ctrlType : CTRL_TYPE_UNDEFINED;
     }
 
@@ -237,7 +256,7 @@
         final Point cornerRadiusCenter = calculateCenterForCornerRadius(ctrlType);
         double distanceFromCenter = Math.hypot(x - cornerRadiusCenter.x, y - cornerRadiusCenter.y);
 
-        if (distanceFromCenter < mTaskCornerRadius + mResizeHandleThickness
+        if (distanceFromCenter < mTaskCornerRadius + mResizeHandleEdgeOutset
                 && distanceFromCenter >= mTaskCornerRadius) {
             return ctrlType;
         }
@@ -288,7 +307,8 @@
 
         return this.mTaskCornerRadius == other.mTaskCornerRadius
                 && this.mTaskSize.equals(other.mTaskSize)
-                && this.mResizeHandleThickness == other.mResizeHandleThickness
+                && this.mResizeHandleEdgeOutset == other.mResizeHandleEdgeOutset
+                && this.mResizeHandleEdgeInset == other.mResizeHandleEdgeInset
                 && this.mFineTaskCorners.equals(other.mFineTaskCorners)
                 && this.mLargeTaskCorners.equals(other.mLargeTaskCorners)
                 && this.mTaskEdges.equals(other.mTaskEdges);
@@ -299,7 +319,8 @@
         return Objects.hash(
                 mTaskCornerRadius,
                 mTaskSize,
-                mResizeHandleThickness,
+                mResizeHandleEdgeOutset,
+                mResizeHandleEdgeInset,
                 mFineTaskCorners,
                 mLargeTaskCorners,
                 mTaskEdges);
@@ -421,26 +442,27 @@
         private final @NonNull Rect mBottomEdgeBounds;
         private final @NonNull Region mRegion;
 
-        private TaskEdges(@NonNull Size taskSize, int resizeHandleThickness) {
+        private TaskEdges(@NonNull Size taskSize, int resizeHandleThickness,
+                int resizeHandleEdgeInset) {
             // Save touch areas for each edge.
             mTopEdgeBounds = new Rect(
                     -resizeHandleThickness,
                     -resizeHandleThickness,
                     taskSize.getWidth() + resizeHandleThickness,
-                    0);
+                    resizeHandleThickness);
             mLeftEdgeBounds = new Rect(
                     -resizeHandleThickness,
                     0,
-                    0,
+                    resizeHandleEdgeInset,
                     taskSize.getHeight());
             mRightEdgeBounds = new Rect(
-                    taskSize.getWidth(),
+                    taskSize.getWidth() - resizeHandleEdgeInset,
                     0,
                     taskSize.getWidth() + resizeHandleThickness,
                     taskSize.getHeight());
             mBottomEdgeBounds = new Rect(
                     -resizeHandleThickness,
-                    taskSize.getHeight(),
+                    taskSize.getHeight() - resizeHandleEdgeInset,
                     taskSize.getWidth() + resizeHandleThickness,
                     taskSize.getHeight() + resizeHandleThickness);
 
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/HandleMenu.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/HandleMenu.kt
index c16c16f..34de94e 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
@@ -45,7 +45,7 @@
 import androidx.core.view.isGone
 import com.android.window.flags.Flags
 import com.android.wm.shell.R
-import com.android.wm.shell.common.split.SplitScreenConstants
+import com.android.wm.shell.shared.split.SplitScreenConstants
 import com.android.wm.shell.splitscreen.SplitScreenController
 import com.android.wm.shell.windowdecor.additionalviewcontainer.AdditionalSystemViewContainer
 import com.android.wm.shell.windowdecor.additionalviewcontainer.AdditionalViewContainer
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/HandleMenuAnimator.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/HandleMenuAnimator.kt
index e3d2234..9590ccd 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/HandleMenuAnimator.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/HandleMenuAnimator.kt
@@ -30,7 +30,7 @@
 import androidx.core.animation.doOnEnd
 import androidx.core.view.children
 import com.android.wm.shell.R
-import com.android.wm.shell.animation.Interpolators
+import com.android.wm.shell.shared.animation.Interpolators
 
 /** Animates the Handle Menu opening. */
 class HandleMenuAnimator(
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/MaximizeMenu.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/MaximizeMenu.kt
index 095d337..114c331 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/MaximizeMenu.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/MaximizeMenu.kt
@@ -59,10 +59,10 @@
 import androidx.core.animation.addListener
 import com.android.wm.shell.R
 import com.android.wm.shell.RootTaskDisplayAreaOrganizer
-import com.android.wm.shell.animation.Interpolators.EMPHASIZED_DECELERATE
-import com.android.wm.shell.animation.Interpolators.FAST_OUT_LINEAR_IN
 import com.android.wm.shell.common.DisplayController
 import com.android.wm.shell.common.SyncTransactionQueue
+import com.android.wm.shell.shared.animation.Interpolators.EMPHASIZED_DECELERATE
+import com.android.wm.shell.shared.animation.Interpolators.FAST_OUT_LINEAR_IN
 import com.android.wm.shell.windowdecor.additionalviewcontainer.AdditionalViewHostViewContainer
 import com.android.wm.shell.windowdecor.common.DecorThemeUtil
 import com.android.wm.shell.windowdecor.common.OPACITY_12
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/AppHandleViewHolder.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/AppHandleViewHolder.kt
index 753723c..510032b 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/AppHandleViewHolder.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/AppHandleViewHolder.kt
@@ -31,7 +31,7 @@
 import android.widget.ImageButton
 import com.android.window.flags.Flags
 import com.android.wm.shell.R
-import com.android.wm.shell.animation.Interpolators
+import com.android.wm.shell.shared.animation.Interpolators
 import com.android.wm.shell.windowdecor.additionalviewcontainer.AdditionalSystemViewContainer
 
 /**
diff --git a/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/DragAppWindowMultiWindowAndPip.kt b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/DragAppWindowMultiWindowAndPip.kt
new file mode 100644
index 0000000..6d52a11
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/DragAppWindowMultiWindowAndPip.kt
@@ -0,0 +1,73 @@
+/*
+ * 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.wm.shell.scenarios
+
+import android.platform.test.annotations.Postsubmit
+import com.android.server.wm.flicker.helpers.DesktopModeAppHelper
+import com.android.server.wm.flicker.helpers.ImeAppHelper
+import com.android.server.wm.flicker.helpers.MailAppHelper
+import com.android.server.wm.flicker.helpers.NewTasksAppHelper
+import com.android.server.wm.flicker.helpers.PipAppHelper
+import com.android.server.wm.flicker.helpers.SimpleAppHelper
+import com.android.window.flags.Flags
+import org.junit.After
+import org.junit.Assume
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.BlockJUnit4ClassRunner
+
+@RunWith(BlockJUnit4ClassRunner::class)
+@Postsubmit
+open class DragAppWindowMultiWindowAndPip : DragAppWindowScenarioTestBase()
+{
+    private val imeAppHelper = ImeAppHelper(instrumentation)
+    private val testApp = DesktopModeAppHelper(SimpleAppHelper(instrumentation))
+    private val pipApp = PipAppHelper(instrumentation)
+    private val mailApp = DesktopModeAppHelper(MailAppHelper(instrumentation))
+    private val newTasksApp = DesktopModeAppHelper(NewTasksAppHelper(instrumentation))
+    private val imeApp = DesktopModeAppHelper(ImeAppHelper(instrumentation))
+
+    @Before
+    fun setup() {
+        Assume.assumeTrue(Flags.enableDesktopWindowingMode() && tapl.isTablet)
+        // Set string extra to ensure the app is on PiP mode at launch
+        pipApp.launchViaIntentAndWaitForPip(wmHelper, stringExtras = mapOf("enter_pip" to "true"))
+        testApp.enterDesktopWithDrag(wmHelper, device)
+        mailApp.launchViaIntent(wmHelper)
+        newTasksApp.launchViaIntent(wmHelper)
+        imeApp.launchViaIntent(wmHelper)
+    }
+
+    @Test
+    override fun dragAppWindow() {
+        val (startXIme, startYIme) = getWindowDragStartCoordinate(imeAppHelper)
+
+        imeApp.dragWindow(startXIme, startYIme,
+            endX = startXIme + 150, endY = startYIme + 150,
+            wmHelper, device)
+    }
+
+    @After
+    fun teardown() {
+        testApp.exit(wmHelper)
+        pipApp.exit(wmHelper)
+        mailApp.exit(wmHelper)
+        newTasksApp.exit(wmHelper)
+        imeApp.exit(wmHelper)
+    }
+}
diff --git a/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/ResizeAppCornerMultiWindow.kt b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/ResizeAppCornerMultiWindow.kt
new file mode 100644
index 0000000..b6bca7a
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/ResizeAppCornerMultiWindow.kt
@@ -0,0 +1,90 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.scenarios
+
+import android.platform.test.annotations.Postsubmit
+import android.app.Instrumentation
+import android.tools.NavBar
+import android.tools.Rotation
+import android.tools.traces.parsers.WindowManagerStateHelper
+import androidx.test.platform.app.InstrumentationRegistry
+import androidx.test.uiautomator.UiDevice
+import com.android.launcher3.tapl.LauncherInstrumentation
+import com.android.server.wm.flicker.helpers.DesktopModeAppHelper
+import com.android.server.wm.flicker.helpers.ImeAppHelper
+import com.android.server.wm.flicker.helpers.MailAppHelper
+import com.android.server.wm.flicker.helpers.NewTasksAppHelper
+import com.android.server.wm.flicker.helpers.SimpleAppHelper
+import com.android.window.flags.Flags
+import com.android.wm.shell.Utils
+import org.junit.After
+import org.junit.Assume
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.BlockJUnit4ClassRunner
+
+@RunWith(BlockJUnit4ClassRunner::class)
+@Postsubmit
+open class ResizeAppCornerMultiWindow
+@JvmOverloads
+constructor(val rotation: Rotation = Rotation.ROTATION_0,
+    val horizontalChange: Int = 50,
+    val verticalChange: Int = -50) {
+
+    private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation()
+    private val tapl = LauncherInstrumentation()
+    private val wmHelper = WindowManagerStateHelper(instrumentation)
+    private val device = UiDevice.getInstance(instrumentation)
+    private val testApp = DesktopModeAppHelper(SimpleAppHelper(instrumentation))
+    private val mailApp = DesktopModeAppHelper(MailAppHelper(instrumentation))
+    private val newTasksApp = DesktopModeAppHelper(NewTasksAppHelper(instrumentation))
+    private val imeApp = DesktopModeAppHelper(ImeAppHelper(instrumentation))
+
+    @Rule
+    @JvmField
+    val testSetupRule = Utils.testSetupRule(NavBar.MODE_GESTURAL, rotation)
+
+    @Before
+    fun setup() {
+        Assume.assumeTrue(Flags.enableDesktopWindowingMode() && tapl.isTablet)
+        tapl.setEnableRotation(true)
+        tapl.setExpectedRotation(rotation.value)
+        testApp.enterDesktopWithDrag(wmHelper, device)
+        mailApp.launchViaIntent(wmHelper)
+        newTasksApp.launchViaIntent(wmHelper)
+        imeApp.launchViaIntent(wmHelper)
+    }
+
+    @Test
+    open fun resizeAppWithCornerResize() {
+        imeApp.cornerResize(wmHelper,
+            device,
+            DesktopModeAppHelper.Corners.RIGHT_TOP,
+            horizontalChange,
+            verticalChange)
+    }
+
+    @After
+    fun teardown() {
+        testApp.exit(wmHelper)
+        mailApp.exit(wmHelper)
+        newTasksApp.exit(wmHelper)
+        imeApp.exit(wmHelper)
+    }
+}
diff --git a/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/ResizeAppCornerMultiWindowAndPip.kt b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/ResizeAppCornerMultiWindowAndPip.kt
new file mode 100644
index 0000000..285ea13
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/e2e/desktopmode/scenarios/src/com/android/wm/shell/scenarios/ResizeAppCornerMultiWindowAndPip.kt
@@ -0,0 +1,95 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.scenarios
+
+import android.platform.test.annotations.Postsubmit
+import android.app.Instrumentation
+import android.tools.NavBar
+import android.tools.Rotation
+import android.tools.traces.parsers.WindowManagerStateHelper
+import androidx.test.platform.app.InstrumentationRegistry
+import androidx.test.uiautomator.UiDevice
+import com.android.launcher3.tapl.LauncherInstrumentation
+import com.android.server.wm.flicker.helpers.DesktopModeAppHelper
+import com.android.server.wm.flicker.helpers.ImeAppHelper
+import com.android.server.wm.flicker.helpers.MailAppHelper
+import com.android.server.wm.flicker.helpers.NewTasksAppHelper
+import com.android.server.wm.flicker.helpers.PipAppHelper
+import com.android.server.wm.flicker.helpers.SimpleAppHelper
+import com.android.window.flags.Flags
+import com.android.wm.shell.Utils
+import org.junit.After
+import org.junit.Assume
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.BlockJUnit4ClassRunner
+
+@RunWith(BlockJUnit4ClassRunner::class)
+@Postsubmit
+open class ResizeAppCornerMultiWindowAndPip
+@JvmOverloads
+constructor(val rotation: Rotation = Rotation.ROTATION_0,
+    val horizontalChange: Int = 50,
+    val verticalChange: Int = -50) {
+
+    private val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation()
+    private val tapl = LauncherInstrumentation()
+    private val wmHelper = WindowManagerStateHelper(instrumentation)
+    private val device = UiDevice.getInstance(instrumentation)
+    private val testApp = DesktopModeAppHelper(SimpleAppHelper(instrumentation))
+    private val pipApp = PipAppHelper(instrumentation)
+    private val mailApp = DesktopModeAppHelper(MailAppHelper(instrumentation))
+    private val newTasksApp = DesktopModeAppHelper(NewTasksAppHelper(instrumentation))
+    private val imeApp = DesktopModeAppHelper(ImeAppHelper(instrumentation))
+
+    @Rule
+    @JvmField
+    val testSetupRule = Utils.testSetupRule(NavBar.MODE_GESTURAL, rotation)
+
+    @Before
+    fun setup() {
+        Assume.assumeTrue(Flags.enableDesktopWindowingMode() && tapl.isTablet)
+        tapl.setEnableRotation(true)
+        tapl.setExpectedRotation(rotation.value)
+        // Set string extra to ensure the app is on PiP mode at launch
+        pipApp.launchViaIntentAndWaitForPip(wmHelper, stringExtras = mapOf("enter_pip" to "true"))
+        testApp.enterDesktopWithDrag(wmHelper, device)
+        mailApp.launchViaIntent(wmHelper)
+        newTasksApp.launchViaIntent(wmHelper)
+        imeApp.launchViaIntent(wmHelper)
+    }
+
+    @Test
+    open fun resizeAppWithCornerResize() {
+        imeApp.cornerResize(wmHelper,
+            device,
+            DesktopModeAppHelper.Corners.RIGHT_TOP,
+            horizontalChange,
+            verticalChange)
+    }
+
+    @After
+    fun teardown() {
+        testApp.exit(wmHelper)
+        pipApp.exit(wmHelper)
+        mailApp.exit(wmHelper)
+        newTasksApp.exit(wmHelper)
+        imeApp.exit(wmHelper)
+    }
+}
diff --git a/libs/WindowManager/Shell/tests/e2e/splitscreen/flicker-legacy/Android.bp b/libs/WindowManager/Shell/tests/e2e/splitscreen/flicker-legacy/Android.bp
index 35b2f56..a231e38 100644
--- a/libs/WindowManager/Shell/tests/e2e/splitscreen/flicker-legacy/Android.bp
+++ b/libs/WindowManager/Shell/tests/e2e/splitscreen/flicker-legacy/Android.bp
@@ -15,6 +15,7 @@
 //
 
 package {
+    default_team: "trendy_team_multitasking_windowing",
     // See: http://go/android-license-faq
     // A large-scale-change added 'default_applicable_licenses' to import
     // all of the 'license_kinds' from "frameworks_base_license"
@@ -30,6 +31,46 @@
     ],
 }
 
+java_library {
+    name: "WMShellFlickerTestsSplitScreenBase",
+    srcs: [
+        ":WMShellFlickerTestsSplitScreenBase-src",
+    ],
+    static_libs: [
+        "WMShellFlickerTestsBase",
+        "wm-shell-flicker-utils",
+        "androidx.test.ext.junit",
+        "flickertestapplib",
+        "flickerlib",
+        "flickerlib-helpers",
+        "flickerlib-trace_processor_shell",
+        "platform-test-annotations",
+        "wm-flicker-common-app-helpers",
+        "wm-flicker-common-assertions",
+        "launcher-helper-lib",
+        "launcher-aosp-tapl",
+    ],
+}
+
+android_test {
+    name: "WMShellFlickerTestsSplitScreen",
+    defaults: ["WMShellFlickerTestsDefault"],
+    manifest: "AndroidManifest.xml",
+    package_name: "com.android.wm.shell.flicker.splitscreen",
+    instrumentation_target_package: "com.android.wm.shell.flicker.splitscreen",
+    test_config_template: "AndroidTestTemplate.xml",
+    srcs: ["src/**/*.kt"],
+    exclude_srcs: ["src/**/benchmark/*.kt"],
+    static_libs: [
+        "WMShellFlickerTestsBase",
+        "WMShellFlickerTestsSplitScreenBase",
+    ],
+    data: ["trace_config/*"],
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// Begin cleanup after gcl merges
+
 filegroup {
     name: "WMShellFlickerTestsSplitScreenGroup1-src",
     srcs: [
@@ -61,27 +102,6 @@
     ],
 }
 
-java_library {
-    name: "WMShellFlickerTestsSplitScreenBase",
-    srcs: [
-        ":WMShellFlickerTestsSplitScreenBase-src",
-    ],
-    static_libs: [
-        "WMShellFlickerTestsBase",
-        "wm-shell-flicker-utils",
-        "androidx.test.ext.junit",
-        "flickertestapplib",
-        "flickerlib",
-        "flickerlib-helpers",
-        "flickerlib-trace_processor_shell",
-        "platform-test-annotations",
-        "wm-flicker-common-app-helpers",
-        "wm-flicker-common-assertions",
-        "launcher-helper-lib",
-        "launcher-aosp-tapl",
-    ],
-}
-
 android_test {
     name: "WMShellFlickerTestsSplitScreenGroup1",
     defaults: ["WMShellFlickerTestsDefault"],
@@ -154,3 +174,156 @@
     ],
     data: ["trace_config/*"],
 }
+
+////////////////////////////////////////////////////////////////////////////////
+// End cleanup after gcl merges
+
+////////////////////////////////////////////////////////////////////////////////
+// Begin breakdowns for FlickerTestsRotation module
+
+test_module_config {
+    name: "WMShellFlickerTestsSplitScreen-CatchAll",
+    base: "WMShellFlickerTestsSplitScreen",
+    exclude_filters: [
+        "com.android.wm.shell.flicker.splitscreen.CopyContentInSplit",
+        "com.android.wm.shell.flicker.splitscreen.DismissSplitScreenByDivider",
+        "com.android.wm.shell.flicker.splitscreen.DismissSplitScreenByGoHome",
+        "com.android.wm.shell.flicker.splitscreen.DragDividerToResize",
+        "com.android.wm.shell.flicker.splitscreen.EnterSplitScreenByDragFromAllApps",
+        "com.android.wm.shell.flicker.splitscreen.EnterSplitScreenByDragFromNotification",
+        "com.android.wm.shell.flicker.splitscreen.EnterSplitScreenByDragFromShortcut",
+        "com.android.wm.shell.flicker.splitscreen.EnterSplitScreenByDragFromTaskbar",
+        "com.android.wm.shell.flicker.splitscreen.EnterSplitScreenFromOverview",
+        "com.android.wm.shell.flicker.splitscreen.MultipleShowImeRequestsInSplitScreen",
+        "com.android.wm.shell.flicker.splitscreen.SwitchAppByDoubleTapDivider",
+        "com.android.wm.shell.flicker.splitscreen.SwitchBackToSplitFromAnotherApp",
+        "com.android.wm.shell.flicker.splitscreen.SwitchBackToSplitFromHome",
+        "com.android.wm.shell.flicker.splitscreen.SwitchBackToSplitFromRecent",
+        "com.android.wm.shell.flicker.splitscreen.SwitchBetweenSplitPairs",
+        "com.android.wm.shell.flicker.splitscreen.SwitchBetweenSplitPairsNoPip",
+        "com.android.wm.shell.flicker.splitscreen.",
+    ],
+    test_suites: ["device-tests"],
+}
+
+test_module_config {
+    name: "WMShellFlickerTestsSplitScreen-CopyContentInSplit",
+    base: "WMShellFlickerTestsSplitScreen",
+    include_filters: ["com.android.wm.shell.flicker.splitscreen.CopyContentInSplit"],
+    test_suites: ["device-tests"],
+}
+
+test_module_config {
+    name: "WMShellFlickerTestsSplitScreen-DismissSplitScreenByDivider",
+    base: "WMShellFlickerTestsSplitScreen",
+    include_filters: ["com.android.wm.shell.flicker.splitscreen.DismissSplitScreenByDivider"],
+    test_suites: ["device-tests"],
+}
+
+test_module_config {
+    name: "WMShellFlickerTestsSplitScreen-DismissSplitScreenByGoHome",
+    base: "WMShellFlickerTestsSplitScreen",
+    include_filters: ["com.android.wm.shell.flicker.splitscreen.DismissSplitScreenByGoHome"],
+    test_suites: ["device-tests"],
+}
+
+test_module_config {
+    name: "WMShellFlickerTestsSplitScreen-DragDividerToResize",
+    base: "WMShellFlickerTestsSplitScreen",
+    include_filters: ["com.android.wm.shell.flicker.splitscreen.DragDividerToResize"],
+    test_suites: ["device-tests"],
+}
+
+test_module_config {
+    name: "WMShellFlickerTestsSplitScreen-EnterSplitScreenByDragFromAllApps",
+    base: "WMShellFlickerTestsSplitScreen",
+    include_filters: ["com.android.wm.shell.flicker.splitscreen.EnterSplitScreenByDragFromAllApps"],
+    test_suites: ["device-tests"],
+}
+
+test_module_config {
+    name: "WMShellFlickerTestsSplitScreen-EnterSplitScreenByDragFromNotification",
+    base: "WMShellFlickerTestsSplitScreen",
+    include_filters: ["com.android.wm.shell.flicker.splitscreen.EnterSplitScreenByDragFromNotification"],
+    test_suites: ["device-tests"],
+}
+
+test_module_config {
+    name: "WMShellFlickerTestsSplitScreen-EnterSplitScreenByDragFromShortcut",
+    base: "WMShellFlickerTestsSplitScreen",
+    include_filters: ["com.android.wm.shell.flicker.splitscreen.EnterSplitScreenByDragFromShortcut"],
+    test_suites: ["device-tests"],
+}
+
+test_module_config {
+    name: "WMShellFlickerTestsSplitScreen-EnterSplitScreenByDragFromTaskbar",
+    base: "WMShellFlickerTestsSplitScreen",
+    include_filters: ["com.android.wm.shell.flicker.splitscreen.EnterSplitScreenByDragFromTaskbar"],
+    test_suites: ["device-tests"],
+}
+
+test_module_config {
+    name: "WMShellFlickerTestsSplitScreen-EnterSplitScreenFromOverview",
+    base: "WMShellFlickerTestsSplitScreen",
+    include_filters: ["com.android.wm.shell.flicker.splitscreen.EnterSplitScreenFromOverview"],
+    test_suites: ["device-tests"],
+}
+
+test_module_config {
+    name: "WMShellFlickerTestsSplitScreen-MultipleShowImeRequestsInSplitScreen",
+    base: "WMShellFlickerTestsSplitScreen",
+    include_filters: ["com.android.wm.shell.flicker.splitscreen.MultipleShowImeRequestsInSplitScreen"],
+    test_suites: ["device-tests"],
+}
+
+test_module_config {
+    name: "WMShellFlickerTestsSplitScreen-SwitchAppByDoubleTapDivider",
+    base: "WMShellFlickerTestsSplitScreen",
+    include_filters: ["com.android.wm.shell.flicker.splitscreen.SwitchAppByDoubleTapDivider"],
+    test_suites: ["device-tests"],
+}
+
+test_module_config {
+    name: "WMShellFlickerTestsSplitScreen-SwitchBackToSplitFromAnotherApp",
+    base: "WMShellFlickerTestsSplitScreen",
+    include_filters: ["com.android.wm.shell.flicker.splitscreen.SwitchBackToSplitFromAnotherApp"],
+    test_suites: ["device-tests"],
+}
+
+test_module_config {
+    name: "WMShellFlickerTestsSplitScreen-SwitchBackToSplitFromHome",
+    base: "WMShellFlickerTestsSplitScreen",
+    include_filters: ["com.android.wm.shell.flicker.splitscreen.SwitchBackToSplitFromHome"],
+    test_suites: ["device-tests"],
+}
+
+test_module_config {
+    name: "WMShellFlickerTestsSplitScreen-SwitchBackToSplitFromRecent",
+    base: "WMShellFlickerTestsSplitScreen",
+    include_filters: ["com.android.wm.shell.flicker.splitscreen.SwitchBackToSplitFromRecent"],
+    test_suites: ["device-tests"],
+}
+
+test_module_config {
+    name: "WMShellFlickerTestsSplitScreen-SwitchBetweenSplitPairs",
+    base: "WMShellFlickerTestsSplitScreen",
+    include_filters: ["com.android.wm.shell.flicker.splitscreen.SwitchBetweenSplitPairs"],
+    test_suites: ["device-tests"],
+}
+
+test_module_config {
+    name: "WMShellFlickerTestsSplitScreen-SwitchBetweenSplitPairsNoPip",
+    base: "WMShellFlickerTestsSplitScreen",
+    include_filters: ["com.android.wm.shell.flicker.splitscreen.SwitchBetweenSplitPairsNoPip"],
+    test_suites: ["device-tests"],
+}
+
+test_module_config {
+    name: "WMShellFlickerTestsSplitScreen-UnlockKeyguardToSplitScreen",
+    base: "WMShellFlickerTestsSplitScreen",
+    include_filters: ["com.android.wm.shell.flicker.splitscreen.UnlockKeyguardToSplitScreen"],
+    test_suites: ["device-tests"],
+}
+
+// End breakdowns for FlickerTestsRotation module
+////////////////////////////////////////////////////////////////////////////////
diff --git a/libs/WindowManager/Shell/tests/flicker/appcompat/Android.bp b/libs/WindowManager/Shell/tests/flicker/appcompat/Android.bp
index e151ab2..29a9f10 100644
--- a/libs/WindowManager/Shell/tests/flicker/appcompat/Android.bp
+++ b/libs/WindowManager/Shell/tests/flicker/appcompat/Android.bp
@@ -15,6 +15,7 @@
 //
 
 package {
+    default_team: "trendy_team_app_compat",
     // See: http://go/android-license-faq
     // A large-scale-change added 'default_applicable_licenses' to import
     // all of the 'license_kinds' from "frameworks_base_license"
@@ -23,6 +24,9 @@
     default_applicable_licenses: ["frameworks_base_license"],
 }
 
+////////////////////////////////////////////////////////////////////////////////
+// Begin cleanup after gcl merge
+
 filegroup {
     name: "WMShellFlickerTestsAppCompat-src",
     srcs: [
@@ -41,3 +45,80 @@
     static_libs: ["WMShellFlickerTestsBase"],
     data: ["trace_config/*"],
 }
+
+////////////////////////////////////////////////////////////////////////////////
+// End cleanup after gcl merge
+
+android_test {
+    name: "WMShellFlickerTestsAppCompat",
+    defaults: ["WMShellFlickerTestsDefault"],
+    manifest: "AndroidManifest.xml",
+    package_name: "com.android.wm.shell.flicker",
+    instrumentation_target_package: "com.android.wm.shell.flicker",
+    test_config_template: "AndroidTestTemplate.xml",
+    srcs: ["src/**/*.kt"],
+    static_libs: ["WMShellFlickerTestsBase"],
+    data: ["trace_config/*"],
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// Begin breakdowns for WMShellFlickerTestsAppCompat module
+
+test_module_config {
+    name: "WMShellFlickerTestsAppCompat-CatchAll",
+    base: "WMShellFlickerTestsAppCompat",
+    exclude_filters: [
+        "com.android.wm.shell.flicker.appcompat.OpenAppInSizeCompatModeTest",
+        "com.android.wm.shell.flicker.appcompat.OpenTransparentActivityTest",
+        "com.android.wm.shell.flicker.appcompat.QuickSwitchLauncherToLetterboxAppTest",
+        "com.android.wm.shell.flicker.appcompat.RepositionFixedPortraitAppTest",
+        "com.android.wm.shell.flicker.appcompat.RestartAppInSizeCompatModeTest",
+        "com.android.wm.shell.flicker.appcompat.RotateImmersiveAppInFullscreenTest",
+    ],
+    test_suites: ["device-tests"],
+}
+
+test_module_config {
+    name: "WMShellFlickerTestsAppCompat-OpenAppInSizeCompatModeTest",
+    base: "WMShellFlickerTestsAppCompat",
+    include_filters: ["com.android.wm.shell.flicker.appcompat.OpenAppInSizeCompatModeTest"],
+    test_suites: ["device-tests"],
+}
+
+test_module_config {
+    name: "WMShellFlickerTestsAppCompat-OpenTransparentActivityTest",
+    base: "WMShellFlickerTestsAppCompat",
+    include_filters: ["com.android.wm.shell.flicker.appcompat.OpenTransparentActivityTest"],
+    test_suites: ["device-tests"],
+}
+
+test_module_config {
+    name: "WMShellFlickerTestsAppCompat-QuickSwitchLauncherToLetterboxAppTest",
+    base: "WMShellFlickerTestsAppCompat",
+    include_filters: ["com.android.wm.shell.flicker.appcompat.QuickSwitchLauncherToLetterboxAppTest"],
+    test_suites: ["device-tests"],
+}
+
+test_module_config {
+    name: "WMShellFlickerTestsAppCompat-RepositionFixedPortraitAppTest",
+    base: "WMShellFlickerTestsAppCompat",
+    include_filters: ["com.android.wm.shell.flicker.appcompat.RepositionFixedPortraitAppTest"],
+    test_suites: ["device-tests"],
+}
+
+test_module_config {
+    name: "WMShellFlickerTestsAppCompat-RestartAppInSizeCompatModeTest",
+    base: "WMShellFlickerTestsAppCompat",
+    include_filters: ["com.android.wm.shell.flicker.appcompat.RestartAppInSizeCompatModeTest"],
+    test_suites: ["device-tests"],
+}
+
+test_module_config {
+    name: "WMShellFlickerTestsAppCompat-RotateImmersiveAppInFullscreenTest",
+    base: "WMShellFlickerTestsAppCompat",
+    include_filters: ["com.android.wm.shell.flicker.appcompat.RotateImmersiveAppInFullscreenTest"],
+    test_suites: ["device-tests"],
+}
+
+// End breakdowns for FlickerTestsRotation module
+////////////////////////////////////////////////////////////////////////////////
diff --git a/libs/WindowManager/Shell/tests/flicker/bubble/Android.bp b/libs/WindowManager/Shell/tests/flicker/bubble/Android.bp
index f0b4f1f..2ff7ab2 100644
--- a/libs/WindowManager/Shell/tests/flicker/bubble/Android.bp
+++ b/libs/WindowManager/Shell/tests/flicker/bubble/Android.bp
@@ -15,6 +15,7 @@
 //
 
 package {
+    default_team: "trendy_team_multitasking_windowing",
     // See: http://go/android-license-faq
     // A large-scale-change added 'default_applicable_licenses' to import
     // all of the 'license_kinds' from "frameworks_base_license"
@@ -34,3 +35,57 @@
     static_libs: ["WMShellFlickerTestsBase"],
     data: ["trace_config/*"],
 }
+
+////////////////////////////////////////////////////////////////////////////////
+// Begin breakdowns for WMShellFlickerTestsBubbles module
+
+test_module_config {
+    name: "WMShellFlickerTestsBubbles-CatchAll",
+    base: "WMShellFlickerTestsBubbles",
+    exclude_filters: [
+        "com.android.wm.shell.flicker.bubble.ChangeActiveActivityFromBubbleTest",
+        "com.android.wm.shell.flicker.bubble.DragToDismissBubbleScreenTest",
+        "com.android.wm.shell.flicker.bubble.OpenActivityFromBubbleOnLocksreenTest",
+        "com.android.wm.shell.flicker.bubble.OpenActivityFromBubbleTest",
+        "com.android.wm.shell.flicker.bubble.SendBubbleNotificationTest",
+    ],
+    test_suites: ["device-tests"],
+}
+
+test_module_config {
+    name: "WMShellFlickerTestsBubbles-ChangeActiveActivityFromBubbleTest",
+    base: "WMShellFlickerTestsBubbles",
+    include_filters: ["com.android.wm.shell.flicker.bubble.ChangeActiveActivityFromBubbleTest"],
+    test_suites: ["device-tests"],
+}
+
+test_module_config {
+    name: "WMShellFlickerTestsBubbles-DragToDismissBubbleScreenTest",
+    base: "WMShellFlickerTestsBubbles",
+    include_filters: ["com.android.wm.shell.flicker.bubble.DragToDismissBubbleScreenTest"],
+    test_suites: ["device-tests"],
+}
+
+test_module_config {
+    name: "WMShellFlickerTestsBubbles-OpenActivityFromBubbleOnLocksreenTest",
+    base: "WMShellFlickerTestsBubbles",
+    include_filters: ["com.android.wm.shell.flicker.bubble.OpenActivityFromBubbleOnLocksreenTest"],
+    test_suites: ["device-tests"],
+}
+
+test_module_config {
+    name: "WMShellFlickerTestsBubbles-OpenActivityFromBubbleTest",
+    base: "WMShellFlickerTestsBubbles",
+    include_filters: ["com.android.wm.shell.flicker.bubble.OpenActivityFromBubbleTest"],
+    test_suites: ["device-tests"],
+}
+
+test_module_config {
+    name: "WMShellFlickerTestsBubbles-SendBubbleNotificationTest",
+    base: "WMShellFlickerTestsBubbles",
+    include_filters: ["com.android.wm.shell.flicker.bubble.SendBubbleNotificationTest"],
+    test_suites: ["device-tests"],
+}
+
+// End breakdowns for WMShellFlickerTestsBubbles module
+////////////////////////////////////////////////////////////////////////////////
diff --git a/libs/WindowManager/Shell/tests/flicker/pip/Android.bp b/libs/WindowManager/Shell/tests/flicker/pip/Android.bp
index faeb342..4165ed0 100644
--- a/libs/WindowManager/Shell/tests/flicker/pip/Android.bp
+++ b/libs/WindowManager/Shell/tests/flicker/pip/Android.bp
@@ -15,6 +15,7 @@
 //
 
 package {
+    default_team: "trendy_team_multitasking_windowing",
     // See: http://go/android-license-faq
     // A large-scale-change added 'default_applicable_licenses' to import
     // all of the 'license_kinds' from "frameworks_base_license"
@@ -24,6 +25,14 @@
 }
 
 filegroup {
+    name: "WMShellFlickerTestsPipApps-src",
+    srcs: ["src/**/apps/*.kt"],
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// Begin cleanup after gcl merges
+
+filegroup {
     name: "WMShellFlickerTestsPip1-src",
     srcs: [
         "src/**/A*.kt",
@@ -52,11 +61,6 @@
     srcs: ["src/**/common/*.kt"],
 }
 
-filegroup {
-    name: "WMShellFlickerTestsPipApps-src",
-    srcs: ["src/**/apps/*.kt"],
-}
-
 android_test {
     name: "WMShellFlickerTestsPip1",
     defaults: ["WMShellFlickerTestsDefault"],
@@ -107,6 +111,21 @@
     data: ["trace_config/*"],
 }
 
+////////////////////////////////////////////////////////////////////////////////
+// End cleanup after gcl merges
+
+android_test {
+    name: "WMShellFlickerTestsPip",
+    defaults: ["WMShellFlickerTestsDefault"],
+    manifest: "AndroidManifest.xml",
+    package_name: "com.android.wm.shell.flicker.pip",
+    instrumentation_target_package: "com.android.wm.shell.flicker.pip",
+    test_config_template: "AndroidTestTemplate.xml",
+    srcs: ["src/**/*.kt"],
+    static_libs: ["WMShellFlickerTestsBase"],
+    data: ["trace_config/*"],
+}
+
 android_test {
     name: "WMShellFlickerTestsPipApps",
     defaults: ["WMShellFlickerTestsDefault"],
@@ -146,3 +165,185 @@
     test_plan_include: "csuitePlan.xml",
     test_config_template: "csuiteDefaultTemplate.xml",
 }
+
+////////////////////////////////////////////////////////////////////////////////
+// Begin breakdowns for WMShellFlickerTestsPip module
+
+test_module_config {
+    name: "WMShellFlickerTestsPip-CatchAll",
+    base: "WMShellFlickerTestsPip",
+    exclude_filters: [
+        "com.android.wm.shell.flicker.pip.AutoEnterPipOnGoToHomeTest",
+        "com.android.wm.shell.flicker.pip.AutoEnterPipWithSourceRectHintTest",
+        "com.android.wm.shell.flicker.pip.ClosePipBySwipingDownTest",
+        "com.android.wm.shell.flicker.pip.ClosePipWithDismissButtonTest",
+        "com.android.wm.shell.flicker.pip.EnterPipOnUserLeaveHintTest",
+        "com.android.wm.shell.flicker.pip.EnterPipViaAppUiButtonTest",
+        "com.android.wm.shell.flicker.pip.ExitPipToAppViaExpandButtonTest",
+        "com.android.wm.shell.flicker.pip.ExitPipToAppViaIntentTest",
+        "com.android.wm.shell.flicker.pip.ExpandPipOnDoubleClickTest",
+        "com.android.wm.shell.flicker.pip.ExpandPipOnPinchOpenTest",
+        "com.android.wm.shell.flicker.pip.FromSplitScreenAutoEnterPipOnGoToHomeTest",
+        "com.android.wm.shell.flicker.pip.FromSplitScreenEnterPipOnUserLeaveHintTest",
+        "com.android.wm.shell.flicker.pip.MovePipDownOnShelfHeightChange",
+        "com.android.wm.shell.flicker.pip.MovePipOnImeVisibilityChangeTest",
+        "com.android.wm.shell.flicker.pip.MovePipUpOnShelfHeightChangeTest",
+        "com.android.wm.shell.flicker.pip.PipAspectRatioChangeTest",
+        "com.android.wm.shell.flicker.pip.PipDragTest",
+        "com.android.wm.shell.flicker.pip.PipDragThenSnapTest",
+        "com.android.wm.shell.flicker.pip.PipPinchInTest",
+        "com.android.wm.shell.flicker.pip.SetRequestedOrientationWhilePinned",
+        "com.android.wm.shell.flicker.pip.ShowPipAndRotateDisplay",
+    ],
+    test_suites: ["device-tests"],
+}
+
+test_module_config {
+    name: "WMShellFlickerTestsPip-AutoEnterPipOnGoToHomeTest",
+    base: "WMShellFlickerTestsPip",
+    include_filters: ["com.android.wm.shell.flicker.pip.AutoEnterPipOnGoToHomeTest"],
+    test_suites: ["device-tests"],
+}
+
+test_module_config {
+    name: "WMShellFlickerTestsPip-AutoEnterPipWithSourceRectHintTest",
+    base: "WMShellFlickerTestsPip",
+    include_filters: ["com.android.wm.shell.flicker.pip.AutoEnterPipWithSourceRectHintTest"],
+    test_suites: ["device-tests"],
+}
+
+test_module_config {
+    name: "WMShellFlickerTestsPip-ClosePipBySwipingDownTest",
+    base: "WMShellFlickerTestsPip",
+    include_filters: ["com.android.wm.shell.flicker.pip.ClosePipBySwipingDownTest"],
+    test_suites: ["device-tests"],
+}
+
+test_module_config {
+    name: "WMShellFlickerTestsPip-ClosePipWithDismissButtonTest",
+    base: "WMShellFlickerTestsPip",
+    include_filters: ["com.android.wm.shell.flicker.pip.ClosePipWithDismissButtonTest"],
+    test_suites: ["device-tests"],
+}
+
+test_module_config {
+    name: "WMShellFlickerTestsPip-EnterPipOnUserLeaveHintTest",
+    base: "WMShellFlickerTestsPip",
+    include_filters: ["com.android.wm.shell.flicker.pip.EnterPipOnUserLeaveHintTest"],
+    test_suites: ["device-tests"],
+}
+
+test_module_config {
+    name: "WMShellFlickerTestsPip-EnterPipViaAppUiButtonTest",
+    base: "WMShellFlickerTestsPip",
+    include_filters: ["com.android.wm.shell.flicker.pip.EnterPipViaAppUiButtonTest"],
+    test_suites: ["device-tests"],
+}
+
+test_module_config {
+    name: "WMShellFlickerTestsPip-ExitPipToAppViaExpandButtonTest",
+    base: "WMShellFlickerTestsPip",
+    include_filters: ["com.android.wm.shell.flicker.pip.ExitPipToAppViaExpandButtonTest"],
+    test_suites: ["device-tests"],
+}
+
+test_module_config {
+    name: "WMShellFlickerTestsPip-ExitPipToAppViaIntentTest",
+    base: "WMShellFlickerTestsPip",
+    include_filters: ["com.android.wm.shell.flicker.pip.ExitPipToAppViaIntentTest"],
+    test_suites: ["device-tests"],
+}
+
+test_module_config {
+    name: "WMShellFlickerTestsPip-ExpandPipOnDoubleClickTest",
+    base: "WMShellFlickerTestsPip",
+    include_filters: ["com.android.wm.shell.flicker.pip.ExpandPipOnDoubleClickTest"],
+    test_suites: ["device-tests"],
+}
+
+test_module_config {
+    name: "WMShellFlickerTestsPip-ExpandPipOnPinchOpenTest",
+    base: "WMShellFlickerTestsPip",
+    include_filters: ["com.android.wm.shell.flicker.pip.ExpandPipOnPinchOpenTest"],
+    test_suites: ["device-tests"],
+}
+
+test_module_config {
+    name: "WMShellFlickerTestsPip-FromSplitScreenAutoEnterPipOnGoToHomeTest",
+    base: "WMShellFlickerTestsPip",
+    include_filters: ["com.android.wm.shell.flicker.pip.FromSplitScreenAutoEnterPipOnGoToHomeTest"],
+    test_suites: ["device-tests"],
+}
+
+test_module_config {
+    name: "WMShellFlickerTestsPip-FromSplitScreenEnterPipOnUserLeaveHintTest",
+    base: "WMShellFlickerTestsPip",
+    include_filters: ["com.android.wm.shell.flicker.pip.FromSplitScreenEnterPipOnUserLeaveHintTest"],
+    test_suites: ["device-tests"],
+}
+
+test_module_config {
+    name: "WMShellFlickerTestsPip-MovePipDownOnShelfHeightChange",
+    base: "WMShellFlickerTestsPip",
+    include_filters: ["com.android.wm.shell.flicker.pip.MovePipDownOnShelfHeightChange"],
+    test_suites: ["device-tests"],
+}
+
+test_module_config {
+    name: "WMShellFlickerTestsPip-MovePipOnImeVisibilityChangeTest",
+    base: "WMShellFlickerTestsPip",
+    include_filters: ["com.android.wm.shell.flicker.pip.MovePipOnImeVisibilityChangeTest"],
+    test_suites: ["device-tests"],
+}
+
+test_module_config {
+    name: "WMShellFlickerTestsPip-MovePipUpOnShelfHeightChangeTest",
+    base: "WMShellFlickerTestsPip",
+    include_filters: ["com.android.wm.shell.flicker.pip.MovePipUpOnShelfHeightChangeTest"],
+    test_suites: ["device-tests"],
+}
+
+test_module_config {
+    name: "WMShellFlickerTestsPip-PipAspectRatioChangeTest",
+    base: "WMShellFlickerTestsPip",
+    include_filters: ["com.android.wm.shell.flicker.pip.PipAspectRatioChangeTest"],
+    test_suites: ["device-tests"],
+}
+
+test_module_config {
+    name: "WMShellFlickerTestsPip-PipDragTest",
+    base: "WMShellFlickerTestsPip",
+    include_filters: ["com.android.wm.shell.flicker.pip.PipDragTest"],
+    test_suites: ["device-tests"],
+}
+
+test_module_config {
+    name: "WMShellFlickerTestsPip-PipDragThenSnapTest",
+    base: "WMShellFlickerTestsPip",
+    include_filters: ["com.android.wm.shell.flicker.pip.PipDragThenSnapTest"],
+    test_suites: ["device-tests"],
+}
+
+test_module_config {
+    name: "WMShellFlickerTestsPip-PipPinchInTest",
+    base: "WMShellFlickerTestsPip",
+    include_filters: ["com.android.wm.shell.flicker.pip.PipPinchInTest"],
+    test_suites: ["device-tests"],
+}
+
+test_module_config {
+    name: "WMShellFlickerTestsPip-SetRequestedOrientationWhilePinned",
+    base: "WMShellFlickerTestsPip",
+    include_filters: ["com.android.wm.shell.flicker.pip.SetRequestedOrientationWhilePinned"],
+    test_suites: ["device-tests"],
+}
+
+test_module_config {
+    name: "WMShellFlickerTestsPip-ShowPipAndRotateDisplay",
+    base: "WMShellFlickerTestsPip",
+    include_filters: ["com.android.wm.shell.flicker.pip.ShowPipAndRotateDisplay"],
+    test_suites: ["device-tests"],
+}
+
+// End breakdowns for WMShellFlickerTestsPip module
+////////////////////////////////////////////////////////////////////////////////
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java
index 311b1c5..90e3f7f 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java
@@ -83,10 +83,10 @@
 import com.android.wm.shell.RootTaskDisplayAreaOrganizer;
 import com.android.wm.shell.ShellTestCase;
 import com.android.wm.shell.TestShellExecutor;
+import com.android.wm.shell.shared.ShellSharedConstants;
 import com.android.wm.shell.sysui.ShellCommandHandler;
 import com.android.wm.shell.sysui.ShellController;
 import com.android.wm.shell.sysui.ShellInit;
-import com.android.wm.shell.sysui.ShellSharedConstants;
 import com.android.wm.shell.transition.Transitions;
 
 import org.junit.Before;
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleDataRepositoryTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleDataRepositoryTest.kt
index e359957..9ec62c9 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleDataRepositoryTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleDataRepositoryTest.kt
@@ -124,6 +124,7 @@
 
     private val testHandler = Handler(Looper.getMainLooper())
     private val mainExecutor = HandlerExecutor(testHandler)
+    private val bgExecutor = HandlerExecutor(testHandler)
     private val launcherApps = mock<LauncherApps>()
 
     private val persistedBubbles = SparseArray<List<BubbleEntity>>()
@@ -134,7 +135,8 @@
     @Before
     fun setup() {
         persistentRepository = BubblePersistentRepository(mContext)
-        dataRepository = spy(BubbleDataRepository(launcherApps, mainExecutor, persistentRepository))
+        dataRepository =
+            spy(BubbleDataRepository(launcherApps, mainExecutor, bgExecutor, persistentRepository))
 
         persistedBubbles.put(0, user0BubbleEntities)
         persistedBubbles.put(1, user1BubbleEntities)
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleDataTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleDataTest.java
index c138a24..859602e 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleDataTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleDataTest.java
@@ -117,6 +117,8 @@
     private BubbleEducationController mEducationController;
     @Mock
     private ShellExecutor mMainExecutor;
+    @Mock
+    private ShellExecutor mBgExecutor;
 
     @Captor
     private ArgumentCaptor<BubbleData.Update> mUpdateCaptor;
@@ -144,47 +146,47 @@
         when(ranking.isTextChanged()).thenReturn(true);
         mEntryInterruptive = createBubbleEntry(1, "interruptive", "package.d", ranking);
         mBubbleInterruptive = new Bubble(mEntryInterruptive, mBubbleMetadataFlagListener, null,
-                mMainExecutor);
+                mMainExecutor, mBgExecutor);
 
         mEntryDismissed = createBubbleEntry(1, "dismissed", "package.d", null);
         mBubbleDismissed = new Bubble(mEntryDismissed, mBubbleMetadataFlagListener, null,
-                mMainExecutor);
+                mMainExecutor, mBgExecutor);
 
         mEntryLocusId = createBubbleEntry(1, "keyLocus", "package.e", null,
                 new LocusId("locusId1"));
         mBubbleLocusId = new Bubble(mEntryLocusId,
                 mBubbleMetadataFlagListener,
                 null /* pendingIntentCanceledListener */,
-                mMainExecutor);
+                mMainExecutor, mBgExecutor);
 
         mBubbleA1 = new Bubble(mEntryA1,
                 mBubbleMetadataFlagListener,
                 mPendingIntentCanceledListener,
-                mMainExecutor);
+                mMainExecutor, mBgExecutor);
         mBubbleA2 = new Bubble(mEntryA2,
                 mBubbleMetadataFlagListener,
                 mPendingIntentCanceledListener,
-                mMainExecutor);
+                mMainExecutor, mBgExecutor);
         mBubbleA3 = new Bubble(mEntryA3,
                 mBubbleMetadataFlagListener,
                 mPendingIntentCanceledListener,
-                mMainExecutor);
+                mMainExecutor, mBgExecutor);
         mBubbleB1 = new Bubble(mEntryB1,
                 mBubbleMetadataFlagListener,
                 mPendingIntentCanceledListener,
-                mMainExecutor);
+                mMainExecutor, mBgExecutor);
         mBubbleB2 = new Bubble(mEntryB2,
                 mBubbleMetadataFlagListener,
                 mPendingIntentCanceledListener,
-                mMainExecutor);
+                mMainExecutor, mBgExecutor);
         mBubbleB3 = new Bubble(mEntryB3,
                 mBubbleMetadataFlagListener,
                 mPendingIntentCanceledListener,
-                mMainExecutor);
+                mMainExecutor, mBgExecutor);
         mBubbleC1 = new Bubble(mEntryC1,
                 mBubbleMetadataFlagListener,
                 mPendingIntentCanceledListener,
-                mMainExecutor);
+                mMainExecutor, mBgExecutor);
 
         Intent appBubbleIntent = new Intent(mContext, BubblesTestActivity.class);
         appBubbleIntent.setPackage(mContext.getPackageName());
@@ -192,12 +194,12 @@
                 appBubbleIntent,
                 new UserHandle(1),
                 mock(Icon.class),
-                mMainExecutor);
+                mMainExecutor, mBgExecutor);
 
         mPositioner = new TestableBubblePositioner(mContext,
                 mContext.getSystemService(WindowManager.class));
         mBubbleData = new BubbleData(getContext(), mBubbleLogger, mPositioner, mEducationController,
-                mMainExecutor);
+                mMainExecutor, mBgExecutor);
 
         // Used by BubbleData to set lastAccessedTime
         when(mTimeSource.currentTimeMillis()).thenReturn(1000L);
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleTest.java
index afec1ee..50c4a18 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleTest.java
@@ -61,6 +61,8 @@
     private StatusBarNotification mSbn;
     @Mock
     private ShellExecutor mMainExecutor;
+    @Mock
+    private ShellExecutor mBgExecutor;
 
     private BubbleEntry mBubbleEntry;
     private Bundle mExtras;
@@ -85,7 +87,8 @@
         when(mNotif.getBubbleMetadata()).thenReturn(metadata);
         when(mSbn.getKey()).thenReturn("mock");
         mBubbleEntry = new BubbleEntry(mSbn, null, true, false, false, false);
-        mBubble = new Bubble(mBubbleEntry, mBubbleMetadataFlagListener, null, mMainExecutor);
+        mBubble = new Bubble(mBubbleEntry, mBubbleMetadataFlagListener, null, mMainExecutor,
+                mBgExecutor);
     }
 
     @Test
@@ -176,7 +179,8 @@
 
     @Test
     public void testBubbleIsConversation_hasNoShortcut() {
-        Bubble bubble = new Bubble(mBubbleEntry, mBubbleMetadataFlagListener, null, mMainExecutor);
+        Bubble bubble = new Bubble(mBubbleEntry, mBubbleMetadataFlagListener, null, mMainExecutor,
+                mBgExecutor);
         assertThat(bubble.getShortcutInfo()).isNull();
         assertThat(bubble.isConversation()).isFalse();
     }
@@ -199,7 +203,7 @@
         Intent intent = new Intent(mContext, BubblesTestActivity.class);
         intent.setPackage(mContext.getPackageName());
         Bubble bubble = Bubble.createAppBubble(intent, new UserHandle(1 /* userId */),
-                null /* icon */, mMainExecutor);
+                null /* icon */, mMainExecutor, mBgExecutor);
         BubbleInfo bubbleInfo = bubble.asBubbleBarBubble();
 
         assertThat(bubble.getShortcutInfo()).isNull();
@@ -215,6 +219,6 @@
                 .build();
         return new Bubble("mockKey", shortcutInfo, 10, Resources.ID_NULL,
                 "mockTitle", 0 /* taskId */, "mockLocus", true /* isDismissible */,
-                mMainExecutor, mBubbleMetadataFlagListener);
+                mMainExecutor, mBgExecutor, mBubbleMetadataFlagListener);
     }
 }
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleViewInfoTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleViewInfoTest.kt
index 4a4c5e8..8035e91 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleViewInfoTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleViewInfoTest.kt
@@ -70,6 +70,7 @@
     private lateinit var bubble: Bubble
     private lateinit var bubbleController: BubbleController
     private lateinit var mainExecutor: ShellExecutor
+    private lateinit var bgExecutor: ShellExecutor
     private lateinit var bubbleStackView: BubbleStackView
     private lateinit var bubbleBarLayerView: BubbleBarLayerView
     private lateinit var bubblePositioner: BubblePositioner
@@ -92,6 +93,7 @@
             )
 
         mainExecutor = TestShellExecutor()
+        bgExecutor = TestShellExecutor()
         val windowManager = context.getSystemService(WindowManager::class.java)
         val shellInit = ShellInit(mainExecutor)
         val shellCommandHandler = ShellCommandHandler()
@@ -104,7 +106,8 @@
                 mock<BubbleLogger>(),
                 bubblePositioner,
                 BubbleEducationController(context),
-                mainExecutor
+                mainExecutor,
+                bgExecutor
             )
         val surfaceSynchronizer = { obj: Runnable -> obj.run() }
 
@@ -132,7 +135,7 @@
                 null,
                 mainExecutor,
                 mock<Handler>(),
-                mock<ShellExecutor>(),
+                bgExecutor,
                 mock<TaskViewTransitions>(),
                 mock<Transitions>(),
                 mock<SyncTransactionQueue>(),
@@ -256,7 +259,7 @@
             "mockLocus",
             true /* isDismissible */,
             mainExecutor,
-            metadataFlagListener
-        )
+            bgExecutor,
+            metadataFlagListener)
     }
 }
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/DisplayImeControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/DisplayImeControllerTest.java
index 654d7a8e..f8f0db9 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/DisplayImeControllerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/DisplayImeControllerTest.java
@@ -43,6 +43,7 @@
 import androidx.test.filters.SmallTest;
 
 import com.android.wm.shell.ShellTestCase;
+import com.android.wm.shell.shared.TransactionPool;
 import com.android.wm.shell.sysui.ShellInit;
 
 import org.junit.Before;
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/split/SplitLayoutTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/split/SplitLayoutTests.java
index cfe8e07..09fcd8b 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/split/SplitLayoutTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/split/SplitLayoutTests.java
@@ -19,9 +19,9 @@
 import static android.content.res.Configuration.ORIENTATION_LANDSCAPE;
 import static android.content.res.Configuration.ORIENTATION_PORTRAIT;
 
-import static com.android.wm.shell.common.split.SplitScreenConstants.SNAP_TO_50_50;
-import static com.android.wm.shell.common.split.SplitScreenConstants.SNAP_TO_END_AND_DISMISS;
-import static com.android.wm.shell.common.split.SplitScreenConstants.SNAP_TO_START_AND_DISMISS;
+import static com.android.wm.shell.shared.split.SplitScreenConstants.SNAP_TO_50_50;
+import static com.android.wm.shell.shared.split.SplitScreenConstants.SNAP_TO_END_AND_DISMISS;
+import static com.android.wm.shell.shared.split.SplitScreenConstants.SNAP_TO_START_AND_DISMISS;
 
 import static com.google.common.truth.Truth.assertThat;
 
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/impl/CompatUIComponentTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/impl/CompatUIComponentTest.kt
new file mode 100644
index 0000000..2c203c4
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/impl/CompatUIComponentTest.kt
@@ -0,0 +1,122 @@
+/*
+ * 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.wm.shell.compatui.impl
+
+import android.app.ActivityManager
+import android.graphics.Point
+import android.testing.AndroidTestingRunner
+import android.view.View
+import androidx.test.filters.SmallTest
+import com.android.wm.shell.ShellTestCase
+import com.android.wm.shell.common.DisplayLayout
+import com.android.wm.shell.common.SyncTransactionQueue
+import com.android.wm.shell.compatui.api.CompatUIComponent
+import com.android.wm.shell.compatui.api.CompatUIComponentState
+import com.android.wm.shell.compatui.api.CompatUIInfo
+import com.android.wm.shell.compatui.api.CompatUIState
+import junit.framework.Assert.assertEquals
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.kotlin.mock
+
+/**
+ * Tests for {@link CompatUIComponent}.
+ *
+ * Build/Install/Run:
+ *  atest WMShellUnitTests:CompatUIComponentTest
+ */
+@RunWith(AndroidTestingRunner::class)
+@SmallTest
+class CompatUIComponentTest : ShellTestCase() {
+
+    private lateinit var component: CompatUIComponent
+    private lateinit var layout: FakeCompatUILayout
+    private lateinit var spec: FakeCompatUISpec
+    private lateinit var state: CompatUIState
+    private lateinit var info: CompatUIInfo
+    private lateinit var syncQueue: SyncTransactionQueue
+    private lateinit var displayLayout: DisplayLayout
+    private lateinit var view: View
+    private lateinit var position: Point
+    private lateinit var componentState: CompatUIComponentState
+
+    @JvmField
+    @Rule
+    val compatUIHandlerRule: CompatUIHandlerRule = CompatUIHandlerRule()
+
+    @Before
+    fun setUp() {
+        state = CompatUIState()
+        view = View(mContext)
+        position = Point(123, 456)
+        layout = FakeCompatUILayout(viewBuilderReturn = view, positionBuilderReturn = position)
+        spec = FakeCompatUISpec("comp", layout = layout)
+        info = testCompatUIInfo()
+        syncQueue = mock<SyncTransactionQueue>()
+        displayLayout = mock<DisplayLayout>()
+        component =
+            CompatUIComponent(spec.getSpec(),
+                "compId",
+                mContext,
+                state,
+                info,
+                syncQueue,
+                displayLayout)
+        componentState = object : CompatUIComponentState {}
+        state.registerUIComponent("compId", component, componentState)
+    }
+
+    @Test
+    fun `when initLayout is invoked spec fields are used`() {
+        compatUIHandlerRule.postBlocking {
+            component.initLayout(info)
+        }
+        with(layout) {
+            assertViewBuilderInvocation(1)
+            assertEquals(info, lastViewBuilderCompatUIInfo)
+            assertEquals(componentState, lastViewBuilderCompState)
+            assertViewBinderInvocation(0)
+            assertPositionFactoryInvocation(1)
+            assertEquals(info, lastPositionFactoryCompatUIInfo)
+            assertEquals(view, lastPositionFactoryView)
+            assertEquals(componentState, lastPositionFactoryCompState)
+            assertEquals(state.sharedState, lastPositionFactorySharedState)
+        }
+    }
+
+    @Test
+    fun `when update is invoked only position and binder spec fields are used`() {
+        compatUIHandlerRule.postBlocking {
+            component.initLayout(info)
+            layout.resetState()
+            component.update(info)
+        }
+        with(layout) {
+            assertViewBuilderInvocation(0)
+            assertViewBinderInvocation(1)
+            assertPositionFactoryInvocation(1)
+        }
+    }
+
+    private fun testCompatUIInfo(): CompatUIInfo {
+        val taskInfo = ActivityManager.RunningTaskInfo()
+        taskInfo.taskId = 1
+        return CompatUIInfo(taskInfo, null)
+    }
+}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/impl/CompatUIHandlerRule.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/impl/CompatUIHandlerRule.kt
new file mode 100644
index 0000000..4b8b65c
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/impl/CompatUIHandlerRule.kt
@@ -0,0 +1,67 @@
+/*
+ * 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.wm.shell.compatui.impl
+
+import android.os.HandlerThread
+import java.util.concurrent.CountDownLatch
+import org.junit.rules.TestRule
+import org.junit.runner.Description
+import org.junit.runners.model.Statement
+
+/**
+ * Utility {@link TestRule} to manage Handlers in Compat UI tests.
+ */
+class CompatUIHandlerRule : TestRule {
+
+    private lateinit var handler: HandlerThread
+
+    /**
+     * Makes the HandlerThread available during the test
+     */
+    override fun apply(base: Statement?, description: Description?): Statement {
+        handler = HandlerThread("CompatUIHandler").apply {
+            start()
+        }
+        return object : Statement() {
+            @Throws(Throwable::class)
+            override fun evaluate() {
+                try {
+                    base!!.evaluate()
+                } finally {
+                    handler.quitSafely()
+                }
+            }
+        }
+    }
+
+    /**
+     * Posts a {@link Runnable} for the Handler
+     * @param runnable The Runnable to execute
+     */
+    fun postBlocking(runnable: Runnable) {
+        val countDown = CountDownLatch(/* count = */ 1)
+        handler.threadHandler.post{
+            runnable.run()
+            countDown.countDown()
+        }
+        try {
+            countDown.await()
+        } catch (e: InterruptedException) {
+            // No-op
+        }
+    }
+}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/impl/CompatUIStateUtil.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/impl/CompatUIStateUtil.kt
index 43bd412..4f0e5b9 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/impl/CompatUIStateUtil.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/impl/CompatUIStateUtil.kt
@@ -17,7 +17,6 @@
 package com.android.wm.shell.compatui.impl
 
 import com.android.wm.shell.compatui.api.CompatUIComponentState
-import com.android.wm.shell.compatui.api.CompatUISpec
 import com.android.wm.shell.compatui.api.CompatUIState
 import junit.framework.Assert.assertEquals
 import junit.framework.Assert.assertNotNull
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/impl/DefaultCompatUIHandlerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/impl/DefaultCompatUIHandlerTest.kt
index 8136074..66852ad5 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/impl/DefaultCompatUIHandlerTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/impl/DefaultCompatUIHandlerTest.kt
@@ -18,13 +18,21 @@
 
 import android.app.ActivityManager
 import android.testing.AndroidTestingRunner
+import android.view.View
 import androidx.test.filters.SmallTest
+import com.android.wm.shell.ShellTestCase
+import com.android.wm.shell.TestShellExecutor
+import com.android.wm.shell.common.DisplayController
+import com.android.wm.shell.common.ShellExecutor
+import com.android.wm.shell.common.SyncTransactionQueue
 import com.android.wm.shell.compatui.api.CompatUIComponentState
 import com.android.wm.shell.compatui.api.CompatUIInfo
 import com.android.wm.shell.compatui.api.CompatUIState
 import org.junit.Before
+import org.junit.Rule
 import org.junit.Test
 import org.junit.runner.RunWith
+import org.mockito.kotlin.mock
 
 /**
  * Tests for {@link DefaultCompatUIHandler}.
@@ -34,20 +42,37 @@
  */
 @RunWith(AndroidTestingRunner::class)
 @SmallTest
-class DefaultCompatUIHandlerTest {
+class DefaultCompatUIHandlerTest : ShellTestCase() {
+
+    @JvmField
+    @Rule
+    val compatUIHandlerRule: CompatUIHandlerRule = CompatUIHandlerRule()
 
     lateinit var compatUIRepository: FakeCompatUIRepository
     lateinit var compatUIHandler: DefaultCompatUIHandler
     lateinit var compatUIState: CompatUIState
     lateinit var fakeIdGenerator: FakeCompatUIComponentIdGenerator
+    lateinit var syncQueue: SyncTransactionQueue
+    lateinit var displayController: DisplayController
+    lateinit var shellExecutor: TestShellExecutor
+    lateinit var componentFactory: FakeCompatUIComponentFactory
 
     @Before
     fun setUp() {
+        shellExecutor = TestShellExecutor()
         compatUIRepository = FakeCompatUIRepository()
         compatUIState = CompatUIState()
         fakeIdGenerator = FakeCompatUIComponentIdGenerator("compId")
-        compatUIHandler = DefaultCompatUIHandler(compatUIRepository, compatUIState,
-            fakeIdGenerator)
+        syncQueue = mock<SyncTransactionQueue>()
+        displayController = mock<DisplayController>()
+        componentFactory = FakeCompatUIComponentFactory(mContext, syncQueue, displayController)
+        compatUIHandler =
+            DefaultCompatUIHandler(
+                compatUIRepository,
+                compatUIState,
+                fakeIdGenerator,
+                componentFactory,
+                shellExecutor)
     }
 
     @Test
@@ -57,12 +82,18 @@
             creationReturn = false,
             removalReturn = false
         )
-        val fakeCompatUISpec = FakeCompatUISpec("one", fakeLifecycle).getSpec()
+        val fakeCompatUILayout = FakeCompatUILayout(viewBuilderReturn = View(mContext))
+        val fakeCompatUISpec =
+            FakeCompatUISpec(name = "one",
+                lifecycle = fakeLifecycle,
+                layout = fakeCompatUILayout).getSpec()
         compatUIRepository.addSpec(fakeCompatUISpec)
 
         val generatedId = fakeIdGenerator.generatedComponentId
 
-        compatUIHandler.onCompatInfoChanged(testCompatUIInfo())
+        compatUIHandlerRule.postBlocking {
+            compatUIHandler.onCompatInfoChanged(testCompatUIInfo())
+        }
 
         fakeIdGenerator.assertGenerateInvocations(1)
         fakeLifecycle.assertCreationInvocation(1)
@@ -71,7 +102,9 @@
         compatUIState.assertHasNoStateFor(generatedId)
         compatUIState.assertHasNoComponentFor(generatedId)
 
-        compatUIHandler.onCompatInfoChanged(testCompatUIInfo())
+        compatUIHandlerRule.postBlocking {
+            compatUIHandler.onCompatInfoChanged(testCompatUIInfo())
+        }
         fakeLifecycle.assertCreationInvocation(2)
         fakeLifecycle.assertRemovalInvocation(0)
         fakeLifecycle.assertInitialStateInvocation(0)
@@ -86,12 +119,18 @@
             creationReturn = true,
             removalReturn = false
         )
-        val fakeCompatUISpec = FakeCompatUISpec("one", fakeLifecycle).getSpec()
+        val fakeCompatUILayout = FakeCompatUILayout(viewBuilderReturn = View(mContext))
+        val fakeCompatUISpec =
+            FakeCompatUISpec(name = "one",
+                lifecycle = fakeLifecycle,
+                layout = fakeCompatUILayout).getSpec()
         compatUIRepository.addSpec(fakeCompatUISpec)
 
         val generatedId = fakeIdGenerator.generatedComponentId
 
-        compatUIHandler.onCompatInfoChanged(testCompatUIInfo())
+        compatUIHandlerRule.postBlocking {
+            compatUIHandler.onCompatInfoChanged(testCompatUIInfo())
+        }
 
         fakeLifecycle.assertCreationInvocation(1)
         fakeLifecycle.assertRemovalInvocation(0)
@@ -99,7 +138,9 @@
         compatUIState.assertHasNoStateFor(generatedId)
         compatUIState.assertHasComponentFor(generatedId)
 
-        compatUIHandler.onCompatInfoChanged(testCompatUIInfo())
+        compatUIHandlerRule.postBlocking {
+            compatUIHandler.onCompatInfoChanged(testCompatUIInfo())
+        }
 
         fakeLifecycle.assertCreationInvocation(1)
         fakeLifecycle.assertRemovalInvocation(1)
@@ -117,12 +158,18 @@
             removalReturn = false,
             initialState = { _, _ -> fakeComponentState }
         )
-        val fakeCompatUISpec = FakeCompatUISpec("one", fakeLifecycle).getSpec()
+        val fakeCompatUILayout = FakeCompatUILayout(viewBuilderReturn = View(mContext))
+        val fakeCompatUISpec =
+            FakeCompatUISpec(name = "one",
+                lifecycle = fakeLifecycle,
+                layout = fakeCompatUILayout).getSpec()
         compatUIRepository.addSpec(fakeCompatUISpec)
 
         val generatedId = fakeIdGenerator.generatedComponentId
 
-        compatUIHandler.onCompatInfoChanged(testCompatUIInfo())
+        compatUIHandlerRule.postBlocking {
+            compatUIHandler.onCompatInfoChanged(testCompatUIInfo())
+        }
 
         fakeLifecycle.assertCreationInvocation(1)
         fakeLifecycle.assertRemovalInvocation(0)
@@ -130,7 +177,9 @@
         compatUIState.assertHasStateEqualsTo(generatedId, fakeComponentState)
         compatUIState.assertHasComponentFor(generatedId)
 
-        compatUIHandler.onCompatInfoChanged(testCompatUIInfo())
+        compatUIHandlerRule.postBlocking {
+            compatUIHandler.onCompatInfoChanged(testCompatUIInfo())
+        }
 
         fakeLifecycle.assertCreationInvocation(1)
         fakeLifecycle.assertRemovalInvocation(1)
@@ -148,12 +197,18 @@
             removalReturn = true,
             initialState = { _, _ -> fakeComponentState }
         )
-        val fakeCompatUISpec = FakeCompatUISpec("one", fakeLifecycle).getSpec()
+        val fakeCompatUILayout = FakeCompatUILayout(viewBuilderReturn = View(mContext))
+        val fakeCompatUISpec =
+            FakeCompatUISpec(name = "one",
+                lifecycle = fakeLifecycle,
+                layout = fakeCompatUILayout).getSpec()
         compatUIRepository.addSpec(fakeCompatUISpec)
 
         val generatedId = fakeIdGenerator.generatedComponentId
 
-        compatUIHandler.onCompatInfoChanged(testCompatUIInfo())
+        compatUIHandlerRule.postBlocking {
+            compatUIHandler.onCompatInfoChanged(testCompatUIInfo())
+        }
 
         fakeLifecycle.assertCreationInvocation(1)
         fakeLifecycle.assertRemovalInvocation(0)
@@ -161,7 +216,9 @@
         compatUIState.assertHasStateEqualsTo(generatedId, fakeComponentState)
         compatUIState.assertHasComponentFor(generatedId)
 
-        compatUIHandler.onCompatInfoChanged(testCompatUIInfo())
+        compatUIHandlerRule.postBlocking {
+            compatUIHandler.onCompatInfoChanged(testCompatUIInfo())
+        }
 
         fakeLifecycle.assertCreationInvocation(1)
         fakeLifecycle.assertRemovalInvocation(1)
@@ -177,17 +234,56 @@
             creationReturn = true,
             removalReturn = true,
         )
-        val fakeCompatUISpec = FakeCompatUISpec("one", fakeLifecycle).getSpec()
+        val fakeCompatUILayout = FakeCompatUILayout(viewBuilderReturn = View(mContext))
+        val fakeCompatUISpec = FakeCompatUISpec("one", fakeLifecycle,
+            fakeCompatUILayout).getSpec()
         compatUIRepository.addSpec(fakeCompatUISpec)
         // Component creation
         fakeIdGenerator.assertGenerateInvocations(0)
-        compatUIHandler.onCompatInfoChanged(testCompatUIInfo())
+        compatUIHandlerRule.postBlocking {
+            compatUIHandler.onCompatInfoChanged(testCompatUIInfo())
+        }
         fakeIdGenerator.assertGenerateInvocations(1)
 
-        compatUIHandler.onCompatInfoChanged(testCompatUIInfo())
+        compatUIHandlerRule.postBlocking {
+            compatUIHandler.onCompatInfoChanged(testCompatUIInfo())
+        }
         fakeIdGenerator.assertGenerateInvocations(2)
     }
 
+    @Test
+    fun `viewBuilder and viewBinder invoked if component is created and released when destroyed`() {
+        // We add a spec to the repository
+        val fakeLifecycle = FakeCompatUILifecyclePredicates(
+            creationReturn = true,
+            removalReturn = true,
+        )
+        val fakeCompatUILayout = FakeCompatUILayout(viewBuilderReturn = View(mContext))
+        val fakeCompatUISpec = FakeCompatUISpec("one", fakeLifecycle,
+            fakeCompatUILayout).getSpec()
+        compatUIRepository.addSpec(fakeCompatUISpec)
+
+        compatUIHandlerRule.postBlocking {
+            compatUIHandler.onCompatInfoChanged(testCompatUIInfo())
+        }
+        shellExecutor.flushAll()
+        componentFactory.assertInvocations(1)
+        fakeCompatUILayout.assertViewBuilderInvocation(1)
+        fakeCompatUILayout.assertViewBinderInvocation(1)
+        fakeCompatUILayout.assertViewReleaserInvocation(0)
+
+        compatUIHandlerRule.postBlocking {
+            compatUIHandler.onCompatInfoChanged(testCompatUIInfo())
+        }
+        shellExecutor.flushAll()
+
+        componentFactory.assertInvocations(1)
+        fakeCompatUILayout.assertViewBuilderInvocation(1)
+        fakeCompatUILayout.assertViewBinderInvocation(1)
+        fakeCompatUILayout.assertViewReleaserInvocation(1)
+    }
+
+
     private fun testCompatUIInfo(): CompatUIInfo {
         val taskInfo = ActivityManager.RunningTaskInfo()
         taskInfo.taskId = 1
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/impl/DefaultCompatUIRepositoryTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/impl/DefaultCompatUIRepositoryTest.kt
index e35acb2..319122d 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/impl/DefaultCompatUIRepositoryTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/impl/DefaultCompatUIRepositoryTest.kt
@@ -16,9 +16,13 @@
 
 package com.android.wm.shell.compatui.impl
 
+
+import android.graphics.Point
 import android.platform.test.flag.junit.DeviceFlagsValueProvider
 import android.testing.AndroidTestingRunner
+import android.view.View
 import androidx.test.filters.SmallTest
+import com.android.wm.shell.compatui.api.CompatUILayout
 import com.android.wm.shell.compatui.api.CompatUILifecyclePredicates
 import com.android.wm.shell.compatui.api.CompatUIRepository
 import com.android.wm.shell.compatui.api.CompatUISpec
@@ -89,8 +93,14 @@
     }
 
     private fun specById(name: String): CompatUISpec =
-        CompatUISpec(name = name, lifecycle = CompatUILifecyclePredicates(
-            creationPredicate = { _, _ -> true },
-            removalPredicate = { _, _, _ -> true }
-        ))
+        CompatUISpec(name = name,
+            lifecycle = CompatUILifecyclePredicates(
+                creationPredicate = { _, _ -> true },
+                removalPredicate = { _, _, _ -> true }
+            ),
+            layout = CompatUILayout(
+                viewBuilder = { ctx, _, _ -> View(ctx) },
+                positionFactory = { _, _, _, _ -> Point(0, 0) }
+            )
+        )
 }
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/impl/FakeCompatUIComponentFactory.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/impl/FakeCompatUIComponentFactory.kt
new file mode 100644
index 0000000..782add8
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/impl/FakeCompatUIComponentFactory.kt
@@ -0,0 +1,69 @@
+/*
+ * 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.wm.shell.compatui.impl
+
+import android.content.Context
+import com.android.wm.shell.common.DisplayController
+import com.android.wm.shell.common.SyncTransactionQueue
+import com.android.wm.shell.compatui.api.CompatUIComponent
+import com.android.wm.shell.compatui.api.CompatUIComponentFactory
+import com.android.wm.shell.compatui.api.CompatUIInfo
+import com.android.wm.shell.compatui.api.CompatUISpec
+import com.android.wm.shell.compatui.api.CompatUIState
+import junit.framework.Assert.assertEquals
+
+/**
+ * Fake {@link CompatUIComponentFactory} implementation.
+ */
+class FakeCompatUIComponentFactory(
+    private val context: Context,
+    private val syncQueue: SyncTransactionQueue,
+    private val displayController: DisplayController
+) : CompatUIComponentFactory {
+
+    var lastSpec: CompatUISpec? = null
+    var lastCompId: String? = null
+    var lastState: CompatUIState? = null
+    var lastInfo: CompatUIInfo? = null
+
+    var numberInvocations = 0
+
+    override fun create(
+        spec: CompatUISpec,
+        compId: String,
+        state: CompatUIState,
+        compatUIInfo: CompatUIInfo
+    ): CompatUIComponent {
+        lastSpec = spec
+        lastCompId = compId
+        lastState = state
+        lastInfo = compatUIInfo
+        numberInvocations++
+        return CompatUIComponent(
+            spec,
+            compId,
+            context,
+            state,
+            compatUIInfo,
+            syncQueue,
+            displayController.getDisplayLayout(compatUIInfo.taskInfo.displayId)
+        )
+    }
+
+    fun assertInvocations(expected: Int) =
+        assertEquals(expected, numberInvocations)
+}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/impl/FakeCompatUILayout.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/impl/FakeCompatUILayout.kt
new file mode 100644
index 0000000..d7a178a
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/impl/FakeCompatUILayout.kt
@@ -0,0 +1,111 @@
+/*
+ * 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.wm.shell.compatui.impl
+
+import android.content.Context
+import android.graphics.Point
+import android.view.View
+import com.android.wm.shell.compatui.api.CompatUIComponentState
+import com.android.wm.shell.compatui.api.CompatUIInfo
+import com.android.wm.shell.compatui.api.CompatUILayout
+import com.android.wm.shell.compatui.api.CompatUISharedState
+import junit.framework.Assert.assertEquals
+
+/**
+ * Fake class for {@link CompatUILayout}
+ */
+class FakeCompatUILayout(
+    private val zOrderReturn: Int = 0,
+    private val layoutParamFlagsReturn: Int = 0,
+    private val viewBuilderReturn: View,
+    private val positionBuilderReturn: Point = Point(0, 0)
+) {
+
+    var viewBuilderInvocation = 0
+    var viewBinderInvocation = 0
+    var positionFactoryInvocation = 0
+    var viewReleaserInvocation = 0
+
+    var lastViewBuilderContext: Context? = null
+    var lastViewBuilderCompatUIInfo: CompatUIInfo? = null
+    var lastViewBuilderCompState: CompatUIComponentState? = null
+    var lastViewBinderView: View? = null
+    var lastViewBinderCompatUIInfo: CompatUIInfo? = null
+    var lastViewBinderSharedState: CompatUISharedState? = null
+    var lastViewBinderCompState: CompatUIComponentState? = null
+    var lastPositionFactoryView: View? = null
+    var lastPositionFactoryCompatUIInfo: CompatUIInfo? = null
+    var lastPositionFactorySharedState: CompatUISharedState? = null
+    var lastPositionFactoryCompState: CompatUIComponentState? = null
+
+    fun getLayout() = CompatUILayout(
+        zOrder = zOrderReturn,
+        layoutParamFlags = layoutParamFlagsReturn,
+        viewBuilder = { ctx, info, componentState ->
+            lastViewBuilderContext = ctx
+            lastViewBuilderCompatUIInfo = info
+            lastViewBuilderCompState = componentState
+            viewBuilderInvocation++
+            viewBuilderReturn
+        },
+        viewBinder = { view, info, sharedState, componentState ->
+            lastViewBinderView = view
+            lastViewBinderCompatUIInfo = info
+            lastViewBinderCompState = componentState
+            lastViewBinderSharedState = sharedState
+            viewBinderInvocation++
+        },
+        positionFactory = { view, info, sharedState, componentState ->
+            lastPositionFactoryView = view
+            lastPositionFactoryCompatUIInfo = info
+            lastPositionFactoryCompState = componentState
+            lastPositionFactorySharedState = sharedState
+            positionFactoryInvocation++
+            positionBuilderReturn
+        },
+        viewReleaser = { viewReleaserInvocation++ }
+    )
+
+    fun assertViewBuilderInvocation(expected: Int) =
+        assertEquals(expected, viewBuilderInvocation)
+
+    fun assertViewBinderInvocation(expected: Int) =
+        assertEquals(expected, viewBinderInvocation)
+
+    fun assertViewReleaserInvocation(expected: Int) =
+        assertEquals(expected, viewReleaserInvocation)
+
+    fun assertPositionFactoryInvocation(expected: Int) =
+        assertEquals(expected, positionFactoryInvocation)
+
+    fun resetState() {
+        viewBuilderInvocation = 0
+        viewBinderInvocation = 0
+        positionFactoryInvocation = 0
+        viewReleaserInvocation = 0
+        lastViewBuilderCompatUIInfo = null
+        lastViewBuilderCompState = null
+        lastViewBinderView = null
+        lastViewBinderCompatUIInfo = null
+        lastViewBinderSharedState = null
+        lastViewBinderCompState = null
+        lastPositionFactoryView = null
+        lastPositionFactoryCompatUIInfo = null
+        lastPositionFactorySharedState = null
+        lastPositionFactoryCompState = null
+    }
+}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/impl/FakeCompatUILifecyclePredicates.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/impl/FakeCompatUILifecyclePredicates.kt
index bbaa2db..f742ca3 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/impl/FakeCompatUILifecyclePredicates.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/impl/FakeCompatUILifecyclePredicates.kt
@@ -26,8 +26,8 @@
  * Fake class for {@link CompatUILifecycle}
  */
 class FakeCompatUILifecyclePredicates(
-    private val creationReturn: Boolean,
-    private val removalReturn: Boolean,
+    private val creationReturn: Boolean = false,
+    private val removalReturn: Boolean = false,
     private val initialState: (
         CompatUIInfo,
         CompatUISharedState
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/impl/FakeCompatUISpec.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/impl/FakeCompatUISpec.kt
index 1ecd52e..0912bf11 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/impl/FakeCompatUISpec.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/impl/FakeCompatUISpec.kt
@@ -23,10 +23,13 @@
  */
 class FakeCompatUISpec(
     val name: String,
-    val lifecycle: FakeCompatUILifecyclePredicates
+    val lifecycle: FakeCompatUILifecyclePredicates = FakeCompatUILifecyclePredicates(),
+    val layout: FakeCompatUILayout
 ) {
     fun getSpec(): CompatUISpec = CompatUISpec(
         name = name,
-        lifecycle = lifecycle.getLifecycle()
+        log = {str -> android.util.Log.d("COMPAT_UI_TEST", str)},
+        lifecycle = lifecycle.getLifecycle(),
+        layout = layout.getLayout()
     )
 }
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeEventLoggerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeEventLoggerTest.kt
index 4548fcb..70b3661 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeEventLoggerTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeEventLoggerTest.kt
@@ -16,14 +16,17 @@
 
 package com.android.wm.shell.desktopmode
 
-import com.android.dx.mockito.inline.extended.ExtendedMockito
+import com.android.dx.mockito.inline.extended.ExtendedMockito.verify
 import com.android.internal.util.FrameworkStatsLog
 import com.android.modules.utils.testing.ExtendedMockitoRule
 import com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.EnterReason
 import com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.ExitReason
+import com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.MinimizeReason
 import com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.TaskUpdate
+import com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.UnminimizeReason
+import com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.UNSET_MINIMIZE_REASON
+import com.android.wm.shell.desktopmode.DesktopModeEventLogger.Companion.UNSET_UNMINIMIZE_REASON
 import kotlinx.coroutines.runBlocking
-import org.junit.Before
 import org.junit.Rule
 import org.junit.Test
 import org.mockito.kotlin.eq
@@ -33,7 +36,7 @@
  */
 class DesktopModeEventLoggerTest {
 
-    private val desktopModeEventLogger  = DesktopModeEventLogger()
+    private val desktopModeEventLogger = DesktopModeEventLogger()
 
     @JvmField
     @Rule
@@ -44,7 +47,7 @@
     fun logSessionEnter_enterReason() = runBlocking {
         desktopModeEventLogger.logSessionEnter(sessionId = SESSION_ID, EnterReason.UNKNOWN_ENTER)
 
-        ExtendedMockito.verify {
+        verify {
             FrameworkStatsLog.write(
                 eq(FrameworkStatsLog.DESKTOP_MODE_UI_CHANGED),
                 /* event */
@@ -63,7 +66,7 @@
     fun logSessionExit_exitReason() = runBlocking {
         desktopModeEventLogger.logSessionExit(sessionId = SESSION_ID, ExitReason.UNKNOWN_EXIT)
 
-        ExtendedMockito.verify {
+        verify {
             FrameworkStatsLog.write(
                 eq(FrameworkStatsLog.DESKTOP_MODE_UI_CHANGED),
                 /* event */
@@ -82,7 +85,7 @@
     fun logTaskAdded_taskUpdate() = runBlocking {
         desktopModeEventLogger.logTaskAdded(sessionId = SESSION_ID, TASK_UPDATE)
 
-        ExtendedMockito.verify {
+        verify {
             FrameworkStatsLog.write(eq(FrameworkStatsLog.DESKTOP_MODE_SESSION_TASK_UPDATE),
                 /* task_event */
                 eq(FrameworkStatsLog.DESKTOP_MODE_SESSION_TASK_UPDATE__TASK_EVENT__TASK_ADDED),
@@ -99,7 +102,9 @@
                 /* task_y */
                 eq(TASK_UPDATE.taskY),
                 /* session_id */
-                eq(SESSION_ID))
+                eq(SESSION_ID),
+                eq(UNSET_MINIMIZE_REASON),
+                eq(UNSET_UNMINIMIZE_REASON))
         }
     }
 
@@ -107,7 +112,7 @@
     fun logTaskRemoved_taskUpdate() = runBlocking {
         desktopModeEventLogger.logTaskRemoved(sessionId = SESSION_ID, TASK_UPDATE)
 
-        ExtendedMockito.verify {
+        verify {
             FrameworkStatsLog.write(eq(FrameworkStatsLog.DESKTOP_MODE_SESSION_TASK_UPDATE),
                 /* task_event */
                 eq(FrameworkStatsLog.DESKTOP_MODE_SESSION_TASK_UPDATE__TASK_EVENT__TASK_REMOVED),
@@ -124,7 +129,9 @@
                 /* task_y */
                 eq(TASK_UPDATE.taskY),
                 /* session_id */
-                eq(SESSION_ID))
+                eq(SESSION_ID),
+                eq(UNSET_MINIMIZE_REASON),
+                eq(UNSET_UNMINIMIZE_REASON))
         }
     }
 
@@ -132,10 +139,11 @@
     fun logTaskInfoChanged_taskUpdate() = runBlocking {
         desktopModeEventLogger.logTaskInfoChanged(sessionId = SESSION_ID, TASK_UPDATE)
 
-        ExtendedMockito.verify {
+        verify {
             FrameworkStatsLog.write(eq(FrameworkStatsLog.DESKTOP_MODE_SESSION_TASK_UPDATE),
                 /* task_event */
-                eq(FrameworkStatsLog.DESKTOP_MODE_SESSION_TASK_UPDATE__TASK_EVENT__TASK_INFO_CHANGED),
+                eq(FrameworkStatsLog
+                    .DESKTOP_MODE_SESSION_TASK_UPDATE__TASK_EVENT__TASK_INFO_CHANGED),
                 /* instance_id */
                 eq(TASK_UPDATE.instanceId),
                 /* uid */
@@ -149,7 +157,71 @@
                 /* task_y */
                 eq(TASK_UPDATE.taskY),
                 /* session_id */
-                eq(SESSION_ID))
+                eq(SESSION_ID),
+                eq(UNSET_MINIMIZE_REASON),
+                eq(UNSET_UNMINIMIZE_REASON))
+        }
+    }
+
+    @Test
+    fun logTaskInfoChanged_logsTaskUpdateWithMinimizeReason() = runBlocking {
+        desktopModeEventLogger.logTaskInfoChanged(sessionId = SESSION_ID,
+            createTaskUpdate(minimizeReason = MinimizeReason.TASK_LIMIT))
+
+        verify {
+            FrameworkStatsLog.write(eq(FrameworkStatsLog.DESKTOP_MODE_SESSION_TASK_UPDATE),
+                /* task_event */
+                eq(FrameworkStatsLog
+                    .DESKTOP_MODE_SESSION_TASK_UPDATE__TASK_EVENT__TASK_INFO_CHANGED),
+                /* instance_id */
+                eq(TASK_UPDATE.instanceId),
+                /* uid */
+                eq(TASK_UPDATE.uid),
+                /* task_height */
+                eq(TASK_UPDATE.taskHeight),
+                /* task_width */
+                eq(TASK_UPDATE.taskWidth),
+                /* task_x */
+                eq(TASK_UPDATE.taskX),
+                /* task_y */
+                eq(TASK_UPDATE.taskY),
+                /* session_id */
+                eq(SESSION_ID),
+                /* minimize_reason */
+                eq(MinimizeReason.TASK_LIMIT.reason),
+                /* unminimize_reason */
+                eq(UNSET_UNMINIMIZE_REASON))
+        }
+    }
+
+    @Test
+    fun logTaskInfoChanged_logsTaskUpdateWithUnminimizeReason() = runBlocking {
+        desktopModeEventLogger.logTaskInfoChanged(sessionId = SESSION_ID,
+            createTaskUpdate(unminimizeReason = UnminimizeReason.TASKBAR_TAP))
+
+        verify {
+            FrameworkStatsLog.write(eq(FrameworkStatsLog.DESKTOP_MODE_SESSION_TASK_UPDATE),
+                /* task_event */
+                eq(FrameworkStatsLog
+                    .DESKTOP_MODE_SESSION_TASK_UPDATE__TASK_EVENT__TASK_INFO_CHANGED),
+                /* instance_id */
+                eq(TASK_UPDATE.instanceId),
+                /* uid */
+                eq(TASK_UPDATE.uid),
+                /* task_height */
+                eq(TASK_UPDATE.taskHeight),
+                /* task_width */
+                eq(TASK_UPDATE.taskWidth),
+                /* task_x */
+                eq(TASK_UPDATE.taskX),
+                /* task_y */
+                eq(TASK_UPDATE.taskY),
+                /* session_id */
+                eq(SESSION_ID),
+                /* minimize_reason */
+                eq(UNSET_MINIMIZE_REASON),
+                /* unminimize_reason */
+                eq(UnminimizeReason.TASKBAR_TAP.reason))
         }
     }
 
@@ -165,5 +237,11 @@
         private val TASK_UPDATE = TaskUpdate(
             TASK_ID, TASK_UID, TASK_HEIGHT, TASK_WIDTH, TASK_X, TASK_Y
         )
+
+        private fun createTaskUpdate(
+            minimizeReason: MinimizeReason? = null,
+            unminimizeReason: UnminimizeReason? = null,
+        ) = TaskUpdate(TASK_ID, TASK_UID, TASK_HEIGHT, TASK_WIDTH, TASK_X, TASK_Y, minimizeReason,
+            unminimizeReason)
     }
 }
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeVisualIndicatorTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeVisualIndicatorTest.kt
index 2dea43b..f558e87 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeVisualIndicatorTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/desktopmode/DesktopModeVisualIndicatorTest.kt
@@ -17,9 +17,6 @@
 package com.android.wm.shell.desktopmode
 
 import android.app.ActivityManager.RunningTaskInfo
-import android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM
-import android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN
-import android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW
 import android.graphics.Rect
 import android.graphics.Region
 import android.testing.AndroidTestingRunner
@@ -38,6 +35,11 @@
 import org.mockito.Mock
 import org.mockito.kotlin.whenever
 
+/**
+ * Test class for [DesktopModeVisualIndicator]
+ *
+ * Usage: atest WMShellUnitTests:DesktopModeVisualIndicatorTest
+ */
 @SmallTest
 @RunWith(AndroidTestingRunner::class)
 class DesktopModeVisualIndicatorTest : ShellTestCase() {
@@ -52,8 +54,6 @@
 
     @Before
     fun setUp() {
-        visualIndicator = DesktopModeVisualIndicator(syncQueue, taskInfo, displayController,
-            context, taskSurface, taskDisplayAreaOrganizer)
         whenever(displayLayout.width()).thenReturn(DISPLAY_BOUNDS.width())
         whenever(displayLayout.height()).thenReturn(DISPLAY_BOUNDS.height())
         whenever(displayLayout.stableInsets()).thenReturn(STABLE_INSETS)
@@ -61,41 +61,52 @@
 
     @Test
     fun testFullscreenRegionCalculation() {
-        var testRegion = visualIndicator.calculateFullscreenRegion(displayLayout,
-            WINDOWING_MODE_FULLSCREEN, CAPTION_HEIGHT)
+        createVisualIndicator(DesktopModeVisualIndicator.DragStartState.FROM_FULLSCREEN)
+        var testRegion = visualIndicator.calculateFullscreenRegion(displayLayout, CAPTION_HEIGHT)
         assertThat(testRegion.bounds).isEqualTo(Rect(0, -50, 2400, 2 * STABLE_INSETS.top))
-        testRegion = visualIndicator.calculateFullscreenRegion(displayLayout,
-            WINDOWING_MODE_FREEFORM, CAPTION_HEIGHT)
 
+        createVisualIndicator(DesktopModeVisualIndicator.DragStartState.FROM_FREEFORM)
+        testRegion = visualIndicator.calculateFullscreenRegion(displayLayout, CAPTION_HEIGHT)
         val transitionHeight = context.resources.getDimensionPixelSize(
             R.dimen.desktop_mode_transition_region_thickness)
         val toFullscreenScale = mContext.resources.getFloat(
             R.dimen.desktop_mode_fullscreen_region_scale
         )
         val toFullscreenWidth = displayLayout.width() * toFullscreenScale
-
         assertThat(testRegion.bounds).isEqualTo(Rect(
             (DISPLAY_BOUNDS.width() / 2f - toFullscreenWidth / 2f).toInt(),
             -50,
             (DISPLAY_BOUNDS.width() / 2f + toFullscreenWidth / 2f).toInt(),
             transitionHeight))
-        testRegion = visualIndicator.calculateFullscreenRegion(displayLayout,
-            WINDOWING_MODE_MULTI_WINDOW, CAPTION_HEIGHT)
+
+        createVisualIndicator(DesktopModeVisualIndicator.DragStartState.FROM_SPLIT)
+        testRegion = visualIndicator.calculateFullscreenRegion(displayLayout, CAPTION_HEIGHT)
         assertThat(testRegion.bounds).isEqualTo(Rect(0, -50, 2400, 2 * STABLE_INSETS.top))
+
+        createVisualIndicator(DesktopModeVisualIndicator.DragStartState.DRAGGED_INTENT)
+        testRegion = visualIndicator.calculateFullscreenRegion(displayLayout, CAPTION_HEIGHT)
+        assertThat(testRegion.bounds).isEqualTo(Rect(0, -50, 2400, transitionHeight))
     }
 
     @Test
     fun testSplitLeftRegionCalculation() {
         val transitionHeight = context.resources.getDimensionPixelSize(
             R.dimen.desktop_mode_split_from_desktop_height)
+        createVisualIndicator(DesktopModeVisualIndicator.DragStartState.FROM_FULLSCREEN)
         var testRegion = visualIndicator.calculateSplitLeftRegion(displayLayout,
-            WINDOWING_MODE_FULLSCREEN, TRANSITION_AREA_WIDTH, CAPTION_HEIGHT)
+            TRANSITION_AREA_WIDTH, CAPTION_HEIGHT)
         assertThat(testRegion.bounds).isEqualTo(Rect(0, -50, 32, 1600))
+        createVisualIndicator(DesktopModeVisualIndicator.DragStartState.FROM_FREEFORM)
         testRegion = visualIndicator.calculateSplitLeftRegion(displayLayout,
-            WINDOWING_MODE_FREEFORM, TRANSITION_AREA_WIDTH, CAPTION_HEIGHT)
+            TRANSITION_AREA_WIDTH, CAPTION_HEIGHT)
         assertThat(testRegion.bounds).isEqualTo(Rect(0, transitionHeight, 32, 1600))
+        createVisualIndicator(DesktopModeVisualIndicator.DragStartState.FROM_SPLIT)
         testRegion = visualIndicator.calculateSplitLeftRegion(displayLayout,
-            WINDOWING_MODE_MULTI_WINDOW, TRANSITION_AREA_WIDTH, CAPTION_HEIGHT)
+            TRANSITION_AREA_WIDTH, CAPTION_HEIGHT)
+        assertThat(testRegion.bounds).isEqualTo(Rect(0, -50, 32, 1600))
+        createVisualIndicator(DesktopModeVisualIndicator.DragStartState.DRAGGED_INTENT)
+        testRegion = visualIndicator.calculateSplitLeftRegion(displayLayout,
+            TRANSITION_AREA_WIDTH, CAPTION_HEIGHT)
         assertThat(testRegion.bounds).isEqualTo(Rect(0, -50, 32, 1600))
     }
 
@@ -103,27 +114,35 @@
     fun testSplitRightRegionCalculation() {
         val transitionHeight = context.resources.getDimensionPixelSize(
             R.dimen.desktop_mode_split_from_desktop_height)
+        createVisualIndicator(DesktopModeVisualIndicator.DragStartState.FROM_FULLSCREEN)
         var testRegion = visualIndicator.calculateSplitRightRegion(displayLayout,
-            WINDOWING_MODE_FULLSCREEN, TRANSITION_AREA_WIDTH, CAPTION_HEIGHT)
+            TRANSITION_AREA_WIDTH, CAPTION_HEIGHT)
         assertThat(testRegion.bounds).isEqualTo(Rect(2368, -50, 2400, 1600))
+        createVisualIndicator(DesktopModeVisualIndicator.DragStartState.FROM_FREEFORM)
         testRegion = visualIndicator.calculateSplitRightRegion(displayLayout,
-            WINDOWING_MODE_FREEFORM, TRANSITION_AREA_WIDTH, CAPTION_HEIGHT)
+            TRANSITION_AREA_WIDTH, CAPTION_HEIGHT)
         assertThat(testRegion.bounds).isEqualTo(Rect(2368, transitionHeight, 2400, 1600))
+        createVisualIndicator(DesktopModeVisualIndicator.DragStartState.FROM_SPLIT)
         testRegion = visualIndicator.calculateSplitRightRegion(displayLayout,
-            WINDOWING_MODE_MULTI_WINDOW, TRANSITION_AREA_WIDTH, CAPTION_HEIGHT)
+            TRANSITION_AREA_WIDTH, CAPTION_HEIGHT)
+        assertThat(testRegion.bounds).isEqualTo(Rect(2368, -50, 2400, 1600))
+        createVisualIndicator(DesktopModeVisualIndicator.DragStartState.DRAGGED_INTENT)
+        testRegion = visualIndicator.calculateSplitRightRegion(displayLayout,
+            TRANSITION_AREA_WIDTH, CAPTION_HEIGHT)
         assertThat(testRegion.bounds).isEqualTo(Rect(2368, -50, 2400, 1600))
     }
 
     @Test
     fun testToDesktopRegionCalculation() {
+        createVisualIndicator(DesktopModeVisualIndicator.DragStartState.FROM_FULLSCREEN)
         val fullscreenRegion = visualIndicator.calculateFullscreenRegion(displayLayout,
-            WINDOWING_MODE_FULLSCREEN, CAPTION_HEIGHT)
+            CAPTION_HEIGHT)
         val splitLeftRegion = visualIndicator.calculateSplitLeftRegion(displayLayout,
-            WINDOWING_MODE_FULLSCREEN, TRANSITION_AREA_WIDTH, CAPTION_HEIGHT)
+            TRANSITION_AREA_WIDTH, CAPTION_HEIGHT)
         val splitRightRegion = visualIndicator.calculateSplitRightRegion(displayLayout,
-            WINDOWING_MODE_FULLSCREEN, TRANSITION_AREA_WIDTH, CAPTION_HEIGHT)
+            TRANSITION_AREA_WIDTH, CAPTION_HEIGHT)
         val desktopRegion = visualIndicator.calculateToDesktopRegion(displayLayout,
-            WINDOWING_MODE_FULLSCREEN, splitLeftRegion, splitRightRegion, fullscreenRegion)
+            splitLeftRegion, splitRightRegion, fullscreenRegion)
         var testRegion = Region()
         testRegion.union(DISPLAY_BOUNDS)
         testRegion.op(splitLeftRegion, Region.Op.DIFFERENCE)
@@ -132,6 +151,11 @@
         assertThat(desktopRegion).isEqualTo(testRegion)
     }
 
+    private fun createVisualIndicator(dragStartState: DesktopModeVisualIndicator.DragStartState) {
+        visualIndicator = DesktopModeVisualIndicator(syncQueue, taskInfo, displayController,
+            context, taskSurface, taskDisplayAreaOrganizer, dragStartState)
+    }
+
     companion object {
         private const val TRANSITION_AREA_WIDTH = 32
         private const val CAPTION_HEIGHT = 50
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 18c6228..a841e16 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
@@ -84,7 +84,6 @@
 import com.android.wm.shell.common.ShellExecutor
 import com.android.wm.shell.common.SyncTransactionQueue
 import com.android.wm.shell.common.desktopmode.DesktopModeTransitionSource.UNKNOWN
-import com.android.wm.shell.common.split.SplitScreenConstants
 import com.android.wm.shell.desktopmode.DesktopTasksController.SnapPosition
 import com.android.wm.shell.desktopmode.DesktopTestHelpers.Companion.createFreeformTask
 import com.android.wm.shell.desktopmode.DesktopTestHelpers.Companion.createFullscreenTask
@@ -95,6 +94,7 @@
 import com.android.wm.shell.recents.RecentsTransitionHandler
 import com.android.wm.shell.recents.RecentsTransitionStateListener
 import com.android.wm.shell.shared.desktopmode.DesktopModeStatus
+import com.android.wm.shell.shared.split.SplitScreenConstants
 import com.android.wm.shell.splitscreen.SplitScreenController
 import com.android.wm.shell.sysui.ShellCommandHandler
 import com.android.wm.shell.sysui.ShellController
@@ -160,6 +160,7 @@
   @Mock lateinit var mReturnToDragStartAnimator: ReturnToDragStartAnimator
   @Mock lateinit var exitDesktopTransitionHandler: ExitDesktopTaskTransitionHandler
   @Mock lateinit var enterDesktopTransitionHandler: EnterDesktopTaskTransitionHandler
+  @Mock lateinit var dragAndDropTransitionHandler: DesktopModeDragAndDropTransitionHandler
   @Mock
   lateinit var toggleResizeDesktopTaskTransitionHandler: ToggleResizeDesktopTaskTransitionHandler
   @Mock lateinit var dragToDesktopTransitionHandler: DragToDesktopTransitionHandler
@@ -254,6 +255,7 @@
         mReturnToDragStartAnimator,
         enterDesktopTransitionHandler,
         exitDesktopTransitionHandler,
+        dragAndDropTransitionHandler,
         toggleResizeDesktopTaskTransitionHandler,
         dragToDesktopTransitionHandler,
         taskRepository,
@@ -867,6 +869,18 @@
 
   @Test
   @EnableFlags(Flags.FLAG_ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS)
+  fun addMoveToDesktopChanges_landscapeDevice_portraitResizableApp_aspectRatioOverridden() {
+    setUpLandscapeDisplay()
+    val task = setUpFullscreenTask(screenOrientation = SCREEN_ORIENTATION_PORTRAIT,
+      shouldLetterbox = true, aspectRatioOverrideApplied = true)
+    val wct = WindowContainerTransaction()
+    controller.addMoveToDesktopChanges(wct, task)
+
+    assertThat(findBoundsChange(wct, task)).isEqualTo(UNRESIZABLE_PORTRAIT_BOUNDS)
+  }
+
+  @Test
+  @EnableFlags(Flags.FLAG_ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS)
   fun addMoveToDesktopChanges_portraitDevice_userFullscreenOverride_defaultPortraitBounds() {
     setUpPortraitDisplay()
     val task = setUpFullscreenTask(enableUserFullscreenOverride = true)
@@ -888,6 +902,19 @@
   }
 
   @Test
+  @EnableFlags(Flags.FLAG_ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS)
+  fun addMoveToDesktopChanges_portraitDevice_landscapeResizableApp_aspectRatioOverridden() {
+    setUpPortraitDisplay()
+    val task = setUpFullscreenTask(screenOrientation = SCREEN_ORIENTATION_LANDSCAPE,
+      deviceOrientation = ORIENTATION_PORTRAIT,
+      shouldLetterbox = true, aspectRatioOverrideApplied = true)
+    val wct = WindowContainerTransaction()
+    controller.addMoveToDesktopChanges(wct, task)
+
+    assertThat(findBoundsChange(wct, task)).isEqualTo(UNRESIZABLE_LANDSCAPE_BOUNDS)
+  }
+
+  @Test
   fun moveToDesktop_tdaFullscreen_windowingModeSetToFreeform() {
     val task = setUpFullscreenTask()
     val tda = rootTaskDisplayAreaOrganizer.getDisplayAreaInfo(DEFAULT_DISPLAY)!!
@@ -1819,6 +1846,19 @@
   }
 
   @Test
+  @EnableFlags(
+    Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY,
+  )
+  fun handleRequest_backTransition_singleTaskNoToken_withWallpaper_notInDesktop_doesNotHandle() {
+    val task = setUpFreeformTask()
+    markTaskHidden(task)
+
+    val result = controller.handleRequest(Binder(), createTransition(task, type = TRANSIT_TO_BACK))
+
+    assertNull(result, "Should not handle request")
+  }
+
+  @Test
   @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_WALLPAPER_ACTIVITY)
   @DisableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_BACK_NAVIGATION)
   fun handleRequest_backTransition_singleTaskNoToken_noBackNav_doesNotHandle() {
@@ -2224,27 +2264,6 @@
   }
 
   @Test
-  fun desktopTasksVisibilityChange_visible_setLaunchAdjacentDisabled() {
-    val task = setUpFreeformTask()
-    clearInvocations(launchAdjacentController)
-
-    markTaskVisible(task)
-    shellExecutor.flushAll()
-    verify(launchAdjacentController).launchAdjacentEnabled = false
-  }
-
-  @Test
-  fun desktopTasksVisibilityChange_invisible_setLaunchAdjacentEnabled() {
-    val task = setUpFreeformTask()
-    markTaskVisible(task)
-    clearInvocations(launchAdjacentController)
-
-    markTaskHidden(task)
-    shellExecutor.flushAll()
-    verify(launchAdjacentController).launchAdjacentEnabled = true
-  }
-
-  @Test
   fun moveFocusedTaskToDesktop_fullscreenTaskIsMovedToDesktop() {
     val task1 = setUpFullscreenTask()
     val task2 = setUpFullscreenTask()
@@ -2349,7 +2368,7 @@
   fun dragToDesktop_landscapeDevice_resizable_undefinedOrientation_defaultLandscapeBounds() {
     val spyController = spy(controller)
     whenever(spyController.getVisualIndicator()).thenReturn(desktopModeVisualIndicator)
-    whenever(desktopModeVisualIndicator.updateIndicatorType(anyOrNull(), anyOrNull()))
+    whenever(desktopModeVisualIndicator.updateIndicatorType(anyOrNull()))
         .thenReturn(DesktopModeVisualIndicator.IndicatorType.TO_DESKTOP_INDICATOR)
 
     val task = setUpFullscreenTask()
@@ -2365,7 +2384,7 @@
   fun dragToDesktop_landscapeDevice_resizable_landscapeOrientation_defaultLandscapeBounds() {
     val spyController = spy(controller)
     whenever(spyController.getVisualIndicator()).thenReturn(desktopModeVisualIndicator)
-    whenever(desktopModeVisualIndicator.updateIndicatorType(anyOrNull(), anyOrNull()))
+    whenever(desktopModeVisualIndicator.updateIndicatorType(anyOrNull()))
         .thenReturn(DesktopModeVisualIndicator.IndicatorType.TO_DESKTOP_INDICATOR)
 
     val task = setUpFullscreenTask(screenOrientation = SCREEN_ORIENTATION_LANDSCAPE)
@@ -2381,7 +2400,7 @@
   fun dragToDesktop_landscapeDevice_resizable_portraitOrientation_resizablePortraitBounds() {
     val spyController = spy(controller)
     whenever(spyController.getVisualIndicator()).thenReturn(desktopModeVisualIndicator)
-    whenever(desktopModeVisualIndicator.updateIndicatorType(anyOrNull(), anyOrNull()))
+    whenever(desktopModeVisualIndicator.updateIndicatorType(anyOrNull()))
         .thenReturn(DesktopModeVisualIndicator.IndicatorType.TO_DESKTOP_INDICATOR)
 
     val task =
@@ -2398,7 +2417,7 @@
   fun dragToDesktop_landscapeDevice_unResizable_landscapeOrientation_defaultLandscapeBounds() {
     val spyController = spy(controller)
     whenever(spyController.getVisualIndicator()).thenReturn(desktopModeVisualIndicator)
-    whenever(desktopModeVisualIndicator.updateIndicatorType(anyOrNull(), anyOrNull()))
+    whenever(desktopModeVisualIndicator.updateIndicatorType(anyOrNull()))
         .thenReturn(DesktopModeVisualIndicator.IndicatorType.TO_DESKTOP_INDICATOR)
 
     val task =
@@ -2415,7 +2434,7 @@
   fun dragToDesktop_landscapeDevice_unResizable_portraitOrientation_unResizablePortraitBounds() {
     val spyController = spy(controller)
     whenever(spyController.getVisualIndicator()).thenReturn(desktopModeVisualIndicator)
-    whenever(desktopModeVisualIndicator.updateIndicatorType(anyOrNull(), anyOrNull()))
+    whenever(desktopModeVisualIndicator.updateIndicatorType(anyOrNull()))
         .thenReturn(DesktopModeVisualIndicator.IndicatorType.TO_DESKTOP_INDICATOR)
 
     val task =
@@ -2435,7 +2454,7 @@
   fun dragToDesktop_portraitDevice_resizable_undefinedOrientation_defaultPortraitBounds() {
     val spyController = spy(controller)
     whenever(spyController.getVisualIndicator()).thenReturn(desktopModeVisualIndicator)
-    whenever(desktopModeVisualIndicator.updateIndicatorType(anyOrNull(), anyOrNull()))
+    whenever(desktopModeVisualIndicator.updateIndicatorType(anyOrNull()))
         .thenReturn(DesktopModeVisualIndicator.IndicatorType.TO_DESKTOP_INDICATOR)
 
     val task = setUpFullscreenTask(deviceOrientation = ORIENTATION_PORTRAIT)
@@ -2451,7 +2470,7 @@
   fun dragToDesktop_portraitDevice_resizable_portraitOrientation_defaultPortraitBounds() {
     val spyController = spy(controller)
     whenever(spyController.getVisualIndicator()).thenReturn(desktopModeVisualIndicator)
-    whenever(desktopModeVisualIndicator.updateIndicatorType(anyOrNull(), anyOrNull()))
+    whenever(desktopModeVisualIndicator.updateIndicatorType(anyOrNull()))
         .thenReturn(DesktopModeVisualIndicator.IndicatorType.TO_DESKTOP_INDICATOR)
 
     val task =
@@ -2470,7 +2489,7 @@
   fun dragToDesktop_portraitDevice_resizable_landscapeOrientation_resizableLandscapeBounds() {
     val spyController = spy(controller)
     whenever(spyController.getVisualIndicator()).thenReturn(desktopModeVisualIndicator)
-    whenever(desktopModeVisualIndicator.updateIndicatorType(anyOrNull(), anyOrNull()))
+    whenever(desktopModeVisualIndicator.updateIndicatorType(anyOrNull()))
         .thenReturn(DesktopModeVisualIndicator.IndicatorType.TO_DESKTOP_INDICATOR)
 
     val task =
@@ -2490,7 +2509,7 @@
   fun dragToDesktop_portraitDevice_unResizable_portraitOrientation_defaultPortraitBounds() {
     val spyController = spy(controller)
     whenever(spyController.getVisualIndicator()).thenReturn(desktopModeVisualIndicator)
-    whenever(desktopModeVisualIndicator.updateIndicatorType(anyOrNull(), anyOrNull()))
+    whenever(desktopModeVisualIndicator.updateIndicatorType(anyOrNull()))
         .thenReturn(DesktopModeVisualIndicator.IndicatorType.TO_DESKTOP_INDICATOR)
 
     val task =
@@ -2510,7 +2529,7 @@
   fun dragToDesktop_portraitDevice_unResizable_landscapeOrientation_unResizableLandscapeBounds() {
     val spyController = spy(controller)
     whenever(spyController.getVisualIndicator()).thenReturn(desktopModeVisualIndicator)
-    whenever(desktopModeVisualIndicator.updateIndicatorType(anyOrNull(), anyOrNull()))
+    whenever(desktopModeVisualIndicator.updateIndicatorType(anyOrNull()))
         .thenReturn(DesktopModeVisualIndicator.IndicatorType.TO_DESKTOP_INDICATOR)
 
     val task =
@@ -2567,7 +2586,7 @@
 
     val currentDragBounds = Rect(100, 200, 500, 1000)
     whenever(spyController.getVisualIndicator()).thenReturn(desktopModeVisualIndicator)
-    whenever(desktopModeVisualIndicator.updateIndicatorType(anyOrNull(), anyOrNull()))
+    whenever(desktopModeVisualIndicator.updateIndicatorType(anyOrNull()))
       .thenReturn(DesktopModeVisualIndicator.IndicatorType.NO_INDICATOR)
 
     spyController.onDragPositioningEnd(
@@ -2883,7 +2902,8 @@
     shouldLetterbox: Boolean = false,
     gravity: Int = Gravity.NO_GRAVITY,
     enableUserFullscreenOverride: Boolean = false,
-    enableSystemFullscreenOverride: Boolean = false
+    enableSystemFullscreenOverride: Boolean = false,
+    aspectRatioOverrideApplied: Boolean = false
   ): RunningTaskInfo {
     val task = createFullscreenTask(displayId)
     val activityInfo = ActivityInfo()
@@ -2898,6 +2918,7 @@
       appCompatTaskInfo.isSystemFullscreenOverrideEnabled = enableSystemFullscreenOverride
 
       if (shouldLetterbox) {
+        appCompatTaskInfo.setHasMinAspectRatioOverride(aspectRatioOverrideApplied)
         if (deviceOrientation == ORIENTATION_LANDSCAPE &&
             screenOrientation == SCREEN_ORIENTATION_PORTRAIT) {
           // Letterbox to portrait size
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 c97bcfb..16a234b 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
@@ -20,8 +20,8 @@
 import com.android.wm.shell.RootTaskDisplayAreaOrganizer
 import com.android.wm.shell.ShellTestCase
 import com.android.wm.shell.TestRunningTaskInfoBuilder
-import com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_BOTTOM_OR_RIGHT
-import com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_TOP_OR_LEFT
+import com.android.wm.shell.shared.split.SplitScreenConstants.SPLIT_POSITION_BOTTOM_OR_RIGHT
+import com.android.wm.shell.shared.split.SplitScreenConstants.SPLIT_POSITION_TOP_OR_LEFT
 import com.android.wm.shell.splitscreen.SplitScreenController
 import com.android.wm.shell.transition.Transitions
 import com.android.wm.shell.transition.Transitions.TRANSIT_DESKTOP_MODE_CANCEL_DRAG_TO_DESKTOP
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/draganddrop/DragAndDropPolicyTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/draganddrop/DragAndDropPolicyTest.java
index 97fa8d6..645b296 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/draganddrop/DragAndDropPolicyTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/draganddrop/DragAndDropPolicyTest.java
@@ -27,9 +27,9 @@
 import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
 
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession;
-import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_BOTTOM_OR_RIGHT;
-import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_TOP_OR_LEFT;
-import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_UNDEFINED;
+import static com.android.wm.shell.shared.split.SplitScreenConstants.SPLIT_POSITION_BOTTOM_OR_RIGHT;
+import static com.android.wm.shell.shared.split.SplitScreenConstants.SPLIT_POSITION_TOP_OR_LEFT;
+import static com.android.wm.shell.shared.split.SplitScreenConstants.SPLIT_POSITION_UNDEFINED;
 import static com.android.wm.shell.draganddrop.DragAndDropPolicy.Target.TYPE_FULLSCREEN;
 import static com.android.wm.shell.draganddrop.DragAndDropPolicy.Target.TYPE_SPLIT_BOTTOM;
 import static com.android.wm.shell.draganddrop.DragAndDropPolicy.Target.TYPE_SPLIT_LEFT;
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/freeform/FreeformTaskListenerTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/freeform/FreeformTaskListenerTests.java
index 6ec6bed..763d015 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/freeform/FreeformTaskListenerTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/freeform/FreeformTaskListenerTests.java
@@ -27,6 +27,7 @@
 import static org.mockito.Mockito.verify;
 
 import android.app.ActivityManager;
+import android.view.SurfaceControl;
 
 import androidx.test.ext.junit.runners.AndroidJUnit4;
 import androidx.test.filters.SmallTest;
@@ -35,6 +36,7 @@
 import com.android.wm.shell.ShellTaskOrganizer;
 import com.android.wm.shell.ShellTestCase;
 import com.android.wm.shell.TestRunningTaskInfoBuilder;
+import com.android.wm.shell.common.LaunchAdjacentController;
 import com.android.wm.shell.desktopmode.DesktopModeTaskRepository;
 import com.android.wm.shell.shared.desktopmode.DesktopModeStatus;
 import com.android.wm.shell.sysui.ShellInit;
@@ -65,7 +67,11 @@
     @Mock
     private WindowDecorViewModel mWindowDecorViewModel;
     @Mock
+    private SurfaceControl mMockSurfaceControl;
+    @Mock
     private DesktopModeTaskRepository mDesktopModeTaskRepository;
+    @Mock
+    private LaunchAdjacentController mLaunchAdjacentController;
     private FreeformTaskListener mFreeformTaskListener;
     private StaticMockitoSession mMockitoSession;
 
@@ -80,6 +86,7 @@
                 mShellInit,
                 mTaskOrganizer,
                 Optional.of(mDesktopModeTaskRepository),
+                mLaunchAdjacentController,
                 mWindowDecorViewModel);
     }
 
@@ -107,6 +114,31 @@
                 .addOrMoveFreeformTaskToTop(fullscreenTask.displayId, fullscreenTask.taskId);
     }
 
+    @Test
+    public void testVisibilityTaskChanged_visible_setLaunchAdjacentDisabled() {
+        ActivityManager.RunningTaskInfo task = new TestRunningTaskInfoBuilder()
+                .setWindowingMode(WINDOWING_MODE_FREEFORM).build();
+        task.isVisible = true;
+
+        mFreeformTaskListener.onTaskAppeared(task, mMockSurfaceControl);
+
+        verify(mLaunchAdjacentController).setLaunchAdjacentEnabled(false);
+    }
+
+    @Test
+    public void testVisibilityTaskChanged_NotVisible_setLaunchAdjacentEnabled() {
+        ActivityManager.RunningTaskInfo task = new TestRunningTaskInfoBuilder()
+                .setWindowingMode(WINDOWING_MODE_FREEFORM).build();
+        task.isVisible = true;
+
+        mFreeformTaskListener.onTaskAppeared(task, mMockSurfaceControl);
+
+        task.isVisible = false;
+        mFreeformTaskListener.onTaskInfoChanged(task);
+
+        verify(mLaunchAdjacentController).setLaunchAdjacentEnabled(true);
+    }
+
     @After
     public void tearDown() {
         mMockitoSession.finishMocking();
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedControllerTest.java
index 8ad3d2a..7d063a0 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedControllerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedControllerTest.java
@@ -48,10 +48,10 @@
 import com.android.wm.shell.common.DisplayLayout;
 import com.android.wm.shell.common.ShellExecutor;
 import com.android.wm.shell.common.TaskStackListenerImpl;
+import com.android.wm.shell.shared.ShellSharedConstants;
 import com.android.wm.shell.sysui.ShellCommandHandler;
 import com.android.wm.shell.sysui.ShellController;
 import com.android.wm.shell.sysui.ShellInit;
-import com.android.wm.shell.sysui.ShellSharedConstants;
 
 import org.junit.Before;
 import org.junit.Test;
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipControllerTest.java
index 75d2145..6ddb678 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipControllerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip/phone/PipControllerTest.java
@@ -67,10 +67,10 @@
 import com.android.wm.shell.pip.PipTaskOrganizer;
 import com.android.wm.shell.pip.PipTransitionController;
 import com.android.wm.shell.pip.PipTransitionState;
+import com.android.wm.shell.shared.ShellSharedConstants;
 import com.android.wm.shell.sysui.ShellCommandHandler;
 import com.android.wm.shell.sysui.ShellController;
 import com.android.wm.shell.sysui.ShellInit;
-import com.android.wm.shell.sysui.ShellSharedConstants;
 
 import org.junit.Before;
 import org.junit.Test;
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip2/PipTransitionStateTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip2/phone/PipTransitionStateTest.java
similarity index 95%
rename from libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip2/PipTransitionStateTest.java
rename to libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip2/phone/PipTransitionStateTest.java
index f3f3c37..571ae93 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip2/PipTransitionStateTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip2/phone/PipTransitionStateTest.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.wm.shell.pip2;
+package com.android.wm.shell.pip2.phone;
 
 import android.os.Bundle;
 import android.os.Handler;
@@ -22,8 +22,6 @@
 import android.testing.AndroidTestingRunner;
 
 import com.android.wm.shell.ShellTestCase;
-import com.android.wm.shell.common.pip.PhoneSizeSpecSource;
-import com.android.wm.shell.pip2.phone.PipTransitionState;
 
 import junit.framework.Assert;
 
@@ -33,7 +31,7 @@
 import org.mockito.Mock;
 
 /**
- * Unit test against {@link PhoneSizeSpecSource}.
+ * Unit test against {@link PipTransitionState}.
  *
  * This test mocks the PiP2 flag to be true.
  */
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip2/phone/PipUiStateChangeControllerTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip2/phone/PipUiStateChangeControllerTests.java
new file mode 100644
index 0000000..82cdfd5
--- /dev/null
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/pip2/phone/PipUiStateChangeControllerTests.java
@@ -0,0 +1,123 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.pip2.phone;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyZeroInteractions;
+import static org.mockito.Mockito.when;
+
+import android.app.Flags;
+import android.app.PictureInPictureUiState;
+import android.os.Bundle;
+import android.platform.test.annotations.EnableFlags;
+import android.testing.AndroidTestingRunner;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import java.util.function.Consumer;
+
+/**
+ * Unit test against {@link PipUiStateChangeController}.
+ */
+@RunWith(AndroidTestingRunner.class)
+public class PipUiStateChangeControllerTests {
+
+    @Mock
+    private PipTransitionState mPipTransitionState;
+
+    private Consumer<PictureInPictureUiState> mPictureInPictureUiStateConsumer;
+    private ArgumentCaptor<PictureInPictureUiState> mArgumentCaptor;
+
+    private PipUiStateChangeController mPipUiStateChangeController;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+        mPipUiStateChangeController = new PipUiStateChangeController(mPipTransitionState);
+        mPictureInPictureUiStateConsumer = spy(pictureInPictureUiState -> {});
+        mPipUiStateChangeController.setPictureInPictureUiStateConsumer(
+                mPictureInPictureUiStateConsumer);
+        mArgumentCaptor = ArgumentCaptor.forClass(PictureInPictureUiState.class);
+    }
+
+    @Test
+    @EnableFlags(Flags.FLAG_ENABLE_PIP_UI_STATE_CALLBACK_ON_ENTERING)
+    public void onPipTransitionStateChanged_swipePipStart_callbackIsTransitioningToPipTrue() {
+        when(mPipTransitionState.isInSwipePipToHomeTransition()).thenReturn(true);
+
+        mPipUiStateChangeController.onPipTransitionStateChanged(
+                PipTransitionState.UNDEFINED, PipTransitionState.SWIPING_TO_PIP, Bundle.EMPTY);
+
+        verify(mPictureInPictureUiStateConsumer).accept(mArgumentCaptor.capture());
+        assertTrue(mArgumentCaptor.getValue().isTransitioningToPip());
+    }
+
+    @Test
+    @EnableFlags(Flags.FLAG_ENABLE_PIP_UI_STATE_CALLBACK_ON_ENTERING)
+    public void onPipTransitionStateChanged_swipePipOngoing_noCallbackIsTransitioningToPip() {
+        when(mPipTransitionState.isInSwipePipToHomeTransition()).thenReturn(true);
+
+        mPipUiStateChangeController.onPipTransitionStateChanged(
+                PipTransitionState.SWIPING_TO_PIP, PipTransitionState.ENTERING_PIP, Bundle.EMPTY);
+
+        verifyZeroInteractions(mPictureInPictureUiStateConsumer);
+    }
+
+    @Test
+    @EnableFlags(Flags.FLAG_ENABLE_PIP_UI_STATE_CALLBACK_ON_ENTERING)
+    public void onPipTransitionStateChanged_swipePipFinish_callbackIsTransitioningToPipFalse() {
+        when(mPipTransitionState.isInSwipePipToHomeTransition()).thenReturn(true);
+
+        mPipUiStateChangeController.onPipTransitionStateChanged(
+                PipTransitionState.SWIPING_TO_PIP, PipTransitionState.ENTERED_PIP, Bundle.EMPTY);
+
+        verify(mPictureInPictureUiStateConsumer).accept(mArgumentCaptor.capture());
+        assertFalse(mArgumentCaptor.getValue().isTransitioningToPip());
+    }
+
+    @Test
+    @EnableFlags(Flags.FLAG_ENABLE_PIP_UI_STATE_CALLBACK_ON_ENTERING)
+    public void onPipTransitionStateChanged_tapHomeStart_callbackIsTransitioningToPipTrue() {
+        when(mPipTransitionState.isInSwipePipToHomeTransition()).thenReturn(false);
+
+        mPipUiStateChangeController.onPipTransitionStateChanged(
+                PipTransitionState.UNDEFINED, PipTransitionState.ENTERING_PIP, Bundle.EMPTY);
+
+        verify(mPictureInPictureUiStateConsumer).accept(mArgumentCaptor.capture());
+        assertTrue(mArgumentCaptor.getValue().isTransitioningToPip());
+    }
+
+    @Test
+    @EnableFlags(Flags.FLAG_ENABLE_PIP_UI_STATE_CALLBACK_ON_ENTERING)
+    public void onPipTransitionStateChanged_tapHomeFinish_callbackIsTransitioningToPipFalse() {
+        when(mPipTransitionState.isInSwipePipToHomeTransition()).thenReturn(false);
+
+        mPipUiStateChangeController.onPipTransitionStateChanged(
+                PipTransitionState.ENTERING_PIP, PipTransitionState.ENTERED_PIP, Bundle.EMPTY);
+
+        verify(mPictureInPictureUiStateConsumer).accept(mArgumentCaptor.capture());
+        assertFalse(mArgumentCaptor.getValue().isTransitioningToPip());
+    }
+}
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/GroupedRecentTaskInfoTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/GroupedRecentTaskInfoTest.kt
index 15b73c5..6736593 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/GroupedRecentTaskInfoTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/GroupedRecentTaskInfoTest.kt
@@ -17,7 +17,6 @@
 package com.android.wm.shell.recents
 
 import android.app.ActivityManager
-import android.app.ActivityManager.RecentTaskInfo
 import android.graphics.Rect
 import android.os.Parcel
 import android.testing.AndroidTestingRunner
@@ -25,7 +24,7 @@
 import android.window.WindowContainerToken
 import androidx.test.filters.SmallTest
 import com.android.wm.shell.ShellTestCase
-import com.android.wm.shell.common.split.SplitScreenConstants.SNAP_TO_50_50
+import com.android.wm.shell.shared.split.SplitScreenConstants.SNAP_TO_50_50
 import com.android.wm.shell.util.GroupedRecentTaskInfo
 import com.android.wm.shell.util.GroupedRecentTaskInfo.CREATOR
 import com.android.wm.shell.util.GroupedRecentTaskInfo.TYPE_FREEFORM
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentTasksControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentTasksControllerTest.java
index a0aab2e..e1fe4e9 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentTasksControllerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/RecentTasksControllerTest.java
@@ -22,7 +22,7 @@
 import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
 
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession;
-import static com.android.wm.shell.common.split.SplitScreenConstants.SNAP_TO_50_50;
+import static com.android.wm.shell.shared.split.SplitScreenConstants.SNAP_TO_50_50;
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
@@ -68,11 +68,11 @@
 import com.android.wm.shell.common.DisplayInsetsController;
 import com.android.wm.shell.common.TaskStackListenerImpl;
 import com.android.wm.shell.desktopmode.DesktopModeTaskRepository;
+import com.android.wm.shell.shared.ShellSharedConstants;
 import com.android.wm.shell.shared.desktopmode.DesktopModeStatus;
 import com.android.wm.shell.sysui.ShellCommandHandler;
 import com.android.wm.shell.sysui.ShellController;
 import com.android.wm.shell.sysui.ShellInit;
-import com.android.wm.shell.sysui.ShellSharedConstants;
 import com.android.wm.shell.util.GroupedRecentTaskInfo;
 import com.android.wm.shell.util.SplitBounds;
 
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/SplitBoundsTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/SplitBoundsTest.java
index b790aee..bfb760b 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/SplitBoundsTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/recents/SplitBoundsTest.java
@@ -1,6 +1,6 @@
 package com.android.wm.shell.recents;
 
-import static com.android.wm.shell.common.split.SplitScreenConstants.SNAP_TO_50_50;
+import static com.android.wm.shell.shared.split.SplitScreenConstants.SNAP_TO_50_50;
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/magnetictarget/MagnetizedObjectTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/shared/magnetictarget/MagnetizedObjectTest.kt
similarity index 99%
rename from libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/magnetictarget/MagnetizedObjectTest.kt
rename to libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/shared/magnetictarget/MagnetizedObjectTest.kt
index 8bb182d..8711ee0 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/magnetictarget/MagnetizedObjectTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/shared/magnetictarget/MagnetizedObjectTest.kt
@@ -13,7 +13,7 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package com.android.wm.shell.common.magnetictarget
+package com.android.wm.shell.shared.magnetictarget
 
 import android.testing.AndroidTestingRunner
 import android.testing.TestableLooper
@@ -33,13 +33,13 @@
 import org.mockito.ArgumentMatchers.anyBoolean
 import org.mockito.ArgumentMatchers.anyFloat
 import org.mockito.Mockito
-import org.mockito.Mockito.`when`
 import org.mockito.Mockito.doAnswer
 import org.mockito.Mockito.mock
 import org.mockito.Mockito.never
 import org.mockito.Mockito.times
 import org.mockito.Mockito.verify
 import org.mockito.Mockito.verifyNoMoreInteractions
+import org.mockito.Mockito.`when`
 
 @TestableLooper.RunWithLooper
 @RunWith(AndroidTestingRunner::class)
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/split/SplitScreenConstantsTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/shared/split/SplitScreenConstantsTest.kt
similarity index 96%
rename from libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/split/SplitScreenConstantsTest.kt
rename to libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/shared/split/SplitScreenConstantsTest.kt
index fe26110..19c18be 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/split/SplitScreenConstantsTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/shared/split/SplitScreenConstantsTest.kt
@@ -17,6 +17,7 @@
 package com.android.wm.shell.common.split
 
 import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.android.wm.shell.shared.split.SplitScreenConstants
 import org.junit.Assert.assertEquals
 import org.junit.Test
 import org.junit.runner.RunWith
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitScreenControllerTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitScreenControllerTests.java
index 1c5d5e9..9260a07 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitScreenControllerTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitScreenControllerTests.java
@@ -23,8 +23,8 @@
 import static android.content.Intent.FLAG_ACTIVITY_MULTIPLE_TASK;
 import static android.content.Intent.FLAG_ACTIVITY_NO_USER_ACTION;
 
-import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_BOTTOM_OR_RIGHT;
-import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_TOP_OR_LEFT;
+import static com.android.wm.shell.shared.split.SplitScreenConstants.SPLIT_POSITION_BOTTOM_OR_RIGHT;
+import static com.android.wm.shell.shared.split.SplitScreenConstants.SPLIT_POSITION_TOP_OR_LEFT;
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assume.assumeTrue;
@@ -70,14 +70,14 @@
 import com.android.wm.shell.common.MultiInstanceHelper;
 import com.android.wm.shell.common.ShellExecutor;
 import com.android.wm.shell.common.SyncTransactionQueue;
-import com.android.wm.shell.common.TransactionPool;
 import com.android.wm.shell.desktopmode.DesktopTasksController;
 import com.android.wm.shell.draganddrop.DragAndDropController;
 import com.android.wm.shell.recents.RecentTasksController;
+import com.android.wm.shell.shared.ShellSharedConstants;
+import com.android.wm.shell.shared.TransactionPool;
 import com.android.wm.shell.sysui.ShellCommandHandler;
 import com.android.wm.shell.sysui.ShellController;
 import com.android.wm.shell.sysui.ShellInit;
-import com.android.wm.shell.sysui.ShellSharedConstants;
 import com.android.wm.shell.transition.Transitions;
 import com.android.wm.shell.windowdecor.WindowDecorViewModel;
 
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTestUtils.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTestUtils.java
index 29d3fb4..aa96c45 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTestUtils.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTestUtils.java
@@ -35,9 +35,9 @@
 import com.android.wm.shell.common.LaunchAdjacentController;
 import com.android.wm.shell.common.ShellExecutor;
 import com.android.wm.shell.common.SyncTransactionQueue;
-import com.android.wm.shell.common.TransactionPool;
 import com.android.wm.shell.common.split.SplitLayout;
 import com.android.wm.shell.recents.RecentTasksController;
+import com.android.wm.shell.shared.TransactionPool;
 import com.android.wm.shell.transition.Transitions;
 import com.android.wm.shell.windowdecor.WindowDecorViewModel;
 
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTransitionTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTransitionTests.java
index 1990fe7..abe3dcc 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTransitionTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/SplitTransitionTests.java
@@ -76,9 +76,9 @@
 import com.android.wm.shell.common.LaunchAdjacentController;
 import com.android.wm.shell.common.ShellExecutor;
 import com.android.wm.shell.common.SyncTransactionQueue;
-import com.android.wm.shell.common.TransactionPool;
 import com.android.wm.shell.common.split.SplitDecorManager;
 import com.android.wm.shell.common.split.SplitLayout;
+import com.android.wm.shell.shared.TransactionPool;
 import com.android.wm.shell.transition.DefaultMixedHandler;
 import com.android.wm.shell.transition.TestRemoteTransition;
 import com.android.wm.shell.transition.TransitionInfoBuilder;
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageCoordinatorTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageCoordinatorTests.java
index ff6c7ee..0054cb6 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageCoordinatorTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/splitscreen/StageCoordinatorTests.java
@@ -19,13 +19,12 @@
 import static android.app.ActivityTaskManager.INVALID_TASK_ID;
 import static android.view.Display.DEFAULT_DISPLAY;
 
-import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_BOTTOM_OR_RIGHT;
-import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_TOP_OR_LEFT;
-import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_UNDEFINED;
+import static com.android.wm.shell.shared.split.SplitScreenConstants.SPLIT_POSITION_BOTTOM_OR_RIGHT;
+import static com.android.wm.shell.shared.split.SplitScreenConstants.SPLIT_POSITION_TOP_OR_LEFT;
+import static com.android.wm.shell.shared.split.SplitScreenConstants.SPLIT_POSITION_UNDEFINED;
 import static com.android.wm.shell.splitscreen.SplitScreen.STAGE_TYPE_MAIN;
 import static com.android.wm.shell.splitscreen.SplitScreen.STAGE_TYPE_SIDE;
 import static com.android.wm.shell.splitscreen.SplitScreen.STAGE_TYPE_UNDEFINED;
-import static com.android.wm.shell.splitscreen.SplitScreenController.EXIT_REASON_RETURN_HOME;
 import static com.android.wm.shell.transition.Transitions.TRANSIT_SPLIT_DISMISS;
 
 import static com.google.common.truth.Truth.assertThat;
@@ -69,9 +68,9 @@
 import com.android.wm.shell.common.LaunchAdjacentController;
 import com.android.wm.shell.common.ShellExecutor;
 import com.android.wm.shell.common.SyncTransactionQueue;
-import com.android.wm.shell.common.TransactionPool;
 import com.android.wm.shell.common.split.SplitDecorManager;
 import com.android.wm.shell.common.split.SplitLayout;
+import com.android.wm.shell.shared.TransactionPool;
 import com.android.wm.shell.splitscreen.SplitScreen.SplitScreenListener;
 import com.android.wm.shell.sysui.ShellController;
 import com.android.wm.shell.sysui.ShellInit;
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawerTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawerTests.java
index af6c077..5f75423 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawerTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/startingsurface/StartingSurfaceDrawerTests.java
@@ -74,7 +74,7 @@
 import com.android.wm.shell.ShellTestCase;
 import com.android.wm.shell.common.HandlerExecutor;
 import com.android.wm.shell.common.ShellExecutor;
-import com.android.wm.shell.common.TransactionPool;
+import com.android.wm.shell.shared.TransactionPool;
 
 import org.junit.Before;
 import org.junit.Test;
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/startingsurface/StartingWindowControllerTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/startingsurface/StartingWindowControllerTests.java
index ff76a2f..7fd1c11 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/startingsurface/StartingWindowControllerTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/startingsurface/StartingWindowControllerTests.java
@@ -42,11 +42,11 @@
 import com.android.wm.shell.ShellTestCase;
 import com.android.wm.shell.common.DisplayInsetsController;
 import com.android.wm.shell.common.ShellExecutor;
-import com.android.wm.shell.common.TransactionPool;
+import com.android.wm.shell.shared.ShellSharedConstants;
+import com.android.wm.shell.shared.TransactionPool;
 import com.android.wm.shell.sysui.ShellCommandHandler;
 import com.android.wm.shell.sysui.ShellController;
 import com.android.wm.shell.sysui.ShellInit;
-import com.android.wm.shell.sysui.ShellSharedConstants;
 
 import org.junit.Before;
 import org.junit.Test;
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 6bc7e49..0c18229 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,7 +48,7 @@
 import com.android.wm.shell.ShellTestCase;
 import com.android.wm.shell.TestShellExecutor;
 import com.android.wm.shell.common.DisplayController;
-import com.android.wm.shell.common.TransactionPool;
+import com.android.wm.shell.shared.TransactionPool;
 import com.android.wm.shell.sysui.ShellInit;
 
 import org.junit.After;
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/HomeTransitionObserverTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/HomeTransitionObserverTest.java
index 0db10ef..d2adae1 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/HomeTransitionObserverTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/HomeTransitionObserverTest.java
@@ -51,8 +51,8 @@
 import com.android.wm.shell.TestShellExecutor;
 import com.android.wm.shell.common.DisplayController;
 import com.android.wm.shell.common.ShellExecutor;
-import com.android.wm.shell.common.TransactionPool;
 import com.android.wm.shell.shared.IHomeTransitionListener;
+import com.android.wm.shell.shared.TransactionPool;
 import com.android.wm.shell.sysui.ShellController;
 import com.android.wm.shell.sysui.ShellInit;
 
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/MockTransactionPool.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/MockTransactionPool.java
index 574a87a..a5a27e2 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/MockTransactionPool.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/MockTransactionPool.java
@@ -21,7 +21,7 @@
 
 import android.view.SurfaceControl;
 
-import com.android.wm.shell.common.TransactionPool;
+import com.android.wm.shell.shared.TransactionPool;
 import com.android.wm.shell.util.StubTransaction;
 
 public class MockTransactionPool extends TransactionPool {
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/ShellTransitionTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/ShellTransitionTests.java
index 81e6d07..7c63fda 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/ShellTransitionTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/transition/ShellTransitionTests.java
@@ -107,12 +107,12 @@
 import com.android.wm.shell.common.DisplayController;
 import com.android.wm.shell.common.DisplayLayout;
 import com.android.wm.shell.common.ShellExecutor;
-import com.android.wm.shell.common.TransactionPool;
 import com.android.wm.shell.recents.RecentTasksController;
 import com.android.wm.shell.recents.RecentsTransitionHandler;
+import com.android.wm.shell.shared.ShellSharedConstants;
+import com.android.wm.shell.shared.TransactionPool;
 import com.android.wm.shell.sysui.ShellController;
 import com.android.wm.shell.sysui.ShellInit;
-import com.android.wm.shell.sysui.ShellSharedConstants;
 import com.android.wm.shell.util.StubTransaction;
 
 import org.junit.Before;
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/unfold/UnfoldAnimationControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/unfold/UnfoldAnimationControllerTest.java
index 8196c5a..8fe0c38 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/unfold/UnfoldAnimationControllerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/unfold/UnfoldAnimationControllerTest.java
@@ -35,7 +35,7 @@
 import com.android.wm.shell.ShellTestCase;
 import com.android.wm.shell.TestRunningTaskInfoBuilder;
 import com.android.wm.shell.TestShellExecutor;
-import com.android.wm.shell.common.TransactionPool;
+import com.android.wm.shell.shared.TransactionPool;
 import com.android.wm.shell.sysui.ShellInit;
 import com.android.wm.shell.unfold.animation.UnfoldTaskAnimator;
 
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/unfold/UnfoldTransitionHandlerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/unfold/UnfoldTransitionHandlerTest.java
index acc0bce..cf2de91 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/unfold/UnfoldTransitionHandlerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/unfold/UnfoldTransitionHandlerTest.java
@@ -40,7 +40,7 @@
 import android.window.WindowContainerTransaction;
 
 import com.android.wm.shell.common.ShellExecutor;
-import com.android.wm.shell.common.TransactionPool;
+import com.android.wm.shell.shared.TransactionPool;
 import com.android.wm.shell.sysui.ShellInit;
 import com.android.wm.shell.transition.TransitionInfoBuilder;
 import com.android.wm.shell.transition.Transitions;
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 fa905e2..4d6b3b9 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
@@ -56,6 +56,7 @@
 import android.view.SurfaceView
 import android.view.View
 import android.view.WindowInsets.Type.statusBars
+import android.widget.Toast
 import android.window.WindowContainerTransaction
 import android.window.WindowContainerTransaction.HierarchyOp
 import androidx.test.filters.SmallTest
@@ -93,6 +94,8 @@
 import java.util.Optional
 import java.util.function.Consumer
 import java.util.function.Supplier
+import junit.framework.Assert.assertFalse
+import junit.framework.Assert.assertTrue
 import org.junit.After
 import org.junit.Assert.assertEquals
 import org.junit.Before
@@ -117,8 +120,7 @@
 import org.mockito.kotlin.spy
 import org.mockito.kotlin.whenever
 import org.mockito.quality.Strictness
-import junit.framework.Assert.assertFalse
-import junit.framework.Assert.assertTrue
+
 
 /**
  * Tests of [DesktopModeWindowDecorViewModel]
@@ -158,6 +160,7 @@
     @Mock private lateinit var mockWindowManager: IWindowManager
     @Mock private lateinit var mockInteractionJankMonitor: InteractionJankMonitor
     @Mock private lateinit var mockGenericLinksParser: AppToWebGenericLinksParser
+    @Mock private lateinit var mockToast: Toast
     private val bgExecutor = TestShellExecutor()
     @Mock private lateinit var mockMultiInstanceHelper: MultiInstanceHelper
     private lateinit var spyContext: TestableContext
@@ -181,6 +184,7 @@
                 .strictness(Strictness.LENIENT)
                 .spyStatic(DesktopModeStatus::class.java)
                 .spyStatic(DragPositioningCallbackUtility::class.java)
+                .spyStatic(Toast::class.java)
                 .startMocking()
         doReturn(true).`when` { DesktopModeStatus.isDesktopModeSupported(Mockito.any()) }
 
@@ -218,6 +222,8 @@
         whenever(mockDisplayLayout.stableInsets()).thenReturn(STABLE_INSETS)
         whenever(mockInputMonitorFactory.create(any(), any())).thenReturn(mockInputMonitor)
 
+        doReturn(mockToast).`when` { Toast.makeText(any(), anyInt(), anyInt()) }
+
         // InputChannel cannot be mocked because it passes to InputEventReceiver.
         val inputChannels = InputChannel.openInputChannelPair(TAG)
         inputChannels.first().dispose()
@@ -284,11 +290,8 @@
     @Test
     @DisableFlags(Flags.FLAG_ENABLE_ADDITIONAL_WINDOWS_ABOVE_STATUS_BAR)
     fun testCreateAndDisposeEventReceiver() {
-        val task = createTask(windowingMode = WINDOWING_MODE_FREEFORM)
-        setUpMockDecorationForTask(task)
-
-        onTaskOpening(task)
-        desktopModeWindowDecorViewModel.destroyWindowDecoration(task)
+        val decor = createOpenTaskDecoration(windowingMode = WINDOWING_MODE_FREEFORM)
+        desktopModeWindowDecorViewModel.destroyWindowDecoration(decor.mTaskInfo)
 
         verify(mockInputMonitorFactory).create(any(), any())
         verify(mockInputMonitor).dispose()
@@ -357,16 +360,14 @@
     }
 
     @Test
-    fun testCloseButtonInFreeform() {
-        val task = createTask(windowingMode = WINDOWING_MODE_FREEFORM)
-        val windowDecor = setUpMockDecorationForTask(task)
+    fun testCloseButtonInFreeform_closeWindow() {
+        val onClickListenerCaptor = forClass(View.OnClickListener::class.java)
+                as ArgumentCaptor<View.OnClickListener>
+        val decor = createOpenTaskDecoration(
+            windowingMode = WINDOWING_MODE_FREEFORM,
+            onCaptionButtonClickListener = onClickListenerCaptor
+        )
 
-        onTaskOpening(task)
-        val onClickListenerCaptor = argumentCaptor<View.OnClickListener>()
-        verify(windowDecor).setCaptionListeners(
-            onClickListenerCaptor.capture(), any(), any(), any())
-
-        val onClickListener = onClickListenerCaptor.firstValue
         val view = mock(View::class.java)
         whenever(view.id).thenReturn(R.id.close_window)
 
@@ -374,7 +375,7 @@
         desktopModeWindowDecorViewModel
             .setFreeformTaskTransitionStarter(freeformTaskTransitionStarter)
 
-        onClickListener.onClick(view)
+        onClickListenerCaptor.value.onClick(view)
 
         val transactionCaptor = argumentCaptor<WindowContainerTransaction>()
         verify(freeformTaskTransitionStarter).startRemoveTransition(transactionCaptor.capture())
@@ -383,7 +384,7 @@
         assertEquals(1, wct.getHierarchyOps().size)
         assertEquals(HierarchyOp.HIERARCHY_OP_TYPE_REMOVE_TASK,
                      wct.getHierarchyOps().get(0).getType())
-        assertEquals(task.token.asBinder(), wct.getHierarchyOps().get(0).getContainer())
+        assertEquals(decor.mTaskInfo.token.asBinder(), wct.getHierarchyOps().get(0).getContainer())
     }
 
     @Test
@@ -458,15 +459,9 @@
 
     @Test
     fun testKeyguardState_notifiesAllDecors() {
-        val task1 = createTask(windowingMode = WINDOWING_MODE_FREEFORM)
-        val decoration1 = setUpMockDecorationForTask(task1)
-        onTaskOpening(task1)
-        val task2 = createTask(windowingMode = WINDOWING_MODE_FREEFORM)
-        val decoration2 = setUpMockDecorationForTask(task2)
-        onTaskOpening(task2)
-        val task3 = createTask(windowingMode = WINDOWING_MODE_FREEFORM)
-        val decoration3 = setUpMockDecorationForTask(task3)
-        onTaskOpening(task3)
+        val decoration1 = createOpenTaskDecoration(windowingMode = WINDOWING_MODE_FREEFORM)
+        val decoration2 = createOpenTaskDecoration(windowingMode = WINDOWING_MODE_FREEFORM)
+        val decoration3 = createOpenTaskDecoration(windowingMode = WINDOWING_MODE_FREEFORM)
 
         desktopModeOnKeyguardChangedListener
             .onKeyguardVisibilityChanged(true /* visible */, true /* occluded */,
@@ -625,6 +620,7 @@
 
         verify(mockDesktopTasksController, never())
             .snapToHalfScreen(decor.mTaskInfo, currentBounds, SnapPosition.LEFT)
+        verify(mockToast).show()
     }
 
     @Test
@@ -690,6 +686,7 @@
 
         verify(mockDesktopTasksController, never())
             .snapToHalfScreen(decor.mTaskInfo, currentBounds, SnapPosition.RIGHT)
+        verify(mockToast).show()
     }
 
     @Test
@@ -1009,6 +1006,8 @@
             forClass(Function0::class.java) as ArgumentCaptor<Function0<Unit>>,
         onOpenInBrowserClickListener: ArgumentCaptor<Consumer<Uri>> =
             forClass(Consumer::class.java) as ArgumentCaptor<Consumer<Uri>>,
+        onCaptionButtonClickListener: ArgumentCaptor<View.OnClickListener> =
+            forClass(View.OnClickListener::class.java) as ArgumentCaptor<View.OnClickListener>
     ): DesktopModeWindowDecoration {
         val decor = setUpMockDecorationForTask(createTask(windowingMode = windowingMode))
         onTaskOpening(decor.mTaskInfo)
@@ -1019,6 +1018,8 @@
         verify(decor).setOnToFullscreenClickListener(onToFullscreenClickListenerCaptor.capture())
         verify(decor).setOnToSplitScreenClickListener(onToSplitScreenClickListenerCaptor.capture())
         verify(decor).setOpenInBrowserClickListener(onOpenInBrowserClickListener.capture())
+        verify(decor).setCaptionListeners(
+                onCaptionButtonClickListener.capture(), any(), any(), any())
         return decor
     }
 
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DragPositioningCallbackUtilityTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DragPositioningCallbackUtilityTest.kt
index e529711..1f33ae6 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DragPositioningCallbackUtilityTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DragPositioningCallbackUtilityTest.kt
@@ -93,7 +93,7 @@
     fun setup() {
         MockitoAnnotations.initMocks(this)
         mockitoSession = ExtendedMockito.mockitoSession().strictness(Strictness.LENIENT)
-                .spyStatic(DesktopModeStatus::class.java).startMocking()
+            .spyStatic(DesktopModeStatus::class.java).startMocking()
 
         whenever(taskToken.asBinder()).thenReturn(taskBinder)
         whenever(mockDisplayController.getDisplayLayout(DISPLAY_ID)).thenReturn(mockDisplayLayout)
@@ -108,9 +108,9 @@
         whenever(mockContext.getResources()).thenReturn(mockResources)
         whenever(mockWindowDecoration.mDecorWindowContext.resources).thenReturn(mockResources)
         whenever(mockResources.getDimensionPixelSize(R.dimen.desktop_mode_minimum_window_width))
-                .thenReturn(DESKTOP_MODE_MIN_WIDTH)
+            .thenReturn(DESKTOP_MODE_MIN_WIDTH)
         whenever(mockResources.getDimensionPixelSize(R.dimen.desktop_mode_minimum_window_height))
-                .thenReturn(DESKTOP_MODE_MIN_HEIGHT)
+            .thenReturn(DESKTOP_MODE_MIN_HEIGHT)
         whenever(mockDisplay.displayId).thenAnswer { DISPLAY_ID }
     }
 
@@ -129,9 +129,11 @@
         val newY = STARTING_BOUNDS.top.toFloat() + 95
         val delta = DragPositioningCallbackUtility.calculateDelta(newX, newY, startingPoint)
 
-        DragPositioningCallbackUtility.changeBounds(CTRL_TYPE_RIGHT or CTRL_TYPE_TOP,
+        DragPositioningCallbackUtility.changeBounds(
+            CTRL_TYPE_RIGHT or CTRL_TYPE_TOP,
             repositionTaskBounds, STARTING_BOUNDS, STABLE_BOUNDS, delta, mockDisplayController,
-            mockWindowDecoration)
+            mockWindowDecoration
+        )
 
         assertThat(repositionTaskBounds.left).isEqualTo(STARTING_BOUNDS.left)
         assertThat(repositionTaskBounds.top).isEqualTo(STARTING_BOUNDS.top)
@@ -149,9 +151,11 @@
         val newY = STARTING_BOUNDS.top.toFloat() + 5
         val delta = DragPositioningCallbackUtility.calculateDelta(newX, newY, startingPoint)
 
-        DragPositioningCallbackUtility.changeBounds(CTRL_TYPE_RIGHT or CTRL_TYPE_TOP,
+        DragPositioningCallbackUtility.changeBounds(
+            CTRL_TYPE_RIGHT or CTRL_TYPE_TOP,
             repositionTaskBounds, STARTING_BOUNDS, STABLE_BOUNDS, delta, mockDisplayController,
-            mockWindowDecoration)
+            mockWindowDecoration
+        )
 
         assertThat(repositionTaskBounds.left).isEqualTo(STARTING_BOUNDS.left)
         assertThat(repositionTaskBounds.top).isEqualTo(STARTING_BOUNDS.top + 5)
@@ -169,9 +173,11 @@
         val newY = STARTING_BOUNDS.top.toFloat() + 105
         val delta = DragPositioningCallbackUtility.calculateDelta(newX, newY, startingPoint)
 
-        DragPositioningCallbackUtility.changeBounds(CTRL_TYPE_RIGHT or CTRL_TYPE_TOP,
+        DragPositioningCallbackUtility.changeBounds(
+            CTRL_TYPE_RIGHT or CTRL_TYPE_TOP,
             repositionTaskBounds, STARTING_BOUNDS, STABLE_BOUNDS, delta, mockDisplayController,
-            mockWindowDecoration)
+            mockWindowDecoration
+        )
 
         assertThat(repositionTaskBounds.left).isEqualTo(STARTING_BOUNDS.left)
         assertThat(repositionTaskBounds.top).isEqualTo(STARTING_BOUNDS.top)
@@ -189,9 +195,11 @@
         val newY = STARTING_BOUNDS.top.toFloat() + 80
         val delta = DragPositioningCallbackUtility.calculateDelta(newX, newY, startingPoint)
 
-        DragPositioningCallbackUtility.changeBounds(CTRL_TYPE_RIGHT or CTRL_TYPE_TOP,
+        DragPositioningCallbackUtility.changeBounds(
+            CTRL_TYPE_RIGHT or CTRL_TYPE_TOP,
             repositionTaskBounds, STARTING_BOUNDS, STABLE_BOUNDS, delta, mockDisplayController,
-            mockWindowDecoration)
+            mockWindowDecoration
+        )
         assertThat(repositionTaskBounds.left).isEqualTo(STARTING_BOUNDS.left)
         assertThat(repositionTaskBounds.top).isEqualTo(STARTING_BOUNDS.top + 80)
         assertThat(repositionTaskBounds.right).isEqualTo(STARTING_BOUNDS.right - 80)
@@ -208,9 +216,11 @@
 
         val delta = DragPositioningCallbackUtility.calculateDelta(newX, newY, startingPoint)
 
-        DragPositioningCallbackUtility.changeBounds(CTRL_TYPE_RIGHT or CTRL_TYPE_TOP,
+        DragPositioningCallbackUtility.changeBounds(
+            CTRL_TYPE_RIGHT or CTRL_TYPE_TOP,
             repositionTaskBounds, STARTING_BOUNDS, STABLE_BOUNDS, delta, mockDisplayController,
-            mockWindowDecoration)
+            mockWindowDecoration
+        )
         assertThat(repositionTaskBounds.left).isEqualTo(STARTING_BOUNDS.left)
         assertThat(repositionTaskBounds.top).isEqualTo(STARTING_BOUNDS.top)
         assertThat(repositionTaskBounds.right).isEqualTo(STARTING_BOUNDS.right)
@@ -221,52 +231,95 @@
     fun testDragEndSnapsTaskBoundsWhenOutsideValidDragArea() {
         val startingPoint = PointF(STARTING_BOUNDS.right.toFloat(), STARTING_BOUNDS.top.toFloat())
         val repositionTaskBounds = Rect(STARTING_BOUNDS)
-        val validDragArea = Rect(DISPLAY_BOUNDS.left - 100,
+        val validDragArea = Rect(
+            DISPLAY_BOUNDS.left - 100,
             STABLE_BOUNDS.top,
             DISPLAY_BOUNDS.right - 100,
-            DISPLAY_BOUNDS.bottom - 100)
+            DISPLAY_BOUNDS.bottom - 100
+        )
 
-        DragPositioningCallbackUtility.updateTaskBounds(repositionTaskBounds, STARTING_BOUNDS,
-            startingPoint, startingPoint.x - 1000, (DISPLAY_BOUNDS.bottom + 1000).toFloat())
-        DragPositioningCallbackUtility.snapTaskBoundsIfNecessary(repositionTaskBounds,
-            validDragArea)
+        DragPositioningCallbackUtility.updateTaskBounds(
+            repositionTaskBounds, STARTING_BOUNDS,
+            startingPoint, startingPoint.x - 1000, (DISPLAY_BOUNDS.bottom + 1000).toFloat()
+        )
+        DragPositioningCallbackUtility.snapTaskBoundsIfNecessary(
+            repositionTaskBounds,
+            validDragArea
+        )
         assertThat(repositionTaskBounds.left).isEqualTo(validDragArea.left)
         assertThat(repositionTaskBounds.top).isEqualTo(validDragArea.bottom)
         assertThat(repositionTaskBounds.right)
-                .isEqualTo(validDragArea.left + STARTING_BOUNDS.width())
+            .isEqualTo(validDragArea.left + STARTING_BOUNDS.width())
         assertThat(repositionTaskBounds.bottom)
-                .isEqualTo(validDragArea.bottom + STARTING_BOUNDS.height())
+            .isEqualTo(validDragArea.bottom + STARTING_BOUNDS.height())
     }
 
     @Test
     fun testChangeBounds_toDisallowedBounds_freezesAtLimit() {
-        val startingPoint = PointF(STARTING_BOUNDS.right.toFloat(),
-            STARTING_BOUNDS.bottom.toFloat())
+        val startingPoint = PointF(
+            STARTING_BOUNDS.right.toFloat(),
+            STARTING_BOUNDS.bottom.toFloat()
+        )
         val repositionTaskBounds = Rect(STARTING_BOUNDS)
         // Initial resize to width and height 110px.
         var newX = STARTING_BOUNDS.right.toFloat() + 10
         var newY = STARTING_BOUNDS.bottom.toFloat() + 10
         var delta = DragPositioningCallbackUtility.calculateDelta(newX, newY, startingPoint)
-        assertTrue(DragPositioningCallbackUtility.changeBounds(CTRL_TYPE_RIGHT or CTRL_TYPE_BOTTOM,
-            repositionTaskBounds, STARTING_BOUNDS, STABLE_BOUNDS, delta, mockDisplayController,
-            mockWindowDecoration))
+        assertTrue(
+            DragPositioningCallbackUtility.changeBounds(
+                CTRL_TYPE_RIGHT or CTRL_TYPE_BOTTOM,
+                repositionTaskBounds, STARTING_BOUNDS, STABLE_BOUNDS, delta, mockDisplayController,
+                mockWindowDecoration
+            )
+        )
         // Resize width to 120px, height to disallowed area which should not result in a change.
         newX += 10
         newY = DISALLOWED_RESIZE_AREA.top.toFloat()
         delta = DragPositioningCallbackUtility.calculateDelta(newX, newY, startingPoint)
-        assertTrue(DragPositioningCallbackUtility.changeBounds(CTRL_TYPE_RIGHT or CTRL_TYPE_BOTTOM,
-            repositionTaskBounds, STARTING_BOUNDS, STABLE_BOUNDS, delta, mockDisplayController,
-            mockWindowDecoration))
+        assertTrue(
+            DragPositioningCallbackUtility.changeBounds(
+                CTRL_TYPE_RIGHT or CTRL_TYPE_BOTTOM,
+                repositionTaskBounds, STARTING_BOUNDS, STABLE_BOUNDS, delta, mockDisplayController,
+                mockWindowDecoration
+            )
+        )
         assertThat(repositionTaskBounds.left).isEqualTo(STARTING_BOUNDS.left)
         assertThat(repositionTaskBounds.top).isEqualTo(STARTING_BOUNDS.top)
         assertThat(repositionTaskBounds.right).isEqualTo(STARTING_BOUNDS.right + 20)
-        assertThat(repositionTaskBounds.bottom).isEqualTo(STARTING_BOUNDS.bottom + 10)
+        assertThat(repositionTaskBounds.bottom).isEqualTo(STABLE_BOUNDS.bottom)
+    }
+
+
+    @Test
+    fun testChangeBounds_beyondStableBounds_freezesAtStableBounds() {
+        val startingPoint = PointF(
+            STARTING_BOUNDS.right.toFloat(),
+            STARTING_BOUNDS.bottom.toFloat()
+        )
+        val repositionTaskBounds = Rect(STARTING_BOUNDS)
+
+        // Resize to beyond stable bounds.
+        val newX = STARTING_BOUNDS.right.toFloat() + STABLE_BOUNDS.width()
+        val newY = STARTING_BOUNDS.bottom.toFloat() + STABLE_BOUNDS.height()
+
+        val delta = DragPositioningCallbackUtility.calculateDelta(newX, newY, startingPoint)
+        assertTrue(
+            DragPositioningCallbackUtility.changeBounds(
+                CTRL_TYPE_RIGHT or CTRL_TYPE_BOTTOM,
+                repositionTaskBounds, STARTING_BOUNDS, STABLE_BOUNDS, delta, mockDisplayController,
+                mockWindowDecoration
+            )
+        )
+        assertThat(repositionTaskBounds.left).isEqualTo(STARTING_BOUNDS.left)
+        assertThat(repositionTaskBounds.top).isEqualTo(STARTING_BOUNDS.top)
+        assertThat(repositionTaskBounds.right).isEqualTo(STABLE_BOUNDS.right)
+        assertThat(repositionTaskBounds.bottom).isEqualTo(STABLE_BOUNDS.bottom)
     }
 
     @Test
     @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_SIZE_CONSTRAINTS)
     fun taskMinWidthHeightUndefined_changeBoundsInDesktopModeLessThanMin_shouldNotChangeBounds() {
-        doReturn(true).`when`{ DesktopModeStatus.canEnterDesktopMode(mockContext) }
+        doReturn(true).`when` { DesktopModeStatus.canEnterDesktopMode(mockContext) }
         initializeTaskInfo(taskMinWidth = -1, taskMinHeight = -1)
         val startingPoint =
             PointF(STARTING_BOUNDS.right.toFloat(), STARTING_BOUNDS.bottom.toFloat())
@@ -277,9 +330,11 @@
         val newY = STARTING_BOUNDS.bottom.toFloat() - 99
         val delta = DragPositioningCallbackUtility.calculateDelta(newX, newY, startingPoint)
 
-        DragPositioningCallbackUtility.changeBounds(CTRL_TYPE_RIGHT or CTRL_TYPE_BOTTOM,
+        DragPositioningCallbackUtility.changeBounds(
+            CTRL_TYPE_RIGHT or CTRL_TYPE_BOTTOM,
             repositionTaskBounds, STARTING_BOUNDS, STABLE_BOUNDS, delta, mockDisplayController,
-            mockWindowDecoration)
+            mockWindowDecoration
+        )
         assertThat(repositionTaskBounds.left).isEqualTo(STARTING_BOUNDS.left)
         assertThat(repositionTaskBounds.top).isEqualTo(STARTING_BOUNDS.top)
         assertThat(repositionTaskBounds.right).isEqualTo(STARTING_BOUNDS.right)
@@ -289,7 +344,7 @@
     @Test
     @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_SIZE_CONSTRAINTS)
     fun taskMinWidthHeightUndefined_changeBoundsInDesktopModeAllowedSize_shouldChangeBounds() {
-        doReturn(true).`when`{ DesktopModeStatus.canEnterDesktopMode(mockContext) }
+        doReturn(true).`when` { DesktopModeStatus.canEnterDesktopMode(mockContext) }
         initializeTaskInfo(taskMinWidth = -1, taskMinHeight = -1)
         val startingPoint =
             PointF(STARTING_BOUNDS.right.toFloat(), STARTING_BOUNDS.bottom.toFloat())
@@ -300,9 +355,11 @@
         val newY = STARTING_BOUNDS.bottom.toFloat() - 80
         val delta = DragPositioningCallbackUtility.calculateDelta(newX, newY, startingPoint)
 
-        DragPositioningCallbackUtility.changeBounds(CTRL_TYPE_RIGHT or CTRL_TYPE_BOTTOM,
+        DragPositioningCallbackUtility.changeBounds(
+            CTRL_TYPE_RIGHT or CTRL_TYPE_BOTTOM,
             repositionTaskBounds, STARTING_BOUNDS, STABLE_BOUNDS, delta, mockDisplayController,
-            mockWindowDecoration)
+            mockWindowDecoration
+        )
         assertThat(repositionTaskBounds.left).isEqualTo(STARTING_BOUNDS.left)
         assertThat(repositionTaskBounds.top).isEqualTo(STARTING_BOUNDS.top)
         assertThat(repositionTaskBounds.right).isEqualTo(STARTING_BOUNDS.right - 80)
@@ -321,9 +378,11 @@
         val newY = STARTING_BOUNDS.bottom.toFloat() - 99
         val delta = DragPositioningCallbackUtility.calculateDelta(newX, newY, startingPoint)
 
-        DragPositioningCallbackUtility.changeBounds(CTRL_TYPE_RIGHT or CTRL_TYPE_BOTTOM,
+        DragPositioningCallbackUtility.changeBounds(
+            CTRL_TYPE_RIGHT or CTRL_TYPE_BOTTOM,
             repositionTaskBounds, STARTING_BOUNDS, STABLE_BOUNDS, delta, mockDisplayController,
-            mockWindowDecoration)
+            mockWindowDecoration
+        )
         assertThat(repositionTaskBounds.left).isEqualTo(STARTING_BOUNDS.left)
         assertThat(repositionTaskBounds.top).isEqualTo(STARTING_BOUNDS.top)
         assertThat(repositionTaskBounds.right).isEqualTo(STARTING_BOUNDS.right)
@@ -342,9 +401,11 @@
         val newY = STARTING_BOUNDS.bottom.toFloat() - 50
         val delta = DragPositioningCallbackUtility.calculateDelta(newX, newY, startingPoint)
 
-        DragPositioningCallbackUtility.changeBounds(CTRL_TYPE_RIGHT or CTRL_TYPE_BOTTOM,
+        DragPositioningCallbackUtility.changeBounds(
+            CTRL_TYPE_RIGHT or CTRL_TYPE_BOTTOM,
             repositionTaskBounds, STARTING_BOUNDS, STABLE_BOUNDS, delta, mockDisplayController,
-            mockWindowDecoration)
+            mockWindowDecoration
+        )
         assertThat(repositionTaskBounds.left).isEqualTo(STARTING_BOUNDS.left)
         assertThat(repositionTaskBounds.top).isEqualTo(STARTING_BOUNDS.top)
         assertThat(repositionTaskBounds.right).isEqualTo(STARTING_BOUNDS.right - 50)
@@ -355,8 +416,10 @@
     @DisableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_SIZE_CONSTRAINTS)
     fun testChangeBounds_windowSizeExceedsStableBounds_shouldBeAllowedToChangeBounds() {
         val startingPoint =
-            PointF(OFF_CENTER_STARTING_BOUNDS.right.toFloat(),
-                OFF_CENTER_STARTING_BOUNDS.bottom.toFloat())
+            PointF(
+                OFF_CENTER_STARTING_BOUNDS.right.toFloat(),
+                OFF_CENTER_STARTING_BOUNDS.bottom.toFloat()
+            )
         val repositionTaskBounds = Rect(OFF_CENTER_STARTING_BOUNDS)
         // Increase height and width by STABLE_BOUNDS. Subtract by 5px so that it doesn't reach
         // the disallowed drag area.
@@ -365,9 +428,11 @@
         val newY = STABLE_BOUNDS.bottom.toFloat() - offset
         val delta = DragPositioningCallbackUtility.calculateDelta(newX, newY, startingPoint)
 
-        DragPositioningCallbackUtility.changeBounds(CTRL_TYPE_RIGHT or CTRL_TYPE_BOTTOM,
+        DragPositioningCallbackUtility.changeBounds(
+            CTRL_TYPE_RIGHT or CTRL_TYPE_BOTTOM,
             repositionTaskBounds, OFF_CENTER_STARTING_BOUNDS, STABLE_BOUNDS, delta,
-            mockDisplayController, mockWindowDecoration)
+            mockDisplayController, mockWindowDecoration
+        )
         assertThat(repositionTaskBounds.width()).isGreaterThan(STABLE_BOUNDS.right)
         assertThat(repositionTaskBounds.height()).isGreaterThan(STABLE_BOUNDS.bottom)
     }
@@ -375,10 +440,12 @@
     @Test
     @EnableFlags(Flags.FLAG_ENABLE_DESKTOP_WINDOWING_SIZE_CONSTRAINTS)
     fun testChangeBoundsInDesktopMode_windowSizeExceedsStableBounds_shouldBeLimitedToDisplaySize() {
-        doReturn(true).`when`{ DesktopModeStatus.canEnterDesktopMode(mockContext) }
+        doReturn(true).`when` { DesktopModeStatus.canEnterDesktopMode(mockContext) }
         val startingPoint =
-            PointF(OFF_CENTER_STARTING_BOUNDS.right.toFloat(),
-                OFF_CENTER_STARTING_BOUNDS.bottom.toFloat())
+            PointF(
+                OFF_CENTER_STARTING_BOUNDS.right.toFloat(),
+                OFF_CENTER_STARTING_BOUNDS.bottom.toFloat()
+            )
         val repositionTaskBounds = Rect(OFF_CENTER_STARTING_BOUNDS)
         // Increase height and width by STABLE_BOUNDS. Subtract by 5px so that it doesn't reach
         // the disallowed drag area.
@@ -387,9 +454,11 @@
         val newY = STABLE_BOUNDS.bottom.toFloat() - offset
         val delta = DragPositioningCallbackUtility.calculateDelta(newX, newY, startingPoint)
 
-        DragPositioningCallbackUtility.changeBounds(CTRL_TYPE_RIGHT or CTRL_TYPE_BOTTOM,
+        DragPositioningCallbackUtility.changeBounds(
+            CTRL_TYPE_RIGHT or CTRL_TYPE_BOTTOM,
             repositionTaskBounds, OFF_CENTER_STARTING_BOUNDS, STABLE_BOUNDS, delta,
-            mockDisplayController, mockWindowDecoration)
+            mockDisplayController, mockWindowDecoration
+        )
         assertThat(repositionTaskBounds.width()).isLessThan(STABLE_BOUNDS.right)
         assertThat(repositionTaskBounds.height()).isLessThan(STABLE_BOUNDS.bottom)
     }
@@ -423,7 +492,8 @@
             DISPLAY_BOUNDS.left,
             DISPLAY_BOUNDS.bottom - NAVBAR_HEIGHT,
             DISPLAY_BOUNDS.right,
-            DISPLAY_BOUNDS.bottom)
+            DISPLAY_BOUNDS.bottom
+        )
         private val STABLE_BOUNDS = Rect(
             DISPLAY_BOUNDS.left,
             DISPLAY_BOUNDS.top,
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DragResizeWindowGeometryTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DragResizeWindowGeometryTests.java
index d8f395d..1691f07 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DragResizeWindowGeometryTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DragResizeWindowGeometryTests.java
@@ -45,6 +45,9 @@
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
+import java.util.Arrays;
+import java.util.List;
+
 /**
  * Tests for {@link DragResizeWindowGeometry}.
  *
@@ -57,11 +60,12 @@
     private static final Size TASK_SIZE = new Size(500, 1000);
     private static final int TASK_CORNER_RADIUS = 10;
     private static final int EDGE_RESIZE_THICKNESS = 12;
+    private static final int EDGE_RESIZE_HANDLE_INSET = 4;
     private static final int FINE_CORNER_SIZE = EDGE_RESIZE_THICKNESS * 2 + 10;
     private static final int LARGE_CORNER_SIZE = FINE_CORNER_SIZE + 10;
     private static final DragResizeWindowGeometry GEOMETRY = new DragResizeWindowGeometry(
-            TASK_CORNER_RADIUS, TASK_SIZE, EDGE_RESIZE_THICKNESS, FINE_CORNER_SIZE,
-            LARGE_CORNER_SIZE);
+            TASK_CORNER_RADIUS, TASK_SIZE, EDGE_RESIZE_THICKNESS, EDGE_RESIZE_HANDLE_INSET,
+            FINE_CORNER_SIZE, LARGE_CORNER_SIZE);
     // Points in the edge resize handle. Note that coordinates start from the top left.
     private static final Point TOP_EDGE_POINT = new Point(TASK_SIZE.getWidth() / 2,
             -EDGE_RESIZE_THICKNESS / 2);
@@ -71,6 +75,16 @@
             TASK_SIZE.getWidth() + EDGE_RESIZE_THICKNESS / 2, TASK_SIZE.getHeight() / 2);
     private static final Point BOTTOM_EDGE_POINT = new Point(TASK_SIZE.getWidth() / 2,
             TASK_SIZE.getHeight() + EDGE_RESIZE_THICKNESS / 2);
+    // Points in the inset of the task bounds still within the edge resize handle.
+    // Note that coordinates start from the top left.
+    private static final Point TOP_INSET_POINT = new Point(TASK_SIZE.getWidth() / 2,
+            EDGE_RESIZE_HANDLE_INSET / 2);
+    private static final Point LEFT_INSET_POINT = new Point(EDGE_RESIZE_HANDLE_INSET / 2,
+            TASK_SIZE.getHeight() / 2);
+    private static final Point RIGHT_INSET_POINT = new Point(
+            TASK_SIZE.getWidth() - EDGE_RESIZE_HANDLE_INSET / 2, TASK_SIZE.getHeight() / 2);
+    private static final Point BOTTOM_INSET_POINT = new Point(TASK_SIZE.getWidth() / 2,
+            TASK_SIZE.getHeight() - EDGE_RESIZE_HANDLE_INSET / 2);
 
     @Rule
     public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
@@ -85,18 +99,23 @@
                 .addEqualityGroup(
                         GEOMETRY,
                         new DragResizeWindowGeometry(TASK_CORNER_RADIUS, TASK_SIZE,
-                                EDGE_RESIZE_THICKNESS, FINE_CORNER_SIZE, LARGE_CORNER_SIZE))
+                                EDGE_RESIZE_THICKNESS, EDGE_RESIZE_HANDLE_INSET, FINE_CORNER_SIZE,
+                                LARGE_CORNER_SIZE))
                 .addEqualityGroup(
                         new DragResizeWindowGeometry(TASK_CORNER_RADIUS, TASK_SIZE,
-                                EDGE_RESIZE_THICKNESS + 10, FINE_CORNER_SIZE, LARGE_CORNER_SIZE),
+                                EDGE_RESIZE_THICKNESS + 10, EDGE_RESIZE_HANDLE_INSET,
+                                FINE_CORNER_SIZE, LARGE_CORNER_SIZE),
                         new DragResizeWindowGeometry(TASK_CORNER_RADIUS, TASK_SIZE,
-                                EDGE_RESIZE_THICKNESS + 10, FINE_CORNER_SIZE, LARGE_CORNER_SIZE))
+                                EDGE_RESIZE_THICKNESS + 10, EDGE_RESIZE_HANDLE_INSET,
+                                FINE_CORNER_SIZE, LARGE_CORNER_SIZE))
                 .addEqualityGroup(
                         new DragResizeWindowGeometry(TASK_CORNER_RADIUS, TASK_SIZE,
-                                EDGE_RESIZE_THICKNESS + 10, FINE_CORNER_SIZE,
+                                EDGE_RESIZE_THICKNESS + 10, EDGE_RESIZE_HANDLE_INSET,
+                                FINE_CORNER_SIZE,
                                 LARGE_CORNER_SIZE + 5),
                         new DragResizeWindowGeometry(TASK_CORNER_RADIUS, TASK_SIZE,
-                                EDGE_RESIZE_THICKNESS + 10, FINE_CORNER_SIZE,
+                                EDGE_RESIZE_THICKNESS + 10, EDGE_RESIZE_HANDLE_INSET,
+                                FINE_CORNER_SIZE,
                                 LARGE_CORNER_SIZE + 5))
                 .testEquals();
     }
@@ -127,7 +146,7 @@
         assertThat(region.contains(point.x - EDGE_RESIZE_THICKNESS, point.y)).isTrue();
         // Vertically along the edge is not contained.
         assertThat(region.contains(point.x, point.y - EDGE_RESIZE_THICKNESS)).isFalse();
-        assertThat(region.contains(point.x, point.y + EDGE_RESIZE_THICKNESS)).isFalse();
+        assertThat(region.contains(point.x, point.y + EDGE_RESIZE_THICKNESS + 10)).isFalse();
     }
 
     private static void verifyVerticalEdge(@NonNull Region region, @NonNull Point point) {
@@ -188,18 +207,18 @@
     }
 
     private void validateCtrlTypeForEdges(boolean isTouchscreen, boolean isEdgeResizePermitted) {
-        assertThat(GEOMETRY.calculateCtrlType(mContext, isTouchscreen, isEdgeResizePermitted,
-                LEFT_EDGE_POINT.x, LEFT_EDGE_POINT.y)).isEqualTo(
-                        isEdgeResizePermitted ? CTRL_TYPE_LEFT : CTRL_TYPE_UNDEFINED);
-        assertThat(GEOMETRY.calculateCtrlType(mContext, isTouchscreen, isEdgeResizePermitted,
-                TOP_EDGE_POINT.x, TOP_EDGE_POINT.y)).isEqualTo(
-                        isEdgeResizePermitted ? CTRL_TYPE_TOP : CTRL_TYPE_UNDEFINED);
-        assertThat(GEOMETRY.calculateCtrlType(mContext, isTouchscreen, isEdgeResizePermitted,
-                RIGHT_EDGE_POINT.x, RIGHT_EDGE_POINT.y)).isEqualTo(
-                        isEdgeResizePermitted ? CTRL_TYPE_RIGHT : CTRL_TYPE_UNDEFINED);
-        assertThat(GEOMETRY.calculateCtrlType(mContext, isTouchscreen, isEdgeResizePermitted,
-                BOTTOM_EDGE_POINT.x, BOTTOM_EDGE_POINT.y)).isEqualTo(
-                        isEdgeResizePermitted ? CTRL_TYPE_BOTTOM : CTRL_TYPE_UNDEFINED);
+        List<Point> points = Arrays.asList(LEFT_EDGE_POINT, TOP_EDGE_POINT, RIGHT_EDGE_POINT,
+                BOTTOM_EDGE_POINT, LEFT_INSET_POINT, TOP_INSET_POINT, RIGHT_INSET_POINT,
+                BOTTOM_INSET_POINT);
+        List<Integer> expectedCtrlType = Arrays.asList(CTRL_TYPE_LEFT, CTRL_TYPE_TOP,
+                CTRL_TYPE_RIGHT, CTRL_TYPE_BOTTOM, CTRL_TYPE_LEFT, CTRL_TYPE_TOP, CTRL_TYPE_RIGHT,
+                CTRL_TYPE_BOTTOM);
+
+        for (int i = 0; i < points.size(); i++) {
+            assertThat(GEOMETRY.calculateCtrlType(mContext, isTouchscreen, isEdgeResizePermitted,
+                    points.get(i).x, points.get(i).y)).isEqualTo(
+                    isEdgeResizePermitted ? expectedCtrlType.get(i) : CTRL_TYPE_UNDEFINED);
+        }
     }
 
     @Test
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/FluidResizeTaskPositionerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/FluidResizeTaskPositionerTest.kt
index 2ce59ff..3a3e965 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/FluidResizeTaskPositionerTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/FluidResizeTaskPositionerTest.kt
@@ -678,6 +678,7 @@
             CTRL_TYPE_RIGHT or CTRL_TYPE_BOTTOM)
         val rectAfterDrag = Rect(STARTING_BOUNDS)
         rectAfterDrag.right += 2000
+        rectAfterDrag.bottom = STABLE_BOUNDS_LANDSCAPE.bottom
         // First drag; we should fetch stable bounds.
         verify(mockDisplayLayout, Mockito.times(1)).getStableBounds(any())
         verify(mockShellTaskOrganizer).applyTransaction(argThat { wct ->
@@ -705,8 +706,8 @@
             STARTING_BOUNDS.right.toFloat(), STARTING_BOUNDS.bottom.toFloat(),
             STARTING_BOUNDS.right.toFloat() + 2000, STARTING_BOUNDS.bottom.toFloat() + 2000,
             CTRL_TYPE_RIGHT or CTRL_TYPE_BOTTOM)
-        rectAfterDrag.right -= 2000
-        rectAfterDrag.bottom += 2000
+        rectAfterDrag.right = STABLE_BOUNDS_PORTRAIT.right
+        rectAfterDrag.bottom = STARTING_BOUNDS.bottom + 2000
 
         verify(mockShellTaskOrganizer).applyTransaction(argThat { wct ->
             return@argThat wct.changes.any { (token, change) ->
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/HandleMenuTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/HandleMenuTest.kt
index a1c7947..627dfe7 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/HandleMenuTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/HandleMenuTest.kt
@@ -41,9 +41,9 @@
 import com.android.wm.shell.TestRunningTaskInfoBuilder
 import com.android.wm.shell.common.DisplayController
 import com.android.wm.shell.common.DisplayLayout
-import com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_BOTTOM_OR_RIGHT
-import com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_TOP_OR_LEFT
-import com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_UNDEFINED
+import com.android.wm.shell.shared.split.SplitScreenConstants.SPLIT_POSITION_BOTTOM_OR_RIGHT
+import com.android.wm.shell.shared.split.SplitScreenConstants.SPLIT_POSITION_TOP_OR_LEFT
+import com.android.wm.shell.shared.split.SplitScreenConstants.SPLIT_POSITION_UNDEFINED
 import com.android.wm.shell.splitscreen.SplitScreenController
 import com.android.wm.shell.windowdecor.additionalviewcontainer.AdditionalSystemViewContainer
 import com.android.wm.shell.windowdecor.additionalviewcontainer.AdditionalViewHostViewContainer
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositionerTest.kt b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositionerTest.kt
index 08a6e1b..6ae16ed 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositionerTest.kt
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositionerTest.kt
@@ -372,6 +372,7 @@
             CTRL_TYPE_RIGHT or CTRL_TYPE_BOTTOM)
         val rectAfterDrag = Rect(STARTING_BOUNDS)
         rectAfterDrag.right += 2000
+        rectAfterDrag.bottom = STABLE_BOUNDS_LANDSCAPE.bottom
         // First drag; we should fetch stable bounds.
         verify(mockDisplayLayout, times(1)).getStableBounds(any())
         verify(mockTransitions).startTransition(eq(TRANSIT_CHANGE), argThat { wct ->
@@ -396,8 +397,8 @@
         performDrag(STARTING_BOUNDS.right.toFloat(), STARTING_BOUNDS.bottom.toFloat(),
             STARTING_BOUNDS.right.toFloat() + 2000, STARTING_BOUNDS.bottom.toFloat() + 2000,
             CTRL_TYPE_RIGHT or CTRL_TYPE_BOTTOM)
-        rectAfterDrag.right -= 2000
-        rectAfterDrag.bottom += 2000
+        rectAfterDrag.right = STABLE_BOUNDS_PORTRAIT.right
+        rectAfterDrag.bottom = STARTING_BOUNDS.bottom + 2000
 
         verify(mockTransitions).startTransition(eq(TRANSIT_CHANGE), argThat { wct ->
             return@argThat wct.changes.any { (token, change) ->
diff --git a/libs/androidfw/AssetManager2.cpp b/libs/androidfw/AssetManager2.cpp
index 822a387..0fa31c7 100644
--- a/libs/androidfw/AssetManager2.cpp
+++ b/libs/androidfw/AssetManager2.cpp
@@ -107,7 +107,7 @@
 }
 
 AssetManager2::AssetManager2() {
-  configurations_.resize(1);
+  configurations_.emplace_back();
 }
 
 bool AssetManager2::SetApkAssets(ApkAssetsList apk_assets, bool invalidate_caches) {
@@ -438,8 +438,8 @@
   return false;
 }
 
-void AssetManager2::SetConfigurations(std::vector<ResTable_config> configurations,
-    bool force_refresh) {
+void AssetManager2::SetConfigurations(std::span<const ResTable_config> configurations,
+                                      bool force_refresh) {
   int diff = 0;
   if (force_refresh) {
     diff = -1;
@@ -452,8 +452,10 @@
       }
     }
   }
-  configurations_ = std::move(configurations);
-
+  configurations_.clear();
+  for (auto&& config : configurations) {
+    configurations_.emplace_back(config);
+  }
   if (diff) {
     RebuildFilterList();
     InvalidateCaches(static_cast<uint32_t>(diff));
diff --git a/libs/androidfw/PosixUtils.cpp b/libs/androidfw/PosixUtils.cpp
index 8ddc572..49ee8f7 100644
--- a/libs/androidfw/PosixUtils.cpp
+++ b/libs/androidfw/PosixUtils.cpp
@@ -119,7 +119,7 @@
       auto err = ReadFile(stderr[0]);
       result.stderr_str = err ? std::move(*err) : "";
       close(stderr[0]);
-      return std::move(result);
+      return result;
   }
 }
 
diff --git a/libs/androidfw/include/androidfw/AssetManager2.h b/libs/androidfw/include/androidfw/AssetManager2.h
index ac46bc5..0fdeefa 100644
--- a/libs/androidfw/include/androidfw/AssetManager2.h
+++ b/libs/androidfw/include/androidfw/AssetManager2.h
@@ -32,6 +32,7 @@
 #include "androidfw/AssetManager.h"
 #include "androidfw/ResourceTypes.h"
 #include "androidfw/Util.h"
+#include "ftl/small_vector.h"
 
 namespace android {
 
@@ -159,9 +160,10 @@
 
   // Sets/resets the configuration for this AssetManager. This will cause all
   // caches that are related to the configuration change to be invalidated.
-  void SetConfigurations(std::vector<ResTable_config> configurations, bool force_refresh = false);
+  void SetConfigurations(std::span<const ResTable_config> configurations,
+                         bool force_refresh = false);
 
-  inline const std::vector<ResTable_config>& GetConfigurations() const {
+  std::span<const ResTable_config> GetConfigurations() const {
     return configurations_;
   }
 
@@ -470,13 +472,13 @@
 
   // An array mapping package ID to index into package_groups. This keeps the lookup fast
   // without taking too much memory.
-  std::array<uint8_t, std::numeric_limits<uint8_t>::max() + 1> package_ids_;
+  std::array<uint8_t, std::numeric_limits<uint8_t>::max() + 1> package_ids_ = {};
 
-  uint32_t default_locale_;
+  uint32_t default_locale_ = 0;
 
   // The current configurations set for this AssetManager. When this changes, cached resources
   // may need to be purged.
-  std::vector<ResTable_config> configurations_;
+  ftl::SmallVector<ResTable_config, 1> configurations_;
 
   // Cached set of bags. These are cached because they can inherit keys from parent bags,
   // which involves some calculation.
diff --git a/libs/androidfw/tests/AssetManager2_test.cpp b/libs/androidfw/tests/AssetManager2_test.cpp
index c62f095..3f22884 100644
--- a/libs/androidfw/tests/AssetManager2_test.cpp
+++ b/libs/androidfw/tests/AssetManager2_test.cpp
@@ -113,7 +113,7 @@
   desired_config.language[1] = 'e';
 
   AssetManager2 assetmanager;
-  assetmanager.SetConfigurations({desired_config});
+  assetmanager.SetConfigurations({{desired_config}});
   assetmanager.SetApkAssets({basic_assets_});
 
   auto value = assetmanager.GetResource(basic::R::string::test1);
@@ -137,7 +137,7 @@
   desired_config.language[1] = 'e';
 
   AssetManager2 assetmanager;
-  assetmanager.SetConfigurations({desired_config});
+  assetmanager.SetConfigurations({{desired_config}});
   assetmanager.SetApkAssets({basic_assets_, basic_de_fr_assets_});
 
   auto value = assetmanager.GetResource(basic::R::string::test1);
@@ -466,10 +466,10 @@
 TEST_F(AssetManager2Test, DensityOverride) {
   AssetManager2 assetmanager;
   assetmanager.SetApkAssets({basic_assets_, basic_xhdpi_assets_, basic_xxhdpi_assets_});
-  assetmanager.SetConfigurations({{
+  assetmanager.SetConfigurations({{{
     .density = ResTable_config::DENSITY_XHIGH,
     .sdkVersion = 21,
-  }});
+  }}});
 
   auto value = assetmanager.GetResource(basic::R::string::density, false /*may_be_bag*/);
   ASSERT_TRUE(value.has_value());
@@ -721,7 +721,7 @@
   ResTable_config desired_config;
 
   AssetManager2 assetmanager;
-  assetmanager.SetConfigurations({desired_config});
+  assetmanager.SetConfigurations({{desired_config}});
   assetmanager.SetApkAssets({basic_assets_});
   assetmanager.SetResourceResolutionLoggingEnabled(false);
 
@@ -736,7 +736,7 @@
   ResTable_config desired_config;
 
   AssetManager2 assetmanager;
-  assetmanager.SetConfigurations({desired_config});
+  assetmanager.SetConfigurations({{desired_config}});
   assetmanager.SetApkAssets({basic_assets_});
 
   auto result = assetmanager.GetLastResourceResolution();
@@ -751,7 +751,7 @@
 
   AssetManager2 assetmanager;
   assetmanager.SetResourceResolutionLoggingEnabled(true);
-  assetmanager.SetConfigurations({desired_config});
+  assetmanager.SetConfigurations({{desired_config}});
   assetmanager.SetApkAssets({basic_assets_});
 
   auto value = assetmanager.GetResource(basic::R::string::test1);
@@ -774,7 +774,7 @@
 
   AssetManager2 assetmanager;
   assetmanager.SetResourceResolutionLoggingEnabled(true);
-  assetmanager.SetConfigurations({desired_config});
+  assetmanager.SetConfigurations({{desired_config}});
   assetmanager.SetApkAssets({basic_assets_, basic_de_fr_assets_});
 
   auto value = assetmanager.GetResource(basic::R::string::test1);
@@ -796,7 +796,7 @@
 
   AssetManager2 assetmanager;
   assetmanager.SetResourceResolutionLoggingEnabled(true);
-  assetmanager.SetConfigurations({desired_config});
+  assetmanager.SetConfigurations({{desired_config}});
   assetmanager.SetApkAssets({basic_assets_});
 
   auto value = assetmanager.GetResource(basic::R::string::test1);
@@ -817,7 +817,7 @@
 
   AssetManager2 assetmanager;
   assetmanager.SetResourceResolutionLoggingEnabled(true);
-  assetmanager.SetConfigurations({desired_config});
+  assetmanager.SetConfigurations({{desired_config}});
   assetmanager.SetApkAssets({overlayable_assets_});
 
   const auto map = assetmanager.GetOverlayableMapForPackage(0x7f);
diff --git a/libs/androidfw/tests/BenchmarkHelpers.cpp b/libs/androidfw/tests/BenchmarkHelpers.cpp
index e3fc0a0..ec2abb8 100644
--- a/libs/androidfw/tests/BenchmarkHelpers.cpp
+++ b/libs/androidfw/tests/BenchmarkHelpers.cpp
@@ -66,7 +66,7 @@
   AssetManager2 assetmanager;
   assetmanager.SetApkAssets(apk_assets);
   if (config != nullptr) {
-    assetmanager.SetConfigurations({*config});
+    assetmanager.SetConfigurations({{{*config}}});
   }
 
   while (state.KeepRunning()) {
diff --git a/libs/androidfw/tests/Theme_test.cpp b/libs/androidfw/tests/Theme_test.cpp
index 181d141..afcb0c1 100644
--- a/libs/androidfw/tests/Theme_test.cpp
+++ b/libs/androidfw/tests/Theme_test.cpp
@@ -260,7 +260,7 @@
   ResTable_config night{};
   night.uiMode = ResTable_config::UI_MODE_NIGHT_YES;
   night.version = 8u;
-  am_night.SetConfigurations({night});
+  am_night.SetConfigurations({{night}});
 
   auto theme = am.NewTheme();
   {
diff --git a/libs/hwui/aconfig/hwui_flags.aconfig b/libs/hwui/aconfig/hwui_flags.aconfig
index a1f5168..faea6d4 100644
--- a/libs/hwui/aconfig/hwui_flags.aconfig
+++ b/libs/hwui/aconfig/hwui_flags.aconfig
@@ -2,6 +2,13 @@
 container: "system"
 
 flag {
+  name: "runtime_color_filters_blenders"
+  namespace: "core_graphics"
+  description: "API for AGSL authored runtime color filters and blenders"
+  bug: "358126864"
+}
+
+flag {
   name: "clip_shader"
   is_exported: true
   namespace: "core_graphics"
diff --git a/media/java/android/media/AudioManager.java b/media/java/android/media/AudioManager.java
index 25c767a..c36eda9 100644
--- a/media/java/android/media/AudioManager.java
+++ b/media/java/android/media/AudioManager.java
@@ -84,6 +84,7 @@
 import android.util.IntArray;
 import android.util.Log;
 import android.util.Pair;
+import android.util.Slog;
 import android.view.KeyEvent;
 
 import com.android.internal.annotations.GuardedBy;
@@ -4283,7 +4284,7 @@
                                     final OnAudioFocusChangeListener listener =
                                             fri.mRequest.getOnAudioFocusChangeListener();
                                     if (listener != null) {
-                                        Log.d(TAG, "dispatching onAudioFocusChange("
+                                        Slog.i(TAG, "dispatching onAudioFocusChange("
                                                 + msg.arg1 + ") to " + msg.obj);
                                         listener.onAudioFocusChange(msg.arg1);
                                     }
diff --git a/media/java/android/media/tv/ITvInputManager.aidl b/media/java/android/media/tv/ITvInputManager.aidl
index be93abb..87cb3e7 100644
--- a/media/java/android/media/tv/ITvInputManager.aidl
+++ b/media/java/android/media/tv/ITvInputManager.aidl
@@ -70,6 +70,7 @@
     void releaseSession(in IBinder sessionToken, int userId);
     int getClientPid(in String sessionId);
     int getClientPriority(int useCase, in String sessionId);
+    int getClientUserId(in String sessionId);
 
     void setMainSession(in IBinder sessionToken, int userId);
     void setSurface(in IBinder sessionToken, in Surface surface, int userId);
diff --git a/media/java/android/media/tv/TvInputManager.java b/media/java/android/media/tv/TvInputManager.java
index aed3e60e..25b6bfa 100644
--- a/media/java/android/media/tv/TvInputManager.java
+++ b/media/java/android/media/tv/TvInputManager.java
@@ -760,6 +760,14 @@
      * @hide
      */
     public static final int UNKNOWN_CLIENT_PID = -1;
+    /**
+     * An unknown state of the client userId gets from the TvInputManager. Client gets this value
+     * when query through {@link #getClientUserId(String sessionId)} fails.
+     *
+     * @hide
+     */
+    @FlaggedApi(Flags.FLAG_KIDS_MODE_TVDB_SHARING)
+    public static final int UNKNOWN_CLIENT_USER_ID = -1;
 
     /**
      * Broadcast intent action when the user blocked content ratings change. For use with the
@@ -2510,6 +2518,21 @@
     };
 
     /**
+     * Get a the client user id when creating the session with the session id provided.
+     *
+     * @param sessionId a String of session id that is used to query the client user id.
+     * @return the client pid when created the session. Returns {@link #UNKNOWN_CLIENT_USER_ID}
+     *         if the call fails.
+     *
+     * @hide
+     */
+    @RequiresPermission(android.Manifest.permission.TUNER_RESOURCE_ACCESS)
+    @FlaggedApi(Flags.FLAG_KIDS_MODE_TVDB_SHARING)
+    public int getClientUserId(@NonNull String sessionId) {
+        return getClientUserIdInternal(sessionId);
+    }
+
+    /**
      * Returns a priority for the given use case type and the client's foreground or background
      * status.
      *
@@ -2599,6 +2622,18 @@
         return clientPid;
     }
 
+    @FlaggedApi(Flags.FLAG_KIDS_MODE_TVDB_SHARING)
+    private int getClientUserIdInternal(String sessionId) {
+        Preconditions.checkNotNull(sessionId);
+        int clientUserId = UNKNOWN_CLIENT_USER_ID;
+        try {
+            clientUserId = mService.getClientUserId(sessionId);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+        return clientUserId;
+    }
+
     private int getClientPriorityInternal(int useCase, String sessionId) {
         try {
             return mService.getClientPriority(useCase, sessionId);
diff --git a/media/java/android/media/tv/flags/media_tv.aconfig b/media/java/android/media/tv/flags/media_tv.aconfig
index 3196ba1..d6e9e4e 100644
--- a/media/java/android/media/tv/flags/media_tv.aconfig
+++ b/media/java/android/media/tv/flags/media_tv.aconfig
@@ -31,4 +31,12 @@
     namespace: "media_tv"
     description: "Introduce ALWAYS_BOUND_TV_INPUT for TIS."
     bug: "332201346"
+}
+
+flag {
+    name: "kids_mode_tvdb_sharing"
+    is_exported: true
+    namespace: "media_tv"
+    description: "Performance and Storage Optimization in Google TV Kids Mode."
+    bug: "288383796"
 }
\ No newline at end of file
diff --git a/media/jni/android_media_MediaCodecLinearBlock.h b/media/jni/android_media_MediaCodecLinearBlock.h
index 060abfd..ffbf0a8 100644
--- a/media/jni/android_media_MediaCodecLinearBlock.h
+++ b/media/jni/android_media_MediaCodecLinearBlock.h
@@ -62,7 +62,7 @@
             std::shared_ptr<C2Buffer> buffer =
                 C2Buffer::CreateLinearBuffer(block.subBlock(offset, size));
             for (const std::shared_ptr<const C2Info> &info : mBuffer->info()) {
-                std::shared_ptr<C2Param> param = std::move(C2Param::Copy(*info));
+                std::shared_ptr<C2Param> param = C2Param::Copy(*info);
                 buffer->setInfo(std::static_pointer_cast<C2Info>(param));
             }
             return buffer;
diff --git a/nfc/api/current.txt b/nfc/api/current.txt
index b0d1f71..447e980 100644
--- a/nfc/api/current.txt
+++ b/nfc/api/current.txt
@@ -220,11 +220,14 @@
     field @Deprecated public static final String ACTION_CHANGE_DEFAULT = "android.nfc.cardemulation.action.ACTION_CHANGE_DEFAULT";
     field public static final String CATEGORY_OTHER = "other";
     field public static final String CATEGORY_PAYMENT = "payment";
+    field @FlaggedApi("android.nfc.nfc_override_recover_routing_table") public static final String DH = "DH";
+    field @FlaggedApi("android.nfc.nfc_override_recover_routing_table") public static final String ESE = "ESE";
     field public static final String EXTRA_CATEGORY = "category";
     field public static final String EXTRA_SERVICE_COMPONENT = "component";
     field public static final int SELECTION_MODE_ALWAYS_ASK = 1; // 0x1
     field public static final int SELECTION_MODE_ASK_IF_CONFLICT = 2; // 0x2
     field public static final int SELECTION_MODE_PREFER_DEFAULT = 0; // 0x0
+    field @FlaggedApi("android.nfc.nfc_override_recover_routing_table") public static final String UICC = "UICC";
   }
 
   public abstract class HostApduService extends android.app.Service {
diff --git a/nfc/api/system-current.txt b/nfc/api/system-current.txt
index 3375e18c..2ff9829 100644
--- a/nfc/api/system-current.txt
+++ b/nfc/api/system-current.txt
@@ -57,6 +57,7 @@
 
   @FlaggedApi("android.nfc.nfc_oem_extension") public final class NfcOemExtension {
     method @FlaggedApi("android.nfc.nfc_oem_extension") @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public void clearPreference();
+    method @FlaggedApi("android.nfc.nfc_oem_extension") @NonNull public java.util.List<java.lang.String> getActiveNfceeList();
     method @FlaggedApi("android.nfc.nfc_oem_extension") @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public void maybeTriggerFirmwareUpdate();
     method @FlaggedApi("android.nfc.nfc_oem_extension") @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public void registerCallback(@NonNull java.util.concurrent.Executor, @NonNull android.nfc.NfcOemExtension.Callback);
     method @FlaggedApi("android.nfc.nfc_oem_extension") @RequiresPermission(android.Manifest.permission.WRITE_SECURE_SETTINGS) public void synchronizeScreenState();
@@ -74,6 +75,8 @@
   public final class CardEmulation {
     method @FlaggedApi("android.permission.flags.wallet_role_enabled") @Nullable @RequiresPermission(android.Manifest.permission.NFC_PREFERRED_PAYMENT_INFO) public static android.content.ComponentName getPreferredPaymentService(@NonNull android.content.Context);
     method @FlaggedApi("android.nfc.enable_nfc_mainline") @NonNull public java.util.List<android.nfc.cardemulation.ApduServiceInfo> getServices(@NonNull String, int);
+    method @FlaggedApi("android.nfc.nfc_override_recover_routing_table") public void overrideRoutingTable(@NonNull android.app.Activity, @Nullable String, @Nullable String);
+    method @FlaggedApi("android.nfc.nfc_override_recover_routing_table") public void recoverRoutingTable(@NonNull android.app.Activity);
   }
 
 }
diff --git a/nfc/java/android/nfc/INfcAdapter.aidl b/nfc/java/android/nfc/INfcAdapter.aidl
index c5b82ed..6c0f933 100644
--- a/nfc/java/android/nfc/INfcAdapter.aidl
+++ b/nfc/java/android/nfc/INfcAdapter.aidl
@@ -113,4 +113,5 @@
     void clearPreference();
     void setScreenState();
     void checkFirmware();
+    List<String> fetchActiveNfceeList();
 }
diff --git a/nfc/java/android/nfc/INfcCardEmulation.aidl b/nfc/java/android/nfc/INfcCardEmulation.aidl
index cb97f23..79f1275 100644
--- a/nfc/java/android/nfc/INfcCardEmulation.aidl
+++ b/nfc/java/android/nfc/INfcCardEmulation.aidl
@@ -48,6 +48,6 @@
     boolean setServiceEnabledForCategoryOther(int userHandle, in ComponentName app, boolean status);
     boolean isDefaultPaymentRegistered();
 
-    boolean overrideRoutingTable(int userHandle, String protocol, String technology);
-    boolean recoverRoutingTable(int userHandle);
+    void overrideRoutingTable(int userHandle, String protocol, String technology, in String pkg);
+    void recoverRoutingTable(int userHandle);
 }
diff --git a/nfc/java/android/nfc/NfcOemExtension.java b/nfc/java/android/nfc/NfcOemExtension.java
index 2ec819c..204ba9f 100644
--- a/nfc/java/android/nfc/NfcOemExtension.java
+++ b/nfc/java/android/nfc/NfcOemExtension.java
@@ -26,6 +26,8 @@
 import android.os.RemoteException;
 import android.util.Log;
 
+import java.util.ArrayList;
+import java.util.List;
 import java.util.concurrent.Executor;
 
 /**
@@ -153,6 +155,19 @@
         NfcAdapter.callService(() -> NfcAdapter.sService.checkFirmware());
     }
 
+    /**
+     * Get the Active NFCEE (NFC Execution Environment) List
+     *
+     * @return List of activated secure elements on success
+     *         which can contain "eSE" and "UICC", otherwise empty list.
+     */
+    @NonNull
+    @FlaggedApi(Flags.FLAG_NFC_OEM_EXTENSION)
+    public List<String> getActiveNfceeList() {
+        return NfcAdapter.callServiceReturn(() ->
+            NfcAdapter.sService.fetchActiveNfceeList(), new ArrayList<String>());
+    }
+
     private final class NfcOemExtensionCallback extends INfcOemExtensionCallback.Stub {
         @Override
         public void onTagConnected(boolean connected, Tag tag) throws RemoteException {
diff --git a/nfc/java/android/nfc/cardemulation/CardEmulation.java b/nfc/java/android/nfc/cardemulation/CardEmulation.java
index e0438ce..0605dbe 100644
--- a/nfc/java/android/nfc/cardemulation/CardEmulation.java
+++ b/nfc/java/android/nfc/cardemulation/CardEmulation.java
@@ -23,6 +23,7 @@
 import android.annotation.RequiresPermission;
 import android.annotation.SdkConstant;
 import android.annotation.SdkConstant.SdkConstantType;
+import android.annotation.StringDef;
 import android.annotation.SystemApi;
 import android.annotation.UserHandleAware;
 import android.annotation.UserIdInt;
@@ -43,6 +44,8 @@
 import android.provider.Settings.SettingNotFoundException;
 import android.util.Log;
 
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
 import java.util.HashMap;
 import java.util.HexFormat;
 import java.util.List;
@@ -148,6 +151,21 @@
      *    that service will be invoked directly.
      */
     public static final int SELECTION_MODE_ASK_IF_CONFLICT = 2;
+    /**
+     * Route to Device Host (DH).
+     */
+    @FlaggedApi(Flags.FLAG_NFC_OVERRIDE_RECOVER_ROUTING_TABLE)
+    public static final String DH = "DH";
+    /**
+     * Route to eSE.
+     */
+    @FlaggedApi(Flags.FLAG_NFC_OVERRIDE_RECOVER_ROUTING_TABLE)
+    public static final String ESE = "ESE";
+    /**
+     * Route to UICC.
+     */
+    @FlaggedApi(Flags.FLAG_NFC_OVERRIDE_RECOVER_ROUTING_TABLE)
+    public static final String UICC = "UICC";
 
     static boolean sIsInitialized = false;
     static HashMap<Context, CardEmulation> sCardEmus = new HashMap<Context, CardEmulation>();
@@ -865,11 +883,22 @@
                 sService.setServiceEnabledForCategoryOther(userId, service, status), false);
     }
 
+    /** @hide */
+    @StringDef({
+        DH,
+        ESE,
+        UICC
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface ProtocolAndTechnologyRoute {}
+
      /**
       * Setting NFC controller routing table, which includes Protocol Route and Technology Route,
       * while this Activity is in the foreground.
       *
-      * The parameter set to null can be used to keep current values for that entry.
+      * The parameter set to null can be used to keep current values for that entry. Either
+      * Protocol Route or Technology Route should be override when calling this API, otherwise
+      * throw {@link IllegalArgumentException}.
       * <p>
       * Example usage in an Activity that requires to set proto route to "ESE" and keep tech route:
       * <pre>
@@ -877,26 +906,40 @@
       *     mNfcAdapter.overrideRoutingTable(this , "ESE" , null);
       * }</pre>
       * </p>
-      * Also activities must call this method when it goes to the background,
-      * with all parameters set to null.
+      * Also activities must call {@link #recoverRoutingTable(Activity)}
+      * when it goes to the background. Only the package of the
+      * currently preferred service (the service set as preferred by the current foreground
+      * application via {@link CardEmulation#setPreferredService(Activity, ComponentName)} or the
+      * current Default Wallet Role Holder {@link android.app.role.RoleManager#ROLE_WALLET}),
+      * otherwise a call to this method will fail and throw {@link SecurityException}.
       * @param activity The Activity that requests NFC controller routing table to be changed.
       * @param protocol ISO-DEP route destination, which can be "DH" or "UICC" or "ESE".
-      * @param technology Tech-A, Tech-B route destination, which can be "DH" or "UICC" or "ESE".
-      * @return true if operation is successful and false otherwise
-      *
+      * @param technology Tech-A, Tech-B and Tech-F route destination, which can be "DH" or "UICC"
+      * or "ESE".
+      * @throws SecurityException if the caller is not the preferred NFC service
+      * @throws IllegalArgumentException if the activity is not resumed or the caller is not in the
+      * foreground, or both protocol route and technology route are null.
+      * <p>
       * This is a high risk API and only included to support mainline effort
       * @hide
       */
-    public boolean overrideRoutingTable(Activity activity, String protocol, String technology) {
-        if (activity == null) {
-            throw new NullPointerException("activity or service or category is null");
-        }
+    @SystemApi
+    @FlaggedApi(Flags.FLAG_NFC_OVERRIDE_RECOVER_ROUTING_TABLE)
+    public void overrideRoutingTable(
+            @NonNull Activity activity, @ProtocolAndTechnologyRoute @Nullable String protocol,
+            @ProtocolAndTechnologyRoute @Nullable String technology) {
         if (!activity.isResumed()) {
             throw new IllegalArgumentException("Activity must be resumed.");
         }
-        return callServiceReturn(() ->
+        if (protocol == null && technology == null) {
+            throw new IllegalArgumentException(("Both Protocol and Technology are null."));
+        }
+        callService(() ->
                 sService.overrideRoutingTable(
-                    mContext.getUser().getIdentifier(), protocol, technology), false);
+                    mContext.getUser().getIdentifier(),
+                    protocol,
+                    technology,
+                    mContext.getPackageName()));
     }
 
     /**
@@ -904,20 +947,19 @@
      * which was changed by {@link #overrideRoutingTable(Activity, String, String)}
      *
      * @param activity The Activity that requested NFC controller routing table to be changed.
-     * @return true if operation is successful and false otherwise
+     * @throws IllegalArgumentException if the caller is not in the foreground.
      *
      * @hide
      */
-    public boolean recoverRoutingTable(Activity activity) {
-        if (activity == null) {
-            throw new NullPointerException("activity is null");
-        }
+    @SystemApi
+    @FlaggedApi(Flags.FLAG_NFC_OVERRIDE_RECOVER_ROUTING_TABLE)
+    public void recoverRoutingTable(@NonNull Activity activity) {
         if (!activity.isResumed()) {
             throw new IllegalArgumentException("Activity must be resumed.");
         }
-        return callServiceReturn(() ->
+        callService(() ->
                 sService.recoverRoutingTable(
-                    mContext.getUser().getIdentifier()), false);
+                    mContext.getUser().getIdentifier()));
     }
 
     /**
diff --git a/nfc/java/android/nfc/flags.aconfig b/nfc/java/android/nfc/flags.aconfig
index 5819b98..0fda91d 100644
--- a/nfc/java/android/nfc/flags.aconfig
+++ b/nfc/java/android/nfc/flags.aconfig
@@ -133,3 +133,11 @@
     description: "Add Settings.ACTION_MANAGE_OTHER_NFC_SERVICES_SETTINGS"
     bug: "358129872"
 }
+
+flag {
+    name: "nfc_override_recover_routing_table"
+    is_exported: true
+    namespace: "nfc"
+    description: "Enable override and recover routing table"
+    bug: "329043523"
+}
diff --git a/packages/ExternalStorageProvider/TEST_MAPPING b/packages/ExternalStorageProvider/TEST_MAPPING
new file mode 100644
index 0000000..dfa0c84
--- /dev/null
+++ b/packages/ExternalStorageProvider/TEST_MAPPING
@@ -0,0 +1,15 @@
+{
+  "postsubmit": [
+    {
+      "name": "ExternalStorageProviderTests",
+      "options": [
+        {
+          "exclude-annotation": "org.junit.Ignore"
+        },
+        {
+          "exclude-annotation": "androidx.test.filters.FlakyTest"
+        }
+      ]
+    }
+  ]
+}
diff --git a/packages/PackageInstaller/TEST_MAPPING b/packages/PackageInstaller/TEST_MAPPING
index ff83610..da6efee 100644
--- a/packages/PackageInstaller/TEST_MAPPING
+++ b/packages/PackageInstaller/TEST_MAPPING
@@ -30,14 +30,47 @@
       "name": "CtsIntentSignatureTestCases"
     },
     {
-      "name": "CtsPackageInstallerCUJTestCases",
+      "name": "CtsPackageInstallerCUJInstallationTestCases",
       "options":[
-          {
-              "exclude-annotation":"androidx.test.filters.FlakyTest"
-          },
-          {
-              "exclude-annotation":"org.junit.Ignore"
-          }
+        {
+          "exclude-annotation":"androidx.test.filters.FlakyTest"
+        },
+        {
+          "exclude-annotation":"org.junit.Ignore"
+        }
+      ]
+    },
+    {
+      "name": "CtsPackageInstallerCUJUninstallationTestCases",
+      "options":[
+        {
+          "exclude-annotation":"androidx.test.filters.FlakyTest"
+        },
+        {
+          "exclude-annotation":"org.junit.Ignore"
+        }
+      ]
+    },
+    {
+      "name": "CtsPackageInstallerCUJUpdateOwnerShipTestCases",
+      "options":[
+        {
+          "exclude-annotation":"androidx.test.filters.FlakyTest"
+        },
+        {
+          "exclude-annotation":"org.junit.Ignore"
+        }
+      ]
+    },
+    {
+      "name": "CtsPackageInstallerCUJUpdateSelfTestCases",
+      "options":[
+        {
+          "exclude-annotation":"androidx.test.filters.FlakyTest"
+        },
+        {
+          "exclude-annotation":"org.junit.Ignore"
+        }
       ]
     }
   ]
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/UnarchiveErrorFragment.java b/packages/PackageInstaller/src/com/android/packageinstaller/UnarchiveErrorFragment.java
index d33433f..2fb32a7 100644
--- a/packages/PackageInstaller/src/com/android/packageinstaller/UnarchiveErrorFragment.java
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/UnarchiveErrorFragment.java
@@ -16,10 +16,12 @@
 
 package com.android.packageinstaller;
 
+import static android.app.ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED;
 import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
 
 import android.app.Activity;
 import android.app.AlertDialog;
+import android.app.BroadcastOptions;
 import android.app.Dialog;
 import android.app.DialogFragment;
 import android.app.PendingIntent;
@@ -161,25 +163,31 @@
             return;
         }
 
+        // Allow the error handling actvities to start in the background.
+        final BroadcastOptions options = BroadcastOptions.makeBasic();
+        options.setPendingIntentBackgroundActivityStartMode(
+                MODE_BACKGROUND_ACTIVITY_START_ALLOWED);
         switch (mStatus) {
             case PackageInstaller.UNARCHIVAL_ERROR_USER_ACTION_NEEDED:
                 activity.startIntentSender(mExtraIntent.getIntentSender(), /* fillInIntent= */
-                        null, /* flagsMask= */ 0, FLAG_ACTIVITY_NEW_TASK, /* extraFlags= */ 0);
+                        null, /* flagsMask= */ 0, FLAG_ACTIVITY_NEW_TASK, /* extraFlags= */ 0,
+                        options.toBundle());
                 break;
             case PackageInstaller.UNARCHIVAL_ERROR_INSUFFICIENT_STORAGE:
                 if (mExtraIntent != null) {
                     activity.startIntentSender(mExtraIntent.getIntentSender(), /* fillInIntent= */
-                            null, /* flagsMask= */ 0, FLAG_ACTIVITY_NEW_TASK, /* extraFlags= */ 0);
+                            null, /* flagsMask= */ 0, FLAG_ACTIVITY_NEW_TASK, /* extraFlags= */ 0,
+                            options.toBundle());
                 } else {
                     Intent intent = new Intent("android.intent.action.MANAGE_PACKAGE_STORAGE");
-                    startActivity(intent);
+                    startActivity(intent, options.toBundle());
                 }
                 break;
             case PackageInstaller.UNARCHIVAL_ERROR_INSTALLER_DISABLED:
                 Intent intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS);
                 Uri uri = Uri.fromParts("package", mInstallerPackageName, null);
                 intent.setData(uri);
-                startActivity(intent);
+                startActivity(intent, options.toBundle());
                 break;
             default:
                 // Do nothing. The rest of the dialogs are purely informational.
diff --git a/packages/SettingsLib/SettingsSpinner/res/layout/settings_spinner_preference.xml b/packages/SettingsLib/SettingsSpinner/res/layout/settings_spinner_preference.xml
index 4c75344..526ce14 100644
--- a/packages/SettingsLib/SettingsSpinner/res/layout/settings_spinner_preference.xml
+++ b/packages/SettingsLib/SettingsSpinner/res/layout/settings_spinner_preference.xml
@@ -19,8 +19,8 @@
     android:layout_width="match_parent"
     android:layout_height="wrap_content"
     android:minHeight="?android:attr/listPreferredItemHeight"
-    android:layout_marginStart="16dp"
-    android:layout_marginEnd="16dp">
+    android:layout_marginStart="?android:attr/listPreferredItemPaddingStart"
+    android:layout_marginEnd="?android:attr/listPreferredItemPaddingEnd">
 
     <Spinner
         android:id="@+id/spinner"
diff --git a/packages/SettingsLib/Spa/build.gradle.kts b/packages/SettingsLib/Spa/build.gradle.kts
index f36344a..a543450 100644
--- a/packages/SettingsLib/Spa/build.gradle.kts
+++ b/packages/SettingsLib/Spa/build.gradle.kts
@@ -29,7 +29,7 @@
 
 allprojects {
     extra["androidTop"] = androidTop
-    extra["jetpackComposeVersion"] = "1.7.0-beta05"
+    extra["jetpackComposeVersion"] = "1.7.0-beta07"
 }
 
 subprojects {
@@ -37,7 +37,7 @@
 
     plugins.withType<AndroidBasePlugin> {
         configure<BaseExtension> {
-            compileSdkVersion(34)
+            compileSdkVersion(35)
 
             defaultConfig {
                 minSdk = 21
diff --git a/packages/SettingsLib/Spa/gradle/libs.versions.toml b/packages/SettingsLib/Spa/gradle/libs.versions.toml
index 1cca73a..3507605 100644
--- a/packages/SettingsLib/Spa/gradle/libs.versions.toml
+++ b/packages/SettingsLib/Spa/gradle/libs.versions.toml
@@ -15,7 +15,7 @@
 #
 
 [versions]
-agp = "8.5.1"
+agp = "8.5.2"
 compose-compiler = "1.5.11"
 dexmaker-mockito = "2.28.3"
 jvm = "17"
diff --git a/packages/SettingsLib/Spa/spa/build.gradle.kts b/packages/SettingsLib/Spa/spa/build.gradle.kts
index ce3d96e..e9153e3 100644
--- a/packages/SettingsLib/Spa/spa/build.gradle.kts
+++ b/packages/SettingsLib/Spa/spa/build.gradle.kts
@@ -54,13 +54,13 @@
 dependencies {
     api(project(":SettingsLibColor"))
     api("androidx.appcompat:appcompat:1.7.0")
-    api("androidx.compose.material3:material3:1.3.0-beta04")
+    api("androidx.compose.material3:material3:1.3.0-beta05")
     api("androidx.compose.material:material-icons-extended:$jetpackComposeVersion")
     api("androidx.compose.runtime:runtime-livedata:$jetpackComposeVersion")
     api("androidx.compose.ui:ui-tooling-preview:$jetpackComposeVersion")
     api("androidx.lifecycle:lifecycle-livedata-ktx")
     api("androidx.lifecycle:lifecycle-runtime-compose")
-    api("androidx.navigation:navigation-compose:2.8.0-beta05")
+    api("androidx.navigation:navigation-compose:2.8.0-beta07")
     api("com.github.PhilJay:MPAndroidChart:v3.1.0-alpha")
     api("com.google.android.material:material:1.11.0")
     debugApi("androidx.compose.ui:ui-tooling:$jetpackComposeVersion")
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothUtils.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothUtils.java
index de60fdc2..b3ac54a 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothUtils.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothUtils.java
@@ -21,6 +21,8 @@
 import android.graphics.Canvas;
 import android.graphics.drawable.BitmapDrawable;
 import android.graphics.drawable.Drawable;
+import android.media.AudioDeviceAttributes;
+import android.media.AudioDeviceInfo;
 import android.media.AudioManager;
 import android.net.Uri;
 import android.provider.DeviceConfig;
@@ -41,9 +43,12 @@
 import com.android.settingslib.widget.AdaptiveIcon;
 import com.android.settingslib.widget.AdaptiveOutlineDrawable;
 
+import com.google.common.collect.ImmutableSet;
+
 import java.io.IOException;
 import java.util.List;
 import java.util.Locale;
+import java.util.Set;
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
 
@@ -57,6 +62,9 @@
     public static final String BT_ADVANCED_HEADER_ENABLED = "bt_advanced_header_enabled";
     private static final int METADATA_FAST_PAIR_CUSTOMIZED_FIELDS = 25;
     private static final String KEY_HEARABLE_CONTROL_SLICE = "HEARABLE_CONTROL_SLICE_WITH_WIDTH";
+    private static final Set<Integer> SA_PROFILES =
+            ImmutableSet.of(
+                    BluetoothProfile.A2DP, BluetoothProfile.LE_AUDIO, BluetoothProfile.HEARING_AID);
 
     private static ErrorListener sErrorListener;
 
@@ -895,4 +903,62 @@
         }
         return null;
     }
+
+    /**
+     * Gets {@link AudioDeviceAttributes} of bluetooth device for spatial audio. Returns null if
+     * it's not an audio device(no A2DP, LE Audio and Hearing Aid profile).
+     */
+    @Nullable
+    public static AudioDeviceAttributes getAudioDeviceAttributesForSpatialAudio(
+            CachedBluetoothDevice cachedDevice,
+            @AudioManager.AudioDeviceCategory int audioDeviceCategory) {
+        AudioDeviceAttributes saDevice = null;
+        for (LocalBluetoothProfile profile : cachedDevice.getProfiles()) {
+            // pick first enabled profile that is compatible with spatial audio
+            if (SA_PROFILES.contains(profile.getProfileId())
+                    && profile.isEnabled(cachedDevice.getDevice())) {
+                switch (profile.getProfileId()) {
+                    case BluetoothProfile.A2DP:
+                        saDevice =
+                                new AudioDeviceAttributes(
+                                        AudioDeviceAttributes.ROLE_OUTPUT,
+                                        AudioDeviceInfo.TYPE_BLUETOOTH_A2DP,
+                                        cachedDevice.getAddress());
+                        break;
+                    case BluetoothProfile.LE_AUDIO:
+                        if (audioDeviceCategory
+                                == AudioManager.AUDIO_DEVICE_CATEGORY_SPEAKER) {
+                            saDevice =
+                                    new AudioDeviceAttributes(
+                                            AudioDeviceAttributes.ROLE_OUTPUT,
+                                            AudioDeviceInfo.TYPE_BLE_SPEAKER,
+                                            cachedDevice.getAddress());
+                        } else {
+                            saDevice =
+                                    new AudioDeviceAttributes(
+                                            AudioDeviceAttributes.ROLE_OUTPUT,
+                                            AudioDeviceInfo.TYPE_BLE_HEADSET,
+                                            cachedDevice.getAddress());
+                        }
+
+                        break;
+                    case BluetoothProfile.HEARING_AID:
+                        saDevice =
+                                new AudioDeviceAttributes(
+                                        AudioDeviceAttributes.ROLE_OUTPUT,
+                                        AudioDeviceInfo.TYPE_HEARING_AID,
+                                        cachedDevice.getAddress());
+                        break;
+                    default:
+                        Log.i(
+                                TAG,
+                                "unrecognized profile for spatial audio: "
+                                        + profile.getProfileId());
+                        break;
+                }
+                break;
+            }
+        }
+        return saDevice;
+    }
 }
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/DeviceSettingId.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/DeviceSettingId.java
index 20a0339..58dc8c7 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/DeviceSettingId.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/DeviceSettingId.java
@@ -108,6 +108,12 @@
     /** Device setting ID for device details footer. */
     int DEVICE_SETTING_ID_DEVICE_DETAILS_FOOTER = 19;
 
+    /** Device setting ID for spatial audio group. */
+    int DEVICE_SETTING_ID_SPATIAL_AUDIO_MULTI_TOGGLE = 20;
+
+    /** Device setting ID for "More Settings" page. */
+    int DEVICE_SETTING_ID_MORE_SETTINGS = 21;
+
     /** Device setting ID for ANC. */
     int DEVICE_SETTING_ID_ANC = 1001;
 }
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/DeviceSettingItem.kt b/packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/DeviceSettingItem.kt
index 9ee33b0..a0fe5d2 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/DeviceSettingItem.kt
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/DeviceSettingItem.kt
@@ -27,6 +27,7 @@
  * @property packageName The package name for service binding.
  * @property className The class name for service binding.
  * @property intentAction The intent action for service binding.
+ * @property preferenceKey The preference key if it's a built-in preference.
  * @property extras Extra bundle
  */
 data class DeviceSettingItem(
@@ -34,6 +35,7 @@
     val packageName: String,
     val className: String,
     val intentAction: String,
+    val preferenceKey: String? = null,
     val extras: Bundle = Bundle.EMPTY,
 ) : Parcelable {
 
@@ -45,6 +47,7 @@
             writeString(packageName)
             writeString(className)
             writeString(intentAction)
+            writeString(preferenceKey)
             writeBundle(extras)
         }
     }
@@ -60,6 +63,7 @@
                             packageName = readString() ?: "",
                             className = readString() ?: "",
                             intentAction = readString() ?: "",
+                            preferenceKey = readString() ?: "",
                             extras = readBundle((Bundle::class.java.classLoader)) ?: Bundle.EMPTY,
                         )
                     }
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/data/repository/DeviceSettingRepository.kt b/packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/data/repository/DeviceSettingRepository.kt
index 326bb31..ce7064c 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/data/repository/DeviceSettingRepository.kt
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/data/repository/DeviceSettingRepository.kt
@@ -18,6 +18,7 @@
 
 import android.bluetooth.BluetoothAdapter
 import android.content.Context
+import android.text.TextUtils
 import com.android.settingslib.bluetooth.CachedBluetoothDevice
 import com.android.settingslib.bluetooth.devicesettings.ActionSwitchPreference
 import com.android.settingslib.bluetooth.devicesettings.DeviceSetting
@@ -28,6 +29,7 @@
 import com.android.settingslib.bluetooth.devicesettings.ToggleInfo
 import com.android.settingslib.bluetooth.devicesettings.shared.model.DeviceSettingConfigItemModel
 import com.android.settingslib.bluetooth.devicesettings.shared.model.DeviceSettingConfigModel
+import com.android.settingslib.bluetooth.devicesettings.shared.model.DeviceSettingIcon
 import com.android.settingslib.bluetooth.devicesettings.shared.model.DeviceSettingModel
 import com.android.settingslib.bluetooth.devicesettings.shared.model.DeviceSettingStateModel
 import com.android.settingslib.bluetooth.devicesettings.shared.model.ToggleModel
@@ -76,8 +78,7 @@
                             coroutineScope,
                             backgroundCoroutineContext,
                         )
-                }
-            )
+                })
 
     override suspend fun getDeviceSettingsConfig(
         cachedDevice: CachedBluetoothDevice
@@ -96,11 +97,15 @@
         DeviceSettingConfigModel(
             mainItems = mainContentItems.map { it.toModel() },
             moreSettingsItems = moreSettingsItems.map { it.toModel() },
-            moreSettingsPageFooter = moreSettingsFooter
-        )
+            moreSettingsPageFooter = moreSettingsFooter)
 
-    private fun DeviceSettingItem.toModel(): DeviceSettingConfigItemModel =
-        DeviceSettingConfigItemModel(settingId)
+    private fun DeviceSettingItem.toModel(): DeviceSettingConfigItemModel {
+        return if (!TextUtils.isEmpty(preferenceKey)) {
+            DeviceSettingConfigItemModel.BuiltinItem(settingId, preferenceKey!!)
+        } else {
+            DeviceSettingConfigItemModel.AppProvidedItem(settingId)
+        }
+    }
 
     private fun DeviceSetting.toModel(
         cachedDevice: CachedBluetoothDevice,
@@ -113,7 +118,7 @@
                     id = settingId,
                     title = pref.title,
                     summary = pref.summary,
-                    icon = pref.icon,
+                    icon = pref.icon?.let { DeviceSettingIcon.BitmapIcon(it) },
                     isAllowedChangingState = pref.isAllowedChangingState,
                     intent = pref.intent,
                     switchState =
@@ -149,5 +154,6 @@
             else -> DeviceSettingModel.Unknown(cachedDevice, settingId)
         }
 
-    private fun ToggleInfo.toModel(): ToggleModel = ToggleModel(label, icon)
+    private fun ToggleInfo.toModel(): ToggleModel =
+        ToggleModel(label, DeviceSettingIcon.BitmapIcon(icon))
 }
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/shared/model/DeviceSettingConfigModel.kt b/packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/shared/model/DeviceSettingConfigModel.kt
index cd597ee..e97f76c 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/shared/model/DeviceSettingConfigModel.kt
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/shared/model/DeviceSettingConfigModel.kt
@@ -25,9 +25,20 @@
     /** Items need to be shown in device details more settings page. */
     val moreSettingsItems: List<DeviceSettingConfigItemModel>,
     /** Footer text in more settings page. */
-    val moreSettingsPageFooter: String)
+    val moreSettingsPageFooter: String
+)
 
 /** Models a device setting item in config. */
-data class DeviceSettingConfigItemModel(
-    @DeviceSettingId val settingId: Int,
-)
+sealed interface DeviceSettingConfigItemModel {
+    @DeviceSettingId val settingId: Int
+
+    /** A built-in item in Settings. */
+    data class BuiltinItem(
+        @DeviceSettingId override val settingId: Int,
+        val preferenceKey: String?
+    ) : DeviceSettingConfigItemModel
+
+    /** A remote item provided by other apps. */
+    data class AppProvidedItem(@DeviceSettingId override val settingId: Int) :
+        DeviceSettingConfigItemModel
+}
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/shared/model/DeviceSettingModel.kt b/packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/shared/model/DeviceSettingModel.kt
index db78280..2a63217 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/shared/model/DeviceSettingModel.kt
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/devicesettings/shared/model/DeviceSettingModel.kt
@@ -18,6 +18,7 @@
 
 import android.content.Intent
 import android.graphics.Bitmap
+import androidx.annotation.DrawableRes
 import com.android.settingslib.bluetooth.CachedBluetoothDevice
 import com.android.settingslib.bluetooth.devicesettings.DeviceSettingId
 
@@ -32,7 +33,7 @@
         @DeviceSettingId override val id: Int,
         val title: String,
         val summary: String? = null,
-        val icon: Bitmap? = null,
+        val icon: DeviceSettingIcon? = null,
         val intent: Intent? = null,
         val switchState: DeviceSettingStateModel.ActionSwitchPreferenceState? = null,
         val isAllowedChangingState: Boolean = true,
@@ -59,4 +60,12 @@
 }
 
 /** Models a toggle in [DeviceSettingModel.MultiTogglePreference]. */
-data class ToggleModel(val label: String, val icon: Bitmap)
+data class ToggleModel(val label: String, val icon: DeviceSettingIcon)
+
+/** Models an icon in device settings. */
+sealed interface DeviceSettingIcon {
+
+    data class BitmapIcon(val bitmap: Bitmap) : DeviceSettingIcon
+
+    data class ResourceIcon(@DrawableRes val resId: Int) : DeviceSettingIcon
+}
diff --git a/packages/SettingsLib/src/com/android/settingslib/notification/modes/ZenMode.java b/packages/SettingsLib/src/com/android/settingslib/notification/modes/ZenMode.java
index 4b141e7..0d4ce5b 100644
--- a/packages/SettingsLib/src/com/android/settingslib/notification/modes/ZenMode.java
+++ b/packages/SettingsLib/src/com/android/settingslib/notification/modes/ZenMode.java
@@ -22,6 +22,7 @@
 import static android.app.NotificationManager.INTERRUPTION_FILTER_PRIORITY;
 import static android.service.notification.SystemZenRules.getTriggerDescriptionForScheduleEvent;
 import static android.service.notification.SystemZenRules.getTriggerDescriptionForScheduleTime;
+import static android.service.notification.SystemZenRules.PACKAGE_ANDROID;
 import static android.service.notification.ZenModeConfig.tryParseCountdownConditionId;
 import static android.service.notification.ZenModeConfig.tryParseEventConditionId;
 import static android.service.notification.ZenModeConfig.tryParseScheduleConditionId;
@@ -52,9 +53,11 @@
 import com.android.settingslib.R;
 
 import com.google.common.base.Strings;
+import com.google.common.collect.ImmutableList;
 import com.google.common.util.concurrent.Futures;
 import com.google.common.util.concurrent.ListenableFuture;
 
+import java.util.Comparator;
 import java.util.Objects;
 
 /**
@@ -87,6 +90,33 @@
                     .allowPriorityChannels(false)
                     .build();
 
+    private static final Comparator<Integer> PRIORITIZED_TYPE_COMPARATOR = new Comparator<>() {
+
+        private static final ImmutableList</* @AutomaticZenRule.Type */ Integer>
+                PRIORITIZED_TYPES = ImmutableList.of(
+                        AutomaticZenRule.TYPE_BEDTIME,
+                        AutomaticZenRule.TYPE_DRIVING);
+
+        @Override
+        public int compare(Integer first, Integer second) {
+            if (PRIORITIZED_TYPES.contains(first) && PRIORITIZED_TYPES.contains(second)) {
+                return PRIORITIZED_TYPES.indexOf(first) - PRIORITIZED_TYPES.indexOf(second);
+            } else if (PRIORITIZED_TYPES.contains(first)) {
+                return -1;
+            } else if (PRIORITIZED_TYPES.contains(second)) {
+                return 1;
+            } else {
+                return 0;
+            }
+        }
+    };
+
+    // Manual DND first, Bedtime/Driving, then alphabetically.
+    static final Comparator<ZenMode> PRIORITIZING_COMPARATOR = Comparator
+            .comparing(ZenMode::isManualDnd).reversed()
+            .thenComparing(ZenMode::getType, PRIORITIZED_TYPE_COMPARATOR)
+            .thenComparing(ZenMode::getName);
+
     public enum Status {
         ENABLED,
         ENABLED_AND_ACTIVE,
@@ -129,7 +159,11 @@
     }
 
     public static ZenMode manualDndMode(AutomaticZenRule manualRule, boolean isActive) {
-        return new ZenMode(MANUAL_DND_MODE_ID, manualRule,
+        // Manual rule is owned by the system, so we set it here
+        AutomaticZenRule manualRuleWithPkg = new AutomaticZenRule.Builder(manualRule)
+                .setPackage(PACKAGE_ANDROID)
+                .build();
+        return new ZenMode(MANUAL_DND_MODE_ID, manualRuleWithPkg,
                 isActive ? Status.ENABLED_AND_ACTIVE : Status.ENABLED, true);
     }
 
diff --git a/packages/SettingsLib/src/com/android/settingslib/notification/modes/ZenModesBackend.java b/packages/SettingsLib/src/com/android/settingslib/notification/modes/ZenModesBackend.java
index 64e503b32..c8a12f4 100644
--- a/packages/SettingsLib/src/com/android/settingslib/notification/modes/ZenModesBackend.java
+++ b/packages/SettingsLib/src/com/android/settingslib/notification/modes/ZenModesBackend.java
@@ -34,7 +34,6 @@
 
 import java.time.Duration;
 import java.util.ArrayList;
-import java.util.Comparator;
 import java.util.List;
 import java.util.Map;
 
@@ -92,10 +91,7 @@
             }
         }
 
-        // Manual DND first, then alphabetically.
-        modes.sort(Comparator.comparing(ZenMode::isManualDnd).reversed()
-                .thenComparing(ZenMode::getName));
-
+        modes.sort(ZenMode.PRIORITIZING_COMPARATOR);
         return modes;
     }
 
diff --git a/packages/SettingsLib/src/com/android/settingslib/volume/data/repository/AudioSharingRepository.kt b/packages/SettingsLib/src/com/android/settingslib/volume/data/repository/AudioSharingRepository.kt
index ebba7f1..2f8105a 100644
--- a/packages/SettingsLib/src/com/android/settingslib/volume/data/repository/AudioSharingRepository.kt
+++ b/packages/SettingsLib/src/com/android/settingslib/volume/data/repository/AudioSharingRepository.kt
@@ -63,7 +63,7 @@
 /** Provides audio sharing functionality. */
 interface AudioSharingRepository {
     /** Whether the device is in audio sharing. */
-    val inAudioSharing: Flow<Boolean>
+    val inAudioSharing: StateFlow<Boolean>
 
     /** The primary headset groupId in audio sharing. */
     val primaryGroupId: StateFlow<Int>
@@ -101,7 +101,7 @@
             .flowOn(backgroundCoroutineContext)
             .stateIn(coroutineScope, SharingStarted.WhileSubscribed(), false)
 
-    override val inAudioSharing: Flow<Boolean> =
+    override val inAudioSharing: StateFlow<Boolean> =
         isAudioSharingProfilesReady.flatMapLatest { ready ->
             if (ready) {
                 btManager.profileManager.leAudioBroadcastProfile.onBroadcastStartedOrStopped
@@ -113,6 +113,7 @@
                 flowOf(false)
             }
         }
+            .stateIn(coroutineScope, SharingStarted.WhileSubscribed(), false)
 
     private val primaryChange: Flow<Unit> = callbackFlow {
         val callback =
@@ -254,7 +255,7 @@
 }
 
 class AudioSharingRepositoryEmptyImpl : AudioSharingRepository {
-    override val inAudioSharing: Flow<Boolean> = flowOf(false)
+    override val inAudioSharing: StateFlow<Boolean> = MutableStateFlow(false)
     override val primaryGroupId: StateFlow<Int> =
         MutableStateFlow(BluetoothCsipSetCoordinator.GROUP_ID_INVALID)
     override val secondaryGroupId: StateFlow<Int> =
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/BluetoothUtilsTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/BluetoothUtilsTest.java
index 4551f1e..926d3cb 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/BluetoothUtilsTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/BluetoothUtilsTest.java
@@ -31,10 +31,13 @@
 import android.bluetooth.BluetoothCsipSetCoordinator;
 import android.bluetooth.BluetoothDevice;
 import android.bluetooth.BluetoothLeBroadcastReceiveState;
+import android.bluetooth.BluetoothProfile;
 import android.content.Context;
 import android.content.pm.ApplicationInfo;
 import android.content.pm.PackageManager;
 import android.graphics.drawable.Drawable;
+import android.media.AudioDeviceAttributes;
+import android.media.AudioDeviceInfo;
 import android.media.AudioManager;
 import android.net.Uri;
 import android.platform.test.flag.junit.SetFlagsRule;
@@ -70,6 +73,9 @@
     @Mock private BluetoothDevice mBluetoothDevice;
     @Mock private AudioManager mAudioManager;
     @Mock private PackageManager mPackageManager;
+    @Mock private LeAudioProfile mA2dpProfile;
+    @Mock private LeAudioProfile mLeAudioProfile;
+    @Mock private LeAudioProfile mHearingAid;
     @Mock private LocalBluetoothLeBroadcast mBroadcast;
     @Mock private LocalBluetoothProfileManager mProfileManager;
     @Mock private LocalBluetoothManager mLocalBluetoothManager;
@@ -100,6 +106,9 @@
         when(mLocalBluetoothManager.getProfileManager()).thenReturn(mProfileManager);
         when(mProfileManager.getLeAudioBroadcastProfile()).thenReturn(mBroadcast);
         when(mProfileManager.getLeAudioBroadcastAssistantProfile()).thenReturn(mAssistant);
+        when(mA2dpProfile.getProfileId()).thenReturn(BluetoothProfile.A2DP);
+        when(mLeAudioProfile.getProfileId()).thenReturn(BluetoothProfile.LE_AUDIO);
+        when(mHearingAid.getProfileId()).thenReturn(BluetoothProfile.HEARING_AID);
     }
 
     @Test
@@ -756,4 +765,84 @@
                                 mContext.getContentResolver(), mLocalBluetoothManager))
                 .isEqualTo(mCachedBluetoothDevice);
     }
+
+    @Test
+    public void getAudioDeviceAttributesForSpatialAudio_bleHeadset() {
+        String address = "11:22:33:44:55:66";
+        when(mCachedBluetoothDevice.getDevice()).thenReturn(mBluetoothDevice);
+        when(mCachedBluetoothDevice.getAddress()).thenReturn(address);
+        when(mCachedBluetoothDevice.getProfiles()).thenReturn(List.of(mLeAudioProfile));
+        when(mLeAudioProfile.isEnabled(mBluetoothDevice)).thenReturn(true);
+
+        AudioDeviceAttributes attr =
+                BluetoothUtils.getAudioDeviceAttributesForSpatialAudio(
+                        mCachedBluetoothDevice, AudioManager.AUDIO_DEVICE_CATEGORY_HEADPHONES);
+
+        assertThat(attr)
+                .isEqualTo(
+                        new AudioDeviceAttributes(
+                                AudioDeviceAttributes.ROLE_OUTPUT,
+                                AudioDeviceInfo.TYPE_BLE_HEADSET,
+                                address));
+    }
+
+    @Test
+    public void getAudioDeviceAttributesForSpatialAudio_bleSpeaker() {
+        String address = "11:22:33:44:55:66";
+        when(mCachedBluetoothDevice.getDevice()).thenReturn(mBluetoothDevice);
+        when(mCachedBluetoothDevice.getAddress()).thenReturn(address);
+        when(mCachedBluetoothDevice.getProfiles()).thenReturn(List.of(mLeAudioProfile));
+        when(mLeAudioProfile.isEnabled(mBluetoothDevice)).thenReturn(true);
+
+        AudioDeviceAttributes attr =
+                BluetoothUtils.getAudioDeviceAttributesForSpatialAudio(
+                        mCachedBluetoothDevice, AudioManager.AUDIO_DEVICE_CATEGORY_SPEAKER);
+
+        assertThat(attr)
+                .isEqualTo(
+                        new AudioDeviceAttributes(
+                                AudioDeviceAttributes.ROLE_OUTPUT,
+                                AudioDeviceInfo.TYPE_BLE_SPEAKER,
+                                address));
+    }
+
+    @Test
+    public void getAudioDeviceAttributesForSpatialAudio_a2dp() {
+        String address = "11:22:33:44:55:66";
+        when(mCachedBluetoothDevice.getDevice()).thenReturn(mBluetoothDevice);
+        when(mCachedBluetoothDevice.getAddress()).thenReturn(address);
+        when(mCachedBluetoothDevice.getProfiles()).thenReturn(List.of(mA2dpProfile));
+        when(mA2dpProfile.isEnabled(mBluetoothDevice)).thenReturn(true);
+
+        AudioDeviceAttributes attr =
+                BluetoothUtils.getAudioDeviceAttributesForSpatialAudio(
+                        mCachedBluetoothDevice, AudioManager.AUDIO_DEVICE_CATEGORY_HEADPHONES);
+
+        assertThat(attr)
+                .isEqualTo(
+                        new AudioDeviceAttributes(
+                                AudioDeviceAttributes.ROLE_OUTPUT,
+                                AudioDeviceInfo.TYPE_BLUETOOTH_A2DP,
+                                address));
+    }
+
+    @Test
+    public void getAudioDeviceAttributesForSpatialAudio_hearingAid() {
+        String address = "11:22:33:44:55:66";
+        when(mCachedBluetoothDevice.getDevice()).thenReturn(mBluetoothDevice);
+        when(mCachedBluetoothDevice.getAddress()).thenReturn(address);
+        when(mCachedBluetoothDevice.getProfiles()).thenReturn(List.of(mHearingAid));
+        when(mHearingAid.isEnabled(mBluetoothDevice)).thenReturn(true);
+
+        AudioDeviceAttributes attr =
+                BluetoothUtils.getAudioDeviceAttributesForSpatialAudio(
+                        mCachedBluetoothDevice, AudioManager.AUDIO_DEVICE_CATEGORY_HEARING_AID);
+
+        assertThat(attr)
+                .isEqualTo(
+                        new AudioDeviceAttributes(
+                                AudioDeviceAttributes.ROLE_OUTPUT,
+                                AudioDeviceInfo.TYPE_HEARING_AID,
+                                address));
+    }
 }
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/devicesettings/DeviceSettingsConfigTest.kt b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/devicesettings/DeviceSettingsConfigTest.kt
index 2b29a6e..9568d66 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/devicesettings/DeviceSettingsConfigTest.kt
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/devicesettings/DeviceSettingsConfigTest.kt
@@ -37,9 +37,9 @@
                             "package_name_1",
                             "class_name_1",
                             "intent_action_1",
-                            Bundle()
-                        )
-                    ),
+                            null,
+                            Bundle(),
+                        )),
                 moreSettingsItems =
                     listOf(
                         DeviceSettingItem(
@@ -47,9 +47,9 @@
                             "package_name_2",
                             "class_name_2",
                             "intent_action_2",
-                            Bundle()
-                        )
-                    ),
+                            null,
+                            Bundle(),
+                        )),
                 moreSettingsFooter = "footer",
                 extras = Bundle().apply { putString("key1", "value1") },
             )
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/devicesettings/data/repository/DeviceSettingRepositoryTest.kt b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/devicesettings/data/repository/DeviceSettingRepositoryTest.kt
index fee2394..4c5ee9e 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/devicesettings/data/repository/DeviceSettingRepositoryTest.kt
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/devicesettings/data/repository/DeviceSettingRepositoryTest.kt
@@ -40,6 +40,7 @@
 import com.android.settingslib.bluetooth.devicesettings.ToggleInfo
 import com.android.settingslib.bluetooth.devicesettings.shared.model.DeviceSettingConfigItemModel
 import com.android.settingslib.bluetooth.devicesettings.shared.model.DeviceSettingConfigModel
+import com.android.settingslib.bluetooth.devicesettings.shared.model.DeviceSettingIcon
 import com.android.settingslib.bluetooth.devicesettings.shared.model.DeviceSettingModel
 import com.android.settingslib.bluetooth.devicesettings.shared.model.DeviceSettingStateModel
 import com.android.settingslib.bluetooth.devicesettings.shared.model.ToggleModel
@@ -352,7 +353,7 @@
                 val pref = serviceResponse.preference as ActionSwitchPreference
                 assertThat(actual.title).isEqualTo(pref.title)
                 assertThat(actual.summary).isEqualTo(pref.summary)
-                assertThat(actual.icon).isEqualTo(pref.icon)
+                assertThat(actual.icon).isEqualTo(DeviceSettingIcon.BitmapIcon(pref.icon!!))
                 assertThat(actual.isAllowedChangingState).isEqualTo(pref.isAllowedChangingState)
                 if (pref.hasSwitch()) {
                     assertThat(actual.switchState!!.checked).isEqualTo(pref.checked)
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/notification/modes/ZenModeTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/notification/modes/ZenModeTest.java
index d9fdcc38..bab4bc3b 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/notification/modes/ZenModeTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/notification/modes/ZenModeTest.java
@@ -16,9 +16,16 @@
 
 package com.android.settingslib.notification.modes;
 
+import static android.app.AutomaticZenRule.TYPE_BEDTIME;
+import static android.app.AutomaticZenRule.TYPE_DRIVING;
+import static android.app.AutomaticZenRule.TYPE_IMMERSIVE;
+import static android.app.AutomaticZenRule.TYPE_OTHER;
+import static android.app.AutomaticZenRule.TYPE_THEATER;
+import static android.app.AutomaticZenRule.TYPE_UNKNOWN;
 import static android.app.NotificationManager.INTERRUPTION_FILTER_ALARMS;
 import static android.app.NotificationManager.INTERRUPTION_FILTER_NONE;
 import static android.app.NotificationManager.INTERRUPTION_FILTER_PRIORITY;
+import static android.service.notification.SystemZenRules.PACKAGE_ANDROID;
 
 import static com.google.common.truth.Truth.assertThat;
 import static com.google.common.truth.Truth.assertWithMessage;
@@ -37,6 +44,9 @@
 import org.junit.runner.RunWith;
 import org.robolectric.RobolectricTestRunner;
 
+import java.util.ArrayList;
+import java.util.List;
+
 @RunWith(RobolectricTestRunner.class)
 public class ZenModeTest {
 
@@ -45,7 +55,7 @@
     private static final AutomaticZenRule ZEN_RULE =
             new AutomaticZenRule.Builder("Driving", Uri.parse("drive"))
                     .setPackage("com.some.driving.thing")
-                    .setType(AutomaticZenRule.TYPE_DRIVING)
+                    .setType(TYPE_DRIVING)
                     .setInterruptionFilter(INTERRUPTION_FILTER_PRIORITY)
                     .setZenPolicy(ZEN_POLICY)
                     .build();
@@ -67,6 +77,7 @@
         assertThat(manualMode.canEditNameAndIcon()).isFalse();
         assertThat(manualMode.canBeDeleted()).isFalse();
         assertThat(manualMode.isActive()).isFalse();
+        assertThat(manualMode.getRule().getPackageName()).isEqualTo(PACKAGE_ANDROID);
     }
 
     @Test
@@ -224,6 +235,28 @@
     }
 
     @Test
+    public void comparator_prioritizes() {
+        ZenMode manualDnd = TestModeBuilder.MANUAL_DND_INACTIVE;
+        ZenMode driving1 = new TestModeBuilder().setName("b1").setType(TYPE_DRIVING).build();
+        ZenMode driving2 = new TestModeBuilder().setName("b2").setType(TYPE_DRIVING).build();
+        ZenMode bedtime1 = new TestModeBuilder().setName("c1").setType(TYPE_BEDTIME).build();
+        ZenMode bedtime2 = new TestModeBuilder().setName("c2").setType(TYPE_BEDTIME).build();
+        ZenMode other = new TestModeBuilder().setName("a1").setType(TYPE_OTHER).build();
+        ZenMode immersive = new TestModeBuilder().setName("a2").setType(TYPE_IMMERSIVE).build();
+        ZenMode unknown = new TestModeBuilder().setName("a3").setType(TYPE_UNKNOWN).build();
+        ZenMode theater = new TestModeBuilder().setName("a4").setType(TYPE_THEATER).build();
+
+        ArrayList<ZenMode> list = new ArrayList<>(List.of(other, theater, bedtime1, unknown,
+                driving2, manualDnd, driving1, bedtime2, immersive));
+        list.sort(ZenMode.PRIORITIZING_COMPARATOR);
+
+        assertThat(list)
+                .containsExactly(manualDnd, bedtime1, bedtime2, driving1, driving2, other,
+                        immersive, unknown, theater)
+                .inOrder();
+    }
+
+    @Test
     public void writeToParcel_equals() {
         assertUnparceledIsEqualToOriginal("example",
                 new ZenMode("id", ZEN_RULE, zenConfigRuleFor(ZEN_RULE, false)));
diff --git a/packages/SettingsProvider/Android.bp b/packages/SettingsProvider/Android.bp
index 75f8384..3e62b7b 100644
--- a/packages/SettingsProvider/Android.bp
+++ b/packages/SettingsProvider/Android.bp
@@ -32,6 +32,7 @@
         "unsupportedappusage",
     ],
     static_libs: [
+        "aconfig_device_paths_java",
         "aconfig_new_storage_flags_lib",
         "aconfigd_java_utils",
         "aconfig_demo_flags_java_lib",
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/DeviceConfigService.java b/packages/SettingsProvider/src/com/android/providers/settings/DeviceConfigService.java
index 8b0772b..121bd3e 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/DeviceConfigService.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/DeviceConfigService.java
@@ -20,8 +20,10 @@
 import static android.provider.Settings.Config.SYNC_DISABLED_MODE_PERSISTENT;
 import static android.provider.Settings.Config.SYNC_DISABLED_MODE_UNTIL_REBOOT;
 
-import android.aconfig.Aconfig.parsed_flag;
-import android.aconfig.Aconfig.parsed_flags;
+import android.aconfig.DeviceProtos;
+import android.aconfig.nano.Aconfig;
+import android.aconfig.nano.Aconfig.parsed_flag;
+import android.aconfig.nano.Aconfig.parsed_flags;
 import android.annotation.SuppressLint;
 import android.app.ActivityManager;
 import android.content.AttributionSource;
@@ -42,7 +44,6 @@
 import android.provider.UpdatableDeviceConfigServiceReadiness;
 import android.util.Slog;
 
-import com.android.internal.pm.pkg.component.AconfigFlags;
 import com.android.internal.util.FastPrintWriter;
 
 import java.io.File;
@@ -136,11 +137,8 @@
                     continue;
                 }
 
-                for (parsed_flag flag : parsedFlags.getParsedFlagList()) {
-                    String namespace = flag.getNamespace();
-                    String packageName = flag.getPackage();
-                    String name = flag.getName();
-                    nameSet.add(namespace + "/" + packageName + "." + name);
+                for (parsed_flag flag : parsedFlags.parsedFlag) {
+                    nameSet.add(flag.namespace + "/" + flag.package_ + "." + flag.name);
                 }
             }
         } catch (IOException e) {
@@ -169,6 +167,7 @@
 
     static final class MyShellCommand extends ShellCommand {
         final SettingsProvider mProvider;
+        private HashMap<String, parsed_flag> mAconfigParsedFlags;
 
         enum CommandVerb {
             GET,
@@ -186,6 +185,51 @@
 
         MyShellCommand(SettingsProvider provider) {
             mProvider = provider;
+
+            if (Flags.checkRootAndReadOnly()) {
+                List<parsed_flag> parsedFlags;
+                try {
+                    parsedFlags = DeviceProtos.loadAndParseFlagProtos();
+                } catch (IOException e) {
+                    throw new IllegalStateException("failed to parse aconfig protos");
+                }
+
+                mAconfigParsedFlags = new HashMap();
+                for (parsed_flag flag : parsedFlags) {
+                    mAconfigParsedFlags.put(flag.package_ + "." + flag.name, flag);
+                }
+            }
+        }
+
+        /**
+         * Return true if a flag is aconfig.
+         */
+        private boolean isAconfigFlag(String name) {
+            return mAconfigParsedFlags.get(name) != null;
+        }
+
+        /**
+         * Return true if a flag is both aconfig and read-only.
+         *
+         * @return true if a flag is both aconfig and read-only
+         */
+        private boolean isReadOnly(String name) {
+            parsed_flag flag = mAconfigParsedFlags.get(name);
+            if (flag != null) {
+                if (flag.permission == Aconfig.READ_ONLY) {
+                    return true;
+                }
+            }
+            return false;
+        }
+
+        /**
+         * Return true if the calling process is root.
+         *
+         * @return true if a flag is aconfig, and the calling process is root
+         */
+        private boolean isRoot() {
+            return Binder.getCallingUid() == Process.ROOT_UID;
         }
 
       public static HashMap<String, String> getAllFlags(IContentProvider provider) {
@@ -414,21 +458,71 @@
                     pout.println(DeviceConfig.getProperty(namespace, key));
                     break;
                 case PUT:
+                    if (Flags.checkRootAndReadOnly()) {
+                        if (isAconfigFlag(key)) {
+                            if (!isRoot()) {
+                                pout.println("Error: must be root to write aconfig flag");
+                                break;
+                            }
+
+                            if (isReadOnly(key)) {
+                                pout.println("Error: cannot write read-only flag");
+                                break;
+                            }
+                        }
+                    }
+
                     DeviceConfig.setProperty(namespace, key, value, makeDefault);
                     break;
                 case OVERRIDE:
-                    AconfigFlags.Permission permission =
-                            (new AconfigFlags()).getFlagPermission(key);
-                    if (permission == AconfigFlags.Permission.READ_ONLY) {
-                        pout.println("cannot override read-only flag " + key);
-                    } else {
-                        DeviceConfig.setLocalOverride(namespace, key, value);
+                    if (Flags.checkRootAndReadOnly()) {
+                        if (isAconfigFlag(key)) {
+                            if (!isRoot()) {
+                                pout.println("Error: must be root to write aconfig flag");
+                                break;
+                            }
+
+                            if (isReadOnly(key)) {
+                                pout.println("Error: cannot write read-only flag");
+                                break;
+                            }
+                        }
                     }
+
+                    DeviceConfig.setLocalOverride(namespace, key, value);
                     break;
                 case CLEAR_OVERRIDE:
+                    if (Flags.checkRootAndReadOnly()) {
+                        if (isAconfigFlag(key)) {
+                            if (!isRoot()) {
+                                pout.println("Error: must be root to write aconfig flag");
+                                break;
+                            }
+
+                            if (isReadOnly(key)) {
+                                pout.println("Error: cannot write read-only flag");
+                                break;
+                            }
+                        }
+                    }
+
                     DeviceConfig.clearLocalOverride(namespace, key);
                     break;
                 case DELETE:
+                    if (Flags.checkRootAndReadOnly()) {
+                        if (isAconfigFlag(key)) {
+                            if (!isRoot()) {
+                                pout.println("Error: must be root to write aconfig flag");
+                                break;
+                            }
+
+                            if (isReadOnly(key)) {
+                                pout.println("Error: cannot write read-only flag");
+                                break;
+                            }
+                        }
+                    }
+
                     pout.println(delete(iprovider, namespace, key)
                             ? "Successfully deleted " + key + " from " + namespace
                             : "Failed to delete " + key + " from " + namespace);
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/device_config_service.aconfig b/packages/SettingsProvider/src/com/android/providers/settings/device_config_service.aconfig
index b1e6d66..006e644 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/device_config_service.aconfig
+++ b/packages/SettingsProvider/src/com/android/providers/settings/device_config_service.aconfig
@@ -70,3 +70,14 @@
         purpose: PURPOSE_BUGFIX
     }
 }
+
+flag {
+    name: "check_root_and_read_only"
+    namespace: "core_experiments_team_internal"
+    description: "Check root and aconfig flag permissions in adb shell device_config commands."
+    bug: "342636474"
+    is_fixed_read_only: true
+    metadata {
+        purpose: PURPOSE_BUGFIX
+    }
+}
diff --git a/packages/SystemUI/Android.bp b/packages/SystemUI/Android.bp
index 6d78705..3aa89ee 100644
--- a/packages/SystemUI/Android.bp
+++ b/packages/SystemUI/Android.bp
@@ -748,6 +748,7 @@
         "//frameworks/libs/systemui:motion_tool_lib",
         "//frameworks/libs/systemui:contextualeducationlib",
         "androidx.core_core-animation-testing",
+        "androidx.lifecycle_lifecycle-runtime-testing",
         "androidx.compose.ui_ui",
         "flag-junit",
         "ravenwood-junit",
@@ -789,6 +790,7 @@
         "SystemUI-tests-base",
         "androidx.test.uiautomator_uiautomator",
         "androidx.core_core-animation-testing",
+        "androidx.lifecycle_lifecycle-runtime-testing",
         "mockito-target-extended-minus-junit4",
         "mockito-kotlin-nodeps",
         "androidx.test.ext.junit",
@@ -836,14 +838,6 @@
     ],
     manifest: "tests/AndroidManifest-base.xml",
 
-    srcs: [
-        "src/**/*.kt",
-        "src/**/*.java",
-        "src/**/I*.aidl",
-        ":ReleaseJavaFiles",
-        "compose/features/src/**/*.kt",
-        "compose/facade/enabled/src/**/*.kt",
-    ],
     static_libs: [
         "//frameworks/libs/systemui:compilelib",
         "SystemUI-tests-base",
diff --git a/packages/SystemUI/AndroidManifest.xml b/packages/SystemUI/AndroidManifest.xml
index 1d9f469..398c915 100644
--- a/packages/SystemUI/AndroidManifest.xml
+++ b/packages/SystemUI/AndroidManifest.xml
@@ -480,6 +480,7 @@
 
         <activity android:name=".touchpad.tutorial.ui.view.TouchpadTutorialActivity"
             android:exported="true"
+            android:screenOrientation="userLandscape"
             android:theme="@style/Theme.AppCompat.NoActionBar">
             <intent-filter>
                 <action android:name="com.android.systemui.action.TOUCHPAD_TUTORIAL"/>
@@ -489,6 +490,7 @@
 
         <activity android:name=".inputdevice.tutorial.ui.view.KeyboardTouchpadTutorialActivity"
             android:exported="true"
+            android:screenOrientation="userLandscape"
             android:theme="@style/Theme.AppCompat.NoActionBar">
             <intent-filter>
                 <action android:name="com.android.systemui.action.TOUCHPAD_KEYBOARD_TUTORIAL"/>
diff --git a/packages/SystemUI/aconfig/systemui.aconfig b/packages/SystemUI/aconfig/systemui.aconfig
index cdbac33..476fd8b 100644
--- a/packages/SystemUI/aconfig/systemui.aconfig
+++ b/packages/SystemUI/aconfig/systemui.aconfig
@@ -281,13 +281,6 @@
 }
 
 flag {
-    name: "qs_new_pipeline"
-    namespace: "systemui"
-    description: "Use the new pipeline for Quick Settings. Should have no behavior changes."
-    bug: "241772429"
-}
-
-flag {
    name: "qs_new_tiles"
    namespace: "systemui"
    description: "Use the new tiles in the Quick Settings. Should have no behavior changes."
@@ -387,6 +380,17 @@
 }
 
 flag {
+    name: "status_bar_stop_updating_window_height"
+    namespace: "systemui"
+    description: "Don't have PhoneStatusBarView manually trigger an update of the height in "
+        "StatusBarWindowController"
+    bug: "360115167"
+    metadata {
+      purpose: PURPOSE_BUGFIX
+    }
+}
+
+flag {
     name: "compose_bouncer"
     namespace: "systemui"
     description: "Use the new compose bouncer in SystemUI"
@@ -537,6 +541,16 @@
 }
 
 flag {
+    name: "clipboard_image_timeout"
+    namespace: "systemui"
+    description: "Wait for clipboard image to load before showing UI"
+    bug: "359864629"
+    metadata {
+        purpose: PURPOSE_BUGFIX
+    }
+}
+
+flag {
     name: "screenshot_action_dismiss_system_windows"
     namespace: "systemui"
     description: "Dismiss existing system windows when starting action from screenshot UI"
@@ -998,6 +1012,16 @@
 }
 
 flag {
+  name: "communal_widget_trampoline_fix"
+  namespace: "systemui"
+  description: "fixes activity starts caused by non-activity trampolines from widgets."
+  bug: "350468769"
+  metadata {
+    purpose: PURPOSE_BUGFIX
+  }
+}
+
+flag {
   name: "app_clips_backlinks"
   namespace: "systemui"
   description: "Enables Backlinks improvement feature in App Clips"
@@ -1035,6 +1059,13 @@
 }
 
 flag {
+  name: "media_controls_button_media3"
+  namespace: "systemui"
+  description: "Enable media action buttons updates using media3"
+  bug: "360196209"
+}
+
+flag {
   namespace: "systemui"
   name: "enable_view_capture_tracing"
   description: "Enables view capture tracing in System UI."
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/BouncerContent.kt b/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/BouncerContent.kt
index f655ac1..d164eab 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/BouncerContent.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/BouncerContent.kt
@@ -95,7 +95,7 @@
 import com.android.systemui.bouncer.ui.helper.BouncerSceneLayout
 import com.android.systemui.bouncer.ui.viewmodel.AuthMethodBouncerViewModel
 import com.android.systemui.bouncer.ui.viewmodel.BouncerMessageViewModel
-import com.android.systemui.bouncer.ui.viewmodel.BouncerViewModel
+import com.android.systemui.bouncer.ui.viewmodel.BouncerSceneContentViewModel
 import com.android.systemui.bouncer.ui.viewmodel.MessageViewModel
 import com.android.systemui.bouncer.ui.viewmodel.PasswordBouncerViewModel
 import com.android.systemui.bouncer.ui.viewmodel.PatternBouncerViewModel
@@ -114,7 +114,7 @@
 
 @Composable
 fun BouncerContent(
-    viewModel: BouncerViewModel,
+    viewModel: BouncerSceneContentViewModel,
     dialogFactory: BouncerDialogFactory,
     modifier: Modifier = Modifier,
 ) {
@@ -128,7 +128,7 @@
 @VisibleForTesting
 fun BouncerContent(
     layout: BouncerSceneLayout,
-    viewModel: BouncerViewModel,
+    viewModel: BouncerSceneContentViewModel,
     dialogFactory: BouncerDialogFactory,
     modifier: Modifier
 ) {
@@ -173,7 +173,7 @@
  */
 @Composable
 private fun StandardLayout(
-    viewModel: BouncerViewModel,
+    viewModel: BouncerSceneContentViewModel,
     modifier: Modifier = Modifier,
 ) {
     val isHeightExpanded =
@@ -235,7 +235,7 @@
  */
 @Composable
 private fun SplitLayout(
-    viewModel: BouncerViewModel,
+    viewModel: BouncerSceneContentViewModel,
     modifier: Modifier = Modifier,
 ) {
     val authMethod by viewModel.authMethodViewModel.collectAsStateWithLifecycle()
@@ -326,7 +326,7 @@
  */
 @Composable
 private fun BesideUserSwitcherLayout(
-    viewModel: BouncerViewModel,
+    viewModel: BouncerSceneContentViewModel,
     modifier: Modifier = Modifier,
 ) {
     val layoutDirection = LocalLayoutDirection.current
@@ -461,7 +461,7 @@
 /** Arranges the bouncer contents and user switcher contents one on top of the other, vertically. */
 @Composable
 private fun BelowUserSwitcherLayout(
-    viewModel: BouncerViewModel,
+    viewModel: BouncerSceneContentViewModel,
     modifier: Modifier = Modifier,
 ) {
     Column(
@@ -506,7 +506,7 @@
 
 @Composable
 private fun FoldAware(
-    viewModel: BouncerViewModel,
+    viewModel: BouncerSceneContentViewModel,
     aboveFold: @Composable BoxScope.() -> Unit,
     belowFold: @Composable BoxScope.() -> Unit,
     modifier: Modifier = Modifier,
@@ -649,7 +649,7 @@
  */
 @Composable
 private fun OutputArea(
-    viewModel: BouncerViewModel,
+    viewModel: BouncerSceneContentViewModel,
     modifier: Modifier = Modifier,
 ) {
     val authMethodViewModel: AuthMethodBouncerViewModel? by
@@ -677,7 +677,7 @@
  */
 @Composable
 private fun InputArea(
-    viewModel: BouncerViewModel,
+    viewModel: BouncerSceneContentViewModel,
     pinButtonRowVerticalSpacing: Dp,
     centerPatternDotsVertically: Boolean,
     modifier: Modifier = Modifier,
@@ -706,7 +706,7 @@
 
 @Composable
 private fun ActionArea(
-    viewModel: BouncerViewModel,
+    viewModel: BouncerSceneContentViewModel,
     modifier: Modifier = Modifier,
 ) {
     val actionButton: BouncerActionButtonModel? by
@@ -774,7 +774,7 @@
 
 @Composable
 private fun Dialog(
-    bouncerViewModel: BouncerViewModel,
+    bouncerViewModel: BouncerSceneContentViewModel,
     dialogFactory: BouncerDialogFactory,
 ) {
     val dialogViewModel by bouncerViewModel.dialogViewModel.collectAsStateWithLifecycle()
@@ -803,7 +803,7 @@
 /** Renders the UI of the user switcher that's displayed on large screens next to the bouncer UI. */
 @Composable
 private fun UserSwitcher(
-    viewModel: BouncerViewModel,
+    viewModel: BouncerSceneContentViewModel,
     modifier: Modifier = Modifier,
 ) {
     if (!viewModel.isUserSwitcherVisible) {
@@ -884,7 +884,7 @@
 @Composable
 private fun UserSwitcherDropdownMenu(
     isExpanded: Boolean,
-    items: List<BouncerViewModel.UserSwitcherDropdownItemViewModel>,
+    items: List<BouncerSceneContentViewModel.UserSwitcherDropdownItemViewModel>,
     onDismissed: () -> Unit,
 ) {
     val context = LocalContext.current
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/BouncerScene.kt b/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/BouncerScene.kt
index 9fd30b4..aeba67b 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/BouncerScene.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/BouncerScene.kt
@@ -27,9 +27,11 @@
 import com.android.compose.animation.scene.UserAction
 import com.android.compose.animation.scene.UserActionResult
 import com.android.systemui.bouncer.ui.BouncerDialogFactory
-import com.android.systemui.bouncer.ui.viewmodel.BouncerViewModel
+import com.android.systemui.bouncer.ui.viewmodel.BouncerSceneActionsViewModel
+import com.android.systemui.bouncer.ui.viewmodel.BouncerSceneContentViewModel
 import com.android.systemui.compose.modifiers.sysuiResTag
 import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.lifecycle.rememberViewModel
 import com.android.systemui.scene.shared.model.Scenes
 import com.android.systemui.scene.ui.composable.ComposableScene
 import javax.inject.Inject
@@ -51,23 +53,37 @@
 class BouncerScene
 @Inject
 constructor(
-    private val viewModel: BouncerViewModel,
+    private val actionsViewModelFactory: BouncerSceneActionsViewModel.Factory,
+    private val contentViewModelFactory: BouncerSceneContentViewModel.Factory,
     private val dialogFactory: BouncerDialogFactory,
 ) : ComposableScene {
     override val key = Scenes.Bouncer
 
+    private val actionsViewModel: BouncerSceneActionsViewModel by lazy {
+        actionsViewModelFactory.create()
+    }
+
     override val destinationScenes: Flow<Map<UserAction, UserActionResult>> =
-        viewModel.destinationScenes
+        actionsViewModel.actions
+
+    override suspend fun activate(): Nothing {
+        actionsViewModel.activate()
+    }
 
     @Composable
     override fun SceneScope.Content(
         modifier: Modifier,
-    ) = BouncerScene(viewModel, dialogFactory, modifier)
+    ) =
+        BouncerScene(
+            viewModel = rememberViewModel { contentViewModelFactory.create() },
+            dialogFactory = dialogFactory,
+            modifier = modifier,
+        )
 }
 
 @Composable
 private fun SceneScope.BouncerScene(
-    viewModel: BouncerViewModel,
+    viewModel: BouncerSceneContentViewModel,
     dialogFactory: BouncerDialogFactory,
     modifier: Modifier = Modifier,
 ) {
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PatternBouncer.kt b/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PatternBouncer.kt
index 069113b..163b355 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PatternBouncer.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/PatternBouncer.kt
@@ -511,7 +511,7 @@
 private const val SELECTED_DOT_DIAMETER_DP = (DOT_DIAMETER_DP * 1.5).toInt()
 private const val SELECTED_DOT_REACTION_ANIMATION_DURATION_MS = 83
 private const val SELECTED_DOT_RETRACT_ANIMATION_DURATION_MS = 750
-private const val LINE_STROKE_WIDTH_DP = DOT_DIAMETER_DP
+private const val LINE_STROKE_WIDTH_DP = 22
 private const val FAILURE_ANIMATION_DOT_DIAMETER_DP = (DOT_DIAMETER_DP * 0.81f).toInt()
 private const val FAILURE_ANIMATION_DOT_SHRINK_ANIMATION_DURATION_MS = 50
 private const val FAILURE_ANIMATION_DOT_SHRINK_STAGGER_DELAY_MS = 33
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 b93b049..91a88bc 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
@@ -170,7 +170,6 @@
 import com.android.systemui.communal.ui.viewmodel.CommunalEditModeViewModel
 import com.android.systemui.communal.ui.viewmodel.CommunalViewModel
 import com.android.systemui.communal.util.DensityUtils.Companion.adjustedDp
-import com.android.systemui.communal.util.DensityUtils.Companion.scalingAdjustment
 import com.android.systemui.communal.widgets.SmartspaceAppWidgetHostView
 import com.android.systemui.communal.widgets.WidgetConfigurator
 import com.android.systemui.res.R
@@ -478,8 +477,7 @@
 }
 
 val hubDimensions: Dimensions
-    @Composable
-    get() = Dimensions(LocalContext.current, LocalConfiguration.current, LocalDensity.current)
+    @Composable get() = Dimensions(LocalContext.current, LocalConfiguration.current)
 
 @Composable
 private fun DisclaimerBottomSheetContent(onButtonClicked: () -> Unit) {
@@ -1288,8 +1286,9 @@
         modifier =
             modifier
                 .background(
-                    MaterialTheme.colorScheme.surfaceVariant,
-                    RoundedCornerShape(dimensionResource(system_app_widget_background_radius))
+                    color = MaterialTheme.colorScheme.surfaceVariant,
+                    shape =
+                        RoundedCornerShape(dimensionResource(system_app_widget_background_radius))
                 )
                 .clickable(
                     enabled = !viewModel.isEditMode,
@@ -1325,10 +1324,8 @@
     Column(
         modifier =
             modifier.background(
-                MaterialTheme.colorScheme.surfaceVariant,
-                RoundedCornerShape(
-                    dimensionResource(system_app_widget_background_radius) * scalingAdjustment
-                )
+                color = MaterialTheme.colorScheme.surfaceVariant,
+                shape = RoundedCornerShape(dimensionResource(system_app_widget_background_radius))
             ),
         verticalArrangement = Arrangement.Center,
         horizontalAlignment = Alignment.CenterHorizontally,
@@ -1410,7 +1407,10 @@
                                     R.string.accessibility_action_label_close_communal_hub
                                 )
                             ) {
-                                viewModel.changeScene(CommunalScenes.Blank)
+                                viewModel.changeScene(
+                                    CommunalScenes.Blank,
+                                    "closed by accessibility"
+                                )
                                 true
                             },
                             CustomAccessibilityAction(
@@ -1511,21 +1511,24 @@
     fun toOffset(): Offset = Offset(start, top)
 }
 
-class Dimensions(val context: Context, val config: Configuration, val density: Density) {
+class Dimensions(val context: Context, val config: Configuration) {
     val GridTopSpacing: Dp
         get() {
-            if (config.orientation == Configuration.ORIENTATION_LANDSCAPE) {
-                return 114.adjustedDp
-            } else {
-                val windowMetrics =
-                    WindowMetricsCalculator.getOrCreate().computeCurrentWindowMetrics(context)
-                val screenHeight = with(density) { windowMetrics.bounds.height().adjustedDp }
-
-                return (screenHeight - CardHeightFull) / 2
-            }
+            val result =
+                if (config.orientation == Configuration.ORIENTATION_LANDSCAPE) {
+                    114.dp
+                } else {
+                    val windowMetrics =
+                        WindowMetricsCalculator.getOrCreate().computeCurrentWindowMetrics(context)
+                    val density = context.resources.displayMetrics.density
+                    val screenHeight = (windowMetrics.bounds.height() / density).dp
+                    ((screenHeight - CardHeightFull) / 2)
+                }
+            return result
         }
 
-    val GridHeight = CardHeightFull + GridTopSpacing
+    val GridHeight: Dp
+        get() = CardHeightFull + GridTopSpacing
 
     companion object {
         val CardHeightFull
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 7fe1b3e..7f059d7 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
@@ -49,7 +49,7 @@
     override val destinationScenes: Flow<Map<UserAction, UserActionResult>> =
         actionsViewModel.actions
 
-    override suspend fun activate() {
+    override suspend fun activate(): Nothing {
         actionsViewModel.activate()
     }
 
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/NotificationsShadeScene.kt b/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/NotificationsShadeScene.kt
index 0cb8bd3..666e324 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/NotificationsShadeScene.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/NotificationsShadeScene.kt
@@ -72,7 +72,7 @@
     override val destinationScenes: Flow<Map<UserAction, UserActionResult>> =
         actionsViewModel.actions
 
-    override suspend fun activate() {
+    override suspend fun activate(): Nothing {
         actionsViewModel.activate()
     }
 
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsScene.kt b/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsScene.kt
index 9db8bf1..e064724 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsScene.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsScene.kt
@@ -139,7 +139,7 @@
     override val destinationScenes: Flow<Map<UserAction, UserActionResult>> =
         actionsViewModel.actions
 
-    override suspend fun activate() {
+    override suspend fun activate(): Nothing {
         actionsViewModel.activate()
     }
 
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsShadeScene.kt b/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsShadeScene.kt
index eea00c4..fb7c422 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsShadeScene.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettingsShadeScene.kt
@@ -29,8 +29,6 @@
 import androidx.compose.foundation.layout.height
 import androidx.compose.foundation.layout.heightIn
 import androidx.compose.foundation.layout.padding
-import androidx.compose.material3.Button
-import androidx.compose.material3.Text
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.getValue
 import androidx.compose.ui.Alignment
@@ -42,6 +40,7 @@
 import com.android.compose.animation.scene.UserActionResult
 import com.android.systemui.battery.BatteryMeterViewController
 import com.android.systemui.brightness.ui.compose.BrightnessSliderContainer
+import com.android.systemui.compose.modifiers.sysuiResTag
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.keyguard.ui.composable.LockscreenContent
 import com.android.systemui.lifecycle.rememberViewModel
@@ -114,7 +113,7 @@
 }
 
 @Composable
-private fun ShadeBody(
+fun ShadeBody(
     viewModel: QuickSettingsContainerViewModel,
 ) {
     val isEditing by viewModel.editModeViewModel.isEditing.collectAsStateWithLifecycle()
@@ -131,6 +130,7 @@
         } else {
             QuickSettingsLayout(
                 viewModel = viewModel,
+                modifier = Modifier.sysuiResTag("quick_settings_panel")
             )
         }
     }
@@ -158,11 +158,6 @@
                 Modifier.fillMaxWidth().heightIn(max = QuickSettingsShade.Dimensions.GridMaxHeight),
             viewModel.editModeViewModel::startEditing,
         )
-        Button(
-            onClick = { viewModel.editModeViewModel.startEditing() },
-        ) {
-            Text("Edit mode")
-        }
     }
 }
 
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/GoneScene.kt b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/GoneScene.kt
index f15e87b..3e22105 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/GoneScene.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/GoneScene.kt
@@ -58,7 +58,7 @@
     override val destinationScenes: Flow<Map<UserAction, UserActionResult>> =
         actionsViewModel.actions
 
-    override suspend fun activate() {
+    override suspend fun activate(): Nothing {
         actionsViewModel.activate()
     }
 
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeScene.kt b/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeScene.kt
index 7920e74..d15bda0 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeScene.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeScene.kt
@@ -56,6 +56,7 @@
 import androidx.compose.ui.input.pointer.pointerInteropFilter
 import androidx.compose.ui.layout.Layout
 import androidx.compose.ui.layout.layoutId
+import androidx.compose.ui.platform.LocalContext
 import androidx.compose.ui.platform.LocalDensity
 import androidx.compose.ui.platform.LocalLifecycleOwner
 import androidx.compose.ui.res.colorResource
@@ -88,6 +89,8 @@
 import com.android.systemui.media.controls.ui.controller.MediaHierarchyManager
 import com.android.systemui.media.controls.ui.view.MediaHost
 import com.android.systemui.media.controls.ui.view.MediaHostState
+import com.android.systemui.media.controls.ui.view.MediaHostState.Companion.COLLAPSED
+import com.android.systemui.media.controls.ui.view.MediaHostState.Companion.EXPANDED
 import com.android.systemui.media.dagger.MediaModule.QS_PANEL
 import com.android.systemui.media.dagger.MediaModule.QUICK_QS_PANEL
 import com.android.systemui.notifications.ui.composable.NotificationScrollingStack
@@ -110,6 +113,7 @@
 import com.android.systemui.statusbar.phone.StatusBarLocation
 import com.android.systemui.statusbar.phone.ui.StatusBarIconController
 import com.android.systemui.statusbar.phone.ui.TintedIconManager
+import com.android.systemui.util.Utils
 import dagger.Lazy
 import javax.inject.Inject
 import javax.inject.Named
@@ -163,7 +167,7 @@
         actionsViewModelFactory.create()
     }
 
-    override suspend fun activate() {
+    override suspend fun activate(): Nothing {
         actionsViewModel.activate()
     }
 
@@ -260,6 +264,11 @@
     shadeSession: SaveableSession,
 ) {
     val cutoutLocation = LocalDisplayCutout.current.location
+    val isLandscape = LocalWindowSizeClass.current.heightSizeClass == WindowHeightSizeClass.Compact
+    val usingCollapsedLandscapeMedia =
+        Utils.useCollapsedMediaInLandscape(LocalContext.current.resources)
+    val isExpanded = !usingCollapsedLandscapeMedia || !isLandscape
+    mediaHost.expansion = if (isExpanded) EXPANDED else COLLAPSED
 
     val maxNotifScrimTop = remember { mutableStateOf(0f) }
     val tileSquishiness by
@@ -275,9 +284,7 @@
         layoutState.isTransitioningBetween(Scenes.Gone, Scenes.Shade) ||
             layoutState.isTransitioningBetween(Scenes.Lockscreen, Scenes.Shade)
     // Media is visible and we are in landscape on a small height screen
-    val mediaInRow =
-        isMediaVisible &&
-            LocalWindowSizeClass.current.heightSizeClass == WindowHeightSizeClass.Compact
+    val mediaInRow = isMediaVisible && isLandscape
     val mediaOffset by
         animateSceneDpAsState(value = InQQS, key = MediaLandscapeTopOffset, canOverflow = false)
 
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/MultiPointerDraggable.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/MultiPointerDraggable.kt
index b329534..3487730 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/MultiPointerDraggable.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/MultiPointerDraggable.kt
@@ -81,6 +81,7 @@
     enabled: () -> Boolean,
     startDragImmediately: (startedPosition: Offset) -> Boolean,
     onDragStarted: (startedPosition: Offset, overSlop: Float, pointersDown: Int) -> DragController,
+    onFirstPointerDown: () -> Unit = {},
     swipeDetector: SwipeDetector = DefaultSwipeDetector,
     dispatcher: NestedScrollDispatcher,
 ): Modifier =
@@ -90,6 +91,7 @@
             enabled,
             startDragImmediately,
             onDragStarted,
+            onFirstPointerDown,
             swipeDetector,
             dispatcher,
         )
@@ -101,6 +103,7 @@
     private val startDragImmediately: (startedPosition: Offset) -> Boolean,
     private val onDragStarted:
         (startedPosition: Offset, overSlop: Float, pointersDown: Int) -> DragController,
+    private val onFirstPointerDown: () -> Unit,
     private val swipeDetector: SwipeDetector,
     private val dispatcher: NestedScrollDispatcher,
 ) : ModifierNodeElement<MultiPointerDraggableNode>() {
@@ -110,6 +113,7 @@
             enabled = enabled,
             startDragImmediately = startDragImmediately,
             onDragStarted = onDragStarted,
+            onFirstPointerDown = onFirstPointerDown,
             swipeDetector = swipeDetector,
             dispatcher = dispatcher,
         )
@@ -119,6 +123,7 @@
         node.enabled = enabled
         node.startDragImmediately = startDragImmediately
         node.onDragStarted = onDragStarted
+        node.onFirstPointerDown = onFirstPointerDown
         node.swipeDetector = swipeDetector
     }
 }
@@ -129,6 +134,7 @@
     var startDragImmediately: (startedPosition: Offset) -> Boolean,
     var onDragStarted:
         (startedPosition: Offset, overSlop: Float, pointersDown: Int) -> DragController,
+    var onFirstPointerDown: () -> Unit,
     var swipeDetector: SwipeDetector = DefaultSwipeDetector,
     private val dispatcher: NestedScrollDispatcher,
 ) :
@@ -225,6 +231,7 @@
                             startedPosition = null
                         } else if (startedPosition == null) {
                             startedPosition = pointers.first().position
+                            onFirstPointerDown()
                         }
                     }
                 }
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SwipeToScene.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SwipeToScene.kt
index f062146..d1e83ba 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SwipeToScene.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SwipeToScene.kt
@@ -67,6 +67,7 @@
                 enabled = ::enabled,
                 startDragImmediately = ::startDragImmediately,
                 onDragStarted = draggableHandler::onDragStarted,
+                onFirstPointerDown = ::onFirstPointerDown,
                 swipeDetector = swipeDetector,
                 dispatcher = dispatcher,
             )
@@ -101,6 +102,15 @@
         delegate(ScrollBehaviorOwnerNode(draggableHandler.nestedScrollKey, nestedScrollHandlerImpl))
     }
 
+    private fun onFirstPointerDown() {
+        // When we drag our finger across the screen, the NestedScrollConnection keeps track of all
+        // the scroll events until we lift our finger. However, in some cases, the connection might
+        // not receive the "up" event. This can lead to an incorrect initial state for the gesture.
+        // To prevent this issue, we can call the reset() method when the first finger touches the
+        // screen. This ensures that the NestedScrollConnection starts from a correct state.
+        nestedScrollHandlerImpl.connection.reset()
+    }
+
     override fun onDetach() {
         // Make sure we reset the scroll connection when this modifier is removed from composition
         nestedScrollHandlerImpl.connection.reset()
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/TransitionDsl.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/TransitionDsl.kt
index 79b3856..5e96381 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/TransitionDsl.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/TransitionDsl.kt
@@ -245,9 +245,7 @@
 interface ElementContentPicker {
     /**
      * Return the content in which [element] should be drawn (when using `Modifier.element(key)`) or
-     * composed (when using `MovableElement(key)`) during the given [transition]. If this element
-     * should not be drawn or composed in neither [transition.fromContent] nor
-     * [transition.toContent], return `null`.
+     * composed (when using `MovableElement(key)`) during the given [transition].
      *
      * Important: For [MovableElements][ContentScope.MovableElement], this content picker will
      * *always* be used during transitions to decide whether we should compose that element in a
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/nestedscroll/PriorityNestedScrollConnection.kt b/packages/SystemUI/compose/scene/src/com/android/compose/nestedscroll/PriorityNestedScrollConnection.kt
index 228f7ba..16fb533b 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/nestedscroll/PriorityNestedScrollConnection.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/nestedscroll/PriorityNestedScrollConnection.kt
@@ -129,7 +129,11 @@
         return onPriorityStop(available)
     }
 
-    /** Method to call before destroying the object or to reset the initial state. */
+    /**
+     * Method to call before destroying the object or to reset the initial state.
+     *
+     * TODO(b/303224944) This method should be removed.
+     */
     fun reset() {
         // Step 3c: To ensure that an onStop is always called for every onStart.
         onPriorityStop(velocity = Velocity.Zero)
diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/NestedScrollToSceneTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/NestedScrollToSceneTest.kt
index 9ebc426..d8a06f5 100644
--- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/NestedScrollToSceneTest.kt
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/NestedScrollToSceneTest.kt
@@ -23,6 +23,9 @@
 import androidx.compose.foundation.layout.fillMaxSize
 import androidx.compose.foundation.layout.size
 import androidx.compose.runtime.Composable
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.setValue
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.geometry.Offset
 import androidx.compose.ui.platform.LocalViewConfiguration
@@ -266,4 +269,38 @@
         val transition = assertThat(state.transitionState).isTransition()
         assertThat(transition).hasProgress(0.5f)
     }
+
+    @Test
+    fun resetScrollTracking_afterMissingPointerUpEvent() {
+        var canScroll = true
+        var hasScrollable by mutableStateOf(true)
+        val state = setup2ScenesAndScrollTouchSlop {
+            if (hasScrollable) {
+                Modifier.scrollable(rememberScrollableState { if (canScroll) it else 0f }, Vertical)
+            } else {
+                Modifier
+            }
+        }
+
+        // The gesture is consumed by the component in the scene.
+        scrollUp(percent = 0.2f)
+
+        // STL keeps track of the scroll consumed. The scene remains in Idle.
+        assertThat(state.transitionState).isIdle()
+
+        // The scrollable component disappears, and does not send the signal (pointer up) to reset
+        // the consumed amount.
+        hasScrollable = false
+        pointerUp()
+
+        // A new scrollable component appears and allows the scene to consume the scroll.
+        hasScrollable = true
+        canScroll = false
+        pointerDownAndScrollTouchSlop()
+        scrollUp(percent = 0.2f)
+
+        // STL can only start the transition if it has reset the amount of scroll consumed.
+        val transition = assertThat(state.transitionState).isTransition()
+        assertThat(transition).hasProgress(0.2f)
+    }
 }
diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/quickaffordance/shared/model/KeyguardPreviewConstants.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/quickaffordance/shared/model/KeyguardPreviewConstants.kt
index 08ee602..a3f40d4 100644
--- a/packages/SystemUI/customization/src/com/android/systemui/shared/quickaffordance/shared/model/KeyguardPreviewConstants.kt
+++ b/packages/SystemUI/customization/src/com/android/systemui/shared/quickaffordance/shared/model/KeyguardPreviewConstants.kt
@@ -18,10 +18,17 @@
 package com.android.systemui.shared.quickaffordance.shared.model
 
 object KeyguardPreviewConstants {
+    const val MESSAGE_ID_DEFAULT_PREVIEW = 707
     const val MESSAGE_ID_HIDE_SMART_SPACE = 1111
-    const val KEY_HIDE_SMART_SPACE = "hide_smart_space"
+    const val MESSAGE_ID_PREVIEW_QUICK_AFFORDANCE_SELECTED = 1988
     const val MESSAGE_ID_SLOT_SELECTED = 1337
-    const val KEY_SLOT_ID = "slot_id"
-    const val KEY_INITIALLY_SELECTED_SLOT_ID = "initially_selected_slot_id"
+    const val MESSAGE_ID_START_CUSTOMIZING_QUICK_AFFORDANCES = 214
+
+    const val KEY_HIDE_SMART_SPACE = "hide_smart_space"
     const val KEY_HIGHLIGHT_QUICK_AFFORDANCES = "highlight_quick_affordances"
+    const val KEY_INITIALLY_SELECTED_SLOT_ID = "initially_selected_slot_id"
+    const val KEY_QUICK_AFFORDANCE_ID = "quick_affordance_id"
+    const val KEY_SLOT_ID = "slot_id"
+
+    const val KEYGUARD_QUICK_AFFORDANCE_ID_NONE = "none"
 }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/AuthMethodBouncerViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/AuthMethodBouncerViewModelTest.kt
index c9fa671..deef652 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/AuthMethodBouncerViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/AuthMethodBouncerViewModelTest.kt
@@ -22,14 +22,14 @@
 import com.android.systemui.authentication.data.repository.FakeAuthenticationRepository
 import com.android.systemui.authentication.data.repository.fakeAuthenticationRepository
 import com.android.systemui.authentication.shared.model.AuthenticationMethodModel
-import com.android.systemui.bouncer.domain.interactor.bouncerInteractor
-import com.android.systemui.bouncer.domain.interactor.simBouncerInteractor
 import com.android.systemui.coroutines.collectLastValue
 import com.android.systemui.kosmos.testScope
+import com.android.systemui.lifecycle.activateIn
 import com.android.systemui.testKosmos
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.flow.MutableStateFlow
 import kotlinx.coroutines.test.runTest
+import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
 
@@ -39,17 +39,16 @@
 
     private val kosmos = testKosmos()
     private val testScope = kosmos.testScope
-    private val bouncerInteractor by lazy { kosmos.bouncerInteractor }
-    private val underTest by lazy {
-        PinBouncerViewModel(
-            applicationContext = context,
-            viewModelScope = testScope.backgroundScope,
-            interactor = bouncerInteractor,
+    private val underTest =
+        kosmos.pinBouncerViewModelFactory.create(
             isInputEnabled = MutableStateFlow(true),
-            simBouncerInteractor = kosmos.simBouncerInteractor,
-            authenticationMethod = AuthenticationMethodModel.Pin,
             onIntentionalUserInput = {},
+            authenticationMethod = AuthenticationMethodModel.Pin,
         )
+
+    @Before
+    fun setUp() {
+        underTest.activateIn(testScope)
     }
 
     @Test
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/BouncerMessageViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/BouncerMessageViewModelTest.kt
index 4f5d0e5..c161525 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/BouncerMessageViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/BouncerMessageViewModelTest.kt
@@ -52,6 +52,7 @@
 import com.android.systemui.keyguard.shared.model.FailFingerprintAuthenticationStatus
 import com.android.systemui.keyguard.shared.model.HelpFingerprintAuthenticationStatus
 import com.android.systemui.kosmos.testScope
+import com.android.systemui.lifecycle.activateIn
 import com.android.systemui.res.R
 import com.android.systemui.testKosmos
 import com.android.systemui.user.data.repository.fakeUserRepository
@@ -87,6 +88,7 @@
             intArrayOf(ignoreHelpMessageId)
         )
         underTest = kosmos.bouncerMessageViewModel
+        underTest.activateIn(testScope)
         overrideResource(R.string.kg_trust_agent_disabled, "Trust agent is unavailable")
         kosmos.fakeSystemPropertiesHelper.set(
             DeviceUnlockedInteractor.SYS_BOOT_REASON_PROP,
@@ -428,6 +430,22 @@
                 .isEqualTo("Can’t unlock with face. Too many attempts.")
         }
 
+    @Test
+    fun startLockdownCountdown_onActivated() =
+        testScope.runTest {
+            val bouncerMessage by collectLastValue(underTest.message)
+            val lockoutSeconds = 200 * 1000 // 200 second lockout
+            kosmos.fakeAuthenticationRepository.setAuthenticationMethod(Pin)
+            kosmos.fakeAuthenticationRepository.reportLockoutStarted(lockoutSeconds)
+            runCurrent()
+
+            assertThat(bouncerMessage?.text).isEqualTo("Try again in 200 seconds.")
+            advanceTimeBy(100.seconds)
+            assertThat(bouncerMessage?.text).isEqualTo("Try again in 100 seconds.")
+            advanceTimeBy(101.seconds)
+            assertThat(bouncerMessage?.text).isEqualTo("Enter PIN")
+        }
+
     private fun TestScope.verifyMessagesForAuthFlags(
         vararg authFlagToMessagePair: Pair<Int, Pair<String, String?>>
     ) {
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/BouncerSceneActionsViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/BouncerSceneActionsViewModelTest.kt
new file mode 100644
index 0000000..a86a0c0
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/BouncerSceneActionsViewModelTest.kt
@@ -0,0 +1,77 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.bouncer.ui.viewmodel
+
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.compose.animation.scene.Back
+import com.android.compose.animation.scene.Swipe
+import com.android.compose.animation.scene.SwipeDirection
+import com.android.compose.animation.scene.UserActionResult
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.flags.EnableSceneContainer
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.lifecycle.activateIn
+import com.android.systemui.scene.domain.startable.sceneContainerStartable
+import com.android.systemui.scene.shared.model.Scenes
+import com.android.systemui.scene.shared.model.fakeSceneDataSource
+import com.android.systemui.testKosmos
+import com.android.systemui.truth.containsEntriesExactly
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+@EnableSceneContainer
+class BouncerSceneActionsViewModelTest : SysuiTestCase() {
+
+    private val kosmos = testKosmos()
+    private val testScope = kosmos.testScope
+
+    private lateinit var underTest: BouncerSceneActionsViewModel
+
+    @Before
+    fun setUp() {
+        kosmos.sceneContainerStartable.start()
+        underTest = kosmos.bouncerSceneActionsViewModel
+        underTest.activateIn(testScope)
+    }
+
+    @Test
+    fun actions() =
+        testScope.runTest {
+            val actions by collectLastValue(underTest.actions)
+            kosmos.fakeSceneDataSource.changeScene(Scenes.QuickSettings)
+            runCurrent()
+
+            kosmos.fakeSceneDataSource.changeScene(Scenes.Bouncer)
+            runCurrent()
+
+            assertThat(actions)
+                .containsEntriesExactly(
+                    Back to UserActionResult(Scenes.QuickSettings),
+                    Swipe(SwipeDirection.Down) to UserActionResult(Scenes.QuickSettings),
+                )
+        }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/BouncerSceneContentViewModelTest.kt
similarity index 88%
rename from packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModelTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/BouncerSceneContentViewModelTest.kt
index ccddc9c..9bddcd2 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/BouncerSceneContentViewModelTest.kt
@@ -18,10 +18,6 @@
 
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
-import com.android.compose.animation.scene.Back
-import com.android.compose.animation.scene.Swipe
-import com.android.compose.animation.scene.SwipeDirection
-import com.android.compose.animation.scene.UserActionResult
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.authentication.data.repository.FakeAuthenticationRepository
 import com.android.systemui.authentication.data.repository.fakeAuthenticationRepository
@@ -38,11 +34,9 @@
 import com.android.systemui.flags.Flags
 import com.android.systemui.flags.fakeFeatureFlagsClassic
 import com.android.systemui.kosmos.testScope
+import com.android.systemui.lifecycle.activateIn
 import com.android.systemui.scene.domain.startable.sceneContainerStartable
-import com.android.systemui.scene.shared.model.Scenes
-import com.android.systemui.scene.shared.model.fakeSceneDataSource
 import com.android.systemui.testKosmos
-import com.android.systemui.truth.containsEntriesExactly
 import com.google.common.truth.Truth.assertThat
 import com.google.common.truth.Truth.assertWithMessage
 import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -62,17 +56,18 @@
 @SmallTest
 @RunWith(AndroidJUnit4::class)
 @EnableSceneContainer
-class BouncerViewModelTest : SysuiTestCase() {
+class BouncerSceneContentViewModelTest : SysuiTestCase() {
 
     private val kosmos = testKosmos()
     private val testScope = kosmos.testScope
 
-    private lateinit var underTest: BouncerViewModel
+    private lateinit var underTest: BouncerSceneContentViewModel
 
     @Before
     fun setUp() {
         kosmos.sceneContainerStartable.start()
-        underTest = kosmos.bouncerViewModel
+        underTest = kosmos.bouncerSceneContentViewModel
+        underTest.activateIn(testScope)
     }
 
     @Test
@@ -201,23 +196,6 @@
             assertThat(isFoldSplitRequired).isTrue()
         }
 
-    @Test
-    fun destinationScenes() =
-        testScope.runTest {
-            val destinationScenes by collectLastValue(underTest.destinationScenes)
-            kosmos.fakeSceneDataSource.changeScene(Scenes.QuickSettings)
-            runCurrent()
-
-            kosmos.fakeSceneDataSource.changeScene(Scenes.Bouncer)
-            runCurrent()
-
-            assertThat(destinationScenes)
-                .containsEntriesExactly(
-                    Back to UserActionResult(Scenes.QuickSettings),
-                    Swipe(SwipeDirection.Down) to UserActionResult(Scenes.QuickSettings),
-                )
-        }
-
     private fun authMethodsToTest(): List<AuthenticationMethodModel> {
         return listOf(None, Pin, Password, Pattern, Sim)
     }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModelTest.kt
index a09189e..492543f 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModelTest.kt
@@ -31,6 +31,7 @@
 import com.android.systemui.inputmethod.data.repository.fakeInputMethodRepository
 import com.android.systemui.inputmethod.domain.interactor.inputMethodInteractor
 import com.android.systemui.kosmos.testScope
+import com.android.systemui.lifecycle.activateIn
 import com.android.systemui.res.R
 import com.android.systemui.scene.domain.interactor.sceneInteractor
 import com.android.systemui.scene.shared.model.Scenes
@@ -44,7 +45,6 @@
 import kotlin.time.Duration.Companion.seconds
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.flow.MutableStateFlow
-import kotlinx.coroutines.flow.asStateFlow
 import kotlinx.coroutines.test.TestScope
 import kotlinx.coroutines.test.advanceTimeBy
 import kotlinx.coroutines.test.runCurrent
@@ -68,12 +68,8 @@
     private val isInputEnabled = MutableStateFlow(true)
 
     private val underTest =
-        PasswordBouncerViewModel(
-            viewModelScope = testScope.backgroundScope,
-            isInputEnabled = isInputEnabled.asStateFlow(),
-            interactor = bouncerInteractor,
-            inputMethodInteractor = inputMethodInteractor,
-            selectedUserInteractor = selectedUserInteractor,
+        kosmos.passwordBouncerViewModelFactory.create(
+            isInputEnabled = isInputEnabled,
             onIntentionalUserInput = {},
         )
 
@@ -81,6 +77,7 @@
     fun setUp() {
         overrideResource(R.string.keyguard_enter_your_password, ENTER_YOUR_PASSWORD)
         overrideResource(R.string.kg_wrong_password, WRONG_PASSWORD)
+        underTest.activateIn(testScope)
     }
 
     @Test
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/PatternBouncerViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/PatternBouncerViewModelTest.kt
index 14d3634..7c773a9 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/PatternBouncerViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/PatternBouncerViewModelTest.kt
@@ -26,9 +26,9 @@
 import com.android.systemui.authentication.domain.interactor.authenticationInteractor
 import com.android.systemui.authentication.shared.model.AuthenticationMethodModel
 import com.android.systemui.authentication.shared.model.AuthenticationPatternCoordinate as Point
-import com.android.systemui.bouncer.domain.interactor.bouncerInteractor
 import com.android.systemui.coroutines.collectLastValue
 import com.android.systemui.kosmos.testScope
+import com.android.systemui.lifecycle.activateIn
 import com.android.systemui.res.R
 import com.android.systemui.scene.domain.interactor.sceneInteractor
 import com.android.systemui.scene.shared.model.Scenes
@@ -54,17 +54,12 @@
     private val testScope = kosmos.testScope
     private val authenticationInteractor by lazy { kosmos.authenticationInteractor }
     private val sceneInteractor by lazy { kosmos.sceneInteractor }
-    private val bouncerInteractor by lazy { kosmos.bouncerInteractor }
-    private val bouncerViewModel by lazy { kosmos.bouncerViewModel }
-    private val underTest by lazy {
-        PatternBouncerViewModel(
-            applicationContext = context,
-            viewModelScope = testScope.backgroundScope,
-            interactor = bouncerInteractor,
+    private val bouncerViewModel by lazy { kosmos.bouncerSceneContentViewModel }
+    private val underTest =
+        kosmos.patternBouncerViewModelFactory.create(
             isInputEnabled = MutableStateFlow(true).asStateFlow(),
             onIntentionalUserInput = {},
         )
-    }
 
     private val containerSize = 90 // px
     private val dotSize = 30 // px
@@ -73,6 +68,7 @@
     fun setUp() {
         overrideResource(R.string.keyguard_enter_your_pattern, ENTER_YOUR_PATTERN)
         overrideResource(R.string.kg_wrong_pattern, WRONG_PATTERN)
+        underTest.activateIn(testScope)
     }
 
     @Test
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModelTest.kt
index 89bafb9..8d82e97 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModelTest.kt
@@ -31,10 +31,9 @@
 import com.android.systemui.authentication.domain.interactor.authenticationInteractor
 import com.android.systemui.authentication.shared.model.AuthenticationMethodModel
 import com.android.systemui.bouncer.data.repository.fakeSimBouncerRepository
-import com.android.systemui.bouncer.domain.interactor.bouncerInteractor
-import com.android.systemui.bouncer.domain.interactor.simBouncerInteractor
 import com.android.systemui.coroutines.collectLastValue
 import com.android.systemui.kosmos.testScope
+import com.android.systemui.lifecycle.activateIn
 import com.android.systemui.res.R
 import com.android.systemui.scene.domain.interactor.sceneInteractor
 import com.android.systemui.scene.shared.model.Scenes
@@ -44,7 +43,6 @@
 import kotlin.random.nextInt
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.flow.MutableStateFlow
-import kotlinx.coroutines.flow.asStateFlow
 import kotlinx.coroutines.flow.map
 import kotlinx.coroutines.test.TestScope
 import kotlinx.coroutines.test.runCurrent
@@ -62,24 +60,18 @@
     private val testScope = kosmos.testScope
     private val sceneInteractor by lazy { kosmos.sceneInteractor }
     private val authenticationInteractor by lazy { kosmos.authenticationInteractor }
-    private val bouncerInteractor by lazy { kosmos.bouncerInteractor }
-    private lateinit var underTest: PinBouncerViewModel
+    private val underTest =
+        kosmos.pinBouncerViewModelFactory.create(
+            isInputEnabled = MutableStateFlow(true),
+            onIntentionalUserInput = {},
+            authenticationMethod = AuthenticationMethodModel.Pin,
+        )
 
     @Before
     fun setUp() {
-        underTest =
-            PinBouncerViewModel(
-                applicationContext = context,
-                viewModelScope = testScope.backgroundScope,
-                interactor = bouncerInteractor,
-                isInputEnabled = MutableStateFlow(true).asStateFlow(),
-                simBouncerInteractor = kosmos.simBouncerInteractor,
-                authenticationMethod = AuthenticationMethodModel.Pin,
-                onIntentionalUserInput = {},
-            )
-
         overrideResource(R.string.keyguard_enter_your_pin, ENTER_YOUR_PIN)
         overrideResource(R.string.kg_wrong_pin, WRONG_PIN)
+        underTest.activateIn(testScope)
     }
 
     @Test
@@ -96,14 +88,10 @@
     fun simBouncerViewModel_simAreaIsVisible() =
         testScope.runTest {
             val underTest =
-                PinBouncerViewModel(
-                    applicationContext = context,
-                    viewModelScope = testScope.backgroundScope,
-                    interactor = bouncerInteractor,
-                    isInputEnabled = MutableStateFlow(true).asStateFlow(),
-                    simBouncerInteractor = kosmos.simBouncerInteractor,
-                    authenticationMethod = AuthenticationMethodModel.Sim,
+                kosmos.pinBouncerViewModelFactory.create(
+                    isInputEnabled = MutableStateFlow(true),
                     onIntentionalUserInput = {},
+                    authenticationMethod = AuthenticationMethodModel.Sim,
                 )
 
             assertThat(underTest.isSimAreaVisible).isTrue()
@@ -125,14 +113,10 @@
     fun simBouncerViewModel_autoConfirmEnabled_hintedPinLengthIsNull() =
         testScope.runTest {
             val underTest =
-                PinBouncerViewModel(
-                    applicationContext = context,
-                    viewModelScope = testScope.backgroundScope,
-                    interactor = bouncerInteractor,
-                    isInputEnabled = MutableStateFlow(true).asStateFlow(),
-                    simBouncerInteractor = kosmos.simBouncerInteractor,
-                    authenticationMethod = AuthenticationMethodModel.Sim,
+                kosmos.pinBouncerViewModelFactory.create(
+                    isInputEnabled = MutableStateFlow(true),
                     onIntentionalUserInput = {},
+                    authenticationMethod = AuthenticationMethodModel.Pin,
                 )
             kosmos.fakeAuthenticationRepository.setAutoConfirmFeatureEnabled(true)
             val hintedPinLength by collectLastValue(underTest.hintedPinLength)
@@ -355,6 +339,7 @@
                 AuthenticationMethodModel.Pin
             )
             kosmos.fakeAuthenticationRepository.setAutoConfirmFeatureEnabled(true)
+            runCurrent()
 
             underTest.onPinButtonClicked(1)
 
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/CommunalSceneStartableTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/CommunalSceneStartableTest.kt
index 9ccf99b..70529cc 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/CommunalSceneStartableTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/CommunalSceneStartableTest.kt
@@ -112,7 +112,7 @@
             testScope.runTest {
                 val scene by collectLastValue(communalSceneInteractor.currentScene)
 
-                communalSceneInteractor.changeScene(CommunalScenes.Communal)
+                communalSceneInteractor.changeScene(CommunalScenes.Communal, "test")
                 assertThat(scene).isEqualTo(CommunalScenes.Communal)
 
                 communalSceneInteractor.setEditModeState(EditModeState.STARTING)
@@ -133,7 +133,7 @@
             testScope.runTest {
                 val scene by collectLastValue(communalSceneInteractor.currentScene)
 
-                communalSceneInteractor.changeScene(CommunalScenes.Communal)
+                communalSceneInteractor.changeScene(CommunalScenes.Communal, "test")
                 assertThat(scene).isEqualTo(CommunalScenes.Communal)
 
                 communalSceneInteractor.setIsLaunchingWidget(true)
@@ -154,7 +154,7 @@
             testScope.runTest {
                 val scene by collectLastValue(communalSceneInteractor.currentScene)
 
-                communalSceneInteractor.changeScene(CommunalScenes.Communal)
+                communalSceneInteractor.changeScene(CommunalScenes.Communal, "test")
                 assertThat(scene).isEqualTo(CommunalScenes.Communal)
 
                 communalSceneInteractor.setIsLaunchingWidget(false)
@@ -174,7 +174,7 @@
         with(kosmos) {
             testScope.runTest {
                 val scene by collectLastValue(communalSceneInteractor.currentScene)
-                communalSceneInteractor.changeScene(CommunalScenes.Communal)
+                communalSceneInteractor.changeScene(CommunalScenes.Communal, "test")
                 assertThat(scene).isEqualTo(CommunalScenes.Communal)
 
                 communalInteractor.setEditModeOpen(true)
@@ -213,7 +213,7 @@
             testScope.runTest {
                 whenever(centralSurfaces.isLaunchingActivityOverLockscreen).thenReturn(false)
                 val scene by collectLastValue(communalSceneInteractor.currentScene)
-                communalSceneInteractor.changeScene(CommunalScenes.Communal)
+                communalSceneInteractor.changeScene(CommunalScenes.Communal, "test")
                 assertThat(scene).isEqualTo(CommunalScenes.Communal)
 
                 updateDocked(true)
@@ -233,7 +233,7 @@
             testScope.runTest {
                 whenever(centralSurfaces.isLaunchingActivityOverLockscreen).thenReturn(true)
                 val scene by collectLastValue(communalSceneInteractor.currentScene)
-                communalSceneInteractor.changeScene(CommunalScenes.Communal)
+                communalSceneInteractor.changeScene(CommunalScenes.Communal, "test")
                 assertThat(scene).isEqualTo(CommunalScenes.Communal)
 
                 updateDocked(true)
@@ -270,7 +270,7 @@
         with(kosmos) {
             testScope.runTest {
                 val scene by collectLastValue(communalSceneInteractor.currentScene)
-                communalSceneInteractor.changeScene(CommunalScenes.Communal)
+                communalSceneInteractor.changeScene(CommunalScenes.Communal, "test")
                 assertThat(scene).isEqualTo(CommunalScenes.Communal)
 
                 fakeKeyguardTransitionRepository.sendTransitionSteps(
@@ -292,7 +292,7 @@
         with(kosmos) {
             testScope.runTest {
                 val scene by collectLastValue(communalSceneInteractor.currentScene)
-                communalSceneInteractor.changeScene(CommunalScenes.Communal)
+                communalSceneInteractor.changeScene(CommunalScenes.Communal, "test")
                 assertThat(scene).isEqualTo(CommunalScenes.Communal)
 
                 fakeKeyguardTransitionRepository.sendTransitionSteps(
@@ -320,7 +320,7 @@
     fun dockingOnLockscreen_forcesCommunal() =
         with(kosmos) {
             testScope.runTest {
-                communalSceneInteractor.changeScene(CommunalScenes.Blank)
+                communalSceneInteractor.changeScene(CommunalScenes.Blank, "test")
                 val scene by collectLastValue(communalSceneInteractor.currentScene)
 
                 // device is docked while on the lockscreen
@@ -342,7 +342,7 @@
     fun dockingOnLockscreen_doesNotForceCommunalIfDreamStarts() =
         with(kosmos) {
             testScope.runTest {
-                communalSceneInteractor.changeScene(CommunalScenes.Blank)
+                communalSceneInteractor.changeScene(CommunalScenes.Blank, "test")
                 val scene by collectLastValue(communalSceneInteractor.currentScene)
 
                 // device is docked while on the lockscreen
@@ -374,7 +374,7 @@
             testScope.runTest {
                 // Device is dreaming and on communal.
                 updateDreaming(true)
-                communalSceneInteractor.changeScene(CommunalScenes.Communal)
+                communalSceneInteractor.changeScene(CommunalScenes.Communal, "test")
 
                 val scene by collectLastValue(communalSceneInteractor.currentScene)
                 assertThat(scene).isEqualTo(CommunalScenes.Communal)
@@ -391,7 +391,7 @@
             testScope.runTest {
                 // Device is not dreaming and on communal.
                 updateDreaming(false)
-                communalSceneInteractor.changeScene(CommunalScenes.Communal)
+                communalSceneInteractor.changeScene(CommunalScenes.Communal, "test")
 
                 // Scene stays as Communal
                 advanceTimeBy(SCREEN_TIMEOUT.milliseconds)
@@ -406,7 +406,7 @@
             testScope.runTest {
                 // Device is dreaming and on communal.
                 updateDreaming(true)
-                communalSceneInteractor.changeScene(CommunalScenes.Communal)
+                communalSceneInteractor.changeScene(CommunalScenes.Communal, "test")
 
                 val scene by collectLastValue(communalSceneInteractor.currentScene)
                 assertThat(scene).isEqualTo(CommunalScenes.Communal)
@@ -429,7 +429,7 @@
             testScope.runTest {
                 // Device is on communal, but not dreaming.
                 updateDreaming(false)
-                communalSceneInteractor.changeScene(CommunalScenes.Communal)
+                communalSceneInteractor.changeScene(CommunalScenes.Communal, "test")
 
                 val scene by collectLastValue(communalSceneInteractor.currentScene)
                 assertThat(scene).isEqualTo(CommunalScenes.Communal)
@@ -450,7 +450,7 @@
         with(kosmos) {
             testScope.runTest {
                 // Device is on communal.
-                communalSceneInteractor.changeScene(CommunalScenes.Communal)
+                communalSceneInteractor.changeScene(CommunalScenes.Communal, "test")
 
                 // Device stays on the hub after the timeout since we're not dreaming.
                 advanceTimeBy(SCREEN_TIMEOUT.milliseconds * 2)
@@ -471,7 +471,7 @@
             testScope.runTest {
                 // Device is dreaming and on communal.
                 updateDreaming(true)
-                communalSceneInteractor.changeScene(CommunalScenes.Communal)
+                communalSceneInteractor.changeScene(CommunalScenes.Communal, "test")
 
                 val scene by collectLastValue(communalSceneInteractor.currentScene)
                 assertThat(scene).isEqualTo(CommunalScenes.Communal)
@@ -500,7 +500,7 @@
 
                 // Device is dreaming and on communal.
                 updateDreaming(true)
-                communalSceneInteractor.changeScene(CommunalScenes.Communal)
+                communalSceneInteractor.changeScene(CommunalScenes.Communal, "test")
 
                 val scene by collectLastValue(communalSceneInteractor.currentScene)
                 assertThat(scene).isEqualTo(CommunalScenes.Communal)
@@ -520,7 +520,7 @@
         with(kosmos) {
             testScope.runTest {
                 val scene by collectLastValue(communalSceneInteractor.currentScene)
-                communalSceneInteractor.changeScene(CommunalScenes.Blank)
+                communalSceneInteractor.changeScene(CommunalScenes.Blank, "test")
                 assertThat(scene).isEqualTo(CommunalScenes.Blank)
 
                 fakeKeyguardTransitionRepository.sendTransitionSteps(
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorTest.kt
index e57a4cb..864795b 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorTest.kt
@@ -482,7 +482,7 @@
         testScope.runTest {
             val targetScene = CommunalScenes.Communal
 
-            underTest.changeScene(targetScene)
+            underTest.changeScene(targetScene, "test")
 
             val desiredScene = collectLastValue(communalRepository.currentScene)
             runCurrent()
@@ -635,7 +635,7 @@
             runCurrent()
             assertThat(isCommunalShowing()).isEqualTo(false)
 
-            underTest.changeScene(CommunalScenes.Communal)
+            underTest.changeScene(CommunalScenes.Communal, "test")
 
             isCommunalShowing = collectLastValue(underTest.isCommunalShowing)
             runCurrent()
@@ -659,12 +659,12 @@
             assertThat(isCommunalShowing).isFalse()
 
             // Verify scene changes (without the flag) to communal sets the value to true
-            underTest.changeScene(CommunalScenes.Communal)
+            underTest.changeScene(CommunalScenes.Communal, "test")
             runCurrent()
             assertThat(isCommunalShowing).isTrue()
 
             // Verify scene changes (without the flag) to blank sets the value back to false
-            underTest.changeScene(CommunalScenes.Blank)
+            underTest.changeScene(CommunalScenes.Blank, "test")
             runCurrent()
             assertThat(isCommunalShowing).isFalse()
         }
@@ -679,7 +679,7 @@
             assertThat(isCommunalShowing).isFalse()
 
             // Verify scene changes without the flag doesn't have any impact
-            underTest.changeScene(CommunalScenes.Communal)
+            underTest.changeScene(CommunalScenes.Communal, "test")
             runCurrent()
             assertThat(isCommunalShowing).isFalse()
 
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalSceneInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalSceneInteractorTest.kt
index 43293c7..ed7e910 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalSceneInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalSceneInteractorTest.kt
@@ -53,7 +53,7 @@
             val currentScene by collectLastValue(underTest.currentScene)
             assertThat(currentScene).isEqualTo(CommunalScenes.Blank)
 
-            underTest.changeScene(CommunalScenes.Communal)
+            underTest.changeScene(CommunalScenes.Communal, "test")
             assertThat(currentScene).isEqualTo(CommunalScenes.Communal)
         }
 
@@ -63,7 +63,7 @@
             val currentScene by collectLastValue(underTest.currentScene)
             assertThat(currentScene).isEqualTo(CommunalScenes.Blank)
 
-            underTest.snapToScene(CommunalScenes.Communal)
+            underTest.snapToScene(CommunalScenes.Communal, "test")
             assertThat(currentScene).isEqualTo(CommunalScenes.Communal)
         }
 
@@ -75,6 +75,7 @@
             assertThat(currentScene).isEqualTo(CommunalScenes.Blank)
             underTest.snapToScene(
                 CommunalScenes.Communal,
+                "test",
                 ActivityTransitionAnimator.TIMINGS.totalDuration
             )
             assertThat(currentScene).isEqualTo(CommunalScenes.Blank)
@@ -86,7 +87,7 @@
     fun changeSceneForActivityStartOnDismissKeyguard() =
         testScope.runTest {
             val currentScene by collectLastValue(underTest.currentScene)
-            underTest.snapToScene(CommunalScenes.Communal)
+            underTest.snapToScene(CommunalScenes.Communal, "test")
             assertThat(currentScene).isEqualTo(CommunalScenes.Communal)
 
             underTest.changeSceneForActivityStartOnDismissKeyguard()
@@ -97,7 +98,7 @@
     fun changeSceneForActivityStartOnDismissKeyguard_willNotChangeScene_forEditModeActivity() =
         testScope.runTest {
             val currentScene by collectLastValue(underTest.currentScene)
-            underTest.snapToScene(CommunalScenes.Communal)
+            underTest.snapToScene(CommunalScenes.Communal, "test")
             assertThat(currentScene).isEqualTo(CommunalScenes.Communal)
 
             underTest.setEditModeState(EditModeState.STARTING)
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 d6712f0..c5518b0 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
@@ -53,6 +53,7 @@
 import com.android.systemui.power.domain.interactor.powerInteractor
 import com.android.systemui.testKosmos
 import com.google.common.truth.Truth.assertThat
+import kotlin.time.Duration.Companion.seconds
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.flow.MutableSharedFlow
 import kotlinx.coroutines.flow.MutableStateFlow
@@ -78,6 +79,7 @@
 
     private val underTest by lazy { kosmos.communalSceneTransitionInteractor }
     private val keyguardTransitionRepository by lazy { kosmos.realKeyguardTransitionRepository }
+    private val keyguardRepository by lazy { kosmos.fakeKeyguardRepository }
 
     private val ownerName = CommunalSceneTransitionInteractor::class.java.simpleName
     private val progress = MutableSharedFlow<Float>()
@@ -789,4 +791,47 @@
                     )
                 )
         }
+
+    /** Verifies that we correctly transition to GONE after keyguard goes away */
+    @Test
+    fun transition_to_blank_after_unlock_should_go_to_gone() =
+        testScope.runTest {
+            keyguardRepository.setKeyguardShowing(true)
+            sceneTransitions.value = Idle(CommunalScenes.Communal)
+
+            val currentStep by collectLastValue(keyguardTransitionRepository.transitions)
+
+            assertThat(currentStep)
+                .isEqualTo(
+                    TransitionStep(
+                        from = LOCKSCREEN,
+                        to = GLANCEABLE_HUB,
+                        transitionState = FINISHED,
+                        value = 1f,
+                        ownerName = ownerName,
+                    )
+                )
+
+            // Keyguard starts exiting after a while, then fully exits after some time.
+            advanceTimeBy(1.seconds)
+            keyguardRepository.setKeyguardGoingAway(true)
+            advanceTimeBy(2.seconds)
+            keyguardRepository.setKeyguardGoingAway(false)
+            keyguardRepository.setKeyguardShowing(false)
+            runCurrent()
+
+            // We snap to the blank scene as a result of keyguard going away.
+            sceneTransitions.value = Idle(CommunalScenes.Blank)
+
+            assertThat(currentStep)
+                .isEqualTo(
+                    TransitionStep(
+                        from = GLANCEABLE_HUB,
+                        to = GONE,
+                        transitionState = FINISHED,
+                        value = 1f,
+                        ownerName = ownerName,
+                    )
+                )
+        }
 }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalTutorialInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalTutorialInteractorTest.kt
index 3a23e14..7e28e19 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalTutorialInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalTutorialInteractorTest.kt
@@ -158,7 +158,7 @@
             kosmos.setCommunalAvailable(true)
             communalTutorialRepository.setTutorialSettingState(HUB_MODE_TUTORIAL_NOT_STARTED)
 
-            communalInteractor.changeScene(CommunalScenes.Blank)
+            communalInteractor.changeScene(CommunalScenes.Blank, "test")
 
             assertThat(tutorialSettingState).isEqualTo(HUB_MODE_TUTORIAL_NOT_STARTED)
         }
@@ -171,7 +171,7 @@
             goToCommunal()
             communalTutorialRepository.setTutorialSettingState(HUB_MODE_TUTORIAL_STARTED)
 
-            communalInteractor.changeScene(CommunalScenes.Blank)
+            communalInteractor.changeScene(CommunalScenes.Blank, "test")
 
             assertThat(tutorialSettingState).isEqualTo(HUB_MODE_TUTORIAL_COMPLETED)
         }
@@ -184,13 +184,13 @@
             goToCommunal()
             communalTutorialRepository.setTutorialSettingState(HUB_MODE_TUTORIAL_COMPLETED)
 
-            communalInteractor.changeScene(CommunalScenes.Blank)
+            communalInteractor.changeScene(CommunalScenes.Blank, "test")
 
             assertThat(tutorialSettingState).isEqualTo(HUB_MODE_TUTORIAL_COMPLETED)
         }
 
     private suspend fun goToCommunal() {
         kosmos.setCommunalAvailable(true)
-        communalInteractor.changeScene(CommunalScenes.Communal)
+        communalInteractor.changeScene(CommunalScenes.Communal, "test")
     }
 }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/WidgetTrampolineInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/WidgetTrampolineInteractorTest.kt
new file mode 100644
index 0000000..b3ffc71
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/WidgetTrampolineInteractorTest.kt
@@ -0,0 +1,220 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.communal.domain.interactor
+
+import android.app.ActivityManager.RunningTaskInfo
+import android.app.usage.UsageEvents
+import android.content.pm.UserInfo
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.common.usagestats.data.repository.fakeUsageStatsRepository
+import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
+import com.android.systemui.keyguard.shared.model.KeyguardState
+import com.android.systemui.keyguard.shared.model.TransitionState
+import com.android.systemui.keyguard.shared.model.TransitionStep
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.plugins.activityStarter
+import com.android.systemui.settings.fakeUserTracker
+import com.android.systemui.shared.system.taskStackChangeListeners
+import com.android.systemui.testKosmos
+import com.android.systemui.util.time.fakeSystemClock
+import kotlin.time.Duration
+import kotlin.time.Duration.Companion.milliseconds
+import kotlin.time.Duration.Companion.seconds
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.advanceTimeBy
+import kotlinx.coroutines.test.currentTime
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.kotlin.any
+import org.mockito.kotlin.anyOrNull
+import org.mockito.kotlin.mock
+import org.mockito.kotlin.never
+import org.mockito.kotlin.times
+import org.mockito.kotlin.verify
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class WidgetTrampolineInteractorTest : SysuiTestCase() {
+
+    private val kosmos = testKosmos()
+    private val testScope = kosmos.testScope
+
+    private val activityStarter = kosmos.activityStarter
+    private val usageStatsRepository = kosmos.fakeUsageStatsRepository
+    private val taskStackChangeListeners = kosmos.taskStackChangeListeners
+    private val keyguardTransitionRepository = kosmos.fakeKeyguardTransitionRepository
+    private val userTracker = kosmos.fakeUserTracker
+    private val systemClock = kosmos.fakeSystemClock
+
+    private val underTest = kosmos.widgetTrampolineInteractor
+
+    @Before
+    fun setUp() {
+        userTracker.set(listOf(MAIN_USER), 0)
+        systemClock.setCurrentTimeMillis(testScope.currentTime)
+    }
+
+    @Test
+    fun testNewTaskStartsWhileOnHub_triggersUnlock() =
+        testScope.runTest {
+            transition(from = KeyguardState.LOCKSCREEN, to = KeyguardState.GLANCEABLE_HUB)
+            backgroundScope.launch { underTest.waitForActivityStartAndDismissKeyguard() }
+            runCurrent()
+
+            verify(activityStarter, never()).dismissKeyguardThenExecute(any(), anyOrNull(), any())
+            moveTaskToFront()
+
+            verify(activityStarter).dismissKeyguardThenExecute(any(), anyOrNull(), any())
+        }
+
+    @Test
+    fun testNewTaskStartsAfterExitingHub_doesNotTriggerUnlock() =
+        testScope.runTest {
+            transition(from = KeyguardState.LOCKSCREEN, to = KeyguardState.GLANCEABLE_HUB)
+            backgroundScope.launch { underTest.waitForActivityStartAndDismissKeyguard() }
+            runCurrent()
+
+            transition(from = KeyguardState.GLANCEABLE_HUB, to = KeyguardState.LOCKSCREEN)
+            moveTaskToFront()
+
+            verify(activityStarter, never()).dismissKeyguardThenExecute(any(), anyOrNull(), any())
+        }
+
+    @Test
+    fun testNewTaskStartsAfterTimeout_doesNotTriggerUnlock() =
+        testScope.runTest {
+            transition(from = KeyguardState.LOCKSCREEN, to = KeyguardState.GLANCEABLE_HUB)
+            backgroundScope.launch { underTest.waitForActivityStartAndDismissKeyguard() }
+            runCurrent()
+
+            advanceTime(2.seconds)
+            moveTaskToFront()
+
+            verify(activityStarter, never()).dismissKeyguardThenExecute(any(), anyOrNull(), any())
+        }
+
+    @Test
+    fun testActivityResumedWhileOnHub_triggersUnlock() =
+        testScope.runTest {
+            transition(from = KeyguardState.LOCKSCREEN, to = KeyguardState.GLANCEABLE_HUB)
+            backgroundScope.launch { underTest.waitForActivityStartAndDismissKeyguard() }
+            runCurrent()
+
+            addActivityEvent(UsageEvents.Event.ACTIVITY_RESUMED)
+            advanceTime(1.seconds)
+
+            verify(activityStarter).dismissKeyguardThenExecute(any(), anyOrNull(), any())
+        }
+
+    @Test
+    fun testActivityResumedAfterExitingHub_doesNotTriggerUnlock() =
+        testScope.runTest {
+            transition(from = KeyguardState.LOCKSCREEN, to = KeyguardState.GLANCEABLE_HUB)
+            backgroundScope.launch { underTest.waitForActivityStartAndDismissKeyguard() }
+            runCurrent()
+
+            transition(from = KeyguardState.GLANCEABLE_HUB, to = KeyguardState.LOCKSCREEN)
+            addActivityEvent(UsageEvents.Event.ACTIVITY_RESUMED)
+            advanceTime(1.seconds)
+
+            verify(activityStarter, never()).dismissKeyguardThenExecute(any(), anyOrNull(), any())
+        }
+
+    @Test
+    fun testActivityDestroyed_doesNotTriggerUnlock() =
+        testScope.runTest {
+            transition(from = KeyguardState.LOCKSCREEN, to = KeyguardState.GLANCEABLE_HUB)
+            backgroundScope.launch { underTest.waitForActivityStartAndDismissKeyguard() }
+            runCurrent()
+
+            addActivityEvent(UsageEvents.Event.ACTIVITY_DESTROYED)
+            advanceTime(1.seconds)
+
+            verify(activityStarter, never()).dismissKeyguardThenExecute(any(), anyOrNull(), any())
+        }
+
+    @Test
+    fun testMultipleActivityEvents_triggersUnlockOnlyOnce() =
+        testScope.runTest {
+            transition(from = KeyguardState.LOCKSCREEN, to = KeyguardState.GLANCEABLE_HUB)
+            backgroundScope.launch { underTest.waitForActivityStartAndDismissKeyguard() }
+            runCurrent()
+
+            addActivityEvent(UsageEvents.Event.ACTIVITY_RESUMED)
+            advanceTime(10.milliseconds)
+            addActivityEvent(UsageEvents.Event.ACTIVITY_RESUMED)
+            advanceTime(1.seconds)
+
+            verify(activityStarter, times(1)).dismissKeyguardThenExecute(any(), anyOrNull(), any())
+        }
+
+    private fun TestScope.advanceTime(duration: Duration) {
+        systemClock.advanceTime(duration.inWholeMilliseconds)
+        advanceTimeBy(duration)
+    }
+
+    private fun TestScope.addActivityEvent(type: Int) {
+        usageStatsRepository.addEvent(
+            instanceId = 1,
+            user = MAIN_USER.userHandle,
+            packageName = "pkg.test",
+            timestamp = systemClock.currentTimeMillis(),
+            type = type,
+        )
+        runCurrent()
+    }
+
+    private fun TestScope.moveTaskToFront() {
+        taskStackChangeListeners.listenerImpl.onTaskMovedToFront(mock<RunningTaskInfo>())
+        runCurrent()
+    }
+
+    private suspend fun TestScope.transition(from: KeyguardState, to: KeyguardState) {
+        keyguardTransitionRepository.sendTransitionSteps(
+            listOf(
+                TransitionStep(
+                    from = from,
+                    to = to,
+                    value = 0.1f,
+                    transitionState = TransitionState.STARTED,
+                    ownerName = "test",
+                ),
+                TransitionStep(
+                    from = from,
+                    to = to,
+                    value = 1f,
+                    transitionState = TransitionState.FINISHED,
+                    ownerName = "test",
+                ),
+            ),
+            testScope
+        )
+        runCurrent()
+    }
+
+    private companion object {
+        val MAIN_USER: UserInfo = UserInfo(0, "primary", UserInfo.FLAG_MAIN)
+    }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/widgets/CommunalTransitionAnimatorControllerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/widgets/CommunalTransitionAnimatorControllerTest.kt
index e36fd75..a052b07 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/widgets/CommunalTransitionAnimatorControllerTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/widgets/CommunalTransitionAnimatorControllerTest.kt
@@ -76,7 +76,7 @@
                 val launching by collectLastValue(communalSceneInteractor.isLaunchingWidget)
                 val scene by collectLastValue(communalSceneInteractor.currentScene)
 
-                communalSceneInteractor.changeScene(CommunalScenes.Communal)
+                communalSceneInteractor.changeScene(CommunalScenes.Communal, "test")
                 Truth.assertThat(scene).isEqualTo(CommunalScenes.Communal)
                 communalSceneInteractor.setIsLaunchingWidget(true)
                 assertTrue(launching!!)
@@ -103,7 +103,7 @@
                 val launching by collectLastValue(communalSceneInteractor.isLaunchingWidget)
                 val scene by collectLastValue(communalSceneInteractor.currentScene)
 
-                communalSceneInteractor.changeScene(CommunalScenes.Communal)
+                communalSceneInteractor.changeScene(CommunalScenes.Communal, "test")
                 Truth.assertThat(scene).isEqualTo(CommunalScenes.Communal)
                 communalSceneInteractor.setIsLaunchingWidget(true)
                 assertTrue(launching!!)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/widgets/EditWidgetsActivityControllerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/widgets/EditWidgetsActivityControllerTest.kt
deleted file mode 100644
index 50fdb31..0000000
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/widgets/EditWidgetsActivityControllerTest.kt
+++ /dev/null
@@ -1,107 +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.communal.widgets
-
-import android.app.Activity
-import android.app.Application.ActivityLifecycleCallbacks
-import android.os.Bundle
-import androidx.test.ext.junit.runners.AndroidJUnit4
-import androidx.test.filters.SmallTest
-import com.android.systemui.SysuiTestCase
-import kotlinx.coroutines.ExperimentalCoroutinesApi
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.mockito.kotlin.argumentCaptor
-import org.mockito.kotlin.clearInvocations
-import org.mockito.kotlin.mock
-import org.mockito.kotlin.never
-import org.mockito.kotlin.verify
-
-@ExperimentalCoroutinesApi
-@SmallTest
-@RunWith(AndroidJUnit4::class)
-class EditWidgetsActivityControllerTest : SysuiTestCase() {
-    @Test
-    fun activityLifecycle_stoppedWhenNotWaitingForResult() {
-        val activity = mock<Activity>()
-        val controller = EditWidgetsActivity.ActivityController(activity)
-
-        val callbackCapture = argumentCaptor<ActivityLifecycleCallbacks>()
-        verify(activity).registerActivityLifecycleCallbacks(callbackCapture.capture())
-
-        callbackCapture.lastValue.onActivityStopped(activity)
-
-        verify(activity).finish()
-    }
-
-    @Test
-    fun activityLifecycle_notStoppedWhenNotWaitingForResult() {
-        val activity = mock<Activity>()
-        val controller = EditWidgetsActivity.ActivityController(activity)
-
-        val callbackCapture = argumentCaptor<ActivityLifecycleCallbacks>()
-        verify(activity).registerActivityLifecycleCallbacks(callbackCapture.capture())
-
-        controller.onWaitingForResult(true)
-        callbackCapture.lastValue.onActivityStopped(activity)
-
-        verify(activity, never()).finish()
-    }
-
-    @Test
-    fun activityLifecycle_stoppedAfterResultReturned() {
-        val activity = mock<Activity>()
-        val controller = EditWidgetsActivity.ActivityController(activity)
-
-        val callbackCapture = argumentCaptor<ActivityLifecycleCallbacks>()
-        verify(activity).registerActivityLifecycleCallbacks(callbackCapture.capture())
-
-        controller.onWaitingForResult(true)
-        controller.onWaitingForResult(false)
-        callbackCapture.lastValue.onActivityStopped(activity)
-
-        verify(activity).finish()
-    }
-
-    @Test
-    fun activityLifecycle_statePreservedThroughInstanceSave() {
-        val activity = mock<Activity>()
-        val bundle = Bundle(1)
-
-        run {
-            val controller = EditWidgetsActivity.ActivityController(activity)
-            val callbackCapture = argumentCaptor<ActivityLifecycleCallbacks>()
-            verify(activity).registerActivityLifecycleCallbacks(callbackCapture.capture())
-
-            controller.onWaitingForResult(true)
-            callbackCapture.lastValue.onActivitySaveInstanceState(activity, bundle)
-        }
-
-        clearInvocations(activity)
-
-        run {
-            val controller = EditWidgetsActivity.ActivityController(activity)
-            val callbackCapture = argumentCaptor<ActivityLifecycleCallbacks>()
-            verify(activity).registerActivityLifecycleCallbacks(callbackCapture.capture())
-
-            callbackCapture.lastValue.onActivityCreated(activity, bundle)
-            callbackCapture.lastValue.onActivityStopped(activity)
-
-            verify(activity, never()).finish()
-        }
-    }
-}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/widgets/WidgetInteractionHandlerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/widgets/WidgetInteractionHandlerTest.kt
index 023de52..9c308a60 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/widgets/WidgetInteractionHandlerTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/widgets/WidgetInteractionHandlerTest.kt
@@ -25,13 +25,19 @@
 import androidx.core.util.component2
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
+import com.android.keyguard.keyguardUpdateMonitor
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.communal.domain.interactor.communalSceneInteractor
+import com.android.systemui.communal.domain.interactor.widgetTrampolineInteractor
 import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.kosmos.applicationCoroutineScope
+import com.android.systemui.kosmos.backgroundCoroutineContext
 import com.android.systemui.kosmos.testScope
 import com.android.systemui.log.logcatLogBuffer
 import com.android.systemui.plugins.ActivityStarter
 import com.android.systemui.testKosmos
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.runCurrent
 import kotlinx.coroutines.test.runTest
 import org.junit.Assert.assertFalse
 import org.junit.Assert.assertTrue
@@ -39,12 +45,14 @@
 import org.junit.Test
 import org.junit.runner.RunWith
 import org.mockito.kotlin.any
+import org.mockito.kotlin.argumentCaptor
 import org.mockito.kotlin.eq
 import org.mockito.kotlin.isNull
 import org.mockito.kotlin.mock
 import org.mockito.kotlin.refEq
 import org.mockito.kotlin.verify
 
+@OptIn(ExperimentalCoroutinesApi::class)
 @SmallTest
 @RunWith(AndroidJUnit4::class)
 class WidgetInteractionHandlerTest : SysuiTestCase() {
@@ -67,9 +75,13 @@
         with(kosmos) {
             underTest =
                 WidgetInteractionHandler(
+                    applicationScope = applicationCoroutineScope,
+                    uiBackgroundContext = backgroundCoroutineContext,
                     activityStarter = activityStarter,
                     communalSceneInteractor = communalSceneInteractor,
+                    keyguardUpdateMonitor = keyguardUpdateMonitor,
                     logBuffer = logcatLogBuffer(),
+                    widgetTrampolineInteractor = widgetTrampolineInteractor,
                 )
         }
     }
@@ -91,16 +103,21 @@
                 // Verify that we set the state correctly
                 assertTrue(launching!!)
                 // Verify that we pass in a non-null Communal animation controller
+
+                val callbackCaptor = argumentCaptor<Runnable>()
                 verify(activityStarter)
                     .startPendingIntentMaybeDismissingKeyguard(
                         /* intent = */ eq(testIntent),
                         /* dismissShade = */ eq(false),
-                        /* intentSentUiThreadCallback = */ isNull(),
+                        /* intentSentUiThreadCallback = */ callbackCaptor.capture(),
                         /* animationController = */ any<CommunalTransitionAnimatorController>(),
                         /* fillInIntent = */ refEq(fillInIntent),
                         /* extraOptions = */ refEq(activityOptions.toBundle()),
                         /* customMessage */ isNull(),
                     )
+                callbackCaptor.firstValue.run()
+                runCurrent()
+                verify(keyguardUpdateMonitor).awakenFromDream()
             }
         }
     }
@@ -119,7 +136,7 @@
             .startPendingIntentMaybeDismissingKeyguard(
                 /* intent = */ eq(testIntent),
                 /* dismissShade = */ eq(false),
-                /* intentSentUiThreadCallback = */ isNull(),
+                /* intentSentUiThreadCallback = */ any(),
                 /* animationController = */ isNull(),
                 /* fillInIntent = */ refEq(fillInIntent),
                 /* extraOptions = */ refEq(activityOptions.toBundle()),
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/DreamOverlayServiceTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/DreamOverlayServiceTest.kt
index 7a86e57..da82b5f 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/DreamOverlayServiceTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/dreams/DreamOverlayServiceTest.kt
@@ -68,7 +68,6 @@
 import com.android.systemui.testKosmos
 import com.android.systemui.touch.TouchInsetManager
 import com.android.systemui.util.concurrency.FakeExecutor
-import com.android.systemui.util.mockito.any
 import com.android.systemui.util.mockito.whenever
 import com.android.systemui.util.time.FakeSystemClock
 import com.google.common.truth.Truth.assertThat
@@ -87,6 +86,7 @@
 import org.mockito.Mockito.isNull
 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.spy
@@ -669,7 +669,7 @@
             runCurrent()
             verify(mDreamOverlayCallback).onRedirectWake(true)
             client.onWakeRequested()
-            verify(mCommunalInteractor).changeScene(eq(CommunalScenes.Communal), isNull())
+            verify(mCommunalInteractor).changeScene(eq(CommunalScenes.Communal), any(), isNull())
             verify(mUiEventLogger).log(CommunalUiEvent.DREAM_TO_COMMUNAL_HUB_DREAM_AWAKE_START)
         }
 
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/education/data/repository/ContextualEducationRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/education/data/repository/ContextualEducationRepositoryTest.kt
index 5dd6c22..f82beff 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/education/data/repository/ContextualEducationRepositoryTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/education/data/repository/ContextualEducationRepositoryTest.kt
@@ -51,6 +51,7 @@
     }
 
     private val testUserId = 1111
+    private val secondTestUserId = 1112
 
     // For deleting any test files created after the test
     @get:Rule val tmpFolder: TemporaryFolder = TemporaryFolder.builder().assureDeletion().build()
@@ -73,12 +74,21 @@
             assertThat(model?.signalCount).isEqualTo(1)
 
             // User is changed.
-            underTest.setUser(1112)
+            underTest.setUser(secondTestUserId)
             // Assert count is 0 after user is changed.
             assertThat(model?.signalCount).isEqualTo(0)
         }
 
     @Test
+    fun changeUserIdForNewUser() =
+        testScope.runTest {
+            val model by collectLastValue(underTest.readGestureEduModelFlow(BACK))
+            assertThat(model?.userId).isEqualTo(testUserId)
+            underTest.setUser(secondTestUserId)
+            assertThat(model?.userId).isEqualTo(secondTestUserId)
+        }
+
+    @Test
     fun dataChangedOnUpdate() =
         testScope.runTest {
             val newModel =
@@ -88,6 +98,7 @@
                     lastShortcutTriggeredTime = kosmos.fakeEduClock.instant(),
                     lastEducationTime = kosmos.fakeEduClock.instant(),
                     usageSessionStartTime = kosmos.fakeEduClock.instant(),
+                    userId = testUserId
                 )
             underTest.updateGestureEduModel(BACK) { newModel }
             val model by collectLastValue(underTest.readGestureEduModelFlow(BACK))
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/education/domain/interactor/KeyboardTouchpadEduInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/education/domain/interactor/KeyboardTouchpadEduInteractorTest.kt
index 6867089..23f923a 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/education/domain/interactor/KeyboardTouchpadEduInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/education/domain/interactor/KeyboardTouchpadEduInteractorTest.kt
@@ -109,7 +109,8 @@
                 .isEqualTo(
                     GestureEduModel(
                         signalCount = 1,
-                        usageSessionStartTime = secondSignalReceivedTime
+                        usageSessionStartTime = secondSignalReceivedTime,
+                        userId = 0
                     )
                 )
         }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/education/domain/ui/view/ContextualEduUiCoordinatorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/education/domain/ui/view/ContextualEduUiCoordinatorTest.kt
index 1f73347..e075b7e 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/education/domain/ui/view/ContextualEduUiCoordinatorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/education/domain/ui/view/ContextualEduUiCoordinatorTest.kt
@@ -16,6 +16,8 @@
 
 package com.android.systemui.education.domain.ui.view
 
+import android.app.Notification
+import android.app.NotificationManager
 import android.content.applicationContext
 import android.widget.Toast
 import androidx.test.ext.junit.runners.AndroidJUnit4
@@ -30,27 +32,35 @@
 import com.android.systemui.education.ui.viewmodel.ContextualEduViewModel
 import com.android.systemui.kosmos.applicationCoroutineScope
 import com.android.systemui.kosmos.testScope
+import com.android.systemui.res.R
 import com.android.systemui.testKosmos
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.test.TestScope
 import kotlinx.coroutines.test.runCurrent
 import kotlinx.coroutines.test.runTest
 import org.junit.Before
 import org.junit.Rule
 import org.junit.Test
 import org.junit.runner.RunWith
+import org.mockito.ArgumentCaptor
+import org.mockito.ArgumentMatchers.anyInt
 import org.mockito.Mock
 import org.mockito.junit.MockitoJUnit
+import org.mockito.kotlin.any
 import org.mockito.kotlin.verify
 
 @SmallTest
 @RunWith(AndroidJUnit4::class)
+@OptIn(kotlinx.coroutines.ExperimentalCoroutinesApi::class)
 class ContextualEduUiCoordinatorTest : SysuiTestCase() {
     private val kosmos = testKosmos()
     private val testScope = kosmos.testScope
     private val interactor = kosmos.contextualEducationInteractor
     private lateinit var underTest: ContextualEduUiCoordinator
     @Mock private lateinit var toast: Toast
-
+    @Mock private lateinit var notificationManager: NotificationManager
     @get:Rule val mockitoRule = MockitoJUnit.rule()
+    private var toastContent = ""
 
     @Before
     fun setUp() {
@@ -60,23 +70,76 @@
                 kosmos.keyboardTouchpadEduInteractor
             )
         underTest =
-            ContextualEduUiCoordinator(kosmos.applicationCoroutineScope, viewModel) { _ -> toast }
+            ContextualEduUiCoordinator(
+                kosmos.applicationCoroutineScope,
+                viewModel,
+                kosmos.applicationContext,
+                notificationManager
+            ) { content ->
+                toastContent = content
+                toast
+            }
         underTest.start()
         kosmos.keyboardTouchpadEduInteractor.start()
     }
 
     @Test
-    @OptIn(kotlinx.coroutines.ExperimentalCoroutinesApi::class)
     fun showToastOnNewEdu() =
         testScope.runTest {
             triggerEducation(BACK)
-            runCurrent()
             verify(toast).show()
         }
 
-    private suspend fun triggerEducation(gestureType: GestureType) {
+    @Test
+    fun showNotificationOn2ndEdu() =
+        testScope.runTest {
+            triggerEducation(BACK)
+            triggerEducation(BACK)
+            verify(notificationManager).notifyAsUser(any(), anyInt(), any(), any())
+        }
+
+    @Test
+    fun verifyBackEduToastContent() =
+        testScope.runTest {
+            triggerEducation(BACK)
+            assertThat(toastContent).isEqualTo(context.getString(R.string.back_edu_toast_content))
+        }
+
+    @Test
+    fun verifyBackEduNotificationContent() =
+        testScope.runTest {
+            val notificationCaptor = ArgumentCaptor.forClass(Notification::class.java)
+            triggerEducation(BACK)
+            triggerEducation(BACK)
+            verify(notificationManager)
+                .notifyAsUser(any(), anyInt(), notificationCaptor.capture(), any())
+            verifyNotificationContent(
+                R.string.back_edu_notification_title,
+                R.string.back_edu_notification_content,
+                notificationCaptor.value
+            )
+        }
+
+    private fun verifyNotificationContent(
+        titleResId: Int,
+        contentResId: Int,
+        notification: Notification
+    ) {
+        val expectedContent = context.getString(contentResId)
+        val expectedTitle = context.getString(titleResId)
+        val actualContent = notification.getString(Notification.EXTRA_TEXT)
+        val actualTitle = notification.getString(Notification.EXTRA_TITLE)
+        assertThat(actualContent).isEqualTo(expectedContent)
+        assertThat(actualTitle).isEqualTo(expectedTitle)
+    }
+
+    private fun Notification.getString(key: String): String =
+        this.extras?.getCharSequence(key).toString()
+
+    private suspend fun TestScope.triggerEducation(gestureType: GestureType) {
         for (i in 1..KeyboardTouchpadEduInteractor.MAX_SIGNAL_COUNT) {
             interactor.incrementSignalCount(gestureType)
         }
+        runCurrent()
     }
 }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/repository/KeyguardQuickAffordanceRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/repository/KeyguardQuickAffordanceRepositoryTest.kt
index 8e109b4..c85cd66 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/repository/KeyguardQuickAffordanceRepositoryTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/repository/KeyguardQuickAffordanceRepositoryTest.kt
@@ -308,6 +308,15 @@
             )
         }
 
+    @Test
+    fun getConfig() =
+        testScope.runTest {
+            assertThat(underTest.getConfig(FakeCustomizationProviderClient.AFFORDANCE_1))
+                .isEqualTo(config1)
+            assertThat(underTest.getConfig(FakeCustomizationProviderClient.AFFORDANCE_2))
+                .isEqualTo(config2)
+        }
+
     private fun assertSelections(
         observed: Map<String, List<KeyguardQuickAffordanceConfig>>?,
         expected: Map<String, List<KeyguardQuickAffordanceConfig>>,
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorTest.kt
index 75c0d3b..ad07c1c 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractorTest.kt
@@ -426,6 +426,64 @@
         }
 
     @Test
+    fun quickAffordanceAlwaysVisible_withNonNullOverrideKeyguardQuickAffordanceId() =
+        testScope.runTest {
+            quickAccessWallet.setState(
+                KeyguardQuickAffordanceConfig.LockScreenState.Visible(
+                    icon = ICON,
+                    activationState = ActivationState.Active,
+                )
+            )
+            homeControls.setState(
+                KeyguardQuickAffordanceConfig.LockScreenState.Visible(
+                    icon = ICON,
+                    activationState = ActivationState.Active,
+                )
+            )
+
+            // The default case
+            val collectedValue =
+                collectLastValue(
+                    underTest.quickAffordanceAlwaysVisible(
+                        KeyguardQuickAffordancePosition.BOTTOM_START,
+                    )
+                )
+            assertThat(collectedValue())
+                .isInstanceOf(KeyguardQuickAffordanceModel.Visible::class.java)
+            val visibleModel = collectedValue() as KeyguardQuickAffordanceModel.Visible
+            assertThat(visibleModel.configKey)
+                .isEqualTo(
+                    "${KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_START}::${homeControls.key}"
+                )
+            assertThat(visibleModel.icon).isEqualTo(ICON)
+            assertThat(visibleModel.icon.contentDescription)
+                .isEqualTo(ContentDescription.Resource(res = CONTENT_DESCRIPTION_RESOURCE_ID))
+            assertThat(visibleModel.activationState).isEqualTo(ActivationState.Active)
+
+            // With override
+            val collectedValueWithOverride =
+                collectLastValue(
+                    underTest.quickAffordanceAlwaysVisible(
+                        position = KeyguardQuickAffordancePosition.BOTTOM_START,
+                        overrideQuickAffordanceId =
+                            BuiltInKeyguardQuickAffordanceKeys.QUICK_ACCESS_WALLET,
+                    )
+                )
+            assertThat(collectedValueWithOverride())
+                .isInstanceOf(KeyguardQuickAffordanceModel.Visible::class.java)
+            val visibleModelWithOverride =
+                collectedValueWithOverride() as KeyguardQuickAffordanceModel.Visible
+            assertThat(visibleModelWithOverride.configKey)
+                .isEqualTo(
+                    "${KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_START}::${quickAccessWallet.key}"
+                )
+            assertThat(visibleModelWithOverride.icon).isEqualTo(ICON)
+            assertThat(visibleModelWithOverride.icon.contentDescription)
+                .isEqualTo(ContentDescription.Resource(res = CONTENT_DESCRIPTION_RESOURCE_ID))
+            assertThat(visibleModelWithOverride.activationState).isEqualTo(ActivationState.Active)
+        }
+
+    @Test
     fun select() =
         testScope.runTest {
             overrideResource(
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 8c1e8de..9792c28 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
@@ -845,7 +845,7 @@
             runTransitionAndSetWakefulness(KeyguardState.LOCKSCREEN, KeyguardState.GONE)
 
             // WHEN the glanceable hub is shown
-            communalSceneInteractor.changeScene(CommunalScenes.Communal)
+            communalSceneInteractor.changeScene(CommunalScenes.Communal, "test")
             runCurrent()
 
             assertThat(transitionRepository)
@@ -1004,7 +1004,7 @@
     fun alternateBouncerToGlanceableHub() =
         testScope.runTest {
             // GIVEN the device is idle on the glanceable hub
-            communalSceneInteractor.changeScene(CommunalScenes.Communal)
+            communalSceneInteractor.changeScene(CommunalScenes.Communal, "test")
             runCurrent()
             clearInvocations(transitionRepository)
 
@@ -1123,7 +1123,7 @@
     fun primaryBouncerToGlanceableHub() =
         testScope.runTest {
             // GIVEN the device is idle on the glanceable hub
-            communalSceneInteractor.changeScene(CommunalScenes.Communal)
+            communalSceneInteractor.changeScene(CommunalScenes.Communal, "test")
             runCurrent()
 
             // GIVEN a prior transition has run to PRIMARY_BOUNCER
@@ -1157,7 +1157,7 @@
             advanceTimeBy(600L)
 
             // GIVEN the device is idle on the glanceable hub
-            communalSceneInteractor.changeScene(CommunalScenes.Communal)
+            communalSceneInteractor.changeScene(CommunalScenes.Communal, "test")
             runCurrent()
 
             // GIVEN a prior transition has run to PRIMARY_BOUNCER
@@ -1971,7 +1971,7 @@
     fun glanceableHubToLockscreen_communalKtfRefactor() =
         testScope.runTest {
             // GIVEN a prior transition has run to GLANCEABLE_HUB
-            communalSceneInteractor.changeScene(CommunalScenes.Communal)
+            communalSceneInteractor.changeScene(CommunalScenes.Communal, "test")
             runCurrent()
             clearInvocations(transitionRepository)
 
@@ -2035,7 +2035,7 @@
     fun glanceableHubToDozing_communalKtfRefactor() =
         testScope.runTest {
             // GIVEN a prior transition has run to GLANCEABLE_HUB
-            communalSceneInteractor.changeScene(CommunalScenes.Communal)
+            communalSceneInteractor.changeScene(CommunalScenes.Communal, "test")
             runCurrent()
             clearInvocations(transitionRepository)
 
@@ -2136,7 +2136,7 @@
     fun glanceableHubToOccluded_communalKtfRefactor() =
         testScope.runTest {
             // GIVEN a prior transition has run to GLANCEABLE_HUB
-            communalSceneInteractor.changeScene(CommunalScenes.Communal)
+            communalSceneInteractor.changeScene(CommunalScenes.Communal, "test")
             runCurrent()
             clearInvocations(transitionRepository)
 
@@ -2184,7 +2184,7 @@
     fun glanceableHubToGone_communalKtfRefactor() =
         testScope.runTest {
             // GIVEN a prior transition has run to GLANCEABLE_HUB
-            communalSceneInteractor.changeScene(CommunalScenes.Communal)
+            communalSceneInteractor.changeScene(CommunalScenes.Communal, "test")
             runCurrent()
             clearInvocations(transitionRepository)
 
@@ -2265,7 +2265,7 @@
             advanceTimeBy(600L)
 
             // GIVEN a prior transition has run to GLANCEABLE_HUB
-            communalSceneInteractor.changeScene(CommunalScenes.Communal)
+            communalSceneInteractor.changeScene(CommunalScenes.Communal, "test")
             runCurrent()
             clearInvocations(transitionRepository)
 
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToLockscreenTransitionViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToLockscreenTransitionViewModelTest.kt
index 409c551..5ec566b 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToLockscreenTransitionViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToLockscreenTransitionViewModelTest.kt
@@ -28,6 +28,7 @@
 import com.android.systemui.keyguard.shared.model.TransitionStep
 import com.android.systemui.kosmos.testScope
 import com.android.systemui.testKosmos
+import com.google.common.collect.Range
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.test.runCurrent
@@ -49,6 +50,24 @@
     val underTest = kosmos.primaryBouncerToLockscreenTransitionViewModel
 
     @Test
+    fun lockscreenAlphaStartsFromViewStateAccessorAlpha() =
+        testScope.runTest {
+            val viewState = ViewStateAccessor(alpha = { 0.5f })
+            val alpha by collectLastValue(underTest.lockscreenAlpha(viewState))
+
+            keyguardTransitionRepository.sendTransitionStep(step(0f, TransitionState.STARTED))
+
+            keyguardTransitionRepository.sendTransitionStep(step(0f))
+            assertThat(alpha).isEqualTo(0.5f)
+
+            keyguardTransitionRepository.sendTransitionStep(step(0.5f))
+            assertThat(alpha).isIn(Range.open(0.5f, 1f))
+
+            keyguardTransitionRepository.sendTransitionStep(step(1f))
+            assertThat(alpha).isEqualTo(1f)
+        }
+
+    @Test
     fun deviceEntryParentViewAlpha() =
         testScope.runTest {
             val deviceEntryParentViewAlpha by collectLastValue(underTest.deviceEntryParentViewAlpha)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/lifecycle/SysUiViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/lifecycle/SysUiViewModelTest.kt
index 46b370f..976dc52 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/lifecycle/SysUiViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/lifecycle/SysUiViewModelTest.kt
@@ -154,7 +154,7 @@
 private class FakeViewModel : SysUiViewModel() {
     var isActivated = false
 
-    override suspend fun onActivated() {
+    override suspend fun onActivated(): Nothing {
         isActivated = true
         try {
             awaitCancellation()
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/composefragment/viewmodel/QSFragmentComposeViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/composefragment/viewmodel/QSFragmentComposeViewModelTest.kt
new file mode 100644
index 0000000..5999265
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/composefragment/viewmodel/QSFragmentComposeViewModelTest.kt
@@ -0,0 +1,151 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.composefragment.viewmodel
+
+import android.content.testableContext
+import android.testing.TestableLooper.RunWithLooper
+import androidx.lifecycle.Lifecycle
+import androidx.lifecycle.lifecycleScope
+import androidx.lifecycle.testing.TestLifecycleOwner
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.common.ui.data.repository.fakeConfigurationRepository
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.kosmos.testDispatcher
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.qs.fgsManagerController
+import com.android.systemui.res.R
+import com.android.systemui.shade.largeScreenHeaderHelper
+import com.android.systemui.testKosmos
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.TestResult
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.resetMain
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import kotlinx.coroutines.test.setMain
+import org.junit.After
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+@RunWithLooper
+@OptIn(ExperimentalCoroutinesApi::class)
+class QSFragmentComposeViewModelTest : SysuiTestCase() {
+    private val kosmos = testKosmos()
+
+    private val lifecycleOwner =
+        TestLifecycleOwner(
+            initialState = Lifecycle.State.CREATED,
+            coroutineDispatcher = kosmos.testDispatcher,
+        )
+
+    private val underTest by lazy {
+        kosmos.qsFragmentComposeViewModelFactory.create(lifecycleOwner.lifecycleScope)
+    }
+
+    @Before
+    fun setUp() {
+        Dispatchers.setMain(kosmos.testDispatcher)
+    }
+
+    @After
+    fun teardown() {
+        Dispatchers.resetMain()
+    }
+
+    // For now the state changes at 0.5f expansion. This will change once we implement animation
+    // (and this test will fail)
+    @Test
+    fun qsExpansionValueChanges_correctExpansionState() =
+        with(kosmos) {
+            testScope.testWithinLifecycle {
+                val expansionState by collectLastValue(underTest.expansionState)
+
+                underTest.qsExpansionValue = 0f
+                assertThat(expansionState)
+                    .isEqualTo(QSFragmentComposeViewModel.QSExpansionState.QQS)
+
+                underTest.qsExpansionValue = 0.3f
+                assertThat(expansionState)
+                    .isEqualTo(QSFragmentComposeViewModel.QSExpansionState.QQS)
+
+                underTest.qsExpansionValue = 0.7f
+                assertThat(expansionState).isEqualTo(QSFragmentComposeViewModel.QSExpansionState.QS)
+
+                underTest.qsExpansionValue = 1f
+                assertThat(expansionState).isEqualTo(QSFragmentComposeViewModel.QSExpansionState.QS)
+            }
+        }
+
+    @Test
+    fun qqsHeaderHeight_largeScreenHeader_0() =
+        with(kosmos) {
+            testScope.testWithinLifecycle {
+                val qqsHeaderHeight by collectLastValue(underTest.qqsHeaderHeight)
+
+                testableContext.orCreateTestableResources.addOverride(
+                    R.bool.config_use_large_screen_shade_header,
+                    true
+                )
+                fakeConfigurationRepository.onConfigurationChange()
+
+                assertThat(qqsHeaderHeight).isEqualTo(0)
+            }
+        }
+
+    @Test
+    fun qqsHeaderHeight_noLargeScreenHeader_providedByHelper() =
+        with(kosmos) {
+            testScope.testWithinLifecycle {
+                val qqsHeaderHeight by collectLastValue(underTest.qqsHeaderHeight)
+
+                testableContext.orCreateTestableResources.addOverride(
+                    R.bool.config_use_large_screen_shade_header,
+                    false
+                )
+                fakeConfigurationRepository.onConfigurationChange()
+
+                assertThat(qqsHeaderHeight)
+                    .isEqualTo(largeScreenHeaderHelper.getLargeScreenHeaderHeight())
+            }
+        }
+
+    @Test
+    fun footerActionsControllerInit() =
+        with(kosmos) {
+            testScope.testWithinLifecycle {
+                underTest
+                runCurrent()
+                assertThat(fgsManagerController.initialized).isTrue()
+            }
+        }
+
+    private inline fun TestScope.testWithinLifecycle(
+        crossinline block: suspend TestScope.() -> TestResult
+    ): TestResult {
+        return runTest {
+            lifecycleOwner.setCurrentState(Lifecycle.State.RESUMED)
+            block().also { lifecycleOwner.setCurrentState(Lifecycle.State.DESTROYED) }
+        }
+    }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/ui/compose/DragAndDropStateTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/ui/compose/DragAndDropStateTest.kt
deleted file mode 100644
index b2f5765..0000000
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/ui/compose/DragAndDropStateTest.kt
+++ /dev/null
@@ -1,123 +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.qs.panels.ui.compose
-
-import androidx.compose.runtime.mutableStateOf
-import androidx.test.ext.junit.runners.AndroidJUnit4
-import androidx.test.filters.SmallTest
-import com.android.systemui.SysuiTestCase
-import com.android.systemui.common.shared.model.Icon
-import com.android.systemui.common.shared.model.Text
-import com.android.systemui.qs.panels.shared.model.SizedTile
-import com.android.systemui.qs.panels.shared.model.SizedTileImpl
-import com.android.systemui.qs.panels.ui.viewmodel.EditTileViewModel
-import com.android.systemui.qs.pipeline.shared.TileSpec
-import com.google.common.truth.Truth.assertThat
-import org.junit.Test
-import org.junit.runner.RunWith
-
-@SmallTest
-@RunWith(AndroidJUnit4::class)
-class DragAndDropStateTest : SysuiTestCase() {
-    private val listState = EditTileListState(TestEditTiles)
-    private val underTest = DragAndDropState(mutableStateOf(null), listState)
-
-    @Test
-    fun isMoving_returnsCorrectValue() {
-        // Asserts no tiles is moving
-        TestEditTiles.forEach { assertThat(underTest.isMoving(it.tile.tileSpec)).isFalse() }
-
-        // Start the drag movement
-        underTest.onStarted(TestEditTiles[0])
-
-        // Assert that the correct tile is marked as moving
-        TestEditTiles.forEach {
-            assertThat(underTest.isMoving(it.tile.tileSpec))
-                .isEqualTo(TestEditTiles[0].tile.tileSpec == it.tile.tileSpec)
-        }
-    }
-
-    @Test
-    fun onMoved_updatesList() {
-        // Start the drag movement
-        underTest.onStarted(TestEditTiles[0])
-
-        // Move the tile to the end of the list
-        underTest.onMoved(listState.tiles[5].tile.tileSpec)
-        assertThat(underTest.currentPosition()).isEqualTo(5)
-
-        // Move the tile to the middle of the list
-        underTest.onMoved(listState.tiles[2].tile.tileSpec)
-        assertThat(underTest.currentPosition()).isEqualTo(2)
-    }
-
-    @Test
-    fun onDrop_resetsMovingTile() {
-        // Start the drag movement
-        underTest.onStarted(TestEditTiles[0])
-
-        // Move the tile to the end of the list
-        underTest.onMoved(listState.tiles[5].tile.tileSpec)
-
-        // Drop the tile
-        underTest.onDrop()
-
-        // Asserts no tiles is moving
-        TestEditTiles.forEach { assertThat(underTest.isMoving(it.tile.tileSpec)).isFalse() }
-    }
-
-    @Test
-    fun onMoveOutOfBounds_removeMovingTileFromCurrentList() {
-        // Start the drag movement
-        underTest.onStarted(TestEditTiles[0])
-
-        // Move the tile outside of the list
-        underTest.movedOutOfBounds()
-
-        // Asserts the moving tile is not current
-        assertThat(
-                listState.tiles.firstOrNull { it.tile.tileSpec == TestEditTiles[0].tile.tileSpec }
-            )
-            .isNull()
-    }
-
-    companion object {
-        private fun createEditTile(tileSpec: String): SizedTile<EditTileViewModel> {
-            return SizedTileImpl(
-                EditTileViewModel(
-                    tileSpec = TileSpec.create(tileSpec),
-                    icon = Icon.Resource(0, null),
-                    label = Text.Loaded("unused"),
-                    appName = null,
-                    isCurrent = true,
-                    availableEditActions = emptySet(),
-                ),
-                1,
-            )
-        }
-
-        private val TestEditTiles =
-            listOf(
-                createEditTile("tileA"),
-                createEditTile("tileB"),
-                createEditTile("tileC"),
-                createEditTile("tileD"),
-                createEditTile("tileE"),
-                createEditTile("tileF"),
-            )
-    }
-}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/ui/compose/DragAndDropTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/ui/compose/DragAndDropTest.kt
new file mode 100644
index 0000000..d9faa30
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/ui/compose/DragAndDropTest.kt
@@ -0,0 +1,228 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.panels.ui.compose
+
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.setValue
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.test.assert
+import androidx.compose.ui.test.hasContentDescription
+import androidx.compose.ui.test.junit4.ComposeContentTestRule
+import androidx.compose.ui.test.junit4.createComposeRule
+import androidx.compose.ui.test.onChildAt
+import androidx.compose.ui.test.onChildren
+import androidx.compose.ui.test.onNodeWithContentDescription
+import androidx.compose.ui.test.onNodeWithTag
+import androidx.compose.ui.test.onNodeWithText
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.FlakyTest
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.common.shared.model.ContentDescription
+import com.android.systemui.common.shared.model.Icon
+import com.android.systemui.common.shared.model.Text
+import com.android.systemui.qs.panels.shared.model.SizedTile
+import com.android.systemui.qs.panels.shared.model.SizedTileImpl
+import com.android.systemui.qs.panels.ui.viewmodel.EditTileViewModel
+import com.android.systemui.qs.pipeline.shared.TileSpec
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@FlakyTest(bugId = 360351805)
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class DragAndDropTest : SysuiTestCase() {
+    @get:Rule val composeRule = createComposeRule()
+
+    // TODO(ostonge): Investigate why drag isn't detected when using performTouchInput
+    @Composable
+    private fun EditTileGridUnderTest(
+        listState: EditTileListState,
+        onSetTiles: (List<TileSpec>) -> Unit
+    ) {
+        DefaultEditTileGrid(
+            currentListState = listState,
+            otherTiles = listOf(),
+            columns = 4,
+            modifier = Modifier.fillMaxSize(),
+            onAddTile = { _, _ -> },
+            onRemoveTile = {},
+            onSetTiles = onSetTiles,
+            onResize = {},
+        )
+    }
+
+    @Test
+    fun draggedTile_shouldDisappear() {
+        var tiles by mutableStateOf(TestEditTiles)
+        val listState = EditTileListState(tiles, 4)
+        composeRule.setContent {
+            EditTileGridUnderTest(listState) {
+                tiles = it.map { tileSpec -> createEditTile(tileSpec.spec) }
+            }
+        }
+        composeRule.waitForIdle()
+
+        listState.onStarted(TestEditTiles[0])
+
+        // Tile is being dragged, it should be replaced with a placeholder
+        composeRule.onNodeWithContentDescription("tileA").assertDoesNotExist()
+
+        // Available tiles should disappear
+        composeRule.onNodeWithTag(AVAILABLE_TILES_GRID_TEST_TAG).assertDoesNotExist()
+
+        // Remove drop zone should appear
+        composeRule.onNodeWithText("Remove").assertExists()
+
+        // Every other tile should still be in the same order
+        composeRule.assertTileGridContainsExactly(listOf("tileB", "tileC", "tileD_large", "tileE"))
+    }
+
+    @Test
+    fun draggedTile_shouldChangePosition() {
+        var tiles by mutableStateOf(TestEditTiles)
+        val listState = EditTileListState(tiles, 4)
+        composeRule.setContent {
+            EditTileGridUnderTest(listState) {
+                tiles = it.map { tileSpec -> createEditTile(tileSpec.spec) }
+            }
+        }
+        composeRule.waitForIdle()
+
+        listState.onStarted(TestEditTiles[0])
+        listState.onMoved(1, false)
+        listState.onDrop()
+
+        // Available tiles should re-appear
+        composeRule.onNodeWithTag(AVAILABLE_TILES_GRID_TEST_TAG).assertExists()
+
+        // Remove drop zone should disappear
+        composeRule.onNodeWithText("Remove").assertDoesNotExist()
+
+        // Tile A and B should swap places
+        composeRule.assertTileGridContainsExactly(
+            listOf("tileB", "tileA", "tileC", "tileD_large", "tileE")
+        )
+    }
+
+    @Test
+    fun draggedTileOut_shouldBeRemoved() {
+        var tiles by mutableStateOf(TestEditTiles)
+        val listState = EditTileListState(tiles, 4)
+        composeRule.setContent {
+            EditTileGridUnderTest(listState) {
+                tiles = it.map { tileSpec -> createEditTile(tileSpec.spec) }
+            }
+        }
+        composeRule.waitForIdle()
+
+        listState.onStarted(TestEditTiles[0])
+        listState.movedOutOfBounds()
+        listState.onDrop()
+
+        // Available tiles should re-appear
+        composeRule.onNodeWithTag(AVAILABLE_TILES_GRID_TEST_TAG).assertExists()
+
+        // Remove drop zone should disappear
+        composeRule.onNodeWithText("Remove").assertDoesNotExist()
+
+        // Tile A is gone
+        composeRule.assertTileGridContainsExactly(listOf("tileB", "tileC", "tileD_large", "tileE"))
+    }
+
+    @Test
+    fun draggedNewTileIn_shouldBeAdded() {
+        var tiles by mutableStateOf(TestEditTiles)
+        val listState = EditTileListState(tiles, 4)
+        composeRule.setContent {
+            EditTileGridUnderTest(listState) {
+                tiles = it.map { tileSpec -> createEditTile(tileSpec.spec) }
+            }
+        }
+        composeRule.waitForIdle()
+
+        listState.onStarted(createEditTile("newTile"))
+        // Insert after tileD, which is at index 4
+        // [ a ] [ b ] [ c ] [ empty ]
+        // [ tile d ] [ e ]
+        listState.onMoved(4, insertAfter = true)
+        listState.onDrop()
+
+        // Available tiles should re-appear
+        composeRule.onNodeWithTag(AVAILABLE_TILES_GRID_TEST_TAG).assertExists()
+
+        // Remove drop zone should disappear
+        composeRule.onNodeWithText("Remove").assertDoesNotExist()
+
+        // newTile is added after tileD
+        composeRule.assertTileGridContainsExactly(
+            listOf("tileA", "tileB", "tileC", "tileD_large", "newTile", "tileE")
+        )
+    }
+
+    private fun ComposeContentTestRule.assertTileGridContainsExactly(specs: List<String>) {
+        onNodeWithTag(CURRENT_TILES_GRID_TEST_TAG).onChildren().apply {
+            fetchSemanticsNodes().forEachIndexed { index, _ ->
+                get(index).onChildAt(0).assert(hasContentDescription(specs[index]))
+            }
+        }
+    }
+
+    companion object {
+        private const val CURRENT_TILES_GRID_TEST_TAG = "CurrentTilesGrid"
+        private const val AVAILABLE_TILES_GRID_TEST_TAG = "AvailableTilesGrid"
+
+        private fun createEditTile(tileSpec: String): SizedTile<EditTileViewModel> {
+            return SizedTileImpl(
+                EditTileViewModel(
+                    tileSpec = TileSpec.create(tileSpec),
+                    icon =
+                        Icon.Resource(
+                            android.R.drawable.star_on,
+                            ContentDescription.Loaded(tileSpec)
+                        ),
+                    label = Text.Loaded(tileSpec),
+                    appName = null,
+                    isCurrent = true,
+                    availableEditActions = emptySet(),
+                ),
+                getWidth(tileSpec),
+            )
+        }
+
+        private fun getWidth(tileSpec: String): Int {
+            return if (tileSpec.endsWith("large")) {
+                2
+            } else {
+                1
+            }
+        }
+
+        private val TestEditTiles =
+            listOf(
+                createEditTile("tileA"),
+                createEditTile("tileB"),
+                createEditTile("tileC"),
+                createEditTile("tileD_large"),
+                createEditTile("tileE"),
+            )
+    }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/ui/compose/EditTileListStateTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/ui/compose/EditTileListStateTest.kt
index a3a6a33..7f01fad 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/ui/compose/EditTileListStateTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/panels/ui/compose/EditTileListStateTest.kt
@@ -23,6 +23,9 @@
 import com.android.systemui.common.shared.model.Text
 import com.android.systemui.qs.panels.shared.model.SizedTile
 import com.android.systemui.qs.panels.shared.model.SizedTileImpl
+import com.android.systemui.qs.panels.ui.model.GridCell
+import com.android.systemui.qs.panels.ui.model.SpacerGridCell
+import com.android.systemui.qs.panels.ui.model.TileGridCell
 import com.android.systemui.qs.panels.ui.viewmodel.EditTileViewModel
 import com.android.systemui.qs.pipeline.shared.TileSpec
 import com.google.common.truth.Truth.assertThat
@@ -32,80 +35,130 @@
 @SmallTest
 @RunWith(AndroidJUnit4::class)
 class EditTileListStateTest : SysuiTestCase() {
-    val underTest = EditTileListState(TestEditTiles)
+    private val underTest = EditTileListState(TestEditTiles, 4)
 
     @Test
-    fun movingNonExistentTile_tileAdded() {
-        val newTile = createEditTile("other_tile", false)
-        underTest.move(newTile, TestEditTiles[0].tile.tileSpec)
-
-        assertThat(underTest.tiles[0]).isEqualTo(newTile)
-        assertThat(underTest.tiles.subList(1, underTest.tiles.size))
-            .containsExactly(*TestEditTiles.toTypedArray())
+    fun noDrag_listUnchanged() {
+        underTest.tiles.forEach { assertThat(it).isNotInstanceOf(SpacerGridCell::class.java) }
+        assertThat(underTest.tiles.map { (it as TileGridCell).tile.tileSpec })
+            .containsExactly(*TestEditTiles.map { it.tile.tileSpec }.toTypedArray())
     }
 
     @Test
-    fun movingTileToNonExistentTarget_listUnchanged() {
-        underTest.move(TestEditTiles[0], TileSpec.create("other_tile"))
+    fun startDrag_listHasSpacers() {
+        underTest.onStarted(TestEditTiles[0])
 
-        assertThat(underTest.tiles).containsExactly(*TestEditTiles.toTypedArray())
+        // [ a ] [ b ] [ c ] [ X ]
+        // [ Large D ] [ e ] [ X ]
+        assertThat(underTest.tiles.toStrings())
+            .isEqualTo(listOf("a", "b", "c", "spacer", "d", "e", "spacer"))
+        assertThat(underTest.isMoving(TestEditTiles[0].tile.tileSpec)).isTrue()
+        assertThat(underTest.dragInProgress).isTrue()
     }
 
     @Test
-    fun movingTileToItself_listUnchanged() {
-        underTest.move(TestEditTiles[0], TestEditTiles[0].tile.tileSpec)
+    fun moveDrag_listChanges() {
+        underTest.onStarted(TestEditTiles[4])
+        underTest.onMoved(3, false)
 
-        assertThat(underTest.tiles).containsExactly(*TestEditTiles.toTypedArray())
+        // Tile E goes to index 3
+        // [ a ] [ b ] [ c ] [ e ]
+        // [ Large D ] [ X ] [ X ]
+        assertThat(underTest.tiles.toStrings())
+            .isEqualTo(listOf("a", "b", "c", "e", "d", "spacer", "spacer"))
     }
 
     @Test
-    fun movingTileToSameSection_listUpdates() {
-        // Move tile at index 0 to index 1. Tile 0 should remain current.
-        underTest.move(TestEditTiles[0], TestEditTiles[1].tile.tileSpec)
+    fun moveDragOnSidesOfLargeTile_listChanges() {
+        val draggedCell = TestEditTiles[4]
 
-        // Assert the tiles 0 and 1 have changed places.
-        assertThat(underTest.tiles[0]).isEqualTo(TestEditTiles[1])
-        assertThat(underTest.tiles[1]).isEqualTo(TestEditTiles[0])
+        underTest.onStarted(draggedCell)
+        underTest.onMoved(4, true)
 
-        // Assert the rest of the list is unchanged
-        assertThat(underTest.tiles.subList(2, 5))
-            .containsExactly(*TestEditTiles.subList(2, 5).toTypedArray())
+        // Tile E goes to the right side of tile D, list is unchanged
+        // [ a ] [ b ] [ c ] [ X ]
+        // [ Large D ] [ e ] [ X ]
+        assertThat(underTest.tiles.toStrings())
+            .isEqualTo(listOf("a", "b", "c", "spacer", "d", "e", "spacer"))
+
+        underTest.onMoved(4, false)
+
+        // Tile E goes to the left side of tile D, they swap positions
+        // [ a ] [ b ] [ c ] [ e ]
+        // [ Large D ] [ X ] [ X ]
+        assertThat(underTest.tiles.toStrings())
+            .isEqualTo(listOf("a", "b", "c", "e", "d", "spacer", "spacer"))
     }
 
-    fun removingTile_listUpdates() {
-        // Remove tile at index 0
-        underTest.remove(TestEditTiles[0].tile.tileSpec)
+    @Test
+    fun moveNewTile_tileIsAdded() {
+        val newTile = createEditTile("newTile", 2)
 
-        // Assert the tile was removed
-        assertThat(underTest.tiles).containsExactly(*TestEditTiles.subList(1, 6).toTypedArray())
+        underTest.onStarted(newTile)
+        underTest.onMoved(5, false)
+
+        // New tile goes to index 5
+        // [ a ] [ b ] [ c ] [ X ]
+        // [ Large D ] [ newTile ]
+        // [ e ] [ X ] [ X ] [ X ]
+        assertThat(underTest.tiles.toStrings())
+            .isEqualTo(
+                listOf("a", "b", "c", "spacer", "d", "newTile", "e", "spacer", "spacer", "spacer")
+            )
+    }
+
+    @Test
+    fun droppedNewTile_spacersDisappear() {
+        underTest.onStarted(TestEditTiles[0])
+        underTest.onDrop()
+
+        assertThat(underTest.tiles.toStrings()).isEqualTo(listOf("a", "b", "c", "d", "e"))
+        assertThat(underTest.isMoving(TestEditTiles[0].tile.tileSpec)).isFalse()
+        assertThat(underTest.dragInProgress).isFalse()
+    }
+
+    @Test
+    fun movedTileOutOfBounds_tileDisappears() {
+        underTest.onStarted(TestEditTiles[0])
+        underTest.movedOutOfBounds()
+
+        assertThat(underTest.tiles.toStrings()).doesNotContain(TestEditTiles[0].tile.tileSpec.spec)
+    }
+
+    private fun List<GridCell>.toStrings(): List<String> {
+        return map {
+            if (it is TileGridCell) {
+                it.tile.tileSpec.spec
+            } else {
+                "spacer"
+            }
+        }
     }
 
     companion object {
-        private fun createEditTile(
-            tileSpec: String,
-            isCurrent: Boolean
-        ): SizedTile<EditTileViewModel> {
+        private fun createEditTile(tileSpec: String, width: Int): SizedTile<EditTileViewModel> {
             return SizedTileImpl(
                 EditTileViewModel(
                     tileSpec = TileSpec.create(tileSpec),
                     icon = Icon.Resource(0, null),
                     label = Text.Loaded("unused"),
                     appName = null,
-                    isCurrent = isCurrent,
+                    isCurrent = true,
                     availableEditActions = emptySet(),
                 ),
-                1,
+                width,
             )
         }
 
+        // [ a ] [ b ] [ c ]
+        // [ Large D ] [ e ] [ f ]
         private val TestEditTiles =
             listOf(
-                createEditTile("tileA", true),
-                createEditTile("tileB", true),
-                createEditTile("tileC", true),
-                createEditTile("tileD", false),
-                createEditTile("tileE", false),
-                createEditTile("tileF", false),
+                createEditTile("a", 1),
+                createEditTile("b", 1),
+                createEditTile("c", 1),
+                createEditTile("d", 2),
+                createEditTile("e", 1),
             )
     }
 }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/interactor/CurrentTilesInteractorImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/interactor/CurrentTilesInteractorImplTest.kt
index 3146318..8995f46 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/interactor/CurrentTilesInteractorImplTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/interactor/CurrentTilesInteractorImplTest.kt
@@ -24,7 +24,6 @@
 import android.service.quicksettings.Tile
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
-import com.android.systemui.Flags.FLAG_QS_NEW_PIPELINE
 import com.android.systemui.Flags.FLAG_QS_NEW_TILES
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.coroutines.collectLastValue
@@ -107,7 +106,6 @@
     fun setup() {
         MockitoAnnotations.initMocks(this)
 
-        mSetFlagsRule.enableFlags(FLAG_QS_NEW_PIPELINE)
         mSetFlagsRule.enableFlags(FLAG_QS_NEW_TILES)
 
         userRepository.setUserInfos(listOf(USER_INFO_0, USER_INFO_1))
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/interactor/NoLowNumberOfTilesTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/interactor/NoLowNumberOfTilesTest.kt
index e8ad038..00c7204 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/interactor/NoLowNumberOfTilesTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/interactor/NoLowNumberOfTilesTest.kt
@@ -20,7 +20,6 @@
 import android.content.pm.UserInfo
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.MediumTest
-import com.android.systemui.Flags
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.coroutines.collectLastValue
 import com.android.systemui.kosmos.Kosmos
@@ -98,8 +97,6 @@
 
     @Before
     fun setUp() {
-        mSetFlagsRule.enableFlags(Flags.FLAG_QS_NEW_PIPELINE)
-
         with(kosmos) {
             restoreReconciliationInteractor.start()
             autoAddInteractor.init(kosmos.currentTilesInteractor)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/interactor/WorkProfileAutoAddedAfterRestoreTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/interactor/WorkProfileAutoAddedAfterRestoreTest.kt
index dffd0d7..6bcaea4 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/interactor/WorkProfileAutoAddedAfterRestoreTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/interactor/WorkProfileAutoAddedAfterRestoreTest.kt
@@ -20,7 +20,6 @@
 import android.os.UserManager
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.MediumTest
-import com.android.systemui.Flags.FLAG_QS_NEW_PIPELINE
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.coroutines.collectLastValue
 import com.android.systemui.kosmos.Kosmos
@@ -59,6 +58,7 @@
     // Getter here so it can change when there is a managed profile.
     private val workTileAvailable: Boolean
         get() = hasManagedProfile()
+
     private val currentUser: Int
         get() = kosmos.userTracker.userId
 
@@ -67,8 +67,6 @@
 
     @Before
     fun setUp() {
-        mSetFlagsRule.enableFlags(FLAG_QS_NEW_PIPELINE)
-
         kosmos.qsTileFactory = FakeQSFactory(::tileCreator)
         kosmos.restoreReconciliationInteractor.start()
         kosmos.autoAddInteractor.init(kosmos.currentTilesInteractor)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/custom/data/repository/CustomTileRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/custom/data/repository/CustomTileRepositoryTest.kt
index dfc004a..c9869bdb 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/custom/data/repository/CustomTileRepositoryTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/custom/data/repository/CustomTileRepositoryTest.kt
@@ -280,9 +280,12 @@
         }
 
     @Test
-    fun isActiveFollowsPackageManagerAdapter() =
+    fun isActiveFollowsPackageManagerAdapter_user0() =
         with(kosmos) {
             testScope.runTest {
+                packageManagerAdapterFacade.setExclusiveForUser(0)
+
+                underTest.updateWithDefaults(UserHandle.of(0), TEST_DEFAULTS_1, true)
                 packageManagerAdapterFacade.setIsActive(false)
                 assertThat(underTest.isTileActive()).isFalse()
 
@@ -295,6 +298,7 @@
     fun isToggleableFollowsPackageManagerAdapter() =
         with(kosmos) {
             testScope.runTest {
+                underTest.updateWithDefaults(UserHandle.of(0), TEST_DEFAULTS_1, true)
                 packageManagerAdapterFacade.setIsToggleable(false)
                 assertThat(underTest.isTileToggleable()).isFalse()
 
@@ -303,6 +307,66 @@
             }
         }
 
+    @Test
+    fun isActiveFollowsPackageManagerAdapter_user10_withAdapterForUser10() =
+        with(kosmos) {
+            testScope.runTest {
+                packageManagerAdapterFacade.setExclusiveForUser(10)
+
+                underTest.updateWithDefaults(UserHandle.of(10), TEST_DEFAULTS_1, true)
+                packageManagerAdapterFacade.setIsActive(false)
+                assertThat(underTest.isTileActive()).isFalse()
+
+                packageManagerAdapterFacade.setIsActive(true)
+                assertThat(underTest.isTileActive()).isTrue()
+            }
+        }
+
+    @Test
+    fun isToggleableFollowsPackageManagerAdapter_user10_withAdapterForUser10() =
+        with(kosmos) {
+            testScope.runTest {
+                packageManagerAdapterFacade.setExclusiveForUser(10)
+
+                underTest.updateWithDefaults(UserHandle.of(10), TEST_DEFAULTS_1, true)
+                packageManagerAdapterFacade.setIsToggleable(false)
+                assertThat(underTest.isTileToggleable()).isFalse()
+
+                packageManagerAdapterFacade.setIsToggleable(true)
+                assertThat(underTest.isTileToggleable()).isTrue()
+            }
+        }
+
+    @Test
+    fun isActiveDoesntFollowPackageManagerAdapter_user10() =
+        with(kosmos) {
+            testScope.runTest {
+                packageManagerAdapterFacade.setExclusiveForUser(0)
+
+                underTest.updateWithDefaults(UserHandle.of(10), TEST_DEFAULTS_1, true)
+                packageManagerAdapterFacade.setIsActive(false)
+                assertThat(underTest.isTileActive()).isFalse()
+
+                packageManagerAdapterFacade.setIsActive(true)
+                assertThat(underTest.isTileActive()).isFalse()
+            }
+        }
+
+    @Test
+    fun isToggleableDoesntFollowPackageManagerAdapter_user10() =
+        with(kosmos) {
+            testScope.runTest {
+                packageManagerAdapterFacade.setExclusiveForUser(0)
+
+                underTest.updateWithDefaults(UserHandle.of(10), TEST_DEFAULTS_1, true)
+                packageManagerAdapterFacade.setIsToggleable(false)
+                assertThat(underTest.isTileToggleable()).isFalse()
+
+                packageManagerAdapterFacade.setIsToggleable(true)
+                assertThat(underTest.isTileToggleable()).isFalse()
+            }
+        }
+
     private companion object {
 
         val TEST_COMPONENT = ComponentName("test.pkg", "test.cls")
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneContentViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneContentViewModelTest.kt
index 9563538..1118a61 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneContentViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/ui/viewmodel/QuickSettingsSceneContentViewModelTest.kt
@@ -17,6 +17,7 @@
 package com.android.systemui.qs.ui.viewmodel
 
 import android.testing.TestableLooper.RunWithLooper
+import androidx.lifecycle.LifecycleOwner
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
@@ -59,7 +60,7 @@
     private val footerActionsViewModel = mock<FooterActionsViewModel>()
     private val footerActionsViewModelFactory =
         mock<FooterActionsViewModel.Factory> {
-            whenever(create(any())).thenReturn(footerActionsViewModel)
+            whenever(create(any<LifecycleOwner>())).thenReturn(footerActionsViewModel)
         }
     private val footerActionsController = mock<FooterActionsController>()
 
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt
index 66e45ab..9122528 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt
@@ -36,10 +36,10 @@
 import com.android.systemui.authentication.shared.model.AuthenticationMethodModel
 import com.android.systemui.bouncer.domain.interactor.BouncerActionButtonInteractor
 import com.android.systemui.bouncer.domain.interactor.bouncerActionButtonInteractor
-import com.android.systemui.bouncer.ui.viewmodel.BouncerViewModel
+import com.android.systemui.bouncer.ui.viewmodel.BouncerSceneContentViewModel
 import com.android.systemui.bouncer.ui.viewmodel.PasswordBouncerViewModel
 import com.android.systemui.bouncer.ui.viewmodel.PinBouncerViewModel
-import com.android.systemui.bouncer.ui.viewmodel.bouncerViewModel
+import com.android.systemui.bouncer.ui.viewmodel.bouncerSceneContentViewModel
 import com.android.systemui.classifier.domain.interactor.falsingInteractor
 import com.android.systemui.communal.domain.interactor.communalInteractor
 import com.android.systemui.coroutines.collectLastValue
@@ -133,13 +133,14 @@
                 sceneInteractor = sceneInteractor,
                 falsingInteractor = kosmos.falsingInteractor,
                 powerInteractor = kosmos.powerInteractor,
+                motionEventHandlerReceiver = {},
             )
             .apply { setTransitionState(transitionState) }
     }
 
     private lateinit var mobileConnectionsRepository: FakeMobileConnectionsRepository
     private lateinit var bouncerActionButtonInteractor: BouncerActionButtonInteractor
-    private lateinit var bouncerViewModel: BouncerViewModel
+    private lateinit var bouncerSceneContentViewModel: BouncerSceneContentViewModel
 
     private val lockscreenSceneActionsViewModel by lazy {
         LockscreenSceneActionsViewModel(
@@ -187,7 +188,7 @@
         }
 
         bouncerActionButtonInteractor = kosmos.bouncerActionButtonInteractor
-        bouncerViewModel = kosmos.bouncerViewModel
+        bouncerSceneContentViewModel = kosmos.bouncerSceneContentViewModel
 
         shadeSceneContentViewModel = kosmos.shadeSceneContentViewModel
         shadeSceneActionsViewModel = kosmos.shadeSceneActionsViewModel
@@ -198,6 +199,8 @@
         lockscreenSceneActionsViewModel.activateIn(testScope)
         shadeSceneContentViewModel.activateIn(testScope)
         shadeSceneActionsViewModel.activateIn(testScope)
+        bouncerSceneContentViewModel.activateIn(testScope)
+        sceneContainerViewModel.activateIn(testScope)
 
         assertWithMessage("Initial scene key mismatch!")
             .that(sceneContainerViewModel.currentScene.value)
@@ -325,6 +328,16 @@
         }
 
     @Test
+    fun lockDeviceLocksDevice() =
+        testScope.runTest {
+            unlockDevice()
+            assertCurrentScene(Scenes.Gone)
+
+            lockDevice()
+            assertCurrentScene(Scenes.Lockscreen)
+        }
+
+    @Test
     fun deviceGoesToSleep_switchesToLockscreen() =
         testScope.runTest {
             unlockDevice()
@@ -397,7 +410,7 @@
             assertThat(upDestinationSceneKey).isEqualTo(Scenes.Bouncer)
             emulateUserDrivenTransition(to = upDestinationSceneKey)
 
-            val bouncerActionButton by collectLastValue(bouncerViewModel.actionButton)
+            val bouncerActionButton by collectLastValue(bouncerSceneContentViewModel.actionButton)
             assertWithMessage("Bouncer action button not visible")
                 .that(bouncerActionButton)
                 .isNotNull()
@@ -417,7 +430,7 @@
             assertThat(upDestinationSceneKey).isEqualTo(Scenes.Bouncer)
             emulateUserDrivenTransition(to = upDestinationSceneKey)
 
-            val bouncerActionButton by collectLastValue(bouncerViewModel.actionButton)
+            val bouncerActionButton by collectLastValue(bouncerSceneContentViewModel.actionButton)
             assertWithMessage("Bouncer action button not visible during call")
                 .that(bouncerActionButton)
                 .isNotNull()
@@ -568,7 +581,7 @@
         bouncerSceneJob =
             if (to == Scenes.Bouncer) {
                 testScope.backgroundScope.launch {
-                    bouncerViewModel.authMethodViewModel.collect {
+                    bouncerSceneContentViewModel.authMethodViewModel.collect {
                         // Do nothing. Need this to turn this otherwise cold flow, hot.
                     }
                 }
@@ -613,7 +626,7 @@
         assertWithMessage("The authentication method of $authMethod is not secure, cannot lock!")
             .that(authMethod.isSecure)
             .isTrue()
-
+        kosmos.sceneInteractor.changeScene(Scenes.Lockscreen, "")
         runCurrent()
     }
 
@@ -644,7 +657,8 @@
         assertWithMessage("Cannot enter PIN when not on the Bouncer scene!")
             .that(getCurrentSceneInUi())
             .isEqualTo(Scenes.Bouncer)
-        val authMethodViewModel by collectLastValue(bouncerViewModel.authMethodViewModel)
+        val authMethodViewModel by
+            collectLastValue(bouncerSceneContentViewModel.authMethodViewModel)
         assertWithMessage("Cannot enter PIN when not using a PIN authentication method!")
             .that(authMethodViewModel)
             .isInstanceOf(PinBouncerViewModel::class.java)
@@ -672,7 +686,8 @@
         assertWithMessage("Cannot enter PIN when not on the Bouncer scene!")
             .that(getCurrentSceneInUi())
             .isEqualTo(Scenes.Bouncer)
-        val authMethodViewModel by collectLastValue(bouncerViewModel.authMethodViewModel)
+        val authMethodViewModel by
+            collectLastValue(bouncerSceneContentViewModel.authMethodViewModel)
         assertWithMessage("Cannot enter PIN when not using a PIN authentication method!")
             .that(authMethodViewModel)
             .isInstanceOf(PinBouncerViewModel::class.java)
@@ -719,7 +734,7 @@
 
     /** Emulates the dismissal of the IME (soft keyboard). */
     private fun TestScope.dismissIme() {
-        (bouncerViewModel.authMethodViewModel.value as? PasswordBouncerViewModel)?.let {
+        (bouncerSceneContentViewModel.authMethodViewModel.value as? PasswordBouncerViewModel)?.let {
             it.onImeDismissed()
             runCurrent()
         }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModelTest.kt
index ea95aab..f85823a 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModelTest.kt
@@ -14,6 +14,8 @@
  * limitations under the License.
  */
 
+@file:OptIn(ExperimentalCoroutinesApi::class)
+
 package com.android.systemui.scene.ui.viewmodel
 
 import android.view.MotionEvent
@@ -25,6 +27,7 @@
 import com.android.systemui.coroutines.collectLastValue
 import com.android.systemui.flags.EnableSceneContainer
 import com.android.systemui.kosmos.testScope
+import com.android.systemui.lifecycle.activateIn
 import com.android.systemui.power.data.repository.fakePowerRepository
 import com.android.systemui.power.domain.interactor.powerInteractor
 import com.android.systemui.scene.domain.interactor.sceneInteractor
@@ -37,6 +40,8 @@
 import com.android.systemui.util.mockito.whenever
 import com.google.common.truth.Truth.assertThat
 import com.google.common.truth.Truth.assertWithMessage
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.Job
 import kotlinx.coroutines.test.runCurrent
 import kotlinx.coroutines.test.runTest
 import org.junit.Before
@@ -57,6 +62,9 @@
 
     private lateinit var underTest: SceneContainerViewModel
 
+    private lateinit var activationJob: Job
+    private var motionEventHandler: SceneContainerViewModel.MotionEventHandler? = null
+
     @Before
     fun setUp() {
         underTest =
@@ -64,10 +72,28 @@
                 sceneInteractor = sceneInteractor,
                 falsingInteractor = kosmos.falsingInteractor,
                 powerInteractor = kosmos.powerInteractor,
+                motionEventHandlerReceiver = { motionEventHandler ->
+                    this@SceneContainerViewModelTest.motionEventHandler = motionEventHandler
+                },
             )
+        activationJob = Job()
+        underTest.activateIn(testScope, activationJob)
     }
 
     @Test
+    fun activate_setsMotionEventHandler() =
+        testScope.runTest { assertThat(motionEventHandler).isNotNull() }
+
+    @Test
+    fun deactivate_clearsMotionEventHandler() =
+        testScope.runTest {
+            activationJob.cancel()
+            runCurrent()
+
+            assertThat(motionEventHandler).isNull()
+        }
+
+    @Test
     fun isVisible() =
         testScope.runTest {
             val isVisible by collectLastValue(underTest.isVisible)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/BaseHeadsUpManagerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/BaseHeadsUpManagerTest.java
index 9005ae3..89aa670 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/BaseHeadsUpManagerTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/BaseHeadsUpManagerTest.java
@@ -241,7 +241,7 @@
         alm.showNotification(entry);
 
         final boolean removedImmediately = alm.removeNotification(
-                entry.getKey(), /* releaseImmediately = */ false);
+                entry.getKey(), /* releaseImmediately = */ false, "removeDeferred");
         assertFalse(removedImmediately);
         assertTrue(alm.isHeadsUpEntry(entry.getKey()));
     }
@@ -254,7 +254,7 @@
         alm.showNotification(entry);
 
         final boolean removedImmediately = alm.removeNotification(
-                entry.getKey(), /* releaseImmediately = */ true);
+                entry.getKey(), /* releaseImmediately = */ true, "forceRemove");
         assertTrue(removedImmediately);
         assertFalse(alm.isHeadsUpEntry(entry.getKey()));
     }
@@ -430,7 +430,7 @@
         hum.showNotification(entry);
 
         final boolean removedImmediately = hum.removeNotification(
-                entry.getKey(), /* releaseImmediately = */ false);
+                entry.getKey(), /* releaseImmediately = */ false, "beforeMinimumDisplayTime");
         assertFalse(removedImmediately);
         assertTrue(hum.isHeadsUpEntry(entry.getKey()));
 
@@ -452,7 +452,7 @@
         assertTrue(hum.isHeadsUpEntry(entry.getKey()));
 
         final boolean removedImmediately = hum.removeNotification(
-                entry.getKey(), /* releaseImmediately = */ false);
+                entry.getKey(), /* releaseImmediately = */ false, "afterMinimumDisplayTime");
         assertTrue(removedImmediately);
         assertFalse(hum.isHeadsUpEntry(entry.getKey()));
     }
@@ -466,7 +466,7 @@
         hum.showNotification(entry);
 
         final boolean removedImmediately = hum.removeNotification(
-                entry.getKey(), /* releaseImmediately = */ true);
+                entry.getKey(), /* releaseImmediately = */ true, "afterMinimumDisplayTime");
         assertTrue(removedImmediately);
         assertFalse(hum.isHeadsUpEntry(entry.getKey()));
     }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/HeadsUpManagerPhoneTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/HeadsUpManagerPhoneTest.kt
index 7a6838a..ca106fa 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/HeadsUpManagerPhoneTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/HeadsUpManagerPhoneTest.kt
@@ -179,8 +179,8 @@
         mContext
             .getOrCreateTestableResources()
             .addOverride(R.integer.ambient_notification_extension_time, 500)
-        mAvalancheController = AvalancheController(dumpManager, mUiEventLogger,
-                mHeadsUpManagerLogger, mBgHandler)
+        mAvalancheController =
+            AvalancheController(dumpManager, mUiEventLogger, mHeadsUpManagerLogger, mBgHandler)
     }
 
     @Test
@@ -200,7 +200,12 @@
         hmp.addSwipedOutNotification(entry.key)
 
         // Remove should succeed because the notification is swiped out
-        val removedImmediately = hmp.removeNotification(entry.key, /* releaseImmediately= */ false)
+        val removedImmediately =
+            hmp.removeNotification(
+                entry.key,
+                /* releaseImmediately= */ false,
+                /* reason= */ "swipe out"
+            )
         Assert.assertTrue(removedImmediately)
         Assert.assertFalse(hmp.isHeadsUpEntry(entry.key))
     }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/TestableHeadsUpManager.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/TestableHeadsUpManager.java
index 69207ba..3efabd7 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/TestableHeadsUpManager.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/TestableHeadsUpManager.java
@@ -100,7 +100,7 @@
 
     @Override
     public boolean removeNotification(@NonNull String key, boolean releaseImmediately,
-            boolean animate) {
+            boolean animate, @NonNull String reason) {
         throw new UnsupportedOperationException();
     }
 
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/ui/dialog/viewmodel/ModesDialogViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/ui/dialog/viewmodel/ModesDialogViewModelTest.kt
index bcad7e7..54b7d25 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/ui/dialog/viewmodel/ModesDialogViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/policy/ui/dialog/viewmodel/ModesDialogViewModelTest.kt
@@ -23,6 +23,7 @@
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import com.android.settingslib.notification.modes.TestModeBuilder
+import com.android.settingslib.notification.modes.ZenMode
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.coroutines.collectLastValue
 import com.android.systemui.kosmos.testDispatcher
@@ -30,6 +31,7 @@
 import com.android.systemui.statusbar.policy.data.repository.fakeZenModeRepository
 import com.android.systemui.statusbar.policy.domain.interactor.zenModeInteractor
 import com.android.systemui.statusbar.policy.ui.dialog.mockModesDialogDelegate
+import com.android.systemui.statusbar.policy.ui.dialog.mockModesDialogEventLogger
 import com.android.systemui.testKosmos
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -40,6 +42,7 @@
 import org.junit.runner.RunWith
 import org.mockito.Mockito.clearInvocations
 import org.mockito.kotlin.argumentCaptor
+import org.mockito.kotlin.times
 import org.mockito.kotlin.verify
 
 @SmallTest
@@ -50,9 +53,16 @@
     private val repository = kosmos.fakeZenModeRepository
     private val interactor = kosmos.zenModeInteractor
     private val mockDialogDelegate = kosmos.mockModesDialogDelegate
+    private val mockDialogEventLogger = kosmos.mockModesDialogEventLogger
 
     private val underTest =
-        ModesDialogViewModel(context, interactor, kosmos.testDispatcher, mockDialogDelegate)
+        ModesDialogViewModel(
+            context,
+            interactor,
+            kosmos.testDispatcher,
+            mockDialogDelegate,
+            mockDialogEventLogger
+        )
 
     @Test
     fun tiles_filtersOutUserDisabledModes() =
@@ -432,4 +442,84 @@
             assertThat(intent.extras?.getString(Settings.EXTRA_AUTOMATIC_ZEN_RULE_ID))
                 .isEqualTo("B")
         }
+
+    @Test
+    fun onClick_logsOnOffEvents() =
+        testScope.runTest {
+            val tiles by collectLastValue(underTest.tiles)
+
+            repository.addModes(
+                listOf(
+                    TestModeBuilder.MANUAL_DND_ACTIVE,
+                    TestModeBuilder()
+                        .setId("id1")
+                        .setName("Inactive Mode One")
+                        .setActive(false)
+                        .setManualInvocationAllowed(true)
+                        .build(),
+                    TestModeBuilder()
+                        .setId("id2")
+                        .setName("Active Non-Invokable Mode Two") // but can be turned off by tile
+                        .setActive(true)
+                        .setManualInvocationAllowed(false)
+                        .build(),
+                )
+            )
+            runCurrent()
+
+            assertThat(tiles?.size).isEqualTo(3)
+
+            // Trigger onClick for each tile in sequence
+            tiles?.forEach { it.onClick.invoke() }
+            runCurrent()
+
+            val onModeCaptor = argumentCaptor<ZenMode>()
+            val offModeCaptor = argumentCaptor<ZenMode>()
+
+            // manual mode and mode 2 should have turned off
+            verify(mockDialogEventLogger, times(2)).logModeOff(offModeCaptor.capture())
+            val off0 = offModeCaptor.firstValue
+            assertThat(off0.isManualDnd).isTrue()
+
+            val off1 = offModeCaptor.secondValue
+            assertThat(off1.id).isEqualTo("id2")
+
+            // should also have logged turning mode 1 on
+            verify(mockDialogEventLogger).logModeOn(onModeCaptor.capture())
+            val on = onModeCaptor.lastValue
+            assertThat(on.id).isEqualTo("id1")
+        }
+
+    @Test
+    fun onLongClick_logsSettingsEvents() =
+        testScope.runTest {
+            val tiles by collectLastValue(underTest.tiles)
+
+            repository.addModes(
+                listOf(
+                    TestModeBuilder.MANUAL_DND_ACTIVE,
+                    TestModeBuilder()
+                        .setId("id1")
+                        .setName("Inactive Mode One")
+                        .setActive(false)
+                        .setManualInvocationAllowed(true)
+                        .build(),
+                )
+            )
+            runCurrent()
+
+            assertThat(tiles?.size).isEqualTo(2)
+            val modeCaptor = argumentCaptor<ZenMode>()
+
+            // long click manual DND and then automatic mode
+            tiles?.forEach { it.onLongClick.invoke() }
+            runCurrent()
+
+            verify(mockDialogEventLogger, times(2)).logModeSettings(modeCaptor.capture())
+            val manualMode = modeCaptor.firstValue
+            assertThat(manualMode.isManualDnd).isTrue()
+
+            val automaticMode = modeCaptor.lastValue
+            assertThat(automaticMode.id).isEqualTo("id1")
+        }
 }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/domain/interactor/AudioOutputInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/domain/interactor/AudioOutputInteractorTest.kt
index 7385a47..7c55f7a 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/domain/interactor/AudioOutputInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/domain/interactor/AudioOutputInteractorTest.kt
@@ -32,7 +32,6 @@
 import com.android.systemui.testKosmos
 import com.android.systemui.volume.data.repository.TestAudioDevicesFactory
 import com.android.systemui.volume.data.repository.audioRepository
-import com.android.systemui.volume.data.repository.audioSharingRepository
 import com.android.systemui.volume.domain.model.AudioOutputDevice
 import com.android.systemui.volume.localMediaController
 import com.android.systemui.volume.localMediaRepository
@@ -222,32 +221,4 @@
 
         val testIcon = TestStubDrawable()
     }
-
-    @Test
-    fun inAudioSharing_returnTrue() {
-        with(kosmos) {
-            testScope.runTest {
-                audioSharingRepository.setInAudioSharing(true)
-
-                val inAudioSharing by collectLastValue(underTest.isInAudioSharing)
-                runCurrent()
-
-                assertThat(inAudioSharing).isTrue()
-            }
-        }
-    }
-
-    @Test
-    fun notInAudioSharing_returnFalse() {
-        with(kosmos) {
-            testScope.runTest {
-                audioSharingRepository.setInAudioSharing(false)
-
-                val inAudioSharing by collectLastValue(underTest.isInAudioSharing)
-                runCurrent()
-
-                assertThat(inAudioSharing).isFalse()
-            }
-        }
-    }
 }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/domain/interactor/AudioSharingInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/domain/interactor/AudioSharingInteractorTest.kt
index a1fcfcd..c9d147b 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/domain/interactor/AudioSharingInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/volume/domain/interactor/AudioSharingInteractorTest.kt
@@ -49,6 +49,24 @@
     }
 
     @Test
+    fun handleInAudioSharingChange() {
+        with(kosmos) {
+            testScope.runTest {
+                with(audioSharingRepository) { setInAudioSharing(true) }
+                val inAudioSharing by collectLastValue(underTest.isInAudioSharing)
+                runCurrent()
+
+                Truth.assertThat(inAudioSharing).isEqualTo(true)
+
+                with(audioSharingRepository) { setInAudioSharing(false) }
+                runCurrent()
+
+                Truth.assertThat(inAudioSharing).isEqualTo(false)
+            }
+        }
+    }
+
+    @Test
     fun handlePrimaryGroupChange_nullVolume() {
         with(kosmos) {
             testScope.runTest {
diff --git a/packages/SystemUI/res-keyguard/values-ar/strings.xml b/packages/SystemUI/res-keyguard/values-ar/strings.xml
index 57fd9ea..d069c01 100644
--- a/packages/SystemUI/res-keyguard/values-ar/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-ar/strings.xml
@@ -71,7 +71,7 @@
     <string name="kg_prompt_after_dpm_lock" msgid="6002804765868345917">"لمزيد من الأمان، تم قفل الجهاز وفقًا لسياسة العمل."</string>
     <string name="kg_prompt_after_user_lockdown_pin" msgid="5374732179740050373">"يجب إدخال رقم التعريف الشخصي بعد إلغاء الفتح الذكي."</string>
     <string name="kg_prompt_after_user_lockdown_password" msgid="9097968458291129795">"يجب إدخال كلمة المرور بعد إلغاء الفتح الذكي."</string>
-    <string name="kg_prompt_after_user_lockdown_pattern" msgid="215072203613597906">"يجب رسم النقش بعد إلغاء التأمين."</string>
+    <string name="kg_prompt_after_user_lockdown_pattern" msgid="215072203613597906">"يجب رسم النقش بعد إلغاء الفتح الذكي."</string>
     <string name="kg_prompt_unattended_update" msgid="4366635751738712452">"سيتم تثبيت التحديث عندما لا يكون الجهاز قيد الاستخدام."</string>
     <string name="kg_prompt_pin_auth_timeout" msgid="5868644725126275245">"‏يجب تعزيز الأمان. لم يُستخدَم رقم PIN لبعض الوقت."</string>
     <string name="kg_prompt_password_auth_timeout" msgid="5809110458491920871">"يجب تعزيز الأمان. لم تستخدَم كلمة المرور لبعض الوقت."</string>
diff --git a/packages/SystemUI/res-keyguard/values-ru/strings.xml b/packages/SystemUI/res-keyguard/values-ru/strings.xml
index fd90d08..cf2057c 100644
--- a/packages/SystemUI/res-keyguard/values-ru/strings.xml
+++ b/packages/SystemUI/res-keyguard/values-ru/strings.xml
@@ -57,7 +57,7 @@
     <string name="kg_wrong_pin" msgid="4160978845968732624">"Неверный PIN-код"</string>
     <string name="kg_wrong_pin_try_again" msgid="3129729383303430190">"Неверный PIN-код."</string>
     <string name="kg_wrong_input_try_fp_suggestion" msgid="3143861542242024833">"Повторите попытку или используйте отпечаток пальца."</string>
-    <string name="kg_fp_not_recognized" msgid="5183108260932029241">"Отпечаток не распознан."</string>
+    <string name="kg_fp_not_recognized" msgid="5183108260932029241">"Отпечаток не распознан"</string>
     <string name="bouncer_face_not_recognized" msgid="1666128054475597485">"Лицо не распознано."</string>
     <string name="kg_bio_try_again_or_pin" msgid="4752168242723808390">"Повторите попытку или введите PIN-код."</string>
     <string name="kg_bio_try_again_or_password" msgid="1473132729225398039">"Повторите попытку или введите пароль."</string>
diff --git a/packages/SystemUI/res/values/config.xml b/packages/SystemUI/res/values/config.xml
index f0c8894..823ff9f 100644
--- a/packages/SystemUI/res/values/config.xml
+++ b/packages/SystemUI/res/values/config.xml
@@ -1053,4 +1053,15 @@
 
     <!-- List of packages for which we want to use activity info (instead of application info) for biometric prompt logo. Empty for AOSP. [DO NOT TRANSLATE] -->
     <string-array name="config_useActivityLogoForBiometricPrompt" translatable="false"/>
+
+    <!--
+    Whether to enable the desktop specific feature set.
+
+    Refrain from using this from code that needs to make decisions
+    regarding the size or density of the display.
+
+    Variant owners and OEMs should override this to true when they want to
+    enable the desktop specific features.
+    -->
+    <bool name="config_enableDesktopFeatureSet">false</bool>
 </resources>
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index f8303ea..8a2e767 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -756,6 +756,8 @@
     <string name="quick_settings_bluetooth_audio_sharing_button">Share audio</string>
     <!-- QuickSettings: Bluetooth dialog audio sharing button text when sharing audio [CHAR LIMIT=50]-->
     <string name="quick_settings_bluetooth_audio_sharing_button_sharing">Sharing audio</string>
+    <!-- QuickSettings: Bluetooth dialog audio sharing button text accessibility label. Used as part of the string "Double tap to enter audio sharing settings". [CHAR LIMIT=50]-->
+    <string name="quick_settings_bluetooth_audio_sharing_button_accessibility">enter audio sharing settings</string>
 
     <!-- QuickSettings: Bluetooth secondary label for the battery level of a connected device [CHAR LIMIT=20]-->
     <string name="quick_settings_bluetooth_secondary_label_battery_level"><xliff:g id="battery_level_as_percentage">%s</xliff:g> battery</string>
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/model/Task.java b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/model/Task.java
index edf855f..64fe78d 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/recents/model/Task.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/recents/model/Task.java
@@ -19,8 +19,8 @@
 import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED;
 import static android.view.Display.DEFAULT_DISPLAY;
 
-import static com.android.wm.shell.common.split.SplitScreenConstants.CONTROLLED_ACTIVITY_TYPES;
-import static com.android.wm.shell.common.split.SplitScreenConstants.CONTROLLED_WINDOWING_MODES_WHEN_ACTIVE;
+import static com.android.wm.shell.shared.split.SplitScreenConstants.CONTROLLED_ACTIVITY_TYPES;
+import static com.android.wm.shell.shared.split.SplitScreenConstants.CONTROLLED_WINDOWING_MODES_WHEN_ACTIVE;
 
 import android.app.ActivityManager;
 import android.app.ActivityManager.TaskDescription;
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
index 0da252d..60fff28 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
@@ -104,6 +104,7 @@
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 
+import com.android.compose.animation.scene.ObservableTransitionState;
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.foldables.FoldGracePeriodProvider;
 import com.android.internal.jank.InteractionJankMonitor;
@@ -119,6 +120,7 @@
 import com.android.systemui.Dumpable;
 import com.android.systemui.biometrics.AuthController;
 import com.android.systemui.biometrics.FingerprintInteractiveToAuthProvider;
+import com.android.systemui.bouncer.domain.interactor.AlternateBouncerInteractor;
 import com.android.systemui.broadcast.BroadcastDispatcher;
 import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.dagger.qualifiers.Background;
@@ -140,6 +142,9 @@
 import com.android.systemui.plugins.clocks.WeatherData;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.res.R;
+import com.android.systemui.scene.domain.interactor.SceneInteractor;
+import com.android.systemui.scene.shared.flag.SceneContainerFlag;
+import com.android.systemui.scene.shared.model.Scenes;
 import com.android.systemui.settings.UserTracker;
 import com.android.systemui.shared.system.TaskStackChangeListener;
 import com.android.systemui.shared.system.TaskStackChangeListeners;
@@ -150,6 +155,7 @@
 import com.android.systemui.telephony.TelephonyListenerManager;
 import com.android.systemui.user.domain.interactor.SelectedUserInteractor;
 import com.android.systemui.util.Assert;
+import com.android.systemui.util.kotlin.JavaAdapter;
 
 import dalvik.annotation.optimization.NeverCompile;
 
@@ -279,6 +285,9 @@
     private final UserTracker mUserTracker;
     private final KeyguardUpdateMonitorLogger mLogger;
     private final boolean mIsSystemUser;
+    private final Provider<JavaAdapter> mJavaAdapter;
+    private final Provider<SceneInteractor> mSceneInteractor;
+    private final Provider<AlternateBouncerInteractor> mAlternateBouncerInteractor;
     private final AuthController mAuthController;
     private final UiEventLogger mUiEventLogger;
     private final Set<String> mAllowFingerprintOnOccludingActivitiesFromPackage;
@@ -563,7 +572,7 @@
      */
     private boolean shouldDismissKeyguardOnTrustGrantedWithCurrentUser(TrustGrantFlags flags) {
         final boolean isBouncerShowing =
-                mPrimaryBouncerIsOrWillBeShowing || mAlternateBouncerShowing;
+                isPrimaryBouncerShowingOrWillBeShowing() || isAlternateBouncerShowing();
         return (flags.isInitiatedByUser() || flags.dismissKeyguardRequested())
                 && (mDeviceInteractive || flags.temporaryAndRenewable())
                 && (isBouncerShowing || flags.dismissKeyguardRequested());
@@ -1170,8 +1179,8 @@
         Assert.isMainThread();
         String reason =
                 mKeyguardBypassController.canBypass() ? "bypass"
-                        : mAlternateBouncerShowing ? "alternateBouncer"
-                                : mPrimaryBouncerFullyShown ? "bouncer"
+                        : isAlternateBouncerShowing() ? "alternateBouncer"
+                                : isPrimaryBouncerFullyShown() ? "bouncer"
                                         : "udfpsFpDown";
         requestActiveUnlock(
                 ActiveUnlockConfig.ActiveUnlockRequestOrigin.BIOMETRIC_FAIL,
@@ -2169,7 +2178,10 @@
             Optional<FingerprintInteractiveToAuthProvider> interactiveToAuthProvider,
             TaskStackChangeListeners taskStackChangeListeners,
             SelectedUserInteractor selectedUserInteractor,
-            IActivityTaskManager activityTaskManagerService) {
+            IActivityTaskManager activityTaskManagerService,
+            Provider<AlternateBouncerInteractor> alternateBouncerInteractor,
+            Provider<JavaAdapter> javaAdapter,
+            Provider<SceneInteractor> sceneInteractor) {
         mContext = context;
         mSubscriptionManager = subscriptionManager;
         mUserTracker = userTracker;
@@ -2214,6 +2226,9 @@
 
         mFingerprintInteractiveToAuthProvider = interactiveToAuthProvider.orElse(null);
         mIsSystemUser = mUserManager.isSystemUser();
+        mAlternateBouncerInteractor = alternateBouncerInteractor;
+        mJavaAdapter = javaAdapter;
+        mSceneInteractor = sceneInteractor;
 
         mHandler = new Handler(mainLooper) {
             @Override
@@ -2470,6 +2485,30 @@
         mContext.getContentResolver().registerContentObserver(
                 Settings.System.getUriFor(Settings.System.TIME_12_24),
                 false, mTimeFormatChangeObserver, UserHandle.USER_ALL);
+
+        if (SceneContainerFlag.isEnabled()) {
+            mJavaAdapter.get().alwaysCollectFlow(
+                    mAlternateBouncerInteractor.get().isVisible(),
+                    this::onAlternateBouncerVisibilityChange);
+            mJavaAdapter.get().alwaysCollectFlow(
+                    mSceneInteractor.get().getTransitionState(),
+                    this::onTransitionStateChanged
+            );
+        }
+    }
+
+    @VisibleForTesting
+    void onAlternateBouncerVisibilityChange(boolean isAlternateBouncerVisible) {
+        setAlternateBouncerShowing(isAlternateBouncerVisible);
+    }
+
+
+    @VisibleForTesting
+    void onTransitionStateChanged(ObservableTransitionState transitionState) {
+        int primaryBouncerFullyShown = isPrimaryBouncerFullyShown(transitionState) ? 1 : 0;
+        int primaryBouncerIsOrWillBeShowing =
+                  isPrimaryBouncerShowingOrWillBeShowing(transitionState) ? 1 : 0;
+        handlePrimaryBouncerChanged(primaryBouncerIsOrWillBeShowing, primaryBouncerFullyShown);
     }
 
     private void initializeSimState() {
@@ -2717,8 +2756,8 @@
         requestActiveUnlock(
                 requestOrigin,
                 extraReason, canFaceBypass
-                        || mAlternateBouncerShowing
-                        || mPrimaryBouncerFullyShown
+                        || isAlternateBouncerShowing()
+                        || isPrimaryBouncerFullyShown()
                         || mAuthController.isUdfpsFingerDown());
     }
 
@@ -2739,7 +2778,7 @@
      */
     public void setAlternateBouncerShowing(boolean showing) {
         mAlternateBouncerShowing = showing;
-        if (mAlternateBouncerShowing) {
+        if (isAlternateBouncerShowing()) {
             requestActiveUnlock(
                     ActiveUnlockConfig.ActiveUnlockRequestOrigin.UNLOCK_INTENT,
                     "alternateBouncer");
@@ -2747,6 +2786,45 @@
         updateFingerprintListeningState(BIOMETRIC_ACTION_UPDATE);
     }
 
+    private boolean isAlternateBouncerShowing() {
+        if (SceneContainerFlag.isEnabled()) {
+            return mAlternateBouncerInteractor.get().isVisibleState();
+        } else {
+            return mAlternateBouncerShowing;
+        }
+    }
+
+    private boolean isPrimaryBouncerShowingOrWillBeShowing() {
+        if (SceneContainerFlag.isEnabled()) {
+            return isPrimaryBouncerShowingOrWillBeShowing(
+                    mSceneInteractor.get().getTransitionState().getValue());
+        } else {
+            return mPrimaryBouncerIsOrWillBeShowing;
+        }
+    }
+
+    private boolean isPrimaryBouncerFullyShown() {
+        if (SceneContainerFlag.isEnabled()) {
+            return isPrimaryBouncerFullyShown(
+                    mSceneInteractor.get().getTransitionState().getValue());
+        } else {
+            return mPrimaryBouncerFullyShown;
+        }
+    }
+
+    private boolean isPrimaryBouncerShowingOrWillBeShowing(
+            ObservableTransitionState transitionState
+    ) {
+        SceneContainerFlag.assertInNewMode();
+        return isPrimaryBouncerFullyShown(transitionState)
+                || transitionState.isTransitioning(null, Scenes.Bouncer);
+    }
+
+    private boolean isPrimaryBouncerFullyShown(ObservableTransitionState transitionState) {
+        SceneContainerFlag.assertInNewMode();
+        return transitionState.isIdle(Scenes.Bouncer);
+    }
+
     /**
      * If the current state of the device allows for triggering active unlock. This does not
      * include active unlock availability.
@@ -2762,7 +2840,7 @@
     private boolean shouldTriggerActiveUnlock(boolean shouldLog) {
         // Triggers:
         final boolean triggerActiveUnlockForAssistant = shouldTriggerActiveUnlockForAssistant();
-        final boolean awakeKeyguard = mPrimaryBouncerFullyShown || mAlternateBouncerShowing
+        final boolean awakeKeyguard = isPrimaryBouncerFullyShown() || isAlternateBouncerShowing()
                 || (isKeyguardVisible() && !mGoingToSleep
                 && mStatusBarState != StatusBarState.SHADE_LOCKED);
 
@@ -2830,14 +2908,14 @@
         final boolean shouldListenKeyguardState =
                 isKeyguardVisible()
                         || !mDeviceInteractive
-                        || (mPrimaryBouncerIsOrWillBeShowing && !mKeyguardGoingAway)
+                        || (isPrimaryBouncerShowingOrWillBeShowing() && !mKeyguardGoingAway)
                         || mGoingToSleep
                         || shouldListenForFingerprintAssistant
                         || (mKeyguardOccluded && mIsDreaming)
                         || (mKeyguardOccluded && userDoesNotHaveTrust && mKeyguardShowing
                         && (mOccludingAppRequestingFp
                         || isUdfps
-                        || mAlternateBouncerShowing
+                        || isAlternateBouncerShowing()
                         || mAllowFingerprintOnCurrentOccludingActivity
                 )
             );
@@ -2856,7 +2934,7 @@
                         && !isUserInLockdown(user);
         final boolean strongerAuthRequired = !isUnlockingWithFingerprintAllowed();
         final boolean shouldListenBouncerState =
-                !strongerAuthRequired || !mPrimaryBouncerIsOrWillBeShowing;
+                !strongerAuthRequired || !isPrimaryBouncerShowingOrWillBeShowing();
 
         final boolean shouldListenUdfpsState = !isUdfps
                 || (!userCanSkipBouncer
@@ -2872,10 +2950,10 @@
                     user,
                     shouldListen,
                     mAllowFingerprintOnCurrentOccludingActivity,
-                    mAlternateBouncerShowing,
+                    isAlternateBouncerShowing(),
                     biometricEnabledForUser,
                     mBiometricPromptShowing,
-                    mPrimaryBouncerIsOrWillBeShowing,
+                    isPrimaryBouncerShowingOrWillBeShowing(),
                     userCanSkipBouncer,
                     mCredentialAttempted,
                     mDeviceInteractive,
@@ -3614,6 +3692,7 @@
      */
     public void sendPrimaryBouncerChanged(boolean primaryBouncerIsOrWillBeShowing,
             boolean primaryBouncerFullyShown) {
+        SceneContainerFlag.assertInLegacyMode();
         mLogger.logSendPrimaryBouncerChanged(primaryBouncerIsOrWillBeShowing,
                 primaryBouncerFullyShown);
         Message message = mHandler.obtainMessage(MSG_KEYGUARD_BOUNCER_CHANGED);
@@ -4031,10 +4110,10 @@
             if (isUdfpsSupported()) {
                 pw.println("        udfpsEnrolled=" + isUdfpsEnrolled());
                 pw.println("        shouldListenForUdfps=" + shouldListenForFingerprint(true));
-                pw.println("        mPrimaryBouncerIsOrWillBeShowing="
-                        + mPrimaryBouncerIsOrWillBeShowing);
+                pw.println("        isPrimaryBouncerShowingOrWillBeShowing="
+                        + isPrimaryBouncerShowingOrWillBeShowing());
                 pw.println("        mStatusBarState=" + StatusBarState.toString(mStatusBarState));
-                pw.println("        mAlternateBouncerShowing=" + mAlternateBouncerShowing);
+                pw.println("        isAlternateBouncerShowing=" + isAlternateBouncerShowing());
             } else if (isSfpsSupported()) {
                 pw.println("        sfpsEnrolled=" + isSfpsEnrolled());
                 pw.println("        shouldListenForSfps=" + shouldListenForFingerprint(false));
diff --git a/packages/SystemUI/src/com/android/systemui/SwipeHelper.java b/packages/SystemUI/src/com/android/systemui/SwipeHelper.java
index 63ad41a..13cd2c5 100644
--- a/packages/SystemUI/src/com/android/systemui/SwipeHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/SwipeHelper.java
@@ -53,6 +53,7 @@
 import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin;
 import com.android.systemui.res.R;
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
+import com.android.systemui.statusbar.notification.shared.NotificationContentAlphaOptimization;
 import com.android.wm.shell.animation.FlingAnimationUtils;
 import com.android.wm.shell.shared.animation.PhysicsAnimator;
 import com.android.wm.shell.shared.animation.PhysicsAnimator.SpringConfig;
@@ -63,13 +64,6 @@
 public class SwipeHelper implements Gefingerpoken, Dumpable {
     static final String TAG = "com.android.systemui.SwipeHelper";
     private static final boolean DEBUG_INVALIDATE = false;
-    private static final boolean CONSTRAIN_SWIPE = true;
-    private static final boolean FADE_OUT_DURING_SWIPE = true;
-    private static final boolean DISMISS_IF_SWIPED_FAR_ENOUGH = true;
-
-    public static final int X = 0;
-    public static final int Y = 1;
-
     private static final float SWIPE_ESCAPE_VELOCITY = 500f; // dp/sec
     private static final int DEFAULT_ESCAPE_ANIMATION_DURATION = 200; // ms
     private static final int MAX_ESCAPE_ANIMATION_DURATION = 400; // ms
@@ -171,10 +165,6 @@
         mPagingTouchSlop = pagingTouchSlop;
     }
 
-    public void setDisableHardwareLayers(boolean disableHwLayers) {
-        mDisableHwLayers = disableHwLayers;
-    }
-
     private float getPos(MotionEvent ev) {
         return ev.getX();
     }
@@ -253,13 +243,14 @@
             float translation) {
         float swipeProgress = getSwipeProgressForOffset(animView, translation);
         if (!mCallback.updateSwipeProgress(animView, dismissable, swipeProgress)) {
-            if (FADE_OUT_DURING_SWIPE && dismissable) {
-                if (!mDisableHwLayers) {
-                    if (swipeProgress != 0f && swipeProgress != 1f) {
-                        animView.setLayerType(View.LAYER_TYPE_HARDWARE, null);
-                    } else {
-                        animView.setLayerType(View.LAYER_TYPE_NONE, null);
-                    }
+            if (dismissable
+                    || (NotificationContentAlphaOptimization.isEnabled() && translation == 0)) {
+                // We need to reset the content alpha even when the view is not dismissible (eg.
+                //  when Guts is visible)
+                if (swipeProgress != 0f && swipeProgress != 1f) {
+                    animView.setLayerType(View.LAYER_TYPE_HARDWARE, null);
+                } else {
+                    animView.setLayerType(View.LAYER_TYPE_NONE, null);
                 }
                 updateSwipeProgressAlpha(animView, getSwipeAlpha(swipeProgress));
             }
@@ -436,9 +427,7 @@
             duration = fixedDuration;
         }
 
-        if (!mDisableHwLayers) {
-            animView.setLayerType(View.LAYER_TYPE_HARDWARE, null);
-        }
+        animView.setLayerType(View.LAYER_TYPE_HARDWARE, null);
         AnimatorUpdateListener updateListener = new AnimatorUpdateListener() {
             @Override
             public void onAnimationUpdate(ValueAnimator animation) {
@@ -493,9 +482,7 @@
                 if (endAction != null) {
                     endAction.accept(mCancelled);
                 }
-                if (!mDisableHwLayers) {
-                    animView.setLayerType(View.LAYER_TYPE_NONE, null);
-                }
+                animView.setLayerType(View.LAYER_TYPE_NONE, null);
                 onDismissChildWithAnimationFinished();
             }
         });
@@ -612,7 +599,11 @@
      * view is being animated to dismiss or snap.
      */
     public void onTranslationUpdate(View animView, float value, boolean canBeDismissed) {
-        updateSwipeProgressFromOffset(animView, canBeDismissed, value);
+        updateSwipeProgressFromOffset(
+                animView,
+                /* dismissable= */ canBeDismissed,
+                /* translation= */ value
+        );
     }
 
     private void snapChildInstantly(final View view) {
@@ -689,7 +680,7 @@
                     } else {
                         // don't let items that can't be dismissed be dragged more than
                         // maxScrollDistance
-                        if (CONSTRAIN_SWIPE && !mCallback.canChildBeDismissedInDirection(
+                        if (!mCallback.canChildBeDismissedInDirection(
                                 mTouchedView,
                                 delta > 0)) {
                             float size = getSize(mTouchedView);
@@ -761,8 +752,7 @@
 
     protected boolean swipedFarEnough() {
         float translation = getTranslation(mTouchedView);
-        return DISMISS_IF_SWIPED_FAR_ENOUGH
-                && Math.abs(translation) > SWIPED_FAR_ENOUGH_SIZE_FRACTION * getSize(
+        return Math.abs(translation) > SWIPED_FAR_ENOUGH_SIZE_FRACTION * getSize(
                 mTouchedView);
     }
 
@@ -822,9 +812,18 @@
     }
 
     public void forceResetSwipeState(@NonNull View view) {
-        if (view.getTranslationX() == 0) return;
+        if (view.getTranslationX() == 0
+                && (!NotificationContentAlphaOptimization.isEnabled() || view.getAlpha() == 1f)
+        ) {
+            // Don't do anything when translation is 0 and alpha is 1
+            return;
+        }
         setTranslation(view, 0);
-        updateSwipeProgressFromOffset(view, /* dismissable= */ true, 0);
+        updateSwipeProgressFromOffset(
+                view,
+                /* dismissable= */ true,
+                /* translation= */ 0
+        );
     }
 
     /** This method resets the swipe state, and if `resetAll` is true, also resets the snap state */
@@ -893,7 +892,6 @@
         pw.append("mTranslation=").println(mTranslation);
         pw.append("mCanCurrViewBeDimissed=").println(mCanCurrViewBeDimissed);
         pw.append("mMenuRowIntercepting=").println(mMenuRowIntercepting);
-        pw.append("mDisableHwLayers=").println(mDisableHwLayers);
         pw.append("mDismissPendingMap: ").println(mDismissPendingMap.size());
         if (!mDismissPendingMap.isEmpty()) {
             mDismissPendingMap.forEach((view, animator) -> {
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/MagnificationImpl.java b/packages/SystemUI/src/com/android/systemui/accessibility/MagnificationImpl.java
index d81a686..6c46318 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/MagnificationImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/MagnificationImpl.java
@@ -415,17 +415,13 @@
     @Override
     @MainThread
     public void showMagnificationButton(int displayId, int magnificationMode) {
-        if (Flags.delayShowMagnificationButton()) {
-            if (mHandler.hasMessages(MSG_SHOW_MAGNIFICATION_BUTTON_INTERNAL)) {
-                return;
-            }
-            mHandler.sendMessageDelayed(
-                    mHandler.obtainMessage(
-                            MSG_SHOW_MAGNIFICATION_BUTTON_INTERNAL, displayId, magnificationMode),
-                    DELAY_SHOW_MAGNIFICATION_TIMEOUT_MS);
-        } else {
-            showMagnificationButtonInternal(displayId, magnificationMode);
+        if (mHandler.hasMessages(MSG_SHOW_MAGNIFICATION_BUTTON_INTERNAL)) {
+            return;
         }
+        mHandler.sendMessageDelayed(
+                mHandler.obtainMessage(
+                        MSG_SHOW_MAGNIFICATION_BUTTON_INTERNAL, displayId, magnificationMode),
+                DELAY_SHOW_MAGNIFICATION_TIMEOUT_MS);
     }
 
     @MainThread
@@ -441,9 +437,7 @@
     @Override
     @MainThread
     public void removeMagnificationButton(int displayId) {
-        if (Flags.delayShowMagnificationButton()) {
-            mHandler.removeMessages(MSG_SHOW_MAGNIFICATION_BUTTON_INTERNAL);
-        }
+        mHandler.removeMessages(MSG_SHOW_MAGNIFICATION_BUTTON_INTERNAL);
         mModeSwitchesController.removeButton(displayId);
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/AccessibilityFloatingMenuController.java b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/AccessibilityFloatingMenuController.java
index f4a1f05..e4b7b7e 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/AccessibilityFloatingMenuController.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/AccessibilityFloatingMenuController.java
@@ -37,6 +37,7 @@
 import com.android.systemui.accessibility.AccessibilityButtonModeObserver.AccessibilityButtonMode;
 import com.android.systemui.accessibility.AccessibilityButtonTargetsObserver;
 import com.android.systemui.dagger.SysUISingleton;
+import com.android.systemui.navigationbar.NavigationModeController;
 import com.android.systemui.settings.DisplayTracker;
 import com.android.systemui.util.settings.SecureSettings;
 
@@ -61,6 +62,7 @@
 
     private final SecureSettings mSecureSettings;
     private final DisplayTracker mDisplayTracker;
+    private final NavigationModeController mNavigationModeController;
     @VisibleForTesting
     IAccessibilityFloatingMenu mFloatingMenu;
     private int mBtnMode;
@@ -106,7 +108,8 @@
             AccessibilityButtonModeObserver accessibilityButtonModeObserver,
             KeyguardUpdateMonitor keyguardUpdateMonitor,
             SecureSettings secureSettings,
-            DisplayTracker displayTracker) {
+            DisplayTracker displayTracker,
+            NavigationModeController navigationModeController) {
         mContext = context;
         mWindowManager = windowManager;
         mViewCaptureAwareWindowManager = viewCaptureAwareWindowManager;
@@ -117,6 +120,7 @@
         mKeyguardUpdateMonitor = keyguardUpdateMonitor;
         mSecureSettings = secureSettings;
         mDisplayTracker = displayTracker;
+        mNavigationModeController = navigationModeController;
 
         mIsKeyguardVisible = false;
     }
@@ -191,7 +195,8 @@
             final Context windowContext = mContext.createWindowContext(defaultDisplay,
                     TYPE_NAVIGATION_BAR_PANEL, /* options= */ null);
             mFloatingMenu = new MenuViewLayerController(windowContext, mWindowManager,
-                    mViewCaptureAwareWindowManager, mAccessibilityManager, mSecureSettings);
+                    mViewCaptureAwareWindowManager, mAccessibilityManager, mSecureSettings,
+                    mNavigationModeController);
         }
 
         mFloatingMenu.show();
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/DragToInteractAnimationController.java b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/DragToInteractAnimationController.java
index 7fd72ec..d718ae3 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/DragToInteractAnimationController.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/DragToInteractAnimationController.java
@@ -32,7 +32,7 @@
 import com.android.systemui.Flags;
 import com.android.wm.shell.common.bubbles.DismissCircleView;
 import com.android.wm.shell.common.bubbles.DismissView;
-import com.android.wm.shell.common.magnetictarget.MagnetizedObject;
+import com.android.wm.shell.shared.magnetictarget.MagnetizedObject;
 
 import java.util.Map;
 import java.util.Objects;
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayer.java b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayer.java
index 27ded74..d62162b 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayer.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayer.java
@@ -77,11 +77,12 @@
 import com.android.internal.messages.nano.SystemMessageProto;
 import com.android.internal.util.Preconditions;
 import com.android.systemui.Flags;
+import com.android.systemui.navigationbar.NavigationModeController;
 import com.android.systemui.res.R;
 import com.android.systemui.util.settings.SecureSettings;
 import com.android.wm.shell.bubbles.DismissViewUtils;
 import com.android.wm.shell.common.bubbles.DismissView;
-import com.android.wm.shell.common.magnetictarget.MagnetizedObject;
+import com.android.wm.shell.shared.magnetictarget.MagnetizedObject;
 
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
@@ -142,6 +143,8 @@
     private boolean mIsNotificationShown;
     private Optional<MenuEduTooltipView> mEduTooltipView = Optional.empty();
     private BroadcastReceiver mNotificationActionReceiver;
+    private NavigationModeController mNavigationModeController;
+    private NavigationModeController.ModeChangedListener mNavigationModeChangedListender;
 
     @IntDef({
             LayerIndex.MENU_VIEW,
@@ -220,7 +223,8 @@
             MenuViewModel menuViewModel,
             MenuViewAppearance menuViewAppearance, MenuView menuView,
             IAccessibilityFloatingMenu floatingMenu,
-            SecureSettings secureSettings) {
+            SecureSettings secureSettings,
+            NavigationModeController navigationModeController) {
         super(context);
 
         // Simplifies the translation positioning and animations
@@ -253,6 +257,8 @@
         mNotificationFactory = new MenuNotificationFactory(context);
         mNotificationManager = context.getSystemService(NotificationManager.class);
         mStatusBarManager = context.getSystemService(StatusBarManager.class);
+        mNavigationModeController = navigationModeController;
+        mNavigationModeChangedListender = (mode -> mMenuView.onPositionChanged());
 
         if (Flags.floatingMenuDragToEdit()) {
             mDragToInteractAnimationController = new DragToInteractAnimationController(
@@ -381,6 +387,7 @@
                 mMigrationTooltipObserver);
         mMessageView.setUndoListener(view -> undo());
         getContext().registerComponentCallbacks(this);
+        mNavigationModeController.addListener(mNavigationModeChangedListender);
     }
 
     @Override
@@ -396,6 +403,7 @@
                 mMigrationTooltipObserver);
         mHandler.removeCallbacksAndMessages(/* token= */ null);
         getContext().unregisterComponentCallbacks(this);
+        mNavigationModeController.removeListener(mNavigationModeChangedListender);
     }
 
     @Override
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayerController.java b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayerController.java
index 623536f..cb96e78 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayerController.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayerController.java
@@ -24,6 +24,7 @@
 import android.view.accessibility.AccessibilityManager;
 
 import com.android.app.viewcapture.ViewCaptureAwareWindowManager;
+import com.android.systemui.navigationbar.NavigationModeController;
 import com.android.systemui.util.settings.SecureSettings;
 
 /**
@@ -37,7 +38,8 @@
 
     MenuViewLayerController(Context context, WindowManager windowManager,
             ViewCaptureAwareWindowManager viewCaptureAwareWindowManager,
-            AccessibilityManager accessibilityManager, SecureSettings secureSettings) {
+            AccessibilityManager accessibilityManager, SecureSettings secureSettings,
+            NavigationModeController navigationModeController) {
         mWindowManager = viewCaptureAwareWindowManager;
 
         MenuViewModel menuViewModel = new MenuViewModel(
@@ -49,7 +51,8 @@
                 menuViewAppearance,
                 new MenuView(context, menuViewModel, menuViewAppearance, secureSettings),
                 this,
-                secureSettings);
+                secureSettings,
+                navigationModeController);
     }
 
     @Override
diff --git a/packages/SystemUI/src/com/android/systemui/assist/AssistDisclosure.java b/packages/SystemUI/src/com/android/systemui/assist/AssistDisclosure.java
index a5c5bec..f4e2b82 100644
--- a/packages/SystemUI/src/com/android/systemui/assist/AssistDisclosure.java
+++ b/packages/SystemUI/src/com/android/systemui/assist/AssistDisclosure.java
@@ -33,6 +33,7 @@
 import android.view.accessibility.AccessibilityEvent;
 
 import com.android.app.animation.Interpolators;
+import com.android.app.viewcapture.ViewCaptureAwareWindowManager;
 import com.android.systemui.res.R;
 
 /**
@@ -40,16 +41,17 @@
  */
 public class AssistDisclosure {
     private final Context mContext;
-    private final WindowManager mWm;
+    private final ViewCaptureAwareWindowManager mWm;
     private final Handler mHandler;
 
     private AssistDisclosureView mView;
     private boolean mViewAdded;
 
-    public AssistDisclosure(Context context, Handler handler) {
+    public AssistDisclosure(Context context, Handler handler,
+            ViewCaptureAwareWindowManager viewCaptureAwareWindowManager) {
         mContext = context;
         mHandler = handler;
-        mWm = mContext.getSystemService(WindowManager.class);
+        mWm = viewCaptureAwareWindowManager;
     }
 
     public void postShow() {
diff --git a/packages/SystemUI/src/com/android/systemui/assist/AssistManager.java b/packages/SystemUI/src/com/android/systemui/assist/AssistManager.java
index a67dcdb..939d96e 100644
--- a/packages/SystemUI/src/com/android/systemui/assist/AssistManager.java
+++ b/packages/SystemUI/src/com/android/systemui/assist/AssistManager.java
@@ -25,6 +25,7 @@
 import android.service.voice.VoiceInteractionSession;
 import android.util.Log;
 
+import com.android.app.viewcapture.ViewCaptureAwareWindowManager;
 import com.android.internal.app.AssistUtils;
 import com.android.internal.app.IVisualQueryDetectionAttentionListener;
 import com.android.internal.app.IVisualQueryRecognitionStatusListener;
@@ -195,12 +196,13 @@
             SecureSettings secureSettings,
             SelectedUserInteractor selectedUserInteractor,
             ActivityManager activityManager,
-            AssistInteractor interactor) {
+            AssistInteractor interactor,
+            ViewCaptureAwareWindowManager viewCaptureAwareWindowManager) {
         mContext = context;
         mDeviceProvisionedController = controller;
         mCommandQueue = commandQueue;
         mAssistUtils = assistUtils;
-        mAssistDisclosure = new AssistDisclosure(context, uiHandler);
+        mAssistDisclosure = new AssistDisclosure(context, uiHandler, viewCaptureAwareWindowManager);
         mOverviewProxyService = overviewProxyService;
         mPhoneStateMonitor = phoneStateMonitor;
         mAssistLogger = assistLogger;
diff --git a/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothDeviceMetadataInteractor.kt b/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothDeviceMetadataInteractor.kt
new file mode 100644
index 0000000..d69e416
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothDeviceMetadataInteractor.kt
@@ -0,0 +1,91 @@
+/*
+ * 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.bluetooth.qsdialog
+
+import android.bluetooth.BluetoothAdapter
+import android.bluetooth.BluetoothDevice
+import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.utils.coroutines.flow.conflatedCallbackFlow
+import java.util.concurrent.Executor
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.channels.awaitClose
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.distinctUntilChangedBy
+import kotlinx.coroutines.flow.flatMapLatest
+import kotlinx.coroutines.flow.flowOn
+import kotlinx.coroutines.flow.merge
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SysUISingleton
+class BluetoothDeviceMetadataInteractor
+@Inject
+constructor(
+    deviceItemInteractor: DeviceItemInteractor,
+    private val bluetoothAdapter: BluetoothAdapter?,
+    private val logger: BluetoothTileDialogLogger,
+    @Background private val executor: Executor,
+    @Background private val backgroundDispatcher: CoroutineDispatcher,
+) {
+    private fun metadataUpdateForDevice(bluetoothDevice: BluetoothDevice): Flow<Unit> =
+        conflatedCallbackFlow {
+            val metadataChangedListener =
+                BluetoothAdapter.OnMetadataChangedListener { device, key, value ->
+                    when (key) {
+                        BluetoothDevice.METADATA_UNTETHERED_LEFT_BATTERY,
+                        BluetoothDevice.METADATA_UNTETHERED_RIGHT_BATTERY,
+                        BluetoothDevice.METADATA_UNTETHERED_CASE_BATTERY,
+                        BluetoothDevice.METADATA_MAIN_BATTERY -> {
+                            trySendWithFailureLogging(Unit, TAG, "onMetadataChanged")
+                            logger.logBatteryChanged(device.address, key, value)
+                        }
+                    }
+                }
+            bluetoothAdapter?.addOnMetadataChangedListener(
+                bluetoothDevice,
+                executor,
+                metadataChangedListener
+            )
+            awaitClose {
+                bluetoothAdapter?.removeOnMetadataChangedListener(
+                    bluetoothDevice,
+                    metadataChangedListener
+                )
+            }
+        }
+
+    val metadataUpdate: Flow<Unit> =
+        deviceItemInteractor.deviceItemUpdate
+            .distinctUntilChangedBy { it.bluetoothDevices }
+            .flatMapLatest { items ->
+                items.bluetoothDevices.map { device -> metadataUpdateForDevice(device) }.merge()
+            }
+            .flowOn(backgroundDispatcher)
+
+    private companion object {
+        private const val TAG = "BluetoothDeviceMetadataInteractor"
+        private val List<DeviceItem>.bluetoothDevices: Set<BluetoothDevice>
+            get() =
+                flatMapTo(mutableSetOf()) { item ->
+                    listOf(item.cachedBluetoothDevice.device) +
+                        item.cachedBluetoothDevice.memberDevice.map { it.device }
+                }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogDelegate.kt b/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogDelegate.kt
index 2808dbe..7deea73 100644
--- a/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogDelegate.kt
+++ b/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogDelegate.kt
@@ -129,8 +129,26 @@
         getPairNewDeviceButton(dialog).setOnClickListener {
             bluetoothTileDialogCallback.onPairNewDeviceClicked(it)
         }
-        getAudioSharingButtonView(dialog).setOnClickListener {
-            bluetoothTileDialogCallback.onAudioSharingButtonClicked(it)
+        getAudioSharingButtonView(dialog).apply {
+            setOnClickListener { bluetoothTileDialogCallback.onAudioSharingButtonClicked(it) }
+            accessibilityDelegate =
+                object : AccessibilityDelegate() {
+                    override fun onInitializeAccessibilityNodeInfo(
+                        host: View,
+                        info: AccessibilityNodeInfo
+                    ) {
+                        super.onInitializeAccessibilityNodeInfo(host, info)
+                        info.addAction(
+                            AccessibilityAction(
+                                AccessibilityAction.ACTION_CLICK.id,
+                                context.getString(
+                                    R.string
+                                        .quick_settings_bluetooth_audio_sharing_button_accessibility
+                                )
+                            )
+                        )
+                    }
+                }
         }
         getScrollViewContent(dialog).apply {
             minimumHeight =
@@ -445,7 +463,6 @@
 
     internal companion object {
         const val MIN_HEIGHT_CHANGE_INTERVAL_MS = 800L
-        const val MAX_DEVICE_ITEM_ENTRY = 3
         const val ACTION_BLUETOOTH_DEVICE_DETAILS =
             "com.android.settings.BLUETOOTH_DEVICE_DETAIL_SETTINGS"
         const val ACTION_PREVIOUSLY_CONNECTED_DEVICE =
diff --git a/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogLogger.kt b/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogLogger.kt
index 72312b8..06116f0 100644
--- a/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogLogger.kt
@@ -90,6 +90,18 @@
             { "ProfileConnectionStateChanged. address=$str1 state=$str2 profileId=$int1" }
         )
 
+    fun logBatteryChanged(address: String, key: Int, value: ByteArray?) =
+        logBuffer.log(
+            TAG,
+            DEBUG,
+            {
+                str1 = address
+                int1 = key
+                str2 = value?.toString() ?: ""
+            },
+            { "BatteryChanged. address=$str1 key=$int1 value=$str2" }
+        )
+
     fun logDeviceFetch(status: JobStatus, trigger: DeviceFetchTrigger, duration: Long) =
         logBuffer.log(
             TAG,
diff --git a/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogRepository.kt b/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogRepository.kt
index 6e51915..56b79d1 100644
--- a/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogRepository.kt
@@ -24,7 +24,7 @@
 
 /** Repository to get CachedBluetoothDevices for the Bluetooth Dialog. */
 @SysUISingleton
-internal class BluetoothTileDialogRepository
+class BluetoothTileDialogRepository
 @Inject
 constructor(
     private val localBluetoothManager: LocalBluetoothManager?,
diff --git a/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogViewModel.kt b/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogViewModel.kt
index 985b158..8b2449a 100644
--- a/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogViewModel.kt
@@ -37,7 +37,6 @@
 import com.android.systemui.bluetooth.qsdialog.BluetoothTileDialogDelegate.Companion.ACTION_BLUETOOTH_DEVICE_DETAILS
 import com.android.systemui.bluetooth.qsdialog.BluetoothTileDialogDelegate.Companion.ACTION_PAIR_NEW_DEVICE
 import com.android.systemui.bluetooth.qsdialog.BluetoothTileDialogDelegate.Companion.ACTION_PREVIOUSLY_CONNECTED_DEVICE
-import com.android.systemui.bluetooth.qsdialog.BluetoothTileDialogDelegate.Companion.MAX_DEVICE_ITEM_ENTRY
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.dagger.qualifiers.Background
@@ -50,8 +49,10 @@
 import kotlinx.coroutines.Job
 import kotlinx.coroutines.channels.awaitClose
 import kotlinx.coroutines.channels.produce
+import kotlinx.coroutines.flow.combine
 import kotlinx.coroutines.flow.filterNotNull
 import kotlinx.coroutines.flow.launchIn
+import kotlinx.coroutines.flow.merge
 import kotlinx.coroutines.flow.onEach
 import kotlinx.coroutines.launch
 import kotlinx.coroutines.withContext
@@ -66,6 +67,7 @@
     private val bluetoothStateInteractor: BluetoothStateInteractor,
     private val bluetoothAutoOnInteractor: BluetoothAutoOnInteractor,
     private val audioSharingInteractor: AudioSharingInteractor,
+    private val bluetoothDeviceMetadataInteractor: BluetoothDeviceMetadataInteractor,
     private val dialogTransitionAnimator: DialogTransitionAnimator,
     private val activityStarter: ActivityStarter,
     private val uiEventLogger: UiEventLogger,
@@ -112,15 +114,17 @@
 
                 // deviceItemUpdate is emitted when device item list is done fetching, update UI and
                 // stop the progress bar.
-                deviceItemInteractor.deviceItemUpdate
-                    .onEach {
+                combine(
+                        deviceItemInteractor.deviceItemUpdate,
+                        deviceItemInteractor.showSeeAllUpdate
+                    ) { deviceItem, showSeeAll ->
                         updateDialogUiJob?.cancel()
                         updateDialogUiJob = launch {
                             dialogDelegate.apply {
                                 onDeviceItemUpdated(
                                     dialog,
-                                    it.take(MAX_DEVICE_ITEM_ENTRY),
-                                    showSeeAll = it.size > MAX_DEVICE_ITEM_ENTRY,
+                                    deviceItem,
+                                    showSeeAll,
                                     showPairNewDevice =
                                         bluetoothStateInteractor.isBluetoothEnabled()
                                 )
@@ -131,8 +135,11 @@
                     .launchIn(this)
 
                 // deviceItemUpdateRequest is emitted when a bluetooth callback is called, re-fetch
-                // the device item list and animiate the progress bar.
-                deviceItemInteractor.deviceItemUpdateRequest
+                // the device item list and animate the progress bar.
+                merge(
+                        deviceItemInteractor.deviceItemUpdateRequest,
+                        bluetoothDeviceMetadataInteractor.metadataUpdate
+                    )
                     .onEach {
                         dialogDelegate.animateProgressBar(dialog, true)
                         updateDeviceItemJob?.cancel()
diff --git a/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/DeviceItemFactory.kt b/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/DeviceItemFactory.kt
index d7893db..e846bf7 100644
--- a/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/DeviceItemFactory.kt
+++ b/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/DeviceItemFactory.kt
@@ -38,7 +38,7 @@
     R.string.accessibility_quick_settings_bluetooth_device_tap_to_disconnect
 
 /** Factories to create different types of Bluetooth device items from CachedBluetoothDevice. */
-internal abstract class DeviceItemFactory {
+abstract class DeviceItemFactory {
     abstract fun isFilterMatched(
         context: Context,
         cachedDevice: CachedBluetoothDevice,
@@ -136,7 +136,7 @@
     }
 }
 
-internal open class AvailableMediaDeviceItemFactory : DeviceItemFactory() {
+open class AvailableMediaDeviceItemFactory : DeviceItemFactory() {
     override fun isFilterMatched(
         context: Context,
         cachedDevice: CachedBluetoothDevice,
diff --git a/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/DeviceItemInteractor.kt b/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/DeviceItemInteractor.kt
index 1526cd9..9524496 100644
--- a/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/DeviceItemInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/bluetooth/qsdialog/DeviceItemInteractor.kt
@@ -34,16 +34,18 @@
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.channels.awaitClose
 import kotlinx.coroutines.flow.MutableSharedFlow
+import kotlinx.coroutines.flow.MutableStateFlow
 import kotlinx.coroutines.flow.SharedFlow
 import kotlinx.coroutines.flow.SharingStarted
 import kotlinx.coroutines.flow.asSharedFlow
+import kotlinx.coroutines.flow.asStateFlow
 import kotlinx.coroutines.flow.shareIn
 import kotlinx.coroutines.isActive
 import kotlinx.coroutines.withContext
 
 /** Holds business logic for the Bluetooth Dialog after clicking on the Bluetooth QS tile. */
 @SysUISingleton
-internal class DeviceItemInteractor
+class DeviceItemInteractor
 @Inject
 constructor(
     private val bluetoothTileDialogRepository: BluetoothTileDialogRepository,
@@ -58,9 +60,13 @@
 
     private val mutableDeviceItemUpdate: MutableSharedFlow<List<DeviceItem>> =
         MutableSharedFlow(extraBufferCapacity = 1)
-    internal val deviceItemUpdate
+    val deviceItemUpdate
         get() = mutableDeviceItemUpdate.asSharedFlow()
 
+    private val mutableShowSeeAllUpdate: MutableStateFlow<Boolean> = MutableStateFlow(false)
+    internal val showSeeAllUpdate
+        get() = mutableShowSeeAllUpdate.asStateFlow()
+
     internal val deviceItemUpdateRequest: SharedFlow<Unit> =
         conflatedCallbackFlow {
                 val listener =
@@ -139,7 +145,8 @@
                     .sort(displayPriority, bluetoothAdapter?.mostRecentlyConnectedDevices)
             // Only emit when the job is not cancelled
             if (isActive) {
-                mutableDeviceItemUpdate.tryEmit(deviceItems)
+                mutableDeviceItemUpdate.tryEmit(deviceItems.take(MAX_DEVICE_ITEM_ENTRY))
+                mutableShowSeeAllUpdate.tryEmit(deviceItems.size > MAX_DEVICE_ITEM_ENTRY)
                 logger.logDeviceFetch(
                     JobStatus.FINISHED,
                     trigger,
@@ -177,5 +184,6 @@
 
     companion object {
         private const val TAG = "DeviceItemInteractor"
+        private const val MAX_DEVICE_ITEM_ENTRY = 3
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/ui/BouncerViewModule.kt b/packages/SystemUI/src/com/android/systemui/bouncer/ui/BouncerViewModule.kt
index aebc50f..3410782 100644
--- a/packages/SystemUI/src/com/android/systemui/bouncer/ui/BouncerViewModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/bouncer/ui/BouncerViewModule.kt
@@ -18,8 +18,6 @@
 
 import android.app.AlertDialog
 import android.content.Context
-import com.android.systemui.bouncer.ui.viewmodel.BouncerMessageViewModelModule
-import com.android.systemui.bouncer.ui.viewmodel.BouncerViewModelModule
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.statusbar.phone.SystemUIDialog
@@ -27,13 +25,7 @@
 import dagger.Module
 import dagger.Provides
 
-@Module(
-    includes =
-        [
-            BouncerViewModelModule::class,
-            BouncerMessageViewModelModule::class,
-        ],
-)
+@Module
 interface BouncerViewModule {
     /** Binds BouncerView to BouncerViewImpl and makes it injectable. */
     @Binds fun bindBouncerView(bouncerViewImpl: BouncerViewImpl): BouncerView
diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/ui/binder/BouncerViewBinder.kt b/packages/SystemUI/src/com/android/systemui/bouncer/ui/binder/BouncerViewBinder.kt
index 78811a9..ad93a25 100644
--- a/packages/SystemUI/src/com/android/systemui/bouncer/ui/binder/BouncerViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/bouncer/ui/binder/BouncerViewBinder.kt
@@ -9,7 +9,7 @@
 import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerInteractor
 import com.android.systemui.bouncer.shared.flag.ComposeBouncerFlags
 import com.android.systemui.bouncer.ui.BouncerDialogFactory
-import com.android.systemui.bouncer.ui.viewmodel.BouncerViewModel
+import com.android.systemui.bouncer.ui.viewmodel.BouncerSceneContentViewModel
 import com.android.systemui.bouncer.ui.viewmodel.KeyguardBouncerViewModel
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.keyguard.ui.viewmodel.PrimaryBouncerToGoneTransitionViewModel
@@ -40,7 +40,7 @@
 @Inject
 constructor(
     val legacyInteractor: PrimaryBouncerInteractor,
-    val viewModel: BouncerViewModel,
+    val viewModelFactory: BouncerSceneContentViewModel.Factory,
     val dialogFactory: BouncerDialogFactory,
     val authenticationInteractor: AuthenticationInteractor,
     val viewMediatorCallback: ViewMediatorCallback?,
@@ -65,7 +65,7 @@
             ComposeBouncerViewBinder.bind(
                 view,
                 deps.legacyInteractor,
-                deps.viewModel,
+                deps.viewModelFactory,
                 deps.dialogFactory,
                 deps.authenticationInteractor,
                 deps.selectedUserInteractor,
diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/ui/binder/ComposeBouncerViewBinder.kt b/packages/SystemUI/src/com/android/systemui/bouncer/ui/binder/ComposeBouncerViewBinder.kt
index eaca276..c1f7d59 100644
--- a/packages/SystemUI/src/com/android/systemui/bouncer/ui/binder/ComposeBouncerViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/bouncer/ui/binder/ComposeBouncerViewBinder.kt
@@ -14,7 +14,8 @@
 import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerInteractor
 import com.android.systemui.bouncer.ui.BouncerDialogFactory
 import com.android.systemui.bouncer.ui.composable.BouncerContent
-import com.android.systemui.bouncer.ui.viewmodel.BouncerViewModel
+import com.android.systemui.bouncer.ui.viewmodel.BouncerSceneContentViewModel
+import com.android.systemui.lifecycle.rememberViewModel
 import com.android.systemui.lifecycle.repeatWhenAttached
 import com.android.systemui.user.domain.interactor.SelectedUserInteractor
 import kotlinx.coroutines.flow.collectLatest
@@ -25,7 +26,7 @@
     fun bind(
         view: ViewGroup,
         legacyInteractor: PrimaryBouncerInteractor,
-        viewModel: BouncerViewModel,
+        viewModelFactory: BouncerSceneContentViewModel.Factory,
         dialogFactory: BouncerDialogFactory,
         authenticationInteractor: AuthenticationInteractor,
         selectedUserInteractor: SelectedUserInteractor,
@@ -48,7 +49,14 @@
                                     this@repeatWhenAttached.lifecycle
                             }
                         )
-                        setContent { PlatformTheme { BouncerContent(viewModel, dialogFactory) } }
+                        setContent {
+                            PlatformTheme {
+                                BouncerContent(
+                                    rememberViewModel { viewModelFactory.create() },
+                                    dialogFactory,
+                                )
+                            }
+                        }
                     }
                 }
             }
diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/AuthMethodBouncerViewModel.kt b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/AuthMethodBouncerViewModel.kt
index 4fbf735..df50e8f 100644
--- a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/AuthMethodBouncerViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/AuthMethodBouncerViewModel.kt
@@ -17,17 +17,19 @@
 package com.android.systemui.bouncer.ui.viewmodel
 
 import android.annotation.StringRes
+import com.android.app.tracing.coroutines.flow.collectLatest
 import com.android.systemui.authentication.domain.interactor.AuthenticationResult
 import com.android.systemui.authentication.shared.model.AuthenticationMethodModel
 import com.android.systemui.bouncer.domain.interactor.BouncerInteractor
-import kotlinx.coroutines.CoroutineScope
+import com.android.systemui.lifecycle.SysUiViewModel
+import kotlinx.coroutines.awaitCancellation
+import kotlinx.coroutines.channels.Channel
 import kotlinx.coroutines.flow.MutableStateFlow
 import kotlinx.coroutines.flow.StateFlow
 import kotlinx.coroutines.flow.asStateFlow
-import kotlinx.coroutines.launch
+import kotlinx.coroutines.flow.receiveAsFlow
 
 sealed class AuthMethodBouncerViewModel(
-    protected val viewModelScope: CoroutineScope,
     protected val interactor: BouncerInteractor,
 
     /**
@@ -37,7 +39,7 @@
      * being able to attempt to unlock the device.
      */
     val isInputEnabled: StateFlow<Boolean>,
-) {
+) : SysUiViewModel() {
 
     private val _animateFailure = MutableStateFlow(false)
     /**
@@ -57,6 +59,30 @@
      */
     @get:StringRes abstract val lockoutMessageId: Int
 
+    private val authenticationRequests = Channel<AuthenticationRequest>(Channel.BUFFERED)
+
+    override suspend fun onActivated(): Nothing {
+        authenticationRequests.receiveAsFlow().collectLatest { request ->
+            if (!isInputEnabled.value) {
+                return@collectLatest
+            }
+
+            val authenticationResult =
+                interactor.authenticate(
+                    input = request.input,
+                    tryAutoConfirm = request.useAutoConfirm,
+                )
+
+            if (authenticationResult == AuthenticationResult.SKIPPED && request.useAutoConfirm) {
+                return@collectLatest
+            }
+
+            _animateFailure.value = authenticationResult != AuthenticationResult.SUCCEEDED
+            clearInput()
+        }
+        awaitCancellation()
+    }
+
     /**
      * Notifies that the UI has been hidden from the user (after any transitions have completed).
      */
@@ -92,14 +118,11 @@
         input: List<Any> = getInput(),
         useAutoConfirm: Boolean = false,
     ) {
-        viewModelScope.launch {
-            val authenticationResult = interactor.authenticate(input, useAutoConfirm)
-            if (authenticationResult == AuthenticationResult.SKIPPED && useAutoConfirm) {
-                return@launch
-            }
-            _animateFailure.value = authenticationResult != AuthenticationResult.SUCCEEDED
-
-            clearInput()
-        }
+        authenticationRequests.trySend(AuthenticationRequest(input, useAutoConfirm))
     }
+
+    private data class AuthenticationRequest(
+        val input: List<Any>,
+        val useAutoConfirm: Boolean,
+    )
 }
diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/BouncerMessageViewModel.kt b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/BouncerMessageViewModel.kt
index 31479f1..cfd4f50 100644
--- a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/BouncerMessageViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/BouncerMessageViewModel.kt
@@ -27,7 +27,6 @@
 import com.android.systemui.bouncer.shared.model.BouncerMessageStrings
 import com.android.systemui.bouncer.shared.model.primaryMessage
 import com.android.systemui.bouncer.shared.model.secondaryMessage
-import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.deviceentry.domain.interactor.BiometricMessageInteractor
 import com.android.systemui.deviceentry.domain.interactor.DeviceEntryBiometricsAllowedInteractor
@@ -39,19 +38,20 @@
 import com.android.systemui.deviceentry.shared.model.FaceTimeoutMessage
 import com.android.systemui.deviceentry.shared.model.FingerprintFailureMessage
 import com.android.systemui.deviceentry.shared.model.FingerprintLockoutMessage
+import com.android.systemui.lifecycle.SysUiViewModel
 import com.android.systemui.res.R.string.kg_too_many_failed_attempts_countdown
 import com.android.systemui.user.ui.viewmodel.UserSwitcherViewModel
-import com.android.systemui.user.ui.viewmodel.UserViewModel
 import com.android.systemui.util.kotlin.Utils.Companion.sample
 import com.android.systemui.util.time.SystemClock
-import dagger.Module
-import dagger.Provides
+import dagger.assisted.AssistedFactory
+import dagger.assisted.AssistedInject
 import kotlin.math.ceil
 import kotlin.math.max
 import kotlin.time.Duration.Companion.seconds
-import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.Job
+import kotlinx.coroutines.awaitCancellation
+import kotlinx.coroutines.coroutineScope
 import kotlinx.coroutines.delay
 import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.MutableSharedFlow
@@ -65,20 +65,21 @@
 
 /** Holds UI state for the 2-line status message shown on the bouncer. */
 @OptIn(ExperimentalCoroutinesApi::class)
-class BouncerMessageViewModel(
+class BouncerMessageViewModel
+@AssistedInject
+constructor(
     @Application private val applicationContext: Context,
-    @Application private val applicationScope: CoroutineScope,
     private val bouncerInteractor: BouncerInteractor,
     private val simBouncerInteractor: SimBouncerInteractor,
     private val authenticationInteractor: AuthenticationInteractor,
-    selectedUser: Flow<UserViewModel>,
+    private val userSwitcherViewModel: UserSwitcherViewModel,
     private val clock: SystemClock,
     private val biometricMessageInteractor: BiometricMessageInteractor,
     private val faceAuthInteractor: DeviceEntryFaceAuthInteractor,
     private val deviceUnlockedInteractor: DeviceUnlockedInteractor,
     private val deviceEntryBiometricsAllowedInteractor: DeviceEntryBiometricsAllowedInteractor,
-    flags: ComposeBouncerFlags,
-) {
+    private val flags: ComposeBouncerFlags,
+) : SysUiViewModel() {
     /**
      * A message shown when the user has attempted the wrong credential too many times and now must
      * wait a while before attempting to authenticate again.
@@ -94,6 +95,26 @@
     /** The user-facing message to show in the bouncer. */
     val message: MutableStateFlow<MessageViewModel?> = MutableStateFlow(null)
 
+    override suspend fun onActivated(): Nothing {
+        if (!flags.isComposeBouncerOrSceneContainerEnabled()) {
+            return awaitCancellation()
+        }
+
+        coroutineScope {
+            launch {
+                // Update the lockout countdown whenever the selected user is switched.
+                userSwitcherViewModel.selectedUser.collect { startLockoutCountdown() }
+            }
+
+            launch { defaultBouncerMessageInitializer() }
+            launch { listenForSimBouncerEvents() }
+            launch { listenForBouncerEvents() }
+            launch { listenForFaceMessages() }
+            launch { listenForFingerprintMessages() }
+            awaitCancellation()
+        }
+    }
+
     /** Initializes the bouncer message to default whenever it is shown. */
     fun onShown() {
         showDefaultMessage()
@@ -108,173 +129,164 @@
 
     private var lockoutCountdownJob: Job? = null
 
-    private fun defaultBouncerMessageInitializer() {
-        applicationScope.launch {
-            resetToDefault.emit(Unit)
-            authenticationInteractor.authenticationMethod
-                .flatMapLatest { authMethod ->
-                    if (authMethod == AuthenticationMethodModel.Sim) {
-                        resetToDefault.map {
-                            MessageViewModel(simBouncerInteractor.getDefaultMessage())
-                        }
-                    } else if (authMethod.isSecure) {
-                        combine(
-                            deviceUnlockedInteractor.deviceEntryRestrictionReason,
-                            lockoutMessage,
-                            deviceEntryBiometricsAllowedInteractor
-                                .isFingerprintCurrentlyAllowedOnBouncer,
-                            resetToDefault,
-                        ) { deviceEntryRestrictedReason, lockoutMsg, isFpAllowedInBouncer, _ ->
-                            lockoutMsg
-                                ?: deviceEntryRestrictedReason.toMessage(
-                                    authMethod,
-                                    isFpAllowedInBouncer
-                                )
-                        }
-                    } else {
-                        emptyFlow()
+    private suspend fun defaultBouncerMessageInitializer() {
+        resetToDefault.emit(Unit)
+        authenticationInteractor.authenticationMethod
+            .flatMapLatest { authMethod ->
+                if (authMethod == AuthenticationMethodModel.Sim) {
+                    resetToDefault.map {
+                        MessageViewModel(simBouncerInteractor.getDefaultMessage())
                     }
+                } else if (authMethod.isSecure) {
+                    combine(
+                        deviceUnlockedInteractor.deviceEntryRestrictionReason,
+                        lockoutMessage,
+                        deviceEntryBiometricsAllowedInteractor
+                            .isFingerprintCurrentlyAllowedOnBouncer,
+                        resetToDefault,
+                    ) { deviceEntryRestrictedReason, lockoutMsg, isFpAllowedInBouncer, _ ->
+                        lockoutMsg
+                            ?: deviceEntryRestrictedReason.toMessage(
+                                authMethod,
+                                isFpAllowedInBouncer
+                            )
+                    }
+                } else {
+                    emptyFlow()
                 }
-                .collectLatest { messageViewModel -> message.value = messageViewModel }
-        }
+            }
+            .collectLatest { messageViewModel -> message.value = messageViewModel }
     }
 
-    private fun listenForSimBouncerEvents() {
+    private suspend fun listenForSimBouncerEvents() {
         // Listen for any events from the SIM bouncer and update the message shown on the bouncer.
-        applicationScope.launch {
-            authenticationInteractor.authenticationMethod
-                .flatMapLatest { authMethod ->
-                    if (authMethod == AuthenticationMethodModel.Sim) {
-                        simBouncerInteractor.bouncerMessageChanged.map { simMsg ->
-                            simMsg?.let { MessageViewModel(it) }
-                        }
-                    } else {
-                        emptyFlow()
+        authenticationInteractor.authenticationMethod
+            .flatMapLatest { authMethod ->
+                if (authMethod == AuthenticationMethodModel.Sim) {
+                    simBouncerInteractor.bouncerMessageChanged.map { simMsg ->
+                        simMsg?.let { MessageViewModel(it) }
                     }
+                } else {
+                    emptyFlow()
                 }
-                .collectLatest {
-                    if (it != null) {
-                        message.value = it
-                    } else {
-                        resetToDefault.emit(Unit)
-                    }
+            }
+            .collectLatest {
+                if (it != null) {
+                    message.value = it
+                } else {
+                    resetToDefault.emit(Unit)
                 }
-        }
+            }
     }
 
-    private fun listenForFaceMessages() {
+    private suspend fun listenForFaceMessages() {
         // Listen for any events from face authentication and update the message shown on the
         // bouncer.
-        applicationScope.launch {
-            biometricMessageInteractor.faceMessage
-                .sample(
-                    authenticationInteractor.authenticationMethod,
-                    deviceEntryBiometricsAllowedInteractor.isFingerprintCurrentlyAllowedOnBouncer,
-                )
-                .collectLatest { (faceMessage, authMethod, fingerprintAllowedOnBouncer) ->
-                    val isFaceAuthStrong = faceAuthInteractor.isFaceAuthStrong()
-                    val defaultPrimaryMessage =
-                        BouncerMessageStrings.defaultMessage(
-                                authMethod,
-                                fingerprintAllowedOnBouncer
+        biometricMessageInteractor.faceMessage
+            .sample(
+                authenticationInteractor.authenticationMethod,
+                deviceEntryBiometricsAllowedInteractor.isFingerprintCurrentlyAllowedOnBouncer,
+            )
+            .collectLatest { (faceMessage, authMethod, fingerprintAllowedOnBouncer) ->
+                val isFaceAuthStrong = faceAuthInteractor.isFaceAuthStrong()
+                val defaultPrimaryMessage =
+                    BouncerMessageStrings.defaultMessage(authMethod, fingerprintAllowedOnBouncer)
+                        .primaryMessage
+                        .toResString()
+                message.value =
+                    when (faceMessage) {
+                        is FaceTimeoutMessage ->
+                            MessageViewModel(
+                                text = defaultPrimaryMessage,
+                                secondaryText = faceMessage.message,
+                                isUpdateAnimated = true
                             )
-                            .primaryMessage
-                            .toResString()
-                    message.value =
-                        when (faceMessage) {
-                            is FaceTimeoutMessage ->
-                                MessageViewModel(
-                                    text = defaultPrimaryMessage,
-                                    secondaryText = faceMessage.message,
-                                    isUpdateAnimated = true
-                                )
-                            is FaceLockoutMessage ->
-                                if (isFaceAuthStrong)
-                                    BouncerMessageStrings.class3AuthLockedOut(authMethod)
-                                        .toMessage()
-                                else
-                                    BouncerMessageStrings.faceLockedOut(
-                                            authMethod,
-                                            fingerprintAllowedOnBouncer
-                                        )
-                                        .toMessage()
-                            is FaceFailureMessage ->
-                                BouncerMessageStrings.incorrectFaceInput(
+                        is FaceLockoutMessage ->
+                            if (isFaceAuthStrong)
+                                BouncerMessageStrings.class3AuthLockedOut(authMethod).toMessage()
+                            else
+                                BouncerMessageStrings.faceLockedOut(
                                         authMethod,
                                         fingerprintAllowedOnBouncer
                                     )
                                     .toMessage()
-                            else ->
-                                MessageViewModel(
-                                    text = defaultPrimaryMessage,
-                                    secondaryText = faceMessage.message,
-                                    isUpdateAnimated = false
+                        is FaceFailureMessage ->
+                            BouncerMessageStrings.incorrectFaceInput(
+                                    authMethod,
+                                    fingerprintAllowedOnBouncer
                                 )
-                        }
-                    delay(MESSAGE_DURATION)
-                    resetToDefault.emit(Unit)
-                }
-        }
-    }
-
-    private fun listenForFingerprintMessages() {
-        applicationScope.launch {
-            // Listen for any events from fingerprint authentication and update the message shown
-            // on the bouncer.
-            biometricMessageInteractor.fingerprintMessage
-                .sample(
-                    authenticationInteractor.authenticationMethod,
-                    deviceEntryBiometricsAllowedInteractor.isFingerprintCurrentlyAllowedOnBouncer
-                )
-                .collectLatest { (fingerprintMessage, authMethod, isFingerprintAllowed) ->
-                    val defaultPrimaryMessage =
-                        BouncerMessageStrings.defaultMessage(authMethod, isFingerprintAllowed)
-                            .primaryMessage
-                            .toResString()
-                    message.value =
-                        when (fingerprintMessage) {
-                            is FingerprintLockoutMessage ->
-                                BouncerMessageStrings.class3AuthLockedOut(authMethod).toMessage()
-                            is FingerprintFailureMessage ->
-                                BouncerMessageStrings.incorrectFingerprintInput(authMethod)
-                                    .toMessage()
-                            else ->
-                                MessageViewModel(
-                                    text = defaultPrimaryMessage,
-                                    secondaryText = fingerprintMessage.message,
-                                    isUpdateAnimated = false
-                                )
-                        }
-                    delay(MESSAGE_DURATION)
-                    resetToDefault.emit(Unit)
-                }
-        }
-    }
-
-    private fun listenForBouncerEvents() {
-        // Keeps the lockout message up-to-date.
-        applicationScope.launch {
-            bouncerInteractor.onLockoutStarted.collect { startLockoutCountdown() }
-        }
-
-        // Listens to relevant bouncer events
-        applicationScope.launch {
-            bouncerInteractor.onIncorrectBouncerInput
-                .sample(
-                    authenticationInteractor.authenticationMethod,
-                    deviceEntryBiometricsAllowedInteractor.isFingerprintCurrentlyAllowedOnBouncer
-                )
-                .collectLatest { (_, authMethod, isFingerprintAllowed) ->
-                    message.emit(
-                        BouncerMessageStrings.incorrectSecurityInput(
-                                authMethod,
-                                isFingerprintAllowed
+                                .toMessage()
+                        else ->
+                            MessageViewModel(
+                                text = defaultPrimaryMessage,
+                                secondaryText = faceMessage.message,
+                                isUpdateAnimated = false
                             )
-                            .toMessage()
+                    }
+                delay(MESSAGE_DURATION)
+                resetToDefault.emit(Unit)
+            }
+    }
+
+    private suspend fun listenForFingerprintMessages() {
+        // Listen for any events from fingerprint authentication and update the message shown
+        // on the bouncer.
+        biometricMessageInteractor.fingerprintMessage
+            .sample(
+                authenticationInteractor.authenticationMethod,
+                deviceEntryBiometricsAllowedInteractor.isFingerprintCurrentlyAllowedOnBouncer
+            )
+            .collectLatest { (fingerprintMessage, authMethod, isFingerprintAllowed) ->
+                val defaultPrimaryMessage =
+                    BouncerMessageStrings.defaultMessage(authMethod, isFingerprintAllowed)
+                        .primaryMessage
+                        .toResString()
+                message.value =
+                    when (fingerprintMessage) {
+                        is FingerprintLockoutMessage ->
+                            BouncerMessageStrings.class3AuthLockedOut(authMethod).toMessage()
+                        is FingerprintFailureMessage ->
+                            BouncerMessageStrings.incorrectFingerprintInput(authMethod).toMessage()
+                        else ->
+                            MessageViewModel(
+                                text = defaultPrimaryMessage,
+                                secondaryText = fingerprintMessage.message,
+                                isUpdateAnimated = false
+                            )
+                    }
+                delay(MESSAGE_DURATION)
+                resetToDefault.emit(Unit)
+            }
+    }
+
+    private suspend fun listenForBouncerEvents() {
+        coroutineScope {
+            // Keeps the lockout message up-to-date.
+            launch { bouncerInteractor.onLockoutStarted.collect { startLockoutCountdown() } }
+
+            // Start already active lockdown if it exists
+            launch { startLockoutCountdown() }
+
+            // Listens to relevant bouncer events
+            launch {
+                bouncerInteractor.onIncorrectBouncerInput
+                    .sample(
+                        authenticationInteractor.authenticationMethod,
+                        deviceEntryBiometricsAllowedInteractor
+                            .isFingerprintCurrentlyAllowedOnBouncer
                     )
-                    delay(MESSAGE_DURATION)
-                    resetToDefault.emit(Unit)
-                }
+                    .collectLatest { (_, authMethod, isFingerprintAllowed) ->
+                        message.emit(
+                            BouncerMessageStrings.incorrectSecurityInput(
+                                    authMethod,
+                                    isFingerprintAllowed
+                                )
+                                .toMessage()
+                        )
+                        delay(MESSAGE_DURATION)
+                        resetToDefault.emit(Unit)
+                    }
+            }
         }
     }
 
@@ -323,10 +335,10 @@
     }
 
     /** Shows the countdown message and refreshes it every second. */
-    private fun startLockoutCountdown() {
+    private suspend fun startLockoutCountdown() {
         lockoutCountdownJob?.cancel()
-        lockoutCountdownJob =
-            applicationScope.launch {
+        lockoutCountdownJob = coroutineScope {
+            launch {
                 authenticationInteractor.authenticationMethod.collectLatest { authMethod ->
                     do {
                         val remainingSeconds = remainingLockoutSeconds()
@@ -352,6 +364,7 @@
                     lockoutCountdownJob = null
                 }
             }
+        }
     }
 
     private fun remainingLockoutSeconds(): Int {
@@ -365,20 +378,9 @@
 
     private fun Int.toResString(): String = applicationContext.getString(this)
 
-    init {
-        if (flags.isComposeBouncerOrSceneContainerEnabled()) {
-            applicationScope.launch {
-                // Update the lockout countdown whenever the selected user is switched.
-                selectedUser.collect { startLockoutCountdown() }
-            }
-
-            defaultBouncerMessageInitializer()
-
-            listenForSimBouncerEvents()
-            listenForBouncerEvents()
-            listenForFaceMessages()
-            listenForFingerprintMessages()
-        }
+    @AssistedFactory
+    interface Factory {
+        fun create(): BouncerMessageViewModel
     }
 
     companion object {
@@ -398,40 +400,3 @@
      */
     val isUpdateAnimated: Boolean = true,
 )
-
-@OptIn(ExperimentalCoroutinesApi::class)
-@Module
-object BouncerMessageViewModelModule {
-
-    @Provides
-    @SysUISingleton
-    fun viewModel(
-        @Application applicationContext: Context,
-        @Application applicationScope: CoroutineScope,
-        bouncerInteractor: BouncerInteractor,
-        simBouncerInteractor: SimBouncerInteractor,
-        authenticationInteractor: AuthenticationInteractor,
-        clock: SystemClock,
-        biometricMessageInteractor: BiometricMessageInteractor,
-        faceAuthInteractor: DeviceEntryFaceAuthInteractor,
-        deviceUnlockedInteractor: DeviceUnlockedInteractor,
-        deviceEntryBiometricsAllowedInteractor: DeviceEntryBiometricsAllowedInteractor,
-        flags: ComposeBouncerFlags,
-        userSwitcherViewModel: UserSwitcherViewModel,
-    ): BouncerMessageViewModel {
-        return BouncerMessageViewModel(
-            applicationContext = applicationContext,
-            applicationScope = applicationScope,
-            bouncerInteractor = bouncerInteractor,
-            simBouncerInteractor = simBouncerInteractor,
-            authenticationInteractor = authenticationInteractor,
-            clock = clock,
-            biometricMessageInteractor = biometricMessageInteractor,
-            faceAuthInteractor = faceAuthInteractor,
-            deviceUnlockedInteractor = deviceUnlockedInteractor,
-            deviceEntryBiometricsAllowedInteractor = deviceEntryBiometricsAllowedInteractor,
-            flags = flags,
-            selectedUser = userSwitcherViewModel.selectedUser,
-        )
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/BouncerSceneActionsViewModel.kt b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/BouncerSceneActionsViewModel.kt
new file mode 100644
index 0000000..2a27271
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/BouncerSceneActionsViewModel.kt
@@ -0,0 +1,56 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.bouncer.ui.viewmodel
+
+import com.android.compose.animation.scene.Back
+import com.android.compose.animation.scene.Swipe
+import com.android.compose.animation.scene.SwipeDirection
+import com.android.compose.animation.scene.UserAction
+import com.android.compose.animation.scene.UserActionResult
+import com.android.systemui.bouncer.domain.interactor.BouncerInteractor
+import com.android.systemui.scene.ui.viewmodel.SceneActionsViewModel
+import dagger.assisted.AssistedFactory
+import dagger.assisted.AssistedInject
+import kotlinx.coroutines.flow.collectLatest
+import kotlinx.coroutines.flow.map
+
+/**
+ * Models UI state for user actions that can lead to navigation to other scenes when showing the
+ * bouncer scene.
+ */
+class BouncerSceneActionsViewModel
+@AssistedInject
+constructor(
+    private val bouncerInteractor: BouncerInteractor,
+) : SceneActionsViewModel() {
+
+    override suspend fun hydrateActions(setActions: (Map<UserAction, UserActionResult>) -> Unit) {
+        bouncerInteractor.dismissDestination
+            .map { prevScene ->
+                mapOf(
+                    Back to UserActionResult(prevScene),
+                    Swipe(SwipeDirection.Down) to UserActionResult(prevScene),
+                )
+            }
+            .collectLatest { actions -> setActions(actions) }
+    }
+
+    @AssistedFactory
+    interface Factory {
+        fun create(): BouncerSceneActionsViewModel
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/BouncerSceneContentViewModel.kt b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/BouncerSceneContentViewModel.kt
new file mode 100644
index 0000000..63b6f01
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/BouncerSceneContentViewModel.kt
@@ -0,0 +1,365 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.bouncer.ui.viewmodel
+
+import android.app.admin.DevicePolicyManager
+import android.app.admin.DevicePolicyResources
+import android.content.Context
+import android.graphics.Bitmap
+import androidx.compose.ui.input.key.KeyEvent
+import androidx.compose.ui.input.key.type
+import androidx.core.graphics.drawable.toBitmap
+import com.android.systemui.authentication.domain.interactor.AuthenticationInteractor
+import com.android.systemui.authentication.shared.model.AuthenticationMethodModel
+import com.android.systemui.authentication.shared.model.AuthenticationWipeModel
+import com.android.systemui.bouncer.domain.interactor.BouncerActionButtonInteractor
+import com.android.systemui.bouncer.domain.interactor.BouncerInteractor
+import com.android.systemui.bouncer.shared.flag.ComposeBouncerFlags
+import com.android.systemui.bouncer.shared.model.BouncerActionButtonModel
+import com.android.systemui.common.shared.model.Icon
+import com.android.systemui.common.shared.model.Text
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.lifecycle.SysUiViewModel
+import com.android.systemui.user.ui.viewmodel.UserSwitcherViewModel
+import dagger.assisted.AssistedFactory
+import dagger.assisted.AssistedInject
+import kotlinx.coroutines.awaitCancellation
+import kotlinx.coroutines.coroutineScope
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.asStateFlow
+import kotlinx.coroutines.flow.collectLatest
+import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.launch
+
+/** Models UI state for the content of the bouncer scene. */
+class BouncerSceneContentViewModel
+@AssistedInject
+constructor(
+    @Application private val applicationContext: Context,
+    private val bouncerInteractor: BouncerInteractor,
+    private val authenticationInteractor: AuthenticationInteractor,
+    private val devicePolicyManager: DevicePolicyManager,
+    private val bouncerMessageViewModelFactory: BouncerMessageViewModel.Factory,
+    private val flags: ComposeBouncerFlags,
+    private val userSwitcher: UserSwitcherViewModel,
+    private val actionButtonInteractor: BouncerActionButtonInteractor,
+    private val pinViewModelFactory: PinBouncerViewModel.Factory,
+    private val patternViewModelFactory: PatternBouncerViewModel.Factory,
+    private val passwordViewModelFactory: PasswordBouncerViewModel.Factory,
+) : SysUiViewModel() {
+    private val _selectedUserImage = MutableStateFlow<Bitmap?>(null)
+    val selectedUserImage: StateFlow<Bitmap?> = _selectedUserImage.asStateFlow()
+
+    val message: BouncerMessageViewModel by lazy { bouncerMessageViewModelFactory.create() }
+
+    private val _userSwitcherDropdown =
+        MutableStateFlow<List<UserSwitcherDropdownItemViewModel>>(emptyList())
+    val userSwitcherDropdown: StateFlow<List<UserSwitcherDropdownItemViewModel>> =
+        _userSwitcherDropdown.asStateFlow()
+
+    val isUserSwitcherVisible: Boolean
+        get() = bouncerInteractor.isUserSwitcherVisible
+
+    /** View-model for the current UI, based on the current authentication method. */
+    private val _authMethodViewModel = MutableStateFlow<AuthMethodBouncerViewModel?>(null)
+    val authMethodViewModel: StateFlow<AuthMethodBouncerViewModel?> =
+        _authMethodViewModel.asStateFlow()
+
+    /**
+     * A message for a dialog to show when the user has attempted the wrong credential too many
+     * times and now must wait a while before attempting again.
+     *
+     * If `null`, the lockout dialog should not be shown.
+     */
+    private val lockoutDialogMessage = MutableStateFlow<String?>(null)
+
+    /**
+     * A message for a dialog to show when the user has attempted the wrong credential too many
+     * times and their user/profile/device data is at risk of being wiped due to a Device Manager
+     * policy.
+     *
+     * If `null`, the wipe dialog should not be shown.
+     */
+    private val wipeDialogMessage = MutableStateFlow<String?>(null)
+
+    private val _dialogViewModel = MutableStateFlow<DialogViewModel?>(createDialogViewModel())
+    /**
+     * Models the dialog to be shown to the user, or `null` if no dialog should be shown.
+     *
+     * Once the dialog is shown, the UI should call [DialogViewModel.onDismiss] when the user
+     * dismisses this dialog.
+     */
+    val dialogViewModel: StateFlow<DialogViewModel?> = _dialogViewModel.asStateFlow()
+
+    private val _actionButton = MutableStateFlow<BouncerActionButtonModel?>(null)
+    /**
+     * The bouncer action button (Return to Call / Emergency Call). If `null`, the button should not
+     * be shown.
+     */
+    val actionButton: StateFlow<BouncerActionButtonModel?> = _actionButton.asStateFlow()
+
+    private val _isSideBySideSupported =
+        MutableStateFlow(isSideBySideSupported(authMethodViewModel.value))
+    /**
+     * Whether the "side-by-side" layout is supported.
+     *
+     * When presented on its own, without a user switcher (e.g. not on communal devices like
+     * tablets, for example), some authentication method UIs don't do well if they're shown in the
+     * side-by-side layout; these need to be shown with the standard layout so they can take up as
+     * much width as possible.
+     */
+    val isSideBySideSupported: StateFlow<Boolean> = _isSideBySideSupported.asStateFlow()
+
+    private val _isFoldSplitRequired =
+        MutableStateFlow(isFoldSplitRequired(authMethodViewModel.value))
+    /**
+     * Whether the splitting the UI around the fold seam (where the hinge is on a foldable device)
+     * is required.
+     */
+    val isFoldSplitRequired: StateFlow<Boolean> = _isFoldSplitRequired.asStateFlow()
+
+    private val _isInputEnabled =
+        MutableStateFlow(authenticationInteractor.lockoutEndTimestamp == null)
+    private val isInputEnabled: StateFlow<Boolean> = _isInputEnabled.asStateFlow()
+
+    override suspend fun onActivated(): Nothing {
+        coroutineScope {
+            launch { message.activate() }
+            launch {
+                authenticationInteractor.authenticationMethod
+                    .map(::getChildViewModel)
+                    .collectLatest { childViewModelOrNull ->
+                        _authMethodViewModel.value = childViewModelOrNull
+                        childViewModelOrNull?.activate()
+                    }
+            }
+
+            launch {
+                authenticationInteractor.upcomingWipe.collect { wipeModel ->
+                    wipeDialogMessage.value = wipeModel?.message
+                }
+            }
+
+            launch {
+                userSwitcher.selectedUser
+                    .map { it.image.toBitmap() }
+                    .collectLatest { _selectedUserImage.value = it }
+            }
+
+            launch {
+                combine(
+                        userSwitcher.users,
+                        userSwitcher.menu,
+                    ) { users, actions ->
+                        users.map { user ->
+                            UserSwitcherDropdownItemViewModel(
+                                icon = Icon.Loaded(user.image, contentDescription = null),
+                                text = user.name,
+                                onClick = user.onClicked ?: {},
+                            )
+                        } +
+                            actions.map { action ->
+                                UserSwitcherDropdownItemViewModel(
+                                    icon =
+                                        Icon.Resource(
+                                            action.iconResourceId,
+                                            contentDescription = null
+                                        ),
+                                    text = Text.Resource(action.textResourceId),
+                                    onClick = action.onClicked,
+                                )
+                            }
+                    }
+                    .collectLatest { _userSwitcherDropdown.value = it }
+            }
+
+            launch {
+                combine(wipeDialogMessage, lockoutDialogMessage) { _, _ -> createDialogViewModel() }
+                    .collectLatest { _dialogViewModel.value = it }
+            }
+
+            launch {
+                actionButtonInteractor.actionButton.collectLatest { _actionButton.value = it }
+            }
+
+            launch {
+                authMethodViewModel
+                    .map { authMethod -> isSideBySideSupported(authMethod) }
+                    .collectLatest { _isSideBySideSupported.value = it }
+            }
+
+            launch {
+                authMethodViewModel
+                    .map { authMethod -> isFoldSplitRequired(authMethod) }
+                    .collectLatest { _isFoldSplitRequired.value = it }
+            }
+
+            launch {
+                message.isLockoutMessagePresent
+                    .map { lockoutMessagePresent -> !lockoutMessagePresent }
+                    .collectLatest { _isInputEnabled.value = it }
+            }
+
+            awaitCancellation()
+        }
+    }
+
+    private fun isSideBySideSupported(authMethod: AuthMethodBouncerViewModel?): Boolean {
+        return isUserSwitcherVisible || authMethod !is PasswordBouncerViewModel
+    }
+
+    private fun isFoldSplitRequired(authMethod: AuthMethodBouncerViewModel?): Boolean {
+        return authMethod !is PasswordBouncerViewModel
+    }
+
+    private fun getChildViewModel(
+        authenticationMethod: AuthenticationMethodModel,
+    ): AuthMethodBouncerViewModel? {
+        // If the current child view-model matches the authentication method, reuse it instead of
+        // creating a new instance.
+        val childViewModel = authMethodViewModel.value
+        if (authenticationMethod == childViewModel?.authenticationMethod) {
+            return childViewModel
+        }
+
+        return when (authenticationMethod) {
+            is AuthenticationMethodModel.Pin ->
+                pinViewModelFactory.create(
+                    authenticationMethod = authenticationMethod,
+                    onIntentionalUserInput = ::onIntentionalUserInput,
+                    isInputEnabled = isInputEnabled,
+                )
+            is AuthenticationMethodModel.Sim ->
+                pinViewModelFactory.create(
+                    authenticationMethod = authenticationMethod,
+                    onIntentionalUserInput = ::onIntentionalUserInput,
+                    isInputEnabled = isInputEnabled,
+                )
+            is AuthenticationMethodModel.Password ->
+                passwordViewModelFactory.create(
+                    onIntentionalUserInput = ::onIntentionalUserInput,
+                    isInputEnabled = isInputEnabled,
+                )
+            is AuthenticationMethodModel.Pattern ->
+                patternViewModelFactory.create(
+                    onIntentionalUserInput = ::onIntentionalUserInput,
+                    isInputEnabled = isInputEnabled,
+                )
+            else -> null
+        }
+    }
+
+    private fun onIntentionalUserInput() {
+        message.showDefaultMessage()
+        bouncerInteractor.onIntentionalUserInput()
+    }
+
+    /**
+     * @return A message warning the user that the user/profile/device will be wiped upon a further
+     *   [AuthenticationWipeModel.remainingAttempts] unsuccessful authentication attempts.
+     */
+    private fun AuthenticationWipeModel.getAlmostAtWipeMessage(): String {
+        val message =
+            applicationContext.getString(
+                wipeTarget.messageIdForAlmostWipe,
+                failedAttempts,
+                remainingAttempts,
+            )
+        return if (wipeTarget == AuthenticationWipeModel.WipeTarget.ManagedProfile) {
+            devicePolicyManager.resources.getString(
+                DevicePolicyResources.Strings.SystemUi
+                    .KEYGUARD_DIALOG_FAILED_ATTEMPTS_ALMOST_ERASING_PROFILE,
+                { message },
+                failedAttempts,
+                remainingAttempts,
+            ) ?: message
+        } else {
+            message
+        }
+    }
+
+    /**
+     * @return A message informing the user that their user/profile/device will be wiped promptly.
+     */
+    private fun AuthenticationWipeModel.getWipeMessage(): String {
+        val message = applicationContext.getString(wipeTarget.messageIdForWipe, failedAttempts)
+        return if (wipeTarget == AuthenticationWipeModel.WipeTarget.ManagedProfile) {
+            devicePolicyManager.resources.getString(
+                DevicePolicyResources.Strings.SystemUi
+                    .KEYGUARD_DIALOG_FAILED_ATTEMPTS_ERASING_PROFILE,
+                { message },
+                failedAttempts,
+            ) ?: message
+        } else {
+            message
+        }
+    }
+
+    private val AuthenticationWipeModel.message: String
+        get() = if (remainingAttempts > 0) getAlmostAtWipeMessage() else getWipeMessage()
+
+    private fun createDialogViewModel(): DialogViewModel? {
+        val wipeText = wipeDialogMessage.value
+        val lockoutText = lockoutDialogMessage.value
+        return when {
+            // The wipe dialog takes priority over the lockout dialog.
+            wipeText != null ->
+                DialogViewModel(
+                    text = wipeText,
+                    onDismiss = { wipeDialogMessage.value = null },
+                )
+            lockoutText != null ->
+                DialogViewModel(
+                    text = lockoutText,
+                    onDismiss = { lockoutDialogMessage.value = null },
+                )
+            else -> null // No dialog to show.
+        }
+    }
+
+    /**
+     * Notifies that a key event has occurred.
+     *
+     * @return `true` when the [KeyEvent] was consumed as user input on bouncer; `false` otherwise.
+     */
+    fun onKeyEvent(keyEvent: KeyEvent): Boolean {
+        return (authMethodViewModel.value as? PinBouncerViewModel)?.onKeyEvent(
+            keyEvent.type,
+            keyEvent.nativeKeyEvent.keyCode
+        ) ?: false
+    }
+
+    data class DialogViewModel(
+        val text: String,
+
+        /** Callback to run after the dialog has been dismissed by the user. */
+        val onDismiss: () -> Unit,
+    )
+
+    data class UserSwitcherDropdownItemViewModel(
+        val icon: Icon,
+        val text: Text,
+        val onClick: () -> Unit,
+    )
+
+    @AssistedFactory
+    interface Factory {
+        fun create(): BouncerSceneContentViewModel
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModel.kt b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModel.kt
deleted file mode 100644
index e2089bb..0000000
--- a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModel.kt
+++ /dev/null
@@ -1,439 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.bouncer.ui.viewmodel
-
-import android.app.admin.DevicePolicyManager
-import android.app.admin.DevicePolicyResources
-import android.content.Context
-import android.graphics.Bitmap
-import androidx.compose.ui.input.key.KeyEvent
-import androidx.compose.ui.input.key.type
-import androidx.core.graphics.drawable.toBitmap
-import com.android.compose.animation.scene.Back
-import com.android.compose.animation.scene.Swipe
-import com.android.compose.animation.scene.SwipeDirection
-import com.android.compose.animation.scene.UserAction
-import com.android.compose.animation.scene.UserActionResult
-import com.android.systemui.authentication.domain.interactor.AuthenticationInteractor
-import com.android.systemui.authentication.shared.model.AuthenticationMethodModel
-import com.android.systemui.authentication.shared.model.AuthenticationWipeModel
-import com.android.systemui.bouncer.domain.interactor.BouncerActionButtonInteractor
-import com.android.systemui.bouncer.domain.interactor.BouncerInteractor
-import com.android.systemui.bouncer.domain.interactor.SimBouncerInteractor
-import com.android.systemui.bouncer.shared.flag.ComposeBouncerFlags
-import com.android.systemui.bouncer.shared.model.BouncerActionButtonModel
-import com.android.systemui.common.shared.model.Icon
-import com.android.systemui.common.shared.model.Text
-import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.dagger.qualifiers.Application
-import com.android.systemui.dagger.qualifiers.Main
-import com.android.systemui.inputmethod.domain.interactor.InputMethodInteractor
-import com.android.systemui.user.domain.interactor.SelectedUserInteractor
-import com.android.systemui.user.ui.viewmodel.UserActionViewModel
-import com.android.systemui.user.ui.viewmodel.UserSwitcherViewModel
-import com.android.systemui.user.ui.viewmodel.UserViewModel
-import dagger.Module
-import dagger.Provides
-import kotlinx.coroutines.CoroutineDispatcher
-import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.SupervisorJob
-import kotlinx.coroutines.cancel
-import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.MutableStateFlow
-import kotlinx.coroutines.flow.SharingStarted
-import kotlinx.coroutines.flow.StateFlow
-import kotlinx.coroutines.flow.combine
-import kotlinx.coroutines.flow.map
-import kotlinx.coroutines.flow.stateIn
-import kotlinx.coroutines.job
-import kotlinx.coroutines.launch
-
-/** Holds UI state and handles user input on bouncer UIs. */
-class BouncerViewModel(
-    @Application private val applicationContext: Context,
-    @Deprecated("TODO(b/354270224): remove this. Injecting CoroutineScope to view-models is banned")
-    @Application
-    private val applicationScope: CoroutineScope,
-    @Main private val mainDispatcher: CoroutineDispatcher,
-    private val bouncerInteractor: BouncerInteractor,
-    private val inputMethodInteractor: InputMethodInteractor,
-    private val simBouncerInteractor: SimBouncerInteractor,
-    private val authenticationInteractor: AuthenticationInteractor,
-    private val selectedUserInteractor: SelectedUserInteractor,
-    private val devicePolicyManager: DevicePolicyManager,
-    bouncerMessageViewModel: BouncerMessageViewModel,
-    flags: ComposeBouncerFlags,
-    selectedUser: Flow<UserViewModel>,
-    users: Flow<List<UserViewModel>>,
-    userSwitcherMenu: Flow<List<UserActionViewModel>>,
-    actionButton: Flow<BouncerActionButtonModel?>,
-) {
-    val selectedUserImage: StateFlow<Bitmap?> =
-        selectedUser
-            .map { it.image.toBitmap() }
-            .stateIn(
-                scope = applicationScope,
-                started = SharingStarted.WhileSubscribed(),
-                initialValue = null,
-            )
-
-    val destinationScenes: Flow<Map<UserAction, UserActionResult>> =
-        bouncerInteractor.dismissDestination.map { prevScene ->
-            mapOf(
-                Back to UserActionResult(prevScene),
-                Swipe(SwipeDirection.Down) to UserActionResult(prevScene),
-            )
-        }
-
-    val message: BouncerMessageViewModel = bouncerMessageViewModel
-
-    val userSwitcherDropdown: StateFlow<List<UserSwitcherDropdownItemViewModel>> =
-        combine(
-                users,
-                userSwitcherMenu,
-            ) { users, actions ->
-                users.map { user ->
-                    UserSwitcherDropdownItemViewModel(
-                        icon = Icon.Loaded(user.image, contentDescription = null),
-                        text = user.name,
-                        onClick = user.onClicked ?: {},
-                    )
-                } +
-                    actions.map { action ->
-                        UserSwitcherDropdownItemViewModel(
-                            icon = Icon.Resource(action.iconResourceId, contentDescription = null),
-                            text = Text.Resource(action.textResourceId),
-                            onClick = action.onClicked,
-                        )
-                    }
-            }
-            .stateIn(
-                scope = applicationScope,
-                started = SharingStarted.WhileSubscribed(),
-                initialValue = emptyList(),
-            )
-
-    val isUserSwitcherVisible: Boolean
-        get() = bouncerInteractor.isUserSwitcherVisible
-
-    // Handle to the scope of the child ViewModel (stored in [authMethod]).
-    private var childViewModelScope: CoroutineScope? = null
-
-    /** View-model for the current UI, based on the current authentication method. */
-    val authMethodViewModel: StateFlow<AuthMethodBouncerViewModel?> =
-        authenticationInteractor.authenticationMethod
-            .map(::getChildViewModel)
-            .stateIn(
-                scope = applicationScope,
-                started = SharingStarted.WhileSubscribed(),
-                initialValue = null,
-            )
-
-    /**
-     * A message for a dialog to show when the user has attempted the wrong credential too many
-     * times and now must wait a while before attempting again.
-     *
-     * If `null`, the lockout dialog should not be shown.
-     */
-    private val lockoutDialogMessage = MutableStateFlow<String?>(null)
-
-    /**
-     * A message for a dialog to show when the user has attempted the wrong credential too many
-     * times and their user/profile/device data is at risk of being wiped due to a Device Manager
-     * policy.
-     *
-     * If `null`, the wipe dialog should not be shown.
-     */
-    private val wipeDialogMessage = MutableStateFlow<String?>(null)
-
-    /**
-     * Models the dialog to be shown to the user, or `null` if no dialog should be shown.
-     *
-     * Once the dialog is shown, the UI should call [DialogViewModel.onDismiss] when the user
-     * dismisses this dialog.
-     */
-    val dialogViewModel: StateFlow<DialogViewModel?> =
-        combine(wipeDialogMessage, lockoutDialogMessage) { _, _ -> createDialogViewModel() }
-            .stateIn(
-                scope = applicationScope,
-                started = SharingStarted.WhileSubscribed(),
-                initialValue = createDialogViewModel(),
-            )
-
-    /**
-     * The bouncer action button (Return to Call / Emergency Call). If `null`, the button should not
-     * be shown.
-     */
-    val actionButton: StateFlow<BouncerActionButtonModel?> =
-        actionButton.stateIn(
-            scope = applicationScope,
-            started = SharingStarted.WhileSubscribed(),
-            initialValue = null
-        )
-
-    /**
-     * Whether the "side-by-side" layout is supported.
-     *
-     * When presented on its own, without a user switcher (e.g. not on communal devices like
-     * tablets, for example), some authentication method UIs don't do well if they're shown in the
-     * side-by-side layout; these need to be shown with the standard layout so they can take up as
-     * much width as possible.
-     */
-    val isSideBySideSupported: StateFlow<Boolean> =
-        authMethodViewModel
-            .map { authMethod -> isSideBySideSupported(authMethod) }
-            .stateIn(
-                scope = applicationScope,
-                started = SharingStarted.WhileSubscribed(),
-                initialValue = isSideBySideSupported(authMethodViewModel.value),
-            )
-
-    /**
-     * Whether the splitting the UI around the fold seam (where the hinge is on a foldable device)
-     * is required.
-     */
-    val isFoldSplitRequired: StateFlow<Boolean> =
-        authMethodViewModel
-            .map { authMethod -> isFoldSplitRequired(authMethod) }
-            .stateIn(
-                scope = applicationScope,
-                started = SharingStarted.WhileSubscribed(),
-                initialValue = isFoldSplitRequired(authMethodViewModel.value),
-            )
-
-    private val isInputEnabled: StateFlow<Boolean> =
-        bouncerMessageViewModel.isLockoutMessagePresent
-            .map { lockoutMessagePresent -> !lockoutMessagePresent }
-            .stateIn(
-                scope = applicationScope,
-                started = SharingStarted.WhileSubscribed(),
-                initialValue = authenticationInteractor.lockoutEndTimestamp == null,
-            )
-
-    init {
-        if (flags.isComposeBouncerOrSceneContainerEnabled()) {
-            // Keeps the upcoming wipe dialog up-to-date.
-            applicationScope.launch {
-                authenticationInteractor.upcomingWipe.collect { wipeModel ->
-                    wipeDialogMessage.value = wipeModel?.message
-                }
-            }
-        }
-    }
-
-    private fun isSideBySideSupported(authMethod: AuthMethodBouncerViewModel?): Boolean {
-        return isUserSwitcherVisible || authMethod !is PasswordBouncerViewModel
-    }
-
-    private fun isFoldSplitRequired(authMethod: AuthMethodBouncerViewModel?): Boolean {
-        return authMethod !is PasswordBouncerViewModel
-    }
-
-    private fun getChildViewModel(
-        authenticationMethod: AuthenticationMethodModel,
-    ): AuthMethodBouncerViewModel? {
-        // If the current child view-model matches the authentication method, reuse it instead of
-        // creating a new instance.
-        val childViewModel = authMethodViewModel.value
-        if (authenticationMethod == childViewModel?.authenticationMethod) {
-            return childViewModel
-        }
-
-        childViewModelScope?.cancel()
-        val newViewModelScope = createChildCoroutineScope(applicationScope)
-        childViewModelScope = newViewModelScope
-        return when (authenticationMethod) {
-            is AuthenticationMethodModel.Pin ->
-                PinBouncerViewModel(
-                    applicationContext = applicationContext,
-                    viewModelScope = newViewModelScope,
-                    interactor = bouncerInteractor,
-                    isInputEnabled = isInputEnabled,
-                    simBouncerInteractor = simBouncerInteractor,
-                    authenticationMethod = authenticationMethod,
-                    onIntentionalUserInput = ::onIntentionalUserInput
-                )
-            is AuthenticationMethodModel.Sim ->
-                PinBouncerViewModel(
-                    applicationContext = applicationContext,
-                    viewModelScope = newViewModelScope,
-                    interactor = bouncerInteractor,
-                    isInputEnabled = isInputEnabled,
-                    simBouncerInteractor = simBouncerInteractor,
-                    authenticationMethod = authenticationMethod,
-                    onIntentionalUserInput = ::onIntentionalUserInput
-                )
-            is AuthenticationMethodModel.Password ->
-                PasswordBouncerViewModel(
-                    viewModelScope = newViewModelScope,
-                    isInputEnabled = isInputEnabled,
-                    interactor = bouncerInteractor,
-                    inputMethodInteractor = inputMethodInteractor,
-                    selectedUserInteractor = selectedUserInteractor,
-                    onIntentionalUserInput = ::onIntentionalUserInput
-                )
-            is AuthenticationMethodModel.Pattern ->
-                PatternBouncerViewModel(
-                    applicationContext = applicationContext,
-                    viewModelScope = newViewModelScope,
-                    interactor = bouncerInteractor,
-                    isInputEnabled = isInputEnabled,
-                    onIntentionalUserInput = ::onIntentionalUserInput
-                )
-            else -> null
-        }
-    }
-
-    private fun onIntentionalUserInput() {
-        message.showDefaultMessage()
-        bouncerInteractor.onIntentionalUserInput()
-    }
-
-    private fun createChildCoroutineScope(parentScope: CoroutineScope): CoroutineScope {
-        return CoroutineScope(
-            SupervisorJob(parent = parentScope.coroutineContext.job) + mainDispatcher
-        )
-    }
-
-    /**
-     * @return A message warning the user that the user/profile/device will be wiped upon a further
-     *   [AuthenticationWipeModel.remainingAttempts] unsuccessful authentication attempts.
-     */
-    private fun AuthenticationWipeModel.getAlmostAtWipeMessage(): String {
-        val message =
-            applicationContext.getString(
-                wipeTarget.messageIdForAlmostWipe,
-                failedAttempts,
-                remainingAttempts,
-            )
-        return if (wipeTarget == AuthenticationWipeModel.WipeTarget.ManagedProfile) {
-            devicePolicyManager.resources.getString(
-                DevicePolicyResources.Strings.SystemUi
-                    .KEYGUARD_DIALOG_FAILED_ATTEMPTS_ALMOST_ERASING_PROFILE,
-                { message },
-                failedAttempts,
-                remainingAttempts,
-            ) ?: message
-        } else {
-            message
-        }
-    }
-
-    /**
-     * @return A message informing the user that their user/profile/device will be wiped promptly.
-     */
-    private fun AuthenticationWipeModel.getWipeMessage(): String {
-        val message = applicationContext.getString(wipeTarget.messageIdForWipe, failedAttempts)
-        return if (wipeTarget == AuthenticationWipeModel.WipeTarget.ManagedProfile) {
-            devicePolicyManager.resources.getString(
-                DevicePolicyResources.Strings.SystemUi
-                    .KEYGUARD_DIALOG_FAILED_ATTEMPTS_ERASING_PROFILE,
-                { message },
-                failedAttempts,
-            ) ?: message
-        } else {
-            message
-        }
-    }
-
-    private val AuthenticationWipeModel.message: String
-        get() = if (remainingAttempts > 0) getAlmostAtWipeMessage() else getWipeMessage()
-
-    private fun createDialogViewModel(): DialogViewModel? {
-        val wipeText = wipeDialogMessage.value
-        val lockoutText = lockoutDialogMessage.value
-        return when {
-            // The wipe dialog takes priority over the lockout dialog.
-            wipeText != null ->
-                DialogViewModel(
-                    text = wipeText,
-                    onDismiss = { wipeDialogMessage.value = null },
-                )
-            lockoutText != null ->
-                DialogViewModel(
-                    text = lockoutText,
-                    onDismiss = { lockoutDialogMessage.value = null },
-                )
-            else -> null // No dialog to show.
-        }
-    }
-
-    /**
-     * Notifies that a key event has occurred.
-     *
-     * @return `true` when the [KeyEvent] was consumed as user input on bouncer; `false` otherwise.
-     */
-    fun onKeyEvent(keyEvent: KeyEvent): Boolean {
-        return (authMethodViewModel.value as? PinBouncerViewModel)?.onKeyEvent(
-            keyEvent.type,
-            keyEvent.nativeKeyEvent.keyCode
-        ) ?: false
-    }
-
-    data class DialogViewModel(
-        val text: String,
-
-        /** Callback to run after the dialog has been dismissed by the user. */
-        val onDismiss: () -> Unit,
-    )
-
-    data class UserSwitcherDropdownItemViewModel(
-        val icon: Icon,
-        val text: Text,
-        val onClick: () -> Unit,
-    )
-}
-
-@Module
-object BouncerViewModelModule {
-
-    @Provides
-    @SysUISingleton
-    fun viewModel(
-        @Application applicationContext: Context,
-        @Application applicationScope: CoroutineScope,
-        @Main mainDispatcher: CoroutineDispatcher,
-        bouncerInteractor: BouncerInteractor,
-        imeInteractor: InputMethodInteractor,
-        simBouncerInteractor: SimBouncerInteractor,
-        actionButtonInteractor: BouncerActionButtonInteractor,
-        authenticationInteractor: AuthenticationInteractor,
-        selectedUserInteractor: SelectedUserInteractor,
-        flags: ComposeBouncerFlags,
-        userSwitcherViewModel: UserSwitcherViewModel,
-        devicePolicyManager: DevicePolicyManager,
-        bouncerMessageViewModel: BouncerMessageViewModel,
-    ): BouncerViewModel {
-        return BouncerViewModel(
-            applicationContext = applicationContext,
-            applicationScope = applicationScope,
-            mainDispatcher = mainDispatcher,
-            bouncerInteractor = bouncerInteractor,
-            inputMethodInteractor = imeInteractor,
-            simBouncerInteractor = simBouncerInteractor,
-            authenticationInteractor = authenticationInteractor,
-            selectedUserInteractor = selectedUserInteractor,
-            devicePolicyManager = devicePolicyManager,
-            bouncerMessageViewModel = bouncerMessageViewModel,
-            flags = flags,
-            selectedUser = userSwitcherViewModel.selectedUser,
-            users = userSwitcherViewModel.users,
-            userSwitcherMenu = userSwitcherViewModel.menu,
-            actionButton = actionButtonInteractor.actionButton,
-        )
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModel.kt b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModel.kt
index 052fb6b..c91fd6a 100644
--- a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModel.kt
@@ -23,29 +23,34 @@
 import com.android.systemui.res.R
 import com.android.systemui.user.domain.interactor.SelectedUserInteractor
 import com.android.systemui.util.kotlin.onSubscriberAdded
+import dagger.assisted.Assisted
+import dagger.assisted.AssistedFactory
+import dagger.assisted.AssistedInject
 import kotlin.time.Duration.Companion.milliseconds
-import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.awaitCancellation
+import kotlinx.coroutines.channels.Channel
+import kotlinx.coroutines.coroutineScope
 import kotlinx.coroutines.delay
 import kotlinx.coroutines.flow.MutableStateFlow
-import kotlinx.coroutines.flow.SharingStarted
 import kotlinx.coroutines.flow.StateFlow
 import kotlinx.coroutines.flow.asStateFlow
+import kotlinx.coroutines.flow.collectLatest
 import kotlinx.coroutines.flow.combine
 import kotlinx.coroutines.flow.onEach
-import kotlinx.coroutines.flow.stateIn
+import kotlinx.coroutines.flow.receiveAsFlow
 import kotlinx.coroutines.launch
 
 /** Holds UI state and handles user input for the password bouncer UI. */
-class PasswordBouncerViewModel(
-    viewModelScope: CoroutineScope,
-    isInputEnabled: StateFlow<Boolean>,
+class PasswordBouncerViewModel
+@AssistedInject
+constructor(
     interactor: BouncerInteractor,
-    private val onIntentionalUserInput: () -> Unit,
     private val inputMethodInteractor: InputMethodInteractor,
     private val selectedUserInteractor: SelectedUserInteractor,
+    @Assisted isInputEnabled: StateFlow<Boolean>,
+    @Assisted private val onIntentionalUserInput: () -> Unit,
 ) :
     AuthMethodBouncerViewModel(
-        viewModelScope = viewModelScope,
         interactor = interactor,
         isInputEnabled = isInputEnabled,
     ) {
@@ -59,28 +64,71 @@
 
     override val lockoutMessageId = R.string.kg_too_many_failed_password_attempts_dialog_message
 
+    private val _isImeSwitcherButtonVisible = MutableStateFlow(false)
     /** Informs the UI whether the input method switcher button should be visible. */
-    val isImeSwitcherButtonVisible: StateFlow<Boolean> = imeSwitcherRefreshingFlow()
+    val isImeSwitcherButtonVisible: StateFlow<Boolean> = _isImeSwitcherButtonVisible.asStateFlow()
 
     /** Whether the text field element currently has focus. */
     private val isTextFieldFocused = MutableStateFlow(false)
 
+    private val _isTextFieldFocusRequested =
+        MutableStateFlow(isInputEnabled.value && !isTextFieldFocused.value)
     /** Whether the UI should request focus on the text field element. */
-    val isTextFieldFocusRequested =
-        combine(isInputEnabled, isTextFieldFocused) { hasInput, hasFocus -> hasInput && !hasFocus }
-            .stateIn(
-                scope = viewModelScope,
-                started = SharingStarted.WhileSubscribed(),
-                initialValue = isInputEnabled.value && !isTextFieldFocused.value,
-            )
+    val isTextFieldFocusRequested = _isTextFieldFocusRequested.asStateFlow()
 
+    private val _selectedUserId = MutableStateFlow(selectedUserInteractor.getSelectedUserId())
     /** The ID of the currently-selected user. */
-    val selectedUserId: StateFlow<Int> =
-        selectedUserInteractor.selectedUser.stateIn(
-            scope = viewModelScope,
-            started = SharingStarted.WhileSubscribed(),
-            initialValue = selectedUserInteractor.getSelectedUserId(),
-        )
+    val selectedUserId: StateFlow<Int> = _selectedUserId.asStateFlow()
+
+    private val requests = Channel<Request>(Channel.BUFFERED)
+
+    override suspend fun onActivated(): Nothing {
+        coroutineScope {
+            launch { super.onActivated() }
+            launch {
+                requests.receiveAsFlow().collect { request ->
+                    when (request) {
+                        is OnImeSwitcherButtonClicked -> {
+                            inputMethodInteractor.showInputMethodPicker(
+                                displayId = request.displayId,
+                                showAuxiliarySubtypes = false,
+                            )
+                        }
+                        is OnImeDismissed -> {
+                            interactor.onImeHiddenByUser()
+                        }
+                    }
+                }
+            }
+            launch {
+                combine(isInputEnabled, isTextFieldFocused) { hasInput, hasFocus ->
+                        hasInput && !hasFocus
+                    }
+                    .collectLatest { _isTextFieldFocusRequested.value = it }
+            }
+            launch {
+                selectedUserInteractor.selectedUser.collectLatest { _selectedUserId.value = it }
+            }
+            launch {
+                // Re-fetch the currently-enabled IMEs whenever the selected user changes, and
+                // whenever
+                // the UI subscribes to the `isImeSwitcherButtonVisible` flow.
+                combine(
+                        // InputMethodManagerService sometimes takes some time to update its
+                        // internal
+                        // state when the selected user changes. As a workaround, delay fetching the
+                        // IME
+                        // info.
+                        selectedUserInteractor.selectedUser.onEach { delay(DELAY_TO_FETCH_IMES) },
+                        _isImeSwitcherButtonVisible.onSubscriberAdded()
+                    ) { selectedUserId, _ ->
+                        inputMethodInteractor.hasMultipleEnabledImesOrSubtypes(selectedUserId)
+                    }
+                    .collectLatest { _isImeSwitcherButtonVisible.value = it }
+            }
+            awaitCancellation()
+        }
+    }
 
     override fun onHidden() {
         super.onHidden()
@@ -106,9 +154,7 @@
 
     /** Notifies that the user clicked the button to change the input method. */
     fun onImeSwitcherButtonClicked(displayId: Int) {
-        viewModelScope.launch {
-            inputMethodInteractor.showInputMethodPicker(displayId, showAuxiliarySubtypes = false)
-        }
+        requests.trySend(OnImeSwitcherButtonClicked(displayId))
     }
 
     /** Notifies that the user has pressed the key for attempting to authenticate the password. */
@@ -120,7 +166,7 @@
 
     /** Notifies that the user has dismissed the software keyboard (IME). */
     fun onImeDismissed() {
-        viewModelScope.launch { interactor.onImeHiddenByUser() }
+        requests.trySend(OnImeDismissed)
     }
 
     /** Notifies that the password text field has gained or lost focus. */
@@ -128,34 +174,21 @@
         isTextFieldFocused.value = isFocused
     }
 
-    /**
-     * Whether the input method switcher button should be displayed in the password bouncer UI. The
-     * value may be stale at the moment of subscription to this flow, but it is guaranteed to be
-     * shortly updated with a fresh value.
-     *
-     * Note: Each added subscription triggers an IPC call in the background, so this should only be
-     * subscribed to by the UI once in its lifecycle (i.e. when the bouncer is shown).
-     */
-    private fun imeSwitcherRefreshingFlow(): StateFlow<Boolean> {
-        val isImeSwitcherButtonVisible = MutableStateFlow(value = false)
-        viewModelScope.launch {
-            // Re-fetch the currently-enabled IMEs whenever the selected user changes, and whenever
-            // the UI subscribes to the `isImeSwitcherButtonVisible` flow.
-            combine(
-                    // InputMethodManagerService sometimes takes some time to update its internal
-                    // state when the selected user changes. As a workaround, delay fetching the IME
-                    // info.
-                    selectedUserInteractor.selectedUser.onEach { delay(DELAY_TO_FETCH_IMES) },
-                    isImeSwitcherButtonVisible.onSubscriberAdded()
-                ) { selectedUserId, _ ->
-                    inputMethodInteractor.hasMultipleEnabledImesOrSubtypes(selectedUserId)
-                }
-                .collect { isImeSwitcherButtonVisible.value = it }
-        }
-        return isImeSwitcherButtonVisible.asStateFlow()
+    @AssistedFactory
+    interface Factory {
+        fun create(
+            isInputEnabled: StateFlow<Boolean>,
+            onIntentionalUserInput: () -> Unit,
+        ): PasswordBouncerViewModel
     }
 
     companion object {
         @VisibleForTesting val DELAY_TO_FETCH_IMES = 300.milliseconds
     }
+
+    private sealed interface Request
+
+    private data class OnImeSwitcherButtonClicked(val displayId: Int) : Request
+
+    private data object OnImeDismissed : Request
 }
diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PatternBouncerViewModel.kt b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PatternBouncerViewModel.kt
index 8b9c0a9a..4c02929 100644
--- a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PatternBouncerViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PatternBouncerViewModel.kt
@@ -22,28 +22,33 @@
 import com.android.systemui.authentication.shared.model.AuthenticationPatternCoordinate
 import com.android.systemui.bouncer.domain.interactor.BouncerInteractor
 import com.android.systemui.res.R
+import dagger.assisted.Assisted
+import dagger.assisted.AssistedFactory
+import dagger.assisted.AssistedInject
 import kotlin.math.max
 import kotlin.math.min
 import kotlin.math.pow
 import kotlin.math.sqrt
-import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.awaitCancellation
+import kotlinx.coroutines.coroutineScope
 import kotlinx.coroutines.flow.MutableStateFlow
-import kotlinx.coroutines.flow.SharingStarted
 import kotlinx.coroutines.flow.StateFlow
 import kotlinx.coroutines.flow.asStateFlow
+import kotlinx.coroutines.flow.collectLatest
 import kotlinx.coroutines.flow.map
-import kotlinx.coroutines.flow.stateIn
+import kotlinx.coroutines.flow.toList
+import kotlinx.coroutines.launch
 
 /** Holds UI state and handles user input for the pattern bouncer UI. */
-class PatternBouncerViewModel(
+class PatternBouncerViewModel
+@AssistedInject
+constructor(
     private val applicationContext: Context,
-    viewModelScope: CoroutineScope,
     interactor: BouncerInteractor,
-    isInputEnabled: StateFlow<Boolean>,
-    private val onIntentionalUserInput: () -> Unit,
+    @Assisted isInputEnabled: StateFlow<Boolean>,
+    @Assisted private val onIntentionalUserInput: () -> Unit,
 ) :
     AuthMethodBouncerViewModel(
-        viewModelScope = viewModelScope,
         interactor = interactor,
         isInputEnabled = isInputEnabled,
     ) {
@@ -54,17 +59,10 @@
     /** The number of rows in the dot grid. */
     val rowCount = 3
 
-    private val _selectedDots = MutableStateFlow<LinkedHashSet<PatternDotViewModel>>(linkedSetOf())
-
+    private val selectedDotSet = MutableStateFlow<LinkedHashSet<PatternDotViewModel>>(linkedSetOf())
+    private val selectedDotList = MutableStateFlow(selectedDotSet.value.toList())
     /** The dots that were selected by the user, in the order of selection. */
-    val selectedDots: StateFlow<List<PatternDotViewModel>> =
-        _selectedDots
-            .map { it.toList() }
-            .stateIn(
-                scope = viewModelScope,
-                started = SharingStarted.WhileSubscribed(),
-                initialValue = emptyList(),
-            )
+    val selectedDots: StateFlow<List<PatternDotViewModel>> = selectedDotList.asStateFlow()
 
     private val _currentDot = MutableStateFlow<PatternDotViewModel?>(null)
 
@@ -83,6 +81,18 @@
 
     override val lockoutMessageId = R.string.kg_too_many_failed_pattern_attempts_dialog_message
 
+    override suspend fun onActivated(): Nothing {
+        coroutineScope {
+            launch { super.onActivated() }
+            launch {
+                selectedDotSet
+                    .map { it.toList() }
+                    .collectLatest { selectedDotList.value = it.toList() }
+            }
+            awaitCancellation()
+        }
+    }
+
     /** Notifies that the user has started a drag gesture across the dot grid. */
     fun onDragStart() {
         onIntentionalUserInput()
@@ -120,7 +130,7 @@
         }
 
         val hitDot = dots.value.firstOrNull { dot -> dot.x == dotColumn && dot.y == dotRow }
-        if (hitDot != null && !_selectedDots.value.contains(hitDot)) {
+        if (hitDot != null && !selectedDotSet.value.contains(hitDot)) {
             val skippedOverDots =
                 currentDot.value?.let { previousDot ->
                     buildList {
@@ -147,9 +157,9 @@
                     }
                 } ?: emptyList()
 
-            _selectedDots.value =
+            selectedDotSet.value =
                 linkedSetOf<PatternDotViewModel>().apply {
-                    addAll(_selectedDots.value)
+                    addAll(selectedDotSet.value)
                     addAll(skippedOverDots)
                     add(hitDot)
                 }
@@ -172,11 +182,11 @@
     override fun clearInput() {
         _dots.value = defaultDots()
         _currentDot.value = null
-        _selectedDots.value = linkedSetOf()
+        selectedDotSet.value = linkedSetOf()
     }
 
     override fun getInput(): List<Any> {
-        return _selectedDots.value.map(PatternDotViewModel::toCoordinate)
+        return selectedDotSet.value.map(PatternDotViewModel::toCoordinate)
     }
 
     private fun defaultDots(): List<PatternDotViewModel> {
@@ -204,6 +214,14 @@
         max(min(outValue.float, 1f), MIN_DOT_HIT_FACTOR)
     }
 
+    @AssistedFactory
+    interface Factory {
+        fun create(
+            isInputEnabled: StateFlow<Boolean>,
+            onIntentionalUserInput: () -> Unit,
+        ): PatternBouncerViewModel
+    }
+
     companion object {
         private const val MIN_DOT_HIT_FACTOR = 0.2f
     }
diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModel.kt b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModel.kt
index aa447ff..c611954 100644
--- a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModel.kt
@@ -32,29 +32,35 @@
 import com.android.systemui.bouncer.domain.interactor.BouncerInteractor
 import com.android.systemui.bouncer.domain.interactor.SimBouncerInteractor
 import com.android.systemui.res.R
-import kotlinx.coroutines.CoroutineScope
+import dagger.assisted.Assisted
+import dagger.assisted.AssistedFactory
+import dagger.assisted.AssistedInject
 import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.awaitCancellation
+import kotlinx.coroutines.channels.Channel
+import kotlinx.coroutines.coroutineScope
 import kotlinx.coroutines.flow.MutableStateFlow
-import kotlinx.coroutines.flow.SharingStarted
 import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.asStateFlow
+import kotlinx.coroutines.flow.collectLatest
 import kotlinx.coroutines.flow.combine
 import kotlinx.coroutines.flow.flowOf
 import kotlinx.coroutines.flow.map
-import kotlinx.coroutines.flow.stateIn
+import kotlinx.coroutines.flow.receiveAsFlow
 import kotlinx.coroutines.launch
 
 /** Holds UI state and handles user input for the PIN code bouncer UI. */
-class PinBouncerViewModel(
+class PinBouncerViewModel
+@AssistedInject
+constructor(
     applicationContext: Context,
-    viewModelScope: CoroutineScope,
     interactor: BouncerInteractor,
-    isInputEnabled: StateFlow<Boolean>,
-    private val onIntentionalUserInput: () -> Unit,
     private val simBouncerInteractor: SimBouncerInteractor,
-    authenticationMethod: AuthenticationMethodModel,
+    @Assisted isInputEnabled: StateFlow<Boolean>,
+    @Assisted private val onIntentionalUserInput: () -> Unit,
+    @Assisted override val authenticationMethod: AuthenticationMethodModel,
 ) :
     AuthMethodBouncerViewModel(
-        viewModelScope = viewModelScope,
         interactor = interactor,
         isInputEnabled = isInputEnabled,
     ) {
@@ -73,69 +79,90 @@
     /** Currently entered pin keys. */
     val pinInput: StateFlow<PinInputViewModel> = mutablePinInput
 
+    private val _hintedPinLength = MutableStateFlow<Int?>(null)
     /** The length of the PIN for which we should show a hint. */
-    val hintedPinLength: StateFlow<Int?> =
-        if (isSimAreaVisible) {
-                flowOf(null)
-            } else {
-                interactor.hintedPinLength
-            }
-            .stateIn(viewModelScope, SharingStarted.WhileSubscribed(), null)
+    val hintedPinLength: StateFlow<Int?> = _hintedPinLength.asStateFlow()
 
+    private val _backspaceButtonAppearance = MutableStateFlow(ActionButtonAppearance.Hidden)
     /** Appearance of the backspace button. */
     val backspaceButtonAppearance: StateFlow<ActionButtonAppearance> =
-        combine(
-                mutablePinInput,
-                interactor.isAutoConfirmEnabled,
-            ) { mutablePinEntries, isAutoConfirmEnabled ->
-                computeBackspaceButtonAppearance(
-                    pinInput = mutablePinEntries,
-                    isAutoConfirmEnabled = isAutoConfirmEnabled,
-                )
-            }
-            .stateIn(
-                scope = viewModelScope,
-                // Make sure this is kept as WhileSubscribed or we can run into a bug where the
-                // downstream continues to receive old/stale/cached values.
-                started = SharingStarted.WhileSubscribed(),
-                initialValue = ActionButtonAppearance.Hidden,
-            )
+        _backspaceButtonAppearance.asStateFlow()
 
+    private val _confirmButtonAppearance = MutableStateFlow(ActionButtonAppearance.Hidden)
     /** Appearance of the confirm button. */
     val confirmButtonAppearance: StateFlow<ActionButtonAppearance> =
-        interactor.isAutoConfirmEnabled
-            .map { if (it) ActionButtonAppearance.Hidden else ActionButtonAppearance.Shown }
-            .stateIn(
-                scope = viewModelScope,
-                started = SharingStarted.WhileSubscribed(),
-                initialValue = ActionButtonAppearance.Hidden,
-            )
-
-    override val authenticationMethod: AuthenticationMethodModel = authenticationMethod
+        _confirmButtonAppearance.asStateFlow()
 
     override val lockoutMessageId = R.string.kg_too_many_failed_pin_attempts_dialog_message
 
-    init {
-        viewModelScope.launch { simBouncerInteractor.subId.collect { onResetSimFlow() } }
+    private val requests = Channel<Request>(Channel.BUFFERED)
+
+    override suspend fun onActivated(): Nothing {
+        coroutineScope {
+            launch { super.onActivated() }
+            launch {
+                requests.receiveAsFlow().collect { request ->
+                    when (request) {
+                        is OnErrorDialogDismissed -> {
+                            simBouncerInteractor.onErrorDialogDismissed()
+                        }
+                        is OnAuthenticateButtonClickedForSim -> {
+                            isSimUnlockingDialogVisible.value = true
+                            simBouncerInteractor.verifySim(getInput())
+                            isSimUnlockingDialogVisible.value = false
+                            clearInput()
+                        }
+                    }
+                }
+            }
+            launch { simBouncerInteractor.subId.collect { onResetSimFlow() } }
+            launch {
+                if (isSimAreaVisible) {
+                        flowOf(null)
+                    } else {
+                        interactor.hintedPinLength
+                    }
+                    .collectLatest { _hintedPinLength.value = it }
+            }
+            launch {
+                combine(
+                        mutablePinInput,
+                        interactor.isAutoConfirmEnabled,
+                    ) { mutablePinEntries, isAutoConfirmEnabled ->
+                        computeBackspaceButtonAppearance(
+                            pinInput = mutablePinEntries,
+                            isAutoConfirmEnabled = isAutoConfirmEnabled,
+                        )
+                    }
+                    .collectLatest { _backspaceButtonAppearance.value = it }
+            }
+            launch {
+                interactor.isAutoConfirmEnabled
+                    .map { if (it) ActionButtonAppearance.Hidden else ActionButtonAppearance.Shown }
+                    .collectLatest { _confirmButtonAppearance.value = it }
+            }
+            launch {
+                interactor.isPinEnhancedPrivacyEnabled
+                    .map { !it }
+                    .collectLatest { _isDigitButtonAnimationEnabled.value = it }
+            }
+            awaitCancellation()
+        }
     }
 
     /** Notifies that the user dismissed the sim pin error dialog. */
     fun onErrorDialogDismissed() {
-        viewModelScope.launch { simBouncerInteractor.onErrorDialogDismissed() }
+        requests.trySend(OnErrorDialogDismissed)
     }
 
+    private val _isDigitButtonAnimationEnabled =
+        MutableStateFlow(!interactor.isPinEnhancedPrivacyEnabled.value)
     /**
      * Whether the digit buttons should be animated when touched. Note that this doesn't affect the
      * delete or enter buttons; those should always animate.
      */
     val isDigitButtonAnimationEnabled: StateFlow<Boolean> =
-        interactor.isPinEnhancedPrivacyEnabled
-            .map { !it }
-            .stateIn(
-                scope = viewModelScope,
-                started = SharingStarted.WhileSubscribed(),
-                initialValue = !interactor.isPinEnhancedPrivacyEnabled.value,
-            )
+        _isDigitButtonAnimationEnabled.asStateFlow()
 
     /** Notifies that the user clicked on a PIN button with the given digit value. */
     fun onPinButtonClicked(input: Int) {
@@ -163,19 +190,14 @@
     /** Notifies that the user clicked the "enter" button. */
     fun onAuthenticateButtonClicked() {
         if (authenticationMethod == AuthenticationMethodModel.Sim) {
-            viewModelScope.launch {
-                isSimUnlockingDialogVisible.value = true
-                simBouncerInteractor.verifySim(getInput())
-                isSimUnlockingDialogVisible.value = false
-                clearInput()
-            }
+            requests.trySend(OnAuthenticateButtonClickedForSim)
         } else {
             tryAuthenticate(useAutoConfirm = false)
         }
     }
 
     fun onDisableEsimButtonClicked() {
-        viewModelScope.launch { simBouncerInteractor.disableEsim() }
+        simBouncerInteractor.disableEsim()
     }
 
     /** Resets the sim screen and shows a default message. */
@@ -242,6 +264,21 @@
             else -> false
         }
     }
+
+    @AssistedFactory
+    interface Factory {
+        fun create(
+            isInputEnabled: StateFlow<Boolean>,
+            onIntentionalUserInput: () -> Unit,
+            authenticationMethod: AuthenticationMethodModel,
+        ): PinBouncerViewModel
+    }
+
+    private sealed interface Request
+
+    private data object OnErrorDialogDismissed : Request
+
+    private data object OnAuthenticateButtonClickedForSim : Request
 }
 
 /** Appearance of pin-pad action buttons. */
diff --git a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayController.java b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayController.java
index 04c6fa9..040af90 100644
--- a/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayController.java
+++ b/packages/SystemUI/src/com/android/systemui/clipboardoverlay/ClipboardOverlayController.java
@@ -19,6 +19,7 @@
 import static android.content.Intent.ACTION_CLOSE_SYSTEM_DIALOGS;
 
 import static com.android.internal.config.sysui.SystemUiDeviceConfigFlags.CLIPBOARD_OVERLAY_SHOW_ACTIONS;
+import static com.android.systemui.Flags.clipboardImageTimeout;
 import static com.android.systemui.clipboardoverlay.ClipboardOverlayEvent.CLIPBOARD_OVERLAY_ACTION_SHOWN;
 import static com.android.systemui.clipboardoverlay.ClipboardOverlayEvent.CLIPBOARD_OVERLAY_ACTION_TAPPED;
 import static com.android.systemui.clipboardoverlay.ClipboardOverlayEvent.CLIPBOARD_OVERLAY_DISMISSED_OTHER;
@@ -32,7 +33,6 @@
 import static com.android.systemui.clipboardoverlay.ClipboardOverlayEvent.CLIPBOARD_OVERLAY_SWIPE_DISMISSED;
 import static com.android.systemui.clipboardoverlay.ClipboardOverlayEvent.CLIPBOARD_OVERLAY_TAP_OUTSIDE;
 import static com.android.systemui.clipboardoverlay.ClipboardOverlayEvent.CLIPBOARD_OVERLAY_TIMED_OUT;
-import static com.android.systemui.flags.Flags.CLIPBOARD_IMAGE_TIMEOUT;
 import static com.android.systemui.flags.Flags.CLIPBOARD_SHARED_TRANSITIONS;
 
 import android.animation.Animator;
@@ -288,7 +288,7 @@
         boolean shouldAnimate = !model.dataMatches(mClipboardModel) || wasExiting;
         mClipboardModel = model;
         mClipboardLogger.setClipSource(mClipboardModel.getSource());
-        if (mFeatureFlags.isEnabled(CLIPBOARD_IMAGE_TIMEOUT)) {
+        if (clipboardImageTimeout()) {
             if (shouldAnimate) {
                 reset();
                 mClipboardLogger.setClipSource(mClipboardModel.getSource());
@@ -452,7 +452,7 @@
                     mClipboardLogger.logUnguarded(CLIPBOARD_OVERLAY_EXPANDED_FROM_MINIMIZED);
                     mIsMinimized = false;
                 }
-                if (mFeatureFlags.isEnabled(CLIPBOARD_IMAGE_TIMEOUT)) {
+                if (clipboardImageTimeout()) {
                     setExpandedView(() -> animateIn());
                 } else {
                     setExpandedView();
@@ -522,7 +522,7 @@
                 mInputMonitor.getInputChannel(), Looper.getMainLooper()) {
             @Override
             public void onInputEvent(InputEvent event) {
-                if ((!mFeatureFlags.isEnabled(CLIPBOARD_IMAGE_TIMEOUT) || mShowingUi)
+                if ((!clipboardImageTimeout() || mShowingUi)
                         && event instanceof MotionEvent) {
                     MotionEvent motionEvent = (MotionEvent) event;
                     if (motionEvent.getActionMasked() == MotionEvent.ACTION_DOWN) {
diff --git a/packages/SystemUI/src/com/android/systemui/communal/CommunalSceneStartable.kt b/packages/SystemUI/src/com/android/systemui/communal/CommunalSceneStartable.kt
index b7c02ea..6e01393 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/CommunalSceneStartable.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/CommunalSceneStartable.kt
@@ -112,7 +112,11 @@
                         communalSceneInteractor.editModeState.value == EditModeState.STARTING ||
                             communalSceneInteractor.isLaunchingWidget.value
                     if (!delaySceneTransition) {
-                        communalSceneInteractor.changeScene(nextScene, nextTransition)
+                        communalSceneInteractor.changeScene(
+                            newScene = nextScene,
+                            loggingReason = "KTF syncing",
+                            transitionKey = nextTransition,
+                        )
                     }
                 }
                 .launchIn(applicationScope)
@@ -176,7 +180,10 @@
                     if (scene == CommunalScenes.Communal && isDreaming && timeoutJob == null) {
                         // If dreaming starts after timeout has expired, ex. if dream restarts under
                         // the hub, just close the hub immediately.
-                        communalSceneInteractor.changeScene(CommunalScenes.Blank)
+                        communalSceneInteractor.changeScene(
+                            CommunalScenes.Blank,
+                            "dream started after timeout",
+                        )
                     }
                 }
         }
@@ -201,7 +208,10 @@
                 bgScope.launch {
                     delay(screenTimeout.milliseconds)
                     if (isDreaming) {
-                        communalSceneInteractor.changeScene(CommunalScenes.Blank)
+                        communalSceneInteractor.changeScene(
+                            newScene = CommunalScenes.Blank,
+                            loggingReason = "hub timeout",
+                        )
                     }
                     timeoutJob = null
                 }
diff --git a/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt b/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt
index 99bcc12..7181b15 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt
@@ -329,8 +329,11 @@
     @Deprecated(
         "Use com.android.systemui.communal.domain.interactor.CommunalSceneInteractor instead"
     )
-    fun changeScene(newScene: SceneKey, transitionKey: TransitionKey? = null) =
-        communalSceneInteractor.changeScene(newScene, transitionKey)
+    fun changeScene(
+        newScene: SceneKey,
+        loggingReason: String,
+        transitionKey: TransitionKey? = null
+    ) = communalSceneInteractor.changeScene(newScene, loggingReason, transitionKey)
 
     fun setEditModeOpen(isOpen: Boolean) {
         _editModeOpen.value = isOpen
diff --git a/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalSceneInteractor.kt b/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalSceneInteractor.kt
index e45a695..a0b9966 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalSceneInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalSceneInteractor.kt
@@ -22,6 +22,7 @@
 import com.android.compose.animation.scene.TransitionKey
 import com.android.systemui.communal.data.repository.CommunalSceneRepository
 import com.android.systemui.communal.domain.model.CommunalTransitionProgressModel
+import com.android.systemui.communal.shared.log.CommunalSceneLogger
 import com.android.systemui.communal.shared.model.CommunalScenes
 import com.android.systemui.communal.shared.model.CommunalTransitionKeys
 import com.android.systemui.communal.shared.model.EditModeState
@@ -29,6 +30,7 @@
 import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.keyguard.shared.model.KeyguardState
 import com.android.systemui.util.kotlin.BooleanFlowOperators.allOf
+import com.android.systemui.util.kotlin.pairwiseBy
 import javax.inject.Inject
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -42,8 +44,8 @@
 import kotlinx.coroutines.flow.flatMapLatest
 import kotlinx.coroutines.flow.flowOf
 import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.onEach
 import kotlinx.coroutines.flow.stateIn
-import kotlinx.coroutines.launch
 
 @OptIn(ExperimentalCoroutinesApi::class)
 @SysUISingleton
@@ -51,7 +53,8 @@
 @Inject
 constructor(
     @Application private val applicationScope: CoroutineScope,
-    private val communalSceneRepository: CommunalSceneRepository,
+    private val repository: CommunalSceneRepository,
+    private val logger: CommunalSceneLogger,
 ) {
     private val _isLaunchingWidget = MutableStateFlow(false)
 
@@ -80,25 +83,39 @@
      */
     fun changeScene(
         newScene: SceneKey,
+        loggingReason: String,
         transitionKey: TransitionKey? = null,
         keyguardState: KeyguardState? = null,
     ) {
-        applicationScope.launch {
+        applicationScope.launch("$TAG#changeScene") {
+            logger.logSceneChangeRequested(
+                from = currentScene.value,
+                to = newScene,
+                reason = loggingReason,
+                isInstant = false,
+            )
             notifyListeners(newScene, keyguardState)
-            communalSceneRepository.changeScene(newScene, transitionKey)
+            repository.changeScene(newScene, transitionKey)
         }
     }
 
     /** Immediately snaps to the new scene. */
     fun snapToScene(
         newScene: SceneKey,
+        loggingReason: String,
         delayMillis: Long = 0,
         keyguardState: KeyguardState? = null
     ) {
         applicationScope.launch("$TAG#snapToScene") {
             delay(delayMillis)
+            logger.logSceneChangeRequested(
+                from = currentScene.value,
+                to = newScene,
+                reason = loggingReason,
+                isInstant = true,
+            )
             notifyListeners(newScene, keyguardState)
-            communalSceneRepository.snapToScene(newScene)
+            repository.snapToScene(newScene)
         }
     }
 
@@ -113,13 +130,30 @@
         if (_editModeState.value == EditModeState.STARTING) {
             return
         }
-        changeScene(CommunalScenes.Blank, CommunalTransitionKeys.SimpleFade)
+        changeScene(
+            CommunalScenes.Blank,
+            "activity start dismissing keyguard",
+            CommunalTransitionKeys.SimpleFade,
+        )
     }
 
     /**
      * Target scene as requested by the underlying [SceneTransitionLayout] or through [changeScene].
      */
-    val currentScene: Flow<SceneKey> = communalSceneRepository.currentScene
+    val currentScene: StateFlow<SceneKey> =
+        repository.currentScene
+            .pairwiseBy(initialValue = repository.currentScene.value) { from, to ->
+                logger.logSceneChangeCommitted(
+                    from = from,
+                    to = to,
+                )
+                to
+            }
+            .stateIn(
+                scope = applicationScope,
+                started = SharingStarted.Eagerly,
+                initialValue = repository.currentScene.value,
+            )
 
     private val _editModeState = MutableStateFlow<EditModeState?>(null)
     /**
@@ -134,7 +168,13 @@
 
     /** Transition state of the hub mode. */
     val transitionState: StateFlow<ObservableTransitionState> =
-        communalSceneRepository.transitionState
+        repository.transitionState
+            .onEach { logger.logSceneTransition(it) }
+            .stateIn(
+                scope = applicationScope,
+                started = SharingStarted.Eagerly,
+                initialValue = repository.transitionState.value,
+            )
 
     /**
      * Updates the transition state of the hub [SceneTransitionLayout].
@@ -142,7 +182,7 @@
      * Note that you must call is with `null` when the UI is done or risk a memory leak.
      */
     fun setTransitionState(transitionState: Flow<ObservableTransitionState>?) {
-        communalSceneRepository.setTransitionState(transitionState)
+        repository.setTransitionState(transitionState)
     }
 
     /** Returns a flow that tracks the progress of transitions to the given scene from 0-1. */
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 c780aac..6343752 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
@@ -41,6 +41,8 @@
 import kotlinx.coroutines.flow.SharingStarted
 import kotlinx.coroutines.flow.StateFlow
 import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.filterNotNull
+import kotlinx.coroutines.flow.onStart
 import kotlinx.coroutines.flow.stateIn
 import kotlinx.coroutines.launch
 
@@ -85,25 +87,29 @@
      */
     private val nextKeyguardStateInternal =
         combine(
-            keyguardInteractor.isAbleToDream,
-            keyguardInteractor.isKeyguardOccluded,
-            keyguardInteractor.isKeyguardGoingAway,
-        ) { dreaming, occluded, keyguardGoingAway ->
-            if (keyguardGoingAway) {
-                KeyguardState.GONE
-            } else if (occluded && !dreaming) {
-                KeyguardState.OCCLUDED
-            } else if (dreaming) {
-                KeyguardState.DREAMING
-            } else {
-                KeyguardState.LOCKSCREEN
+                keyguardInteractor.isAbleToDream,
+                keyguardInteractor.isKeyguardOccluded,
+                keyguardInteractor.isKeyguardGoingAway,
+                keyguardInteractor.isKeyguardShowing,
+            ) { dreaming, occluded, keyguardGoingAway, keyguardShowing ->
+                if (keyguardGoingAway) {
+                    KeyguardState.GONE
+                } else if (occluded && !dreaming) {
+                    KeyguardState.OCCLUDED
+                } else if (dreaming) {
+                    KeyguardState.DREAMING
+                } else if (keyguardShowing) {
+                    KeyguardState.LOCKSCREEN
+                } else {
+                    null
+                }
             }
-        }
+            .filterNotNull()
 
     private val nextKeyguardState: StateFlow<KeyguardState> =
         combine(
                 repository.nextLockscreenTargetState,
-                nextKeyguardStateInternal,
+                nextKeyguardStateInternal.onStart { emit(KeyguardState.LOCKSCREEN) },
             ) { override, nextState ->
                 override ?: nextState
             }
diff --git a/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/WidgetTrampolineInteractor.kt b/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/WidgetTrampolineInteractor.kt
new file mode 100644
index 0000000..7453368
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/WidgetTrampolineInteractor.kt
@@ -0,0 +1,140 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.communal.domain.interactor
+
+import android.app.ActivityManager
+import com.android.systemui.common.usagestats.domain.UsageStatsInteractor
+import com.android.systemui.common.usagestats.shared.model.ActivityEventModel
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
+import com.android.systemui.keyguard.shared.model.KeyguardState
+import com.android.systemui.log.LogBuffer
+import com.android.systemui.log.core.Logger
+import com.android.systemui.log.dagger.CommunalLog
+import com.android.systemui.plugins.ActivityStarter
+import com.android.systemui.scene.shared.model.Scenes
+import com.android.systemui.shared.system.TaskStackChangeListener
+import com.android.systemui.shared.system.TaskStackChangeListeners
+import com.android.systemui.util.kotlin.race
+import com.android.systemui.util.time.SystemClock
+import javax.inject.Inject
+import kotlin.time.Duration.Companion.milliseconds
+import kotlin.time.Duration.Companion.seconds
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.TimeoutCancellationException
+import kotlinx.coroutines.delay
+import kotlinx.coroutines.flow.takeWhile
+import kotlinx.coroutines.suspendCancellableCoroutine
+import kotlinx.coroutines.withTimeout
+
+/**
+ * Detects activity starts that occur while the communal hub is showing, within a short delay of a
+ * widget interaction occurring. Used for detecting non-activity trampolines which otherwise would
+ * not prompt the user for authentication.
+ */
+@OptIn(ExperimentalCoroutinesApi::class)
+@SysUISingleton
+class WidgetTrampolineInteractor
+@Inject
+constructor(
+    private val activityStarter: ActivityStarter,
+    private val systemClock: SystemClock,
+    private val keyguardTransitionInteractor: KeyguardTransitionInteractor,
+    private val taskStackChangeListeners: TaskStackChangeListeners,
+    private val usageStatsInteractor: UsageStatsInteractor,
+    @CommunalLog logBuffer: LogBuffer,
+) {
+    private companion object {
+        const val TAG = "WidgetTrampolineInteractor"
+    }
+
+    private val logger = Logger(logBuffer, TAG)
+
+    /** Waits for a new task to be moved to the foreground. */
+    private suspend fun waitForNewForegroundTask() = suspendCancellableCoroutine { cont ->
+        val listener =
+            object : TaskStackChangeListener {
+                override fun onTaskMovedToFront(taskInfo: ActivityManager.RunningTaskInfo) {
+                    if (!cont.isCompleted) {
+                        cont.resume(Unit, null)
+                    }
+                }
+            }
+        taskStackChangeListeners.registerTaskStackListener(listener)
+        cont.invokeOnCancellation { taskStackChangeListeners.unregisterTaskStackListener(listener) }
+    }
+
+    /**
+     * Waits for an activity to enter a [ActivityEventModel.Lifecycle.RESUMED] state by periodically
+     * polling the system to see if any activities have started.
+     */
+    private suspend fun waitForActivityStartByPolling(startTime: Long): Boolean {
+        while (true) {
+            val events = usageStatsInteractor.queryActivityEvents(startTime = startTime)
+            if (events.any { event -> event.lifecycle == ActivityEventModel.Lifecycle.RESUMED }) {
+                return true
+            } else {
+                // Poll again in the future to check if an activity started.
+                delay(200.milliseconds)
+            }
+        }
+    }
+
+    /** Waits for a transition away from the hub to occur. */
+    private suspend fun waitForTransitionAwayFromHub() {
+        keyguardTransitionInteractor
+            .isFinishedIn(Scenes.Communal, KeyguardState.GLANCEABLE_HUB)
+            .takeWhile { it }
+            .collect {}
+    }
+
+    private suspend fun waitForActivityStartWhileOnHub(): Boolean {
+        val startTime = systemClock.currentTimeMillis()
+        return try {
+            return withTimeout(1.seconds) {
+                race(
+                    {
+                        waitForNewForegroundTask()
+                        true
+                    },
+                    { waitForActivityStartByPolling(startTime) },
+                    {
+                        waitForTransitionAwayFromHub()
+                        false
+                    },
+                )
+            }
+        } catch (e: TimeoutCancellationException) {
+            false
+        }
+    }
+
+    /**
+     * Checks if an activity starts while on the glanceable hub and dismisses the keyguard if it
+     * does. This can detect activities started due to broadcast trampolines from widgets.
+     */
+    suspend fun waitForActivityStartAndDismissKeyguard() {
+        if (waitForActivityStartWhileOnHub()) {
+            logger.d("Detected trampoline, requesting unlock")
+            activityStarter.dismissKeyguardThenExecute(
+                /* action= */ { false },
+                /* cancel= */ null,
+                /* afterKeyguardGone= */ false
+            )
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/communal/shared/log/CommunalSceneLogger.kt b/packages/SystemUI/src/com/android/systemui/communal/shared/log/CommunalSceneLogger.kt
new file mode 100644
index 0000000..aed9215
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/communal/shared/log/CommunalSceneLogger.kt
@@ -0,0 +1,97 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.communal.shared.log
+
+import com.android.compose.animation.scene.ObservableTransitionState
+import com.android.compose.animation.scene.SceneKey
+import com.android.systemui.log.LogBuffer
+import com.android.systemui.log.core.LogLevel
+import com.android.systemui.log.dagger.CommunalLog
+import javax.inject.Inject
+
+class CommunalSceneLogger @Inject constructor(@CommunalLog private val logBuffer: LogBuffer) {
+
+    fun logSceneChangeRequested(
+        from: SceneKey,
+        to: SceneKey,
+        reason: String,
+        isInstant: Boolean,
+    ) {
+        logBuffer.log(
+            tag = TAG,
+            level = LogLevel.INFO,
+            messageInitializer = {
+                str1 = from.toString()
+                str2 = to.toString()
+                str3 = reason
+                bool1 = isInstant
+            },
+            messagePrinter = {
+                buildString {
+                    append("Scene change requested: $str1 → $str2")
+                    if (isInstant) {
+                        append(" (instant)")
+                    }
+                    append(", reason: $str3")
+                }
+            },
+        )
+    }
+
+    fun logSceneChangeCommitted(
+        from: SceneKey,
+        to: SceneKey,
+    ) {
+        logBuffer.log(
+            tag = TAG,
+            level = LogLevel.INFO,
+            messageInitializer = {
+                str1 = from.toString()
+                str2 = to.toString()
+            },
+            messagePrinter = { "Scene change committed: $str1 → $str2" },
+        )
+    }
+
+    fun logSceneTransition(transitionState: ObservableTransitionState) {
+        when (transitionState) {
+            is ObservableTransitionState.Transition -> {
+                logBuffer.log(
+                    tag = TAG,
+                    level = LogLevel.INFO,
+                    messageInitializer = {
+                        str1 = transitionState.fromScene.toString()
+                        str2 = transitionState.toScene.toString()
+                    },
+                    messagePrinter = { "Scene transition started: $str1 → $str2" },
+                )
+            }
+            is ObservableTransitionState.Idle -> {
+                logBuffer.log(
+                    tag = TAG,
+                    level = LogLevel.INFO,
+                    messageInitializer = { str1 = transitionState.currentScene.toString() },
+                    messagePrinter = { "Scene transition idle on: $str1" },
+                )
+            }
+        }
+    }
+
+    companion object {
+        private const val TAG = "CommunalSceneLogger"
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/communal/smartspace/SmartspaceInteractionHandler.kt b/packages/SystemUI/src/com/android/systemui/communal/smartspace/SmartspaceInteractionHandler.kt
index c4edcac..99e3232 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/smartspace/SmartspaceInteractionHandler.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/smartspace/SmartspaceInteractionHandler.kt
@@ -48,7 +48,17 @@
         InteractionHandlerDelegate(
             communalSceneInteractor,
             findViewToAnimate = { view -> view is SmartspaceAppWidgetHostView },
-            intentStarter = this::startIntent,
+            intentStarter =
+                object : InteractionHandlerDelegate.IntentStarter {
+                    override fun startActivity(
+                        intent: PendingIntent,
+                        fillInIntent: Intent,
+                        activityOptions: ActivityOptions,
+                        controller: ActivityTransitionAnimator.Controller?
+                    ): Boolean {
+                        return startIntent(intent, fillInIntent, activityOptions, controller)
+                    }
+                },
             logger = Logger(logBuffer, TAG),
         )
 
diff --git a/packages/SystemUI/src/com/android/systemui/communal/ui/binder/CommunalAppWidgetHostViewBinder.kt b/packages/SystemUI/src/com/android/systemui/communal/ui/binder/CommunalAppWidgetHostViewBinder.kt
index 7a05671..5f421fd 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/ui/binder/CommunalAppWidgetHostViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/ui/binder/CommunalAppWidgetHostViewBinder.kt
@@ -21,10 +21,13 @@
 import android.view.View
 import android.view.ViewGroup
 import android.widget.FrameLayout
+import androidx.core.view.doOnLayout
 import com.android.app.tracing.coroutines.launch
 import com.android.systemui.communal.domain.model.CommunalContentModel
 import com.android.systemui.communal.util.WidgetViewFactory
 import com.android.systemui.util.kotlin.DisposableHandles
+import kotlin.coroutines.resume
+import kotlin.coroutines.suspendCoroutine
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.DisposableHandle
 
@@ -44,13 +47,8 @@
         val loadingJob =
             applicationScope.launch("$TAG#createWidgetView") {
                 val widget = factory.createWidget(context, model, size)
-                // TODO(b/358662507): Remove this workaround
-                (container.parent as? ViewGroup)?.let { parent ->
-                    val index = parent.indexOfChild(container)
-                    parent.removeView(container)
-                    parent.addView(container, index)
-                }
-                container.setView(widget)
+                waitForLayout(container)
+                container.post { container.setView(widget) }
             }
 
         disposables += DisposableHandle { loadingJob.cancel() }
@@ -58,6 +56,10 @@
 
         return disposables
     }
+
+    private suspend fun waitForLayout(container: FrameLayout) = suspendCoroutine { cont ->
+        container.doOnLayout { cont.resume(Unit) }
+    }
 }
 
 private fun ViewGroup.setView(view: View) {
diff --git a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/BaseCommunalViewModel.kt b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/BaseCommunalViewModel.kt
index d1a5a4b..b822133 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
@@ -106,10 +106,11 @@
      */
     fun changeScene(
         scene: SceneKey,
+        loggingReason: String,
         transitionKey: TransitionKey? = null,
         keyguardState: KeyguardState? = null
     ) {
-        communalSceneInteractor.changeScene(scene, transitionKey, keyguardState)
+        communalSceneInteractor.changeScene(scene, loggingReason, transitionKey, keyguardState)
     }
 
     fun setEditModeState(state: EditModeState?) = communalSceneInteractor.setEditModeState(state)
diff --git a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalTransitionViewModel.kt
index bbd8596..6239373 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalTransitionViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/ui/viewmodel/CommunalTransitionViewModel.kt
@@ -67,7 +67,10 @@
      * transition.
      */
     fun snapToCommunal() {
-        communalSceneInteractor.snapToScene(CommunalScenes.Communal)
+        communalSceneInteractor.snapToScene(
+            newScene = CommunalScenes.Communal,
+            loggingReason = "transition view model",
+        )
     }
 
     // Show UMO on glanceable hub immediately on transition into glanceable hub
diff --git a/packages/SystemUI/src/com/android/systemui/communal/util/InteractionHandlerDelegate.kt b/packages/SystemUI/src/com/android/systemui/communal/util/InteractionHandlerDelegate.kt
index d2029d5..5e21afa 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/util/InteractionHandlerDelegate.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/util/InteractionHandlerDelegate.kt
@@ -19,6 +19,7 @@
 import android.app.ActivityOptions
 import android.app.PendingIntent
 import android.content.Intent
+import android.util.Pair as UtilPair
 import android.view.View
 import android.widget.RemoteViews
 import androidx.core.util.component1
@@ -36,14 +37,28 @@
     private val logger: Logger,
 ) : RemoteViews.InteractionHandler {
 
-    /** Responsible for starting the pending intent for launching activities. */
-    fun interface IntentStarter {
-        fun startPendingIntent(
+    interface IntentStarter {
+        /** Responsible for starting the pending intent for launching activities. */
+        fun startActivity(
             intent: PendingIntent,
             fillInIntent: Intent,
             activityOptions: ActivityOptions,
             controller: ActivityTransitionAnimator.Controller?,
         ): Boolean
+
+        /** Responsible for starting the pending intent for non-activity launches. */
+        fun startPendingIntent(
+            view: View,
+            pendingIntent: PendingIntent,
+            fillInIntent: Intent,
+            activityOptions: ActivityOptions,
+        ): Boolean {
+            return RemoteViews.startPendingIntent(
+                view,
+                pendingIntent,
+                UtilPair(fillInIntent, activityOptions),
+            )
+        }
     }
 
     override fun onInteraction(
@@ -55,7 +70,7 @@
             str1 = pendingIntent.toLoggingString()
             str2 = pendingIntent.creatorPackage
         }
-        val launchOptions = response.getLaunchOptions(view)
+        val (fillInIntent, activityOptions) = response.getLaunchOptions(view)
         return when {
             pendingIntent.isActivity -> {
                 // Forward the fill-in intent and activity options retrieved from the response
@@ -67,15 +82,15 @@
                         communalSceneInteractor.setIsLaunchingWidget(true)
                         CommunalTransitionAnimatorController(it, communalSceneInteractor)
                     }
-                val (fillInIntent, activityOptions) = launchOptions
-                intentStarter.startPendingIntent(
+                intentStarter.startActivity(
                     pendingIntent,
                     fillInIntent,
                     activityOptions,
                     animationController
                 )
             }
-            else -> RemoteViews.startPendingIntent(view, pendingIntent, launchOptions)
+            else ->
+                intentStarter.startPendingIntent(view, pendingIntent, fillInIntent, activityOptions)
         }
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/communal/widgets/CommunalTransitionAnimatorController.kt b/packages/SystemUI/src/com/android/systemui/communal/widgets/CommunalTransitionAnimatorController.kt
index 0844462..e7cedc6 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/widgets/CommunalTransitionAnimatorController.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/widgets/CommunalTransitionAnimatorController.kt
@@ -42,6 +42,7 @@
         // TODO(b/330672236): move this to onTransitionAnimationEnd() without the delay.
         communalSceneInteractor.snapToScene(
             CommunalScenes.Blank,
+            "CommunalTransitionAnimatorController",
             ActivityTransitionAnimator.TIMINGS.totalDuration
         )
     }
diff --git a/packages/SystemUI/src/com/android/systemui/communal/widgets/EditWidgetsActivity.kt b/packages/SystemUI/src/com/android/systemui/communal/widgets/EditWidgetsActivity.kt
index 668fef6..b421e59 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/widgets/EditWidgetsActivity.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/widgets/EditWidgetsActivity.kt
@@ -16,10 +16,7 @@
 
 package com.android.systemui.communal.widgets
 
-import android.app.Activity
-import android.app.Application.ActivityLifecycleCallbacks
 import android.content.Intent
-import android.content.IntentSender
 import android.os.Bundle
 import android.os.RemoteException
 import android.util.Log
@@ -71,78 +68,12 @@
         const val EXTRA_OPEN_WIDGET_PICKER_ON_START = "open_widget_picker_on_start"
     }
 
-    /**
-     * [ActivityController] handles closing the activity in the case it is backgrounded without
-     * waiting for an activity result
-     */
-    class ActivityController(activity: Activity) {
-        companion object {
-            private const val STATE_EXTRA_IS_WAITING_FOR_RESULT = "extra_is_waiting_for_result"
-        }
-
-        private var waitingForResult: Boolean = false
-
-        init {
-            activity.registerActivityLifecycleCallbacks(
-                object : ActivityLifecycleCallbacks {
-                    override fun onActivityCreated(
-                        activity: Activity,
-                        savedInstanceState: Bundle?
-                    ) {
-                        waitingForResult =
-                            savedInstanceState?.getBoolean(STATE_EXTRA_IS_WAITING_FOR_RESULT)
-                                ?: false
-                    }
-
-                    override fun onActivityStarted(activity: Activity) {
-                        // Nothing to implement.
-                    }
-
-                    override fun onActivityResumed(activity: Activity) {
-                        // Nothing to implement.
-                    }
-
-                    override fun onActivityPaused(activity: Activity) {
-                        // Nothing to implement.
-                    }
-
-                    override fun onActivityStopped(activity: Activity) {
-                        // If we're not backgrounded due to waiting for a resul (either widget
-                        // selection
-                        // or configuration), finish activity.
-                        if (!waitingForResult) {
-                            activity.finish()
-                        }
-                    }
-
-                    override fun onActivitySaveInstanceState(activity: Activity, outState: Bundle) {
-                        outState.putBoolean(STATE_EXTRA_IS_WAITING_FOR_RESULT, waitingForResult)
-                    }
-
-                    override fun onActivityDestroyed(activity: Activity) {
-                        // Nothing to implement.
-                    }
-                }
-            )
-        }
-
-        /**
-         * Invoked when waiting for an activity result changes, either initiating such wait or
-         * finishing due to the return of a result.
-         */
-        fun onWaitingForResult(waitingForResult: Boolean) {
-            this.waitingForResult = waitingForResult
-        }
-    }
-
     private val logger = Logger(logBuffer, "EditWidgetsActivity")
 
     private val widgetConfigurator by lazy { widgetConfiguratorFactory.create(this) }
 
     private var shouldOpenWidgetPickerOnStart = false
 
-    private val activityController: ActivityController = ActivityController(this)
-
     private val addWidgetActivityLauncher: ActivityResultLauncher<Intent> =
         registerForActivityResult(StartActivityForResult()) { result ->
             when (result.resultCode) {
@@ -218,9 +149,10 @@
         lifecycleScope.launch {
             communalViewModel.canShowEditMode.collect {
                 communalViewModel.changeScene(
-                    CommunalScenes.Blank,
-                    CommunalTransitionKeys.ToEditMode,
-                    KeyguardState.GONE,
+                    scene = CommunalScenes.Blank,
+                    loggingReason = "edit mode opening",
+                    transitionKey = CommunalTransitionKeys.ToEditMode,
+                    keyguardState = KeyguardState.GONE,
                 )
                 // wait till transitioned to Blank scene, then animate in communal content in
                 // edit mode
@@ -252,8 +184,9 @@
             communalViewModel.cleanupEditModeState()
 
             communalViewModel.changeScene(
-                CommunalScenes.Communal,
-                CommunalTransitionKeys.FromEditMode
+                scene = CommunalScenes.Communal,
+                loggingReason = "edit mode closing",
+                transitionKey = CommunalTransitionKeys.FromEditMode
             )
 
             // Wait for the current scene to be idle on communal.
@@ -265,34 +198,7 @@
         }
     }
 
-    override fun startActivityForResult(intent: Intent, requestCode: Int, options: Bundle?) {
-        activityController.onWaitingForResult(true)
-        super.startActivityForResult(intent, requestCode, options)
-    }
-
-    override fun startIntentSenderForResult(
-        intent: IntentSender,
-        requestCode: Int,
-        fillInIntent: Intent?,
-        flagsMask: Int,
-        flagsValues: Int,
-        extraFlags: Int,
-        options: Bundle?
-    ) {
-        activityController.onWaitingForResult(true)
-        super.startIntentSenderForResult(
-            intent,
-            requestCode,
-            fillInIntent,
-            flagsMask,
-            flagsValues,
-            extraFlags,
-            options
-        )
-    }
-
     override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
-        activityController.onWaitingForResult(false)
         super.onActivityResult(requestCode, resultCode, data)
         if (requestCode == WidgetConfigurationController.REQUEST_CODE) {
             widgetConfigurator.setConfigurationResult(resultCode)
diff --git a/packages/SystemUI/src/com/android/systemui/communal/widgets/WidgetInteractionHandler.kt b/packages/SystemUI/src/com/android/systemui/communal/widgets/WidgetInteractionHandler.kt
index 0eeb506..542b988 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/widgets/WidgetInteractionHandler.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/widgets/WidgetInteractionHandler.kt
@@ -21,22 +21,35 @@
 import android.content.Intent
 import android.view.View
 import android.widget.RemoteViews
+import com.android.app.tracing.coroutines.launch
+import com.android.keyguard.KeyguardUpdateMonitor
+import com.android.systemui.Flags.communalWidgetTrampolineFix
 import com.android.systemui.animation.ActivityTransitionAnimator
 import com.android.systemui.communal.domain.interactor.CommunalSceneInteractor
+import com.android.systemui.communal.domain.interactor.WidgetTrampolineInteractor
 import com.android.systemui.communal.util.InteractionHandlerDelegate
 import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.dagger.qualifiers.UiBackground
 import com.android.systemui.log.LogBuffer
 import com.android.systemui.log.core.Logger
 import com.android.systemui.log.dagger.CommunalLog
 import com.android.systemui.plugins.ActivityStarter
 import javax.inject.Inject
+import kotlin.coroutines.CoroutineContext
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Job
 
 @SysUISingleton
 class WidgetInteractionHandler
 @Inject
 constructor(
+    @Application private val applicationScope: CoroutineScope,
+    @UiBackground private val uiBackgroundContext: CoroutineContext,
     private val activityStarter: ActivityStarter,
+    private val keyguardUpdateMonitor: KeyguardUpdateMonitor,
     communalSceneInteractor: CommunalSceneInteractor,
+    private val widgetTrampolineInteractor: WidgetTrampolineInteractor,
     @CommunalLog val logBuffer: LogBuffer,
 ) : RemoteViews.InteractionHandler {
 
@@ -48,7 +61,52 @@
         InteractionHandlerDelegate(
             communalSceneInteractor,
             findViewToAnimate = { view -> view is CommunalAppWidgetHostView },
-            intentStarter = this::startIntent,
+            intentStarter =
+                object : InteractionHandlerDelegate.IntentStarter {
+                    private var job: Job? = null
+
+                    override fun startActivity(
+                        intent: PendingIntent,
+                        fillInIntent: Intent,
+                        activityOptions: ActivityOptions,
+                        controller: ActivityTransitionAnimator.Controller?
+                    ): Boolean {
+                        cancelTrampolineMonitoring()
+                        return startActivityIntent(
+                            intent,
+                            fillInIntent,
+                            activityOptions,
+                            controller
+                        )
+                    }
+
+                    override fun startPendingIntent(
+                        view: View,
+                        pendingIntent: PendingIntent,
+                        fillInIntent: Intent,
+                        activityOptions: ActivityOptions
+                    ): Boolean {
+                        cancelTrampolineMonitoring()
+                        if (communalWidgetTrampolineFix()) {
+                            job =
+                                applicationScope.launch("$TAG#monitorForActivityStart") {
+                                    widgetTrampolineInteractor
+                                        .waitForActivityStartAndDismissKeyguard()
+                                }
+                        }
+                        return super.startPendingIntent(
+                            view,
+                            pendingIntent,
+                            fillInIntent,
+                            activityOptions
+                        )
+                    }
+
+                    private fun cancelTrampolineMonitoring() {
+                        job?.cancel()
+                        job = null
+                    }
+                },
             logger = Logger(logBuffer, TAG),
         )
 
@@ -58,7 +116,7 @@
         response: RemoteViews.RemoteResponse
     ): Boolean = delegate.onInteraction(view, pendingIntent, response)
 
-    private fun startIntent(
+    private fun startActivityIntent(
         pendingIntent: PendingIntent,
         fillInIntent: Intent,
         extraOptions: ActivityOptions,
@@ -67,7 +125,14 @@
         activityStarter.startPendingIntentMaybeDismissingKeyguard(
             pendingIntent,
             /* dismissShade = */ false,
-            /* intentSentUiThreadCallback = */ null,
+            {
+                applicationScope.launch("$TAG#awakenFromDream", uiBackgroundContext) {
+                    // This activity could have started while the device is dreaming, in which case
+                    // the dream would occlude the activity. In order to show the newly started
+                    // activity, we wake from the dream.
+                    keyguardUpdateMonitor.awakenFromDream()
+                }
+            },
             controller,
             fillInIntent,
             extraOptions.toBundle(),
diff --git a/packages/SystemUI/src/com/android/systemui/controls/management/ControlsListingControllerImpl.kt b/packages/SystemUI/src/com/android/systemui/controls/management/ControlsListingControllerImpl.kt
index 74e1dc0..a5f29aa 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/management/ControlsListingControllerImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/management/ControlsListingControllerImpl.kt
@@ -38,31 +38,35 @@
 import com.android.systemui.util.asIndenting
 import com.android.systemui.util.indentIfPossible
 import java.io.PrintWriter
+import java.util.concurrent.CopyOnWriteArraySet
 import java.util.concurrent.Executor
 import java.util.concurrent.atomic.AtomicInteger
 import javax.inject.Inject
 
 private fun createServiceListing(context: Context): ServiceListing {
-    return ServiceListing.Builder(context).apply {
-        setIntentAction(ControlsProviderService.SERVICE_CONTROLS)
-        setPermission("android.permission.BIND_CONTROLS")
-        setNoun("Controls Provider")
-        setSetting("controls_providers")
-        setTag("controls_providers")
-        setAddDeviceLockedFlags(true)
-    }.build()
+    return ServiceListing.Builder(context)
+        .apply {
+            setIntentAction(ControlsProviderService.SERVICE_CONTROLS)
+            setPermission("android.permission.BIND_CONTROLS")
+            setNoun("Controls Provider")
+            setSetting("controls_providers")
+            setTag("controls_providers")
+            setAddDeviceLockedFlags(true)
+        }
+        .build()
 }
 
 /**
  * Provides a listing of components to be used as ControlsServiceProvider.
  *
  * This controller keeps track of components that satisfy:
- *
  * * Has an intent-filter responding to [ControlsProviderService.CONTROLS_ACTION]
  * * Has the bind permission `android.permission.BIND_CONTROLS`
  */
 @SysUISingleton
-class ControlsListingControllerImpl @VisibleForTesting constructor(
+class ControlsListingControllerImpl
+@VisibleForTesting
+constructor(
     private val context: Context,
     @Background private val backgroundExecutor: Executor,
     private val serviceListingBuilder: (Context) -> ServiceListing,
@@ -74,12 +78,12 @@
 
     @Inject
     constructor(
-            context: Context,
-            @Background executor: Executor,
-            userTracker: UserTracker,
-            activityTaskManagerProxy: ActivityTaskManagerProxy,
-            dumpManager: DumpManager,
-            featureFlags: FeatureFlags
+        context: Context,
+        @Background executor: Executor,
+        userTracker: UserTracker,
+        activityTaskManagerProxy: ActivityTaskManagerProxy,
+        dumpManager: DumpManager,
+        featureFlags: FeatureFlags
     ) : this(
         context,
         executor,
@@ -92,7 +96,7 @@
 
     private var serviceListing = serviceListingBuilder(context)
     // All operations in background thread
-    private val callbacks = mutableSetOf<ControlsListingController.ControlsListingCallback>()
+    private val callbacks = CopyOnWriteArraySet<ControlsListingController.ControlsListingCallback>()
 
     companion object {
         private const val TAG = "ControlsListingControllerImpl"
@@ -104,15 +108,17 @@
     override var currentUserId = userTracker.userId
         private set
 
-    private val serviceListingCallback = ServiceListing.Callback { list ->
-        Log.d(TAG, "ServiceConfig reloaded, count: ${list.size}")
-        val newServices = list.map { ControlsServiceInfo(userTracker.userContext, it) }
-        // After here, `list` is not captured, so we don't risk modifying it outside of the callback
-        backgroundExecutor.execute {
-            if (userChangeInProgress.get() > 0) return@execute
-            updateServices(newServices)
+    private val serviceListingCallback =
+        ServiceListing.Callback { list ->
+            Log.d(TAG, "ServiceConfig reloaded, count: ${list.size}")
+            val newServices = list.map { ControlsServiceInfo(userTracker.userContext, it) }
+            // After here, `list` is not captured, so we don't risk modifying it outside of the
+            // callback
+            backgroundExecutor.execute {
+                if (userChangeInProgress.get() > 0) return@execute
+                updateServices(newServices)
+            }
         }
-    }
 
     init {
         Log.d(TAG, "Initializing")
@@ -124,15 +130,12 @@
 
     private fun updateServices(newServices: List<ControlsServiceInfo>) {
         if (activityTaskManagerProxy.supportsMultiWindow(context)) {
-            newServices.forEach {
-                it.resolvePanelActivity() }
+            newServices.forEach { it.resolvePanelActivity() }
         }
 
         if (newServices != availableServices) {
             availableServices = newServices
-            callbacks.forEach {
-                it.onServicesUpdated(getCurrentServices())
-            }
+            callbacks.forEach { it.onServicesUpdated(getCurrentServices()) }
         }
     }
 
@@ -155,8 +158,8 @@
     /**
      * Adds a callback to this controller.
      *
-     * The callback will be notified after it is added as well as any time that the valid
-     * components change.
+     * The callback will be notified after it is added as well as any time that the valid components
+     * change.
      *
      * @param listener a callback to be notified
      */
@@ -188,26 +191,29 @@
     }
 
     /**
-     * @return a list of components that satisfy the requirements to be a
-     *         [ControlsProviderService]
+     * @return a list of components that satisfy the requirements to be a [ControlsProviderService]
      */
     override fun getCurrentServices(): List<ControlsServiceInfo> =
-            availableServices.map(ControlsServiceInfo::copy)
+        availableServices.map(ControlsServiceInfo::copy)
 
     @WorkerThread
     override fun forceReload() {
         val packageManager = context.packageManager
         val intent = Intent(ControlsProviderService.SERVICE_CONTROLS)
         val user = userTracker.userHandle
-        val flags = PackageManager.GET_SERVICES or
+        val flags =
+            PackageManager.GET_SERVICES or
                 PackageManager.GET_META_DATA or
                 PackageManager.MATCH_DIRECT_BOOT_UNAWARE or
                 PackageManager.MATCH_DIRECT_BOOT_AWARE
-        val services = packageManager.queryIntentServicesAsUser(
-                intent,
-                PackageManager.ResolveInfoFlags.of(flags.toLong()),
-                user
-        ).map { ControlsServiceInfo(userTracker.userContext, it.serviceInfo) }
+        val services =
+            packageManager
+                .queryIntentServicesAsUser(
+                    intent,
+                    PackageManager.ResolveInfoFlags.of(flags.toLong()),
+                    user
+                )
+                .map { ControlsServiceInfo(userTracker.userContext, it.serviceInfo) }
         updateServices(services)
     }
 
@@ -218,8 +224,7 @@
      * @return a label as returned by [CandidateInfo.loadLabel] or `null`.
      */
     override fun getAppLabel(name: ComponentName): CharSequence? {
-        return availableServices.firstOrNull { it.componentName == name }
-                ?.loadLabel()
+        return availableServices.firstOrNull { it.componentName == name }?.loadLabel()
     }
 
     override fun dump(writer: PrintWriter, args: Array<out String>) {
diff --git a/packages/SystemUI/src/com/android/systemui/display/domain/interactor/ConnectedDisplayInteractor.kt b/packages/SystemUI/src/com/android/systemui/display/domain/interactor/ConnectedDisplayInteractor.kt
index e4b290d..15a3cbd 100644
--- a/packages/SystemUI/src/com/android/systemui/display/domain/interactor/ConnectedDisplayInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/display/domain/interactor/ConnectedDisplayInteractor.kt
@@ -17,7 +17,6 @@
 package com.android.systemui.display.domain.interactor
 
 import android.companion.virtual.VirtualDeviceManager
-import android.companion.virtual.flags.Flags
 import android.view.Display
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Background
@@ -155,8 +154,7 @@
     }
 
     private fun isVirtualDeviceOwnedMirrorDisplay(display: Display): Boolean {
-        return Flags.interactiveScreenMirror() &&
-            virtualDeviceManager != null &&
+        return virtualDeviceManager != null &&
             virtualDeviceManager.isVirtualDeviceOwnedMirrorDisplay(display.displayId)
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayService.java b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayService.java
index 4b9e5a0..0c1fb72 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayService.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/DreamOverlayService.java
@@ -442,7 +442,9 @@
     @Override
     public void onWakeRequested() {
         mUiEventLogger.log(CommunalUiEvent.DREAM_TO_COMMUNAL_HUB_DREAM_AWAKE_START);
-        mCommunalInteractor.changeScene(CommunalScenes.Communal, null);
+        mCommunalInteractor.changeScene(CommunalScenes.Communal,
+                "dream wake requested",
+                null);
     }
 
     private Lifecycle.State getLifecycleStateLocked() {
@@ -493,7 +495,7 @@
         mSystemDialogsCloser.closeSystemDialogs();
 
         // Hide glanceable hub (this is a nop if glanceable hub is not open).
-        mCommunalInteractor.changeScene(CommunalScenes.Blank, null);
+        mCommunalInteractor.changeScene(CommunalScenes.Blank, "dream come to front", null);
     }
 
     /**
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/ui/viewmodel/DreamViewModel.kt b/packages/SystemUI/src/com/android/systemui/dreams/ui/viewmodel/DreamViewModel.kt
index 4b07f78..5c0335a6 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/ui/viewmodel/DreamViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/dreams/ui/viewmodel/DreamViewModel.kt
@@ -20,9 +20,9 @@
 import com.android.systemui.Flags.glanceableHubAllowKeyguardWhenDreaming
 import com.android.systemui.common.ui.domain.interactor.ConfigurationInteractor
 import com.android.systemui.communal.domain.interactor.CommunalInteractor
-import com.android.systemui.communal.shared.model.CommunalScenes
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dump.DumpManager
+import com.android.systemui.keyguard.domain.interactor.FromDreamingTransitionInteractor
 import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
 import com.android.systemui.keyguard.shared.model.Edge
 import com.android.systemui.keyguard.shared.model.KeyguardState.DREAMING
@@ -51,6 +51,7 @@
     fromGlanceableHubTransitionInteractor: GlanceableHubToDreamingTransitionViewModel,
     toGlanceableHubTransitionViewModel: DreamingToGlanceableHubTransitionViewModel,
     private val toLockscreenTransitionViewModel: DreamingToLockscreenTransitionViewModel,
+    private val fromDreamingTransitionInteractor: FromDreamingTransitionInteractor,
     private val communalInteractor: CommunalInteractor,
     private val keyguardUpdateMonitor: KeyguardUpdateMonitor,
     private val userTracker: UserTracker,
@@ -61,11 +62,9 @@
         val showGlanceableHub =
             communalInteractor.isCommunalEnabled.value &&
                 !keyguardUpdateMonitor.isEncryptedOrLockdown(userTracker.userId)
-        if (showGlanceableHub && !glanceableHubAllowKeyguardWhenDreaming()) {
-            communalInteractor.changeScene(CommunalScenes.Communal)
-        } else {
-            toLockscreenTransitionViewModel.startTransition()
-        }
+        fromDreamingTransitionInteractor.startToLockscreenOrGlanceableHubTransition(
+            showGlanceableHub && !glanceableHubAllowKeyguardWhenDreaming()
+        )
     }
 
     val dreamOverlayTranslationX: Flow<Float> =
diff --git a/packages/SystemUI/src/com/android/systemui/education/data/model/GestureEduModel.kt b/packages/SystemUI/src/com/android/systemui/education/data/model/GestureEduModel.kt
index a171f87..1daaa11 100644
--- a/packages/SystemUI/src/com/android/systemui/education/data/model/GestureEduModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/education/data/model/GestureEduModel.kt
@@ -28,4 +28,5 @@
     val lastShortcutTriggeredTime: Instant? = null,
     val usageSessionStartTime: Instant? = null,
     val lastEducationTime: Instant? = null,
+    val userId: Int
 )
diff --git a/packages/SystemUI/src/com/android/systemui/education/data/repository/UserContextualEducationRepository.kt b/packages/SystemUI/src/com/android/systemui/education/data/repository/UserContextualEducationRepository.kt
index 7c3d6338..4fd79d7 100644
--- a/packages/SystemUI/src/com/android/systemui/education/data/repository/UserContextualEducationRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/education/data/repository/UserContextualEducationRepository.kt
@@ -33,6 +33,7 @@
 import java.time.Instant
 import javax.inject.Inject
 import javax.inject.Provider
+import kotlin.properties.Delegates.notNull
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.cancel
 import kotlinx.coroutines.flow.Flow
@@ -79,6 +80,8 @@
         const val DATASTORE_DIR = "education/USER%s_ContextualEducation"
     }
 
+    private var userId by notNull<Int>()
+
     private var dataStoreScope: CoroutineScope? = null
 
     private val datastore = MutableStateFlow<DataStore<Preferences>?>(null)
@@ -89,6 +92,7 @@
     override fun setUser(userId: Int) {
         dataStoreScope?.cancel()
         val newDsScope = dataStoreScopeProvider.get()
+        this.userId = userId
         datastore.value =
             PreferenceDataStoreFactory.create(
                 produceFile = {
@@ -123,6 +127,7 @@
                 preferences[getLastEducationTimeKey(gestureType)]?.let {
                     Instant.ofEpochSecond(it)
                 },
+            userId = userId
         )
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/education/domain/interactor/KeyboardTouchpadEduInteractor.kt b/packages/SystemUI/src/com/android/systemui/education/domain/interactor/KeyboardTouchpadEduInteractor.kt
index 3a3fb8c..ad3335b 100644
--- a/packages/SystemUI/src/com/android/systemui/education/domain/interactor/KeyboardTouchpadEduInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/education/domain/interactor/KeyboardTouchpadEduInteractor.kt
@@ -56,7 +56,7 @@
                 if (isUsageSessionExpired(it)) {
                     contextualEducationInteractor.startNewUsageSession(BACK)
                 } else if (isEducationNeeded(it)) {
-                    _educationTriggered.value = EducationInfo(BACK, getEduType(it))
+                    _educationTriggered.value = EducationInfo(BACK, getEduType(it), it.userId)
                     contextualEducationInteractor.updateOnEduTriggered(BACK)
                 }
             }
diff --git a/packages/SystemUI/src/com/android/systemui/education/shared/model/EducationInfo.kt b/packages/SystemUI/src/com/android/systemui/education/shared/model/EducationInfo.kt
index d92fb9b..27c41cff 100644
--- a/packages/SystemUI/src/com/android/systemui/education/shared/model/EducationInfo.kt
+++ b/packages/SystemUI/src/com/android/systemui/education/shared/model/EducationInfo.kt
@@ -22,7 +22,11 @@
  * Model for education triggered. [gestureType] indicates what gesture it is trying to educate about
  * and [educationUiType] is how we educate user in the UI
  */
-data class EducationInfo(val gestureType: GestureType, val educationUiType: EducationUiType)
+data class EducationInfo(
+    val gestureType: GestureType,
+    val educationUiType: EducationUiType,
+    val userId: Int
+)
 
 enum class EducationUiType {
     Toast,
diff --git a/packages/SystemUI/src/com/android/systemui/education/ui/view/ContextualEduUiCoordinator.kt b/packages/SystemUI/src/com/android/systemui/education/ui/view/ContextualEduUiCoordinator.kt
index b446ea2..e62b26b 100644
--- a/packages/SystemUI/src/com/android/systemui/education/ui/view/ContextualEduUiCoordinator.kt
+++ b/packages/SystemUI/src/com/android/systemui/education/ui/view/ContextualEduUiCoordinator.kt
@@ -16,14 +16,24 @@
 
 package com.android.systemui.education.ui.view
 
+import android.app.Notification
+import android.app.NotificationChannel
+import android.app.NotificationManager
+import android.app.PendingIntent
 import android.content.Context
+import android.content.Intent
+import android.os.Bundle
+import android.os.UserHandle
 import android.widget.Toast
+import androidx.core.app.NotificationCompat
 import com.android.systemui.CoreStartable
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Application
-import com.android.systemui.education.shared.model.EducationUiType
-import com.android.systemui.education.ui.viewmodel.ContextualEduContentViewModel
+import com.android.systemui.education.ui.viewmodel.ContextualEduNotificationViewModel
+import com.android.systemui.education.ui.viewmodel.ContextualEduToastViewModel
 import com.android.systemui.education.ui.viewmodel.ContextualEduViewModel
+import com.android.systemui.inputdevice.tutorial.ui.view.KeyboardTouchpadTutorialActivity
+import com.android.systemui.res.R
 import javax.inject.Inject
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.launch
@@ -37,32 +47,96 @@
 constructor(
     @Application private val applicationScope: CoroutineScope,
     private val viewModel: ContextualEduViewModel,
+    private val context: Context,
+    private val notificationManager: NotificationManager,
     private val createToast: (String) -> Toast
 ) : CoreStartable {
 
+    companion object {
+        private const val CHANNEL_ID = "ContextualEduNotificationChannel"
+        private const val TAG = "ContextualEduUiCoordinator"
+        private const val NOTIFICATION_ID = 1000
+    }
+
     @Inject
     constructor(
         @Application applicationScope: CoroutineScope,
         context: Context,
         viewModel: ContextualEduViewModel,
+        notificationManager: NotificationManager,
     ) : this(
         applicationScope,
         viewModel,
+        context,
+        notificationManager,
         createToast = { message -> Toast.makeText(context, message, Toast.LENGTH_LONG) }
     )
 
     override fun start() {
+        createEduNotificationChannel()
         applicationScope.launch {
             viewModel.eduContent.collect { contentModel ->
-                if (contentModel.type == EducationUiType.Toast) {
-                    showToast(contentModel)
+                when (contentModel) {
+                    is ContextualEduToastViewModel -> showToast(contentModel)
+                    is ContextualEduNotificationViewModel -> showNotification(contentModel)
                 }
             }
         }
     }
 
-    private fun showToast(model: ContextualEduContentViewModel) {
+    private fun createEduNotificationChannel() {
+        val channel =
+            NotificationChannel(
+                CHANNEL_ID,
+                context.getString(com.android.internal.R.string.android_system_label),
+                // Make it as silent notification
+                NotificationManager.IMPORTANCE_LOW
+            )
+        notificationManager.createNotificationChannel(channel)
+    }
+
+    private fun showToast(model: ContextualEduToastViewModel) {
         val toast = createToast(model.message)
         toast.show()
     }
+
+    private fun showNotification(model: ContextualEduNotificationViewModel) {
+        // Replace "System UI" app name with "Android System"
+        val extras = Bundle()
+        extras.putString(
+            Notification.EXTRA_SUBSTITUTE_APP_NAME,
+            context.getString(com.android.internal.R.string.android_system_label)
+        )
+
+        val notification =
+            NotificationCompat.Builder(context, CHANNEL_ID)
+                .setSmallIcon(R.drawable.ic_settings)
+                .setContentTitle(model.title)
+                .setContentText(model.message)
+                .setContentIntent(createPendingIntent())
+                .setPriority(NotificationCompat.PRIORITY_DEFAULT)
+                .setAutoCancel(true)
+                .addExtras(extras)
+                .build()
+        notificationManager.notifyAsUser(
+            TAG,
+            NOTIFICATION_ID,
+            notification,
+            UserHandle.of(model.userId)
+        )
+    }
+
+    private fun createPendingIntent(): PendingIntent {
+        val intent =
+            Intent(context, KeyboardTouchpadTutorialActivity::class.java).apply {
+                addCategory(Intent.CATEGORY_DEFAULT)
+                flags = Intent.FLAG_ACTIVITY_NEW_TASK
+            }
+        return PendingIntent.getActivity(
+            context,
+            /* requestCode= */ 0,
+            intent,
+            PendingIntent.FLAG_IMMUTABLE
+        )
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/education/ui/viewmodel/ContextualEduContentViewModel.kt b/packages/SystemUI/src/com/android/systemui/education/ui/viewmodel/ContextualEduContentViewModel.kt
index 3cba4c8..632b250 100644
--- a/packages/SystemUI/src/com/android/systemui/education/ui/viewmodel/ContextualEduContentViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/education/ui/viewmodel/ContextualEduContentViewModel.kt
@@ -16,6 +16,13 @@
 
 package com.android.systemui.education.ui.viewmodel
 
-import com.android.systemui.education.shared.model.EducationUiType
+sealed class ContextualEduContentViewModel(open val userId: Int)
 
-data class ContextualEduContentViewModel(val message: String, val type: EducationUiType)
+data class ContextualEduNotificationViewModel(
+    val title: String,
+    val message: String,
+    override val userId: Int
+) : ContextualEduContentViewModel(userId)
+
+data class ContextualEduToastViewModel(val message: String, override val userId: Int) :
+    ContextualEduContentViewModel(userId)
diff --git a/packages/SystemUI/src/com/android/systemui/education/ui/viewmodel/ContextualEduViewModel.kt b/packages/SystemUI/src/com/android/systemui/education/ui/viewmodel/ContextualEduViewModel.kt
index 58276e0..cd4a8ad 100644
--- a/packages/SystemUI/src/com/android/systemui/education/ui/viewmodel/ContextualEduViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/education/ui/viewmodel/ContextualEduViewModel.kt
@@ -17,11 +17,15 @@
 package com.android.systemui.education.ui.viewmodel
 
 import android.content.res.Resources
-import com.android.systemui.contextualeducation.GestureType
+import com.android.systemui.contextualeducation.GestureType.ALL_APPS
+import com.android.systemui.contextualeducation.GestureType.BACK
+import com.android.systemui.contextualeducation.GestureType.HOME
+import com.android.systemui.contextualeducation.GestureType.OVERVIEW
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Main
 import com.android.systemui.education.domain.interactor.KeyboardTouchpadEduInteractor
 import com.android.systemui.education.shared.model.EducationInfo
+import com.android.systemui.education.shared.model.EducationUiType
 import com.android.systemui.res.R
 import javax.inject.Inject
 import kotlinx.coroutines.flow.Flow
@@ -34,18 +38,43 @@
 constructor(@Main private val resources: Resources, interactor: KeyboardTouchpadEduInteractor) {
     val eduContent: Flow<ContextualEduContentViewModel> =
         interactor.educationTriggered.filterNotNull().map {
-            ContextualEduContentViewModel(getEduContent(it), it.educationUiType)
+            if (it.educationUiType == EducationUiType.Notification) {
+                ContextualEduNotificationViewModel(getEduTitle(it), getEduContent(it), it.userId)
+            } else {
+                ContextualEduToastViewModel(getEduContent(it), it.userId)
+            }
         }
 
     private fun getEduContent(educationInfo: EducationInfo): String {
-        // Todo: also check UiType in educationInfo to determine the string
+        val resourceId =
+            if (educationInfo.educationUiType == EducationUiType.Notification) {
+                when (educationInfo.gestureType) {
+                    BACK -> R.string.back_edu_notification_content
+                    HOME -> R.string.home_edu_notification_content
+                    OVERVIEW -> R.string.overview_edu_notification_content
+                    ALL_APPS -> R.string.all_apps_edu_notification_content
+                }
+            } else {
+                when (educationInfo.gestureType) {
+                    BACK -> R.string.back_edu_toast_content
+                    HOME -> R.string.home_edu_toast_content
+                    OVERVIEW -> R.string.overview_edu_toast_content
+                    ALL_APPS -> R.string.all_apps_edu_toast_content
+                }
+            }
+
+        return resources.getString(resourceId)
+    }
+
+    private fun getEduTitle(educationInfo: EducationInfo): String {
         val resourceId =
             when (educationInfo.gestureType) {
-                GestureType.BACK -> R.string.back_edu_toast_content
-                GestureType.HOME -> R.string.home_edu_toast_content
-                GestureType.OVERVIEW -> R.string.overview_edu_toast_content
-                GestureType.ALL_APPS -> R.string.all_apps_edu_toast_content
+                BACK -> R.string.back_edu_notification_title
+                HOME -> R.string.home_edu_notification_title
+                OVERVIEW -> R.string.overview_edu_notification_title
+                ALL_APPS -> R.string.all_apps_edu_notification_title
             }
+
         return resources.getString(resourceId)
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/inputdevice/oobe/ui/view/KeyboardTouchpadTutorialActivity.kt b/packages/SystemUI/src/com/android/systemui/inputdevice/oobe/ui/view/KeyboardTouchpadTutorialActivity.kt
deleted file mode 100644
index 3e382d6..0000000
--- a/packages/SystemUI/src/com/android/systemui/inputdevice/oobe/ui/view/KeyboardTouchpadTutorialActivity.kt
+++ /dev/null
@@ -1,74 +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.inputdevice.tutorial.ui.view
-
-import android.os.Bundle
-import android.view.WindowManager
-import androidx.activity.ComponentActivity
-import androidx.activity.compose.setContent
-import androidx.activity.enableEdgeToEdge
-import androidx.activity.viewModels
-import androidx.compose.runtime.Composable
-import com.android.compose.theme.PlatformTheme
-import com.android.systemui.inputdevice.tutorial.TouchpadTutorialScreensProvider
-import com.android.systemui.inputdevice.tutorial.ui.viewmodel.KeyboardTouchpadTutorialViewModel
-import java.util.Optional
-import javax.inject.Inject
-
-/**
- * Activity for out of the box experience for keyboard and touchpad. Note that it's possible that
- * either of them are actually not connected when this is launched
- */
-class KeyboardTouchpadTutorialActivity
-@Inject
-constructor(
-    private val viewModelFactory: KeyboardTouchpadTutorialViewModel.Factory,
-    private val touchpadTutorialScreensProvider: Optional<TouchpadTutorialScreensProvider>,
-) : ComponentActivity() {
-
-    private val vm by
-        viewModels<KeyboardTouchpadTutorialViewModel>(factoryProducer = { viewModelFactory })
-
-    override fun onCreate(savedInstanceState: Bundle?) {
-        super.onCreate(savedInstanceState)
-        enableEdgeToEdge()
-        setContent {
-            PlatformTheme {
-                KeyboardTouchpadTutorialContainer(vm, touchpadTutorialScreensProvider) { finish() }
-            }
-        }
-        // required to handle 3+ fingers on touchpad
-        window.addPrivateFlags(WindowManager.LayoutParams.PRIVATE_FLAG_TRUSTED_OVERLAY)
-    }
-
-    override fun onResume() {
-        super.onResume()
-        vm.onOpened()
-    }
-
-    override fun onPause() {
-        super.onPause()
-        vm.onClosed()
-    }
-}
-
-@Composable
-fun KeyboardTouchpadTutorialContainer(
-    vm: KeyboardTouchpadTutorialViewModel,
-    touchpadTutorialScreensProvider: Optional<TouchpadTutorialScreensProvider>,
-    closeTutorial: () -> Unit
-) {}
diff --git a/packages/SystemUI/src/com/android/systemui/inputdevice/oobe/ui/viewmodel/KeyboardTouchpadTutorialViewModel.kt b/packages/SystemUI/src/com/android/systemui/inputdevice/oobe/ui/viewmodel/KeyboardTouchpadTutorialViewModel.kt
deleted file mode 100644
index 39b1ec0..0000000
--- a/packages/SystemUI/src/com/android/systemui/inputdevice/oobe/ui/viewmodel/KeyboardTouchpadTutorialViewModel.kt
+++ /dev/null
@@ -1,62 +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.inputdevice.tutorial.ui.viewmodel
-
-import androidx.lifecycle.ViewModel
-import androidx.lifecycle.ViewModelProvider
-import com.android.systemui.touchpad.tutorial.domain.interactor.TouchpadGesturesInteractor
-import java.util.Optional
-import javax.inject.Inject
-import kotlinx.coroutines.flow.MutableStateFlow
-import kotlinx.coroutines.flow.StateFlow
-
-class KeyboardTouchpadTutorialViewModel(
-    private val gesturesInteractor: Optional<TouchpadGesturesInteractor>
-) : ViewModel() {
-
-    private val _screen = MutableStateFlow(Screen.BACK_GESTURE)
-    val screen: StateFlow<Screen> = _screen
-
-    fun goTo(screen: Screen) {
-        _screen.value = screen
-    }
-
-    fun onOpened() {
-        gesturesInteractor.ifPresent { it.disableGestures() }
-    }
-
-    fun onClosed() {
-        gesturesInteractor.ifPresent { it.enableGestures() }
-    }
-
-    class Factory
-    @Inject
-    constructor(private val gesturesInteractor: Optional<TouchpadGesturesInteractor>) :
-        ViewModelProvider.Factory {
-
-        @Suppress("UNCHECKED_CAST")
-        override fun <T : ViewModel> create(modelClass: Class<T>): T {
-            return KeyboardTouchpadTutorialViewModel(gesturesInteractor) as T
-        }
-    }
-}
-
-enum class Screen {
-    BACK_GESTURE,
-    HOME_GESTURE,
-    ACTION_KEY
-}
diff --git a/packages/SystemUI/src/com/android/systemui/inputdevice/oobe/KeyboardTouchpadTutorialModule.kt b/packages/SystemUI/src/com/android/systemui/inputdevice/tutorial/KeyboardTouchpadTutorialModule.kt
similarity index 100%
rename from packages/SystemUI/src/com/android/systemui/inputdevice/oobe/KeyboardTouchpadTutorialModule.kt
rename to packages/SystemUI/src/com/android/systemui/inputdevice/tutorial/KeyboardTouchpadTutorialModule.kt
diff --git a/packages/SystemUI/src/com/android/systemui/inputdevice/oobe/TouchpadTutorialScreensProvider.kt b/packages/SystemUI/src/com/android/systemui/inputdevice/tutorial/TouchpadTutorialScreensProvider.kt
similarity index 100%
rename from packages/SystemUI/src/com/android/systemui/inputdevice/oobe/TouchpadTutorialScreensProvider.kt
rename to packages/SystemUI/src/com/android/systemui/inputdevice/tutorial/TouchpadTutorialScreensProvider.kt
diff --git a/packages/SystemUI/src/com/android/systemui/inputdevice/tutorial/data/model/TutorialSchedulerInfo.kt b/packages/SystemUI/src/com/android/systemui/inputdevice/tutorial/data/model/TutorialSchedulerInfo.kt
index 9f46846..1dbe83a 100644
--- a/packages/SystemUI/src/com/android/systemui/inputdevice/tutorial/data/model/TutorialSchedulerInfo.kt
+++ b/packages/SystemUI/src/com/android/systemui/inputdevice/tutorial/data/model/TutorialSchedulerInfo.kt
@@ -16,7 +16,23 @@
 
 package com.android.systemui.inputdevice.tutorial.data.model
 
-data class DeviceSchedulerInfo(var isLaunched: Boolean = false, var connectTime: Long? = null) {
+import java.time.Instant
+
+data class DeviceSchedulerInfo(
+    var launchTime: Instant? = null,
+    var firstConnectionTime: Instant? = null
+) {
+    constructor(
+        launchTimeSec: Long?,
+        firstConnectionTimeSec: Long?
+    ) : this(
+        launchTimeSec?.let { Instant.ofEpochSecond(it) },
+        firstConnectionTimeSec?.let { Instant.ofEpochSecond(it) }
+    )
+
     val wasEverConnected: Boolean
-        get() = connectTime != null
+        get() = firstConnectionTime != null
+
+    val isLaunched: Boolean
+        get() = launchTime != null
 }
diff --git a/packages/SystemUI/src/com/android/systemui/inputdevice/tutorial/data/repository/TutorialSchedulerRepository.kt b/packages/SystemUI/src/com/android/systemui/inputdevice/tutorial/data/repository/TutorialSchedulerRepository.kt
index b9b3895..d8d4bd6 100644
--- a/packages/SystemUI/src/com/android/systemui/inputdevice/tutorial/data/repository/TutorialSchedulerRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/inputdevice/tutorial/data/repository/TutorialSchedulerRepository.kt
@@ -17,9 +17,9 @@
 package com.android.systemui.inputdevice.tutorial.data.repository
 
 import android.content.Context
+import androidx.annotation.VisibleForTesting
 import androidx.datastore.core.DataStore
 import androidx.datastore.preferences.core.Preferences
-import androidx.datastore.preferences.core.booleanPreferencesKey
 import androidx.datastore.preferences.core.edit
 import androidx.datastore.preferences.core.longPreferencesKey
 import androidx.datastore.preferences.preferencesDataStore
@@ -27,39 +27,47 @@
 import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.dagger.qualifiers.Background
 import com.android.systemui.inputdevice.tutorial.data.model.DeviceSchedulerInfo
+import java.time.Instant
 import javax.inject.Inject
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.flow.first
 import kotlinx.coroutines.flow.map
 
 @SysUISingleton
-class TutorialSchedulerRepository
-@Inject
-constructor(
-    @Application private val applicationContext: Context,
-    @Background private val backgroundScope: CoroutineScope
+class TutorialSchedulerRepository(
+    private val applicationContext: Context,
+    backgroundScope: CoroutineScope,
+    dataStoreName: String
 ) {
+    @Inject
+    constructor(
+        @Application applicationContext: Context,
+        @Background backgroundScope: CoroutineScope
+    ) : this(applicationContext, backgroundScope, dataStoreName = DATASTORE_NAME)
 
     private val Context.dataStore: DataStore<Preferences> by
-        preferencesDataStore(name = DATASTORE_NAME, scope = backgroundScope)
+        preferencesDataStore(name = dataStoreName, scope = backgroundScope)
 
     suspend fun isLaunched(deviceType: DeviceType): Boolean = loadData()[deviceType]!!.isLaunched
 
+    suspend fun launchTime(deviceType: DeviceType): Instant? = loadData()[deviceType]!!.launchTime
+
     suspend fun wasEverConnected(deviceType: DeviceType): Boolean =
         loadData()[deviceType]!!.wasEverConnected
 
-    suspend fun connectTime(deviceType: DeviceType): Long = loadData()[deviceType]!!.connectTime!!
+    suspend fun firstConnectionTime(deviceType: DeviceType): Instant? =
+        loadData()[deviceType]!!.firstConnectionTime
 
     private suspend fun loadData(): Map<DeviceType, DeviceSchedulerInfo> {
         return applicationContext.dataStore.data.map { pref -> getSchedulerInfo(pref) }.first()
     }
 
-    suspend fun updateConnectTime(device: DeviceType, time: Long) {
-        applicationContext.dataStore.edit { pref -> pref[getConnectKey(device)] = time }
+    suspend fun updateFirstConnectionTime(device: DeviceType, time: Instant) {
+        applicationContext.dataStore.edit { pref -> pref[getConnectKey(device)] = time.epochSecond }
     }
 
-    suspend fun updateLaunch(device: DeviceType) {
-        applicationContext.dataStore.edit { pref -> pref[getLaunchedKey(device)] = true }
+    suspend fun updateLaunchTime(device: DeviceType, time: Instant) {
+        applicationContext.dataStore.edit { pref -> pref[getLaunchKey(device)] = time.epochSecond }
     }
 
     private fun getSchedulerInfo(pref: Preferences): Map<DeviceType, DeviceSchedulerInfo> {
@@ -70,21 +78,26 @@
     }
 
     private fun getDeviceSchedulerInfo(pref: Preferences, device: DeviceType): DeviceSchedulerInfo {
-        val isLaunched = pref[getLaunchedKey(device)] ?: false
-        val connectionTime = pref[getConnectKey(device)] ?: null
-        return DeviceSchedulerInfo(isLaunched, connectionTime)
+        val launchTime = pref[getLaunchKey(device)]
+        val connectionTime = pref[getConnectKey(device)]
+        return DeviceSchedulerInfo(launchTime, connectionTime)
     }
 
-    private fun getLaunchedKey(device: DeviceType) =
-        booleanPreferencesKey(device.name + IS_LAUNCHED_SUFFIX)
+    private fun getLaunchKey(device: DeviceType) =
+        longPreferencesKey(device.name + LAUNCH_TIME_SUFFIX)
 
     private fun getConnectKey(device: DeviceType) =
         longPreferencesKey(device.name + CONNECT_TIME_SUFFIX)
 
+    @VisibleForTesting
+    suspend fun clearDataStore() {
+        applicationContext.dataStore.edit { it.clear() }
+    }
+
     companion object {
         const val DATASTORE_NAME = "TutorialScheduler"
-        const val IS_LAUNCHED_SUFFIX = "_IS_LAUNCHED"
-        const val CONNECT_TIME_SUFFIX = "_CONNECTED_TIME"
+        const val LAUNCH_TIME_SUFFIX = "_LAUNCH_TIME"
+        const val CONNECT_TIME_SUFFIX = "_CONNECT_TIME"
     }
 }
 
diff --git a/packages/SystemUI/src/com/android/systemui/inputdevice/tutorial/domain/interactor/KeyboardTouchpadConnectionInteractor.kt b/packages/SystemUI/src/com/android/systemui/inputdevice/tutorial/domain/interactor/KeyboardTouchpadConnectionInteractor.kt
new file mode 100644
index 0000000..3f1f68a
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/inputdevice/tutorial/domain/interactor/KeyboardTouchpadConnectionInteractor.kt
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.inputdevice.tutorial.domain.interactor
+
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.keyboard.data.repository.KeyboardRepository
+import com.android.systemui.touchpad.data.repository.TouchpadRepository
+import javax.inject.Inject
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.combine
+
+@SysUISingleton
+class KeyboardTouchpadConnectionInteractor
+@Inject
+constructor(
+    keyboardRepository: KeyboardRepository,
+    touchpadRepository: TouchpadRepository,
+) {
+
+    val connectionState: Flow<ConnectionState> =
+        combine(
+            keyboardRepository.isAnyKeyboardConnected,
+            touchpadRepository.isAnyTouchpadConnected
+        ) { keyboardConnected, touchpadConnected ->
+            ConnectionState(keyboardConnected, touchpadConnected)
+        }
+}
+
+data class ConnectionState(val keyboardConnected: Boolean, val touchpadConnected: Boolean)
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 b3b8f21..a8d7dad 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
@@ -26,9 +26,11 @@
 import com.android.systemui.inputdevice.tutorial.data.repository.TutorialSchedulerRepository
 import com.android.systemui.keyboard.data.repository.KeyboardRepository
 import com.android.systemui.touchpad.data.repository.TouchpadRepository
+import java.time.Duration
 import java.time.Instant
 import javax.inject.Inject
 import kotlin.time.Duration.Companion.hours
+import kotlin.time.toKotlinDuration
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.delay
 import kotlinx.coroutines.flow.filter
@@ -84,9 +86,9 @@
     private suspend fun schedule(deviceType: DeviceType) {
         if (!repo.wasEverConnected(deviceType)) {
             waitForDeviceConnection(deviceType)
-            repo.updateConnectTime(deviceType, Instant.now().toEpochMilli())
+            repo.updateFirstConnectionTime(deviceType, Instant.now())
         }
-        delay(remainingTimeMillis(start = repo.connectTime(deviceType)))
+        delay(remainingTime(start = repo.firstConnectionTime(deviceType)!!))
         waitForDeviceConnection(deviceType)
     }
 
@@ -95,9 +97,9 @@
 
     private suspend fun launchTutorial(tutorialType: TutorialType) {
         if (tutorialType == TutorialType.KEYBOARD || tutorialType == TutorialType.BOTH)
-            repo.updateLaunch(KEYBOARD)
+            repo.updateLaunchTime(KEYBOARD, Instant.now())
         if (tutorialType == TutorialType.TOUCHPAD || tutorialType == TutorialType.BOTH)
-            repo.updateLaunch(TOUCHPAD)
+            repo.updateLaunchTime(TOUCHPAD, Instant.now())
         // TODO: launch tutorial
         Log.d(TAG, "Launch tutorial for $tutorialType")
     }
@@ -113,19 +115,21 @@
         return if (deviceType == KEYBOARD) TutorialType.KEYBOARD else TutorialType.TOUCHPAD
     }
 
-    private fun remainingTimeMillis(start: Long): Long {
-        val elapsed = Instant.now().toEpochMilli() - start
-        return LAUNCH_DELAY - elapsed
+    private fun remainingTime(start: Instant): kotlin.time.Duration {
+        val elapsed = Duration.between(start, Instant.now())
+        return LAUNCH_DELAY.minus(elapsed).toKotlinDuration()
     }
 
     companion object {
         const val TAG = "TutorialSchedulerInteractor"
-        private val DEFAULT_LAUNCH_DELAY = 72.hours.inWholeMilliseconds
-        private val LAUNCH_DELAY: Long
+        private val DEFAULT_LAUNCH_DELAY_SEC = 72.hours.inWholeSeconds
+        private val LAUNCH_DELAY: Duration
             get() =
-                SystemProperties.getLong(
-                    "persist.peripheral_tutorial_delay_ms",
-                    DEFAULT_LAUNCH_DELAY
+                Duration.ofSeconds(
+                    SystemProperties.getLong(
+                        "persist.peripheral_tutorial_delay_sec",
+                        DEFAULT_LAUNCH_DELAY_SEC
+                    )
                 )
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/inputdevice/oobe/ui/composable/ActionKeyTutorialScreen.kt b/packages/SystemUI/src/com/android/systemui/inputdevice/tutorial/ui/composable/ActionKeyTutorialScreen.kt
similarity index 100%
rename from packages/SystemUI/src/com/android/systemui/inputdevice/oobe/ui/composable/ActionKeyTutorialScreen.kt
rename to packages/SystemUI/src/com/android/systemui/inputdevice/tutorial/ui/composable/ActionKeyTutorialScreen.kt
diff --git a/packages/SystemUI/src/com/android/systemui/inputdevice/oobe/ui/composable/ActionTutorialContent.kt b/packages/SystemUI/src/com/android/systemui/inputdevice/tutorial/ui/composable/ActionTutorialContent.kt
similarity index 100%
rename from packages/SystemUI/src/com/android/systemui/inputdevice/oobe/ui/composable/ActionTutorialContent.kt
rename to packages/SystemUI/src/com/android/systemui/inputdevice/tutorial/ui/composable/ActionTutorialContent.kt
diff --git a/packages/SystemUI/src/com/android/systemui/inputdevice/oobe/ui/composable/TutorialComponents.kt b/packages/SystemUI/src/com/android/systemui/inputdevice/tutorial/ui/composable/TutorialComponents.kt
similarity index 100%
rename from packages/SystemUI/src/com/android/systemui/inputdevice/oobe/ui/composable/TutorialComponents.kt
rename to packages/SystemUI/src/com/android/systemui/inputdevice/tutorial/ui/composable/TutorialComponents.kt
diff --git a/packages/SystemUI/src/com/android/systemui/inputdevice/oobe/ui/composable/TutorialScreenConfig.kt b/packages/SystemUI/src/com/android/systemui/inputdevice/tutorial/ui/composable/TutorialScreenConfig.kt
similarity index 100%
rename from packages/SystemUI/src/com/android/systemui/inputdevice/oobe/ui/composable/TutorialScreenConfig.kt
rename to packages/SystemUI/src/com/android/systemui/inputdevice/tutorial/ui/composable/TutorialScreenConfig.kt
diff --git a/packages/SystemUI/src/com/android/systemui/inputdevice/tutorial/ui/view/KeyboardTouchpadTutorialActivity.kt b/packages/SystemUI/src/com/android/systemui/inputdevice/tutorial/ui/view/KeyboardTouchpadTutorialActivity.kt
new file mode 100644
index 0000000..34ecc95
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/inputdevice/tutorial/ui/view/KeyboardTouchpadTutorialActivity.kt
@@ -0,0 +1,106 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.inputdevice.tutorial.ui.view
+
+import android.os.Bundle
+import android.view.WindowManager
+import androidx.activity.ComponentActivity
+import androidx.activity.compose.setContent
+import androidx.activity.enableEdgeToEdge
+import androidx.activity.viewModels
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.getValue
+import androidx.lifecycle.Lifecycle.State.STARTED
+import androidx.lifecycle.compose.collectAsStateWithLifecycle
+import androidx.lifecycle.lifecycleScope
+import com.android.compose.theme.PlatformTheme
+import com.android.systemui.inputdevice.tutorial.TouchpadTutorialScreensProvider
+import com.android.systemui.inputdevice.tutorial.ui.composable.ActionKeyTutorialScreen
+import com.android.systemui.inputdevice.tutorial.ui.viewmodel.KeyboardTouchpadTutorialViewModel
+import com.android.systemui.inputdevice.tutorial.ui.viewmodel.KeyboardTouchpadTutorialViewModel.Factory.ViewModelFactoryAssistedProvider
+import com.android.systemui.inputdevice.tutorial.ui.viewmodel.Screen.ACTION_KEY
+import com.android.systemui.inputdevice.tutorial.ui.viewmodel.Screen.BACK_GESTURE
+import com.android.systemui.inputdevice.tutorial.ui.viewmodel.Screen.HOME_GESTURE
+import java.util.Optional
+import javax.inject.Inject
+import kotlinx.coroutines.launch
+
+/**
+ * Activity for out of the box experience for keyboard and touchpad. Note that it's possible that
+ * either of them are actually not connected when this is launched
+ */
+class KeyboardTouchpadTutorialActivity
+@Inject
+constructor(
+    private val viewModelFactoryAssistedProvider: ViewModelFactoryAssistedProvider,
+    private val touchpadTutorialScreensProvider: Optional<TouchpadTutorialScreensProvider>,
+) : ComponentActivity() {
+
+    companion object {
+        const val INTENT_TUTORIAL_TYPE_KEY = "tutorial_type"
+        const val INTENT_TUTORIAL_TYPE_TOUCHPAD = "touchpad"
+        const val INTENT_TUTORIAL_TYPE_KEYBOARD = "keyboard"
+    }
+
+    private val vm by
+        viewModels<KeyboardTouchpadTutorialViewModel>(
+            factoryProducer = {
+                viewModelFactoryAssistedProvider.create(touchpadTutorialScreensProvider.isPresent)
+            }
+        )
+
+    override fun onCreate(savedInstanceState: Bundle?) {
+        super.onCreate(savedInstanceState)
+        enableEdgeToEdge()
+        // required to handle 3+ fingers on touchpad
+        window.addPrivateFlags(WindowManager.LayoutParams.PRIVATE_FLAG_TRUSTED_OVERLAY)
+        lifecycle.addObserver(vm)
+        lifecycleScope.launch {
+            vm.closeActivity.collect { finish ->
+                if (finish) {
+                    finish()
+                }
+            }
+        }
+        setContent {
+            PlatformTheme { KeyboardTouchpadTutorialContainer(vm, touchpadTutorialScreensProvider) }
+        }
+    }
+}
+
+@Composable
+fun KeyboardTouchpadTutorialContainer(
+    vm: KeyboardTouchpadTutorialViewModel,
+    touchpadScreens: Optional<TouchpadTutorialScreensProvider>,
+) {
+    val activeScreen by vm.screen.collectAsStateWithLifecycle(STARTED)
+    when (activeScreen) {
+        BACK_GESTURE ->
+            touchpadScreens
+                .get()
+                .BackGesture(onDoneButtonClicked = vm::onDoneButtonClicked, onBack = vm::onBack)
+        HOME_GESTURE ->
+            touchpadScreens
+                .get()
+                .HomeGesture(onDoneButtonClicked = vm::onDoneButtonClicked, onBack = vm::onBack)
+        ACTION_KEY ->
+            ActionKeyTutorialScreen(
+                onDoneButtonClicked = vm::onDoneButtonClicked,
+                onBack = vm::onBack
+            )
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/inputdevice/tutorial/ui/viewmodel/KeyboardTouchpadTutorialViewModel.kt b/packages/SystemUI/src/com/android/systemui/inputdevice/tutorial/ui/viewmodel/KeyboardTouchpadTutorialViewModel.kt
new file mode 100644
index 0000000..315c102
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/inputdevice/tutorial/ui/viewmodel/KeyboardTouchpadTutorialViewModel.kt
@@ -0,0 +1,205 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.inputdevice.tutorial.ui.viewmodel
+
+import androidx.lifecycle.AbstractSavedStateViewModelFactory
+import androidx.lifecycle.DefaultLifecycleObserver
+import androidx.lifecycle.LifecycleOwner
+import androidx.lifecycle.SavedStateHandle
+import androidx.lifecycle.ViewModel
+import androidx.lifecycle.viewModelScope
+import com.android.systemui.inputdevice.tutorial.domain.interactor.ConnectionState
+import com.android.systemui.inputdevice.tutorial.domain.interactor.KeyboardTouchpadConnectionInteractor
+import com.android.systemui.inputdevice.tutorial.ui.view.KeyboardTouchpadTutorialActivity.Companion.INTENT_TUTORIAL_TYPE_KEY
+import com.android.systemui.inputdevice.tutorial.ui.view.KeyboardTouchpadTutorialActivity.Companion.INTENT_TUTORIAL_TYPE_KEYBOARD
+import com.android.systemui.inputdevice.tutorial.ui.viewmodel.RequiredHardware.KEYBOARD
+import com.android.systemui.inputdevice.tutorial.ui.viewmodel.RequiredHardware.TOUCHPAD
+import com.android.systemui.inputdevice.tutorial.ui.viewmodel.Screen.ACTION_KEY
+import com.android.systemui.inputdevice.tutorial.ui.viewmodel.Screen.BACK_GESTURE
+import com.android.systemui.touchpad.tutorial.domain.interactor.TouchpadGesturesInteractor
+import dagger.assisted.Assisted
+import dagger.assisted.AssistedFactory
+import dagger.assisted.AssistedInject
+import java.util.Optional
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.filter
+import kotlinx.coroutines.flow.filterNot
+import kotlinx.coroutines.flow.runningFold
+import kotlinx.coroutines.launch
+
+class KeyboardTouchpadTutorialViewModel(
+    private val gesturesInteractor: Optional<TouchpadGesturesInteractor>,
+    private val keyboardTouchpadConnectionInteractor: KeyboardTouchpadConnectionInteractor,
+    private val hasTouchpadTutorialScreens: Boolean,
+    handle: SavedStateHandle
+) : ViewModel(), DefaultLifecycleObserver {
+
+    private fun startingScreen(handle: SavedStateHandle): Screen {
+        val tutorialType: String? = handle[INTENT_TUTORIAL_TYPE_KEY]
+        return if (tutorialType == INTENT_TUTORIAL_TYPE_KEYBOARD) ACTION_KEY else BACK_GESTURE
+    }
+
+    private val _screen = MutableStateFlow(startingScreen(handle))
+    val screen: Flow<Screen> = _screen.filter { it.canBeShown() }
+
+    private val _closeActivity: MutableStateFlow<Boolean> = MutableStateFlow(false)
+    val closeActivity: StateFlow<Boolean> = _closeActivity
+
+    private val screensBackStack = ArrayDeque(listOf(_screen.value))
+
+    private var connectionState: ConnectionState =
+        ConnectionState(keyboardConnected = false, touchpadConnected = false)
+
+    init {
+        viewModelScope.launch {
+            keyboardTouchpadConnectionInteractor.connectionState.collect { connectionState = it }
+        }
+
+        viewModelScope.launch {
+            screen
+                .runningFold<Screen, Pair<Screen?, Screen?>>(null to null) {
+                    previousScreensPair,
+                    currentScreen ->
+                    previousScreensPair.second to currentScreen
+                }
+                .collect { (previousScreen, currentScreen) ->
+                    // ignore first empty emission
+                    if (currentScreen != null) {
+                        setupDeviceState(previousScreen, currentScreen)
+                    }
+                }
+        }
+
+        viewModelScope.launch {
+            // close activity if screen requires touchpad but we don't have it. This can only happen
+            // when current sysui build doesn't contain touchpad module dependency
+            _screen.filterNot { it.canBeShown() }.collect { _closeActivity.value = true }
+        }
+    }
+
+    override fun onCleared() {
+        // this shouldn't be needed as onTutorialInvisible should already clear device state but
+        // it'd be really bad if we'd block gestures/shortcuts after leaving tutorial so just to be
+        // extra sure...
+        clearDeviceStateForScreen(_screen.value)
+    }
+
+    override fun onStart(owner: LifecycleOwner) {
+        setupDeviceState(previousScreen = null, currentScreen = _screen.value)
+    }
+
+    override fun onStop(owner: LifecycleOwner) {
+        clearDeviceStateForScreen(_screen.value)
+    }
+
+    fun onDoneButtonClicked() {
+        var nextScreen = _screen.value.next()
+        while (nextScreen != null) {
+            if (requiredHardwarePresent(nextScreen)) {
+                break
+            }
+            nextScreen = nextScreen.next()
+        }
+        if (nextScreen == null) {
+            _closeActivity.value = true
+        } else {
+            _screen.value = nextScreen
+            screensBackStack.add(nextScreen)
+        }
+    }
+
+    private fun Screen.canBeShown() = requiredHardware != TOUCHPAD || hasTouchpadTutorialScreens
+
+    private fun setupDeviceState(previousScreen: Screen?, currentScreen: Screen) {
+        if (previousScreen?.requiredHardware == currentScreen.requiredHardware) return
+        previousScreen?.let { clearDeviceStateForScreen(it) }
+        when (currentScreen.requiredHardware) {
+            TOUCHPAD -> gesturesInteractor.get().disableGestures()
+            KEYBOARD -> {} // TODO(b/358587037) disabled keyboard shortcuts
+        }
+    }
+
+    private fun clearDeviceStateForScreen(screen: Screen) {
+        when (screen.requiredHardware) {
+            TOUCHPAD -> gesturesInteractor.get().enableGestures()
+            KEYBOARD -> {} // TODO(b/358587037) enable keyboard shortcuts
+        }
+    }
+
+    private fun requiredHardwarePresent(screen: Screen): Boolean =
+        when (screen.requiredHardware) {
+            KEYBOARD -> connectionState.keyboardConnected
+            TOUCHPAD -> connectionState.touchpadConnected
+        }
+
+    fun onBack() {
+        if (screensBackStack.size <= 1) {
+            _closeActivity.value = true
+        } else {
+            screensBackStack.removeLast()
+            _screen.value = screensBackStack.last()
+        }
+    }
+
+    class Factory
+    @AssistedInject
+    constructor(
+        private val gesturesInteractor: Optional<TouchpadGesturesInteractor>,
+        private val keyboardTouchpadConnected: KeyboardTouchpadConnectionInteractor,
+        @Assisted private val hasTouchpadTutorialScreens: Boolean,
+    ) : AbstractSavedStateViewModelFactory() {
+
+        @AssistedFactory
+        fun interface ViewModelFactoryAssistedProvider {
+            fun create(@Assisted hasTouchpadTutorialScreens: Boolean): Factory
+        }
+
+        @Suppress("UNCHECKED_CAST")
+        override fun <T : ViewModel> create(
+            key: String,
+            modelClass: Class<T>,
+            handle: SavedStateHandle
+        ): T =
+            KeyboardTouchpadTutorialViewModel(
+                gesturesInteractor,
+                keyboardTouchpadConnected,
+                hasTouchpadTutorialScreens,
+                handle
+            )
+                as T
+    }
+}
+
+enum class RequiredHardware {
+    TOUCHPAD,
+    KEYBOARD
+}
+
+enum class Screen(val requiredHardware: RequiredHardware) {
+    BACK_GESTURE(requiredHardware = TOUCHPAD),
+    HOME_GESTURE(requiredHardware = TOUCHPAD),
+    ACTION_KEY(requiredHardware = KEYBOARD);
+
+    fun next(): Screen? =
+        when (this) {
+            BACK_GESTURE -> HOME_GESTURE
+            HOME_GESTURE -> ACTION_KEY
+            ACTION_KEY -> null
+        }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/NewPickerUiKeyguardPreview.kt b/packages/SystemUI/src/com/android/systemui/keyguard/NewPickerUiKeyguardPreview.kt
new file mode 100644
index 0000000..7e09a10
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/NewPickerUiKeyguardPreview.kt
@@ -0,0 +1,29 @@
+/*
+ * 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
+
+import com.android.systemui.Flags
+
+/** Helper for reading or using the new picker UI flag. */
+@Suppress("NOTHING_TO_INLINE")
+object NewPickerUiKeyguardPreview {
+
+    /** Is the new picker UI enabled */
+    @JvmStatic
+    inline val isEnabled
+        get() = Flags.newPickerUi()
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardQuickAffordanceRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardQuickAffordanceRepository.kt
index 698328e..d49550e 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardQuickAffordanceRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardQuickAffordanceRepository.kt
@@ -22,7 +22,6 @@
 import android.os.UserHandle
 import android.util.LayoutDirection
 import com.android.systemui.Dumpable
-import com.android.systemui.res.R
 import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging
 import com.android.systemui.common.coroutine.ConflatedCallbackFlow
 import com.android.systemui.dagger.SysUISingleton
@@ -35,6 +34,7 @@
 import com.android.systemui.keyguard.data.quickaffordance.KeyguardQuickAffordanceSelectionManager
 import com.android.systemui.keyguard.shared.model.KeyguardQuickAffordancePickerRepresentation
 import com.android.systemui.keyguard.shared.model.KeyguardSlotPickerRepresentation
+import com.android.systemui.res.R
 import com.android.systemui.settings.UserTracker
 import java.io.PrintWriter
 import javax.inject.Inject
@@ -61,10 +61,13 @@
     private val remoteUserSelectionManager: KeyguardQuickAffordanceRemoteUserSelectionManager,
     private val userTracker: UserTracker,
     legacySettingSyncer: KeyguardQuickAffordanceLegacySettingSyncer,
-    private val configs: Set<@JvmSuppressWildcards KeyguardQuickAffordanceConfig>,
+    configs: Set<@JvmSuppressWildcards KeyguardQuickAffordanceConfig>,
     dumpManager: DumpManager,
     userHandle: UserHandle,
 ) {
+    // Configs for all keyguard quick affordances, mapped by the quick affordance ID as key
+    private val configsByAffordanceId: Map<String, KeyguardQuickAffordanceConfig> =
+        configs.associateBy { it.key }
     private val userId: Flow<Int> =
         ConflatedCallbackFlow.conflatedCallbackFlow {
             val callback =
@@ -126,7 +129,7 @@
      */
     fun getCurrentSelections(slotId: String): List<KeyguardQuickAffordanceConfig> {
         val selections = selectionManager.value.getSelections().getOrDefault(slotId, emptyList())
-        return configs.filter { selections.contains(it.key) }
+        return configsByAffordanceId.values.filter { selections.contains(it.key) }
     }
 
     /**
@@ -159,7 +162,7 @@
      */
     suspend fun getAffordancePickerRepresentations():
         List<KeyguardQuickAffordancePickerRepresentation> {
-        return configs
+        return configsByAffordanceId.values
             .associateWith { config -> config.getPickerScreenState() }
             .filterNot { (_, pickerState) ->
                 pickerState is KeyguardQuickAffordanceConfig.PickerScreenState.UnavailableOnDevice
@@ -226,6 +229,11 @@
         }
     }
 
+    /** Get the config of a quick affordance. */
+    fun getConfig(quickAffordanceId: String): KeyguardQuickAffordanceConfig? {
+        return configsByAffordanceId[quickAffordanceId]
+    }
+
     private inner class Dumpster : Dumpable {
         override fun dump(pw: PrintWriter, args: Array<out String>) {
             val slotPickerRepresentations = getSlotPickerRepresentations()
@@ -246,7 +254,7 @@
                 pw.println("    $slotId$selectionText (capacity = $capacity)")
             }
             pw.println("Available affordances on device:")
-            configs.forEach { config ->
+            configsByAffordanceId.values.forEach { config ->
                 pw.println("    ${config.key} (\"${config.pickerName()}\")")
             }
         }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt
index 81b0064..49303e0 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardRepository.kt
@@ -91,11 +91,11 @@
      * the z-order (which is not really above the system UI window, but rather - the lock-screen
      * becomes invisible to reveal the "occluding activity").
      */
-    val isKeyguardShowing: Flow<Boolean>
+    val isKeyguardShowing: StateFlow<Boolean>
 
     /** Is an activity showing over the keyguard? */
     @Deprecated("Use KeyguardTransitionInteractor + KeyguardState.OCCLUDED")
-    val isKeyguardOccluded: Flow<Boolean>
+    val isKeyguardOccluded: StateFlow<Boolean>
 
     /**
      * Whether the device is locked or unlocked right now. This is true when keyguard has been
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAlternateBouncerTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAlternateBouncerTransitionInteractor.kt
index ae830ee..1042ae3 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAlternateBouncerTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAlternateBouncerTransitionInteractor.kt
@@ -30,7 +30,7 @@
 import com.android.systemui.scene.shared.flag.SceneContainerFlag
 import com.android.systemui.scene.shared.model.Scenes
 import com.android.systemui.util.kotlin.Utils.Companion.sample as sampleCombine
-import com.android.wm.shell.animation.Interpolators
+import com.android.wm.shell.shared.animation.Interpolators
 import javax.inject.Inject
 import kotlin.time.Duration.Companion.milliseconds
 import kotlinx.coroutines.CoroutineDispatcher
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractor.kt
index 2a8bb47..6e04133 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromAodTransitionInteractor.kt
@@ -36,8 +36,6 @@
 import kotlin.time.Duration.Companion.milliseconds
 import kotlinx.coroutines.CoroutineDispatcher
 import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.combine
 import kotlinx.coroutines.flow.debounce
 
 @SysUISingleton
@@ -73,15 +71,11 @@
         listenForTransitionToCamera(scope, keyguardInteractor)
     }
 
-    private val canDismissLockscreen: Flow<Boolean> =
-        combine(
-            keyguardInteractor.isKeyguardShowing,
-            keyguardInteractor.isKeyguardDismissible,
-            keyguardInteractor.biometricUnlockState,
-        ) { isKeyguardShowing, isKeyguardDismissible, biometricUnlockState ->
-            (isWakeAndUnlock(biometricUnlockState.mode) ||
-                (!isKeyguardShowing && isKeyguardDismissible))
-        }
+    private fun canDismissLockscreen(): Boolean {
+        return isWakeAndUnlock(keyguardInteractor.biometricUnlockState.value.mode) ||
+            (!keyguardInteractor.isKeyguardShowing.value &&
+                keyguardInteractor.isKeyguardDismissible.value)
+    }
 
     /**
      * Listen for the signal that we're waking up and figure what state we need to transition to.
@@ -96,22 +90,18 @@
                 .debounce(50L)
                 .sample(
                     startedKeyguardTransitionStep,
-                    keyguardInteractor.biometricUnlockState,
-                    keyguardInteractor.primaryBouncerShowing,
-                    keyguardInteractor.isKeyguardOccluded,
-                    canDismissLockscreen,
                     wakeToGoneInteractor.canWakeDirectlyToGone,
                 )
                 .collect {
                     (
                         _,
                         startedStep,
-                        biometricUnlockState,
-                        primaryBouncerShowing,
-                        isKeyguardOccludedLegacy,
-                        canDismissLockscreen,
                         canWakeDirectlyToGone,
                     ) ->
+                    val isKeyguardOccludedLegacy = keyguardInteractor.isKeyguardOccluded.value
+                    val biometricUnlockMode = keyguardInteractor.biometricUnlockState.value.mode
+                    val primaryBouncerShowing = keyguardInteractor.primaryBouncerShowing.value
+
                     if (!maybeHandleInsecurePowerGesture()) {
                         val shouldTransitionToLockscreen =
                             if (KeyguardWmStateRefactor.isEnabled) {
@@ -121,12 +111,10 @@
                                 // completes.
                                 !maybeStartTransitionToOccludedOrInsecureCamera { state, reason ->
                                     startTransitionTo(state, ownerReason = reason)
-                                } &&
-                                    !isWakeAndUnlock(biometricUnlockState.mode) &&
-                                    !primaryBouncerShowing
+                                } && !isWakeAndUnlock(biometricUnlockMode) && !primaryBouncerShowing
                             } else {
                                 !isKeyguardOccludedLegacy &&
-                                    !isWakeAndUnlock(biometricUnlockState.mode) &&
+                                    !isWakeAndUnlock(biometricUnlockMode) &&
                                     !primaryBouncerShowing
                             }
 
@@ -136,11 +124,11 @@
                             !KeyguardWmStateRefactor.isEnabled && isKeyguardOccludedLegacy
 
                         val shouldTransitionToGone =
-                            (!KeyguardWmStateRefactor.isEnabled && canDismissLockscreen) ||
+                            (!KeyguardWmStateRefactor.isEnabled && canDismissLockscreen()) ||
                                 (KeyguardWmStateRefactor.isEnabled && canWakeDirectlyToGone)
 
                         if (shouldTransitionToGone) {
-                            // TODO(b/336576536): Check if adaptation for scene framework is needed
+                            // TODO(b/360368320): Adapt for scene framework
                             if (SceneContainerFlag.isEnabled) return@collect
                             startTransitionTo(
                                 toState = KeyguardState.GONE,
@@ -198,7 +186,6 @@
      * PRIMARY_BOUNCER.
      */
     private fun listenForAodToPrimaryBouncer() {
-        // TODO(b/336576536): Check if adaptation for scene framework is needed
         if (SceneContainerFlag.isEnabled) return
         scope.launch("$TAG#listenForAodToPrimaryBouncer") {
             keyguardInteractor.primaryBouncerShowing
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractor.kt
index 61446c1..49e4c70 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDozingTransitionInteractor.kt
@@ -42,8 +42,6 @@
 import kotlin.time.Duration.Companion.milliseconds
 import kotlinx.coroutines.CoroutineDispatcher
 import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.combine
 import kotlinx.coroutines.flow.debounce
 import kotlinx.coroutines.launch
 
@@ -83,13 +81,10 @@
         listenForTransitionToCamera(scope, keyguardInteractor)
     }
 
-    private val canTransitionToGoneOnWake: Flow<Boolean> =
-        combine(
-            keyguardInteractor.isKeyguardShowing,
-            keyguardInteractor.isKeyguardDismissible,
-        ) { isKeyguardShowing, isKeyguardDismissible ->
-            isKeyguardDismissible && !isKeyguardShowing
-        }
+    private fun canDismissLockscreen(): Boolean {
+        return !keyguardInteractor.isKeyguardShowing.value &&
+            keyguardInteractor.isKeyguardDismissible.value
+    }
 
     private fun listenForDozingToGoneViaBiometrics() {
         if (KeyguardWmStateRefactor.isEnabled) {
@@ -112,7 +107,7 @@
                     ) ->
                     if (isWakeAndUnlock(biometricUnlockState.mode)) {
                         if (SceneContainerFlag.isEnabled) {
-                            // TODO(b/336576536): Check if adaptation for scene framework is needed
+                            // TODO(b/360368320): Adapt for scene framework
                         } else {
                             startTransitionTo(
                                 KeyguardState.GONE,
@@ -135,44 +130,29 @@
                 .debounce(50L)
                 .filterRelevantKeyguardStateAnd { isAwake -> isAwake }
                 .sample(
-                    keyguardInteractor.isKeyguardOccluded,
                     communalInteractor.isCommunalAvailable,
                     communalSceneInteractor.isIdleOnCommunal,
-                    canTransitionToGoneOnWake,
-                    keyguardInteractor.primaryBouncerShowing,
                 )
-                .collect {
-                    (
-                        _,
-                        occluded,
-                        isCommunalAvailable,
-                        isIdleOnCommunal,
-                        canTransitionToGoneOnWake,
-                        primaryBouncerShowing) ->
+                .collect { (_, isCommunalAvailable, isIdleOnCommunal) ->
+                    val isKeyguardOccludedLegacy = keyguardInteractor.isKeyguardOccluded.value
+                    val primaryBouncerShowing = keyguardInteractor.primaryBouncerShowing.value
+
                     if (!deviceEntryInteractor.isLockscreenEnabled()) {
-                        if (SceneContainerFlag.isEnabled) {
-                            // TODO(b/336576536): Check if adaptation for scene framework is needed
-                        } else {
+                        if (!SceneContainerFlag.isEnabled) {
                             startTransitionTo(KeyguardState.GONE)
                         }
-                    } else if (canTransitionToGoneOnWake) {
-                        if (SceneContainerFlag.isEnabled) {
-                            // TODO(b/336576536): Check if adaptation for scene framework is needed
-                        } else {
+                    } else if (canDismissLockscreen()) {
+                        if (!SceneContainerFlag.isEnabled) {
                             startTransitionTo(KeyguardState.GONE)
                         }
                     } else if (primaryBouncerShowing) {
-                        if (SceneContainerFlag.isEnabled) {
-                            // TODO(b/336576536): Check if adaptation for scene framework is needed
-                        } else {
+                        if (!SceneContainerFlag.isEnabled) {
                             startTransitionTo(KeyguardState.PRIMARY_BOUNCER)
                         }
-                    } else if (occluded) {
+                    } else if (isKeyguardOccludedLegacy) {
                         startTransitionTo(KeyguardState.OCCLUDED)
                     } else if (isIdleOnCommunal && !communalSceneKtfRefactor()) {
-                        if (SceneContainerFlag.isEnabled) {
-                            // TODO(b/336576536): Check if adaptation for scene framework is needed
-                        } else {
+                        if (!SceneContainerFlag.isEnabled) {
                             startTransitionTo(KeyguardState.GLANCEABLE_HUB)
                         }
                     } else if (
@@ -183,9 +163,7 @@
                     ) {
                         // This case handles tapping the power button to transition through
                         // dream -> off -> hub.
-                        if (SceneContainerFlag.isEnabled) {
-                            // TODO(b/336576536): Check if adaptation for scene framework is needed
-                        } else {
+                        if (!SceneContainerFlag.isEnabled) {
                             transitionToGlanceableHub()
                         }
                     } else {
@@ -228,30 +206,21 @@
                             !isWakeAndUnlock(biometricUnlockState.mode)
                     ) {
                         if (canWakeDirectlyToGone) {
-                            if (SceneContainerFlag.isEnabled) {
-                                // TODO(b/336576536): Check if adaptation for scene framework is
-                                // needed
-                            } else {
+                            if (!SceneContainerFlag.isEnabled) {
                                 startTransitionTo(
                                     KeyguardState.GONE,
                                     ownerReason = "waking from dozing"
                                 )
                             }
                         } else if (primaryBouncerShowing) {
-                            if (SceneContainerFlag.isEnabled) {
-                                // TODO(b/336576536): Check if adaptation for scene framework is
-                                // needed
-                            } else {
+                            if (!SceneContainerFlag.isEnabled) {
                                 startTransitionTo(
                                     KeyguardState.PRIMARY_BOUNCER,
                                     ownerReason = "waking from dozing"
                                 )
                             }
                         } else if (isIdleOnCommunal && !communalSceneKtfRefactor()) {
-                            if (SceneContainerFlag.isEnabled) {
-                                // TODO(b/336576536): Check if adaptation for scene framework is
-                                // needed
-                            } else {
+                            if (!SceneContainerFlag.isEnabled) {
                                 startTransitionTo(
                                     KeyguardState.GLANCEABLE_HUB,
                                     ownerReason = "waking from dozing"
@@ -265,10 +234,7 @@
                         ) {
                             // This case handles tapping the power button to transition through
                             // dream -> off -> hub.
-                            if (SceneContainerFlag.isEnabled) {
-                                // TODO(b/336576536): Check if adaptation for scene framework is
-                                // needed
-                            } else {
+                            if (!SceneContainerFlag.isEnabled) {
                                 transitionToGlanceableHub()
                             }
                         } else {
@@ -285,9 +251,10 @@
     private suspend fun transitionToGlanceableHub() {
         if (communalSceneKtfRefactor()) {
             communalSceneInteractor.changeScene(
-                CommunalScenes.Communal,
+                newScene = CommunalScenes.Communal,
+                loggingReason = "from dozing to hub",
                 // Immediately show the hub when transitioning from dozing to hub.
-                CommunalTransitionKeys.Immediately,
+                transitionKey = CommunalTransitionKeys.Immediately,
             )
         } else {
             startTransitionTo(KeyguardState.GLANCEABLE_HUB)
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractor.kt
index 17c1e82..4666430 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
@@ -20,7 +20,9 @@
 import com.android.app.animation.Interpolators
 import com.android.app.tracing.coroutines.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
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Background
 import com.android.systemui.dagger.qualifiers.Main
@@ -32,7 +34,6 @@
 import com.android.systemui.keyguard.shared.model.KeyguardState
 import com.android.systemui.power.domain.interactor.PowerInteractor
 import com.android.systemui.scene.shared.flag.SceneContainerFlag
-import com.android.systemui.util.kotlin.Utils.Companion.sample as sampleCombine
 import com.android.systemui.util.kotlin.sample
 import javax.inject.Inject
 import kotlin.time.Duration.Companion.milliseconds
@@ -59,6 +60,7 @@
     @Main mainDispatcher: CoroutineDispatcher,
     keyguardInteractor: KeyguardInteractor,
     private val glanceableHubTransitions: GlanceableHubTransitions,
+    private val communalSceneInteractor: CommunalSceneInteractor,
     private val communalSettingsInteractor: CommunalSettingsInteractor,
     powerInteractor: PowerInteractor,
     keyguardOcclusionInteractor: KeyguardOcclusionInteractor,
@@ -127,17 +129,24 @@
         }
     }
 
-    fun startToLockscreenTransition() {
+    fun startToLockscreenOrGlanceableHubTransition(openHub: Boolean) {
         scope.launch {
             if (
                 transitionInteractor.startedKeyguardState.replayCache.last() ==
                     KeyguardState.DREAMING
             ) {
                 if (powerInteractor.detailedWakefulness.value.isAwake()) {
-                    startTransitionTo(
-                        KeyguardState.LOCKSCREEN,
-                        ownerReason = "Dream has ended and device is awake"
-                    )
+                    if (openHub) {
+                        communalSceneInteractor.changeScene(
+                            newScene = CommunalScenes.Communal,
+                            loggingReason = "FromDreamingTransitionInteractor",
+                        )
+                    } else {
+                        startTransitionTo(
+                            KeyguardState.LOCKSCREEN,
+                            ownerReason = "Dream has ended and device is awake"
+                        )
+                    }
                 }
             }
         }
@@ -208,15 +217,15 @@
 
         scope.launch {
             keyguardInteractor.isAbleToDream
-                .sampleCombine(
-                    keyguardInteractor.isKeyguardShowing,
-                    keyguardInteractor.isKeyguardDismissible,
-                )
-                .filterRelevantKeyguardStateAnd {
-                    (isDreaming, isKeyguardShowing, isKeyguardDismissible) ->
-                    !isDreaming && isKeyguardDismissible && !isKeyguardShowing
+                .filterRelevantKeyguardStateAnd { isDreaming -> !isDreaming }
+                .collect {
+                    if (
+                        keyguardInteractor.isKeyguardDismissible.value &&
+                            !keyguardInteractor.isKeyguardShowing.value
+                    ) {
+                        startTransitionTo(KeyguardState.GONE)
+                    }
                 }
-                .collect { startTransitionTo(KeyguardState.GONE) }
         }
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromGlanceableHubTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromGlanceableHubTransitionInteractor.kt
index befcc9e..0aa50e0 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
@@ -159,6 +159,7 @@
                     if (communalSceneKtfRefactor()) {
                         communalSceneInteractor.changeScene(
                             newScene = CommunalScenes.Blank,
+                            loggingReason = "hub to dozing",
                             transitionKey = CommunalTransitionKeys.Immediately,
                             keyguardState = KeyguardState.DOZING,
                         )
@@ -182,6 +183,7 @@
                             if (communalSceneKtfRefactor()) {
                                 communalSceneInteractor.changeScene(
                                     newScene = CommunalScenes.Blank,
+                                    loggingReason = "hub to occluded (KeyguardWmStateRefactor)",
                                     transitionKey = CommunalTransitionKeys.SimpleFade,
                                     keyguardState = state,
                                 )
@@ -211,6 +213,7 @@
                     .collect { _ ->
                         communalSceneInteractor.changeScene(
                             newScene = CommunalScenes.Blank,
+                            loggingReason = "hub to occluded",
                             transitionKey = CommunalTransitionKeys.SimpleFade,
                             keyguardState = KeyguardState.OCCLUDED,
                         )
@@ -228,7 +231,6 @@
     }
 
     private fun listenForHubToGone() {
-        // TODO(b/336576536): Check if adaptation for scene framework is needed
         if (SceneContainerFlag.isEnabled) return
         if (communalSceneKtfRefactor()) {
             scope.launch {
@@ -254,6 +256,7 @@
                         } else {
                             communalSceneInteractor.changeScene(
                                 newScene = CommunalScenes.Blank,
+                                loggingReason = "hub to gone",
                                 transitionKey = CommunalTransitionKeys.SimpleFade,
                                 keyguardState = 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 5dc020f..cd3df07 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
@@ -276,7 +276,6 @@
     }
 
     private fun listenForLockscreenToGone() {
-        // TODO(b/336576536): Check if adaptation for scene framework is needed
         if (SceneContainerFlag.isEnabled) return
         if (KeyguardWmStateRefactor.isEnabled) return
         scope.launch("$TAG#listenForLockscreenToGone") {
@@ -292,7 +291,6 @@
     }
 
     private fun listenForLockscreenToGoneDragging() {
-        // TODO(b/336576536): Check if adaptation for scene framework is needed
         if (SceneContainerFlag.isEnabled) return
         if (KeyguardWmStateRefactor.isEnabled) {
             // When the refactor is enabled, we no longer use isKeyguardGoingAway.
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 905ca8e..0343786 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
@@ -76,7 +76,6 @@
     }
 
     private fun listenForOccludedToPrimaryBouncer() {
-        // TODO(b/336576536): Check if adaptation for scene framework is needed
         if (SceneContainerFlag.isEnabled) return
         scope.launch {
             keyguardInteractor.primaryBouncerShowing
@@ -146,8 +145,9 @@
             if (SceneContainerFlag.isEnabled) return
             if (communalSceneKtfRefactor()) {
                 communalSceneInteractor.changeScene(
-                    CommunalScenes.Communal,
-                    CommunalTransitionKeys.SimpleFade
+                    newScene = CommunalScenes.Communal,
+                    loggingReason = "occluded to hub",
+                    transitionKey = CommunalTransitionKeys.SimpleFade
                 )
             } else {
                 startTransitionTo(KeyguardState.GLANCEABLE_HUB)
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 2823b93..52323a5 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
@@ -35,7 +35,7 @@
 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.animation.Interpolators
+import com.android.wm.shell.shared.animation.Interpolators
 import javax.inject.Inject
 import kotlin.time.Duration.Companion.milliseconds
 import kotlinx.coroutines.CoroutineDispatcher
@@ -175,7 +175,10 @@
                 !communalSceneInteractor.isLaunchingWidget.value &&
                 communalSceneInteractor.editModeState.value == null
         ) {
-            communalSceneInteractor.snapToScene(CommunalScenes.Blank)
+            communalSceneInteractor.snapToScene(
+                newScene = CommunalScenes.Blank,
+                loggingReason = "FromPrimaryBouncerTransitionInteractor",
+            )
         }
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt
index 0df989e..4cab2bb 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt
@@ -216,14 +216,14 @@
 
     /** Whether the keyguard is showing or not. */
     @Deprecated("Use KeyguardTransitionInteractor + KeyguardState")
-    val isKeyguardShowing: Flow<Boolean> = repository.isKeyguardShowing
+    val isKeyguardShowing: StateFlow<Boolean> = repository.isKeyguardShowing
 
     /** Whether the keyguard is dismissible or not. */
     val isKeyguardDismissible: StateFlow<Boolean> = repository.isKeyguardDismissible
 
     /** Whether the keyguard is occluded (covered by an activity). */
     @Deprecated("Use KeyguardTransitionInteractor + KeyguardState.OCCLUDED")
-    val isKeyguardOccluded: Flow<Boolean> = repository.isKeyguardOccluded
+    val isKeyguardOccluded: StateFlow<Boolean> = repository.isKeyguardOccluded
 
     /** Whether the keyguard is going away. */
     @Deprecated("Use KeyguardTransitionInteractor + KeyguardState.GONE")
@@ -253,7 +253,7 @@
     val ambientIndicationVisible: Flow<Boolean> = repository.ambientIndicationVisible.asStateFlow()
 
     /** Whether the primary bouncer is showing or not. */
-    @JvmField val primaryBouncerShowing: Flow<Boolean> = bouncerRepository.primaryBouncerShow
+    @JvmField val primaryBouncerShowing: StateFlow<Boolean> = bouncerRepository.primaryBouncerShow
 
     /** Whether the alternate bouncer is showing or not. */
     val alternateBouncerShowing: Flow<Boolean> =
@@ -274,7 +274,7 @@
     val statusBarState: Flow<StatusBarState> = repository.statusBarState
 
     /** Observable for [BiometricUnlockModel] when biometrics are used to unlock the device. */
-    val biometricUnlockState: Flow<BiometricUnlockModel> = repository.biometricUnlockState
+    val biometricUnlockState: StateFlow<BiometricUnlockModel> = repository.biometricUnlockState
 
     /** Keyguard is present and is not occluded. */
     val isKeyguardVisible: Flow<Boolean> =
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractor.kt
index 0682d87..2af95f2 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardQuickAffordanceInteractor.kt
@@ -53,6 +53,7 @@
 import com.android.systemui.settings.UserTracker
 import com.android.systemui.shade.domain.interactor.ShadeInteractor
 import com.android.systemui.shared.customization.data.content.CustomizationProviderContract as Contract
+import com.android.systemui.shared.quickaffordance.shared.model.KeyguardPreviewConstants.KEYGUARD_QUICK_AFFORDANCE_ID_NONE
 import com.android.systemui.statusbar.phone.SystemUIDialog
 import com.android.systemui.statusbar.policy.KeyguardStateController
 import dagger.Lazy
@@ -142,14 +143,18 @@
      *
      * This is useful for experiences like the lock screen preview mode, where the affordances must
      * always be visible.
+     *
+     * @param overrideQuickAffordanceId If null, return the currently-set quick affordance;
+     *   otherwise, override and return the correspondent [KeyguardQuickAffordanceModel].
      */
     suspend fun quickAffordanceAlwaysVisible(
         position: KeyguardQuickAffordancePosition,
+        overrideQuickAffordanceId: String? = null,
     ): Flow<KeyguardQuickAffordanceModel> {
         return if (isFeatureDisabledByDevicePolicy()) {
             flowOf(KeyguardQuickAffordanceModel.Hidden)
         } else {
-            quickAffordanceInternal(position)
+            quickAffordanceInternal(position, overrideQuickAffordanceId)
         }
     }
 
@@ -299,12 +304,24 @@
     }
 
     private fun quickAffordanceInternal(
-        position: KeyguardQuickAffordancePosition
+        position: KeyguardQuickAffordancePosition,
+        overrideAffordanceId: String? = null,
     ): Flow<KeyguardQuickAffordanceModel> =
         repository
             .get()
             .selections
-            .map { it[position.toSlotId()] ?: emptyList() }
+            .map { selections ->
+                val overrideQuickAffordanceConfigs =
+                    overrideAffordanceId?.let {
+                        if (it == KEYGUARD_QUICK_AFFORDANCE_ID_NONE) {
+                            emptyList()
+                        } else {
+                            val config = repository.get().getConfig(it)
+                            listOfNotNull(config)
+                        }
+                    }
+                overrideQuickAffordanceConfigs ?: selections[position.toSlotId()] ?: emptyList()
+            }
             .flatMapLatest { configs -> combinedConfigs(position, configs) }
 
     private fun combinedConfigs(
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionBootInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionBootInteractor.kt
index 2ebd9e8..b218300 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionBootInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionBootInteractor.kt
@@ -71,7 +71,7 @@
                 )
             } else {
                 if (SceneContainerFlag.isEnabled) {
-                    // TODO(b/336576536): Some part of the transition implemented for flag off is
+                    // TODO(b/360372242): Some part of the transition implemented for flag off is
                     //  missing here. There are two things achieved with this:
                     //  1. Keyguard is hidden when the setup wizard is shown. This part is already
                     //     implemented in scene container by disabling visibility instead of going
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/shared/quickaffordance/KeyguardQuickAffordancePosition.kt b/packages/SystemUI/src/com/android/systemui/keyguard/shared/quickaffordance/KeyguardQuickAffordancePosition.kt
index 2581b59..1bbe843 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/shared/quickaffordance/KeyguardQuickAffordancePosition.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/shared/quickaffordance/KeyguardQuickAffordancePosition.kt
@@ -16,7 +16,8 @@
 
 package com.android.systemui.keyguard.shared.quickaffordance
 
-import com.android.systemui.shared.keyguard.shared.model.KeyguardQuickAffordanceSlots
+import com.android.systemui.shared.keyguard.shared.model.KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_END
+import com.android.systemui.shared.keyguard.shared.model.KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_START
 
 /** Enumerates all possible positions for quick affordances that can appear on the lock-screen. */
 enum class KeyguardQuickAffordancePosition {
@@ -25,8 +26,19 @@
 
     fun toSlotId(): String {
         return when (this) {
-            BOTTOM_START -> KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_START
-            BOTTOM_END -> KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_END
+            BOTTOM_START -> SLOT_ID_BOTTOM_START
+            BOTTOM_END -> SLOT_ID_BOTTOM_END
         }
     }
+
+    companion object {
+
+        /** If the slot ID does not match any string, return null. */
+        fun parseKeyguardQuickAffordancePosition(slotId: String): KeyguardQuickAffordancePosition? =
+            when (slotId) {
+                SLOT_ID_BOTTOM_START -> BOTTOM_START
+                SLOT_ID_BOTTOM_END -> BOTTOM_END
+                else -> null
+            }
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardQuickAffordanceViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardQuickAffordanceViewBinder.kt
index 15e6b1d..28a17ef 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardQuickAffordanceViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardQuickAffordanceViewBinder.kt
@@ -92,9 +92,9 @@
         val button = view as ImageView
         val configurationBasedDimensions = MutableStateFlow(loadFromResources(view))
         val disposableHandle =
-            view.repeatWhenAttached(mainImmediateDispatcher) {
-                repeatOnLifecycle(Lifecycle.State.STARTED) {
-                    launch("$TAG#viewModel") {
+            view.repeatWhenAttached {
+                repeatOnLifecycle(Lifecycle.State.CREATED) {
+                    launch {
                         viewModel.collect { buttonModel ->
                             updateButton(
                                 view = button,
@@ -104,7 +104,7 @@
                         }
                     }
 
-                    launch("$TAG#updateButtonAlpha") {
+                    launch {
                         updateButtonAlpha(
                             view = button,
                             viewModel = viewModel,
@@ -112,7 +112,7 @@
                         )
                     }
 
-                    launch("$TAG#configurationBasedDimensions") {
+                    launch {
                         configurationBasedDimensions.collect { dimensions ->
                             button.updateLayoutParams<ViewGroup.LayoutParams> {
                                 width = dimensions.buttonSizePx.width
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardRootViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardRootViewBinder.kt
index aab5b9b..89851db 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardRootViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardRootViewBinder.kt
@@ -152,6 +152,62 @@
                             }
                         }
                     }
+
+                    if (
+                        KeyguardBottomAreaRefactor.isEnabled || DeviceEntryUdfpsRefactor.isEnabled
+                    ) {
+                        launch("$TAG#alpha") {
+                            viewModel.alpha(viewState).collect { alpha ->
+                                view.alpha = alpha
+                                if (KeyguardBottomAreaRefactor.isEnabled) {
+                                    childViews[statusViewId]?.alpha = alpha
+                                    childViews[burnInLayerId]?.alpha = alpha
+                                }
+                            }
+                        }
+                    }
+
+                    if (MigrateClocksToBlueprint.isEnabled) {
+                        launch("$TAG#translationY") {
+                            // When translation happens in burnInLayer, it won't be weather clock
+                            // large clock isn't added to burnInLayer due to its scale transition
+                            // so we also need to add translation to it here
+                            // same as translationX
+                            viewModel.translationY.collect { y ->
+                                childViews[burnInLayerId]?.translationY = y
+                                childViews[largeClockId]?.translationY = y
+                                childViews[aodNotificationIconContainerId]?.translationY = y
+                            }
+                        }
+
+                        launch("$TAG#translationX") {
+                            viewModel.translationX.collect { state ->
+                                val px = state.value ?: return@collect
+                                when {
+                                    state.isToOrFrom(KeyguardState.AOD) -> {
+                                        // Large Clock is not translated in the x direction
+                                        childViews[burnInLayerId]?.translationX = px
+                                        childViews[aodNotificationIconContainerId]?.translationX =
+                                            px
+                                    }
+                                    state.isToOrFrom(KeyguardState.GLANCEABLE_HUB) -> {
+                                        for ((key, childView) in childViews.entries) {
+                                            when (key) {
+                                                indicationArea,
+                                                startButton,
+                                                endButton,
+                                                lockIcon,
+                                                deviceEntryIcon -> {
+                                                    // Do not move these views
+                                                }
+                                                else -> childView.translationX = px
+                                            }
+                                        }
+                                    }
+                                }
+                            }
+                        }
+                    }
                 }
             }
         disposables +=
@@ -188,20 +244,6 @@
                         }
                     }
 
-                    if (
-                        KeyguardBottomAreaRefactor.isEnabled || DeviceEntryUdfpsRefactor.isEnabled
-                    ) {
-                        launch {
-                            viewModel.alpha(viewState).collect { alpha ->
-                                view.alpha = alpha
-                                if (KeyguardBottomAreaRefactor.isEnabled) {
-                                    childViews[statusViewId]?.alpha = alpha
-                                    childViews[burnInLayerId]?.alpha = alpha
-                                }
-                            }
-                        }
-                    }
-
                     if (MigrateClocksToBlueprint.isEnabled) {
                         launch {
                             viewModel.burnInLayerVisibility.collect { visibility ->
@@ -222,46 +264,6 @@
                         }
 
                         launch {
-                            // When translation happens in burnInLayer, it won't be weather clock
-                            // large clock isn't added to burnInLayer due to its scale transition
-                            // so we also need to add translation to it here
-                            // same as translationX
-                            viewModel.translationY.collect { y ->
-                                childViews[burnInLayerId]?.translationY = y
-                                childViews[largeClockId]?.translationY = y
-                                childViews[aodNotificationIconContainerId]?.translationY = y
-                            }
-                        }
-
-                        launch {
-                            viewModel.translationX.collect { state ->
-                                val px = state.value ?: return@collect
-                                when {
-                                    state.isToOrFrom(KeyguardState.AOD) -> {
-                                        // Large Clock is not translated in the x direction
-                                        childViews[burnInLayerId]?.translationX = px
-                                        childViews[aodNotificationIconContainerId]?.translationX =
-                                            px
-                                    }
-                                    state.isToOrFrom(KeyguardState.GLANCEABLE_HUB) -> {
-                                        for ((key, childView) in childViews.entries) {
-                                            when (key) {
-                                                indicationArea,
-                                                startButton,
-                                                endButton,
-                                                lockIcon,
-                                                deviceEntryIcon -> {
-                                                    // Do not move these views
-                                                }
-                                                else -> childView.translationX = px
-                                            }
-                                        }
-                                    }
-                                }
-                            }
-                        }
-
-                        launch {
                             viewModel.scale.collect { scaleViewModel ->
                                 if (scaleViewModel.scaleClockOnly) {
                                     // For clocks except weather clock, we have scale transition
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardSurfaceBehindParamsApplier.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardSurfaceBehindParamsApplier.kt
index fb6efd3..3b36762 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardSurfaceBehindParamsApplier.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardSurfaceBehindParamsApplier.kt
@@ -34,7 +34,7 @@
 import com.android.systemui.keyguard.TAG
 import com.android.systemui.keyguard.domain.interactor.KeyguardSurfaceBehindInteractor
 import com.android.systemui.keyguard.shared.model.KeyguardSurfaceBehindModel
-import com.android.wm.shell.animation.Interpolators
+import com.android.wm.shell.shared.animation.Interpolators
 import java.util.concurrent.Executor
 import javax.inject.Inject
 
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardPreviewRenderer.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardPreviewRenderer.kt
index 6031ef6..51ce355 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardPreviewRenderer.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardPreviewRenderer.kt
@@ -275,6 +275,15 @@
         }
     }
 
+    fun onStartCustomizingQuickAffordances(
+        initiallySelectedSlotId: String?,
+    ) {
+        quickAffordancesCombinedViewModel.enablePreviewMode(
+            initiallySelectedSlotId = initiallySelectedSlotId,
+            shouldHighlightSelectedAffordance = true,
+        )
+    }
+
     fun onSlotSelected(slotId: String) {
         if (KeyguardBottomAreaRefactor.isEnabled) {
             quickAffordancesCombinedViewModel.onPreviewSlotSelected(slotId = slotId)
@@ -283,6 +292,21 @@
         }
     }
 
+    fun onPreviewQuickAffordanceSelected(slotId: String, quickAffordanceId: String) {
+        quickAffordancesCombinedViewModel.onPreviewQuickAffordanceSelected(
+            slotId,
+            quickAffordanceId,
+        )
+    }
+
+    fun onDefaultPreview() {
+        quickAffordancesCombinedViewModel.onClearPreviewQuickAffordances()
+        quickAffordancesCombinedViewModel.enablePreviewMode(
+            initiallySelectedSlotId = null,
+            shouldHighlightSelectedAffordance = false,
+        )
+    }
+
     fun destroy() {
         isDestroyed = true
         lockscreenSmartspaceController.disconnect()
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardRemotePreviewManager.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardRemotePreviewManager.kt
index 0532ee2..a6108c4 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardRemotePreviewManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardRemotePreviewManager.kt
@@ -31,7 +31,16 @@
 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.shared.quickaffordance.shared.model.KeyguardPreviewConstants
+import com.android.systemui.shared.keyguard.shared.model.KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_START
+import com.android.systemui.shared.quickaffordance.shared.model.KeyguardPreviewConstants.KEY_HIDE_SMART_SPACE
+import com.android.systemui.shared.quickaffordance.shared.model.KeyguardPreviewConstants.KEY_INITIALLY_SELECTED_SLOT_ID
+import com.android.systemui.shared.quickaffordance.shared.model.KeyguardPreviewConstants.KEY_QUICK_AFFORDANCE_ID
+import com.android.systemui.shared.quickaffordance.shared.model.KeyguardPreviewConstants.KEY_SLOT_ID
+import com.android.systemui.shared.quickaffordance.shared.model.KeyguardPreviewConstants.MESSAGE_ID_DEFAULT_PREVIEW
+import com.android.systemui.shared.quickaffordance.shared.model.KeyguardPreviewConstants.MESSAGE_ID_HIDE_SMART_SPACE
+import com.android.systemui.shared.quickaffordance.shared.model.KeyguardPreviewConstants.MESSAGE_ID_PREVIEW_QUICK_AFFORDANCE_SELECTED
+import com.android.systemui.shared.quickaffordance.shared.model.KeyguardPreviewConstants.MESSAGE_ID_SLOT_SELECTED
+import com.android.systemui.shared.quickaffordance.shared.model.KeyguardPreviewConstants.MESSAGE_ID_START_CUSTOMIZING_QUICK_AFFORDANCES
 import com.android.systemui.util.kotlin.logD
 import javax.inject.Inject
 import kotlinx.coroutines.CoroutineDispatcher
@@ -59,7 +68,7 @@
         return try {
             val renderer =
                 if (Flags.lockscreenPreviewRendererCreateOnMainThread()) {
-                    runBlocking ("$TAG#previewRendererFactory.create", mainDispatcher) {
+                    runBlocking("$TAG#previewRendererFactory.create", mainDispatcher) {
                         previewRendererFactory.create(request)
                     }
                 } else {
@@ -157,16 +166,35 @@
         }
 
         when (message.what) {
-            KeyguardPreviewConstants.MESSAGE_ID_SLOT_SELECTED -> {
-                message.data.getString(KeyguardPreviewConstants.KEY_SLOT_ID)?.let { slotId ->
+            MESSAGE_ID_START_CUSTOMIZING_QUICK_AFFORDANCES -> {
+                checkNotNull(renderer)
+                    .onStartCustomizingQuickAffordances(
+                        initiallySelectedSlotId =
+                            message.data.getString(KEY_INITIALLY_SELECTED_SLOT_ID)
+                                ?: SLOT_ID_BOTTOM_START
+                    )
+            }
+            MESSAGE_ID_SLOT_SELECTED -> {
+                message.data.getString(KEY_SLOT_ID)?.let { slotId ->
                     checkNotNull(renderer).onSlotSelected(slotId = slotId)
                 }
             }
-            KeyguardPreviewConstants.MESSAGE_ID_HIDE_SMART_SPACE -> {
-                checkNotNull(renderer)
-                    .hideSmartspace(
-                        message.data.getBoolean(KeyguardPreviewConstants.KEY_HIDE_SMART_SPACE)
-                    )
+            MESSAGE_ID_PREVIEW_QUICK_AFFORDANCE_SELECTED -> {
+                val slotId = message.data.getString(KEY_SLOT_ID)
+                val quickAffordanceId = message.data.getString(KEY_QUICK_AFFORDANCE_ID)
+                if (slotId != null && quickAffordanceId != null) {
+                    checkNotNull(renderer)
+                        .onPreviewQuickAffordanceSelected(
+                            slotId = slotId,
+                            quickAffordanceId = quickAffordanceId,
+                        )
+                }
+            }
+            MESSAGE_ID_DEFAULT_PREVIEW -> {
+                checkNotNull(renderer).onDefaultPreview()
+            }
+            MESSAGE_ID_HIDE_SMART_SPACE -> {
+                checkNotNull(renderer).hideSmartspace(message.data.getBoolean(KEY_HIDE_SMART_SPACE))
             }
             else -> checkNotNull(onDestroy).invoke(this)
         }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AccessibilityActionsViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AccessibilityActionsViewModel.kt
index 34c1436..38f5d3e 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AccessibilityActionsViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AccessibilityActionsViewModel.kt
@@ -50,5 +50,9 @@
             }
             .distinctUntilChanged()
 
-    fun openCommunalHub() = communalInteractor.changeScene(CommunalScenes.Communal)
+    fun openCommunalHub() =
+        communalInteractor.changeScene(
+            newScene = CommunalScenes.Communal,
+            loggingReason = "accessibility",
+        )
 }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToLockscreenTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToLockscreenTransitionViewModel.kt
index b5ec7a6..10605b2 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToLockscreenTransitionViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToLockscreenTransitionViewModel.kt
@@ -18,7 +18,6 @@
 
 import com.android.app.animation.Interpolators.EMPHASIZED
 import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.keyguard.domain.interactor.FromDreamingTransitionInteractor
 import com.android.systemui.keyguard.domain.interactor.FromDreamingTransitionInteractor.Companion.TO_LOCKSCREEN_DURATION
 import com.android.systemui.keyguard.shared.model.Edge
 import com.android.systemui.keyguard.shared.model.KeyguardState.DREAMING
@@ -39,10 +38,8 @@
 class DreamingToLockscreenTransitionViewModel
 @Inject
 constructor(
-    private val fromDreamingTransitionInteractor: FromDreamingTransitionInteractor,
     animationFlow: KeyguardTransitionAnimationFlow,
 ) : DeviceEntryIconTransition {
-    fun startTransition() = fromDreamingTransitionInteractor.startToLockscreenTransition()
 
     private val transitionAnimation =
         animationFlow.setup(
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardQuickAffordancesCombinedViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardQuickAffordancesCombinedViewModel.kt
index 2426f97..c885c9a 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardQuickAffordancesCombinedViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardQuickAffordancesCombinedViewModel.kt
@@ -20,6 +20,7 @@
 import androidx.annotation.VisibleForTesting
 import com.android.app.tracing.FlowTracing.traceEmissionCount
 import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.keyguard.NewPickerUiKeyguardPreview
 import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
 import com.android.systemui.keyguard.domain.interactor.KeyguardQuickAffordanceInteractor
 import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
@@ -29,6 +30,7 @@
 import com.android.systemui.keyguard.shared.quickaffordance.KeyguardQuickAffordancePosition
 import com.android.systemui.shade.domain.interactor.ShadeInteractor
 import com.android.systemui.shared.keyguard.shared.model.KeyguardQuickAffordanceSlots
+import com.android.systemui.utils.coroutines.flow.flatMapLatestConflated
 import javax.inject.Inject
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -164,13 +166,36 @@
             .map { alpha -> alpha >= AFFORDANCE_FULLY_OPAQUE_ALPHA_THRESHOLD }
             .distinctUntilChanged()
 
+    private val previewAffordances =
+        MutableStateFlow<Map<KeyguardQuickAffordancePosition, String>>(emptyMap())
+
     /** An observable for the view-model of the "start button" quick affordance. */
     val startButton: Flow<KeyguardQuickAffordanceViewModel> =
-        button(KeyguardQuickAffordancePosition.BOTTOM_START)
+        if (NewPickerUiKeyguardPreview.isEnabled) {
+            previewAffordances.flatMapLatestConflated {
+                button(
+                    position = KeyguardQuickAffordancePosition.BOTTOM_START,
+                    overrideQuickAffordanceId = it[KeyguardQuickAffordancePosition.BOTTOM_START],
+                )
+            }
+        } else {
+            button(
+                KeyguardQuickAffordancePosition.BOTTOM_START,
+            )
+        }
 
     /** An observable for the view-model of the "end button" quick affordance. */
     val endButton: Flow<KeyguardQuickAffordanceViewModel> =
-        button(KeyguardQuickAffordancePosition.BOTTOM_END)
+        if (NewPickerUiKeyguardPreview.isEnabled) {
+            previewAffordances.flatMapLatestConflated {
+                button(
+                    position = KeyguardQuickAffordancePosition.BOTTOM_END,
+                    overrideQuickAffordanceId = it[KeyguardQuickAffordancePosition.BOTTOM_END],
+                )
+            }
+        } else {
+            button(KeyguardQuickAffordancePosition.BOTTOM_END)
+        }
 
     /**
      * Notifies that a slot with the given ID has been selected in the preview experience that is
@@ -183,6 +208,28 @@
     }
 
     /**
+     * Notifies to preview an affordance at a given slot ID. This is ignored for the real lock
+     * screen experience.
+     */
+    fun onPreviewQuickAffordanceSelected(slotId: String, affordanceId: String) {
+        val position =
+            KeyguardQuickAffordancePosition.parseKeyguardQuickAffordancePosition(slotId) ?: return
+        previewAffordances.value =
+            previewAffordances.value.toMutableMap().let {
+                it[position] = affordanceId
+                HashMap(it)
+            }
+    }
+
+    /**
+     * Notifies to clear up the preview affordances map. This is ignored for the real lock screen
+     * experience.
+     */
+    fun onClearPreviewQuickAffordances() {
+        previewAffordances.value = emptyMap()
+    }
+
+    /**
      * Puts this view-model in "preview mode", which means it's being used for UI that is rendering
      * the lock screen preview in wallpaper picker / settings and not the real experience on the
      * lock screen.
@@ -207,14 +254,16 @@
     }
 
     private fun button(
-        position: KeyguardQuickAffordancePosition
+        position: KeyguardQuickAffordancePosition,
+        overrideQuickAffordanceId: String? = null,
     ): Flow<KeyguardQuickAffordanceViewModel> {
         return previewMode
             .flatMapLatest { previewMode ->
                 combine(
                         if (previewMode.isInPreviewMode) {
                             quickAffordanceInteractor.quickAffordanceAlwaysVisible(
-                                position = position
+                                position = position,
+                                overrideQuickAffordanceId = overrideQuickAffordanceId,
                             )
                         } else {
                             quickAffordanceInteractor.quickAffordance(position = position)
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModel.kt
index 050ef6f..06f77bf 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModel.kt
@@ -270,7 +270,7 @@
                         occludedToLockscreenTransitionViewModel.lockscreenAlpha,
                         primaryBouncerToAodTransitionViewModel.lockscreenAlpha,
                         primaryBouncerToGoneTransitionViewModel.lockscreenAlpha,
-                        primaryBouncerToLockscreenTransitionViewModel.lockscreenAlpha,
+                        primaryBouncerToLockscreenTransitionViewModel.lockscreenAlpha(viewState),
                     )
                     .onStart { emit(1f) }
             ) { hideKeyguard, alpha ->
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenContentViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenContentViewModel.kt
index 59cb6e5..666c9f8 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenContentViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenContentViewModel.kt
@@ -32,6 +32,7 @@
 import dagger.assisted.AssistedFactory
 import dagger.assisted.AssistedInject
 import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.awaitCancellation
 import kotlinx.coroutines.coroutineScope
 import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.MutableStateFlow
@@ -72,7 +73,7 @@
     /** Whether the content of the scene UI should be shown. */
     val isContentVisible: StateFlow<Boolean> = _isContentVisible.asStateFlow()
 
-    override suspend fun onActivated() {
+    override suspend fun onActivated(): Nothing {
         coroutineScope {
             launch {
                 combine(
@@ -92,6 +93,8 @@
                     .map { !it }
                     .collectLatest { _isContentVisible.value = it }
             }
+
+            awaitCancellation()
         }
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToLockscreenTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToLockscreenTransitionViewModel.kt
index 7511101..d29f512 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToLockscreenTransitionViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToLockscreenTransitionViewModel.kt
@@ -16,6 +16,7 @@
 
 package com.android.systemui.keyguard.ui.viewmodel
 
+import android.util.MathUtils
 import com.android.app.animation.Interpolators.EMPHASIZED_ACCELERATE
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.keyguard.domain.interactor.FromPrimaryBouncerTransitionInteractor
@@ -58,7 +59,14 @@
             onStep = { it }
         )
 
-    val lockscreenAlpha: Flow<Float> = shortcutsAlpha
+    fun lockscreenAlpha(viewState: ViewStateAccessor): Flow<Float> {
+        var currentAlpha = 0f
+        return transitionAnimation.sharedFlow(
+            duration = 250.milliseconds,
+            onStart = { currentAlpha = viewState.alpha() },
+            onStep = { MathUtils.lerp(currentAlpha, 1f, it) },
+        )
+    }
 
     val deviceEntryBackgroundViewAlpha: Flow<Float> =
         transitionAnimation.immediatelyTransitionTo(1f)
diff --git a/packages/SystemUI/src/com/android/systemui/lifecycle/Activatable.kt b/packages/SystemUI/src/com/android/systemui/lifecycle/Activatable.kt
index ebb0ea62..bd3d40b 100644
--- a/packages/SystemUI/src/com/android/systemui/lifecycle/Activatable.kt
+++ b/packages/SystemUI/src/com/android/systemui/lifecycle/Activatable.kt
@@ -57,7 +57,7 @@
      * }
      * ```
      */
-    suspend fun activate()
+    suspend fun activate(): Nothing
 }
 
 /**
diff --git a/packages/SystemUI/src/com/android/systemui/lifecycle/SafeActivatable.kt b/packages/SystemUI/src/com/android/systemui/lifecycle/SafeActivatable.kt
index f080a42..4dd76f8 100644
--- a/packages/SystemUI/src/com/android/systemui/lifecycle/SafeActivatable.kt
+++ b/packages/SystemUI/src/com/android/systemui/lifecycle/SafeActivatable.kt
@@ -34,7 +34,7 @@
             _isActive.set(value)
         }
 
-    final override suspend fun activate() {
+    final override suspend fun activate(): Nothing {
         val allowed = _isActive.compareAndSet(false, true)
         check(allowed) { "Cannot activate an already active activatable!" }
 
@@ -57,7 +57,7 @@
      *
      * Implementations could follow this pattern:
      * ```kotlin
-     * override suspend fun onActivated() {
+     * override suspend fun onActivated(): Nothing {
      *     coroutineScope {
      *         launch { ... }
      *         launch { ... }
@@ -68,5 +68,5 @@
      *
      * @see activate
      */
-    protected abstract suspend fun onActivated()
+    protected abstract suspend fun onActivated(): Nothing
 }
diff --git a/packages/SystemUI/src/com/android/systemui/lifecycle/SysUiViewModel.kt b/packages/SystemUI/src/com/android/systemui/lifecycle/SysUiViewModel.kt
index 7731481..2edde4a 100644
--- a/packages/SystemUI/src/com/android/systemui/lifecycle/SysUiViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/lifecycle/SysUiViewModel.kt
@@ -19,12 +19,15 @@
 import android.view.View
 import androidx.compose.runtime.Composable
 import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.awaitCancellation
 import kotlinx.coroutines.launch
 
 /** Base class for all System UI view-models. */
 abstract class SysUiViewModel : SafeActivatable() {
 
-    override suspend fun onActivated() = Unit
+    override suspend fun onActivated(): Nothing {
+        awaitCancellation()
+    }
 }
 
 /**
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/binder/MediaControlViewBinder.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/binder/MediaControlViewBinder.kt
index 62759a4..3c25e62 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/binder/MediaControlViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/binder/MediaControlViewBinder.kt
@@ -54,7 +54,6 @@
 import com.android.systemui.media.controls.ui.viewmodel.MediaOutputSwitcherViewModel
 import com.android.systemui.media.controls.ui.viewmodel.MediaPlayerViewModel
 import com.android.systemui.media.controls.util.MediaDataUtils
-import com.android.systemui.media.controls.util.MediaFlags
 import com.android.systemui.monet.ColorScheme
 import com.android.systemui.plugins.FalsingManager
 import com.android.systemui.res.R
@@ -76,7 +75,6 @@
         falsingManager: FalsingManager,
         @Background backgroundDispatcher: CoroutineDispatcher,
         @Main mainDispatcher: CoroutineDispatcher,
-        mediaFlags: MediaFlags,
     ) {
         val mediaCard = viewHolder.player
         mediaCard.repeatWhenAttached {
@@ -91,7 +89,6 @@
                                 falsingManager,
                                 backgroundDispatcher,
                                 mainDispatcher,
-                                mediaFlags
                             )
                         }
                     }
@@ -107,7 +104,6 @@
         falsingManager: FalsingManager,
         backgroundDispatcher: CoroutineDispatcher,
         mainDispatcher: CoroutineDispatcher,
-        mediaFlags: MediaFlags,
     ) {
         // Set up media control location and its listener.
         viewModel.onLocationChanged(viewController.currentEndLocation)
@@ -164,18 +160,6 @@
             isSongUpdated
         )
 
-        // TODO: We don't need to refresh this state constantly, only if the
-        // state actually changed to something which might impact the
-        // measurement. State refresh interferes with the translation
-        // animation, only run it if it's not running.
-        if (!viewController.metadataAnimationHandler.isRunning) {
-            // Don't refresh in scene framework, because it will calculate
-            // with invalid layout sizes
-            if (!mediaFlags.isSceneContainerEnabled()) {
-                viewController.refreshState()
-            }
-        }
-
         if (viewModel.playTurbulenceNoise) {
             viewController.setUpTurbulenceNoise()
         }
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaCarouselController.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaCarouselController.kt
index 62a7218..fb2bbde 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaCarouselController.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaCarouselController.kt
@@ -223,7 +223,7 @@
                 if (!mediaFlags.isSceneContainerEnabled()) {
                     MediaPlayerData.players().forEach { it.updateAnimatorDurationScale() }
                 } else {
-                    controllerByViewModel.values.forEach { it.updateAnimatorDurationScale() }
+                    controllerById.values.forEach { it.updateAnimatorDurationScale() }
                 }
             }
         }
@@ -324,7 +324,7 @@
     private var widthInSceneContainerPx = 0
     private var heightInSceneContainerPx = 0
 
-    private val controllerByViewModel = mutableMapOf<MediaCommonViewModel, MediaViewController>()
+    private val controllerById = mutableMapOf<String, MediaViewController>()
     private val commonViewModels = mutableListOf<MediaCommonViewModel>()
 
     init {
@@ -738,7 +738,7 @@
                     viewController.heightInSceneContainerPx = heightInSceneContainerPx
                 }
                 viewController.attachPlayer(viewHolder)
-                viewController.mediaViewHolder.player.layoutParams = lp
+                viewController.mediaViewHolder?.player?.layoutParams = lp
                 MediaControlViewBinder.bind(
                     viewHolder,
                     commonViewModel.controlViewModel,
@@ -746,15 +746,15 @@
                     falsingManager,
                     backgroundDispatcher,
                     mainDispatcher,
-                    mediaFlags
                 )
                 mediaContent.addView(viewHolder.player, position)
+                controllerById[commonViewModel.instanceId.toString()] = viewController
             }
             is MediaCommonViewModel.MediaRecommendations -> {
                 val viewHolder =
                     RecommendationViewHolder.create(LayoutInflater.from(context), mediaContent)
                 viewController.attachRecommendations(viewHolder)
-                viewController.recommendationViewHolder.recommendations.layoutParams = lp
+                viewController.recommendationViewHolder?.recommendations?.layoutParams = lp
                 MediaRecommendationsViewBinder.bind(
                     viewHolder,
                     commonViewModel.recsViewModel,
@@ -762,11 +762,11 @@
                     falsingManager,
                 )
                 mediaContent.addView(viewHolder.recommendations, position)
+                controllerById[commonViewModel.key] = viewController
             }
         }
         onAddOrUpdateVisibleToUserCard(position, isMediaCardUpdate = false)
         viewController.setListening(mediaCarouselScrollHandler.visibleToUser && currentlyExpanded)
-        controllerByViewModel[commonViewModel] = viewController
         updateViewControllerToState(viewController, noAnimation = true)
         updatePageIndicator()
         if (
@@ -793,14 +793,19 @@
     }
 
     private fun onRemoved(commonViewModel: MediaCommonViewModel) {
-        controllerByViewModel.remove(commonViewModel)?.let {
+        val id =
+            when (commonViewModel) {
+                is MediaCommonViewModel.MediaControl -> commonViewModel.instanceId.toString()
+                is MediaCommonViewModel.MediaRecommendations -> commonViewModel.key
+            }
+        controllerById.remove(id)?.let {
             when (commonViewModel) {
                 is MediaCommonViewModel.MediaControl -> {
-                    mediaCarouselScrollHandler.onPrePlayerRemoved(it.mediaViewHolder.player)
-                    mediaContent.removeView(it.mediaViewHolder.player)
+                    mediaCarouselScrollHandler.onPrePlayerRemoved(it.mediaViewHolder!!.player)
+                    mediaContent.removeView(it.mediaViewHolder!!.player)
                 }
                 is MediaCommonViewModel.MediaRecommendations -> {
-                    mediaContent.removeView(it.recommendationViewHolder.recommendations)
+                    mediaContent.removeView(it.recommendationViewHolder!!.recommendations)
                 }
             }
             it.onDestroy()
@@ -811,14 +816,19 @@
     }
 
     private fun onMoved(commonViewModel: MediaCommonViewModel, from: Int, to: Int) {
-        controllerByViewModel[commonViewModel]?.let {
+        val id =
+            when (commonViewModel) {
+                is MediaCommonViewModel.MediaControl -> commonViewModel.instanceId.toString()
+                is MediaCommonViewModel.MediaRecommendations -> commonViewModel.key
+            }
+        controllerById[id]?.let {
             mediaContent.removeViewAt(from)
             when (commonViewModel) {
                 is MediaCommonViewModel.MediaControl -> {
-                    mediaContent.addView(it.mediaViewHolder.player, to)
+                    mediaContent.addView(it.mediaViewHolder!!.player, to)
                 }
                 is MediaCommonViewModel.MediaRecommendations -> {
-                    mediaContent.addView(it.recommendationViewHolder.recommendations, to)
+                    mediaContent.addView(it.recommendationViewHolder!!.recommendations, to)
                 }
             }
         }
@@ -855,15 +865,16 @@
                     }
                 }
                 .toHashSet()
-        controllerByViewModel
-            .filter {
-                when (val viewModel = it.key) {
-                    is MediaCommonViewModel.MediaControl ->
-                        !viewIds.contains(viewModel.instanceId.toString())
-                    is MediaCommonViewModel.MediaRecommendations -> !viewIds.contains(viewModel.key)
-                }
+        controllerById
+            .filter { !viewIds.contains(it.key) }
+            .forEach {
+                mediaCarouselScrollHandler.onPrePlayerRemoved(it.value.mediaViewHolder?.player)
+                mediaContent.removeView(it.value.mediaViewHolder?.player)
+                mediaContent.removeView(it.value.recommendationViewHolder?.recommendations)
+                it.value.onDestroy()
+                mediaCarouselScrollHandler.onPlayersChanged()
+                updatePageIndicator()
             }
-            .forEach { onRemoved(it.key) }
     }
 
     private suspend fun getMediaLockScreenSetting(): Boolean {
@@ -1176,12 +1187,12 @@
             commonViewModels.forEach { viewModel ->
                 when (viewModel) {
                     is MediaCommonViewModel.MediaControl -> {
-                        controllerByViewModel[viewModel]?.mediaViewHolder?.let {
+                        controllerById[viewModel.instanceId.toString()]?.mediaViewHolder?.let {
                             mediaContent.addView(it.player)
                         }
                     }
                     is MediaCommonViewModel.MediaRecommendations -> {
-                        controllerByViewModel[viewModel]?.recommendationViewHolder?.let {
+                        controllerById[viewModel.key]?.recommendationViewHolder?.let {
                             mediaContent.addView(it.recommendations)
                         }
                     }
@@ -1234,9 +1245,7 @@
                     updateViewControllerToState(mediaPlayer.mediaViewController, immediately)
                 }
             } else {
-                controllerByViewModel.values.forEach {
-                    updateViewControllerToState(it, immediately)
-                }
+                controllerById.values.forEach { updateViewControllerToState(it, immediately) }
             }
             maybeResetSettingsCog()
             updatePageIndicatorAlpha()
@@ -1296,9 +1305,7 @@
                 player.setListening(visibleToUser && currentlyExpanded)
             }
         } else {
-            controllerByViewModel.values.forEach {
-                it.setListening(visibleToUser && currentlyExpanded)
-            }
+            controllerById.values.forEach { it.setListening(visibleToUser && currentlyExpanded) }
         }
     }
 
@@ -1316,7 +1323,7 @@
                     Math.max(height, controller.currentHeight + controller.translationY.toInt())
             }
         } else {
-            controllerByViewModel.values.forEach {
+            controllerById.values.forEach {
                 // When transitioning the view to gone, the view gets smaller, but the translation
                 // Doesn't, let's add the translation
                 width = Math.max(width, it.currentWidth + it.translationX.toInt())
@@ -1413,7 +1420,7 @@
                         mediaPlayer.mediaViewController.onLocationPreChange(desiredLocation)
                     }
                 } else {
-                    controllerByViewModel.values.forEach { controller ->
+                    controllerById.values.forEach { controller ->
                         if (animate) {
                             controller.animatePendingStateChange(duration, startDelay)
                         }
@@ -1441,7 +1448,7 @@
         if (!mediaFlags.isSceneContainerEnabled()) {
             MediaPlayerData.players().forEach { it.closeGuts(immediate) }
         } else {
-            controllerByViewModel.values.forEach { it.closeGuts(immediate) }
+            controllerById.values.forEach { it.closeGuts(immediate) }
         }
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaViewController.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaViewController.kt
index 681bf39..584908f 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaViewController.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/controller/MediaViewController.kt
@@ -196,8 +196,8 @@
     private var isNextButtonAvailable = false
 
     /** View holders for controller */
-    lateinit var recommendationViewHolder: RecommendationViewHolder
-    lateinit var mediaViewHolder: MediaViewHolder
+    var recommendationViewHolder: RecommendationViewHolder? = null
+    var mediaViewHolder: MediaViewHolder? = null
 
     private lateinit var seekBarObserver: SeekBarObserver
     private lateinit var turbulenceNoiseController: TurbulenceNoiseController
@@ -752,16 +752,18 @@
     private fun updateDisplayForScrubbingChange() {
         mainExecutor.execute {
             val isTimeVisible = canShowScrubbingTime && isScrubbing
-            MediaControlViewBinder.setVisibleAndAlpha(
-                expandedLayout,
-                mediaViewHolder.scrubbingTotalTimeView.id,
-                isTimeVisible
-            )
-            MediaControlViewBinder.setVisibleAndAlpha(
-                expandedLayout,
-                mediaViewHolder.scrubbingElapsedTimeView.id,
-                isTimeVisible
-            )
+            mediaViewHolder!!.let {
+                MediaControlViewBinder.setVisibleAndAlpha(
+                    expandedLayout,
+                    it.scrubbingTotalTimeView.id,
+                    isTimeVisible
+                )
+                MediaControlViewBinder.setVisibleAndAlpha(
+                    expandedLayout,
+                    it.scrubbingElapsedTimeView.id,
+                    isTimeVisible
+                )
+            }
 
             MediaControlViewModel.SEMANTIC_ACTIONS_HIDE_WHEN_SCRUBBING.forEach { id ->
                 val isButtonVisible: Boolean
@@ -780,14 +782,16 @@
                         notVisibleValue = ConstraintSet.GONE
                     }
                 }
-                MediaControlViewBinder.setSemanticButtonVisibleAndAlpha(
-                    mediaViewHolder.getAction(id),
-                    expandedLayout,
-                    collapsedLayout,
-                    isButtonVisible,
-                    notVisibleValue,
-                    showInCollapsed = true
-                )
+                mediaViewHolder!!.let {
+                    MediaControlViewBinder.setSemanticButtonVisibleAndAlpha(
+                        it.getAction(id),
+                        expandedLayout,
+                        collapsedLayout,
+                        isButtonVisible,
+                        notVisibleValue,
+                        showInCollapsed = true
+                    )
+                }
             }
 
             if (!metadataAnimationHandler.isRunning) {
@@ -813,39 +817,41 @@
 
     fun setUpTurbulenceNoise() {
         if (!mediaFlags.isSceneContainerEnabled()) return
-        if (!this::turbulenceNoiseAnimationConfig.isInitialized) {
-            turbulenceNoiseAnimationConfig =
-                createTurbulenceNoiseConfig(
-                    mediaViewHolder.loadingEffectView,
-                    mediaViewHolder.turbulenceNoiseView,
-                    colorSchemeTransition
-                )
-        }
-        if (Flags.shaderlibLoadingEffectRefactor()) {
-            if (!this::loadingEffect.isInitialized) {
-                loadingEffect =
-                    LoadingEffect(
-                        TurbulenceNoiseShader.Companion.Type.SIMPLEX_NOISE,
-                        turbulenceNoiseAnimationConfig,
-                        noiseDrawCallback,
-                        stateChangedCallback
+        mediaViewHolder!!.let {
+            if (!this::turbulenceNoiseAnimationConfig.isInitialized) {
+                turbulenceNoiseAnimationConfig =
+                    createTurbulenceNoiseConfig(
+                        it.loadingEffectView,
+                        it.turbulenceNoiseView,
+                        colorSchemeTransition
                     )
             }
-            colorSchemeTransition.loadingEffect = loadingEffect
-            loadingEffect.play()
-            mainExecutor.executeDelayed(
-                loadingEffect::finish,
-                MediaControlViewModel.TURBULENCE_NOISE_PLAY_MS_DURATION
-            )
-        } else {
-            turbulenceNoiseController.play(
-                TurbulenceNoiseShader.Companion.Type.SIMPLEX_NOISE,
-                turbulenceNoiseAnimationConfig
-            )
-            mainExecutor.executeDelayed(
-                turbulenceNoiseController::finish,
-                MediaControlViewModel.TURBULENCE_NOISE_PLAY_MS_DURATION
-            )
+            if (Flags.shaderlibLoadingEffectRefactor()) {
+                if (!this::loadingEffect.isInitialized) {
+                    loadingEffect =
+                        LoadingEffect(
+                            TurbulenceNoiseShader.Companion.Type.SIMPLEX_NOISE,
+                            turbulenceNoiseAnimationConfig,
+                            noiseDrawCallback,
+                            stateChangedCallback
+                        )
+                }
+                colorSchemeTransition.loadingEffect = loadingEffect
+                loadingEffect.play()
+                mainExecutor.executeDelayed(
+                    loadingEffect::finish,
+                    MediaControlViewModel.TURBULENCE_NOISE_PLAY_MS_DURATION
+                )
+            } else {
+                turbulenceNoiseController.play(
+                    TurbulenceNoiseShader.Companion.Type.SIMPLEX_NOISE,
+                    turbulenceNoiseAnimationConfig
+                )
+                mainExecutor.executeDelayed(
+                    turbulenceNoiseController::finish,
+                    MediaControlViewModel.TURBULENCE_NOISE_PLAY_MS_DURATION
+                )
+            }
         }
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/view/MediaProjectionRecentsViewController.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/view/MediaProjectionRecentsViewController.kt
index 2ce7044..dd1fa76 100644
--- a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/view/MediaProjectionRecentsViewController.kt
+++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/view/MediaProjectionRecentsViewController.kt
@@ -36,8 +36,8 @@
 import com.android.systemui.mediaprojection.appselector.view.TaskPreviewSizeProvider.TaskPreviewSizeListener
 import com.android.systemui.res.R
 import com.android.systemui.util.recycler.HorizontalSpacerItemDecoration
-import com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_BOTTOM_OR_RIGHT
-import com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_TOP_OR_LEFT
+import com.android.wm.shell.shared.split.SplitScreenConstants.SPLIT_POSITION_BOTTOM_OR_RIGHT
+import com.android.wm.shell.shared.split.SplitScreenConstants.SPLIT_POSITION_TOP_OR_LEFT
 import com.android.wm.shell.splitscreen.SplitScreen
 import com.android.wm.shell.util.SplitBounds
 import java.util.Optional
diff --git a/packages/SystemUI/src/com/android/systemui/qs/AutoAddTracker.kt b/packages/SystemUI/src/com/android/systemui/qs/AutoAddTracker.kt
deleted file mode 100644
index e4bafcd..0000000
--- a/packages/SystemUI/src/com/android/systemui/qs/AutoAddTracker.kt
+++ /dev/null
@@ -1,298 +0,0 @@
-/*
- * Copyright (C) 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.qs
-
-import android.content.BroadcastReceiver
-import android.content.Context
-import android.content.Intent
-import android.content.IntentFilter
-import android.database.ContentObserver
-import android.net.Uri
-import android.os.Handler
-import android.os.UserHandle
-import android.provider.Settings
-import android.text.TextUtils
-import android.util.ArraySet
-import android.util.Log
-import androidx.annotation.GuardedBy
-import androidx.annotation.VisibleForTesting
-import com.android.systemui.Dumpable
-import com.android.systemui.broadcast.BroadcastDispatcher
-import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.dagger.qualifiers.Background
-import com.android.systemui.dagger.qualifiers.Main
-import com.android.systemui.dump.DumpManager
-import com.android.systemui.util.UserAwareController
-import com.android.systemui.util.settings.SecureSettings
-import java.io.PrintWriter
-import java.util.concurrent.Executor
-import javax.inject.Inject
-
-private const val TAG = "AutoAddTracker"
-private const val DELIMITER = ","
-
-/**
- * Class to track tiles that have been auto-added
- *
- * The list is backed by [Settings.Secure.QS_AUTO_ADDED_TILES].
- *
- * It also handles restore gracefully.
- */
-class AutoAddTracker
-@VisibleForTesting
-constructor(
-    private val secureSettings: SecureSettings,
-    private val broadcastDispatcher: BroadcastDispatcher,
-    private val qsHost: QSHost,
-    private val dumpManager: DumpManager,
-    private val mainHandler: Handler?,
-    private val backgroundExecutor: Executor,
-    private var userId: Int
-) : UserAwareController, Dumpable {
-
-    companion object {
-        private val FILTER = IntentFilter(Intent.ACTION_SETTING_RESTORED)
-    }
-
-    @GuardedBy("autoAdded") private val autoAdded = ArraySet<String>()
-    private var restoredTiles: Map<String, AutoTile>? = null
-
-    override val currentUserId: Int
-        get() = userId
-
-    private val contentObserver =
-        object : ContentObserver(mainHandler) {
-            override fun onChange(
-                selfChange: Boolean,
-                uris: Collection<Uri>,
-                flags: Int,
-                _userId: Int
-            ) {
-                if (_userId != userId) {
-                    // Ignore changes outside of our user. We'll load the correct value on user
-                    // change
-                    return
-                }
-                loadTiles()
-            }
-        }
-
-    private val restoreReceiver =
-        object : BroadcastReceiver() {
-            override fun onReceive(context: Context, intent: Intent) {
-                if (intent.action != Intent.ACTION_SETTING_RESTORED) return
-                processRestoreIntent(intent)
-            }
-        }
-
-    private fun processRestoreIntent(intent: Intent) {
-        when (intent.getStringExtra(Intent.EXTRA_SETTING_NAME)) {
-            Settings.Secure.QS_TILES -> {
-                restoredTiles =
-                    intent
-                        .getStringExtra(Intent.EXTRA_SETTING_NEW_VALUE)
-                        ?.split(DELIMITER)
-                        ?.mapIndexed(::AutoTile)
-                        ?.associateBy(AutoTile::tileType)
-                        ?: run {
-                            Log.w(TAG, "Null restored tiles for user $userId")
-                            emptyMap()
-                        }
-            }
-            Settings.Secure.QS_AUTO_ADDED_TILES -> {
-                restoredTiles?.let { restoredTiles ->
-                    val restoredAutoAdded =
-                        intent.getStringExtra(Intent.EXTRA_SETTING_NEW_VALUE)?.split(DELIMITER)
-                            ?: emptyList()
-                    val autoAddedBeforeRestore =
-                        intent.getStringExtra(Intent.EXTRA_SETTING_PREVIOUS_VALUE)?.split(DELIMITER)
-                            ?: emptyList()
-
-                    val tilesToRemove = restoredAutoAdded.filter { it !in restoredTiles }
-                    if (tilesToRemove.isNotEmpty()) {
-                        Log.d(TAG, "Removing tiles: $tilesToRemove")
-                        qsHost.removeTiles(tilesToRemove)
-                    }
-                    val tiles =
-                        synchronized(autoAdded) {
-                            autoAdded.clear()
-                            autoAdded.addAll(restoredAutoAdded + autoAddedBeforeRestore)
-                            getTilesFromListLocked()
-                        }
-                    saveTiles(tiles)
-                }
-                    ?: run {
-                        Log.w(
-                            TAG,
-                            "${Settings.Secure.QS_AUTO_ADDED_TILES} restored before " +
-                                "${Settings.Secure.QS_TILES} for user $userId"
-                        )
-                    }
-            }
-            else -> {} // Do nothing for other Settings
-        }
-    }
-
-    /** Init method must be called after construction to start listening */
-    fun initialize() {
-        dumpManager.registerDumpable(TAG, this)
-        loadTiles()
-        secureSettings.registerContentObserverForUserSync(
-            secureSettings.getUriFor(Settings.Secure.QS_AUTO_ADDED_TILES),
-            contentObserver,
-            UserHandle.USER_ALL
-        )
-        registerBroadcastReceiver()
-    }
-
-    /** Unregister listeners, receivers and observers */
-    fun destroy() {
-        dumpManager.unregisterDumpable(TAG)
-        secureSettings.unregisterContentObserverSync(contentObserver)
-        unregisterBroadcastReceiver()
-    }
-
-    private fun registerBroadcastReceiver() {
-        broadcastDispatcher.registerReceiver(
-            restoreReceiver,
-            FILTER,
-            backgroundExecutor,
-            UserHandle.of(userId)
-        )
-    }
-
-    private fun unregisterBroadcastReceiver() {
-        broadcastDispatcher.unregisterReceiver(restoreReceiver)
-    }
-
-    override fun changeUser(newUser: UserHandle) {
-        if (newUser.identifier == userId) return
-        unregisterBroadcastReceiver()
-        userId = newUser.identifier
-        restoredTiles = null
-        loadTiles()
-        registerBroadcastReceiver()
-    }
-
-    fun getRestoredTilePosition(tile: String): Int =
-        restoredTiles?.get(tile)?.index ?: QSHost.POSITION_AT_END
-
-    /** Returns `true` if the tile has been auto-added before */
-    fun isAdded(tile: String): Boolean {
-        return synchronized(autoAdded) { tile in autoAdded }
-    }
-
-    /**
-     * Sets a tile as auto-added.
-     *
-     * From here on, [isAdded] will return true for that tile.
-     */
-    fun setTileAdded(tile: String) {
-        val tiles =
-            synchronized(autoAdded) {
-                if (autoAdded.add(tile)) {
-                    getTilesFromListLocked()
-                } else {
-                    null
-                }
-            }
-        tiles?.let { saveTiles(it) }
-    }
-
-    /**
-     * Removes a tile from the list of auto-added.
-     *
-     * This allows for this tile to be auto-added again in the future.
-     */
-    fun setTileRemoved(tile: String) {
-        val tiles =
-            synchronized(autoAdded) {
-                if (autoAdded.remove(tile)) {
-                    getTilesFromListLocked()
-                } else {
-                    null
-                }
-            }
-        tiles?.let { saveTiles(it) }
-    }
-
-    private fun getTilesFromListLocked(): String {
-        return TextUtils.join(DELIMITER, autoAdded)
-    }
-
-    private fun saveTiles(tiles: String) {
-        secureSettings.putStringForUser(
-            Settings.Secure.QS_AUTO_ADDED_TILES,
-            tiles,
-            /* tag */ null,
-            /* makeDefault */ false,
-            userId,
-            /* overrideableByRestore */ true
-        )
-    }
-
-    private fun loadTiles() {
-        synchronized(autoAdded) {
-            autoAdded.clear()
-            autoAdded.addAll(getAdded())
-        }
-    }
-
-    private fun getAdded(): Collection<String> {
-        val current = secureSettings.getStringForUser(Settings.Secure.QS_AUTO_ADDED_TILES, userId)
-        return current?.split(DELIMITER) ?: emptySet()
-    }
-
-    override fun dump(pw: PrintWriter, args: Array<out String>) {
-        pw.println("Current user: $userId")
-        pw.println("Restored tiles: $restoredTiles")
-        pw.println("Added tiles: $autoAdded")
-    }
-
-    @SysUISingleton
-    class Builder
-    @Inject
-    constructor(
-        private val secureSettings: SecureSettings,
-        private val broadcastDispatcher: BroadcastDispatcher,
-        private val qsHost: QSHost,
-        private val dumpManager: DumpManager,
-        @Main private val handler: Handler,
-        @Background private val executor: Executor
-    ) {
-        private var userId: Int = 0
-
-        fun setUserId(_userId: Int): Builder {
-            userId = _userId
-            return this
-        }
-
-        fun build(): AutoAddTracker {
-            return AutoAddTracker(
-                secureSettings,
-                broadcastDispatcher,
-                qsHost,
-                dumpManager,
-                handler,
-                executor,
-                userId
-            )
-        }
-    }
-
-    private data class AutoTile(val index: Int, val tileType: String)
-}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSFragmentStartable.kt b/packages/SystemUI/src/com/android/systemui/qs/QSFragmentStartable.kt
index 9fa6769..bb238f2 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSFragmentStartable.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSFragmentStartable.kt
@@ -19,6 +19,7 @@
 import com.android.systemui.CoreStartable
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.fragments.FragmentService
+import com.android.systemui.qs.composefragment.QSFragmentCompose
 import dagger.Binds
 import dagger.Module
 import dagger.multibindings.ClassKey
@@ -31,13 +32,18 @@
 @Inject
 constructor(
     private val fragmentService: FragmentService,
-    private val qsFragmentLegacyProvider: Provider<QSFragmentLegacy>
+    private val qsFragmentLegacyProvider: Provider<QSFragmentLegacy>,
+    private val qsFragmentComposeProvider: Provider<QSFragmentCompose>,
 ) : CoreStartable {
     override fun start() {
         fragmentService.addFragmentInstantiationProvider(
             QSFragmentLegacy::class.java,
             qsFragmentLegacyProvider
         )
+        fragmentService.addFragmentInstantiationProvider(
+            QSFragmentCompose::class.java,
+            qsFragmentComposeProvider
+        )
     }
 }
 
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSHostAdapter.kt b/packages/SystemUI/src/com/android/systemui/qs/QSHostAdapter.kt
index c77233e..4323b31 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QSHostAdapter.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSHostAdapter.kt
@@ -47,12 +47,10 @@
 class QSHostAdapter
 @Inject
 constructor(
-    private val qsTileHost: QSTileHost,
     private val interactor: CurrentTilesInteractor,
     private val context: Context,
     private val tileServiceRequestControllerBuilder: TileServiceRequestController.Builder,
     @Application private val scope: CoroutineScope,
-    flags: QSPipelineFlagsRepository,
     dumpManager: DumpManager,
 ) : QSHost {
 
@@ -60,123 +58,69 @@
         private const val TAG = "QSTileHost"
     }
 
-    private val useNewHost = flags.pipelineEnabled
-
     @GuardedBy("callbacksMap") private val callbacksMap = mutableMapOf<QSHost.Callback, Job>()
 
     init {
         scope.launch { tileServiceRequestControllerBuilder.create(this@QSHostAdapter).init() }
         // Redirect dump to the correct host (needed for CTS tests)
-        dumpManager.registerCriticalDumpable(TAG, if (useNewHost) interactor else qsTileHost)
+        dumpManager.registerCriticalDumpable(TAG, interactor)
     }
 
     override fun getTiles(): Collection<QSTile> {
-        return if (useNewHost) {
-            interactor.currentQSTiles
-        } else {
-            qsTileHost.getTiles()
-        }
+        return interactor.currentQSTiles
     }
 
     override fun getSpecs(): List<String> {
-        return if (useNewHost) {
-            interactor.currentTilesSpecs.map { it.spec }
-        } else {
-            qsTileHost.getSpecs()
-        }
+        return interactor.currentTilesSpecs.map { it.spec }
     }
 
     override fun removeTile(spec: String) {
-        if (useNewHost) {
-            interactor.removeTiles(listOf(TileSpec.create(spec)))
-        } else {
-            qsTileHost.removeTile(spec)
-        }
+        interactor.removeTiles(listOf(TileSpec.create(spec)))
     }
 
     override fun addCallback(callback: QSHost.Callback) {
-        if (useNewHost) {
-            val job = scope.launch { interactor.currentTiles.collect { callback.onTilesChanged() } }
-            synchronized(callbacksMap) { callbacksMap.put(callback, job) }
-        } else {
-            qsTileHost.addCallback(callback)
-        }
+        val job = scope.launch { interactor.currentTiles.collect { callback.onTilesChanged() } }
+        synchronized(callbacksMap) { callbacksMap.put(callback, job) }
     }
 
     override fun removeCallback(callback: QSHost.Callback) {
-        if (useNewHost) {
-            synchronized(callbacksMap) { callbacksMap.remove(callback)?.cancel() }
-        } else {
-            qsTileHost.removeCallback(callback)
-        }
+        synchronized(callbacksMap) { callbacksMap.remove(callback)?.cancel() }
     }
 
     override fun removeTiles(specs: Collection<String>) {
-        if (useNewHost) {
-            interactor.removeTiles(specs.map(TileSpec::create))
-        } else {
-            qsTileHost.removeTiles(specs)
-        }
+        interactor.removeTiles(specs.map(TileSpec::create))
     }
 
     override fun removeTileByUser(component: ComponentName) {
-        if (useNewHost) {
-            interactor.removeTiles(listOf(TileSpec.create(component)))
-        } else {
-            qsTileHost.removeTileByUser(component)
-        }
+        interactor.removeTiles(listOf(TileSpec.create(component)))
     }
 
     override fun addTile(spec: String, position: Int) {
-        if (useNewHost) {
-            interactor.addTile(TileSpec.create(spec), position)
-        } else {
-            qsTileHost.addTile(spec, position)
-        }
+        interactor.addTile(TileSpec.create(spec), position)
     }
 
     override fun addTile(component: ComponentName, end: Boolean) {
-        if (useNewHost) {
-            interactor.addTile(TileSpec.create(component), if (end) POSITION_AT_END else 0)
-        } else {
-            qsTileHost.addTile(component, end)
-        }
+        interactor.addTile(TileSpec.create(component), if (end) POSITION_AT_END else 0)
     }
 
     override fun changeTilesByUser(previousTiles: List<String>, newTiles: List<String>) {
-        if (useNewHost) {
-            interactor.setTiles(newTiles.map(TileSpec::create))
-        } else {
-            qsTileHost.changeTilesByUser(previousTiles, newTiles)
-        }
+        interactor.setTiles(newTiles.map(TileSpec::create))
     }
 
     override fun getContext(): Context {
-        return if (useNewHost) {
-            context
-        } else {
-            qsTileHost.context
-        }
+        return context
     }
 
     override fun getUserContext(): Context {
-        return if (useNewHost) {
-            interactor.userContext.value
-        } else {
-            qsTileHost.userContext
-        }
+        return interactor.userContext.value
     }
 
     override fun getUserId(): Int {
-        return if (useNewHost) {
-            interactor.userId.value
-        } else {
-            qsTileHost.userId
-        }
+        return interactor.userId.value
     }
 
     override fun createTile(tileSpec: String): QSTile? {
-        return qsTileHost.createTile(tileSpec)
+        return interactor.createTileSync(TileSpec.create(tileSpec))
     }
 
     override fun addTile(spec: String) {
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSModesEvent.kt b/packages/SystemUI/src/com/android/systemui/qs/QSModesEvent.kt
new file mode 100644
index 0000000..1891c41
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/QSModesEvent.kt
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs
+
+import com.android.internal.logging.UiEvent
+import com.android.internal.logging.UiEventLogger
+
+/** Events of user interactions with modes from the QS Modes dialog. {@see ModesDialogViewModel} */
+enum class QSModesEvent(private val _id: Int) : UiEventLogger.UiEventEnum {
+    @UiEvent(doc = "User turned manual Do Not Disturb on via modes dialog") QS_MODES_DND_ON(1870),
+    @UiEvent(doc = "User turned manual Do Not Disturb off via modes dialog") QS_MODES_DND_OFF(1871),
+    @UiEvent(doc = "User opened mode settings from the Do Not Disturb tile in the modes dialog")
+    QS_MODES_DND_SETTINGS(1872),
+    @UiEvent(doc = "User turned automatic mode on via modes dialog") QS_MODES_MODE_ON(1873),
+    @UiEvent(doc = "User turned automatic mode off via modes dialog") QS_MODES_MODE_OFF(1874),
+    @UiEvent(doc = "User opened mode settings from a mode tile in the modes dialog")
+    QS_MODES_MODE_SETTINGS(1875),
+    @UiEvent(doc = "User clicked on Settings from the modes dialog") QS_MODES_SETTINGS(1876),
+    @UiEvent(doc = "User clicked on Do Not Disturb tile, opening the time selection dialog")
+    QS_MODES_DURATION_DIALOG(1879);
+
+    override fun getId() = _id
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QSTileHost.java b/packages/SystemUI/src/com/android/systemui/qs/QSTileHost.java
deleted file mode 100644
index 03c2aa6..0000000
--- a/packages/SystemUI/src/com/android/systemui/qs/QSTileHost.java
+++ /dev/null
@@ -1,630 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
- * except in compliance with the License. You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software distributed under the
- * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the specific language governing
- * permissions and limitations under the License.
- */
-
-package com.android.systemui.qs;
-
-import android.content.ComponentName;
-import android.content.Context;
-import android.content.Intent;
-import android.content.res.Resources;
-import android.os.UserHandle;
-import android.os.UserManager;
-import android.provider.Settings.Secure;
-import android.text.TextUtils;
-import android.util.ArraySet;
-import android.util.Log;
-
-import androidx.annotation.MainThread;
-import androidx.annotation.Nullable;
-
-import com.android.internal.annotations.VisibleForTesting;
-import com.android.systemui.Dumpable;
-import com.android.systemui.ProtoDumpable;
-import com.android.systemui.dagger.SysUISingleton;
-import com.android.systemui.dagger.qualifiers.Main;
-import com.android.systemui.dump.nano.SystemUIProtoDump;
-import com.android.systemui.plugins.PluginListener;
-import com.android.systemui.plugins.PluginManager;
-import com.android.systemui.plugins.qs.QSFactory;
-import com.android.systemui.plugins.qs.QSTile;
-import com.android.systemui.qs.external.CustomTile;
-import com.android.systemui.qs.external.CustomTileStatePersister;
-import com.android.systemui.qs.external.TileLifecycleManager;
-import com.android.systemui.qs.external.TileServiceKey;
-import com.android.systemui.qs.logging.QSLogger;
-import com.android.systemui.qs.nano.QsTileState;
-import com.android.systemui.qs.pipeline.data.repository.CustomTileAddedRepository;
-import com.android.systemui.qs.pipeline.domain.interactor.PanelInteractor;
-import com.android.systemui.qs.pipeline.shared.QSPipelineFlagsRepository;
-import com.android.systemui.qs.tiles.di.NewQSTileFactory;
-import com.android.systemui.res.R;
-import com.android.systemui.settings.UserFileManager;
-import com.android.systemui.settings.UserTracker;
-import com.android.systemui.shade.ShadeController;
-import com.android.systemui.statusbar.phone.AutoTileManager;
-import com.android.systemui.tuner.TunerService;
-import com.android.systemui.tuner.TunerService.Tunable;
-import com.android.systemui.util.settings.SecureSettings;
-
-import dagger.Lazy;
-
-import org.jetbrains.annotations.NotNull;
-
-import java.io.PrintWriter;
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.LinkedHashMap;
-import java.util.List;
-import java.util.Objects;
-import java.util.Set;
-import java.util.concurrent.Executor;
-import java.util.function.Predicate;
-import java.util.stream.Collectors;
-
-import javax.inject.Inject;
-import javax.inject.Provider;
-
-/** Platform implementation of the quick settings tile host
- *
- * This class keeps track of the set of current tiles and is the in memory source of truth
- * (ground truth is kept in {@link Secure#QS_TILES}). When the ground truth changes,
- * {@link #onTuningChanged} will be called and the tiles will be re-created as needed.
- *
- * This class also provides the interface for adding/removing/changing tiles.
- */
-@SysUISingleton
-public class QSTileHost implements QSHost, Tunable, PluginListener<QSFactory>, ProtoDumpable,
-        PanelInteractor, CustomTileAddedRepository {
-    private static final String TAG = "QSTileHost";
-    private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
-
-    // Shared prefs that hold tile lifecycle info.
-    @VisibleForTesting
-    static final String TILES = "tiles_prefs";
-
-    private final Context mContext;
-    private final LinkedHashMap<String, QSTile> mTiles = new LinkedHashMap<>();
-    private final ArrayList<String> mTileSpecs = new ArrayList<>();
-    private final TunerService mTunerService;
-    private final PluginManager mPluginManager;
-    private final QSLogger mQSLogger;
-    private final CustomTileStatePersister mCustomTileStatePersister;
-    private final Executor mMainExecutor;
-    private final UserFileManager mUserFileManager;
-
-    private final List<Callback> mCallbacks = new ArrayList<>();
-    @Nullable
-    private AutoTileManager mAutoTiles;
-    private final ArrayList<QSFactory> mQsFactories = new ArrayList<>();
-    private int mCurrentUser;
-    private final Lazy<ShadeController> mShadeControllerProvider;
-    private Context mUserContext;
-    private UserTracker mUserTracker;
-    private SecureSettings mSecureSettings;
-    // Keep track of whether mTilesList contains the same information as the Settings value.
-    // This is a performance optimization to reduce the number of blocking calls to Settings from
-    // main thread.
-    // This is enforced by only cleaning the flag at the end of a successful run of #onTuningChanged
-    private boolean mTilesListDirty = true;
-
-    private TileLifecycleManager.Factory mTileLifeCycleManagerFactory;
-
-    private final QSPipelineFlagsRepository mFeatureFlags;
-
-    @Inject
-    public QSTileHost(Context context,
-            Lazy<NewQSTileFactory> newQsTileFactoryProvider,
-            QSFactory defaultFactory,
-            @Main Executor mainExecutor,
-            PluginManager pluginManager,
-            TunerService tunerService,
-            Provider<AutoTileManager> autoTiles,
-            Lazy<ShadeController> shadeControllerProvider,
-            QSLogger qsLogger,
-            UserTracker userTracker,
-            SecureSettings secureSettings,
-            CustomTileStatePersister customTileStatePersister,
-            TileLifecycleManager.Factory tileLifecycleManagerFactory,
-            UserFileManager userFileManager,
-            QSPipelineFlagsRepository featureFlags
-    ) {
-        mContext = context;
-        mUserContext = context;
-        mTunerService = tunerService;
-        mPluginManager = pluginManager;
-        mQSLogger = qsLogger;
-        mMainExecutor = mainExecutor;
-        mTileLifeCycleManagerFactory = tileLifecycleManagerFactory;
-        mUserFileManager = userFileManager;
-        mFeatureFlags = featureFlags;
-
-        mShadeControllerProvider = shadeControllerProvider;
-
-        if (featureFlags.getTilesEnabled()) {
-            mQsFactories.add(newQsTileFactoryProvider.get());
-        }
-        mQsFactories.add(defaultFactory);
-        pluginManager.addPluginListener(this, QSFactory.class, true);
-        mUserTracker = userTracker;
-        mCurrentUser = userTracker.getUserId();
-        mSecureSettings = secureSettings;
-        mCustomTileStatePersister = customTileStatePersister;
-
-        mainExecutor.execute(() -> {
-            // This is technically a hack to avoid circular dependency of
-            // QSTileHost -> XXXTile -> QSTileHost. Posting ensures creation
-            // finishes before creating any tiles.
-            tunerService.addTunable(this, TILES_SETTING);
-            // AutoTileManager can modify mTiles so make sure mTiles has already been initialized.
-            if (!mFeatureFlags.getPipelineEnabled()) {
-                mAutoTiles = autoTiles.get();
-            }
-        });
-    }
-
-    public void destroy() {
-        mTiles.values().forEach(tile -> tile.destroy());
-        mAutoTiles.destroy();
-        mTunerService.removeTunable(this);
-        mPluginManager.removePluginListener(this);
-    }
-
-    @Override
-    public void onPluginConnected(QSFactory plugin, Context pluginContext) {
-        // Give plugins priority over creation so they can override if they wish.
-        mQsFactories.add(0, plugin);
-        String value = mTunerService.getValue(TILES_SETTING);
-        // Force remove and recreate of all tiles.
-        onTuningChanged(TILES_SETTING, "");
-        onTuningChanged(TILES_SETTING, value);
-    }
-
-    @Override
-    public void onPluginDisconnected(QSFactory plugin) {
-        mQsFactories.remove(plugin);
-        // Force remove and recreate of all tiles.
-        String value = mTunerService.getValue(TILES_SETTING);
-        onTuningChanged(TILES_SETTING, "");
-        onTuningChanged(TILES_SETTING, value);
-    }
-
-    @Override
-    public void addCallback(Callback callback) {
-        mCallbacks.add(callback);
-    }
-
-    @Override
-    public void removeCallback(Callback callback) {
-        mCallbacks.remove(callback);
-    }
-
-    @Override
-    public Collection<QSTile> getTiles() {
-        return mTiles.values();
-    }
-
-    @Override
-    public void collapsePanels() {
-        mShadeControllerProvider.get().postAnimateCollapseShade();
-    }
-
-    @Override
-    public void forceCollapsePanels() {
-        mShadeControllerProvider.get().postAnimateForceCollapseShade();
-    }
-
-    @Override
-    public void openPanels() {
-        mShadeControllerProvider.get().postAnimateExpandQs();
-    }
-
-    @Override
-    public Context getContext() {
-        return mContext;
-    }
-
-    @Override
-    public Context getUserContext() {
-        return mUserContext;
-    }
-
-    @Override
-    public int getUserId() {
-        return mCurrentUser;
-    }
-
-    public int indexOf(String spec) {
-        return mTileSpecs.indexOf(spec);
-    }
-
-    /**
-     * Whenever the Secure Setting keeping track of the current tiles changes (or upon start) this
-     * will be called with the new value of the setting.
-     *
-     * This method will do the following:
-     * <ol>
-     *     <li>Destroy any existing tile that's not one of the current tiles (in the setting)</li>
-     *     <li>Create new tiles for those that don't already exist. If this tiles end up being
-     *         not available, they'll also be destroyed.</li>
-     *     <li>Save the resolved list of tiles (current tiles that are available) into the setting.
-     *         This means that after this call ends, the tiles in the Setting, {@link #mTileSpecs},
-     *         and visible tiles ({@link #mTiles}) must match.
-     *         </li>
-     * </ol>
-     *
-     * Additionally, if the user has changed, it'll do the following:
-     * <ul>
-     *     <li>Change the user for SystemUI tiles: {@link QSTile#userSwitch}.</li>
-     *     <li>Destroy any {@link CustomTile} and recreate it for the new user.</li>
-     * </ul>
-     *
-     * This happens in main thread as {@link com.android.systemui.tuner.TunerServiceImpl} dispatches
-     * in main thread.
-     *
-     * @see QSTile#isAvailable
-     */
-    @MainThread
-    @Override
-    public void onTuningChanged(String key, String newValue) {
-        if (!TILES_SETTING.equals(key)) {
-            return;
-        }
-        int currentUser = mUserTracker.getUserId();
-        if (currentUser != mCurrentUser) {
-            mUserContext = mUserTracker.getUserContext();
-            if (mAutoTiles != null) {
-                mAutoTiles.changeUser(UserHandle.of(currentUser));
-            }
-        }
-        // Do not process tiles if the flag is enabled.
-        if (mFeatureFlags.getPipelineEnabled()) {
-            return;
-        }
-        QSPipelineFlagsRepository.Utils.assertInLegacyMode();
-        if (newValue == null && UserManager.isDeviceInDemoMode(mContext)) {
-            newValue = mContext.getResources().getString(R.string.quick_settings_tiles_retail_mode);
-        }
-        final List<String> tileSpecs = loadTileSpecs(mContext, newValue);
-        if (tileSpecs.equals(mTileSpecs) && currentUser == mCurrentUser) return;
-        Log.d(TAG, "Recreating tiles: " + tileSpecs);
-        mTiles.entrySet().stream().filter(tile -> !tileSpecs.contains(tile.getKey())).forEach(
-                tile -> {
-                    Log.d(TAG, "Destroying tile: " + tile.getKey());
-                    mQSLogger.logTileDestroyed(tile.getKey(), "Tile removed");
-                    tile.getValue().destroy();
-                });
-        final LinkedHashMap<String, QSTile> newTiles = new LinkedHashMap<>();
-        for (String tileSpec : tileSpecs) {
-            QSTile tile = mTiles.get(tileSpec);
-            if (tile != null && (!(tile instanceof CustomTile)
-                    || ((CustomTile) tile).getUser() == currentUser)) {
-                if (tile.isAvailable()) {
-                    Log.d(TAG, "Adding " + tile);
-                    tile.removeCallbacks();
-                    if (!(tile instanceof CustomTile) && mCurrentUser != currentUser) {
-                        tile.userSwitch(currentUser);
-                    }
-                    newTiles.put(tileSpec, tile);
-                    mQSLogger.logTileAdded(tileSpec);
-                } else {
-                    tile.destroy();
-                    Log.d(TAG, "Destroying not available tile: " + tileSpec);
-                    mQSLogger.logTileDestroyed(tileSpec, "Tile not available");
-                }
-            } else {
-                // This means that the tile is a CustomTile AND the user is different, so let's
-                // destroy it
-                if (tile != null) {
-                    tile.destroy();
-                    Log.d(TAG, "Destroying tile for wrong user: " + tileSpec);
-                    mQSLogger.logTileDestroyed(tileSpec, "Tile for wrong user");
-                }
-                Log.d(TAG, "Creating tile: " + tileSpec);
-                try {
-                    tile = createTile(tileSpec);
-                    if (tile != null) {
-                        if (tile.isAvailable()) {
-                            newTiles.put(tileSpec, tile);
-                            mQSLogger.logTileAdded(tileSpec);
-                        } else {
-                            tile.destroy();
-                            Log.d(TAG, "Destroying not available tile: " + tileSpec);
-                            mQSLogger.logTileDestroyed(tileSpec, "Tile not available");
-                        }
-                    } else {
-                        Log.d(TAG, "No factory for a spec: " + tileSpec);
-                    }
-                } catch (Throwable t) {
-                    Log.w(TAG, "Error creating tile for spec: " + tileSpec, t);
-                }
-            }
-        }
-        mCurrentUser = currentUser;
-        List<String> currentSpecs = new ArrayList<>(mTileSpecs);
-        mTileSpecs.clear();
-        mTileSpecs.addAll(newTiles.keySet()); // Only add the valid (available) tiles.
-        mTiles.clear();
-        mTiles.putAll(newTiles);
-        if (newTiles.isEmpty() && !tileSpecs.isEmpty()) {
-            // If we didn't manage to create any tiles, set it to empty (default)
-            Log.d(TAG, "No valid tiles on tuning changed. Setting to default.");
-            changeTilesByUser(currentSpecs, loadTileSpecs(mContext, ""));
-        } else {
-            String resolvedTiles = TextUtils.join(",", mTileSpecs);
-            if (!resolvedTiles.equals(newValue)) {
-                // If the resolved tiles (those we actually ended up with) are different than
-                // the ones that are in the setting, update the Setting.
-                saveTilesToSettings(mTileSpecs);
-            }
-            mTilesListDirty = false;
-            for (int i = 0; i < mCallbacks.size(); i++) {
-                mCallbacks.get(i).onTilesChanged();
-            }
-        }
-    }
-
-    /**
-     * Only use with [CustomTile] if the tile doesn't exist anymore (and therefore doesn't need
-     * its lifecycle terminated).
-     */
-    @Override
-    public void removeTile(String spec) {
-        if (spec.startsWith(CustomTile.PREFIX)) {
-            // If the tile is removed (due to it not actually existing), mark it as removed. That
-            // way it will be marked as newly added if it appears in the future.
-            setTileAdded(CustomTile.getComponentFromSpec(spec), mCurrentUser, false);
-        }
-        mMainExecutor.execute(() -> changeTileSpecs(tileSpecs-> tileSpecs.remove(spec)));
-    }
-
-    /**
-     * Remove many tiles at once.
-     *
-     * It will only save to settings once (as opposed to {@link QSTileHost#removeTileByUser} called
-     * multiple times).
-     */
-    @Override
-    public void removeTiles(Collection<String> specs) {
-        mMainExecutor.execute(() -> changeTileSpecs(tileSpecs -> tileSpecs.removeAll(specs)));
-    }
-
-    /**
-     * Add a tile to the end
-     *
-     * @param spec string matching a pre-defined tilespec
-     */
-    public void addTile(String spec) {
-        addTile(spec, POSITION_AT_END);
-    }
-
-    @Override
-    public void addTile(String spec, int requestPosition) {
-        mMainExecutor.execute(() ->
-                changeTileSpecs(tileSpecs -> {
-                    if (tileSpecs.contains(spec)) return false;
-
-                    int size = tileSpecs.size();
-                    if (requestPosition == POSITION_AT_END || requestPosition >= size) {
-                        tileSpecs.add(spec);
-                    } else {
-                        tileSpecs.add(requestPosition, spec);
-                    }
-                    return true;
-                })
-        );
-    }
-
-    // When calling this, you may want to modify mTilesListDirty accordingly.
-    @MainThread
-    private void saveTilesToSettings(List<String> tileSpecs) {
-        Log.d(TAG, "Saving tiles: " + tileSpecs + " for user: " + mCurrentUser);
-        mSecureSettings.putStringForUser(TILES_SETTING, TextUtils.join(",", tileSpecs),
-                null /* tag */, false /* default */, mCurrentUser,
-                true /* overrideable by restore */);
-    }
-
-    @MainThread
-    private void changeTileSpecs(Predicate<List<String>> changeFunction) {
-        final List<String> tileSpecs;
-        if (!mTilesListDirty) {
-            tileSpecs = new ArrayList<>(mTileSpecs);
-        } else {
-            tileSpecs = loadTileSpecs(mContext,
-                    mSecureSettings.getStringForUser(TILES_SETTING, mCurrentUser));
-        }
-        if (changeFunction.test(tileSpecs)) {
-            mTilesListDirty = true;
-            saveTilesToSettings(tileSpecs);
-        }
-    }
-
-    @Override
-    public void addTile(ComponentName tile) {
-        addTile(tile, /* end */ false);
-    }
-
-    @Override
-    public void addTile(ComponentName tile, boolean end) {
-        String spec = CustomTile.toSpec(tile);
-        addTile(spec, end ? POSITION_AT_END : 0);
-    }
-
-    /**
-     * This will call through {@link #changeTilesByUser}. It should only be used when a tile is
-     * removed by a <b>user action</b> like {@code adb}.
-     */
-    @Override
-    public void removeTileByUser(ComponentName tile) {
-        mMainExecutor.execute(() -> {
-            List<String> newSpecs = new ArrayList<>(mTileSpecs);
-            if (newSpecs.remove(CustomTile.toSpec(tile))) {
-                changeTilesByUser(mTileSpecs, newSpecs);
-            }
-        });
-    }
-
-    /**
-     * Change the tiles triggered by the user editing.
-     * <p>
-     * This is not called on device start, or on user change.
-     *
-     * {@link android.service.quicksettings.TileService#onTileRemoved} will be called for tiles
-     * that are removed.
-     */
-    @MainThread
-    @Override
-    public void changeTilesByUser(List<String> previousTiles, List<String> newTiles) {
-        final List<String> copy = new ArrayList<>(previousTiles);
-        final int NP = copy.size();
-        for (int i = 0; i < NP; i++) {
-            String tileSpec = copy.get(i);
-            if (!tileSpec.startsWith(CustomTile.PREFIX)) continue;
-            if (!newTiles.contains(tileSpec)) {
-                ComponentName component = CustomTile.getComponentFromSpec(tileSpec);
-                Intent intent = new Intent().setComponent(component);
-                TileLifecycleManager lifecycleManager = mTileLifeCycleManagerFactory.create(
-                        intent, new UserHandle(mCurrentUser));
-                lifecycleManager.onStopListening();
-                lifecycleManager.onTileRemoved();
-                mCustomTileStatePersister.removeState(new TileServiceKey(component, mCurrentUser));
-                setTileAdded(component, mCurrentUser, false);
-                lifecycleManager.flushMessagesAndUnbind();
-            }
-        }
-        Log.d(TAG, "saveCurrentTiles " + newTiles);
-        mTilesListDirty = true;
-        saveTilesToSettings(newTiles);
-    }
-
-    @Nullable
-    @Override
-    public QSTile createTile(String tileSpec) {
-        for (int i = 0; i < mQsFactories.size(); i++) {
-            QSTile t = mQsFactories.get(i).createTile(tileSpec);
-            if (t != null) {
-                return t;
-            }
-        }
-        return null;
-    }
-
-    /**
-     * Check if a particular {@link CustomTile} has been added for a user and has not been removed
-     * since.
-     * @param componentName the {@link ComponentName} of the
-     *                      {@link android.service.quicksettings.TileService} associated with the
-     *                      tile.
-     * @param userId the user to check
-     */
-    @Override
-    public boolean isTileAdded(ComponentName componentName, int userId) {
-        return mUserFileManager
-                .getSharedPreferences(TILES, 0, userId)
-                .getBoolean(componentName.flattenToString(), false);
-    }
-
-    /**
-     * Persists whether a particular {@link CustomTile} has been added and it's currently in the
-     * set of selected tiles ({@link #mTiles}.
-     * @param componentName the {@link ComponentName} of the
-     *                      {@link android.service.quicksettings.TileService} associated
-     *                      with the tile.
-     * @param userId the user for this tile
-     * @param added {@code true} if the tile is being added, {@code false} otherwise
-     */
-    @Override
-    public void setTileAdded(ComponentName componentName, int userId, boolean added) {
-        mUserFileManager.getSharedPreferences(TILES, 0, userId)
-                .edit()
-                .putBoolean(componentName.flattenToString(), added)
-                .apply();
-    }
-
-    @Override
-    public List<String> getSpecs() {
-        return mTileSpecs;
-    }
-
-    protected static List<String> loadTileSpecs(Context context, String tileList) {
-        final Resources res = context.getResources();
-
-        if (TextUtils.isEmpty(tileList)) {
-            tileList = res.getString(R.string.quick_settings_tiles);
-            Log.d(TAG, "Loaded tile specs from default config: " + tileList);
-        } else {
-            Log.d(TAG, "Loaded tile specs from setting: " + tileList);
-        }
-        final ArrayList<String> tiles = new ArrayList<String>();
-        boolean addedDefault = false;
-        Set<String> addedSpecs = new ArraySet<>();
-        for (String tile : tileList.split(",")) {
-            tile = tile.trim();
-            if (tile.isEmpty()) continue;
-            if (tile.equals("default")) {
-                if (!addedDefault) {
-                    List<String> defaultSpecs = QSHost.getDefaultSpecs(context.getResources());
-                    for (String spec : defaultSpecs) {
-                        if (!addedSpecs.contains(spec)) {
-                            tiles.add(spec);
-                            addedSpecs.add(spec);
-                        }
-                    }
-                    addedDefault = true;
-                }
-            } else {
-                if (!addedSpecs.contains(tile)) {
-                    tiles.add(tile);
-                    addedSpecs.add(tile);
-                }
-            }
-        }
-
-        if (!tiles.contains("internet")) {
-            if (tiles.contains("wifi")) {
-                // Replace the WiFi with Internet, and remove the Cell
-                tiles.set(tiles.indexOf("wifi"), "internet");
-                tiles.remove("cell");
-            } else if (tiles.contains("cell")) {
-                // Replace the Cell with Internet
-                tiles.set(tiles.indexOf("cell"), "internet");
-            }
-        } else {
-            tiles.remove("wifi");
-            tiles.remove("cell");
-        }
-        return tiles;
-    }
-
-    @Override
-    public void dump(PrintWriter pw, String[] args) {
-        pw.println("QSTileHost:");
-        pw.println("tile specs: " + mTileSpecs);
-        pw.println("current user: " + mCurrentUser);
-        pw.println("is dirty: " + mTilesListDirty);
-        pw.println("tiles:");
-        mTiles.values().stream().filter(obj -> obj instanceof Dumpable)
-                .forEach(o -> ((Dumpable) o).dump(pw, args));
-    }
-
-    @Override
-    public void dumpProto(@NotNull SystemUIProtoDump systemUIProtoDump, @NotNull String[] args) {
-        List<QsTileState> data = mTiles.values().stream()
-                .map(QSTile::getState)
-                .map(TileStateToProtoKt::toProto)
-                .filter(Objects::nonNull)
-                .collect(Collectors.toList());
-
-        systemUIProtoDump.tiles = data.toArray(new QsTileState[0]);
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanelController.java b/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanelController.java
index f207b1d..bc695bdd4 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanelController.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/QuickQSPanelController.java
@@ -34,6 +34,7 @@
 import com.android.systemui.qs.dagger.QSScope;
 import com.android.systemui.qs.logging.QSLogger;
 import com.android.systemui.res.R;
+import com.android.systemui.scene.shared.flag.SceneContainerFlag;
 import com.android.systemui.statusbar.policy.SplitShadeStateController;
 import com.android.systemui.util.leak.RotationUtils;
 
@@ -77,9 +78,11 @@
     @Override
     protected void onInit() {
         super.onInit();
-        updateMediaExpansion();
-        mMediaHost.setShowsOnlyActiveMedia(true);
-        mMediaHost.init(MediaHierarchyManager.LOCATION_QQS);
+        if (!SceneContainerFlag.isEnabled()) {
+            updateMediaExpansion();
+            mMediaHost.setShowsOnlyActiveMedia(true);
+            mMediaHost.init(MediaHierarchyManager.LOCATION_QQS);
+        }
     }
 
     @Override
@@ -125,7 +128,9 @@
         if (newMaxTiles != mView.getNumQuickTiles()) {
             setMaxTiles(newMaxTiles);
         }
-        updateMediaExpansion();
+        if (!SceneContainerFlag.isEnabled()) {
+            updateMediaExpansion();
+        }
     }
 
     @Override
diff --git a/packages/SystemUI/src/com/android/systemui/qs/composefragment/QSFragmentCompose.kt b/packages/SystemUI/src/com/android/systemui/qs/composefragment/QSFragmentCompose.kt
new file mode 100644
index 0000000..5d81d4f
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/composefragment/QSFragmentCompose.kt
@@ -0,0 +1,442 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.composefragment
+
+import android.annotation.SuppressLint
+import android.graphics.Rect
+import android.os.Bundle
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import androidx.activity.OnBackPressedDispatcher
+import androidx.activity.OnBackPressedDispatcherOwner
+import androidx.activity.setViewTreeOnBackPressedDispatcherOwner
+import androidx.compose.animation.AnimatedContent
+import androidx.compose.animation.AnimatedVisibility
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.Spacer
+import androidx.compose.foundation.layout.WindowInsets
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.navigationBars
+import androidx.compose.foundation.layout.windowInsetsPadding
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.DisposableEffect
+import androidx.compose.runtime.getValue
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.layout.layout
+import androidx.compose.ui.layout.onGloballyPositioned
+import androidx.compose.ui.layout.positionInRoot
+import androidx.compose.ui.platform.ComposeView
+import androidx.compose.ui.res.dimensionResource
+import androidx.compose.ui.unit.round
+import androidx.lifecycle.Lifecycle
+import androidx.lifecycle.compose.collectAsStateWithLifecycle
+import androidx.lifecycle.lifecycleScope
+import androidx.lifecycle.repeatOnLifecycle
+import com.android.compose.modifiers.height
+import com.android.compose.modifiers.padding
+import com.android.compose.theme.PlatformTheme
+import com.android.systemui.compose.modifiers.sysuiResTag
+import com.android.systemui.lifecycle.repeatWhenAttached
+import com.android.systemui.plugins.qs.QS
+import com.android.systemui.plugins.qs.QSContainerController
+import com.android.systemui.qs.composefragment.viewmodel.QSFragmentComposeViewModel
+import com.android.systemui.qs.flags.QSComposeFragment
+import com.android.systemui.qs.footer.ui.compose.FooterActions
+import com.android.systemui.qs.panels.ui.compose.QuickQuickSettings
+import com.android.systemui.qs.ui.composable.QuickSettingsTheme
+import com.android.systemui.qs.ui.composable.ShadeBody
+import com.android.systemui.res.R
+import com.android.systemui.util.LifecycleFragment
+import java.util.function.Consumer
+import javax.inject.Inject
+import kotlinx.coroutines.awaitCancellation
+import kotlinx.coroutines.coroutineScope
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.collectLatest
+import kotlinx.coroutines.launch
+
+@SuppressLint("ValidFragment")
+class QSFragmentCompose
+@Inject
+constructor(
+    private val qsFragmentComposeViewModelFactory: QSFragmentComposeViewModel.Factory,
+) : LifecycleFragment(), QS {
+
+    private val scrollListener = MutableStateFlow<QS.ScrollListener?>(null)
+    private val heightListener = MutableStateFlow<QS.HeightListener?>(null)
+    private val qsContainerController = MutableStateFlow<QSContainerController?>(null)
+
+    private lateinit var viewModel: QSFragmentComposeViewModel
+
+    // Starting with a non-zero value makes it so that it has a non-zero height on first expansion
+    // This is important for `QuickSettingsControllerImpl.mMinExpansionHeight` to detect a "change".
+    private val qqsHeight = MutableStateFlow(1)
+    private val qsHeight = MutableStateFlow(0)
+    private val qqsVisible = MutableStateFlow(false)
+    private val qqsPositionOnRoot = Rect()
+    private val composeViewPositionOnScreen = Rect()
+
+    override fun onCreate(savedInstanceState: Bundle?) {
+        super.onCreate(savedInstanceState)
+
+        QSComposeFragment.isUnexpectedlyInLegacyMode()
+        viewModel = qsFragmentComposeViewModelFactory.create(lifecycleScope)
+
+        setListenerCollections()
+    }
+
+    override fun onCreateView(
+        inflater: LayoutInflater,
+        container: ViewGroup?,
+        savedInstanceState: Bundle?
+    ): View {
+        val context = inflater.context
+        return ComposeView(context).apply {
+            setBackPressedDispatcher()
+            setContent {
+                PlatformTheme {
+                    val visible by viewModel.qsVisible.collectAsStateWithLifecycle()
+                    val qsState by viewModel.expansionState.collectAsStateWithLifecycle()
+
+                    AnimatedVisibility(
+                        visible = visible,
+                        modifier = Modifier.windowInsetsPadding(WindowInsets.navigationBars)
+                    ) {
+                        AnimatedContent(targetState = qsState) {
+                            when (it) {
+                                QSFragmentComposeViewModel.QSExpansionState.QQS -> {
+                                    QuickQuickSettingsElement()
+                                }
+                                QSFragmentComposeViewModel.QSExpansionState.QS -> {
+                                    QuickSettingsElement()
+                                }
+                                else -> {}
+                            }
+                        }
+                    }
+                }
+            }
+        }
+    }
+
+    override fun setPanelView(notificationPanelView: QS.HeightListener?) {
+        heightListener.value = notificationPanelView
+    }
+
+    override fun hideImmediately() {
+        //        view?.animate()?.cancel()
+        //        view?.y = -qsMinExpansionHeight.toFloat()
+    }
+
+    override fun getQsMinExpansionHeight(): Int {
+        // TODO (b/353253277) implement split screen
+        return qqsHeight.value
+    }
+
+    override fun getDesiredHeight(): Int {
+        /*
+         * Looking at the code, it seems that
+         * * If customizing, then the height is that of the view post-layout, which is set by
+         *   QSContainerImpl.calculateContainerHeight, which is the height the customizer takes
+         * * If not customizing, it's the measured height. So we may want to surface that.
+         */
+        return view?.height ?: 0
+    }
+
+    override fun setHeightOverride(desiredHeight: Int) {
+        viewModel.heightOverrideValue = desiredHeight
+    }
+
+    override fun setHeaderClickable(qsExpansionEnabled: Boolean) {
+        // Empty method
+    }
+
+    override fun isCustomizing(): Boolean {
+        return viewModel.containerViewModel.editModeViewModel.isEditing.value
+    }
+
+    override fun closeCustomizer() {
+        viewModel.containerViewModel.editModeViewModel.stopEditing()
+    }
+
+    override fun setOverscrolling(overscrolling: Boolean) {
+        viewModel.stackScrollerOverscrollingValue = overscrolling
+    }
+
+    override fun setExpanded(qsExpanded: Boolean) {
+        viewModel.isQSExpanded = qsExpanded
+    }
+
+    override fun setListening(listening: Boolean) {
+        // Not needed, views start listening and collection when composed
+    }
+
+    override fun setQsVisible(qsVisible: Boolean) {
+        viewModel.isQSVisible = qsVisible
+    }
+
+    override fun isShowingDetail(): Boolean {
+        return isCustomizing
+    }
+
+    override fun closeDetail() {
+        closeCustomizer()
+    }
+
+    override fun animateHeaderSlidingOut() {
+        // TODO(b/353254353)
+    }
+
+    override fun setQsExpansion(
+        qsExpansionFraction: Float,
+        panelExpansionFraction: Float,
+        headerTranslation: Float,
+        squishinessFraction: Float
+    ) {
+        viewModel.qsExpansionValue = qsExpansionFraction
+        viewModel.panelExpansionFractionValue = panelExpansionFraction
+        viewModel.squishinessFractionValue = squishinessFraction
+
+        // TODO(b/353254353) Handle header translation
+    }
+
+    override fun setHeaderListening(listening: Boolean) {
+        // Not needed, header will start listening as soon as it's composed
+    }
+
+    override fun notifyCustomizeChanged() {
+        // Not needed, only called from inside customizer
+    }
+
+    override fun setContainerController(controller: QSContainerController?) {
+        qsContainerController.value = controller
+    }
+
+    override fun setCollapseExpandAction(action: Runnable?) {
+        // Nothing to do yet. But this should be wired to a11y
+    }
+
+    override fun getHeightDiff(): Int {
+        return 0 // For now TODO(b/353254353)
+    }
+
+    override fun getHeader(): View? {
+        QSComposeFragment.isUnexpectedlyInLegacyMode()
+        return null
+    }
+
+    override fun setShouldUpdateSquishinessOnMedia(shouldUpdate: Boolean) {
+        super.setShouldUpdateSquishinessOnMedia(shouldUpdate)
+        // TODO (b/353253280)
+    }
+
+    override fun setInSplitShade(shouldTranslate: Boolean) {
+        // TODO (b/356435605)
+    }
+
+    override fun setTransitionToFullShadeProgress(
+        isTransitioningToFullShade: Boolean,
+        qsTransitionFraction: Float,
+        qsSquishinessFraction: Float
+    ) {
+        super.setTransitionToFullShadeProgress(
+            isTransitioningToFullShade,
+            qsTransitionFraction,
+            qsSquishinessFraction
+        )
+    }
+
+    override fun setFancyClipping(
+        leftInset: Int,
+        top: Int,
+        rightInset: Int,
+        bottom: Int,
+        cornerRadius: Int,
+        visible: Boolean,
+        fullWidth: Boolean
+    ) {}
+
+    override fun isFullyCollapsed(): Boolean {
+        return !viewModel.isQSVisible
+    }
+
+    override fun setCollapsedMediaVisibilityChangedListener(listener: Consumer<Boolean>?) {
+        // TODO (b/353253280)
+    }
+
+    override fun setScrollListener(scrollListener: QS.ScrollListener?) {
+        this.scrollListener.value = scrollListener
+    }
+
+    override fun setOverScrollAmount(overScrollAmount: Int) {
+        super.setOverScrollAmount(overScrollAmount)
+    }
+
+    override fun setIsNotificationPanelFullWidth(isFullWidth: Boolean) {
+        viewModel.isSmallScreenValue = isFullWidth
+    }
+
+    override fun getHeaderTop(): Int {
+        return viewModel.qqsHeaderHeight.value
+    }
+
+    override fun getHeaderBottom(): Int {
+        return headerTop + qqsHeight.value
+    }
+
+    override fun getHeaderLeft(): Int {
+        return qqsPositionOnRoot.left
+    }
+
+    override fun getHeaderBoundsOnScreen(outBounds: Rect) {
+        outBounds.set(qqsPositionOnRoot)
+        view?.getBoundsOnScreen(composeViewPositionOnScreen)
+            ?: run { composeViewPositionOnScreen.setEmpty() }
+        qqsPositionOnRoot.offset(composeViewPositionOnScreen.left, composeViewPositionOnScreen.top)
+    }
+
+    override fun isHeaderShown(): Boolean {
+        return qqsVisible.value
+    }
+
+    private fun setListenerCollections() {
+        lifecycleScope.launch {
+            lifecycle.repeatOnLifecycle(Lifecycle.State.STARTED) {
+                launch {
+                    //                    TODO
+                    //                    setListenerJob(
+                    //                            scrollListener,
+                    //
+                    //                    )
+                }
+                launch {
+                    setListenerJob(
+                        heightListener,
+                        viewModel.containerViewModel.editModeViewModel.isEditing
+                    ) {
+                        onQsHeightChanged()
+                    }
+                }
+                launch {
+                    setListenerJob(
+                        qsContainerController,
+                        viewModel.containerViewModel.editModeViewModel.isEditing
+                    ) {
+                        setCustomizerShowing(it)
+                    }
+                }
+            }
+        }
+    }
+
+    @Composable
+    private fun QuickQuickSettingsElement() {
+        val qqsPadding by viewModel.qqsHeaderHeight.collectAsStateWithLifecycle()
+        DisposableEffect(Unit) {
+            qqsVisible.value = true
+
+            onDispose { qqsVisible.value = false }
+        }
+        Column(modifier = Modifier.sysuiResTag("quick_qs_panel")) {
+            QuickQuickSettings(
+                viewModel = viewModel.containerViewModel.quickQuickSettingsViewModel,
+                modifier =
+                    Modifier.onGloballyPositioned { coordinates ->
+                            val (leftFromRoot, topFromRoot) = coordinates.positionInRoot().round()
+                            val (width, height) = coordinates.size
+                            qqsPositionOnRoot.set(
+                                leftFromRoot,
+                                topFromRoot,
+                                leftFromRoot + width,
+                                topFromRoot + height
+                            )
+                        }
+                        .layout { measurable, constraints ->
+                            val placeable = measurable.measure(constraints)
+                            qqsHeight.value = placeable.height
+
+                            layout(placeable.width, placeable.height) { placeable.place(0, 0) }
+                        }
+                        .padding(top = { qqsPadding })
+            )
+            Spacer(modifier = Modifier.weight(1f))
+        }
+    }
+
+    @Composable
+    private fun QuickSettingsElement() {
+        val qqsPadding by viewModel.qqsHeaderHeight.collectAsStateWithLifecycle()
+        val qsExtraPadding = dimensionResource(R.dimen.qs_panel_padding_top)
+        Column {
+            Box(modifier = Modifier.fillMaxSize().weight(1f)) {
+                Column {
+                    Spacer(modifier = Modifier.height { qqsPadding + qsExtraPadding.roundToPx() })
+                    ShadeBody(viewModel = viewModel.containerViewModel)
+                }
+            }
+            QuickSettingsTheme {
+                FooterActions(
+                    viewModel = viewModel.footerActionsViewModel,
+                    qsVisibilityLifecycleOwner = this@QSFragmentCompose,
+                    modifier = Modifier.sysuiResTag("qs_footer_actions")
+                )
+            }
+        }
+    }
+}
+
+private fun View.setBackPressedDispatcher() {
+    repeatWhenAttached {
+        repeatOnLifecycle(Lifecycle.State.CREATED) {
+            setViewTreeOnBackPressedDispatcherOwner(
+                object : OnBackPressedDispatcherOwner {
+                    override val onBackPressedDispatcher =
+                        OnBackPressedDispatcher().apply {
+                            setOnBackInvokedDispatcher(it.viewRootImpl.onBackInvokedDispatcher)
+                        }
+
+                    override val lifecycle: Lifecycle = this@repeatWhenAttached.lifecycle
+                }
+            )
+        }
+    }
+}
+
+private suspend inline fun <Listener : Any, Data> setListenerJob(
+    listenerFlow: MutableStateFlow<Listener?>,
+    dataFlow: Flow<Data>,
+    crossinline onCollect: suspend Listener.(Data) -> Unit
+) {
+    coroutineScope {
+        try {
+            listenerFlow.collectLatest { listenerOrNull ->
+                listenerOrNull?.let { currentListener ->
+                    launch {
+                        // Called when editing mode changes
+                        dataFlow.collect { currentListener.onCollect(it) }
+                    }
+                }
+            }
+            awaitCancellation()
+        } finally {
+            listenerFlow.value = null
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/composefragment/viewmodel/QSFragmentComposeViewModel.kt b/packages/SystemUI/src/com/android/systemui/qs/composefragment/viewmodel/QSFragmentComposeViewModel.kt
new file mode 100644
index 0000000..9e109e4
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/composefragment/viewmodel/QSFragmentComposeViewModel.kt
@@ -0,0 +1,196 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.composefragment.viewmodel
+
+import android.content.res.Resources
+import android.graphics.Rect
+import androidx.lifecycle.LifecycleCoroutineScope
+import com.android.systemui.common.ui.domain.interactor.ConfigurationInteractor
+import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.qs.FooterActionsController
+import com.android.systemui.qs.footer.ui.viewmodel.FooterActionsViewModel
+import com.android.systemui.qs.ui.viewmodel.QuickSettingsContainerViewModel
+import com.android.systemui.shade.LargeScreenHeaderHelper
+import com.android.systemui.shade.transition.LargeScreenShadeInterpolator
+import com.android.systemui.statusbar.SysuiStatusBarStateController
+import com.android.systemui.statusbar.disableflags.data.repository.DisableFlagsRepository
+import com.android.systemui.statusbar.phone.KeyguardBypassController
+import com.android.systemui.util.LargeScreenUtils
+import dagger.assisted.Assisted
+import dagger.assisted.AssistedFactory
+import dagger.assisted.AssistedInject
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.asStateFlow
+import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.stateIn
+import kotlinx.coroutines.launch
+
+class QSFragmentComposeViewModel
+@AssistedInject
+constructor(
+    val containerViewModel: QuickSettingsContainerViewModel,
+    @Main private val resources: Resources,
+    private val footerActionsViewModelFactory: FooterActionsViewModel.Factory,
+    private val footerActionsController: FooterActionsController,
+    private val sysuiStatusBarStateController: SysuiStatusBarStateController,
+    private val keyguardBypassController: KeyguardBypassController,
+    private val disableFlagsRepository: DisableFlagsRepository,
+    private val largeScreenShadeInterpolator: LargeScreenShadeInterpolator,
+    private val configurationInteractor: ConfigurationInteractor,
+    private val largeScreenHeaderHelper: LargeScreenHeaderHelper,
+    @Assisted private val lifecycleScope: LifecycleCoroutineScope,
+) {
+    val footerActionsViewModel =
+        footerActionsViewModelFactory.create(lifecycleScope).also {
+            lifecycleScope.launch { footerActionsController.init() }
+        }
+
+    private val _qsBounds = MutableStateFlow(Rect())
+
+    private val _qsExpanded = MutableStateFlow(false)
+    var isQSExpanded: Boolean
+        get() = _qsExpanded.value
+        set(value) {
+            _qsExpanded.value = value
+        }
+
+    private val _qsVisible = MutableStateFlow(false)
+    val qsVisible = _qsVisible.asStateFlow()
+    var isQSVisible: Boolean
+        get() = qsVisible.value
+        set(value) {
+            _qsVisible.value = value
+        }
+
+    private val _qsExpansion = MutableStateFlow(0f)
+    var qsExpansionValue: Float
+        get() = _qsExpansion.value
+        set(value) {
+            _qsExpansion.value = value
+        }
+
+    private val _panelFraction = MutableStateFlow(0f)
+    var panelExpansionFractionValue: Float
+        get() = _panelFraction.value
+        set(value) {
+            _panelFraction.value = value
+        }
+
+    private val _squishinessFraction = MutableStateFlow(0f)
+    var squishinessFractionValue: Float
+        get() = _squishinessFraction.value
+        set(value) {
+            _squishinessFraction.value = value
+        }
+
+    val qqsHeaderHeight =
+        configurationInteractor.onAnyConfigurationChange
+            .map {
+                if (LargeScreenUtils.shouldUseLargeScreenShadeHeader(resources)) {
+                    0
+                } else {
+                    largeScreenHeaderHelper.getLargeScreenHeaderHeight()
+                }
+            }
+            .stateIn(lifecycleScope, SharingStarted.WhileSubscribed(), 0)
+
+    private val _headerAnimating = MutableStateFlow(false)
+
+    private val _stackScrollerOverscrolling = MutableStateFlow(false)
+    var stackScrollerOverscrollingValue: Boolean
+        get() = _stackScrollerOverscrolling.value
+        set(value) {
+            _stackScrollerOverscrolling.value = value
+        }
+
+    private val qsDisabled =
+        disableFlagsRepository.disableFlags
+            .map { !it.isQuickSettingsEnabled() }
+            .stateIn(
+                lifecycleScope,
+                SharingStarted.WhileSubscribed(),
+                !disableFlagsRepository.disableFlags.value.isQuickSettingsEnabled()
+            )
+
+    private val _showCollapsedOnKeyguard = MutableStateFlow(false)
+
+    private val _keyguardAndExpanded = MutableStateFlow(false)
+
+    private val _statusBarState = MutableStateFlow(-1)
+
+    private val _viewHeight = MutableStateFlow(0)
+
+    private val _headerTranslation = MutableStateFlow(0f)
+
+    private val _inSplitShade = MutableStateFlow(false)
+
+    private val _transitioningToFullShade = MutableStateFlow(false)
+
+    private val _lockscreenToShadeProgress = MutableStateFlow(false)
+
+    private val _overscrolling = MutableStateFlow(false)
+
+    private val _isSmallScreen = MutableStateFlow(false)
+    var isSmallScreenValue: Boolean
+        get() = _isSmallScreen.value
+        set(value) {
+            _isSmallScreen.value = value
+        }
+
+    private val _shouldUpdateMediaSquishiness = MutableStateFlow(false)
+
+    private val _heightOverride = MutableStateFlow(-1)
+    val heightOverride = _heightOverride.asStateFlow()
+    var heightOverrideValue: Int
+        get() = heightOverride.value
+        set(value) {
+            _heightOverride.value = value
+        }
+
+    val expansionState: StateFlow<QSExpansionState> =
+        combine(
+                _stackScrollerOverscrolling,
+                _qsExpanded,
+                _qsExpansion,
+            ) { args: Array<Any> ->
+                val expansion = args[2] as Float
+                if (expansion > 0.5f) {
+                    QSExpansionState.QS
+                } else {
+                    QSExpansionState.QQS
+                }
+            }
+            .stateIn(lifecycleScope, SharingStarted.WhileSubscribed(), QSExpansionState.QQS)
+
+    @AssistedFactory
+    interface Factory {
+        fun create(lifecycleScope: LifecycleCoroutineScope): QSFragmentComposeViewModel
+    }
+
+    sealed interface QSExpansionState {
+        data object QQS : QSExpansionState
+
+        data object QS : QSExpansionState
+
+        @JvmInline value class Expanding(val progress: Float) : QSExpansionState
+
+        @JvmInline value class Collapsing(val progress: Float) : QSExpansionState
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/dagger/QSHostModule.kt b/packages/SystemUI/src/com/android/systemui/qs/dagger/QSHostModule.kt
index 496a6f8..a947d36 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/dagger/QSHostModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/dagger/QSHostModule.kt
@@ -18,17 +18,14 @@
 
 import com.android.systemui.qs.QSHost
 import com.android.systemui.qs.QSHostAdapter
-import com.android.systemui.qs.QSTileHost
 import com.android.systemui.qs.QsEventLogger
 import com.android.systemui.qs.QsEventLoggerImpl
 import com.android.systemui.qs.pipeline.data.repository.CustomTileAddedRepository
 import com.android.systemui.qs.pipeline.data.repository.CustomTileAddedSharedPrefsRepository
 import com.android.systemui.qs.pipeline.domain.interactor.PanelInteractor
 import com.android.systemui.qs.pipeline.domain.interactor.PanelInteractorImpl
-import com.android.systemui.qs.pipeline.shared.QSPipelineFlagsRepository
 import dagger.Binds
 import dagger.Module
-import dagger.Provides
 
 @Module
 interface QSHostModule {
@@ -37,36 +34,10 @@
 
     @Binds fun provideEventLogger(impl: QsEventLoggerImpl): QsEventLogger
 
-    @Module
-    companion object {
-        private const val MAX_QS_INSTANCE_ID = 1 shl 20
+    @Binds fun providePanelInteractor(impl: PanelInteractorImpl): PanelInteractor
 
-        @Provides
-        @JvmStatic
-        fun providePanelInteractor(
-            featureFlags: QSPipelineFlagsRepository,
-            qsHost: QSTileHost,
-            panelInteractorImpl: PanelInteractorImpl
-        ): PanelInteractor {
-            return if (featureFlags.pipelineEnabled) {
-                panelInteractorImpl
-            } else {
-                qsHost
-            }
-        }
-
-        @Provides
-        @JvmStatic
-        fun provideCustomTileAddedRepository(
-            featureFlags: QSPipelineFlagsRepository,
-            qsHost: QSTileHost,
-            customTileAddedRepository: CustomTileAddedSharedPrefsRepository
-        ): CustomTileAddedRepository {
-            return if (featureFlags.pipelineEnabled) {
-                customTileAddedRepository
-            } else {
-                qsHost
-            }
-        }
-    }
+    @Binds
+    fun provideCustomTileAddedRepository(
+        impl: CustomTileAddedSharedPrefsRepository
+    ): CustomTileAddedRepository
 }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/dagger/QSModule.java b/packages/SystemUI/src/com/android/systemui/qs/dagger/QSModule.java
index b705a03..29bcad4 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/dagger/QSModule.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/dagger/QSModule.java
@@ -16,17 +16,7 @@
 
 package com.android.systemui.qs.dagger;
 
-import static com.android.systemui.qs.dagger.QSFlagsModule.RBC_AVAILABLE;
-
-import android.content.Context;
-import android.os.Handler;
-
-import com.android.systemui.dagger.NightDisplayListenerModule;
-import com.android.systemui.dagger.SysUISingleton;
-import com.android.systemui.dagger.qualifiers.Background;
 import com.android.systemui.media.dagger.MediaModule;
-import com.android.systemui.qs.AutoAddTracker;
-import com.android.systemui.qs.QSHost;
 import com.android.systemui.qs.ReduceBrightColorsController;
 import com.android.systemui.qs.ReduceBrightColorsControllerImpl;
 import com.android.systemui.qs.external.QSExternalModule;
@@ -36,24 +26,12 @@
 import com.android.systemui.qs.tiles.di.QSTilesModule;
 import com.android.systemui.qs.ui.adapter.QSSceneAdapter;
 import com.android.systemui.qs.ui.adapter.QSSceneAdapterImpl;
-import com.android.systemui.statusbar.phone.AutoTileManager;
-import com.android.systemui.statusbar.phone.ManagedProfileController;
-import com.android.systemui.statusbar.policy.CastController;
-import com.android.systemui.statusbar.policy.DataSaverController;
-import com.android.systemui.statusbar.policy.DeviceControlsController;
-import com.android.systemui.statusbar.policy.HotspotController;
-import com.android.systemui.statusbar.policy.SafetyController;
-import com.android.systemui.statusbar.policy.WalletController;
-import com.android.systemui.util.settings.SecureSettings;
-
-import dagger.Binds;
-import dagger.Module;
-import dagger.Provides;
-import dagger.multibindings.Multibinds;
 
 import java.util.Map;
 
-import javax.inject.Named;
+import dagger.Binds;
+import dagger.Module;
+import dagger.multibindings.Multibinds;
 
 /**
  * Module for QS dependencies
@@ -78,45 +56,6 @@
     @Multibinds
     Map<String, QSTileImpl<?>> tileMap();
 
-    @Provides
-    @SysUISingleton
-    static AutoTileManager provideAutoTileManager(
-            Context context,
-            AutoAddTracker.Builder autoAddTrackerBuilder,
-            QSHost host,
-            @Background Handler handler,
-            SecureSettings secureSettings,
-            HotspotController hotspotController,
-            DataSaverController dataSaverController,
-            ManagedProfileController managedProfileController,
-            NightDisplayListenerModule.Builder nightDisplayListenerBuilder,
-            CastController castController,
-            ReduceBrightColorsController reduceBrightColorsController,
-            DeviceControlsController deviceControlsController,
-            WalletController walletController,
-            SafetyController safetyController,
-            @Named(RBC_AVAILABLE) boolean isReduceBrightColorsAvailable) {
-        AutoTileManager manager = new AutoTileManager(
-                context,
-                autoAddTrackerBuilder,
-                host,
-                handler,
-                secureSettings,
-                hotspotController,
-                dataSaverController,
-                managedProfileController,
-                nightDisplayListenerBuilder,
-                castController,
-                reduceBrightColorsController,
-                deviceControlsController,
-                walletController,
-                safetyController,
-                isReduceBrightColorsAvailable
-        );
-        manager.init();
-        return manager;
-    }
-
     @Binds
     QSSceneAdapter bindsQsSceneInteractor(QSSceneAdapterImpl impl);
 
diff --git a/packages/SystemUI/src/com/android/systemui/qs/external/PackageManagerAdapter.java b/packages/SystemUI/src/com/android/systemui/qs/external/PackageManagerAdapter.java
index 6cf4441..28e4fd0 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/external/PackageManagerAdapter.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/external/PackageManagerAdapter.java
@@ -26,6 +26,8 @@
 import android.content.pm.ServiceInfo;
 import android.os.RemoteException;
 
+import androidx.annotation.Nullable;
+
 import javax.inject.Inject;
 
 // Adapter that wraps calls to PackageManager or IPackageManager for {@link TileLifecycleManager}.
@@ -45,6 +47,7 @@
         mIPackageManager = AppGlobals.getPackageManager();
     }
 
+    @Nullable
     public ServiceInfo getServiceInfo(ComponentName className, int flags, int userId)
             throws RemoteException {
         return mIPackageManager.getServiceInfo(className, flags, userId);
diff --git a/packages/SystemUI/src/com/android/systemui/qs/external/TileLifecycleManager.java b/packages/SystemUI/src/com/android/systemui/qs/external/TileLifecycleManager.java
index 96df728..cbcf68c 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/external/TileLifecycleManager.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/external/TileLifecycleManager.java
@@ -188,10 +188,10 @@
     public boolean isActiveTile() {
         try {
             ServiceInfo info = mPackageManagerAdapter.getServiceInfo(mIntent.getComponent(),
-                    META_DATA_QUERY_FLAGS);
-            return info.metaData != null
+                    META_DATA_QUERY_FLAGS, mUser.getIdentifier());
+            return info != null && info.metaData != null
                     && info.metaData.getBoolean(TileService.META_DATA_ACTIVE_TILE, false);
-        } catch (PackageManager.NameNotFoundException e) {
+        } catch (RemoteException e) {
             return false;
         }
     }
@@ -206,10 +206,10 @@
     public boolean isToggleableTile() {
         try {
             ServiceInfo info = mPackageManagerAdapter.getServiceInfo(mIntent.getComponent(),
-                    META_DATA_QUERY_FLAGS);
-            return info.metaData != null
+                    META_DATA_QUERY_FLAGS, mUser.getIdentifier());
+            return info != null && info.metaData != null
                     && info.metaData.getBoolean(TileService.META_DATA_TOGGLEABLE_TILE, false);
-        } catch (PackageManager.NameNotFoundException e) {
+        } catch (RemoteException e) {
             return false;
         }
     }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/footer/ui/viewmodel/FooterActionsViewModel.kt b/packages/SystemUI/src/com/android/systemui/qs/footer/ui/viewmodel/FooterActionsViewModel.kt
index ba45d17..6dc101a 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/footer/ui/viewmodel/FooterActionsViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/footer/ui/viewmodel/FooterActionsViewModel.kt
@@ -21,6 +21,7 @@
 import android.view.ContextThemeWrapper
 import androidx.lifecycle.DefaultLifecycleObserver
 import androidx.lifecycle.Lifecycle
+import androidx.lifecycle.LifecycleCoroutineScope
 import androidx.lifecycle.LifecycleOwner
 import com.android.settingslib.Utils
 import com.android.systemui.animation.Expandable
@@ -41,6 +42,7 @@
 import javax.inject.Named
 import javax.inject.Provider
 import kotlin.math.max
+import kotlinx.coroutines.awaitCancellation
 import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.MutableStateFlow
 import kotlinx.coroutines.flow.StateFlow
@@ -48,6 +50,8 @@
 import kotlinx.coroutines.flow.combine
 import kotlinx.coroutines.flow.distinctUntilChanged
 import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.isActive
+import kotlinx.coroutines.launch
 
 private const val TAG = "FooterActionsViewModel"
 
@@ -140,6 +144,30 @@
                 showPowerButton,
             )
         }
+
+        fun create(lifecycleCoroutineScope: LifecycleCoroutineScope): FooterActionsViewModel {
+            val globalActionsDialogLite = globalActionsDialogLiteProvider.get()
+            if (lifecycleCoroutineScope.isActive) {
+                lifecycleCoroutineScope.launch {
+                    try {
+                        awaitCancellation()
+                    } finally {
+                        globalActionsDialogLite.destroy()
+                    }
+                }
+            } else {
+                globalActionsDialogLite.destroy()
+            }
+
+            return FooterActionsViewModel(
+                context,
+                footerActionsInteractor,
+                falsingManager,
+                globalActionsDialogLite,
+                activityStarter,
+                showPowerButton,
+            )
+        }
     }
 }
 
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/DragAndDropState.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/DragAndDropState.kt
index 2c57813..0b9cd96 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/DragAndDropState.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/DragAndDropState.kt
@@ -20,103 +20,40 @@
 import androidx.compose.foundation.draganddrop.dragAndDropSource
 import androidx.compose.foundation.draganddrop.dragAndDropTarget
 import androidx.compose.foundation.gestures.detectTapGestures
+import androidx.compose.foundation.lazy.grid.LazyGridItemInfo
+import androidx.compose.foundation.lazy.grid.LazyGridState
 import androidx.compose.runtime.Composable
-import androidx.compose.runtime.MutableState
-import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.getValue
 import androidx.compose.runtime.remember
+import androidx.compose.runtime.rememberUpdatedState
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.draganddrop.DragAndDropEvent
 import androidx.compose.ui.draganddrop.DragAndDropTarget
 import androidx.compose.ui.draganddrop.DragAndDropTransferData
 import androidx.compose.ui.draganddrop.mimeTypes
+import androidx.compose.ui.draganddrop.toAndroidDragEvent
+import androidx.compose.ui.geometry.Offset
+import androidx.compose.ui.unit.IntRect
+import androidx.compose.ui.unit.center
+import androidx.compose.ui.unit.toRect
 import com.android.systemui.qs.panels.shared.model.SizedTile
 import com.android.systemui.qs.panels.ui.viewmodel.EditTileViewModel
 import com.android.systemui.qs.pipeline.shared.TileSpec
 
-@Composable
-fun rememberDragAndDropState(listState: EditTileListState): DragAndDropState {
-    val draggedCell: MutableState<SizedTile<EditTileViewModel>?> = remember { mutableStateOf(null) }
-    return remember(listState) { DragAndDropState(draggedCell, listState) }
-}
-
-/**
- * Holds the [TileSpec] of the tile being moved and modify the [EditTileListState] based on drag and
- * drop events.
- */
-class DragAndDropState(
-    val draggedCell: MutableState<SizedTile<EditTileViewModel>?>,
-    private val listState: EditTileListState,
-) {
+/** Holds the [TileSpec] of the tile being moved and receives drag and drop events. */
+interface DragAndDropState {
+    val draggedCell: SizedTile<EditTileViewModel>?
     val dragInProgress: Boolean
-        get() = draggedCell.value != null
 
-    /** Returns index of the dragged tile if it's present in the list. Returns -1 if not. */
-    fun currentPosition(): Int {
-        return draggedCell.value?.let { listState.indexOf(it.tile.tileSpec) } ?: -1
-    }
+    fun isMoving(tileSpec: TileSpec): Boolean
 
-    fun isMoving(tileSpec: TileSpec): Boolean {
-        return draggedCell.value?.let { it.tile.tileSpec == tileSpec } ?: false
-    }
+    fun onStarted(cell: SizedTile<EditTileViewModel>)
 
-    fun onStarted(cell: SizedTile<EditTileViewModel>) {
-        draggedCell.value = cell
-    }
+    fun onMoved(target: Int, insertAfter: Boolean)
 
-    fun onMoved(targetSpec: TileSpec) {
-        draggedCell.value?.let { listState.move(it, targetSpec) }
-    }
+    fun movedOutOfBounds()
 
-    fun movedOutOfBounds() {
-        // Removing the tiles from the current tile grid if it moves out of bounds. This clears
-        // the spacer and makes it apparent that dropping the tile at that point would remove it.
-        draggedCell.value?.let { listState.remove(it.tile.tileSpec) }
-    }
-
-    fun onDrop() {
-        draggedCell.value = null
-    }
-}
-
-/**
- * Registers a tile as a [DragAndDropTarget] to receive drag events and update the
- * [DragAndDropState] with the tile's position, which can be used to insert a temporary placeholder.
- *
- * @param dragAndDropState The [DragAndDropState] using the tiles list
- * @param tileSpec The [TileSpec] of the tile
- * @param acceptDrops Whether the tile should accept a drop based on a given [TileSpec]
- * @param onDrop Action to be executed when a [TileSpec] is dropped on the tile
- */
-@Composable
-fun Modifier.dragAndDropTile(
-    dragAndDropState: DragAndDropState,
-    tileSpec: TileSpec,
-    acceptDrops: (TileSpec) -> Boolean,
-    onDrop: (TileSpec, Int) -> Unit,
-): Modifier {
-    val target =
-        remember(dragAndDropState) {
-            object : DragAndDropTarget {
-                override fun onDrop(event: DragAndDropEvent): Boolean {
-                    return dragAndDropState.draggedCell.value?.let {
-                        onDrop(it.tile.tileSpec, dragAndDropState.currentPosition())
-                        dragAndDropState.onDrop()
-                        true
-                    } ?: false
-                }
-
-                override fun onEntered(event: DragAndDropEvent) {
-                    dragAndDropState.onMoved(tileSpec)
-                }
-            }
-        }
-    return dragAndDropTarget(
-        shouldStartDragAndDrop = { event ->
-            event.mimeTypes().contains(QsDragAndDrop.TILESPEC_MIME_TYPE) &&
-                dragAndDropState.draggedCell.value?.let { acceptDrops(it.tile.tileSpec) } ?: false
-        },
-        target = target,
-    )
+    fun onDrop()
 }
 
 /**
@@ -135,7 +72,7 @@
         remember(dragAndDropState) {
             object : DragAndDropTarget {
                 override fun onDrop(event: DragAndDropEvent): Boolean {
-                    return dragAndDropState.draggedCell.value?.let {
+                    return dragAndDropState.draggedCell?.let {
                         onDrop(it.tile.tileSpec)
                         dragAndDropState.onDrop()
                         true
@@ -156,19 +93,22 @@
 }
 
 /**
- * Registers a tile list as a [DragAndDropTarget] to receive drop events. Use this on list
- * containers to catch drops outside of tiles.
+ * Registers a tile list as a [DragAndDropTarget] to receive drop events. Use this on the lazy tile
+ * grid to receive drag and drops events.
  *
+ * @param gridState The [LazyGridState] of the tile list
+ * @param contentOffset The [Offset] of the tile list
  * @param dragAndDropState The [DragAndDropState] using the tiles list
- * @param acceptDrops Whether the tile should accept a drop based on a given [TileSpec]
- * @param onDrop Action to be executed when a [TileSpec] is dropped on the tile
+ * @param onDrop Callback when a tile is dropped
  */
 @Composable
 fun Modifier.dragAndDropTileList(
+    gridState: LazyGridState,
+    contentOffset: Offset,
     dragAndDropState: DragAndDropState,
-    acceptDrops: (TileSpec) -> Boolean,
-    onDrop: (TileSpec, Int) -> Unit,
+    onDrop: () -> Unit,
 ): Modifier {
+    val currentContentOffset by rememberUpdatedState(contentOffset)
     val target =
         remember(dragAndDropState) {
             object : DragAndDropTarget {
@@ -176,9 +116,23 @@
                     dragAndDropState.onDrop()
                 }
 
+                override fun onMoved(event: DragAndDropEvent) {
+                    // Drag offset relative to the list's top left corner
+                    val relativeDragOffset = event.dragOffsetRelativeTo(currentContentOffset)
+                    val targetItem =
+                        gridState.layoutInfo.visibleItemsInfo.firstOrNull { item ->
+                            // Check if the drag is on this item
+                            IntRect(item.offset, item.size).toRect().contains(relativeDragOffset)
+                        }
+
+                    targetItem?.let {
+                        dragAndDropState.onMoved(it.index, insertAfter(it, relativeDragOffset))
+                    }
+                }
+
                 override fun onDrop(event: DragAndDropEvent): Boolean {
-                    return dragAndDropState.draggedCell.value?.let {
-                        onDrop(it.tile.tileSpec, dragAndDropState.currentPosition())
+                    return dragAndDropState.draggedCell?.let {
+                        onDrop()
                         dragAndDropState.onDrop()
                         true
                     } ?: false
@@ -188,12 +142,22 @@
     return dragAndDropTarget(
         target = target,
         shouldStartDragAndDrop = { event ->
-            event.mimeTypes().contains(QsDragAndDrop.TILESPEC_MIME_TYPE) &&
-                dragAndDropState.draggedCell.value?.let { acceptDrops(it.tile.tileSpec) } ?: false
+            event.mimeTypes().contains(QsDragAndDrop.TILESPEC_MIME_TYPE)
         },
     )
 }
 
+private fun DragAndDropEvent.dragOffsetRelativeTo(offset: Offset): Offset {
+    return toAndroidDragEvent().run { Offset(x, y) } - offset
+}
+
+private fun insertAfter(item: LazyGridItemInfo, offset: Offset): Boolean {
+    // We want to insert the tile after the target if we're aiming at the right side of a large tile
+    // TODO(ostonge): Verify this behavior in RTL
+    val itemCenter = item.offset + item.size.center
+    return item.span != 1 && offset.x > itemCenter.x
+}
+
 fun Modifier.dragAndDropTileSource(
     sizedTile: SizedTile<EditTileViewModel>,
     onTap: (TileSpec) -> Unit,
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/EditMode.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/EditMode.kt
index 3bda775..1674865 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/EditMode.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/EditMode.kt
@@ -43,6 +43,7 @@
             Modifier,
             viewModel::addTile,
             viewModel::removeTile,
+            viewModel::setTiles,
         )
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/EditTileListState.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/EditTileListState.kt
index fa3008e..4830ba7 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/EditTileListState.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/EditTileListState.kt
@@ -17,46 +17,106 @@
 package com.android.systemui.qs.panels.ui.compose
 
 import androidx.compose.runtime.Composable
+import androidx.compose.runtime.mutableStateOf
 import androidx.compose.runtime.remember
 import androidx.compose.runtime.snapshots.SnapshotStateList
 import androidx.compose.runtime.toMutableStateList
 import com.android.systemui.qs.panels.shared.model.SizedTile
+import com.android.systemui.qs.panels.ui.model.GridCell
+import com.android.systemui.qs.panels.ui.model.TileGridCell
+import com.android.systemui.qs.panels.ui.model.toGridCells
 import com.android.systemui.qs.panels.ui.viewmodel.EditTileViewModel
 import com.android.systemui.qs.pipeline.shared.TileSpec
 
+/**
+ * Creates the edit tile list state that is remembered across compositions.
+ *
+ * Changes to the tiles or columns will recreate the state.
+ */
 @Composable
 fun rememberEditListState(
     tiles: List<SizedTile<EditTileViewModel>>,
+    columns: Int,
 ): EditTileListState {
-    return remember(tiles) { EditTileListState(tiles) }
+    return remember(tiles, columns) { EditTileListState(tiles, columns) }
 }
 
 /** Holds the temporary state of the tile list during a drag movement where we move tiles around. */
-class EditTileListState(tiles: List<SizedTile<EditTileViewModel>>) {
-    val tiles: SnapshotStateList<SizedTile<EditTileViewModel>> = tiles.toMutableStateList()
+class EditTileListState(
+    tiles: List<SizedTile<EditTileViewModel>>,
+    private val columns: Int,
+) : DragAndDropState {
+    private val _draggedCell = mutableStateOf<SizedTile<EditTileViewModel>?>(null)
+    override val draggedCell
+        get() = _draggedCell.value
 
-    fun move(sizedTile: SizedTile<EditTileViewModel>, target: TileSpec) {
-        val fromIndex = indexOf(sizedTile.tile.tileSpec)
-        val toIndex = indexOf(target)
+    override val dragInProgress: Boolean
+        get() = _draggedCell.value != null
 
-        if (toIndex == -1 || fromIndex == toIndex) {
-            return
-        }
+    private val _tiles: SnapshotStateList<GridCell> =
+        tiles.toGridCells(columns).toMutableStateList()
+    val tiles: List<GridCell>
+        get() = _tiles.toList()
 
-        if (fromIndex == -1) {
-            // If tile isn't in the list, simply insert it
-            tiles.add(toIndex, sizedTile)
-        } else {
-            // If tile is present in the list, move it
-            tiles.apply { add(toIndex, removeAt(fromIndex)) }
-        }
-    }
-
-    fun remove(tileSpec: TileSpec) {
-        tiles.removeIf { it.tile.tileSpec == tileSpec }
+    fun tileSpecs(): List<TileSpec> {
+        return _tiles.filterIsInstance<TileGridCell>().map { it.tile.tileSpec }
     }
 
     fun indexOf(tileSpec: TileSpec): Int {
-        return tiles.indexOfFirst { it.tile.tileSpec == tileSpec }
+        return _tiles.indexOfFirst { it is TileGridCell && it.tile.tileSpec == tileSpec }
+    }
+
+    override fun isMoving(tileSpec: TileSpec): Boolean {
+        return _draggedCell.value?.let { it.tile.tileSpec == tileSpec } ?: false
+    }
+
+    override fun onStarted(cell: SizedTile<EditTileViewModel>) {
+        _draggedCell.value = cell
+
+        // Add visible spacers to the grid to indicate where the user can move a tile
+        regenerateGrid(includeSpacers = true)
+    }
+
+    override fun onMoved(target: Int, insertAfter: Boolean) {
+        val draggedTile = _draggedCell.value ?: return
+
+        val fromIndex = indexOf(draggedTile.tile.tileSpec)
+        if (fromIndex == target) {
+            return
+        }
+
+        val insertionIndex = if (insertAfter) target + 1 else target
+        if (fromIndex != -1) {
+            val cell = _tiles.removeAt(fromIndex)
+            regenerateGrid(includeSpacers = true)
+            _tiles.add(insertionIndex.coerceIn(0, _tiles.size), cell)
+        } else {
+            // Add the tile with a temporary row which will get reassigned when regenerating spacers
+            _tiles.add(insertionIndex.coerceIn(0, _tiles.size), TileGridCell(draggedTile, 0))
+        }
+
+        regenerateGrid(includeSpacers = true)
+    }
+
+    override fun movedOutOfBounds() {
+        val draggedTile = _draggedCell.value ?: return
+
+        _tiles.removeIf { cell ->
+            cell is TileGridCell && cell.tile.tileSpec == draggedTile.tile.tileSpec
+        }
+    }
+
+    override fun onDrop() {
+        _draggedCell.value = null
+
+        // Remove the spacers
+        regenerateGrid(includeSpacers = false)
+    }
+
+    private fun regenerateGrid(includeSpacers: Boolean) {
+        _tiles.filterIsInstance<TileGridCell>().toGridCells(columns, includeSpacers).let {
+            _tiles.clear()
+            _tiles.addAll(it)
+        }
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/GridLayout.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/GridLayout.kt
index e2f6bcf..fd276c2 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/GridLayout.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/GridLayout.kt
@@ -39,6 +39,7 @@
         modifier: Modifier,
         onAddTile: (TileSpec, Int) -> Unit,
         onRemoveTile: (TileSpec) -> Unit,
+        onSetTiles: (List<TileSpec>) -> Unit,
     )
 }
 
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/InfiniteGridLayout.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/InfiniteGridLayout.kt
index bd925fe..d948dfd 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/InfiniteGridLayout.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/InfiniteGridLayout.kt
@@ -76,6 +76,7 @@
         modifier: Modifier,
         onAddTile: (TileSpec, Int) -> Unit,
         onRemoveTile: (TileSpec) -> Unit,
+        onSetTiles: (List<TileSpec>) -> Unit,
     ) {
         val columns by gridSizeViewModel.columns.collectAsStateWithLifecycle()
         val largeTiles by iconTilesViewModel.largeTiles.collectAsStateWithLifecycle()
@@ -91,12 +92,16 @@
                 }
             }
 
+        val (currentTiles, otherTiles) = sizedTiles.partition { it.tile.isCurrent }
+        val currentListState = rememberEditListState(currentTiles, columns)
         DefaultEditTileGrid(
-            sizedTiles = sizedTiles,
+            currentListState = currentListState,
+            otherTiles = otherTiles,
             columns = columns,
             modifier = modifier,
             onAddTile = onAddTile,
             onRemoveTile = onRemoveTile,
+            onSetTiles = onSetTiles,
             onResize = iconTilesViewModel::resize,
         )
     }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/PaginatedGridLayout.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/PaginatedGridLayout.kt
index 2ee957e..08a56bf 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/PaginatedGridLayout.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/PaginatedGridLayout.kt
@@ -39,6 +39,7 @@
 import androidx.compose.ui.res.stringResource
 import androidx.compose.ui.unit.dp
 import androidx.lifecycle.compose.collectAsStateWithLifecycle
+import com.android.systemui.compose.modifiers.sysuiResTag
 import com.android.systemui.qs.panels.dagger.PaginatedBaseLayoutType
 import com.android.systemui.qs.panels.ui.compose.PaginatedGridLayout.Dimensions.FooterHeight
 import com.android.systemui.qs.panels.ui.compose.PaginatedGridLayout.Dimensions.InterPageSpacing
@@ -77,7 +78,7 @@
         Column {
             HorizontalPager(
                 state = pagerState,
-                modifier = Modifier,
+                modifier = Modifier.sysuiResTag("qs_pager"),
                 pageSpacing = if (pages.size > 1) InterPageSpacing else 0.dp,
                 beyondViewportPageCount = 1,
                 verticalAlignment = Alignment.Top,
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/QuickQuickSettings.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/QuickQuickSettings.kt
index af3803b..a9027ff 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/QuickQuickSettings.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/QuickQuickSettings.kt
@@ -25,6 +25,7 @@
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.res.dimensionResource
 import androidx.lifecycle.compose.collectAsStateWithLifecycle
+import com.android.systemui.compose.modifiers.sysuiResTag
 import com.android.systemui.qs.panels.ui.viewmodel.QuickQuickSettingsViewModel
 import com.android.systemui.res.R
 
@@ -44,7 +45,10 @@
     }
     val columns by viewModel.columns.collectAsStateWithLifecycle()
 
-    TileLazyGrid(modifier = modifier, columns = GridCells.Fixed(columns)) {
+    TileLazyGrid(
+        modifier = modifier.sysuiResTag("qqs_tile_layout"),
+        columns = GridCells.Fixed(columns)
+    ) {
         items(
             tiles.size,
             key = { index -> sizedTiles[index].tile.spec.spec },
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/Tile.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/Tile.kt
index 7e6ccd6..c06d6d2 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/Tile.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/compose/Tile.kt
@@ -22,7 +22,6 @@
 import android.service.quicksettings.Tile.STATE_ACTIVE
 import android.service.quicksettings.Tile.STATE_INACTIVE
 import android.text.TextUtils
-import androidx.appcompat.content.res.AppCompatResources
 import androidx.compose.animation.AnimatedContent
 import androidx.compose.animation.AnimatedVisibility
 import androidx.compose.animation.fadeIn
@@ -53,8 +52,11 @@
 import androidx.compose.foundation.layout.size
 import androidx.compose.foundation.layout.wrapContentSize
 import androidx.compose.foundation.lazy.grid.GridCells
+import androidx.compose.foundation.lazy.grid.LazyGridItemScope
 import androidx.compose.foundation.lazy.grid.LazyGridScope
+import androidx.compose.foundation.lazy.grid.LazyGridState
 import androidx.compose.foundation.lazy.grid.LazyVerticalGrid
+import androidx.compose.foundation.lazy.grid.rememberLazyGridState
 import androidx.compose.foundation.rememberScrollState
 import androidx.compose.foundation.shape.CircleShape
 import androidx.compose.foundation.shape.RoundedCornerShape
@@ -76,9 +78,13 @@
 import androidx.compose.ui.Alignment
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.draw.clip
+import androidx.compose.ui.geometry.Offset
 import androidx.compose.ui.graphics.Color
 import androidx.compose.ui.graphics.ColorFilter
+import androidx.compose.ui.layout.onGloballyPositioned
+import androidx.compose.ui.layout.positionInRoot
 import androidx.compose.ui.platform.LocalContext
+import androidx.compose.ui.platform.testTag
 import androidx.compose.ui.res.dimensionResource
 import androidx.compose.ui.res.stringResource
 import androidx.compose.ui.semantics.onClick
@@ -90,6 +96,7 @@
 import androidx.compose.ui.unit.dp
 import androidx.lifecycle.compose.collectAsStateWithLifecycle
 import com.android.compose.animation.Expandable
+import com.android.compose.modifiers.background
 import com.android.compose.modifiers.thenIf
 import com.android.systemui.animation.Expandable
 import com.android.systemui.common.shared.model.Icon
@@ -97,8 +104,10 @@
 import com.android.systemui.common.ui.compose.load
 import com.android.systemui.plugins.qs.QSTile
 import com.android.systemui.qs.panels.shared.model.SizedTile
+import com.android.systemui.qs.panels.shared.model.SizedTileImpl
+import com.android.systemui.qs.panels.ui.model.GridCell
+import com.android.systemui.qs.panels.ui.model.SpacerGridCell
 import com.android.systemui.qs.panels.ui.model.TileGridCell
-import com.android.systemui.qs.panels.ui.model.toTileGridCells
 import com.android.systemui.qs.panels.ui.viewmodel.EditTileViewModel
 import com.android.systemui.qs.panels.ui.viewmodel.TileViewModel
 import com.android.systemui.qs.panels.ui.viewmodel.toUiState
@@ -270,10 +279,12 @@
 @Composable
 fun TileLazyGrid(
     modifier: Modifier = Modifier,
+    state: LazyGridState = rememberLazyGridState(),
     columns: GridCells,
     content: LazyGridScope.() -> Unit,
 ) {
     LazyVerticalGrid(
+        state = state,
         columns = columns,
         verticalArrangement = spacedBy(dimensionResource(R.dimen.qs_tile_margin_vertical)),
         horizontalArrangement = spacedBy(dimensionResource(R.dimen.qs_tile_margin_horizontal)),
@@ -284,23 +295,18 @@
 
 @Composable
 fun DefaultEditTileGrid(
-    sizedTiles: List<SizedTile<EditTileViewModel>>,
+    currentListState: EditTileListState,
+    otherTiles: List<SizedTile<EditTileViewModel>>,
     columns: Int,
     modifier: Modifier,
     onAddTile: (TileSpec, Int) -> Unit,
     onRemoveTile: (TileSpec) -> Unit,
+    onSetTiles: (List<TileSpec>) -> Unit,
     onResize: (TileSpec) -> Unit,
 ) {
-    val (currentTiles, otherTiles) = sizedTiles.partition { it.tile.isCurrent }
-    val currentListState = rememberEditListState(currentTiles)
-    val dragAndDropState = rememberDragAndDropState(currentListState)
-
     val addTileToEnd: (TileSpec) -> Unit by rememberUpdatedState {
         onAddTile(it, CurrentTilesInteractor.POSITION_AT_END)
     }
-    val onDropAdd: (TileSpec, Int) -> Unit by rememberUpdatedState { tileSpec, position ->
-        onAddTile(tileSpec, position)
-    }
     val tilePadding = dimensionResource(R.dimen.qs_tile_margin_vertical)
 
     CompositionLocalProvider(LocalOverscrollConfiguration provides null) {
@@ -310,10 +316,10 @@
             modifier = modifier.fillMaxSize().verticalScroll(rememberScrollState())
         ) {
             AnimatedContent(
-                targetState = dragAndDropState.dragInProgress,
+                targetState = currentListState.dragInProgress,
                 modifier = Modifier.wrapContentSize()
             ) { dragIsInProgress ->
-                EditGridHeader(Modifier.dragAndDropRemoveZone(dragAndDropState, onRemoveTile)) {
+                EditGridHeader(Modifier.dragAndDropRemoveZone(currentListState, onRemoveTile)) {
                     if (dragIsInProgress) {
                         RemoveTileTarget()
                     } else {
@@ -323,18 +329,17 @@
             }
 
             CurrentTilesGrid(
-                currentListState.tiles,
+                currentListState,
                 columns,
                 tilePadding,
                 onRemoveTile,
                 onResize,
-                dragAndDropState,
-                onDropAdd,
+                onSetTiles,
             )
 
             // Hide available tiles when dragging
             AnimatedVisibility(
-                visible = !dragAndDropState.dragInProgress,
+                visible = !currentListState.dragInProgress,
                 enter = fadeIn(),
                 exit = fadeOut()
             ) {
@@ -350,7 +355,7 @@
                         columns,
                         tilePadding,
                         addTileToEnd,
-                        dragAndDropState,
+                        currentListState,
                     )
                 }
             }
@@ -360,7 +365,7 @@
                 modifier =
                     Modifier.fillMaxWidth()
                         .weight(1f)
-                        .dragAndDropRemoveZone(dragAndDropState, onRemoveTile)
+                        .dragAndDropRemoveZone(currentListState, onRemoveTile)
             )
         }
     }
@@ -376,7 +381,7 @@
     ) {
         Box(
             contentAlignment = Alignment.Center,
-            modifier = modifier.fillMaxWidth().height(TileDefaults.EditGridHeaderHeight)
+            modifier = modifier.fillMaxWidth().height(EditModeTileDefaults.EditGridHeaderHeight)
         ) {
             content()
         }
@@ -415,35 +420,42 @@
 
 @Composable
 private fun CurrentTilesGrid(
-    tiles: List<SizedTile<EditTileViewModel>>,
+    listState: EditTileListState,
     columns: Int,
     tilePadding: Dp,
     onClick: (TileSpec) -> Unit,
     onResize: (TileSpec) -> Unit,
-    dragAndDropState: DragAndDropState,
-    onDrop: (TileSpec, Int) -> Unit
+    onSetTiles: (List<TileSpec>) -> Unit,
 ) {
-    // Current tiles
+    val currentListState by rememberUpdatedState(listState)
+
     CurrentTilesContainer {
-        val cells = tiles.toTileGridCells(columns)
         val tileHeight = tileHeight()
-        val totalRows = cells.lastOrNull()?.row ?: 0
+        val totalRows = listState.tiles.lastOrNull()?.row ?: 0
         val totalHeight = gridHeight(totalRows + 1, tileHeight, tilePadding)
+        val gridState = rememberLazyGridState()
+        var gridContentOffset by remember { mutableStateOf(Offset(0f, 0f)) }
+
         TileLazyGrid(
+            state = gridState,
             modifier =
                 Modifier.height(totalHeight)
-                    .dragAndDropTileList(dragAndDropState, { true }, onDrop),
+                    .dragAndDropTileList(gridState, gridContentOffset, listState) {
+                        onSetTiles(currentListState.tileSpecs())
+                    }
+                    .onGloballyPositioned { coordinates ->
+                        gridContentOffset = coordinates.positionInRoot()
+                    }
+                    .testTag(CURRENT_TILES_GRID_TEST_TAG),
             columns = GridCells.Fixed(columns)
         ) {
             editTiles(
-                cells,
+                listState.tiles,
                 ClickAction.REMOVE,
                 onClick,
-                dragAndDropState,
+                listState,
                 onResize = onResize,
                 indicatePosition = true,
-                acceptDrops = { true },
-                onDrop = onDrop,
             )
         }
     }
@@ -465,7 +477,7 @@
 
     // Available tiles
     TileLazyGrid(
-        modifier = Modifier.height(availableGridHeight),
+        modifier = Modifier.height(availableGridHeight).testTag(AVAILABLE_TILES_GRID_TEST_TAG),
         columns = GridCells.Fixed(columns)
     ) {
         editTiles(
@@ -473,7 +485,6 @@
             ClickAction.ADD,
             onClick,
             dragAndDropState = dragAndDropState,
-            acceptDrops = { false },
             showLabels = true,
         )
         editTiles(
@@ -481,7 +492,6 @@
             ClickAction.ADD,
             onClick,
             dragAndDropState = dragAndDropState,
-            acceptDrops = { false },
             showLabels = true,
         )
     }
@@ -496,64 +506,109 @@
     return ((tileHeight + padding) * rows) - padding
 }
 
+private fun GridCell.key(index: Int, dragAndDropState: DragAndDropState): Any {
+    return if (this is TileGridCell && !dragAndDropState.isMoving(tile.tileSpec)) {
+        key
+    } else {
+        index
+    }
+}
+
 fun LazyGridScope.editTiles(
-    cells: List<TileGridCell>,
+    cells: List<GridCell>,
     clickAction: ClickAction,
     onClick: (TileSpec) -> Unit,
     dragAndDropState: DragAndDropState,
-    acceptDrops: (TileSpec) -> Boolean,
     onResize: (TileSpec) -> Unit = {},
-    onDrop: (TileSpec, Int) -> Unit = { _, _ -> },
     showLabels: Boolean = false,
     indicatePosition: Boolean = false,
 ) {
     items(
         count = cells.size,
-        key = { cells[it].key },
+        key = { cells[it].key(it, dragAndDropState) },
         span = { cells[it].span },
         contentType = { TileType }
     ) { index ->
-        val cell = cells[index]
-        val tileHeight = tileHeight(cell.isIcon && showLabels)
-
-        if (!dragAndDropState.isMoving(cell.tile.tileSpec)) {
-            val onClickActionName =
-                when (clickAction) {
-                    ClickAction.ADD ->
-                        stringResource(id = R.string.accessibility_qs_edit_tile_add_action)
-                    ClickAction.REMOVE ->
-                        stringResource(id = R.string.accessibility_qs_edit_remove_tile_action)
-                }
-            val stateDescription =
-                if (indicatePosition) {
-                    stringResource(id = R.string.accessibility_qs_edit_position, index + 1)
+        when (val cell = cells[index]) {
+            is TileGridCell ->
+                if (dragAndDropState.isMoving(cell.tile.tileSpec)) {
+                    // If the tile is being moved, replace it with a visible spacer
+                    SpacerGridCell(
+                        Modifier.background(
+                                color = MaterialTheme.colorScheme.secondary,
+                                alpha = { EditModeTileDefaults.PLACEHOLDER_ALPHA },
+                                shape = TileDefaults.TileShape
+                            )
+                            .animateItem()
+                    )
                 } else {
-                    ""
+                    TileGridCell(
+                        cell = cell,
+                        index = index,
+                        dragAndDropState = dragAndDropState,
+                        clickAction = clickAction,
+                        onClick = onClick,
+                        onResize = onResize,
+                        showLabels = showLabels,
+                        indicatePosition = indicatePosition
+                    )
                 }
-            EditTile(
-                tileViewModel = cell.tile,
-                iconOnly = cell.isIcon,
-                showLabels = showLabels,
-                modifier =
-                    Modifier.height(tileHeight)
-                        .animateItem()
-                        .semantics {
-                            onClick(onClickActionName) { false }
-                            this.stateDescription = stateDescription
-                        }
-                        .dragAndDropTile(dragAndDropState, cell.tile.tileSpec, acceptDrops, onDrop)
-                        .dragAndDropTileSource(
-                            cell,
-                            onClick,
-                            onResize,
-                            dragAndDropState,
-                        )
-            )
+            is SpacerGridCell -> SpacerGridCell()
         }
     }
 }
 
 @Composable
+private fun LazyGridItemScope.TileGridCell(
+    cell: TileGridCell,
+    index: Int,
+    dragAndDropState: DragAndDropState,
+    clickAction: ClickAction,
+    onClick: (TileSpec) -> Unit,
+    onResize: (TileSpec) -> Unit = {},
+    showLabels: Boolean = false,
+    indicatePosition: Boolean = false,
+) {
+    val tileHeight = tileHeight(cell.isIcon && showLabels)
+    val onClickActionName =
+        when (clickAction) {
+            ClickAction.ADD -> stringResource(id = R.string.accessibility_qs_edit_tile_add_action)
+            ClickAction.REMOVE ->
+                stringResource(id = R.string.accessibility_qs_edit_remove_tile_action)
+        }
+    val stateDescription =
+        if (indicatePosition) {
+            stringResource(id = R.string.accessibility_qs_edit_position, index + 1)
+        } else {
+            ""
+        }
+    EditTile(
+        tileViewModel = cell.tile,
+        iconOnly = cell.isIcon,
+        showLabels = showLabels,
+        modifier =
+            Modifier.height(tileHeight)
+                .animateItem()
+                .semantics {
+                    onClick(onClickActionName) { false }
+                    this.stateDescription = stateDescription
+                }
+                .dragAndDropTileSource(
+                    SizedTileImpl(cell.tile, cell.width),
+                    onClick,
+                    onResize,
+                    dragAndDropState,
+                )
+    )
+}
+
+@Composable
+private fun SpacerGridCell(modifier: Modifier = Modifier) {
+    // By default, spacers are invisible and exist purely to catch drag movements
+    Box(modifier.height(tileHeight()).fillMaxWidth().tilePadding())
+}
+
+@Composable
 fun EditTile(
     tileViewModel: EditTileViewModel,
     iconOnly: Boolean,
@@ -593,15 +648,15 @@
 }
 
 @Composable
-private fun getTileIcon(icon: Supplier<QSTile.Icon>): Icon {
+private fun getTileIcon(icon: Supplier<QSTile.Icon?>): Icon {
     val context = LocalContext.current
-    return icon.get().let {
+    return icon.get()?.let {
         if (it is QSTileImpl.ResourceIcon) {
             Icon.Resource(it.resId, null)
         } else {
             Icon.Loaded(it.getDrawable(context), null)
         }
-    }
+    } ?: Icon.Resource(R.drawable.ic_error_outline, null)
 }
 
 @OptIn(ExperimentalAnimationGraphicsApi::class)
@@ -618,7 +673,7 @@
         remember(icon, context) {
             when (icon) {
                 is Icon.Loaded -> icon.drawable
-                is Icon.Resource -> AppCompatResources.getDrawable(context, icon.res)
+                is Icon.Resource -> context.getDrawable(icon.res)
             }
         }
     if (loadedDrawable !is Animatable) {
@@ -642,7 +697,7 @@
             }
         Image(
             painter = painter,
-            contentDescription = null,
+            contentDescription = icon.contentDescription?.load(),
             colorFilter = ColorFilter.tint(color = color),
             modifier = iconModifier
         )
@@ -679,10 +734,14 @@
     val icon: Color,
 )
 
+private object EditModeTileDefaults {
+    const val PLACEHOLDER_ALPHA = .3f
+    val EditGridHeaderHeight = 60.dp
+}
+
 private object TileDefaults {
     val TileShape = CircleShape
     val IconTileWithLabelHeight = 140.dp
-    val EditGridHeaderHeight = 60.dp
 
     @Composable
     fun activeTileColors(): TileColors =
@@ -723,3 +782,6 @@
         }
     }
 }
+
+private const val CURRENT_TILES_GRID_TEST_TAG = "CurrentTilesGrid"
+private const val AVAILABLE_TILES_GRID_TEST_TAG = "AvailableTilesGrid"
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/model/TileGridCell.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/model/TileGridCell.kt
index c241fd8..8ca8de7 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/model/TileGridCell.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/model/TileGridCell.kt
@@ -22,6 +22,12 @@
 import com.android.systemui.qs.panels.shared.model.splitInRowsSequence
 import com.android.systemui.qs.panels.ui.viewmodel.EditTileViewModel
 
+/** Represents an item from a grid associated with a row and a span */
+interface GridCell {
+    val row: Int
+    val span: GridItemSpan
+}
+
 /**
  * Represents a [EditTileViewModel] from a grid associated with a tile format and the row it's
  * positioned at
@@ -29,10 +35,12 @@
 @Immutable
 data class TileGridCell(
     override val tile: EditTileViewModel,
-    val row: Int,
-    val key: String = "${tile.tileSpec.spec}-$row",
+    override val row: Int,
     override val width: Int,
-) : SizedTile<EditTileViewModel> {
+    override val span: GridItemSpan = GridItemSpan(width)
+) : GridCell, SizedTile<EditTileViewModel> {
+    val key: String = "${tile.tileSpec.spec}-$row"
+
     constructor(
         sizedTile: SizedTile<EditTileViewModel>,
         row: Int
@@ -41,12 +49,30 @@
         row = row,
         width = sizedTile.width,
     )
-
-    val span = GridItemSpan(width)
 }
 
-fun List<SizedTile<EditTileViewModel>>.toTileGridCells(columns: Int): List<TileGridCell> {
+/** Represents an empty space used to fill incomplete rows. Will always display as a 1x1 tile */
+@Immutable
+data class SpacerGridCell(
+    override val row: Int,
+    override val span: GridItemSpan = GridItemSpan(1)
+) : GridCell
+
+fun List<SizedTile<EditTileViewModel>>.toGridCells(
+    columns: Int,
+    includeSpacers: Boolean = false
+): List<GridCell> {
     return splitInRowsSequence(this, columns)
-        .flatMapIndexed { index, sizedTiles -> sizedTiles.map { TileGridCell(it, index) } }
+        .flatMapIndexed { rowIndex, sizedTiles ->
+            val row: List<GridCell> = sizedTiles.map { TileGridCell(it, rowIndex) }
+
+            if (includeSpacers) {
+                // Fill the incomplete rows with spacers
+                val numSpacers = columns - sizedTiles.sumOf { it.width }
+                row.toMutableList().apply { repeat(numSpacers) { add(SpacerGridCell(rowIndex)) } }
+            } else {
+                row
+            }
+        }
         .toList()
 }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/EditModeViewModel.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/EditModeViewModel.kt
index ef2c8bf..42715be 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/EditModeViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/EditModeViewModel.kt
@@ -179,6 +179,10 @@
         currentTilesInteractor.removeTiles(listOf(tileSpec))
     }
 
+    fun setTiles(tileSpecs: List<TileSpec>) {
+        currentTilesInteractor.setTiles(tileSpecs)
+    }
+
     /** Immediately resets the current tiles to the default list. */
     fun resetCurrentTilesToDefault() {
         throw NotImplementedError("This is not supported yet")
diff --git a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/TileUiState.kt b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/TileUiState.kt
index 4ec59c9..c83e3b2 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/TileUiState.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/panels/ui/viewmodel/TileUiState.kt
@@ -25,7 +25,7 @@
     val label: String,
     val secondaryLabel: String,
     val state: Int,
-    val icon: Supplier<QSTile.Icon>,
+    val icon: Supplier<QSTile.Icon?>,
 )
 
 fun QSTile.State.toUiState(): TileUiState {
@@ -33,6 +33,6 @@
         label?.toString() ?: "",
         secondaryLabel?.toString() ?: "",
         state,
-        icon?.let { Supplier { icon } } ?: iconSupplier,
+        icon?.let { Supplier { icon } } ?: iconSupplier ?: Supplier { null },
     )
 }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/interactor/CurrentTilesInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/interactor/CurrentTilesInteractor.kt
index 02379e6..4a96710 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/interactor/CurrentTilesInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/interactor/CurrentTilesInteractor.kt
@@ -116,6 +116,8 @@
      */
     fun setTiles(specs: List<TileSpec>)
 
+    fun createTileSync(spec: TileSpec): QSTile?
+
     companion object {
         val POSITION_AT_END: Int = TileSpecRepository.POSITION_AT_END
     }
@@ -190,9 +192,7 @@
             }
 
     init {
-        if (featureFlags.pipelineEnabled) {
-            startTileCollection()
-        }
+        startTileCollection()
     }
 
     private fun startTileCollection() {
@@ -342,15 +342,16 @@
         lifecycleManager.flushMessagesAndUnbind()
     }
 
+    override fun createTileSync(spec: TileSpec): QSTile? {
+        return if (featureFlags.tilesEnabled) {
+            newQSTileFactory.get().createTile(spec.spec)
+        } else {
+            null
+        } ?: tileFactory.createTile(spec.spec)
+    }
+
     private suspend fun createTile(spec: TileSpec): QSTile? {
-        val tile =
-            withContext(mainDispatcher) {
-                if (featureFlags.tilesEnabled) {
-                    newQSTileFactory.get().createTile(spec.spec)
-                } else {
-                    null
-                } ?: tileFactory.createTile(spec.spec)
-            }
+        val tile = withContext(mainDispatcher) { createTileSync(spec) }
         if (tile == null) {
             logger.logTileNotFoundInFactory(spec)
             return null
diff --git a/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/startable/QSPipelineCoreStartable.kt b/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/startable/QSPipelineCoreStartable.kt
index c8fbeb5..0bcb6b7 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/startable/QSPipelineCoreStartable.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/startable/QSPipelineCoreStartable.kt
@@ -40,14 +40,12 @@
 ) : CoreStartable {
 
     override fun start() {
-        if (featureFlags.pipelineEnabled) {
-            accessibilityTilesInteractor.init(currentTilesInteractor)
-            autoAddInteractor.init(currentTilesInteractor)
-            restoreReconciliationInteractor.start()
+        accessibilityTilesInteractor.init(currentTilesInteractor)
+        autoAddInteractor.init(currentTilesInteractor)
+        restoreReconciliationInteractor.start()
 
-            if (NewQsUI.isEnabled) {
-                gridConsistencyInteractor.start()
-            }
+        if (NewQsUI.isEnabled) {
+            gridConsistencyInteractor.start()
         }
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/pipeline/shared/QSPipelineFlagsRepository.kt b/packages/SystemUI/src/com/android/systemui/qs/pipeline/shared/QSPipelineFlagsRepository.kt
index 42bee3c..5dc8d1b 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/pipeline/shared/QSPipelineFlagsRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/pipeline/shared/QSPipelineFlagsRepository.kt
@@ -9,19 +9,10 @@
 @SysUISingleton
 class QSPipelineFlagsRepository @Inject constructor() {
 
-    val pipelineEnabled: Boolean
-        get() = AconfigFlags.qsNewPipeline()
-
     val tilesEnabled: Boolean
         get() = AconfigFlags.qsNewTiles()
 
     companion object Utils {
-        fun assertInLegacyMode() =
-            RefactorFlagUtils.assertInLegacyMode(
-                AconfigFlags.qsNewPipeline(),
-                AconfigFlags.FLAG_QS_NEW_PIPELINE
-            )
-
         fun assertNewTiles() =
             RefactorFlagUtils.assertInNewMode(
                 AconfigFlags.qsNewTiles(),
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/ModesTile.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/ModesTile.kt
index 2a33a16..5f10b38 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/ModesTile.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/ModesTile.kt
@@ -20,6 +20,7 @@
 import android.content.Intent
 import android.os.Handler
 import android.os.Looper
+import android.service.quicksettings.Tile
 import androidx.lifecycle.Lifecycle
 import androidx.lifecycle.coroutineScope
 import androidx.lifecycle.repeatOnLifecycle
@@ -94,7 +95,13 @@
 
     override fun getTileLabel(): CharSequence = tileState.label
 
-    override fun newTileState() = QSTile.State()
+    override fun newTileState(): QSTile.State {
+        return QSTile.State().apply {
+            label = mContext.getString(R.string.quick_settings_modes_label)
+            icon = ResourceIcon.get(R.drawable.qs_dnd_icon_off)
+            state = Tile.STATE_INACTIVE
+        }
+    }
 
     override fun handleClick(expandable: Expandable?) = runBlocking {
         userActionInteractor.handleClick(expandable)
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogController.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogController.java
index b2873c5..5ea9e6a 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogController.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogController.java
@@ -346,7 +346,6 @@
         mCallback = null;
     }
 
-    @VisibleForTesting
     boolean isAirplaneModeEnabled() {
         return mGlobalSettings.getInt(Settings.Global.AIRPLANE_MODE_ON, 0) != 0;
     }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogDelegate.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogDelegate.java
index f018336..71f8639 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogDelegate.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/dialog/InternetDialogDelegate.java
@@ -30,7 +30,6 @@
 import android.telephony.SignalStrength;
 import android.telephony.SubscriptionManager;
 import android.telephony.TelephonyDisplayInfo;
-import android.telephony.TelephonyManager;
 import android.text.Html;
 import android.text.Layout;
 import android.text.TextUtils;
@@ -50,9 +49,14 @@
 import android.widget.TextView;
 
 import androidx.annotation.MainThread;
+import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 import androidx.annotation.VisibleForTesting;
 import androidx.annotation.WorkerThread;
+import androidx.lifecycle.Lifecycle;
+import androidx.lifecycle.LifecycleOwner;
+import androidx.lifecycle.LifecycleRegistry;
+import androidx.lifecycle.MutableLiveData;
 import androidx.recyclerview.widget.LinearLayoutManager;
 import androidx.recyclerview.widget.RecyclerView;
 
@@ -110,7 +114,6 @@
     protected boolean mCanConfigWifi;
 
     private final InternetDialogManager mInternetDialogManager;
-    private TelephonyManager mTelephonyManager;
     @Nullable
     private AlertDialog mAlertDialog;
     private final UiEventLogger mUiEventLogger;
@@ -169,6 +172,13 @@
     @Nullable
     private Job mClickJob;
 
+    // These are to reduce the UI janky frame duration. b/323286540
+    private LifecycleRegistry mLifecycleRegistry;
+    @VisibleForTesting
+    LifecycleOwner mLifecycleOwner;
+    @VisibleForTesting
+    MutableLiveData<InternetContent> mDataInternetContent = new MutableLiveData<>();
+
     @AssistedFactory
     public interface Factory {
         InternetDialogDelegate create(
@@ -205,7 +215,6 @@
         mInternetDialogManager = internetDialogManager;
         mInternetDialogController = internetDialogController;
         mDefaultDataSubId = mInternetDialogController.getDefaultDataSubscriptionId();
-        mTelephonyManager = mInternetDialogController.getTelephonyManager();
         mCanConfigMobileData = canConfigMobileData;
         mCanConfigWifi = canConfigWifi;
         mCanChangeWifiState = WifiEnterpriseRestrictionUtils.isChangeWifiStateAllowed(context);
@@ -227,6 +236,14 @@
             mDialog.dismiss();
         }
         mDialog = dialog;
+        mLifecycleOwner = new LifecycleOwner() {
+            @NonNull
+            @Override
+            public Lifecycle getLifecycle() {
+                return mLifecycleRegistry;
+            }
+        };
+        mLifecycleRegistry = new LifecycleRegistry(mLifecycleOwner);
 
         return dialog;
     }
@@ -249,7 +266,9 @@
 
         mWifiNetworkHeight = context.getResources()
                 .getDimensionPixelSize(R.dimen.internet_dialog_wifi_network_height);
-
+        mLifecycleRegistry.setCurrentState(Lifecycle.State.CREATED);
+        mDataInternetContent.observe(
+                mLifecycleOwner, (internetContent) -> updateDialogUI(internetContent));
         mInternetDialogTitle = mDialogView.requireViewById(R.id.internet_dialog_title);
         mInternetDialogSubTitle = mDialogView.requireViewById(R.id.internet_dialog_subtitle);
         mDivider = mDialogView.requireViewById(R.id.divider);
@@ -294,6 +313,8 @@
         if (DEBUG) {
             Log.d(TAG, "onStart");
         }
+
+        mLifecycleRegistry.setCurrentState(Lifecycle.State.RESUMED);
         mInternetDialogController.onStart(this, mCanConfigWifi);
         if (!mCanConfigWifi) {
             hideWifiViews();
@@ -315,6 +336,7 @@
         if (DEBUG) {
             Log.d(TAG, "onStop");
         }
+        mLifecycleRegistry.setCurrentState(Lifecycle.State.DESTROYED);
         mMobileNetworkLayout.setOnClickListener(null);
         mConnectedWifListLayout.setOnClickListener(null);
         if (mSecondaryMobileNetworkLayout != null) {
@@ -348,31 +370,50 @@
      *                                  otherwise {@code false}.
      */
     void updateDialog(boolean shouldUpdateMobileNetwork) {
-        if (DEBUG) {
-            Log.d(TAG, "updateDialog");
-        }
-        mInternetDialogTitle.setText(getDialogTitleText());
-        mInternetDialogSubTitle.setText(getSubtitleText());
-        mAirplaneModeButton.setVisibility(
-                mInternetDialogController.isAirplaneModeEnabled() ? View.VISIBLE : View.GONE);
+        mBackgroundExecutor.execute(() -> {
+            mDataInternetContent.postValue(getInternetContent(shouldUpdateMobileNetwork));
+        });
+    }
 
-        updateEthernet();
-        if (shouldUpdateMobileNetwork) {
-            setMobileDataLayout(mInternetDialogController.activeNetworkIsCellular(),
-                    mInternetDialogController.isCarrierNetworkActive());
+    private void updateDialogUI(InternetContent internetContent) {
+        if (DEBUG) {
+            Log.d(TAG, "updateDialog ");
         }
 
+        mInternetDialogTitle.setText(internetContent.mInternetDialogTitleString);
+        mInternetDialogSubTitle.setText(internetContent.mInternetDialogSubTitle);
+        mAirplaneModeButton.setVisibility(
+                internetContent.mIsAirplaneModeEnabled ? View.VISIBLE : View.GONE);
+
+        updateEthernet(internetContent);
+        setMobileDataLayout(internetContent);
+
         if (!mCanConfigWifi) {
             return;
         }
+        updateWifiToggle(internetContent);
+        updateConnectedWifi(internetContent);
+        updateWifiListAndSeeAll(internetContent);
+        updateWifiScanNotify(internetContent);
+    }
 
-        final boolean isDeviceLocked = mInternetDialogController.isDeviceLocked();
-        final boolean isWifiEnabled = mInternetDialogController.isWifiEnabled();
-        final boolean isWifiScanEnabled = mInternetDialogController.isWifiScanEnabled();
-        updateWifiToggle(isWifiEnabled, isDeviceLocked);
-        updateConnectedWifi(isWifiEnabled, isDeviceLocked);
-        updateWifiListAndSeeAll(isWifiEnabled, isDeviceLocked);
-        updateWifiScanNotify(isWifiEnabled, isWifiScanEnabled, isDeviceLocked);
+    private InternetContent getInternetContent(boolean shouldUpdateMobileNetwork) {
+        InternetContent internetContent = new InternetContent();
+        internetContent.mShouldUpdateMobileNetwork = shouldUpdateMobileNetwork;
+        internetContent.mInternetDialogTitleString = getDialogTitleText();
+        internetContent.mInternetDialogSubTitle = getSubtitleText();
+        internetContent.mActiveNetworkIsCellular =
+                mInternetDialogController.activeNetworkIsCellular();
+        internetContent.mIsCarrierNetworkActive =
+                mInternetDialogController.isCarrierNetworkActive();
+        internetContent.mIsAirplaneModeEnabled = mInternetDialogController.isAirplaneModeEnabled();
+        internetContent.mHasEthernet = mInternetDialogController.hasEthernet();
+        internetContent.mIsWifiEnabled = mInternetDialogController.isWifiEnabled();
+        internetContent.mHasActiveSubIdOnDds = mInternetDialogController.hasActiveSubIdOnDds();
+        internetContent.mIsMobileDataEnabled = mInternetDialogController.isMobileDataEnabled();
+        internetContent.mIsDeviceLocked = mInternetDialogController.isDeviceLocked();
+        internetContent.mIsWifiScanEnabled = mInternetDialogController.isWifiScanEnabled();
+        return internetContent;
     }
 
     private void setOnClickListener(SystemUIDialog dialog) {
@@ -436,39 +477,39 @@
     }
 
     @MainThread
-    private void updateEthernet() {
+    private void updateEthernet(InternetContent internetContent) {
         mEthernetLayout.setVisibility(
-                mInternetDialogController.hasEthernet() ? View.VISIBLE : View.GONE);
+                internetContent.mHasEthernet ? View.VISIBLE : View.GONE);
     }
 
-    private void setMobileDataLayout(boolean activeNetworkIsCellular,
-            boolean isCarrierNetworkActive) {
-
-        if (mDialog != null) {
-            setMobileDataLayout(mDialog, activeNetworkIsCellular, isCarrierNetworkActive);
+    private void setMobileDataLayout(InternetContent internetContent) {
+        if (!internetContent.mShouldUpdateMobileNetwork && mDialog == null) {
+            return;
         }
+        setMobileDataLayout(mDialog, internetContent);
     }
 
-    private void setMobileDataLayout(SystemUIDialog dialog, boolean activeNetworkIsCellular,
-            boolean isCarrierNetworkActive) {
-        boolean isNetworkConnected = activeNetworkIsCellular || isCarrierNetworkActive;
+    private void setMobileDataLayout(SystemUIDialog dialog, InternetContent internetContent) {
+        boolean isNetworkConnected =
+                internetContent.mActiveNetworkIsCellular
+                        || internetContent.mIsCarrierNetworkActive;
         // 1. Mobile network should be gone if airplane mode ON or the list of active
         //    subscriptionId is null.
         // 2. Carrier network should be gone if airplane mode ON and Wi-Fi is OFF.
         if (DEBUG) {
-            Log.d(TAG, "setMobileDataLayout, isCarrierNetworkActive = " + isCarrierNetworkActive);
+            Log.d(TAG, "setMobileDataLayout, isCarrierNetworkActive = "
+                    + internetContent.mIsCarrierNetworkActive);
         }
 
-        boolean isWifiEnabled = mInternetDialogController.isWifiEnabled();
-        if (!mInternetDialogController.hasActiveSubIdOnDds()
-                && (!isWifiEnabled || !isCarrierNetworkActive)) {
+        if (!internetContent.mHasActiveSubIdOnDds && (!internetContent.mIsWifiEnabled
+                || !internetContent.mIsCarrierNetworkActive)) {
             mMobileNetworkLayout.setVisibility(View.GONE);
             if (mSecondaryMobileNetworkLayout != null) {
                 mSecondaryMobileNetworkLayout.setVisibility(View.GONE);
             }
         } else {
             mMobileNetworkLayout.setVisibility(View.VISIBLE);
-            mMobileDataToggle.setChecked(mInternetDialogController.isMobileDataEnabled());
+            mMobileDataToggle.setChecked(internetContent.mIsMobileDataEnabled);
             mMobileTitleText.setText(getMobileNetworkTitle(mDefaultDataSubId));
             String summary = getMobileNetworkSummary(mDefaultDataSubId);
             if (!TextUtils.isEmpty(summary)) {
@@ -508,7 +549,7 @@
                 if (stub != null) {
                     stub.inflate();
                 }
-                mSecondaryMobileNetworkLayout = dialog.findViewById(
+                mSecondaryMobileNetworkLayout = mDialogView.findViewById(
                         R.id.secondary_mobile_network_layout);
                 mSecondaryMobileNetworkLayout.setOnClickListener(
                         this::onClickConnectedSecondarySub);
@@ -567,7 +608,7 @@
             }
 
             // Set airplane mode to the summary for carrier network
-            if (mInternetDialogController.isAirplaneModeEnabled()) {
+            if (internetContent.mIsAirplaneModeEnabled) {
                 mAirplaneModeSummaryText.setVisibility(View.VISIBLE);
                 mAirplaneModeSummaryText.setText(
                         dialog.getContext().getText(R.string.airplane_mode));
@@ -579,17 +620,18 @@
     }
 
     @MainThread
-    private void updateWifiToggle(boolean isWifiEnabled, boolean isDeviceLocked) {
-        if (mWiFiToggle.isChecked() != isWifiEnabled) {
-            mWiFiToggle.setChecked(isWifiEnabled);
+    private void updateWifiToggle(InternetContent internetContent) {
+        if (mWiFiToggle.isChecked() != internetContent.mIsWifiEnabled) {
+            mWiFiToggle.setChecked(internetContent.mIsWifiEnabled);
         }
-        if (isDeviceLocked) {
+        if (internetContent.mIsDeviceLocked) {
             mWifiToggleTitleText.setTextAppearance((mConnectedWifiEntry != null)
                     ? R.style.TextAppearance_InternetDialog_Active
                     : R.style.TextAppearance_InternetDialog);
         }
         mTurnWifiOnLayout.setBackground(
-                (isDeviceLocked && mConnectedWifiEntry != null) ? mBackgroundOn : null);
+                (internetContent.mIsDeviceLocked && mConnectedWifiEntry != null) ? mBackgroundOn
+                        : null);
 
         if (!mCanChangeWifiState && mWiFiToggle.isEnabled()) {
             mWiFiToggle.setEnabled(false);
@@ -601,8 +643,9 @@
     }
 
     @MainThread
-    private void updateConnectedWifi(boolean isWifiEnabled, boolean isDeviceLocked) {
-        if (mDialog == null || !isWifiEnabled || mConnectedWifiEntry == null || isDeviceLocked) {
+    private void updateConnectedWifi(InternetContent internetContent) {
+        if (mDialog == null || !internetContent.mIsWifiEnabled || mConnectedWifiEntry == null
+                || internetContent.mIsDeviceLocked) {
             mConnectedWifListLayout.setVisibility(View.GONE);
             mShareWifiButton.setVisibility(View.GONE);
             return;
@@ -627,8 +670,8 @@
     }
 
     @MainThread
-    private void updateWifiListAndSeeAll(boolean isWifiEnabled, boolean isDeviceLocked) {
-        if (!isWifiEnabled || isDeviceLocked) {
+    private void updateWifiListAndSeeAll(InternetContent internetContent) {
+        if (!internetContent.mIsWifiEnabled || internetContent.mIsDeviceLocked) {
             mWifiRecyclerView.setVisibility(View.GONE);
             mSeeAllLayout.setVisibility(View.GONE);
             return;
@@ -670,9 +713,10 @@
     }
 
     @MainThread
-    private void updateWifiScanNotify(boolean isWifiEnabled, boolean isWifiScanEnabled,
-            boolean isDeviceLocked) {
-        if (mDialog == null || isWifiEnabled || !isWifiScanEnabled || isDeviceLocked) {
+    private void updateWifiScanNotify(InternetContent internetContent) {
+        if (mDialog == null || internetContent.mIsWifiEnabled
+                || !internetContent.mIsWifiScanEnabled
+                || internetContent.mIsDeviceLocked) {
             mWifiScanNotifyLayout.setVisibility(View.GONE);
             return;
         }
@@ -805,62 +849,62 @@
 
     @Override
     public void onRefreshCarrierInfo() {
-        mHandler.post(() -> updateDialog(true /* shouldUpdateMobileNetwork */));
+        updateDialog(true /* shouldUpdateMobileNetwork */);
     }
 
     @Override
     public void onSimStateChanged() {
-        mHandler.post(() -> updateDialog(true /* shouldUpdateMobileNetwork */));
+        updateDialog(true /* shouldUpdateMobileNetwork */);
     }
 
     @Override
     @WorkerThread
     public void onCapabilitiesChanged(Network network, NetworkCapabilities networkCapabilities) {
-        mHandler.post(() -> updateDialog(true /* shouldUpdateMobileNetwork */));
+        updateDialog(true /* shouldUpdateMobileNetwork */);
     }
 
     @Override
     @WorkerThread
     public void onLost(Network network) {
-        mHandler.post(() -> updateDialog(true /* shouldUpdateMobileNetwork */));
+        updateDialog(true /* shouldUpdateMobileNetwork */);
     }
 
     @Override
     public void onSubscriptionsChanged(int defaultDataSubId) {
         mDefaultDataSubId = defaultDataSubId;
-        mTelephonyManager = mTelephonyManager.createForSubscriptionId(mDefaultDataSubId);
-        mHandler.post(() -> updateDialog(true /* shouldUpdateMobileNetwork */));
+        updateDialog(true /* shouldUpdateMobileNetwork */);
     }
 
     @Override
     public void onUserMobileDataStateChanged(boolean enabled) {
-        mHandler.post(() -> updateDialog(true /* shouldUpdateMobileNetwork */));
+        updateDialog(true /* shouldUpdateMobileNetwork */);
     }
 
     @Override
     public void onServiceStateChanged(ServiceState serviceState) {
-        mHandler.post(() -> updateDialog(true /* shouldUpdateMobileNetwork */));
+        updateDialog(true /* shouldUpdateMobileNetwork */);
     }
 
     @Override
     @WorkerThread
     public void onDataConnectionStateChanged(int state, int networkType) {
-        mHandler.post(() -> updateDialog(true /* shouldUpdateMobileNetwork */));
+        updateDialog(true /* shouldUpdateMobileNetwork */);
     }
 
     @Override
     public void onSignalStrengthsChanged(SignalStrength signalStrength) {
-        mHandler.post(() -> updateDialog(true /* shouldUpdateMobileNetwork */));
+        updateDialog(true /* shouldUpdateMobileNetwork */);
     }
 
     @Override
     public void onDisplayInfoChanged(TelephonyDisplayInfo telephonyDisplayInfo) {
-        mHandler.post(() -> updateDialog(true /* shouldUpdateMobileNetwork */));
+        updateDialog(true /* shouldUpdateMobileNetwork */);
     }
 
     @Override
     public void onCarrierNetworkChange(boolean active) {
-        mHandler.post(() -> updateDialog(true /* shouldUpdateMobileNetwork */));
+
+        updateDialog(true /* shouldUpdateMobileNetwork */);
     }
 
     @Override
@@ -912,4 +956,20 @@
             return mId;
         }
     }
+
+    @VisibleForTesting
+    static class InternetContent {
+        CharSequence mInternetDialogTitleString = "";
+        CharSequence mInternetDialogSubTitle = "";
+        boolean mIsAirplaneModeEnabled = false;
+        boolean mHasEthernet = false;
+        boolean mShouldUpdateMobileNetwork = false;
+        boolean mActiveNetworkIsCellular = false;
+        boolean mIsCarrierNetworkActive = false;
+        boolean mIsWifiEnabled = false;
+        boolean mHasActiveSubIdOnDds = false;
+        boolean mIsMobileDataEnabled = false;
+        boolean mIsDeviceLocked = false;
+        boolean mIsWifiScanEnabled = false;
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/data/repository/CustomTileRepository.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/data/repository/CustomTileRepository.kt
index c932cee..0aaea8f 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/data/repository/CustomTileRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/data/repository/CustomTileRepository.kt
@@ -17,8 +17,8 @@
 package com.android.systemui.qs.tiles.impl.custom.data.repository
 
 import android.content.pm.PackageManager
-import android.content.pm.ServiceInfo
 import android.graphics.drawable.Icon
+import android.os.RemoteException
 import android.os.UserHandle
 import android.service.quicksettings.Tile
 import android.service.quicksettings.TileService
@@ -163,13 +163,14 @@
     override suspend fun isTileActive(): Boolean =
         withContext(backgroundContext) {
             try {
-                val info: ServiceInfo =
+                val info =
                     packageManagerAdapter.getServiceInfo(
                         tileSpec.componentName,
-                        META_DATA_QUERY_FLAGS
+                        META_DATA_QUERY_FLAGS,
+                        getCurrentTileWithUser()?.user?.identifier ?: UserHandle.USER_CURRENT,
                     )
-                info.metaData?.getBoolean(TileService.META_DATA_ACTIVE_TILE, false) == true
-            } catch (e: PackageManager.NameNotFoundException) {
+                info?.metaData?.getBoolean(TileService.META_DATA_ACTIVE_TILE, false) == true
+            } catch (e: RemoteException) {
                 false
             }
         }
@@ -177,13 +178,14 @@
     override suspend fun isTileToggleable(): Boolean =
         withContext(backgroundContext) {
             try {
-                val info: ServiceInfo =
+                val info =
                     packageManagerAdapter.getServiceInfo(
                         tileSpec.componentName,
-                        META_DATA_QUERY_FLAGS
+                        META_DATA_QUERY_FLAGS,
+                        getCurrentTileWithUser()?.user?.identifier ?: UserHandle.USER_CURRENT
                     )
-                info.metaData?.getBoolean(TileService.META_DATA_TOGGLEABLE_TILE, false) == true
-            } catch (e: PackageManager.NameNotFoundException) {
+                info?.metaData?.getBoolean(TileService.META_DATA_TOGGLEABLE_TILE, false) == true
+            } catch (e: RemoteException) {
                 false
             }
         }
diff --git a/packages/SystemUI/src/com/android/systemui/scene/shared/model/Scene.kt b/packages/SystemUI/src/com/android/systemui/scene/shared/model/Scene.kt
index 103b4a5..61a06db 100644
--- a/packages/SystemUI/src/com/android/systemui/scene/shared/model/Scene.kt
+++ b/packages/SystemUI/src/com/android/systemui/scene/shared/model/Scene.kt
@@ -20,6 +20,7 @@
 import com.android.compose.animation.scene.UserAction
 import com.android.compose.animation.scene.UserActionResult
 import com.android.systemui.lifecycle.Activatable
+import kotlinx.coroutines.awaitCancellation
 import kotlinx.coroutines.flow.Flow
 
 /**
@@ -35,7 +36,9 @@
     /** Uniquely-identifying key for this scene. The key must be unique within its container. */
     val key: SceneKey
 
-    override suspend fun activate() = Unit
+    override suspend fun activate(): Nothing {
+        awaitCancellation()
+    }
 
     /**
      * The mapping between [UserAction] and destination [UserActionResult]s.
diff --git a/packages/SystemUI/src/com/android/systemui/scene/ui/view/SceneWindowRootView.kt b/packages/SystemUI/src/com/android/systemui/scene/ui/view/SceneWindowRootView.kt
index f6924f2..8aa601f 100644
--- a/packages/SystemUI/src/com/android/systemui/scene/ui/view/SceneWindowRootView.kt
+++ b/packages/SystemUI/src/com/android/systemui/scene/ui/view/SceneWindowRootView.kt
@@ -26,13 +26,12 @@
         attrs,
     ) {
 
-    private lateinit var viewModel: SceneContainerViewModel
-
+    private var motionEventHandler: SceneContainerViewModel.MotionEventHandler? = null
     // TODO(b/298525212): remove once Compose exposes window inset bounds.
     private val windowInsets: MutableStateFlow<WindowInsets?> = MutableStateFlow(null)
 
     fun init(
-        viewModel: SceneContainerViewModel,
+        viewModelFactory: SceneContainerViewModel.Factory,
         containerConfig: SceneContainerConfig,
         sharedNotificationContainer: SharedNotificationContainer,
         scenes: Set<Scene>,
@@ -40,11 +39,13 @@
         sceneDataSourceDelegator: SceneDataSourceDelegator,
         alternateBouncerDependencies: AlternateBouncerDependencies,
     ) {
-        this.viewModel = viewModel
         setLayoutInsetsController(layoutInsetController)
         SceneWindowRootViewBinder.bind(
             view = this@SceneWindowRootView,
-            viewModel = viewModel,
+            viewModelFactory = viewModelFactory,
+            motionEventHandlerReceiver = { motionEventHandler ->
+                this.motionEventHandler = motionEventHandler
+            },
             windowInsets = windowInsets,
             containerConfig = containerConfig,
             sharedNotificationContainer = sharedNotificationContainer,
@@ -69,10 +70,10 @@
     }
 
     override fun dispatchTouchEvent(ev: MotionEvent): Boolean {
-        viewModel.onMotionEvent(ev)
+        motionEventHandler?.onMotionEvent(ev)
         return super.dispatchTouchEvent(ev).also {
             TouchLogger.logDispatchTouch(TAG, ev, it)
-            viewModel.onMotionEventComplete()
+            motionEventHandler?.onMotionEventComplete()
         }
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/scene/ui/view/SceneWindowRootViewBinder.kt b/packages/SystemUI/src/com/android/systemui/scene/ui/view/SceneWindowRootViewBinder.kt
index 73a8e4c..ad68f17 100644
--- a/packages/SystemUI/src/com/android/systemui/scene/ui/view/SceneWindowRootViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/scene/ui/view/SceneWindowRootViewBinder.kt
@@ -29,8 +29,6 @@
 import androidx.compose.ui.unit.dp
 import androidx.core.view.isVisible
 import androidx.lifecycle.Lifecycle
-import androidx.lifecycle.lifecycleScope
-import androidx.lifecycle.repeatOnLifecycle
 import com.android.compose.animation.scene.SceneKey
 import com.android.compose.theme.PlatformTheme
 import com.android.internal.policy.ScreenDecorationsUtils
@@ -39,7 +37,9 @@
 import com.android.systemui.common.ui.compose.windowinsets.ScreenDecorProvider
 import com.android.systemui.keyguard.ui.composable.AlternateBouncer
 import com.android.systemui.keyguard.ui.viewmodel.AlternateBouncerDependencies
+import com.android.systemui.lifecycle.WindowLifecycleState
 import com.android.systemui.lifecycle.repeatWhenAttached
+import com.android.systemui.lifecycle.viewModel
 import com.android.systemui.res.R
 import com.android.systemui.scene.shared.flag.SceneContainerFlag
 import com.android.systemui.scene.shared.model.Scene
@@ -51,6 +51,7 @@
 import com.android.systemui.statusbar.notification.stack.ui.view.SharedNotificationContainer
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.awaitCancellation
 import kotlinx.coroutines.flow.SharingStarted
 import kotlinx.coroutines.flow.StateFlow
 import kotlinx.coroutines.flow.map
@@ -63,7 +64,8 @@
     /** Binds between the view and view-model pertaining to a specific scene container. */
     fun bind(
         view: ViewGroup,
-        viewModel: SceneContainerViewModel,
+        viewModelFactory: SceneContainerViewModel.Factory,
+        motionEventHandlerReceiver: (SceneContainerViewModel.MotionEventHandler?) -> Unit,
         windowInsets: StateFlow<WindowInsets?>,
         containerConfig: SceneContainerConfig,
         sharedNotificationContainer: SharedNotificationContainer,
@@ -85,8 +87,11 @@
         }
 
         view.repeatWhenAttached {
-            lifecycleScope.launch {
-                repeatOnLifecycle(Lifecycle.State.CREATED) {
+            view.viewModel(
+                minWindowLifecycleState = WindowLifecycleState.ATTACHED,
+                factory = { viewModelFactory.create(motionEventHandlerReceiver) },
+            ) { viewModel ->
+                try {
                     view.setViewTreeOnBackPressedDispatcherOwner(
                         object : OnBackPressedDispatcherOwner {
                             override val onBackPressedDispatcher =
@@ -140,10 +145,11 @@
                             onVisibilityChangedInternal(isVisible)
                         }
                     }
+                    awaitCancellation()
+                } finally {
+                    // Here when destroyed.
+                    view.removeAllViews()
                 }
-
-                // Here when destroyed.
-                view.removeAllViews()
             }
         }
     }
diff --git a/packages/SystemUI/src/com/android/systemui/scene/ui/viewmodel/SceneActionsViewModel.kt b/packages/SystemUI/src/com/android/systemui/scene/ui/viewmodel/SceneActionsViewModel.kt
index c2fd65b..b5de1b6 100644
--- a/packages/SystemUI/src/com/android/systemui/scene/ui/viewmodel/SceneActionsViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/scene/ui/viewmodel/SceneActionsViewModel.kt
@@ -41,7 +41,7 @@
      */
     val actions: StateFlow<Map<UserAction, UserActionResult>> = _actions.asStateFlow()
 
-    final override suspend fun onActivated() {
+    final override suspend fun onActivated(): Nothing {
         try {
             hydrateActions { state -> _actions.value = state }
             awaitCancellation()
diff --git a/packages/SystemUI/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModel.kt b/packages/SystemUI/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModel.kt
index a28222e..0b4fb32 100644
--- a/packages/SystemUI/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/scene/ui/viewmodel/SceneContainerViewModel.kt
@@ -23,25 +23,26 @@
 import com.android.compose.animation.scene.UserActionResult
 import com.android.systemui.classifier.Classifier
 import com.android.systemui.classifier.domain.interactor.FalsingInteractor
-import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.lifecycle.SysUiViewModel
 import com.android.systemui.power.domain.interactor.PowerInteractor
 import com.android.systemui.scene.domain.interactor.SceneInteractor
 import com.android.systemui.scene.shared.model.Scenes
-import javax.inject.Inject
+import dagger.assisted.Assisted
+import dagger.assisted.AssistedFactory
+import dagger.assisted.AssistedInject
+import kotlinx.coroutines.awaitCancellation
 import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.StateFlow
-import kotlinx.coroutines.flow.combine
-import kotlinx.coroutines.flow.map
 
 /** Models UI state for the scene container. */
-@SysUISingleton
 class SceneContainerViewModel
-@Inject
+@AssistedInject
 constructor(
     private val sceneInteractor: SceneInteractor,
     private val falsingInteractor: FalsingInteractor,
     private val powerInteractor: PowerInteractor,
-) {
+    @Assisted private val motionEventHandlerReceiver: (MotionEventHandler?) -> Unit,
+) : SysUiViewModel() {
     /**
      * Keys of all scenes in the container.
      *
@@ -56,6 +57,29 @@
     /** Whether the container is visible. */
     val isVisible: StateFlow<Boolean> = sceneInteractor.isVisible
 
+    override suspend fun onActivated(): Nothing {
+        try {
+            // Sends a MotionEventHandler to the owner of the view-model so they can report
+            // MotionEvents into the view-model.
+            motionEventHandlerReceiver(
+                object : MotionEventHandler {
+                    override fun onMotionEvent(motionEvent: MotionEvent) {
+                        this@SceneContainerViewModel.onMotionEvent(motionEvent)
+                    }
+
+                    override fun onMotionEventComplete() {
+                        this@SceneContainerViewModel.onMotionEventComplete()
+                    }
+                }
+            )
+            awaitCancellation()
+        } finally {
+            // Clears the previously-sent MotionEventHandler so the owner of the view-model releases
+            // their reference to it.
+            motionEventHandlerReceiver(null)
+        }
+    }
+
     /**
      * Binds the given flow so the system remembers it.
      *
@@ -136,21 +160,22 @@
         }
     }
 
-    private fun replaceSceneFamilies(
-        destinationScenes: Map<UserAction, UserActionResult>,
-    ): Flow<Map<UserAction, UserActionResult>> {
-        return destinationScenes
-            .mapValues { (_, actionResult) ->
-                sceneInteractor.resolveSceneFamily(actionResult.toScene).map { scene ->
-                    actionResult.copy(toScene = scene)
-                }
-            }
-            .combineValueFlows()
+    /** Defines interface for classes that can handle externally-reported [MotionEvent]s. */
+    interface MotionEventHandler {
+        /** Notifies that a [MotionEvent] has occurred. */
+        fun onMotionEvent(motionEvent: MotionEvent)
+
+        /**
+         * Notifies that the previous [MotionEvent] reported by [onMotionEvent] has finished
+         * processing.
+         */
+        fun onMotionEventComplete()
+    }
+
+    @AssistedFactory
+    interface Factory {
+        fun create(
+            motionEventHandlerReceiver: (MotionEventHandler?) -> Unit,
+        ): SceneContainerViewModel
     }
 }
-
-private fun <K, V> Map<K, Flow<V>>.combineValueFlows(): Flow<Map<K, V>> =
-    combine(
-        asIterable().map { (k, fv) -> fv.map { k to it } },
-        transform = Array<Pair<K, V>>::toMap,
-    )
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeViewProviderModule.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeViewProviderModule.kt
index 21bbaa5..606fef0 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ShadeViewProviderModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeViewProviderModule.kt
@@ -79,7 +79,7 @@
         @SysUISingleton
         fun providesWindowRootView(
             layoutInflater: LayoutInflater,
-            viewModelProvider: Provider<SceneContainerViewModel>,
+            viewModelFactory: SceneContainerViewModel.Factory,
             containerConfigProvider: Provider<SceneContainerConfig>,
             scenesProvider: Provider<Set<@JvmSuppressWildcards Scene>>,
             layoutInsetController: NotificationInsetsController,
@@ -91,7 +91,7 @@
                 val sceneWindowRootView =
                     layoutInflater.inflate(R.layout.scene_window_root, null) as SceneWindowRootView
                 sceneWindowRootView.init(
-                    viewModel = viewModelProvider.get(),
+                    viewModelFactory = viewModelFactory,
                     containerConfig = containerConfigProvider.get(),
                     sharedNotificationContainer =
                         sceneWindowRootView.requireViewById(R.id.shared_notification_container),
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/OverlayShadeViewModel.kt b/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/OverlayShadeViewModel.kt
index 566bc16..00c0235 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/OverlayShadeViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/OverlayShadeViewModel.kt
@@ -24,6 +24,7 @@
 import com.android.systemui.shade.domain.interactor.ShadeInteractor
 import dagger.assisted.AssistedFactory
 import dagger.assisted.AssistedInject
+import kotlinx.coroutines.awaitCancellation
 import kotlinx.coroutines.flow.MutableStateFlow
 import kotlinx.coroutines.flow.StateFlow
 import kotlinx.coroutines.flow.asStateFlow
@@ -44,10 +45,11 @@
     /** Dictates the alignment of the overlay shade panel on the screen. */
     val panelAlignment = shadeInteractor.shadeAlignment
 
-    override suspend fun onActivated() {
+    override suspend fun onActivated(): Nothing {
         sceneInteractor.resolveSceneFamily(SceneFamilies.Home).collectLatest { sceneKey ->
             _backgroundScene.value = sceneKey
         }
+        awaitCancellation()
     }
 
     /** Notifies that the user has clicked the semi-transparent background scrim. */
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeHeaderViewModel.kt b/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeHeaderViewModel.kt
index 03fdfa9..f0e9d41 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeHeaderViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeHeaderViewModel.kt
@@ -41,6 +41,7 @@
 import dagger.assisted.AssistedInject
 import java.util.Date
 import java.util.Locale
+import kotlinx.coroutines.awaitCancellation
 import kotlinx.coroutines.coroutineScope
 import kotlinx.coroutines.flow.MutableStateFlow
 import kotlinx.coroutines.flow.StateFlow
@@ -104,7 +105,7 @@
     private val _longerDateText: MutableStateFlow<String> = MutableStateFlow("")
     val longerDateText: StateFlow<String> = _longerDateText.asStateFlow()
 
-    override suspend fun onActivated() {
+    override suspend fun onActivated(): Nothing {
         coroutineScope {
             launch {
                 broadcastDispatcher
@@ -137,6 +138,8 @@
             launch {
                 shadeInteractor.isQsEnabled.map { !it }.collectLatest { _isDisabled.value = it }
             }
+
+            awaitCancellation()
         }
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneContentViewModel.kt b/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneContentViewModel.kt
index a4d3416..fe3bcb5 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneContentViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/ui/viewmodel/ShadeSceneContentViewModel.kt
@@ -35,6 +35,7 @@
 import dagger.assisted.AssistedInject
 import java.util.concurrent.atomic.AtomicBoolean
 import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.awaitCancellation
 import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.MutableStateFlow
 import kotlinx.coroutines.flow.StateFlow
@@ -73,10 +74,12 @@
 
     private val footerActionsControllerInitialized = AtomicBoolean(false)
 
-    override suspend fun onActivated() {
+    override suspend fun onActivated(): Nothing {
         deviceEntryInteractor.isDeviceEntered.collectLatest { isDeviceEntered ->
             _isEmptySpaceClickable.value = !isDeviceEntered
         }
+
+        awaitCancellation()
     }
 
     /**
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/LockscreenShadeTransitionController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/LockscreenShadeTransitionController.kt
index 14e14f4..1481b73 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/LockscreenShadeTransitionController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/LockscreenShadeTransitionController.kt
@@ -20,7 +20,6 @@
 import com.android.systemui.classifier.FalsingCollector
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dump.DumpManager
-import com.android.systemui.keyguard.MigrateClocksToBlueprint
 import com.android.systemui.keyguard.WakefulnessLifecycle
 import com.android.systemui.keyguard.domain.interactor.NaturalScrollingSettingObserver
 import com.android.systemui.media.controls.ui.controller.MediaHierarchyManager
@@ -45,7 +44,7 @@
 import com.android.systemui.statusbar.phone.LSShadeTransitionLogger
 import com.android.systemui.statusbar.policy.ConfigurationController
 import com.android.systemui.statusbar.policy.SplitShadeStateController
-import com.android.wm.shell.animation.Interpolators
+import com.android.wm.shell.shared.animation.Interpolators
 import dagger.Lazy
 import java.io.PrintWriter
 import javax.inject.Inject
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/OWNERS b/packages/SystemUI/src/com/android/systemui/statusbar/OWNERS
index c4f539a..408fc6d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/OWNERS
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/OWNERS
@@ -13,4 +13,12 @@
 per-file *Keyboard* = set noparent
 per-file *Keyboard* = file:../keyguard/OWNERS
 per-file *Keyguard* = set noparent
-per-file *Keyguard* = file:../keyguard/OWNERS
\ No newline at end of file
+per-file *Keyguard* = file:../keyguard/OWNERS
+per-file *Notification* = set noparent
+per-file *Notification* = file:notification/OWNERS
+per-file *Mode* = set noparent
+per-file *Mode* = file:notification/OWNERS
+per-file *RemoteInput* = set noparent
+per-file *RemoteInput* = file:notification/OWNERS
+per-file *EmptyShadeView* = set noparent
+per-file *EmptyShadeView* = file:notification/OWNERS
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationTransitionAnimatorController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationTransitionAnimatorController.kt
index 2b7df7d..67c53d46 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationTransitionAnimatorController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationTransitionAnimatorController.kt
@@ -142,14 +142,15 @@
     }
 
     override fun onIntentStarted(willAnimate: Boolean) {
+        val reason = "onIntentStarted(willAnimate=$willAnimate)"
         if (ActivityTransitionAnimator.DEBUG_TRANSITION_ANIMATION) {
-            Log.d(TAG, "onIntentStarted(willAnimate=$willAnimate)")
+            Log.d(TAG, reason)
         }
         notificationLaunchAnimationInteractor.setIsLaunchAnimationRunning(willAnimate)
         notificationEntry.isExpandAnimationRunning = willAnimate
 
         if (!willAnimate) {
-            removeHun(animate = true)
+            removeHun(animate = true, reason)
             onFinishAnimationCallback?.run()
         }
     }
@@ -166,13 +167,18 @@
             }
         }
 
-    private fun removeHun(animate: Boolean) {
+    private fun removeHun(animate: Boolean, reason: String) {
         val row = headsUpNotificationRow ?: return
 
         // TODO: b/297247841 - Call on the row we're removing, which may differ from notification.
         HeadsUpUtil.setNeedsHeadsUpDisappearAnimationAfterClick(notification, animate)
 
-        headsUpManager.removeNotification(row.entry.key, true /* releaseImmediately */, animate)
+        headsUpManager.removeNotification(
+            row.entry.key,
+            true /* releaseImmediately */,
+            animate,
+            reason
+        )
     }
 
     override fun onTransitionAnimationCancelled(newKeyguardOccludedState: Boolean?) {
@@ -184,7 +190,7 @@
         // here?
         notificationLaunchAnimationInteractor.setIsLaunchAnimationRunning(false)
         notificationEntry.isExpandAnimationRunning = false
-        removeHun(animate = true)
+        removeHun(animate = true, "onLaunchAnimationCancelled()")
         onFinishAnimationCallback?.run()
     }
 
@@ -206,7 +212,7 @@
         notificationEntry.isExpandAnimationRunning = false
         notificationListContainer.setExpandingNotification(null)
         applyParams(null)
-        removeHun(animate = false)
+        removeHun(animate = false, "onLaunchAnimationEnd()")
         onFinishAnimationCallback?.run()
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinator.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinator.kt
index e50d64b..ec8566b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinator.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinator.kt
@@ -496,7 +496,11 @@
                 if (posted?.shouldHeadsUpEver == false) {
                     if (posted.isHeadsUpEntry) {
                         // We don't want this to be interrupting anymore, let's remove it
-                        mHeadsUpManager.removeNotification(posted.key, false /*removeImmediately*/)
+                        mHeadsUpManager.removeNotification(
+                            posted.key,
+                            /* removeImmediately= */ false,
+                            "onEntryUpdated"
+                        )
                     } else if (posted.isBinding) {
                         // Don't let the bind finish
                         cancelHeadsUpBind(posted.entry)
@@ -520,7 +524,11 @@
                     val removeImmediatelyForRemoteInput =
                         (mRemoteInputManager.isSpinning(entryKey) &&
                             !NotificationRemoteInputManager.FORCE_REMOTE_INPUT_HISTORY)
-                    mHeadsUpManager.removeNotification(entry.key, removeImmediatelyForRemoteInput)
+                    mHeadsUpManager.removeNotification(
+                        entry.key,
+                        removeImmediatelyForRemoteInput,
+                        "onEntryRemoved, reason: $reason"
+                    )
                 }
             }
 
@@ -721,7 +729,9 @@
                             {
                                 mHeadsUpManager.removeNotification(
                                     entry.key, /* releaseImmediately */
-                                    true
+                                    true,
+                                    "cancel lifetime extension - extended for reason: " +
+                                        "$reason, isSticky: true"
                                 )
                             },
                             removeAfterMillis
@@ -730,7 +740,9 @@
                     mExecutor.execute {
                         mHeadsUpManager.removeNotification(
                             entry.key, /* releaseImmediately */
-                            false
+                            false,
+                            "lifetime extension - extended for reason: $reason" +
+                                ", isSticky: false"
                         )
                     }
                     mNotifsExtendingLifetime[entry] = null
@@ -902,7 +914,7 @@
 
     fun commitModifications() {
         deferred.forEach { (key, releaseImmediately) ->
-            headsUpManager.removeNotification(key, releaseImmediately)
+            headsUpManager.removeNotification(key, releaseImmediately, "commitModifications")
         }
         deferred.clear()
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/CommonNotifCollection.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/CommonNotifCollection.java
index fc7d682..0d209d8 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/CommonNotifCollection.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/collection/notifcollection/CommonNotifCollection.java
@@ -27,9 +27,6 @@
 /**
  * A notification collection that manages the list of {@link NotificationEntry}s that will be
  * rendered.
- *
- * TODO: (b/145659174) Once we fully migrate to {@link NotifPipeline}, we probably won't need this,
- * but having it for now makes it easy to switch between the two.
  */
 public interface CommonNotifCollection {
     /**
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
index 20b1fff..e802076 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
@@ -258,7 +258,7 @@
     private float mOverScrolledBottomPixels;
     private final ListenerSet<Runnable> mStackHeightChangedListeners = new ListenerSet<>();
     private final ListenerSet<Runnable> mHeadsUpHeightChangedListeners = new ListenerSet<>();
-    private NotificationLogger.OnChildLocationsChangedListener mListener;
+    private NotificationLogger.OnChildLocationsChangedListener mLegacyLocationsChangedListener;
     private OnNotificationLocationsChangedListener mLocationsChangedListener;
     private OnOverscrollTopChangedListener mOverscrollTopChangedListener;
     private ExpandableView.OnHeightChangedListener mOnHeightChangedListener;
@@ -1281,7 +1281,7 @@
     public void setChildLocationsChangedListener(
             NotificationLogger.OnChildLocationsChangedListener listener) {
         NotificationsLiveDataStoreRefactor.assertInLegacyMode();
-        mListener = listener;
+        mLegacyLocationsChangedListener = listener;
     }
 
     private void setMaxLayoutHeight(int maxLayoutHeight) {
@@ -4433,8 +4433,8 @@
                 mLocationsChangedListener.onChildLocationsChanged(collectVisibleLocationsCallable);
             }
         } else {
-            if (mListener != null) {
-                mListener.onChildLocationsChanged();
+            if (mLegacyLocationsChangedListener != null) {
+                mLegacyLocationsChangedListener.onChildLocationsChanged();
             }
         }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
index 41195aa..fa12bb9 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
@@ -638,8 +638,11 @@
                         if (row.isPinned() && !canChildBeDismissed(row)
                                 && row.getEntry().getSbn().getNotification().fullScreenIntent
                                 == null) {
-                            mHeadsUpManager.removeNotification(row.getEntry().getSbn().getKey(),
-                                    true /* removeImmediately */);
+                            mHeadsUpManager.removeNotification(
+                                    row.getEntry().getSbn().getKey(),
+                                    /* removeImmediately= */ true ,
+                                    /* reason= */ "onChildSnappedBack"
+                            );
                         }
                     }
                 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationScrollViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationScrollViewModel.kt
index bfb624a..a205179 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationScrollViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationScrollViewModel.kt
@@ -59,7 +59,7 @@
     ActivatableFlowDumper by ActivatableFlowDumperImpl(dumpManager, "NotificationScrollViewModel"),
     SysUiViewModel() {
 
-    override suspend fun onActivated() {
+    override suspend fun onActivated(): Nothing {
         activateFlowDumper()
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationsPlaceholderViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationsPlaceholderViewModel.kt
index 53fab62..2fbb23e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationsPlaceholderViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationsPlaceholderViewModel.kt
@@ -16,6 +16,7 @@
 
 package com.android.systemui.statusbar.notification.stack.ui.viewmodel
 
+import com.android.systemui.dump.DumpManager
 import com.android.systemui.flags.FeatureFlagsClassic
 import com.android.systemui.flags.Flags
 import com.android.systemui.lifecycle.SysUiViewModel
@@ -25,6 +26,8 @@
 import com.android.systemui.statusbar.notification.stack.domain.interactor.NotificationStackAppearanceInteractor
 import com.android.systemui.statusbar.notification.stack.shared.model.ShadeScrimBounds
 import com.android.systemui.statusbar.notification.stack.shared.model.ShadeScrimRounding
+import com.android.systemui.util.kotlin.ActivatableFlowDumper
+import com.android.systemui.util.kotlin.ActivatableFlowDumperImpl
 import dagger.assisted.AssistedFactory
 import dagger.assisted.AssistedInject
 import kotlinx.coroutines.flow.Flow
@@ -40,7 +43,13 @@
     shadeInteractor: ShadeInteractor,
     private val headsUpNotificationInteractor: HeadsUpNotificationInteractor,
     featureFlags: FeatureFlagsClassic,
-) : SysUiViewModel() {
+    dumpManager: DumpManager,
+) :
+    SysUiViewModel(),
+    ActivatableFlowDumper by ActivatableFlowDumperImpl(
+        dumpManager = dumpManager,
+        tag = "NotificationsPlaceholderViewModel",
+    ) {
 
     /** DEBUG: whether the placeholder should be made slightly visible for positional debugging. */
     val isVisualDebuggingEnabled: Boolean = featureFlags.isEnabled(Flags.NSSL_DEBUG_LINES)
@@ -48,6 +57,10 @@
     /** DEBUG: whether the debug logging should be output. */
     val isDebugLoggingEnabled: Boolean = SceneContainerFlag.isEnabled
 
+    override suspend fun onActivated(): Nothing {
+        activateFlowDumper()
+    }
+
     /** Notifies that the bounds of the notification scrim have changed. */
     fun onScrimBoundsChanged(bounds: ShadeScrimBounds?) {
         interactor.setShadeScrimBounds(bounds)
@@ -68,37 +81,35 @@
         headsUpNotificationInteractor.isHeadsUpOrAnimatingAway
 
     /** Corner rounding of the stack */
-    // TODO(b/359244921): add .dumpWhileCollecting("shadeScrimRounding")
-    val shadeScrimRounding: Flow<ShadeScrimRounding> = interactor.shadeScrimRounding
+    val shadeScrimRounding: Flow<ShadeScrimRounding> =
+        interactor.shadeScrimRounding.dumpWhileCollecting("shadeScrimRounding")
 
     /**
      * The amount [0-1] that the shade or quick settings has been opened. At 0, the shade is closed;
      * at 1, either the shade or quick settings is open.
      */
-    // TODO(b/359244921): add .dumpValue("expandFraction")
-    val expandFraction: Flow<Float> = shadeInteractor.anyExpansion
+    val expandFraction: Flow<Float> = shadeInteractor.anyExpansion.dumpValue("expandFraction")
 
     /**
      * The amount [0-1] that quick settings has been opened. At 0, the shade may be open or closed;
      * at 1, the quick settings are open.
      */
-    // TODO(b/359244921): add .dumpValue("shadeToQsFraction")
-    val shadeToQsFraction: Flow<Float> = shadeInteractor.qsExpansion
+    val shadeToQsFraction: Flow<Float> = shadeInteractor.qsExpansion.dumpValue("shadeToQsFraction")
 
     /**
      * The amount in px that the notification stack should scroll due to internal expansion. This
      * should only happen when a notification expansion hits the bottom of the screen, so it is
      * necessary to scroll up to keep expanding the notification.
      */
-    // TODO(b/359244921): add .dumpWhileCollecting("syntheticScroll")
-    val syntheticScroll: Flow<Float> = interactor.syntheticScroll
+    val syntheticScroll: Flow<Float> =
+        interactor.syntheticScroll.dumpWhileCollecting("syntheticScroll")
 
     /**
      * Whether the current touch gesture is overscroll. If true, it means the NSSL has already
      * consumed part of the gesture.
      */
-    // TODO(b/359244921): add .dumpWhileCollecting("isCurrentGestureOverScroll")
-    val isCurrentGestureOverscroll: Flow<Boolean> = interactor.isCurrentGestureOverscroll
+    val isCurrentGestureOverscroll: Flow<Boolean> =
+        interactor.isCurrentGestureOverscroll.dumpWhileCollecting("isCurrentGestureOverScroll")
 
     /** Sets whether the notification stack is scrolled to the top. */
     fun setScrolledToTop(scrolledToTop: Boolean) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModel.kt
index 99f7a75..f63ee7b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModel.kt
@@ -511,7 +511,7 @@
                 occludedToAodTransitionViewModel.lockscreenAlpha,
                 occludedToGoneTransitionViewModel.notificationAlpha(viewState),
                 occludedToLockscreenTransitionViewModel.lockscreenAlpha,
-                primaryBouncerToLockscreenTransitionViewModel.lockscreenAlpha,
+                primaryBouncerToLockscreenTransitionViewModel.lockscreenAlpha(viewState),
                 glanceableHubToLockscreenTransitionViewModel.keyguardAlpha,
                 lockscreenToGlanceableHubTransitionViewModel.keyguardAlpha,
             )
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ActivityStarterInternalImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ActivityStarterInternalImpl.kt
index 107bf1e..d4ef42c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ActivityStarterInternalImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ActivityStarterInternalImpl.kt
@@ -613,8 +613,9 @@
                     super.onTransitionAnimationStart(isExpandingFullyAbove)
                     if (Flags.communalHub()) {
                         communalSceneInteractor.snapToScene(
-                            CommunalScenes.Blank,
-                            ActivityTransitionAnimator.TIMINGS.totalDuration
+                            newScene = CommunalScenes.Blank,
+                            loggingReason = "ActivityStarterInternalImpl",
+                            delayMillis = ActivityTransitionAnimator.TIMINGS.totalDuration
                         )
                     }
                 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/AutoTileManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/AutoTileManager.java
deleted file mode 100644
index a538856..0000000
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/AutoTileManager.java
+++ /dev/null
@@ -1,520 +0,0 @@
-/*
- * Copyright (C) 2016 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
- * except in compliance with the License. You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software distributed under the
- * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the specific language governing
- * permissions and limitations under the License.
- */
-
-package com.android.systemui.statusbar.phone;
-
-import static com.android.systemui.qs.dagger.QSFlagsModule.RBC_AVAILABLE;
-
-import android.annotation.Nullable;
-import android.content.ComponentName;
-import android.content.Context;
-import android.content.res.Resources;
-import android.database.ContentObserver;
-import android.hardware.display.ColorDisplayManager;
-import android.hardware.display.NightDisplayListener;
-import android.os.Handler;
-import android.os.UserHandle;
-import android.util.Log;
-
-import com.android.internal.annotations.VisibleForTesting;
-import com.android.systemui.dagger.NightDisplayListenerModule;
-import com.android.systemui.dagger.qualifiers.Background;
-import com.android.systemui.plugins.qs.QSTile;
-import com.android.systemui.qs.AutoAddTracker;
-import com.android.systemui.qs.QSHost;
-import com.android.systemui.qs.ReduceBrightColorsController;
-import com.android.systemui.qs.UserSettingObserver;
-import com.android.systemui.qs.external.CustomTile;
-import com.android.systemui.qs.pipeline.shared.QSPipelineFlagsRepository;
-import com.android.systemui.res.R;
-import com.android.systemui.statusbar.policy.CastController;
-import com.android.systemui.statusbar.policy.CastDevice;
-import com.android.systemui.statusbar.policy.DataSaverController;
-import com.android.systemui.statusbar.policy.DataSaverController.Listener;
-import com.android.systemui.statusbar.policy.DeviceControlsController;
-import com.android.systemui.statusbar.policy.HotspotController;
-import com.android.systemui.statusbar.policy.HotspotController.Callback;
-import com.android.systemui.statusbar.policy.SafetyController;
-import com.android.systemui.statusbar.policy.WalletController;
-import com.android.systemui.util.UserAwareController;
-import com.android.systemui.util.settings.SecureSettings;
-
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.Objects;
-
-import javax.inject.Named;
-
-/**
- * Manages which tiles should be automatically added to QS.
- */
-public class AutoTileManager implements UserAwareController {
-    private static final String TAG = "AutoTileManager";
-
-    public static final String HOTSPOT = "hotspot";
-    public static final String SAVER = "saver";
-    public static final String INVERSION = "inversion";
-    public static final String WORK = "work";
-    public static final String NIGHT = "night";
-    public static final String CAST = "cast";
-    public static final String DEVICE_CONTROLS = "controls";
-    public static final String WALLET = "wallet";
-    public static final String BRIGHTNESS = "reduce_brightness";
-    static final String SETTING_SEPARATOR = ":";
-
-    private UserHandle mCurrentUser;
-    private boolean mInitialized;
-    private final String mSafetySpec;
-
-    protected final Context mContext;
-    protected final QSHost mHost;
-    protected final Handler mHandler;
-    protected final SecureSettings mSecureSettings;
-    protected final AutoAddTracker mAutoTracker;
-    private final HotspotController mHotspotController;
-    private final DataSaverController mDataSaverController;
-    private final ManagedProfileController mManagedProfileController;
-    private final NightDisplayListenerModule.Builder mNightDisplayListenerBuilder;
-    private NightDisplayListener mNightDisplayListener;
-    private final CastController mCastController;
-    private final DeviceControlsController mDeviceControlsController;
-    private final WalletController mWalletController;
-    private final ReduceBrightColorsController mReduceBrightColorsController;
-    private final SafetyController mSafetyController;
-    private final boolean mIsReduceBrightColorsAvailable;
-    private final ArrayList<AutoAddSetting> mAutoAddSettingList = new ArrayList<>();
-
-    public AutoTileManager(Context context, AutoAddTracker.Builder autoAddTrackerBuilder,
-            QSHost host,
-            @Background Handler handler,
-            SecureSettings secureSettings,
-            HotspotController hotspotController,
-            DataSaverController dataSaverController,
-            ManagedProfileController managedProfileController,
-            NightDisplayListenerModule.Builder nightDisplayListenerBuilder,
-            CastController castController,
-            ReduceBrightColorsController reduceBrightColorsController,
-            DeviceControlsController deviceControlsController,
-            WalletController walletController,
-            SafetyController safetyController,
-            @Named(RBC_AVAILABLE) boolean isReduceBrightColorsAvailable) {
-        mContext = context;
-        mHost = host;
-        mSecureSettings = secureSettings;
-        mCurrentUser = mHost.getUserContext().getUser();
-        mAutoTracker = autoAddTrackerBuilder.setUserId(mCurrentUser.getIdentifier()).build();
-        mHandler = handler;
-        mHotspotController = hotspotController;
-        mDataSaverController = dataSaverController;
-        mManagedProfileController = managedProfileController;
-        mNightDisplayListenerBuilder = nightDisplayListenerBuilder;
-        mCastController = castController;
-        mReduceBrightColorsController = reduceBrightColorsController;
-        mIsReduceBrightColorsAvailable = isReduceBrightColorsAvailable;
-        mDeviceControlsController = deviceControlsController;
-        mWalletController = walletController;
-        mSafetyController = safetyController;
-        String safetySpecClass;
-        try {
-            safetySpecClass =
-                    context.getResources().getString(R.string.safety_quick_settings_tile_class);
-            if (safetySpecClass.length() == 0) {
-                safetySpecClass = null;
-            }
-        } catch (Resources.NotFoundException | NullPointerException e) {
-            safetySpecClass = null;
-        }
-        mSafetySpec = safetySpecClass != null ? CustomTile.toSpec(new ComponentName(mContext
-                .getPackageManager().getPermissionControllerPackageName(), safetySpecClass)) : null;
-    }
-
-    /**
-     * Init method must be called after construction to start listening
-     */
-    public void init() {
-        QSPipelineFlagsRepository.Utils.assertInLegacyMode();
-        if (mInitialized) {
-            Log.w(TAG, "Trying to re-initialize");
-            return;
-        }
-        mAutoTracker.initialize();
-        populateSettingsList();
-        startControllersAndSettingsListeners();
-        mInitialized = true;
-    }
-
-    protected void startControllersAndSettingsListeners() {
-        if (!mAutoTracker.isAdded(HOTSPOT)) {
-            mHotspotController.addCallback(mHotspotCallback);
-        }
-        if (!mAutoTracker.isAdded(SAVER)) {
-            mDataSaverController.addCallback(mDataSaverListener);
-        }
-        mManagedProfileController.addCallback(mProfileCallback);
-
-        mNightDisplayListener = mNightDisplayListenerBuilder
-                .setUser(mCurrentUser.getIdentifier())
-                .build();
-        if (!mAutoTracker.isAdded(NIGHT)
-                && ColorDisplayManager.isNightDisplayAvailable(mContext)) {
-            mNightDisplayListener.setCallback(mNightDisplayCallback);
-        }
-        if (!mAutoTracker.isAdded(CAST)) {
-            mCastController.addCallback(mCastCallback);
-        }
-        if (!mAutoTracker.isAdded(BRIGHTNESS) && mIsReduceBrightColorsAvailable) {
-            mReduceBrightColorsController.addCallback(mReduceBrightColorsCallback);
-        }
-        // We always want this callback, because if the feature stops being supported,
-        // we want to remove the tile from AutoAddTracker. That way it will be re-added when the
-        // feature is reenabled (similar to work tile).
-        mDeviceControlsController.setCallback(mDeviceControlsCallback);
-        if (!mAutoTracker.isAdded(WALLET)) {
-            initWalletController();
-        }
-        if (mSafetySpec != null) {
-            if (!mAutoTracker.isAdded(mSafetySpec)) {
-                initSafetyTile();
-            }
-            mSafetyController.addCallback(mSafetyCallback);
-        }
-
-        int settingsN = mAutoAddSettingList.size();
-        for (int i = 0; i < settingsN; i++) {
-            if (!mAutoTracker.isAdded(mAutoAddSettingList.get(i).mSpec)) {
-                mAutoAddSettingList.get(i).setListening(true);
-            }
-        }
-    }
-
-    protected void stopListening() {
-        mHotspotController.removeCallback(mHotspotCallback);
-        mDataSaverController.removeCallback(mDataSaverListener);
-        mManagedProfileController.removeCallback(mProfileCallback);
-        if (ColorDisplayManager.isNightDisplayAvailable(mContext)
-                && mNightDisplayListener != null) {
-            mNightDisplayListener.setCallback(null);
-        }
-        if (mIsReduceBrightColorsAvailable) {
-            mReduceBrightColorsController.removeCallback(mReduceBrightColorsCallback);
-        }
-        mCastController.removeCallback(mCastCallback);
-        mDeviceControlsController.removeCallback();
-        if (mSafetySpec != null) {
-            mSafetyController.removeCallback(mSafetyCallback);
-        }
-        int settingsN = mAutoAddSettingList.size();
-        for (int i = 0; i < settingsN; i++) {
-            mAutoAddSettingList.get(i).setListening(false);
-        }
-    }
-
-    public void destroy() {
-        stopListening();
-        mAutoTracker.destroy();
-    }
-
-    /**
-     * Populates a list with the pairs setting:spec in the config resource.
-     * <p>
-     * This will only create {@link AutoAddSetting} objects for those tiles that have not been
-     * auto-added before, and set the corresponding {@link ContentObserver} to listening.
-     */
-    private void populateSettingsList() {
-        String [] autoAddList;
-        try {
-            autoAddList = mContext.getResources().getStringArray(
-                    R.array.config_quickSettingsAutoAdd);
-        } catch (Resources.NotFoundException e) {
-            Log.w(TAG, "Missing config resource");
-            return;
-        }
-        // getStringArray returns @NotNull, so if we got here, autoAddList is not null
-        for (String tile : autoAddList) {
-            String[] split = tile.split(SETTING_SEPARATOR);
-            if (split.length == 2) {
-                String setting = split[0];
-                String spec = split[1];
-                // Populate all the settings. As they may not have been added in other users
-                AutoAddSetting s = new AutoAddSetting(
-                        mSecureSettings, mHandler, setting, mCurrentUser.getIdentifier(), spec);
-                mAutoAddSettingList.add(s);
-            } else {
-                Log.w(TAG, "Malformed item in array: " + tile);
-            }
-        }
-    }
-
-    /*
-     * This will be sent off the main thread if needed
-     */
-    @Override
-    public void changeUser(UserHandle newUser) {
-        if (!mInitialized) {
-            throw new IllegalStateException("AutoTileManager not initialized");
-        }
-        if (!Thread.currentThread().equals(mHandler.getLooper().getThread())) {
-            mHandler.post(() -> changeUser(newUser));
-            return;
-        }
-        if (newUser.getIdentifier() == mCurrentUser.getIdentifier()) {
-            return;
-        }
-        stopListening();
-        mCurrentUser = newUser;
-        int settingsN = mAutoAddSettingList.size();
-        for (int i = 0; i < settingsN; i++) {
-            mAutoAddSettingList.get(i).setUserId(newUser.getIdentifier());
-        }
-        mAutoTracker.changeUser(newUser);
-        startControllersAndSettingsListeners();
-    }
-
-    @Override
-    public int getCurrentUserId() {
-        return mCurrentUser.getIdentifier();
-    }
-
-    private final ManagedProfileController.Callback mProfileCallback =
-            new ManagedProfileController.Callback() {
-                @Override
-                public void onManagedProfileChanged() {
-                    if (mManagedProfileController.hasActiveProfile()) {
-                        if (mAutoTracker.isAdded(WORK)) return;
-                        final int position = mAutoTracker.getRestoredTilePosition(WORK);
-                        mHost.addTile(WORK, position);
-                        mAutoTracker.setTileAdded(WORK);
-                    } else {
-                        if (!mAutoTracker.isAdded(WORK)) return;
-                        mHost.removeTile(WORK);
-                        mAutoTracker.setTileRemoved(WORK);
-                    }
-                }
-
-                @Override
-                public void onManagedProfileRemoved() {
-                }
-            };
-
-    private final DataSaverController.Listener mDataSaverListener = new Listener() {
-        @Override
-        public void onDataSaverChanged(boolean isDataSaving) {
-            if (mAutoTracker.isAdded(SAVER)) return;
-            if (isDataSaving) {
-                mHost.addTile(SAVER);
-                mAutoTracker.setTileAdded(SAVER);
-                mHandler.post(() -> mDataSaverController.removeCallback(mDataSaverListener));
-            }
-        }
-    };
-
-    private final HotspotController.Callback mHotspotCallback = new Callback() {
-        @Override
-        public void onHotspotChanged(boolean enabled, int numDevices) {
-            if (mAutoTracker.isAdded(HOTSPOT)) return;
-            if (enabled) {
-                mHost.addTile(HOTSPOT);
-                mAutoTracker.setTileAdded(HOTSPOT);
-                mHandler.post(() -> mHotspotController.removeCallback(mHotspotCallback));
-            }
-        }
-    };
-
-    private final DeviceControlsController.Callback mDeviceControlsCallback =
-            new DeviceControlsController.Callback() {
-        @Override
-        public void onControlsUpdate(@Nullable Integer position) {
-            if (mAutoTracker.isAdded(DEVICE_CONTROLS)) return;
-            if (position != null && !hasTile(DEVICE_CONTROLS)) {
-                mHost.addTile(DEVICE_CONTROLS, position);
-                mAutoTracker.setTileAdded(DEVICE_CONTROLS);
-            }
-            mHandler.post(() -> mDeviceControlsController.removeCallback());
-        }
-
-        @Override
-        public void removeControlsAutoTracker() {
-            mAutoTracker.setTileRemoved(DEVICE_CONTROLS);
-        }
-    };
-
-    private boolean hasTile(String tileSpec) {
-        if (tileSpec == null) return false;
-        Collection<QSTile> tiles = mHost.getTiles();
-        for (QSTile tile : tiles) {
-            if (tileSpec.equals(tile.getTileSpec())) {
-                return true;
-            }
-        }
-        return false;
-    }
-
-    private void initWalletController() {
-        if (mAutoTracker.isAdded(WALLET)) return;
-        Integer position = mWalletController.getWalletPosition();
-
-        if (position != null) {
-            mHost.addTile(WALLET, position);
-            mAutoTracker.setTileAdded(WALLET);
-        }
-    }
-
-    private void initSafetyTile() {
-        if (mSafetySpec == null || mAutoTracker.isAdded(mSafetySpec)) {
-            return;
-        }
-        mHost.addTile(CustomTile.getComponentFromSpec(mSafetySpec), true);
-        mAutoTracker.setTileAdded(mSafetySpec);
-    }
-
-    @VisibleForTesting
-    final NightDisplayListener.Callback mNightDisplayCallback =
-            new NightDisplayListener.Callback() {
-        @Override
-        public void onActivated(boolean activated) {
-            if (activated) {
-                addNightTile();
-            }
-        }
-
-        @Override
-        public void onAutoModeChanged(int autoMode) {
-            if (autoMode == ColorDisplayManager.AUTO_MODE_CUSTOM_TIME
-                    || autoMode == ColorDisplayManager.AUTO_MODE_TWILIGHT) {
-                addNightTile();
-            }
-        }
-
-        private void addNightTile() {
-            if (mAutoTracker.isAdded(NIGHT)) return;
-            mHost.addTile(NIGHT);
-            mAutoTracker.setTileAdded(NIGHT);
-            mHandler.post(() -> mNightDisplayListener.setCallback(null));
-        }
-    };
-
-    @VisibleForTesting
-    final ReduceBrightColorsController.Listener mReduceBrightColorsCallback =
-            new ReduceBrightColorsController.Listener() {
-                @Override
-                public void onActivated(boolean activated) {
-                    if (activated) {
-                        addReduceBrightColorsTile();
-                    }
-                }
-
-                @Override
-                public void onFeatureEnabledChanged(boolean enabled) {
-                    if (!enabled) {
-                        mHost.removeTile(BRIGHTNESS);
-                        mHandler.post(() -> mReduceBrightColorsController.removeCallback(this));
-                    }
-                }
-
-                private void addReduceBrightColorsTile() {
-                    if (mAutoTracker.isAdded(BRIGHTNESS)) return;
-                    mHost.addTile(BRIGHTNESS);
-                    mAutoTracker.setTileAdded(BRIGHTNESS);
-                    mHandler.post(() -> mReduceBrightColorsController.removeCallback(this));
-                }
-            };
-
-    @VisibleForTesting
-    final CastController.Callback mCastCallback = new CastController.Callback() {
-        @Override
-        public void onCastDevicesChanged() {
-            if (mAutoTracker.isAdded(CAST)) return;
-
-            boolean isCasting = false;
-            for (CastDevice device : mCastController.getCastDevices()) {
-                if (device.isCasting()) {
-                    isCasting = true;
-                    break;
-                }
-            }
-
-            if (isCasting) {
-                mHost.addTile(CAST);
-                mAutoTracker.setTileAdded(CAST);
-                mHandler.post(() -> mCastController.removeCallback(mCastCallback));
-            }
-        }
-    };
-
-    @VisibleForTesting
-    final SafetyController.Listener mSafetyCallback = new SafetyController.Listener() {
-        @Override
-        public void onSafetyCenterEnableChanged(boolean isSafetyCenterEnabled) {
-            if (mSafetySpec == null) {
-                return;
-            }
-
-            if (isSafetyCenterEnabled && !mAutoTracker.isAdded(mSafetySpec)) {
-                initSafetyTile();
-            } else if (!isSafetyCenterEnabled && mAutoTracker.isAdded(mSafetySpec)) {
-                mHost.removeTile(mSafetySpec);
-                mAutoTracker.setTileRemoved(mSafetySpec);
-            }
-        }
-    };
-
-    @VisibleForTesting
-    protected UserSettingObserver getSecureSettingForKey(String key) {
-        for (UserSettingObserver s : mAutoAddSettingList) {
-            if (Objects.equals(key, s.getKey())) {
-                return s;
-            }
-        }
-        return null;
-    }
-
-    /**
-     * Tracks tiles that should be auto added when a setting changes.
-     * <p>
-     * When the setting changes to a value different from 0, if the tile has not been auto added
-     * before, it will be added and the listener will be stopped.
-     */
-    private class AutoAddSetting extends UserSettingObserver {
-        private final String mSpec;
-
-        AutoAddSetting(
-                SecureSettings secureSettings,
-                Handler handler,
-                String setting,
-                int userId,
-                String tileSpec
-        ) {
-            super(secureSettings, handler, setting, userId);
-            mSpec = tileSpec;
-        }
-
-        @Override
-        protected void handleValueChanged(int value, boolean observedChange) {
-            if (mAutoTracker.isAdded(mSpec)) {
-                // This should not be listening anymore
-                mHandler.post(() -> setListening(false));
-                return;
-            }
-            if (value != 0) {
-                if (mSpec.startsWith(CustomTile.PREFIX)) {
-                    mHost.addTile(CustomTile.getComponentFromSpec(mSpec), /* end */ true);
-                } else {
-                    mHost.addTile(mSpec);
-                }
-                mAutoTracker.setTileAdded(mSpec);
-                mHandler.post(() -> setListening(false));
-            }
-        }
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
index c4fbc37..94dd9bb 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
@@ -160,6 +160,8 @@
 import com.android.systemui.power.domain.interactor.PowerInteractor;
 import com.android.systemui.qs.QSFragmentLegacy;
 import com.android.systemui.qs.QSPanelController;
+import com.android.systemui.qs.composefragment.QSFragmentCompose;
+import com.android.systemui.qs.flags.QSComposeFragment;
 import com.android.systemui.res.R;
 import com.android.systemui.scene.domain.interactor.WindowRootViewVisibilityInteractor;
 import com.android.systemui.scene.shared.flag.SceneContainerFlag;
@@ -1432,9 +1434,15 @@
     }
 
     protected QS createDefaultQSFragment() {
+        Class<? extends QS> klass;
+        if (QSComposeFragment.isEnabled()) {
+            klass = QSFragmentCompose.class;
+        } else {
+            klass = QSFragmentLegacy.class;
+        }
         return mFragmentService
                 .getFragmentHostManager(getNotificationShadeWindowView())
-                .create(QSFragmentLegacy.class);
+                .create(klass);
     }
 
     private void setUpPresenter() {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeServiceHost.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeServiceHost.java
index ac10155..ec92990 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeServiceHost.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeServiceHost.java
@@ -193,7 +193,11 @@
     void fireNotificationPulse(NotificationEntry entry) {
         Runnable pulseSuppressedListener = () -> {
             mHeadsUpManager.removeNotification(
-                    entry.getKey(), /* releaseImmediately= */ true, /* animate= */ false);
+                    entry.getKey(),
+                    /* releaseImmediately= */ true,
+                    /* animate= */ false,
+                    "fireNotificationPulse"
+            );
         };
         Assert.isMainThread();
         for (Callback callback : mCallbacks) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhone.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhone.java
index 25d9cc7..544a8a5 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhone.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/HeadsUpManagerPhone.java
@@ -60,11 +60,6 @@
 import com.android.systemui.util.settings.GlobalSettings;
 import com.android.systemui.util.time.SystemClock;
 
-import kotlinx.coroutines.flow.Flow;
-import kotlinx.coroutines.flow.MutableStateFlow;
-import kotlinx.coroutines.flow.StateFlow;
-import kotlinx.coroutines.flow.StateFlowKt;
-
 import java.io.PrintWriter;
 import java.util.ArrayList;
 import java.util.HashSet;
@@ -75,6 +70,11 @@
 
 import javax.inject.Inject;
 
+import kotlinx.coroutines.flow.Flow;
+import kotlinx.coroutines.flow.MutableStateFlow;
+import kotlinx.coroutines.flow.StateFlow;
+import kotlinx.coroutines.flow.StateFlowKt;
+
 /** A implementation of HeadsUpManager for phone. */
 @SysUISingleton
 public class HeadsUpManagerPhone extends BaseHeadsUpManager implements
@@ -365,12 +365,14 @@
 
     @Override
     public boolean removeNotification(@NonNull String key, boolean releaseImmediately,
-            boolean animate) {
+            boolean animate, @NonNull String reason) {
         if (animate) {
-            return removeNotification(key, releaseImmediately);
+            return removeNotification(key, releaseImmediately,
+                    "removeNotification(animate: true), reason: " + reason);
         } else {
             mAnimationStateHandler.setHeadsUpGoingAwayAnimationsAllowed(false);
-            boolean removed = removeNotification(key, releaseImmediately);
+            final boolean removed = removeNotification(key, releaseImmediately,
+                    "removeNotification(animate: false), reason: " + reason);
             mAnimationStateHandler.setHeadsUpGoingAwayAnimationsAllowed(true);
             return removed;
         }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LegacyActivityStarterInternalImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LegacyActivityStarterInternalImpl.kt
index 1a47081..4604233 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LegacyActivityStarterInternalImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LegacyActivityStarterInternalImpl.kt
@@ -333,10 +333,6 @@
             }
             if (intent.isActivity) {
                 assistManagerLazy.get().hideAssist()
-                // This activity could have started while the device is dreaming, in which case
-                // the dream would occlude the activity. In order to show the newly started
-                // activity, we wake from the dream.
-                keyguardUpdateMonitor.awakenFromDream()
             }
             intentSentUiThreadCallback?.let { postOnUiThread(runnable = it) }
         }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarView.java
index f178708..e7d5cd1 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarView.java
@@ -16,8 +16,6 @@
 
 package com.android.systemui.statusbar.phone;
 
-
-
 import android.annotation.Nullable;
 import android.content.Context;
 import android.content.res.Configuration;
@@ -38,11 +36,8 @@
 import com.android.systemui.Dependency;
 import com.android.systemui.Flags;
 import com.android.systemui.Gefingerpoken;
-import com.android.systemui.plugins.DarkIconDispatcher;
-import com.android.systemui.plugins.DarkIconDispatcher.DarkReceiver;
 import com.android.systemui.res.R;
 import com.android.systemui.statusbar.phone.userswitcher.StatusBarUserSwitcherContainer;
-import com.android.systemui.statusbar.policy.Clock;
 import com.android.systemui.statusbar.window.StatusBarWindowController;
 import com.android.systemui.user.ui.binder.StatusBarUserChipViewBinder;
 import com.android.systemui.user.ui.viewmodel.StatusBarUserChipViewModel;
@@ -55,8 +50,6 @@
     private final StatusBarContentInsetsProvider mContentInsetsProvider;
     private final StatusBarWindowController mStatusBarWindowController;
 
-    private DarkReceiver mBattery;
-    private Clock mClock;
     private int mRotationOrientation = -1;
     @Nullable
     private View mCutoutSpace;
@@ -93,8 +86,6 @@
     @Override
     public void onFinishInflate() {
         super.onFinishInflate();
-        mBattery = findViewById(R.id.battery);
-        mClock = findViewById(R.id.clock);
         mCutoutSpace = findViewById(R.id.cutout_space_view);
 
         updateResources();
@@ -103,9 +94,6 @@
     @Override
     protected void onAttachedToWindow() {
         super.onAttachedToWindow();
-        // Always have Battery meters in the status bar observe the dark/light modes.
-        Dependency.get(DarkIconDispatcher.class).addDarkReceiver(mBattery);
-        Dependency.get(DarkIconDispatcher.class).addDarkReceiver(mClock);
         if (updateDisplayParameters()) {
             updateLayoutForCutout();
             updateWindowHeight();
@@ -115,8 +103,6 @@
     @Override
     protected void onDetachedFromWindow() {
         super.onDetachedFromWindow();
-        Dependency.get(DarkIconDispatcher.class).removeDarkReceiver(mBattery);
-        Dependency.get(DarkIconDispatcher.class).removeDarkReceiver(mClock);
         mDisplayCutout = null;
     }
 
@@ -136,10 +122,6 @@
         updateWindowHeight();
     }
 
-    void onDensityOrFontScaleChanged() {
-        mClock.onDensityOrFontScaleChanged();
-    }
-
     @Override
     public WindowInsets onApplyWindowInsets(WindowInsets insets) {
         if (updateDisplayParameters()) {
@@ -316,6 +298,9 @@
     }
 
     private void updateWindowHeight() {
+        if (Flags.statusBarStopUpdatingWindowHeight()) {
+            return;
+        }
         mStatusBarWindowController.refreshStatusBarHeight();
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewController.kt
index a818c05..468a3c3 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewController.kt
@@ -23,10 +23,13 @@
 import android.view.View
 import android.view.ViewGroup
 import android.view.ViewTreeObserver
+import androidx.annotation.VisibleForTesting
 import com.android.systemui.Flags
 import com.android.systemui.Gefingerpoken
+import com.android.systemui.battery.BatteryMeterView
 import com.android.systemui.flags.FeatureFlags
 import com.android.systemui.flags.Flags.ENABLE_UNFOLD_STATUS_BAR_ANIMATIONS
+import com.android.systemui.plugins.DarkIconDispatcher
 import com.android.systemui.res.R
 import com.android.systemui.scene.shared.flag.SceneContainerFlag
 import com.android.systemui.scene.ui.view.WindowRootView
@@ -35,6 +38,7 @@
 import com.android.systemui.shade.ShadeViewController
 import com.android.systemui.shade.domain.interactor.PanelExpansionInteractor
 import com.android.systemui.shared.animation.UnfoldMoveFromCenterAnimator
+import com.android.systemui.statusbar.policy.Clock
 import com.android.systemui.statusbar.policy.ConfigurationController
 import com.android.systemui.statusbar.window.StatusBarWindowStateController
 import com.android.systemui.unfold.SysUIUnfoldComponent
@@ -68,19 +72,27 @@
     private val viewUtil: ViewUtil,
     private val configurationController: ConfigurationController,
     private val statusOverlayHoverListenerFactory: StatusOverlayHoverListenerFactory,
+    private val darkIconDispatcher: DarkIconDispatcher,
 ) : ViewController<PhoneStatusBarView>(view) {
 
+    private lateinit var battery: BatteryMeterView
+    private lateinit var clock: Clock
     private lateinit var statusContainer: View
 
     private val configurationListener =
         object : ConfigurationController.ConfigurationListener {
             override fun onDensityOrFontScaleChanged() {
-                mView.onDensityOrFontScaleChanged()
+                clock.onDensityOrFontScaleChanged()
             }
         }
 
     override fun onViewAttached() {
         statusContainer = mView.requireViewById(R.id.system_icons)
+        clock = mView.requireViewById(R.id.clock)
+        battery = mView.requireViewById(R.id.battery)
+
+        addDarkReceivers()
+
         statusContainer.setOnHoverListener(
             statusOverlayHoverListenerFactory.createDarkAwareListener(statusContainer)
         )
@@ -133,7 +145,9 @@
         }
     }
 
-    override fun onViewDetached() {
+    @VisibleForTesting
+    public override fun onViewDetached() {
+        removeDarkReceivers()
         statusContainer.setOnHoverListener(null)
         progressProvider?.setReadyToHandleTransition(false)
         moveFromCenterAnimationController?.onViewDetached()
@@ -182,6 +196,16 @@
         }
     }
 
+    private fun addDarkReceivers() {
+        darkIconDispatcher.addDarkReceiver(battery)
+        darkIconDispatcher.addDarkReceiver(clock)
+    }
+
+    private fun removeDarkReceivers() {
+        darkIconDispatcher.removeDarkReceiver(battery)
+        darkIconDispatcher.removeDarkReceiver(clock)
+    }
+
     inner class PhoneStatusBarViewTouchHandler : Gefingerpoken {
         override fun onInterceptTouchEvent(event: MotionEvent): Boolean {
             return if (Flags.statusBarSwipeOverChip()) {
@@ -285,6 +309,7 @@
         private val viewUtil: ViewUtil,
         private val configurationController: ConfigurationController,
         private val statusOverlayHoverListenerFactory: StatusOverlayHoverListenerFactory,
+        private val darkIconDispatcher: DarkIconDispatcher,
     ) {
         fun create(view: PhoneStatusBarView): PhoneStatusBarViewController {
             val statusBarMoveFromCenterAnimationController =
@@ -309,6 +334,7 @@
                 viewUtil,
                 configurationController,
                 statusOverlayHoverListenerFactory,
+                darkIconDispatcher,
             )
         }
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
index 5486abb..de4d14d 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
@@ -1007,7 +1007,9 @@
             mKeyguardMessageAreaController.setIsVisible(isShowingAlternateBouncer);
             mKeyguardMessageAreaController.setMessage("");
         }
-        mKeyguardUpdateManager.setAlternateBouncerShowing(isShowingAlternateBouncer);
+        if (!SceneContainerFlag.isEnabled()) {
+            mKeyguardUpdateManager.setAlternateBouncerShowing(isShowingAlternateBouncer);
+        }
 
         if (updateScrim) {
             mCentralSurfaces.updateScrimController();
@@ -1449,10 +1451,13 @@
             mNotificationShadeWindowController.setBouncerShowing(primaryBouncerShowing);
             mCentralSurfaces.setBouncerShowing(primaryBouncerShowing);
         }
-        if (primaryBouncerIsOrWillBeShowing != mLastPrimaryBouncerIsOrWillBeShowing || mFirstUpdate
-                || isPrimaryBouncerShowingChanged) {
-            mKeyguardUpdateManager.sendPrimaryBouncerChanged(primaryBouncerIsOrWillBeShowing,
-                    primaryBouncerShowing);
+        if (!SceneContainerFlag.isEnabled()) {
+            if (primaryBouncerIsOrWillBeShowing != mLastPrimaryBouncerIsOrWillBeShowing
+                    || mFirstUpdate
+                    || isPrimaryBouncerShowingChanged) {
+                mKeyguardUpdateManager.sendPrimaryBouncerChanged(primaryBouncerIsOrWillBeShowing,
+                        primaryBouncerShowing);
+            }
         }
 
         mFirstUpdate = false;
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 e92058b..0a6e7f5 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationActivityStarter.java
@@ -230,7 +230,8 @@
         Runnable action = () -> {
             mBubblesManagerOptional.ifPresent(bubblesManager ->
                     bubblesManager.onUserChangedBubble(entry, !entry.isBubble()));
-            mHeadsUpManager.removeNotification(entry.getKey(), /* releaseImmediately= */ true);
+            mHeadsUpManager.removeNotification(entry.getKey(), /* releaseImmediately= */ true,
+                    /* reason= */ "onNotificationBubbleIconClicked");
         };
         if (entry.isBubble()) {
             // entry is being un-bubbled, no need to unlock
@@ -621,7 +622,8 @@
 
             // In most cases, when FLAG_AUTO_CANCEL is set, the notification will
             // become canceled shortly by NoMan, but we can't assume that.
-            mHeadsUpManager.removeNotification(key, true /* releaseImmediately */);
+            mHeadsUpManager.removeNotification(key, /* releaseImmediately= */ true,
+                    "removeHunAfterClick");
         }
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/TapAgainView.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/TapAgainView.java
index 66ac17e..b9cba99 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/TapAgainView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/TapAgainView.java
@@ -31,7 +31,7 @@
 
 import com.android.settingslib.Utils;
 import com.android.systemui.res.R;
-import com.android.wm.shell.animation.Interpolators;
+import com.android.wm.shell.shared.animation.Interpolators;
 
 /**
  * View to show a toast-like popup on the notification shade and quick settings.
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BaseHeadsUpManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BaseHeadsUpManager.java
index 3786958..f37393a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BaseHeadsUpManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BaseHeadsUpManager.java
@@ -40,13 +40,14 @@
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
 import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.InflationFlag;
 import com.android.systemui.statusbar.notification.shared.NotificationThrottleHun;
-import com.android.systemui.statusbar.notification.shared.NotificationsHeadsUpRefactor;
 import com.android.systemui.statusbar.phone.ExpandHeadsUpOnInlineReply;
 import com.android.systemui.util.ListenerSet;
 import com.android.systemui.util.concurrency.DelayableExecutor;
 import com.android.systemui.util.settings.GlobalSettings;
 import com.android.systemui.util.time.SystemClock;
 
+import org.jetbrains.annotations.NotNull;
+
 import java.io.PrintWriter;
 import java.util.ArrayList;
 import java.util.List;
@@ -191,12 +192,14 @@
      * enough and needs to be kept around.
      * @param key the key of the notification to remove
      * @param releaseImmediately force a remove regardless of earliest removal time
+     * @param reason reason for removing the notification
      * @return true if notification is removed, false otherwise
      */
     @Override
-    public boolean removeNotification(@NonNull String key, boolean releaseImmediately) {
+    public boolean removeNotification(@NotNull String key, boolean releaseImmediately,
+            @NonNull String reason) {
         final boolean isWaiting = mAvalancheController.isWaiting(key);
-        mLogger.logRemoveNotification(key, releaseImmediately, isWaiting);
+        mLogger.logRemoveNotification(key, releaseImmediately, isWaiting, reason);
 
         if (mAvalancheController.isWaiting(key)) {
             removeEntry(key, "removeNotification (isWaiting)");
@@ -204,6 +207,7 @@
         }
         HeadsUpEntry headsUpEntry = mHeadsUpEntryMap.get(key);
         if (headsUpEntry == null) {
+            mLogger.logNullEntry(key, reason);
             return true;
         }
         if (releaseImmediately) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/DeviceControlsControllerImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/policy/DeviceControlsControllerImpl.kt
index 1224275..e29e069 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/DeviceControlsControllerImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/DeviceControlsControllerImpl.kt
@@ -21,13 +21,12 @@
 import android.content.SharedPreferences
 import android.provider.Settings
 import android.util.Log
-import com.android.systemui.res.R
 import com.android.systemui.controls.ControlsServiceInfo
 import com.android.systemui.controls.dagger.ControlsComponent
 import com.android.systemui.controls.management.ControlsListingController
 import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.res.R
 import com.android.systemui.settings.UserContextProvider
-import com.android.systemui.statusbar.phone.AutoTileManager
 import com.android.systemui.statusbar.policy.DeviceControlsController.Callback
 import com.android.systemui.util.settings.SecureSettings
 import javax.inject.Inject
@@ -35,14 +34,16 @@
 /**
  * Watches for Device Controls QS Tile activation, which can happen in two ways:
  * <ol>
- *   <li>Migration from Power Menu - For existing Android 11 users, create a tile in a high
- *       priority position.
- *   <li>Device controls service becomes available - For non-migrated users, create a tile and
- *       place at the end of active tiles, and initiate seeding where possible.
+ * <li>Migration from Power Menu - For existing Android 11 users, create a tile in a high priority
+ *   position.
+ * <li>Device controls service becomes available - For non-migrated users, create a tile and place
+ *   at the end of active tiles, and initiate seeding where possible.
  * </ol>
  */
 @SysUISingleton
-public class DeviceControlsControllerImpl @Inject constructor(
+public class DeviceControlsControllerImpl
+@Inject
+constructor(
     private val context: Context,
     private val controlsComponent: ControlsComponent,
     private val userContextProvider: UserContextProvider,
@@ -52,13 +53,14 @@
     private var callback: Callback? = null
     internal var position: Int? = null
 
-    private val listingCallback = object : ControlsListingController.ControlsListingCallback {
-        override fun onServicesUpdated(serviceInfos: List<ControlsServiceInfo>) {
-            if (!serviceInfos.isEmpty()) {
-                seedFavorites(serviceInfos)
+    private val listingCallback =
+        object : ControlsListingController.ControlsListingCallback {
+            override fun onServicesUpdated(serviceInfos: List<ControlsServiceInfo>) {
+                if (!serviceInfos.isEmpty()) {
+                    seedFavorites(serviceInfos)
+                }
             }
         }
-    }
 
     companion object {
         private const val TAG = "DeviceControlsControllerImpl"
@@ -80,7 +82,7 @@
     }
 
     /**
-     * This migration logic assumes that something like [AutoTileManager] is tracking state
+     * This migration logic assumes that something like [AutoAddTracker] is tracking state
      * externally, and won't call this method after receiving a response via
      * [Callback#onControlsUpdate], once per user. Otherwise the calculated position may be
      * incorrect.
@@ -118,16 +120,19 @@
     }
 
     /**
-     * See if any available control service providers match one of the preferred components. If
-     * they do, and there are no current favorites for that component, query the preferred
-     * component for a limited number of suggested controls.
+     * See if any available control service providers match one of the preferred components. If they
+     * do, and there are no current favorites for that component, query the preferred component for
+     * a limited number of suggested controls.
      */
     private fun seedFavorites(serviceInfos: List<ControlsServiceInfo>) {
-        val preferredControlsPackages = context.getResources().getStringArray(
-            R.array.config_controlsPreferredPackages)
+        val preferredControlsPackages =
+            context.getResources().getStringArray(R.array.config_controlsPreferredPackages)
 
-        val prefs = userContextProvider.userContext.getSharedPreferences(
-            PREFS_CONTROLS_FILE, Context.MODE_PRIVATE)
+        val prefs =
+            userContextProvider.userContext.getSharedPreferences(
+                PREFS_CONTROLS_FILE,
+                Context.MODE_PRIVATE
+            )
         val seededPackages =
             prefs.getStringSet(PREFS_CONTROLS_SEEDING_COMPLETED, emptySet()) ?: emptySet()
 
@@ -157,21 +162,22 @@
         if (componentsToSeed.isEmpty()) return
 
         controlsController.seedFavoritesForComponents(
-                componentsToSeed,
-                { response ->
-                    Log.d(TAG, "Controls seeded: $response")
-                    if (response.accepted) {
-                        addPackageToSeededSet(prefs, response.packageName)
-                        if (position == null) {
-                            position = QS_DEFAULT_POSITION
-                        }
-                        fireControlsUpdate()
-
-                        controlsComponent.getControlsListingController().ifPresent {
-                            it.removeCallback(listingCallback)
-                        }
+            componentsToSeed,
+            { response ->
+                Log.d(TAG, "Controls seeded: $response")
+                if (response.accepted) {
+                    addPackageToSeededSet(prefs, response.packageName)
+                    if (position == null) {
+                        position = QS_DEFAULT_POSITION
                     }
-                })
+                    fireControlsUpdate()
+
+                    controlsComponent.getControlsListingController().ifPresent {
+                        it.removeCallback(listingCallback)
+                    }
+                }
+            }
+        )
     }
 
     private fun addPackageToSeededSet(prefs: SharedPreferences, pkg: String) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManager.kt b/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManager.kt
index fcf77d5..04fe6b3 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManager.kt
@@ -96,9 +96,10 @@
      *
      * @param key the key of the notification to remove
      * @param releaseImmediately force a remove regardless of earliest removal time
+     * @param reason reason for removing the notification
      * @return true if notification is removed, false otherwise
      */
-    fun removeNotification(key: String, releaseImmediately: Boolean): Boolean
+    fun removeNotification(key: String, releaseImmediately: Boolean, reason: String): Boolean
 
     /**
      * Try to remove the notification. May not succeed if the notification has not been shown long
@@ -107,9 +108,15 @@
      * @param key the key of the notification to remove
      * @param releaseImmediately force a remove regardless of earliest removal time
      * @param animate if true, animate the removal
+     * @param reason reason for removing the notification
      * @return true if notification is removed, false otherwise
      */
-    fun removeNotification(key: String, releaseImmediately: Boolean, animate: Boolean): Boolean
+    fun removeNotification(
+        key: String,
+        releaseImmediately: Boolean,
+        animate: Boolean,
+        reason: String
+    ): Boolean
 
     /** Clears all managed notifications. */
     fun releaseAllImmediately()
@@ -246,11 +253,16 @@
 
     override fun removeListener(listener: OnHeadsUpChangedListener) {}
 
-    override fun removeNotification(key: String, releaseImmediately: Boolean) = false
-
-    override fun removeNotification(key: String, releaseImmediately: Boolean, animate: Boolean) =
+    override fun removeNotification(key: String, releaseImmediately: Boolean, reason: String) =
         false
 
+    override fun removeNotification(
+        key: String,
+        releaseImmediately: Boolean,
+        animate: Boolean,
+        reason: String
+    ) = false
+
     override fun setAnimationStateHandler(handler: AnimationStateHandler) {}
 
     override fun setExpanded(entry: NotificationEntry, expanded: Boolean) {}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManagerLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManagerLogger.kt
index 80c595f..c6fc547 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManagerLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/HeadsUpManagerLogger.kt
@@ -16,244 +16,283 @@
 
 package com.android.systemui.statusbar.policy
 
-import com.android.systemui.log.dagger.NotificationHeadsUpLog
 import com.android.systemui.log.LogBuffer
 import com.android.systemui.log.core.LogLevel.INFO
 import com.android.systemui.log.core.LogLevel.VERBOSE
+import com.android.systemui.log.dagger.NotificationHeadsUpLog
 import com.android.systemui.statusbar.notification.collection.NotificationEntry
 import com.android.systemui.statusbar.notification.logKey
 import javax.inject.Inject
 
 /** Logger for [HeadsUpManager]. */
-class HeadsUpManagerLogger @Inject constructor(
-    @NotificationHeadsUpLog private val buffer: LogBuffer
-) {
+class HeadsUpManagerLogger
+@Inject
+constructor(@NotificationHeadsUpLog private val buffer: LogBuffer) {
     fun logPackageSnoozed(snoozeKey: String) {
-        buffer.log(TAG, INFO, {
-            str1 = snoozeKey
-        }, {
-            "package snoozed $str1"
-        })
+        buffer.log(TAG, INFO, { str1 = snoozeKey }, { "package snoozed $str1" })
     }
 
     fun logPackageUnsnoozed(snoozeKey: String) {
-        buffer.log(TAG, INFO, {
-            str1 = snoozeKey
-        }, {
-            "package unsnoozed $str1"
-        })
+        buffer.log(TAG, INFO, { str1 = snoozeKey }, { "package unsnoozed $str1" })
     }
 
     fun logIsSnoozedReturned(snoozeKey: String) {
-        buffer.log(TAG, INFO, {
-            str1 = snoozeKey
-        }, {
-            "package snoozed when queried $str1"
-        })
+        buffer.log(TAG, INFO, { str1 = snoozeKey }, { "package snoozed when queried $str1" })
     }
 
     fun logReleaseAllImmediately() {
-        buffer.log(TAG, INFO, { }, {
-            "release all immediately"
-        })
+        buffer.log(TAG, INFO, {}, { "release all immediately" })
     }
 
     fun logShowNotificationRequest(entry: NotificationEntry) {
-        buffer.log(TAG, INFO, {
-            str1 = entry.logKey
-        }, {
-            "request: show notification $str1"
-        })
+        buffer.log(TAG, INFO, { str1 = entry.logKey }, { "request: show notification $str1" })
     }
 
-    fun logAvalancheUpdate(caller: String, isEnabled: Boolean, notifEntryKey: String,
-                           outcome: String) {
-        buffer.log(TAG, INFO, {
-            str1 = caller
-            str2 = notifEntryKey
-            str3 = outcome
-            bool1 = isEnabled
-        }, {
-            "$str1\n\t=> AC[isEnabled:$bool1] update: $str2\n\t=> $str3"
-        })
+    fun logAvalancheUpdate(
+        caller: String,
+        isEnabled: Boolean,
+        notifEntryKey: String,
+        outcome: String
+    ) {
+        buffer.log(
+            TAG,
+            INFO,
+            {
+                str1 = caller
+                str2 = notifEntryKey
+                str3 = outcome
+                bool1 = isEnabled
+            },
+            { "$str1\n\t=> AC[isEnabled:$bool1] update: $str2\n\t=> $str3" }
+        )
     }
 
-    fun logAvalancheDelete(caller: String, isEnabled: Boolean, notifEntryKey: String,
-                           outcome: String) {
-        buffer.log(TAG, INFO, {
-            str1 = caller
-            str2 = notifEntryKey
-            str3 = outcome
-            bool1 = isEnabled
-        }, {
-            "$str1\n\t=> AC[isEnabled:$bool1] delete: $str2\n\t=> $str3"
-        })
+    fun logAvalancheDelete(
+        caller: String,
+        isEnabled: Boolean,
+        notifEntryKey: String,
+        outcome: String
+    ) {
+        buffer.log(
+            TAG,
+            INFO,
+            {
+                str1 = caller
+                str2 = notifEntryKey
+                str3 = outcome
+                bool1 = isEnabled
+            },
+            { "$str1\n\t=> AC[isEnabled:$bool1] delete: $str2\n\t=> $str3" }
+        )
     }
 
     fun logShowNotification(entry: NotificationEntry) {
-        buffer.log(TAG, INFO, {
-            str1 = entry.logKey
-        }, {
-            "show notification $str1"
-        })
+        buffer.log(TAG, INFO, { str1 = entry.logKey }, { "show notification $str1" })
     }
 
     fun logAutoRemoveScheduled(entry: NotificationEntry, delayMillis: Long, reason: String) {
-        buffer.log(TAG, INFO, {
-            str1 = entry.logKey
-            long1 = delayMillis
-            str2 = reason
-        }, {
-            "schedule auto remove of $str1 in $long1 ms reason: $str2"
-        })
+        buffer.log(
+            TAG,
+            INFO,
+            {
+                str1 = entry.logKey
+                long1 = delayMillis
+                str2 = reason
+            },
+            { "schedule auto remove of $str1 in $long1 ms reason: $str2" }
+        )
     }
 
     fun logAutoRemoveRequest(entry: NotificationEntry, reason: String) {
-        buffer.log(TAG, INFO, {
-            str1 = entry.logKey
-            str2 = reason
-        }, {
-            "request: reschedule auto remove of $str1 reason: $str2"
-        })
+        buffer.log(
+            TAG,
+            INFO,
+            {
+                str1 = entry.logKey
+                str2 = reason
+            },
+            { "request: reschedule auto remove of $str1 reason: $str2" }
+        )
     }
 
     fun logAutoRemoveRescheduled(entry: NotificationEntry, delayMillis: Long, reason: String) {
-        buffer.log(TAG, INFO, {
-            str1 = entry.logKey
-            long1 = delayMillis
-            str2 = reason
-        }, {
-            "reschedule auto remove of $str1 in $long1 ms reason: $str2"
-        })
+        buffer.log(
+            TAG,
+            INFO,
+            {
+                str1 = entry.logKey
+                long1 = delayMillis
+                str2 = reason
+            },
+            { "reschedule auto remove of $str1 in $long1 ms reason: $str2" }
+        )
     }
 
     fun logAutoRemoveCancelRequest(entry: NotificationEntry, reason: String?) {
-        buffer.log(TAG, INFO, {
-            str1 = entry.logKey
-            str2 = reason ?: "unknown"
-        }, {
-            "request: cancel auto remove of $str1 reason: $str2"
-        })
+        buffer.log(
+            TAG,
+            INFO,
+            {
+                str1 = entry.logKey
+                str2 = reason ?: "unknown"
+            },
+            { "request: cancel auto remove of $str1 reason: $str2" }
+        )
     }
 
     fun logAutoRemoveCanceled(entry: NotificationEntry, reason: String?) {
-        buffer.log(TAG, INFO, {
-            str1 = entry.logKey
-            str2 = reason ?: "unknown"
-        }, {
-            "cancel auto remove of $str1 reason: $str2"
-        })
+        buffer.log(
+            TAG,
+            INFO,
+            {
+                str1 = entry.logKey
+                str2 = reason ?: "unknown"
+            },
+            { "cancel auto remove of $str1 reason: $str2" }
+        )
     }
 
     fun logRemoveEntryRequest(key: String, reason: String, isWaiting: Boolean) {
-        buffer.log(TAG, INFO, {
-            str1 = logKey(key)
-            str2 = reason
-            bool1 = isWaiting
-        }, {
-            "request: $str2 => remove entry $str1 isWaiting: $isWaiting"
-        })
+        buffer.log(
+            TAG,
+            INFO,
+            {
+                str1 = logKey(key)
+                str2 = reason
+                bool1 = isWaiting
+            },
+            { "request: $str2 => remove entry $str1 isWaiting: $isWaiting" }
+        )
     }
 
     fun logRemoveEntry(key: String, reason: String, isWaiting: Boolean) {
-        buffer.log(TAG, INFO, {
-            str1 = logKey(key)
-            str2 = reason
-            bool1 = isWaiting
-        }, {
-            "$str2 => remove entry $str1 isWaiting: $isWaiting"
-        })
+        buffer.log(
+            TAG,
+            INFO,
+            {
+                str1 = logKey(key)
+                str2 = reason
+                bool1 = isWaiting
+            },
+            { "$str2 => remove entry $str1 isWaiting: $isWaiting" }
+        )
     }
 
     fun logUnpinEntryRequest(key: String) {
-        buffer.log(TAG, INFO, {
-            str1 = logKey(key)
-        }, {
-            "request: unpin entry $str1"
-        })
+        buffer.log(TAG, INFO, { str1 = logKey(key) }, { "request: unpin entry $str1" })
     }
 
     fun logUnpinEntry(key: String) {
-        buffer.log(TAG, INFO, {
-            str1 = logKey(key)
-        }, {
-            "unpin entry $str1"
-        })
+        buffer.log(TAG, INFO, { str1 = logKey(key) }, { "unpin entry $str1" })
     }
 
-    fun logRemoveNotification(key: String, releaseImmediately: Boolean, isWaiting: Boolean) {
-        buffer.log(TAG, INFO, {
-            str1 = logKey(key)
-            bool1 = releaseImmediately
-            bool2 = isWaiting
-        }, {
-            "remove notification $str1 releaseImmediately: $bool1 isWaiting: $bool2"
-        })
+    fun logRemoveNotification(
+        key: String,
+        releaseImmediately: Boolean,
+        isWaiting: Boolean,
+        reason: String
+    ) {
+        buffer.log(
+            TAG,
+            INFO,
+            {
+                str1 = logKey(key)
+                bool1 = releaseImmediately
+                bool2 = isWaiting
+                str2 = reason
+            },
+            {
+                "remove notification $str1 releaseImmediately: $bool1 isWaiting: $bool2 " +
+                    "reason: $str2"
+            }
+        )
+    }
+
+    fun logNullEntry(key: String, reason: String) {
+        buffer.log(
+            TAG,
+            INFO,
+            {
+                str1 = logKey(key)
+                str2 = reason
+            },
+            { "remove notification $str1 when headsUpEntry is null, reason: $str2" }
+        )
     }
 
     fun logNotificationActuallyRemoved(entry: NotificationEntry) {
-        buffer.log(TAG, INFO, {
-            str1 = entry.logKey
-        }, {
-            "notification removed $str1 "
-        })
+        buffer.log(TAG, INFO, { str1 = entry.logKey }, { "notification removed $str1 " })
     }
 
     fun logUpdateNotificationRequest(key: String, alert: Boolean, hasEntry: Boolean) {
-        buffer.log(TAG, INFO, {
-            str1 = logKey(key)
-            bool1 = alert
-            bool2 = hasEntry
-        }, {
-            "request: update notification $str1 alert: $bool1 hasEntry: $bool2"
-        })
+        buffer.log(
+            TAG,
+            INFO,
+            {
+                str1 = logKey(key)
+                bool1 = alert
+                bool2 = hasEntry
+            },
+            { "request: update notification $str1 alert: $bool1 hasEntry: $bool2" }
+        )
     }
 
     fun logUpdateNotification(key: String, alert: Boolean, hasEntry: Boolean) {
-        buffer.log(TAG, INFO, {
-            str1 = logKey(key)
-            bool1 = alert
-            bool2 = hasEntry
-        }, {
-            "update notification $str1 alert: $bool1 hasEntry: $bool2"
-        })
+        buffer.log(
+            TAG,
+            INFO,
+            {
+                str1 = logKey(key)
+                bool1 = alert
+                bool2 = hasEntry
+            },
+            { "update notification $str1 alert: $bool1 hasEntry: $bool2" }
+        )
     }
 
     fun logUpdateEntry(entry: NotificationEntry, updatePostTime: Boolean, reason: String?) {
-        buffer.log(TAG, INFO, {
-            str1 = entry.logKey
-            bool1 = updatePostTime
-            str2 = reason ?: "unknown"
-        }, {
-            "update entry $str1 updatePostTime: $bool1 reason: $str2"
-        })
+        buffer.log(
+            TAG,
+            INFO,
+            {
+                str1 = entry.logKey
+                bool1 = updatePostTime
+                str2 = reason ?: "unknown"
+            },
+            { "update entry $str1 updatePostTime: $bool1 reason: $str2" }
+        )
     }
 
     fun logSnoozeLengthChange(packageSnoozeLengthMs: Int) {
-        buffer.log(TAG, INFO, {
-            int1 = packageSnoozeLengthMs
-        }, {
-            "snooze length changed: ${int1}ms"
-        })
+        buffer.log(
+            TAG,
+            INFO,
+            { int1 = packageSnoozeLengthMs },
+            { "snooze length changed: ${int1}ms" }
+        )
     }
 
     fun logSetEntryPinned(entry: NotificationEntry, isPinned: Boolean, reason: String) {
-        buffer.log(TAG, VERBOSE, {
-            str1 = entry.logKey
-            bool1 = isPinned
-            str2 = reason
-        }, {
-            "$str2 => set entry pinned $str1 pinned: $bool1"
-        })
+        buffer.log(
+            TAG,
+            VERBOSE,
+            {
+                str1 = entry.logKey
+                bool1 = isPinned
+                str2 = reason
+            },
+            { "$str2 => set entry pinned $str1 pinned: $bool1" }
+        )
     }
 
     fun logUpdatePinnedMode(hasPinnedNotification: Boolean) {
-        buffer.log(TAG, INFO, {
-            bool1 = hasPinnedNotification
-        }, {
-            "has pinned notification changed to $bool1"
-        })
+        buffer.log(
+            TAG,
+            INFO,
+            { bool1 = hasPinnedNotification },
+            { "has pinned notification changed to $bool1" }
+        )
     }
 }
 
-private const val TAG = "HeadsUpManager"
\ No newline at end of file
+private const val TAG = "HeadsUpManager"
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/ui/dialog/ModesDialogDelegate.kt b/packages/SystemUI/src/com/android/systemui/statusbar/policy/ui/dialog/ModesDialogDelegate.kt
index b0ebf37..e1dcc52 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/ui/dialog/ModesDialogDelegate.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/ui/dialog/ModesDialogDelegate.kt
@@ -61,6 +61,7 @@
     private val activityStarter: ActivityStarter,
     // Using a provider to avoid a circular dependency.
     private val viewModel: Provider<ModesDialogViewModel>,
+    private val dialogEventLogger: ModesDialogEventLogger,
     @Main private val mainCoroutineContext: CoroutineContext,
 ) : SystemUIDialog.Delegate {
     // NOTE: This should only be accessed/written from the main thread.
@@ -114,7 +115,9 @@
         )
     }
 
-    private fun openSettings(dialog: SystemUIDialog) {
+    @VisibleForTesting
+    fun openSettings(dialog: SystemUIDialog) {
+        dialogEventLogger.logDialogSettings()
         val animationController =
             dialogTransitionAnimator.createActivityTransitionController(dialog)
         if (animationController == null) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/ui/dialog/ModesDialogEventLogger.kt b/packages/SystemUI/src/com/android/systemui/statusbar/policy/ui/dialog/ModesDialogEventLogger.kt
new file mode 100644
index 0000000..33ed419
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/ui/dialog/ModesDialogEventLogger.kt
@@ -0,0 +1,60 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.policy.ui.dialog
+
+import com.android.internal.logging.UiEventLogger
+import com.android.settingslib.notification.modes.ZenMode
+import com.android.systemui.qs.QSModesEvent
+import javax.inject.Inject
+
+class ModesDialogEventLogger
+@Inject
+constructor(
+    private val uiEventLogger: UiEventLogger,
+) {
+
+    fun logModeOn(mode: ZenMode) {
+        val id =
+            if (mode.isManualDnd) QSModesEvent.QS_MODES_DND_ON else QSModesEvent.QS_MODES_MODE_ON
+        uiEventLogger.log(id, /* uid= */ 0, mode.rule.packageName)
+    }
+
+    fun logModeOff(mode: ZenMode) {
+        val id =
+            if (mode.isManualDnd) QSModesEvent.QS_MODES_DND_OFF else QSModesEvent.QS_MODES_MODE_OFF
+        uiEventLogger.log(id, /* uid= */ 0, mode.rule.packageName)
+    }
+
+    fun logModeSettings(mode: ZenMode) {
+        val id =
+            if (mode.isManualDnd) QSModesEvent.QS_MODES_DND_SETTINGS
+            else QSModesEvent.QS_MODES_MODE_SETTINGS
+        uiEventLogger.log(id, /* uid= */ 0, mode.rule.packageName)
+    }
+
+    fun logOpenDurationDialog(mode: ZenMode) {
+        // should only occur for manual Do Not Disturb.
+        if (!mode.isManualDnd) {
+            return
+        }
+        uiEventLogger.log(QSModesEvent.QS_MODES_DURATION_DIALOG)
+    }
+
+    fun logDialogSettings() {
+        uiEventLogger.log(QSModesEvent.QS_MODES_SETTINGS)
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/ui/dialog/viewmodel/ModesDialogViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/policy/ui/dialog/viewmodel/ModesDialogViewModel.kt
index 44b692f..5772099 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/ui/dialog/viewmodel/ModesDialogViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/ui/dialog/viewmodel/ModesDialogViewModel.kt
@@ -30,6 +30,7 @@
 import com.android.systemui.statusbar.phone.SystemUIDialog
 import com.android.systemui.statusbar.policy.domain.interactor.ZenModeInteractor
 import com.android.systemui.statusbar.policy.ui.dialog.ModesDialogDelegate
+import com.android.systemui.statusbar.policy.ui.dialog.ModesDialogEventLogger
 import javax.inject.Inject
 import kotlinx.coroutines.CoroutineDispatcher
 import kotlinx.coroutines.flow.Flow
@@ -49,6 +50,7 @@
     zenModeInteractor: ZenModeInteractor,
     @Background val bgDispatcher: CoroutineDispatcher,
     private val dialogDelegate: ModesDialogDelegate,
+    private val dialogEventLogger: ModesDialogEventLogger,
 ) {
     private val zenDialogMetricsLogger = QSZenModeDialogMetricsLogger(context)
 
@@ -94,14 +96,17 @@
                             if (!mode.rule.isEnabled) {
                                 openSettings(mode)
                             } else if (mode.isActive) {
+                                dialogEventLogger.logModeOff(mode)
                                 zenModeInteractor.deactivateMode(mode)
                             } else {
                                 if (mode.rule.isManualInvocationAllowed) {
                                     if (zenModeInteractor.shouldAskForZenDuration(mode)) {
+                                        dialogEventLogger.logOpenDurationDialog(mode)
                                         // NOTE: The dialog handles turning on the mode itself.
                                         val dialog = makeZenModeDialog()
                                         dialog.show()
                                     } else {
+                                        dialogEventLogger.logModeOn(mode)
                                         zenModeInteractor.activateMode(mode)
                                     }
                                 }
@@ -114,6 +119,7 @@
             .flowOn(bgDispatcher)
 
     private fun openSettings(mode: ZenMode) {
+        dialogEventLogger.logModeSettings(mode)
         val intent: Intent =
             Intent(ACTION_AUTOMATIC_ZEN_RULE_SETTINGS)
                 .putExtra(EXTRA_AUTOMATIC_ZEN_RULE_ID, mode.id)
diff --git a/packages/SystemUI/src/com/android/systemui/util/kotlin/FlowDumper.kt b/packages/SystemUI/src/com/android/systemui/util/kotlin/FlowDumper.kt
index ade6c3d..ef9f8ff 100644
--- a/packages/SystemUI/src/com/android/systemui/util/kotlin/FlowDumper.kt
+++ b/packages/SystemUI/src/com/android/systemui/util/kotlin/FlowDumper.kt
@@ -170,7 +170,7 @@
  * [Activatable.activate()][com.android.systemui.lifecycle.Activatable.activate].
  */
 interface ActivatableFlowDumper : FlowDumper {
-    suspend fun activateFlowDumper()
+    suspend fun activateFlowDumper(): Nothing
 }
 
 /**
@@ -190,7 +190,7 @@
 
     private val registration =
         object : SafeActivatable() {
-            override suspend fun onActivated() {
+            override suspend fun onActivated(): Nothing {
                 try {
                     dumpManager.registerCriticalDumpable(
                         dumpManagerName,
@@ -205,7 +205,7 @@
 
     private val dumpManagerName = "[$idString] $tag"
 
-    override suspend fun activateFlowDumper() {
+    override suspend fun activateFlowDumper(): Nothing {
         registration.activate()
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogControllerImpl.java b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogControllerImpl.java
index 066bfc5..1522cc4 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogControllerImpl.java
@@ -165,6 +165,7 @@
     private boolean mShowSafetyWarning;
     private long mLastToggledRingerOn;
     private boolean mDeviceInteractive = true;
+    boolean mInAudioSharing = false;
 
     private VolumePolicy mVolumePolicy;
     @GuardedBy("this")
@@ -295,6 +296,9 @@
             mJavaAdapter.alwaysCollectFlow(
                     mAudioSharingInteractor.getVolume(),
                     this::handleAudioSharingStreamVolumeChanges);
+            mJavaAdapter.alwaysCollectFlow(
+                    mAudioSharingInteractor.isInAudioSharing(),
+                    inSharing -> mInAudioSharing = inSharing);
         }
     }
 
@@ -510,11 +514,18 @@
             //       Since their values overlap with DEVICE_OUT_EARPIECE and DEVICE_OUT_SPEAKER.
             //       Anyway, we can check BLE devices by using just DEVICE_OUT_BLE_HEADSET.
             final boolean routedToBluetooth =
-                    (mAudio.getDevicesForStream(AudioManager.STREAM_MUSIC) &
-                            (AudioManager.DEVICE_OUT_BLUETOOTH_A2DP |
-                            AudioManager.DEVICE_OUT_BLUETOOTH_A2DP_HEADPHONES |
-                            AudioManager.DEVICE_OUT_BLUETOOTH_A2DP_SPEAKER |
-                            AudioManager.DEVICE_OUT_BLE_HEADSET)) != 0;
+                    // TODO(b/359737651): Need audio support to return broadcast mask.
+                    // For now, mAudio.getDevicesForStream(AudioManager.STREAM_MUSIC) will return
+                    // AudioManager.DEVICE_NONE, so we also need to check if the device is in audio
+                    // sharing here.
+                    mInAudioSharing
+                            || (mAudio.getDevicesForStream(AudioManager.STREAM_MUSIC)
+                                            & (AudioManager.DEVICE_OUT_BLUETOOTH_A2DP
+                                                    | AudioManager
+                                                            .DEVICE_OUT_BLUETOOTH_A2DP_HEADPHONES
+                                                    | AudioManager.DEVICE_OUT_BLUETOOTH_A2DP_SPEAKER
+                                                    | AudioManager.DEVICE_OUT_BLE_HEADSET))
+                                    != 0;
             changed |= updateStreamRoutedToBluetoothW(stream, routedToBluetooth);
         } else if (stream == AudioManager.STREAM_VOICE_CALL) {
             final boolean routedToBluetooth =
@@ -813,6 +824,7 @@
                 ss.dynamic = true;
                 ss.levelMin = mAudioSharingInteractor.getVolumeMin();
                 ss.levelMax = mAudioSharingInteractor.getVolumeMax();
+                ss.routedToBluetooth = true;
                 if (ss.level != volume) {
                     ss.level = volume;
                 }
diff --git a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java
index e56f6b3..2468449 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/volume/VolumeDialogImpl.java
@@ -1892,8 +1892,8 @@
                                 .equals(ss.remoteLabel)) {
                     addRow(
                             stream,
-                            R.drawable.ic_volume_media,
-                            R.drawable.ic_volume_media_mute,
+                            R.drawable.ic_volume_media_bt,
+                            R.drawable.ic_volume_media_bt_mute,
                             true,
                             false,
                             true);
diff --git a/packages/SystemUI/src/com/android/systemui/volume/domain/interactor/AudioOutputInteractor.kt b/packages/SystemUI/src/com/android/systemui/volume/domain/interactor/AudioOutputInteractor.kt
index 154737c..4f77cd0 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/domain/interactor/AudioOutputInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/domain/interactor/AudioOutputInteractor.kt
@@ -26,7 +26,6 @@
 import com.android.settingslib.media.MediaDevice.MediaDeviceType
 import com.android.settingslib.media.PhoneMediaDevice
 import com.android.settingslib.volume.data.repository.AudioRepository
-import com.android.settingslib.volume.data.repository.AudioSharingRepository
 import com.android.settingslib.volume.domain.interactor.AudioModeInteractor
 import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.dagger.qualifiers.Background
@@ -37,7 +36,6 @@
 import kotlin.coroutines.CoroutineContext
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.SharingStarted
 import kotlinx.coroutines.flow.StateFlow
 import kotlinx.coroutines.flow.flatMapLatest
@@ -60,7 +58,6 @@
     private val bluetoothAdapter: BluetoothAdapter?,
     private val deviceIconInteractor: DeviceIconInteractor,
     private val mediaOutputInteractor: MediaOutputInteractor,
-    audioSharingRepository: AudioSharingRepository,
 ) {
 
     val currentAudioDevice: StateFlow<AudioOutputDevice> =
@@ -80,9 +77,6 @@
             .flowOn(backgroundCoroutineContext)
             .stateIn(scope, SharingStarted.Eagerly, AudioOutputDevice.Unknown)
 
-    /** Whether the device is in audio sharing */
-    val isInAudioSharing: Flow<Boolean> = audioSharingRepository.inAudioSharing
-
     private fun AudioDeviceInfo.toAudioOutputDevice(): AudioOutputDevice {
         if (
             BluetoothAdapter.checkBluetoothAddress(address) &&
diff --git a/packages/SystemUI/src/com/android/systemui/volume/domain/interactor/AudioSharingInteractor.kt b/packages/SystemUI/src/com/android/systemui/volume/domain/interactor/AudioSharingInteractor.kt
index 2170c36..9aed8ab 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/domain/interactor/AudioSharingInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/domain/interactor/AudioSharingInteractor.kt
@@ -36,11 +36,15 @@
 import kotlinx.coroutines.flow.emptyFlow
 import kotlinx.coroutines.flow.filterNotNull
 import kotlinx.coroutines.flow.first
+import kotlinx.coroutines.flow.flowOf
 import kotlinx.coroutines.flow.map
 import kotlinx.coroutines.launch
 import kotlinx.coroutines.withContext
 
 interface AudioSharingInteractor {
+    /** Audio sharing state on the device. */
+    val isInAudioSharing: Flow<Boolean>
+
     /** Audio sharing secondary headset volume changes. */
     val volume: Flow<Int?>
 
@@ -76,6 +80,7 @@
     private val audioVolumeInteractor: AudioVolumeInteractor,
     private val audioSharingRepository: AudioSharingRepository
 ) : AudioSharingInteractor {
+    override val isInAudioSharing: Flow<Boolean> = audioSharingRepository.inAudioSharing
 
     override val volume: Flow<Int?> =
         combine(audioSharingRepository.secondaryGroupId, audioSharingRepository.volumeMap) {
@@ -125,13 +130,13 @@
     }
 
     private companion object {
-        const val TAG = "AudioSharingInteractor"
         const val DEFAULT_VOLUME = 20
     }
 }
 
 @SysUISingleton
 class AudioSharingInteractorEmptyImpl @Inject constructor() : AudioSharingInteractor {
+    override val isInAudioSharing: Flow<Boolean> = flowOf(false)
     override val volume: Flow<Int?> = emptyFlow()
     override val volumeMin: Int = EMPTY_VOLUME
     override val volumeMax: Int = EMPTY_VOLUME
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/domain/interactor/MediaOutputComponentInteractor.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/domain/interactor/MediaOutputComponentInteractor.kt
index ed25129..a270d5f 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/domain/interactor/MediaOutputComponentInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/component/mediaoutput/domain/interactor/MediaOutputComponentInteractor.kt
@@ -18,6 +18,7 @@
 
 import com.android.settingslib.volume.domain.interactor.AudioModeInteractor
 import com.android.systemui.volume.domain.interactor.AudioOutputInteractor
+import com.android.systemui.volume.domain.interactor.AudioSharingInteractor
 import com.android.systemui.volume.domain.model.AudioOutputDevice
 import com.android.systemui.volume.panel.component.mediaoutput.domain.model.MediaOutputComponentModel
 import com.android.systemui.volume.panel.component.mediaoutput.shared.model.SessionWithPlaybackState
@@ -49,11 +50,12 @@
     private val mediaDeviceSessionInteractor: MediaDeviceSessionInteractor,
     audioOutputInteractor: AudioOutputInteractor,
     audioModeInteractor: AudioModeInteractor,
-    interactor: MediaOutputInteractor,
+    mediaOutputInteractor: MediaOutputInteractor,
+    audioSharingInteractor: AudioSharingInteractor,
 ) {
 
     private val sessionWithPlaybackState: StateFlow<Result<SessionWithPlaybackState?>> =
-        interactor.defaultActiveMediaSession
+        mediaOutputInteractor.defaultActiveMediaSession
             .filterData()
             .flatMapLatest { session ->
                 if (session == null) {
@@ -77,7 +79,7 @@
     val mediaOutputModel: StateFlow<Result<MediaOutputComponentModel>> =
         audioModeInteractor.isOngoingCall
             .flatMapLatest { isOngoingCall ->
-                audioOutputInteractor.isInAudioSharing.flatMapLatest { isInAudioSharing ->
+                audioSharingInteractor.isInAudioSharing.flatMapLatest { isInAudioSharing ->
                     if (isOngoingCall) {
                         currentAudioDevice.map {
                             MediaOutputComponentModel.Calling(it, isInAudioSharing)
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/domain/interactor/AudioSlidersInteractor.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/domain/interactor/AudioSlidersInteractor.kt
index fa40059..0451ce6 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/domain/interactor/AudioSlidersInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/domain/interactor/AudioSlidersInteractor.kt
@@ -19,6 +19,7 @@
 import android.media.AudioDeviceInfo
 import android.media.AudioManager
 import com.android.settingslib.volume.data.repository.AudioRepository
+import com.android.settingslib.volume.domain.interactor.AudioModeInteractor
 import com.android.settingslib.volume.shared.model.AudioStream
 import com.android.systemui.volume.panel.component.mediaoutput.domain.interactor.MediaOutputInteractor
 import com.android.systemui.volume.panel.component.mediaoutput.shared.model.MediaDeviceSession
@@ -42,6 +43,7 @@
     @VolumePanelScope scope: CoroutineScope,
     mediaOutputInteractor: MediaOutputInteractor,
     audioRepository: AudioRepository,
+    audioModeInteractor: AudioModeInteractor,
 ) {
 
     val volumePanelSliders: StateFlow<List<SliderType>> =
@@ -49,9 +51,14 @@
                 mediaOutputInteractor.activeMediaDeviceSessions,
                 mediaOutputInteractor.defaultActiveMediaSession.filterData(),
                 audioRepository.communicationDevice,
-            ) { activeSessions, defaultSession, communicationDevice ->
+                audioModeInteractor.isOngoingCall,
+            ) { activeSessions, defaultSession, communicationDevice, isOngoingCall ->
                 coroutineScope {
                     val viewModels = buildList {
+                        if (isOngoingCall) {
+                            addCall(communicationDevice?.type)
+                        }
+
                         if (defaultSession?.isTheSameSession(activeSessions.remote) == true) {
                             addSession(activeSessions.remote)
                             addStream(AudioManager.STREAM_MUSIC)
@@ -60,11 +67,10 @@
                             addSession(activeSessions.remote)
                         }
 
-                        if (communicationDevice?.type == AudioDeviceInfo.TYPE_BLUETOOTH_SCO) {
-                            addStream(AudioManager.STREAM_BLUETOOTH_SCO)
-                        } else {
-                            addStream(AudioManager.STREAM_VOICE_CALL)
+                        if (!isOngoingCall) {
+                            addCall(communicationDevice?.type)
                         }
+
                         addStream(AudioManager.STREAM_RING)
                         addStream(AudioManager.STREAM_NOTIFICATION)
                         addStream(AudioManager.STREAM_ALARM)
@@ -74,6 +80,14 @@
             }
             .stateIn(scope, SharingStarted.Eagerly, emptyList())
 
+    private fun MutableList<SliderType>.addCall(communicationDeviceType: Int?) {
+        if (communicationDeviceType == AudioDeviceInfo.TYPE_BLUETOOTH_SCO) {
+            addStream(AudioManager.STREAM_BLUETOOTH_SCO)
+        } else {
+            addStream(AudioManager.STREAM_VOICE_CALL)
+        }
+    }
+
     private fun MutableList<SliderType>.addSession(remoteMediaDeviceSession: MediaDeviceSession?) {
         if (remoteMediaDeviceSession?.canAdjustVolume == true) {
             add(SliderType.MediaDeviceCast(remoteMediaDeviceSession))
diff --git a/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/ui/viewmodel/AudioVolumeComponentViewModel.kt b/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/ui/viewmodel/AudioVolumeComponentViewModel.kt
index 4b4d69a..45732de 100644
--- a/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/ui/viewmodel/AudioVolumeComponentViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/volume/panel/component/volume/ui/viewmodel/AudioVolumeComponentViewModel.kt
@@ -16,6 +16,7 @@
 
 package com.android.systemui.volume.panel.component.volume.ui.viewmodel
 
+import com.android.settingslib.volume.domain.interactor.AudioModeInteractor
 import com.android.settingslib.volume.shared.model.AudioStream
 import com.android.systemui.volume.panel.component.mediaoutput.domain.interactor.MediaDeviceSessionInteractor
 import com.android.systemui.volume.panel.component.mediaoutput.domain.interactor.MediaOutputInteractor
@@ -35,9 +36,11 @@
 import kotlinx.coroutines.flow.MutableStateFlow
 import kotlinx.coroutines.flow.SharingStarted
 import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.combine
 import kotlinx.coroutines.flow.filterNotNull
 import kotlinx.coroutines.flow.flatMapLatest
 import kotlinx.coroutines.flow.flowOf
+import kotlinx.coroutines.flow.launchIn
 import kotlinx.coroutines.flow.map
 import kotlinx.coroutines.flow.onEach
 import kotlinx.coroutines.flow.stateIn
@@ -58,24 +61,31 @@
     mediaDeviceSessionInteractor: MediaDeviceSessionInteractor,
     private val streamSliderViewModelFactory: AudioStreamSliderViewModel.Factory,
     private val castVolumeSliderViewModelFactory: CastVolumeSliderViewModel.Factory,
+    audioModeInteractor: AudioModeInteractor,
     streamsInteractor: AudioSlidersInteractor,
 ) {
 
     private val mutableIsExpanded = MutableStateFlow<Boolean?>(null)
-    private val isPlaybackActive: Flow<Boolean?> =
-        mediaOutputInteractor.defaultActiveMediaSession
-            .filterData()
-            .flatMapLatest { session ->
-                if (session == null) {
-                    flowOf(false)
-                } else {
-                    mediaDeviceSessionInteractor.playbackState(session).map { it?.isActive == true }
-                }
+    private val isActive: Flow<Boolean?> =
+        combine(
+                audioModeInteractor.isOngoingCall,
+                mediaOutputInteractor.defaultActiveMediaSession.filterData().flatMapLatest { session
+                    ->
+                    if (session == null) {
+                        flowOf(false)
+                    } else {
+                        mediaDeviceSessionInteractor.playbackState(session).map {
+                            it?.isActive == true
+                        }
+                    }
+                },
+            ) { isOngoingCall, isPlaybackActive ->
+                isOngoingCall || isPlaybackActive
             }
-            .onEach { isPlaybackActive -> mutableIsExpanded.value = !isPlaybackActive }
             .stateIn(scope, SharingStarted.Eagerly, null)
+
     private val portraitExpandable: Flow<SlidersExpandableViewModel> =
-        isPlaybackActive
+        isActive
             .filterNotNull()
             .flatMapLatest { isActive ->
                 if (isActive) {
@@ -105,6 +115,10 @@
             }
             .stateIn(scope, SharingStarted.Eagerly, emptyList())
 
+    init {
+        isActive.filterNotNull().onEach { mutableIsExpanded.value = !it }.launchIn(scope)
+    }
+
     fun isExpandable(isPortrait: Boolean): Flow<SlidersExpandableViewModel> {
         return if (isPortrait) {
             portraitExpandable
diff --git a/packages/SystemUI/src/com/android/systemui/wallpapers/ImageWallpaper.java b/packages/SystemUI/src/com/android/systemui/wallpapers/ImageWallpaper.java
index 2e29bbd..b1c6455 100644
--- a/packages/SystemUI/src/com/android/systemui/wallpapers/ImageWallpaper.java
+++ b/packages/SystemUI/src/com/android/systemui/wallpapers/ImageWallpaper.java
@@ -21,6 +21,7 @@
 import static android.app.WallpaperManager.SetWallpaperFlags;
 
 import static com.android.systemui.Flags.fixImageWallpaperCrashSurfaceAlreadyReleased;
+import static com.android.window.flags.Flags.multiCrop;
 import static com.android.window.flags.Flags.offloadColorExtraction;
 
 import android.annotation.Nullable;
@@ -190,7 +191,10 @@
             }
             mWallpaperManager = getDisplayContext().getSystemService(WallpaperManager.class);
             mSurfaceHolder = surfaceHolder;
-            Rect dimensions = mWallpaperManager.peekBitmapDimensions(getSourceFlag(), true);
+            Rect dimensions = !multiCrop()
+                    ? mWallpaperManager.peekBitmapDimensions(getSourceFlag(), true)
+                    : mWallpaperManager.peekBitmapDimensionsAsUser(getSourceFlag(), true,
+                    mUserTracker.getUserId());
             int width = Math.max(MIN_SURFACE_WIDTH, dimensions.width());
             int height = Math.max(MIN_SURFACE_HEIGHT, dimensions.height());
             mSurfaceHolder.setFixedSize(width, height);
diff --git a/packages/SystemUI/src/com/android/systemui/wmshell/BubblesManager.java b/packages/SystemUI/src/com/android/systemui/wmshell/BubblesManager.java
index 45799b2..7385b82 100644
--- a/packages/SystemUI/src/com/android/systemui/wmshell/BubblesManager.java
+++ b/packages/SystemUI/src/com/android/systemui/wmshell/BubblesManager.java
@@ -115,7 +115,6 @@
     private final Executor mSysuiUiBgExecutor;
 
     private final Bubbles.SysuiProxy mSysuiProxy;
-    // TODO (b/145659174): allow for multiple callbacks to support the "shadow" new notif pipeline
     private final List<NotifCallback> mCallbacks = new ArrayList<>();
     private final StatusBarWindowCallback mStatusBarWindowCallback;
     private final Runnable mSensitiveStateChangedListener;
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
index 075d8ae..7aa415b 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
@@ -102,6 +102,7 @@
 import android.os.RemoteException;
 import android.os.UserHandle;
 import android.os.UserManager;
+import android.platform.test.flag.junit.FlagsParameterization;
 import android.service.dreams.IDreamManager;
 import android.service.trust.TrustAgentService;
 import android.telephony.ServiceState;
@@ -111,9 +112,9 @@
 import android.testing.TestableLooper;
 import android.text.TextUtils;
 
-import androidx.test.ext.junit.runners.AndroidJUnit4;
 import androidx.test.filters.SmallTest;
 
+import com.android.compose.animation.scene.ObservableTransitionState;
 import com.android.dx.mockito.inline.extended.ExtendedMockito;
 import com.android.internal.foldables.FoldGracePeriodProvider;
 import com.android.internal.jank.InteractionJankMonitor;
@@ -129,6 +130,7 @@
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.biometrics.AuthController;
 import com.android.systemui.biometrics.FingerprintInteractiveToAuthProvider;
+import com.android.systemui.bouncer.domain.interactor.AlternateBouncerInteractor;
 import com.android.systemui.broadcast.BroadcastDispatcher;
 import com.android.systemui.deviceentry.data.repository.FaceWakeUpTriggersConfig;
 import com.android.systemui.deviceentry.data.repository.FaceWakeUpTriggersConfigImpl;
@@ -138,9 +140,13 @@
 import com.android.systemui.deviceentry.shared.model.FaceDetectionStatus;
 import com.android.systemui.deviceentry.shared.model.FailedFaceAuthenticationStatus;
 import com.android.systemui.dump.DumpManager;
+import com.android.systemui.flags.SceneContainerFlagParameterizationKt;
 import com.android.systemui.kosmos.KosmosJavaAdapter;
 import com.android.systemui.log.SessionTracker;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
+import com.android.systemui.scene.domain.interactor.SceneInteractor;
+import com.android.systemui.scene.shared.flag.SceneContainerFlag;
+import com.android.systemui.scene.shared.model.Scenes;
 import com.android.systemui.settings.UserTracker;
 import com.android.systemui.shared.system.TaskStackChangeListener;
 import com.android.systemui.shared.system.TaskStackChangeListeners;
@@ -149,6 +155,7 @@
 import com.android.systemui.statusbar.policy.DevicePostureController;
 import com.android.systemui.telephony.TelephonyListenerManager;
 import com.android.systemui.user.domain.interactor.SelectedUserInteractor;
+import com.android.systemui.util.kotlin.JavaAdapter;
 import com.android.systemui.util.settings.GlobalSettings;
 
 import org.junit.After;
@@ -175,8 +182,11 @@
 import java.util.concurrent.atomic.AtomicBoolean;
 import java.util.concurrent.atomic.AtomicInteger;
 
+import platform.test.runner.parameterized.ParameterizedAndroidJunit4;
+import platform.test.runner.parameterized.Parameters;
+
 @SmallTest
-@RunWith(AndroidJUnit4.class)
+@RunWith(ParameterizedAndroidJunit4.class)
 @TestableLooper.RunWithLooper
 public class KeyguardUpdateMonitorTest extends SysuiTestCase {
     private static final String PKG_ALLOWING_FP_LISTEN_ON_OCCLUDING_ACTIVITY =
@@ -277,6 +287,12 @@
     private SelectedUserInteractor mSelectedUserInteractor;
     @Mock
     private DeviceEntryFaceAuthInteractor mFaceAuthInteractor;
+    @Mock
+    private AlternateBouncerInteractor mAlternateBouncerInteractor;
+    @Mock
+    private JavaAdapter mJavaAdapter;
+    @Mock
+    private SceneInteractor mSceneInteractor;
     @Captor
     private ArgumentCaptor<FaceAuthenticationListener> mFaceAuthenticationListener;
 
@@ -301,6 +317,16 @@
             mFingerprintAuthenticatorsRegisteredCallback;
     private final InstanceId mKeyguardInstanceId = InstanceId.fakeInstanceId(999);
 
+    @Parameters(name = "{0}")
+    public static List<FlagsParameterization> getParams() {
+        return SceneContainerFlagParameterizationKt.parameterizeSceneContainerFlag();
+    }
+
+    public KeyguardUpdateMonitorTest(FlagsParameterization flags) {
+        super();
+        mSetFlagsRule.setFlagsParameterization(flags);
+    }
+
     @Before
     public void setup() throws RemoteException {
         mKosmos = new KosmosJavaAdapter(this);
@@ -993,7 +1019,7 @@
         verifyFingerprintAuthenticateNeverCalled();
         // WHEN alternate bouncer is shown
         mKeyguardUpdateMonitor.setKeyguardShowing(true, true);
-        mKeyguardUpdateMonitor.setAlternateBouncerShowing(true);
+        mKeyguardUpdateMonitor.setAlternateBouncerVisibility(true);
 
         // THEN make sure FP listening begins
         verifyFingerprintAuthenticateCall();
@@ -1489,7 +1515,7 @@
     @Test
     public void testShouldNotListenForUdfps_whenInLockDown() {
         // GIVEN a "we should listen for udfps" state
-        setKeyguardBouncerVisibility(false /* isVisible */);
+        mKeyguardUpdateMonitor.setPrimaryBouncerVisibility(false /* isVisible */);
         mStatusBarStateListener.onStateChanged(StatusBarState.KEYGUARD);
         when(mStrongAuthTracker.hasUserAuthenticatedSinceBoot()).thenReturn(true);
 
@@ -2124,7 +2150,7 @@
         verifyFingerprintAuthenticateNeverCalled();
 
         mKeyguardUpdateMonitor.setKeyguardShowing(true, true);
-        mKeyguardUpdateMonitor.setAlternateBouncerShowing(true);
+        mKeyguardUpdateMonitor.setAlternateBouncerVisibility(true);
 
         verifyFingerprintAuthenticateCall();
     }
@@ -2323,12 +2349,7 @@
     }
 
     private void bouncerFullyVisible() {
-        setKeyguardBouncerVisibility(true);
-    }
-
-    private void setKeyguardBouncerVisibility(boolean isVisible) {
-        mKeyguardUpdateMonitor.sendPrimaryBouncerChanged(isVisible, isVisible);
-        mTestableLooper.processAllMessages();
+        mKeyguardUpdateMonitor.setPrimaryBouncerVisibility(true);
     }
 
     private void setBroadcastReceiverPendingResult(BroadcastReceiver receiver) {
@@ -2434,7 +2455,12 @@
                     mPackageManager, mFingerprintManager, mBiometricManager,
                     mFaceWakeUpTriggersConfig, mDevicePostureController,
                     Optional.of(mInteractiveToAuthProvider),
-                    mTaskStackChangeListeners, mSelectedUserInteractor, mActivityTaskManager);
+                    mTaskStackChangeListeners, mSelectedUserInteractor, mActivityTaskManager,
+                    () -> mAlternateBouncerInteractor,
+                    () -> mJavaAdapter,
+                    () -> mSceneInteractor);
+            setAlternateBouncerVisibility(false);
+            setPrimaryBouncerVisibility(false);
             setStrongAuthTracker(KeyguardUpdateMonitorTest.this.mStrongAuthTracker);
             start();
         }
@@ -2458,5 +2484,25 @@
         protected int getBiometricLockoutDelay() {
             return 0;
         }
+
+        private void setPrimaryBouncerVisibility(boolean isVisible) {
+            if (SceneContainerFlag.isEnabled()) {
+                ObservableTransitionState transitionState = new ObservableTransitionState.Idle(
+                        isVisible ? Scenes.Bouncer : Scenes.Lockscreen);
+                when(mSceneInteractor.getTransitionState()).thenReturn(
+                        MutableStateFlow(transitionState));
+                onTransitionStateChanged(transitionState);
+            } else {
+                sendPrimaryBouncerChanged(isVisible, isVisible);
+                mTestableLooper.processAllMessages();
+            }
+        }
+
+        private void setAlternateBouncerVisibility(boolean isVisible) {
+            if (SceneContainerFlag.isEnabled()) {
+                when(mAlternateBouncerInteractor.isVisibleState()).thenReturn(isVisible);
+            }
+            onAlternateBouncerVisibilityChange(isVisible);
+        }
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/IMagnificationConnectionTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/IMagnificationConnectionTest.java
index ff47fd1..c74d340 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/IMagnificationConnectionTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/IMagnificationConnectionTest.java
@@ -32,10 +32,6 @@
 import android.content.Context;
 import android.hardware.display.DisplayManager;
 import android.os.RemoteException;
-import android.platform.test.annotations.RequiresFlagsDisabled;
-import android.platform.test.annotations.RequiresFlagsEnabled;
-import android.platform.test.flag.junit.CheckFlagsRule;
-import android.platform.test.flag.junit.DeviceFlagsValueProvider;
 import android.provider.Settings;
 import android.testing.TestableLooper;
 import android.view.Display;
@@ -49,7 +45,6 @@
 import androidx.test.filters.SmallTest;
 
 import com.android.app.viewcapture.ViewCaptureAwareWindowManager;
-import com.android.systemui.Flags;
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.model.SysUiState;
 import com.android.systemui.recents.OverviewProxyService;
@@ -58,7 +53,6 @@
 import com.android.systemui.util.settings.SecureSettings;
 
 import org.junit.Before;
-import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.Mock;
@@ -73,9 +67,6 @@
 @TestableLooper.RunWithLooper(setAsMainLooper = true)
 public class IMagnificationConnectionTest extends SysuiTestCase {
 
-    @Rule
-    public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule();
-
     private static final int TEST_DISPLAY = Display.DEFAULT_DISPLAY;
     @Mock
     private AccessibilityManager mAccessibilityManager;
@@ -198,22 +189,7 @@
     }
 
     @Test
-    @RequiresFlagsDisabled(Flags.FLAG_DELAY_SHOW_MAGNIFICATION_BUTTON)
-    public void showMagnificationButton_flagOff_directlyShowButton() throws RemoteException {
-        // magnification settings panel should not be showing
-        assertFalse(mMagnification.isMagnificationSettingsPanelShowing(TEST_DISPLAY));
-
-        mIMagnificationConnection.showMagnificationButton(TEST_DISPLAY,
-                Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN);
-        processAllPendingMessages();
-
-        verify(mModeSwitchesController).showButton(TEST_DISPLAY,
-                Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN);
-    }
-
-    @Test
-    @RequiresFlagsEnabled(Flags.FLAG_DELAY_SHOW_MAGNIFICATION_BUTTON)
-    public void showMagnificationButton_flagOn_delayedShowButton() throws RemoteException {
+    public void showMagnificationButton_delayedShowButton() throws RemoteException {
         // magnification settings panel should not be showing
         assertFalse(mMagnification.isMagnificationSettingsPanelShowing(TEST_DISPLAY));
 
@@ -243,12 +219,9 @@
         // showMagnificationButton request to Magnification.
         processAllPendingMessages();
 
-        // If the flag is on, the isMagnificationSettingsShowing will be checked after timeout, so
+        // The isMagnificationSettingsShowing will be checked after timeout, so
         // process all message after a timeout here to verify the showButton will not be called.
-        int timeout = Flags.delayShowMagnificationButton()
-                ? DELAY_SHOW_MAGNIFICATION_TIMEOUT_MS + 100
-                : 0;
-        processAllPendingMessages(timeout);
+        processAllPendingMessages(DELAY_SHOW_MAGNIFICATION_TIMEOUT_MS + 100);
         verify(mModeSwitchesController, never()).showButton(TEST_DISPLAY,
                 Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN);
     }
@@ -262,7 +235,6 @@
     }
 
     @Test
-    @RequiresFlagsEnabled(Flags.FLAG_DELAY_SHOW_MAGNIFICATION_BUTTON)
     public void removeMagnificationButton_delayingShowButton_doNotShowButtonAfterTimeout()
             throws RemoteException {
         // magnification settings panel should not be showing
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationSettingsTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationSettingsTest.java
index 9507077..7c0c5c2 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationSettingsTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationSettingsTest.java
@@ -22,6 +22,9 @@
 import static android.provider.Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW;
 import static android.view.WindowInsets.Type.systemBars;
 
+import static com.android.internal.accessibility.common.MagnificationConstants.SCALE_MAX_VALUE;
+import static com.android.internal.accessibility.common.MagnificationConstants.SCALE_MIN_VALUE;
+
 import static com.google.common.truth.Truth.assertThat;
 
 import static junit.framework.Assert.assertEquals;
@@ -429,7 +432,7 @@
         mSettingView = mWindowMagnificationSettings.getSettingView();
         mZoomSeekbar = mSettingView.findViewById(R.id.magnifier_zoom_slider);
         assertThat(mZoomSeekbar.getProgress()).isEqualTo(10);
-        assertThat(mZoomSeekbar.getMax()).isEqualTo(70);
+        assertThat(mZoomSeekbar.getMax()).isEqualTo(getSeekBarMax());
     }
 
     @Test
@@ -473,29 +476,26 @@
 
     @Test
     public void seekbarProgress_maxMagnificationBefore_seekbarProgressIsMax() {
-        mWindowMagnificationSettings.setMagnificationScale(8f);
+        mWindowMagnificationSettings.setMagnificationScale(SCALE_MAX_VALUE);
         setupMagnificationCapabilityAndMode(
                 /* capability= */ ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW,
                 /* mode= */ ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW);
 
         mWindowMagnificationSettings.showSettingPanel();
 
-        // 8.0f is max magnification {@link MagnificationScaleProvider#MAX_SCALE}.
-        // Max zoom seek bar is 70.
-        assertThat(mZoomSeekbar.getProgress()).isEqualTo(70);
+        assertThat(mZoomSeekbar.getProgress()).isEqualTo(getSeekBarMax());
     }
 
     @Test
     public void seekbarProgress_aboveMaxMagnificationBefore_seekbarProgressIsMax() {
-        mWindowMagnificationSettings.setMagnificationScale(9f);
+        mWindowMagnificationSettings.setMagnificationScale(SCALE_MAX_VALUE + 1f);
         setupMagnificationCapabilityAndMode(
                 /* capability= */ ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW,
                 /* mode= */ ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW);
 
         mWindowMagnificationSettings.showSettingPanel();
 
-        // Max zoom seek bar is 70.
-        assertThat(mZoomSeekbar.getProgress()).isEqualTo(70);
+        assertThat(mZoomSeekbar.getProgress()).isEqualTo(getSeekBarMax());
     }
 
     @Test
@@ -589,4 +589,11 @@
                 anyInt(),
                 eq(UserHandle.USER_CURRENT))).thenReturn(mode);
     }
+
+    private int getSeekBarMax() {
+        // Calculates the maximum index (or positions) the seekbar can have.
+        // This is achieved by multiplying the range of possible scales with the magnitude of
+        // change per each movement on the seekbar.
+        return (int) ((SCALE_MAX_VALUE - SCALE_MIN_VALUE) * mZoomSeekbar.getChangeMagnitude());
+    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/AccessibilityFloatingMenuControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/AccessibilityFloatingMenuControllerTest.java
index 5ff3915..113a8c0 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/AccessibilityFloatingMenuControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/AccessibilityFloatingMenuControllerTest.java
@@ -45,6 +45,7 @@
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.accessibility.AccessibilityButtonModeObserver;
 import com.android.systemui.accessibility.AccessibilityButtonTargetsObserver;
+import com.android.systemui.navigationbar.NavigationModeController;
 import com.android.systemui.settings.FakeDisplayTracker;
 import com.android.systemui.util.settings.SecureSettings;
 
@@ -90,6 +91,8 @@
     private SecureSettings mSecureSettings;
     @Mock
     private Lazy<ViewCapture> mLazyViewCapture;
+    @Mock
+    private NavigationModeController mNavigationModeController;
 
     @Before
     public void setUp() throws Exception {
@@ -163,7 +166,8 @@
         enableAccessibilityFloatingMenuConfig();
         mController = setUpController();
         mController.mFloatingMenu = new MenuViewLayerController(mContextWrapper, mWindowManager,
-                mViewCaptureAwareWindowManager, mAccessibilityManager, mSecureSettings);
+                mViewCaptureAwareWindowManager, mAccessibilityManager, mSecureSettings,
+                mNavigationModeController);
         captureKeyguardUpdateMonitorCallback();
         mKeyguardCallback.onUserUnlocked();
 
@@ -190,7 +194,8 @@
         enableAccessibilityFloatingMenuConfig();
         mController = setUpController();
         mController.mFloatingMenu = new MenuViewLayerController(mContextWrapper, mWindowManager,
-                mViewCaptureAwareWindowManager, mAccessibilityManager, mSecureSettings);
+                mViewCaptureAwareWindowManager, mAccessibilityManager, mSecureSettings,
+                mNavigationModeController);
         captureKeyguardUpdateMonitorCallback();
 
         mKeyguardCallback.onUserSwitching(fakeUserId);
@@ -204,7 +209,8 @@
         enableAccessibilityFloatingMenuConfig();
         mController = setUpController();
         mController.mFloatingMenu = new MenuViewLayerController(mContextWrapper, mWindowManager,
-                mViewCaptureAwareWindowManager, mAccessibilityManager, mSecureSettings);
+                mViewCaptureAwareWindowManager, mAccessibilityManager, mSecureSettings,
+                mNavigationModeController);
         captureKeyguardUpdateMonitorCallback();
         mKeyguardCallback.onUserUnlocked();
         mKeyguardCallback.onKeyguardVisibilityChanged(true);
@@ -340,7 +346,7 @@
                 new AccessibilityFloatingMenuController(mContextWrapper, windowManager,
                         viewCaptureAwareWindowManager, displayManager, mAccessibilityManager,
                         mTargetsObserver, mModeObserver, mKeyguardUpdateMonitor, mSecureSettings,
-                        displayTracker);
+                        displayTracker, mNavigationModeController);
         controller.init();
 
         return controller;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/DragToInteractAnimationControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/DragToInteractAnimationControllerTest.java
index 19b2700..d7acaaf 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/DragToInteractAnimationControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/DragToInteractAnimationControllerTest.java
@@ -34,7 +34,7 @@
 import com.android.systemui.accessibility.utils.TestUtils;
 import com.android.systemui.util.settings.SecureSettings;
 import com.android.wm.shell.common.bubbles.DismissView;
-import com.android.wm.shell.common.magnetictarget.MagnetizedObject;
+import com.android.wm.shell.shared.magnetictarget.MagnetizedObject;
 
 import org.junit.Before;
 import org.junit.Rule;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuItemAccessibilityDelegateTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuItemAccessibilityDelegateTest.java
index c5509ac..157cccc 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuItemAccessibilityDelegateTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuItemAccessibilityDelegateTest.java
@@ -44,6 +44,7 @@
 import com.android.systemui.Flags;
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.accessibility.utils.TestUtils;
+import com.android.systemui.navigationbar.NavigationModeController;
 import com.android.systemui.res.R;
 import com.android.systemui.util.settings.SecureSettings;
 
@@ -94,7 +95,8 @@
         mMenuViewLayer = spy(new MenuViewLayer(
                 mContext, stubWindowManager, mAccessibilityManager,
                 stubMenuViewModel, stubMenuViewAppearance, mMenuView,
-                mock(IAccessibilityFloatingMenu.class), mSecureSettings));
+                mock(IAccessibilityFloatingMenu.class), mSecureSettings,
+                mock(NavigationModeController.class)));
         doNothing().when(mMenuViewLayer).gotoEditScreen();
 
         doReturn(mDraggableBounds).when(mMenuView).getMenuDraggableBounds();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayerControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayerControllerTest.java
index 07ce7b9..fcdeff9 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayerControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayerControllerTest.java
@@ -21,6 +21,7 @@
 
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.Mockito.doAnswer;
+import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
@@ -41,6 +42,7 @@
 import com.android.app.viewcapture.ViewCapture;
 import com.android.app.viewcapture.ViewCaptureAwareWindowManager;
 import com.android.systemui.SysuiTestCase;
+import com.android.systemui.navigationbar.NavigationModeController;
 import com.android.systemui.util.settings.SecureSettings;
 
 import kotlin.Lazy;
@@ -90,7 +92,8 @@
         when(mWindowMetrics.getBounds()).thenReturn(new Rect(0, 0, 1080, 2340));
         when(mWindowMetrics.getWindowInsets()).thenReturn(stubDisplayInsets());
         mMenuViewLayerController = new MenuViewLayerController(mContext, mWindowManager,
-                viewCaptureAwareWm, mAccessibilityManager, mSecureSettings);
+                viewCaptureAwareWm, mAccessibilityManager, mSecureSettings,
+                mock(NavigationModeController.class));
     }
 
     @Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayerTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayerTest.java
index 12140b5..c451c32 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayerTest.java
@@ -81,9 +81,10 @@
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.SysuiTestableContext;
 import com.android.systemui.accessibility.utils.TestUtils;
+import com.android.systemui.navigationbar.NavigationModeController;
 import com.android.systemui.res.R;
 import com.android.systemui.util.settings.SecureSettings;
-import com.android.wm.shell.common.magnetictarget.MagnetizedObject;
+import com.android.wm.shell.shared.magnetictarget.MagnetizedObject;
 
 import org.junit.After;
 import org.junit.Before;
@@ -169,7 +170,7 @@
 
         mMenuViewLayer = spy(new MenuViewLayer(mSpyContext, mStubWindowManager,
                 mStubAccessibilityManager, mMenuViewModel, menuViewAppearance, mMenuView,
-                mFloatingMenu, mSecureSettings));
+                mFloatingMenu, mSecureSettings, mock(NavigationModeController.class)));
         mMenuAnimationController = mMenuView.getMenuAnimationController();
 
         doNothing().when(mSpyContext).startActivity(any());
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/BluetoothDeviceMetadataInteractorKosmos.kt b/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/BluetoothDeviceMetadataInteractorKosmos.kt
new file mode 100644
index 0000000..969e26a
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/BluetoothDeviceMetadataInteractorKosmos.kt
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.bluetooth.qsdialog
+
+import com.android.systemui.bluetooth.bluetoothAdapter
+import com.android.systemui.concurrency.fakeExecutor
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.testDispatcher
+import org.mockito.kotlin.mock
+
+val Kosmos.deviceItemInteractor: DeviceItemInteractor by
+    Kosmos.Fixture { mock<DeviceItemInteractor>() }
+
+val Kosmos.bluetoothDeviceMetadataInteractor by
+    Kosmos.Fixture {
+        BluetoothDeviceMetadataInteractor(
+            deviceItemInteractor,
+            bluetoothAdapter,
+            bluetoothTileDialogLogger,
+            fakeExecutor,
+            testDispatcher,
+        )
+    }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/BluetoothDeviceMetadataInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/BluetoothDeviceMetadataInteractorTest.kt
new file mode 100644
index 0000000..f06b105
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/BluetoothDeviceMetadataInteractorTest.kt
@@ -0,0 +1,226 @@
+/*
+ * 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.bluetooth.qsdialog
+
+import android.bluetooth.BluetoothAdapter
+import android.bluetooth.BluetoothDevice
+import android.testing.TestableLooper
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.settingslib.bluetooth.CachedBluetoothDevice
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.bluetooth.bluetoothAdapter
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.kosmos.testDispatcher
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.testKosmos
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.MutableSharedFlow
+import kotlinx.coroutines.test.UnconfinedTestDispatcher
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentCaptor
+import org.mockito.Captor
+import org.mockito.Mock
+import org.mockito.junit.MockitoJUnit
+import org.mockito.junit.MockitoRule
+import org.mockito.kotlin.any
+import org.mockito.kotlin.eq
+import org.mockito.kotlin.never
+import org.mockito.kotlin.times
+import org.mockito.kotlin.verify
+import org.mockito.kotlin.whenever
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@RunWith(AndroidJUnit4::class)
+@SmallTest
+@TestableLooper.RunWithLooper(setAsMainLooper = true)
+class BluetoothDeviceMetadataInteractorTest : SysuiTestCase() {
+    @get:Rule val mockitoRule: MockitoRule = MockitoJUnit.rule()
+
+    private val kosmos = testKosmos().apply { testDispatcher = UnconfinedTestDispatcher() }
+
+    private val deviceItemUpdate: MutableSharedFlow<List<DeviceItem>> = MutableSharedFlow()
+    @Mock private lateinit var cachedDevice1: CachedBluetoothDevice
+    @Mock private lateinit var bluetoothDevice1: BluetoothDevice
+    @Mock private lateinit var cachedDevice2: CachedBluetoothDevice
+    @Mock private lateinit var bluetoothDevice2: BluetoothDevice
+    @Captor
+    private lateinit var argumentCaptor: ArgumentCaptor<BluetoothAdapter.OnMetadataChangedListener>
+    private lateinit var interactor: BluetoothDeviceMetadataInteractor
+
+    @Before
+    fun setUp() {
+        with(kosmos) {
+            whenever(deviceItemInteractor.deviceItemUpdate).thenReturn(deviceItemUpdate)
+
+            whenever(cachedDevice1.device).thenReturn(bluetoothDevice1)
+            whenever(cachedDevice1.name).thenReturn(DEVICE_NAME)
+            whenever(cachedDevice1.address).thenReturn(DEVICE_ADDRESS)
+            whenever(cachedDevice1.connectionSummary).thenReturn(CONNECTION_SUMMARY)
+            whenever(bluetoothDevice1.address).thenReturn(DEVICE_ADDRESS)
+
+            whenever(cachedDevice2.device).thenReturn(bluetoothDevice2)
+            whenever(cachedDevice2.name).thenReturn(DEVICE_NAME)
+            whenever(cachedDevice2.address).thenReturn(DEVICE_ADDRESS)
+            whenever(cachedDevice2.connectionSummary).thenReturn(CONNECTION_SUMMARY)
+            whenever(bluetoothDevice2.address).thenReturn(DEVICE_ADDRESS)
+
+            interactor = bluetoothDeviceMetadataInteractor
+        }
+    }
+
+    @Test
+    fun deviceItemUpdateEmpty_doNothing() {
+        with(kosmos) {
+            testScope.runTest {
+                val update by collectLastValue(interactor.metadataUpdate)
+                deviceItemUpdate.emit(emptyList())
+                runCurrent()
+
+                assertThat(update).isNull()
+                verify(bluetoothAdapter, never()).addOnMetadataChangedListener(any(), any(), any())
+                verify(bluetoothAdapter, never()).removeOnMetadataChangedListener(any(), any())
+            }
+        }
+    }
+
+    @Test
+    fun deviceItemUpdate_registerListener() {
+        with(kosmos) {
+            testScope.runTest {
+                val deviceItem = AvailableMediaDeviceItemFactory().create(context, cachedDevice1)
+                val update by collectLastValue(interactor.metadataUpdate)
+                deviceItemUpdate.emit(listOf(deviceItem))
+                runCurrent()
+
+                assertThat(update).isNull()
+                verify(bluetoothAdapter)
+                    .addOnMetadataChangedListener(eq(bluetoothDevice1), any(), any())
+                verify(bluetoothAdapter, never()).removeOnMetadataChangedListener(any(), any())
+            }
+        }
+    }
+
+    @Test
+    fun deviceItemUpdate_sameDeviceItems_registerListenerOnce() {
+        with(kosmos) {
+            testScope.runTest {
+                val deviceItem = AvailableMediaDeviceItemFactory().create(context, cachedDevice1)
+                val update by collectLastValue(interactor.metadataUpdate)
+                deviceItemUpdate.emit(listOf(deviceItem))
+                deviceItemUpdate.emit(listOf(deviceItem))
+                runCurrent()
+
+                assertThat(update).isNull()
+                verify(bluetoothAdapter)
+                    .addOnMetadataChangedListener(eq(bluetoothDevice1), any(), any())
+                verify(bluetoothAdapter, never()).removeOnMetadataChangedListener(any(), any())
+            }
+        }
+    }
+
+    @Test
+    fun deviceItemUpdate_differentDeviceItems_unregisterOldAndRegisterNew() {
+        with(kosmos) {
+            testScope.runTest {
+                val deviceItem1 = AvailableMediaDeviceItemFactory().create(context, cachedDevice1)
+                val deviceItem2 = AvailableMediaDeviceItemFactory().create(context, cachedDevice2)
+                val update by collectLastValue(interactor.metadataUpdate)
+                deviceItemUpdate.emit(listOf(deviceItem1))
+                deviceItemUpdate.emit(listOf(deviceItem1, deviceItem2))
+                runCurrent()
+
+                assertThat(update).isNull()
+                verify(bluetoothAdapter, times(2))
+                    .addOnMetadataChangedListener(eq(bluetoothDevice1), any(), any())
+                verify(bluetoothAdapter)
+                    .addOnMetadataChangedListener(eq(bluetoothDevice2), any(), any())
+                verify(bluetoothAdapter)
+                    .removeOnMetadataChangedListener(eq(bluetoothDevice1), any())
+            }
+        }
+    }
+
+    @Test
+    fun metadataUpdate_triggerCallback_emit() {
+        with(kosmos) {
+            testScope.runTest {
+                val deviceItem = AvailableMediaDeviceItemFactory().create(context, cachedDevice1)
+                val update by collectLastValue(interactor.metadataUpdate)
+                deviceItemUpdate.emit(listOf(deviceItem))
+                runCurrent()
+
+                assertThat(update).isNull()
+                verify(bluetoothAdapter)
+                    .addOnMetadataChangedListener(
+                        eq(bluetoothDevice1),
+                        any(),
+                        argumentCaptor.capture()
+                    )
+
+                val listener = argumentCaptor.value
+                listener.onMetadataChanged(
+                    bluetoothDevice1,
+                    BluetoothDevice.METADATA_UNTETHERED_LEFT_BATTERY,
+                    ByteArray(0)
+                )
+                assertThat(update).isEqualTo(Unit)
+            }
+        }
+    }
+
+    @Test
+    fun metadataUpdate_triggerCallbackNonBatteryKey_doNothing() {
+        with(kosmos) {
+            testScope.runTest {
+                val deviceItem = AvailableMediaDeviceItemFactory().create(context, cachedDevice1)
+                val update by collectLastValue(interactor.metadataUpdate)
+                deviceItemUpdate.emit(listOf(deviceItem))
+                runCurrent()
+
+                assertThat(update).isNull()
+                verify(bluetoothAdapter)
+                    .addOnMetadataChangedListener(
+                        eq(bluetoothDevice1),
+                        any(),
+                        argumentCaptor.capture()
+                    )
+
+                val listener = argumentCaptor.value
+                listener.onMetadataChanged(
+                    bluetoothDevice1,
+                    BluetoothDevice.METADATA_MODEL_NAME,
+                    ByteArray(0)
+                )
+
+                assertThat(update).isNull()
+            }
+        }
+    }
+
+    companion object {
+        private const val DEVICE_NAME = "DeviceName"
+        private const val CONNECTION_SUMMARY = "ConnectionSummary"
+        private const val DEVICE_ADDRESS = "04:52:C7:0B:D8:3C"
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogViewModelTest.kt
index 9abb85d..d7bea66 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/BluetoothTileDialogViewModelTest.kt
@@ -77,6 +77,8 @@
 
     @Mock private lateinit var audioSharingInteractor: AudioSharingInteractor
 
+    @Mock private lateinit var bluetoothDeviceMetadataInteractor: BluetoothDeviceMetadataInteractor
+
     @Mock private lateinit var deviceItemInteractor: DeviceItemInteractor
 
     @Mock private lateinit var deviceItemActionInteractor: DeviceItemActionInteractor
@@ -138,6 +140,7 @@
                     )
                 ),
                 audioSharingInteractor,
+                bluetoothDeviceMetadataInteractor,
                 mDialogTransitionAnimator,
                 activityStarter,
                 uiEventLogger,
@@ -150,6 +153,8 @@
         whenever(deviceItemInteractor.deviceItemUpdate).thenReturn(MutableSharedFlow())
         whenever(deviceItemInteractor.deviceItemUpdateRequest)
             .thenReturn(MutableStateFlow(Unit).asStateFlow())
+        whenever(deviceItemInteractor.showSeeAllUpdate).thenReturn(getMutableStateFlow(false))
+        whenever(bluetoothDeviceMetadataInteractor.metadataUpdate).thenReturn(MutableSharedFlow())
         whenever(mBluetoothTileDialogDelegateDelegateFactory.create(any(), anyInt(), any(), any()))
             .thenReturn(bluetoothTileDialogDelegate)
         whenever(bluetoothTileDialogDelegate.createDialog()).thenReturn(sysuiDialog)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/DeviceItemInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/DeviceItemInteractorTest.kt
index 7f7abaf..194590c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/DeviceItemInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/bluetooth/qsdialog/DeviceItemInteractorTest.kt
@@ -113,9 +113,11 @@
             )
 
             val latest by collectLastValue(interactor.deviceItemUpdate)
+            val latestShowSeeAll by collectLastValue(interactor.showSeeAllUpdate)
             interactor.updateDeviceItems(mContext, DeviceFetchTrigger.FIRST_LOAD)
 
             assertThat(latest).isEqualTo(emptyList<DeviceItem>())
+            assertThat(latestShowSeeAll).isFalse()
         }
     }
 
@@ -128,9 +130,11 @@
             )
 
             val latest by collectLastValue(interactor.deviceItemUpdate)
+            val latestShowSeeAll by collectLastValue(interactor.showSeeAllUpdate)
             interactor.updateDeviceItems(mContext, DeviceFetchTrigger.FIRST_LOAD)
 
             assertThat(latest).isEqualTo(emptyList<DeviceItem>())
+            assertThat(latestShowSeeAll).isFalse()
         }
     }
 
@@ -143,9 +147,11 @@
             )
 
             val latest by collectLastValue(interactor.deviceItemUpdate)
+            val latestShowSeeAll by collectLastValue(interactor.showSeeAllUpdate)
             interactor.updateDeviceItems(mContext, DeviceFetchTrigger.FIRST_LOAD)
 
             assertThat(latest).isEqualTo(listOf(deviceItem1))
+            assertThat(latestShowSeeAll).isFalse()
         }
     }
 
@@ -158,9 +164,11 @@
             )
 
             val latest by collectLastValue(interactor.deviceItemUpdate)
+            val latestShowSeeAll by collectLastValue(interactor.showSeeAllUpdate)
             interactor.updateDeviceItems(mContext, DeviceFetchTrigger.FIRST_LOAD)
 
             assertThat(latest).isEqualTo(listOf(deviceItem2, deviceItem2))
+            assertThat(latestShowSeeAll).isFalse()
         }
     }
 
@@ -184,9 +192,11 @@
             `when`(deviceItem2.type).thenReturn(DeviceItemType.SAVED_BLUETOOTH_DEVICE)
 
             val latest by collectLastValue(interactor.deviceItemUpdate)
+            val latestShowSeeAll by collectLastValue(interactor.showSeeAllUpdate)
             interactor.updateDeviceItems(mContext, DeviceFetchTrigger.FIRST_LOAD)
 
             assertThat(latest).isEqualTo(listOf(deviceItem2, deviceItem1))
+            assertThat(latestShowSeeAll).isFalse()
         }
     }
 
@@ -207,9 +217,30 @@
             `when`(deviceItem2.type).thenReturn(DeviceItemType.CONNECTED_BLUETOOTH_DEVICE)
 
             val latest by collectLastValue(interactor.deviceItemUpdate)
+            val latestShowSeeAll by collectLastValue(interactor.showSeeAllUpdate)
             interactor.updateDeviceItems(mContext, DeviceFetchTrigger.FIRST_LOAD)
 
             assertThat(latest).isEqualTo(listOf(deviceItem2, deviceItem1))
+            assertThat(latestShowSeeAll).isFalse()
+        }
+    }
+
+    @Test
+    fun testUpdateDeviceItems_showMaxDeviceItems_showSeeAll() {
+        testScope.runTest {
+            `when`(bluetoothTileDialogRepository.cachedDevices)
+                .thenReturn(listOf(cachedDevice2, cachedDevice2, cachedDevice2, cachedDevice2))
+            `when`(adapter.mostRecentlyConnectedDevices).thenReturn(null)
+            interactor.setDeviceItemFactoryListForTesting(
+                listOf(createFactory({ true }, deviceItem2))
+            )
+
+            val latest by collectLastValue(interactor.deviceItemUpdate)
+            val latestShowSeeAll by collectLastValue(interactor.showSeeAllUpdate)
+            interactor.updateDeviceItems(mContext, DeviceFetchTrigger.FIRST_LOAD)
+
+            assertThat(latest).isEqualTo(listOf(deviceItem2, deviceItem2, deviceItem2))
+            assertThat(latestShowSeeAll).isTrue()
         }
     }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/composable/BouncerContentTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/composable/BouncerContentTest.kt
index a4936e6..8e215f9 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/composable/BouncerContentTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/composable/BouncerContentTest.kt
@@ -33,9 +33,10 @@
 import com.android.systemui.authentication.shared.model.AuthenticationMethodModel
 import com.android.systemui.bouncer.ui.BouncerDialogFactory
 import com.android.systemui.bouncer.ui.helper.BouncerSceneLayout
-import com.android.systemui.bouncer.ui.viewmodel.bouncerViewModel
+import com.android.systemui.bouncer.ui.viewmodel.bouncerSceneContentViewModelFactory
 import com.android.systemui.flags.Flags
 import com.android.systemui.flags.fakeFeatureFlagsClassic
+import com.android.systemui.lifecycle.rememberViewModel
 import com.android.systemui.motion.createSysUiComposeMotionTestRule
 import com.android.systemui.scene.domain.startable.sceneContainerStartable
 import com.android.systemui.testKosmos
@@ -81,7 +82,8 @@
     private fun BouncerContentUnderTest() {
         PlatformTheme {
             BouncerContent(
-                viewModel = kosmos.bouncerViewModel,
+                viewModel =
+                    rememberViewModel { kosmos.bouncerSceneContentViewModelFactory.create() },
                 layout = BouncerSceneLayout.BESIDE_USER_SWITCHER,
                 modifier = Modifier.fillMaxSize().testTag("BouncerContent"),
                 dialogFactory = bouncerDialogFactory
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/composable/PatternBouncerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/composable/PatternBouncerTest.kt
index 2948c02..4b61a0d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/composable/PatternBouncerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/composable/PatternBouncerTest.kt
@@ -24,14 +24,14 @@
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.LargeTest
 import com.android.systemui.SysuiTestCase
-import com.android.systemui.bouncer.domain.interactor.bouncerInteractor
-import com.android.systemui.bouncer.ui.viewmodel.PatternBouncerViewModel
-import com.android.systemui.kosmos.testScope
+import com.android.systemui.bouncer.ui.viewmodel.patternBouncerViewModelFactory
+import com.android.systemui.lifecycle.activateIn
 import com.android.systemui.motion.createSysUiComposeMotionTestRule
 import com.android.systemui.testKosmos
 import kotlinx.coroutines.flow.MutableStateFlow
 import kotlinx.coroutines.flow.asStateFlow
 import kotlinx.coroutines.flow.takeWhile
+import org.junit.Before
 import org.junit.Rule
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -51,15 +51,15 @@
 
     @get:Rule val motionTestRule = createSysUiComposeMotionTestRule(kosmos)
 
-    private val bouncerInteractor by lazy { kosmos.bouncerInteractor }
-    private val viewModel by lazy {
-        PatternBouncerViewModel(
-            applicationContext = context,
-            viewModelScope = kosmos.testScope.backgroundScope,
-            interactor = bouncerInteractor,
+    private val viewModel =
+        kosmos.patternBouncerViewModelFactory.create(
             isInputEnabled = MutableStateFlow(true).asStateFlow(),
             onIntentionalUserInput = {},
         )
+
+    @Before
+    fun setUp() {
+        viewModel.activateIn(motionTestRule.toolkit.testScope)
     }
 
     @Composable
diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/management/ControlsListingControllerImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/controls/management/ControlsListingControllerImplTest.kt
index e4f0910..f1782e8 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/controls/management/ControlsListingControllerImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/controls/management/ControlsListingControllerImplTest.kt
@@ -31,11 +31,11 @@
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import com.android.settingslib.applications.ServiceListing
-import com.android.systemui.res.R
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.controls.ControlsServiceInfo
 import com.android.systemui.dump.DumpManager
 import com.android.systemui.flags.FeatureFlags
+import com.android.systemui.res.R
 import com.android.systemui.settings.UserTracker
 import com.android.systemui.util.ActivityTaskManagerProxy
 import com.android.systemui.util.concurrency.FakeExecutor
@@ -45,6 +45,8 @@
 import com.android.systemui.util.mockito.eq
 import com.android.systemui.util.time.FakeSystemClock
 import com.google.common.truth.Truth.assertThat
+import java.io.PrintWriter
+import java.util.concurrent.CountDownLatch
 import java.util.concurrent.Executor
 import org.junit.After
 import org.junit.Assert.assertEquals
@@ -56,39 +58,33 @@
 import org.mockito.ArgumentCaptor
 import org.mockito.ArgumentMatcher
 import org.mockito.Mock
-import org.mockito.Mockito.`when`
 import org.mockito.Mockito.inOrder
 import org.mockito.Mockito.mock
 import org.mockito.Mockito.never
 import org.mockito.Mockito.reset
 import org.mockito.Mockito.verify
+import org.mockito.Mockito.`when`
 import org.mockito.MockitoAnnotations
+import org.mockito.kotlin.mock
 
 @SmallTest
 @RunWith(AndroidJUnit4::class)
 class ControlsListingControllerImplTest : SysuiTestCase() {
 
     companion object {
-        private const val FLAGS = PackageManager.MATCH_DIRECT_BOOT_AWARE.toLong() or
+        private const val FLAGS =
+            PackageManager.MATCH_DIRECT_BOOT_AWARE.toLong() or
                 PackageManager.MATCH_DIRECT_BOOT_UNAWARE.toLong()
     }
 
-    @Mock
-    private lateinit var mockSL: ServiceListing
-    @Mock
-    private lateinit var mockCallback: ControlsListingController.ControlsListingCallback
-    @Mock
-    private lateinit var mockCallbackOther: ControlsListingController.ControlsListingCallback
-    @Mock(stubOnly = true)
-    private lateinit var userTracker: UserTracker
-    @Mock(stubOnly = true)
-    private lateinit var dumpManager: DumpManager
-    @Mock
-    private lateinit var packageManager: PackageManager
-    @Mock
-    private lateinit var featureFlags: FeatureFlags
-    @Mock
-    private lateinit var activityTaskManagerProxy: ActivityTaskManagerProxy
+    @Mock private lateinit var mockSL: ServiceListing
+    @Mock private lateinit var mockCallback: ControlsListingController.ControlsListingCallback
+    @Mock private lateinit var mockCallbackOther: ControlsListingController.ControlsListingCallback
+    @Mock(stubOnly = true) private lateinit var userTracker: UserTracker
+    @Mock(stubOnly = true) private lateinit var dumpManager: DumpManager
+    @Mock private lateinit var packageManager: PackageManager
+    @Mock private lateinit var featureFlags: FeatureFlags
+    @Mock private lateinit var activityTaskManagerProxy: ActivityTaskManagerProxy
 
     private var componentName = ComponentName("pkg", "class1")
     private var activityName = ComponentName("pkg", "activity")
@@ -98,7 +94,7 @@
     private lateinit var controller: ControlsListingControllerImpl
 
     private var serviceListingCallbackCaptor =
-            ArgumentCaptor.forClass(ServiceListing.Callback::class.java)
+        ArgumentCaptor.forClass(ServiceListing.Callback::class.java)
 
     private val user = mContext.userId
     private val otherUser = user + 1
@@ -112,23 +108,24 @@
         `when`(userTracker.userContext).thenReturn(context)
         // Return disabled by default
         `when`(packageManager.getComponentEnabledSetting(any()))
-                .thenReturn(PackageManager.COMPONENT_ENABLED_STATE_DISABLED)
+            .thenReturn(PackageManager.COMPONENT_ENABLED_STATE_DISABLED)
         `when`(activityTaskManagerProxy.supportsMultiWindow(any())).thenReturn(true)
         mContext.setMockPackageManager(packageManager)
 
-        mContext.orCreateTestableResources
-                .addOverride(
-                        R.array.config_controlsPreferredPackages,
-                        arrayOf(componentName.packageName)
-                )
+        mContext.orCreateTestableResources.addOverride(
+            R.array.config_controlsPreferredPackages,
+            arrayOf(componentName.packageName)
+        )
 
-        val wrapper = object : ContextWrapper(mContext) {
-            override fun createContextAsUser(user: UserHandle, flags: Int): Context {
-                return baseContext
+        val wrapper =
+            object : ContextWrapper(mContext) {
+                override fun createContextAsUser(user: UserHandle, flags: Int): Context {
+                    return baseContext
+                }
             }
-        }
 
-        controller = ControlsListingControllerImpl(
+        controller =
+            ControlsListingControllerImpl(
                 wrapper,
                 executor,
                 { mockSL },
@@ -136,7 +133,7 @@
                 activityTaskManagerProxy,
                 dumpManager,
                 featureFlags
-        )
+            )
         verify(mockSL).addCallback(capture(serviceListingCallbackCaptor))
     }
 
@@ -165,13 +162,13 @@
             callback?.onServicesReloaded(listOf(ServiceInfo(componentName)))
         }
         ControlsListingControllerImpl(
-                mContext,
-                exec,
-                { mockServiceListing },
-                userTracker,
-                activityTaskManagerProxy,
-                dumpManager,
-                featureFlags
+            mContext,
+            exec,
+            { mockServiceListing },
+            userTracker,
+            activityTaskManagerProxy,
+            dumpManager,
+            featureFlags
         )
     }
 
@@ -201,8 +198,7 @@
 
         @Suppress("unchecked_cast")
         val captor: ArgumentCaptor<List<ControlsServiceInfo>> =
-                ArgumentCaptor.forClass(List::class.java)
-                        as ArgumentCaptor<List<ControlsServiceInfo>>
+            ArgumentCaptor.forClass(List::class.java) as ArgumentCaptor<List<ControlsServiceInfo>>
 
         executor.runAllReady()
         reset(mockCallback)
@@ -242,8 +238,7 @@
 
         @Suppress("unchecked_cast")
         val captor: ArgumentCaptor<List<ControlsServiceInfo>> =
-                ArgumentCaptor.forClass(List::class.java)
-                        as ArgumentCaptor<List<ControlsServiceInfo>>
+            ArgumentCaptor.forClass(List::class.java) as ArgumentCaptor<List<ControlsServiceInfo>>
         executor.runAllReady()
         reset(mockCallback)
 
@@ -285,10 +280,7 @@
 
     @Test
     fun testNoActivity_nullPanel() {
-        val serviceInfo = ServiceInfo(
-                componentName,
-                activityName
-        )
+        val serviceInfo = ServiceInfo(componentName, activityName)
 
         val list = listOf(serviceInfo)
         serviceListingCallbackCaptor.value.onServicesReloaded(list)
@@ -300,10 +292,7 @@
 
     @Test
     fun testActivityWithoutPermission_nullPanel() {
-        val serviceInfo = ServiceInfo(
-                componentName,
-                activityName
-        )
+        val serviceInfo = ServiceInfo(componentName, activityName)
 
         setUpQueryResult(listOf(ActivityInfo(activityName)))
 
@@ -317,14 +306,11 @@
 
     @Test
     fun testActivityPermissionNotExported_nullPanel() {
-        val serviceInfo = ServiceInfo(
-                componentName,
-                activityName
-        )
+        val serviceInfo = ServiceInfo(componentName, activityName)
 
-        setUpQueryResult(listOf(
-                ActivityInfo(activityName, permission = Manifest.permission.BIND_CONTROLS)
-        ))
+        setUpQueryResult(
+            listOf(ActivityInfo(activityName, permission = Manifest.permission.BIND_CONTROLS))
+        )
 
         val list = listOf(serviceInfo)
         serviceListingCallbackCaptor.value.onServicesReloaded(list)
@@ -336,18 +322,17 @@
 
     @Test
     fun testActivityDisabled_nullPanel() {
-        val serviceInfo = ServiceInfo(
-                componentName,
-                activityName
-        )
+        val serviceInfo = ServiceInfo(componentName, activityName)
 
-        setUpQueryResult(listOf(
+        setUpQueryResult(
+            listOf(
                 ActivityInfo(
-                        activityName,
-                        exported = true,
-                        permission = Manifest.permission.BIND_CONTROLS
+                    activityName,
+                    exported = true,
+                    permission = Manifest.permission.BIND_CONTROLS
                 )
-        ))
+            )
+        )
 
         val list = listOf(serviceInfo)
         serviceListingCallbackCaptor.value.onServicesReloaded(list)
@@ -359,21 +344,20 @@
 
     @Test
     fun testActivityEnabled_correctPanel() {
-        val serviceInfo = ServiceInfo(
-                componentName,
-                activityName
-        )
+        val serviceInfo = ServiceInfo(componentName, activityName)
 
         `when`(packageManager.getComponentEnabledSetting(eq(activityName)))
-                .thenReturn(PackageManager.COMPONENT_ENABLED_STATE_ENABLED)
+            .thenReturn(PackageManager.COMPONENT_ENABLED_STATE_ENABLED)
 
-        setUpQueryResult(listOf(
+        setUpQueryResult(
+            listOf(
                 ActivityInfo(
-                        activityName,
-                        exported = true,
-                        permission = Manifest.permission.BIND_CONTROLS
+                    activityName,
+                    exported = true,
+                    permission = Manifest.permission.BIND_CONTROLS
                 )
-        ))
+            )
+        )
 
         val list = listOf(serviceInfo)
         serviceListingCallbackCaptor.value.onServicesReloaded(list)
@@ -385,22 +369,21 @@
 
     @Test
     fun testActivityDefaultEnabled_correctPanel() {
-        val serviceInfo = ServiceInfo(
-                componentName,
-                activityName
-        )
+        val serviceInfo = ServiceInfo(componentName, activityName)
 
         `when`(packageManager.getComponentEnabledSetting(eq(activityName)))
-                .thenReturn(PackageManager.COMPONENT_ENABLED_STATE_DEFAULT)
+            .thenReturn(PackageManager.COMPONENT_ENABLED_STATE_DEFAULT)
 
-        setUpQueryResult(listOf(
+        setUpQueryResult(
+            listOf(
                 ActivityInfo(
-                        activityName,
-                        enabled = true,
-                        exported = true,
-                        permission = Manifest.permission.BIND_CONTROLS
+                    activityName,
+                    enabled = true,
+                    exported = true,
+                    permission = Manifest.permission.BIND_CONTROLS
                 )
-        ))
+            )
+        )
 
         val list = listOf(serviceInfo)
         serviceListingCallbackCaptor.value.onServicesReloaded(list)
@@ -412,22 +395,21 @@
 
     @Test
     fun testActivityDefaultDisabled_nullPanel() {
-        val serviceInfo = ServiceInfo(
-                componentName,
-                activityName
-        )
+        val serviceInfo = ServiceInfo(componentName, activityName)
 
         `when`(packageManager.getComponentEnabledSetting(eq(activityName)))
-                .thenReturn(PackageManager.COMPONENT_ENABLED_STATE_DEFAULT)
+            .thenReturn(PackageManager.COMPONENT_ENABLED_STATE_DEFAULT)
 
-        setUpQueryResult(listOf(
+        setUpQueryResult(
+            listOf(
                 ActivityInfo(
-                        activityName,
-                        enabled = false,
-                        exported = true,
-                        permission = Manifest.permission.BIND_CONTROLS
+                    activityName,
+                    enabled = false,
+                    exported = true,
+                    permission = Manifest.permission.BIND_CONTROLS
                 )
-        ))
+            )
+        )
 
         val list = listOf(serviceInfo)
         serviceListingCallbackCaptor.value.onServicesReloaded(list)
@@ -439,22 +421,21 @@
 
     @Test
     fun testActivityDifferentPackage_nullPanel() {
-        val serviceInfo = ServiceInfo(
-                componentName,
-                ComponentName("other_package", "cls")
-        )
+        val serviceInfo = ServiceInfo(componentName, ComponentName("other_package", "cls"))
 
         `when`(packageManager.getComponentEnabledSetting(eq(activityName)))
-                .thenReturn(PackageManager.COMPONENT_ENABLED_STATE_DEFAULT)
+            .thenReturn(PackageManager.COMPONENT_ENABLED_STATE_DEFAULT)
 
-        setUpQueryResult(listOf(
+        setUpQueryResult(
+            listOf(
                 ActivityInfo(
-                        activityName,
-                        enabled = true,
-                        exported = true,
-                        permission = Manifest.permission.BIND_CONTROLS
+                    activityName,
+                    enabled = true,
+                    exported = true,
+                    permission = Manifest.permission.BIND_CONTROLS
                 )
-        ))
+            )
+        )
 
         val list = listOf(serviceInfo)
         serviceListingCallbackCaptor.value.onServicesReloaded(list)
@@ -466,24 +447,25 @@
 
     @Test
     fun testPackageNotPreferred_correctPanel() {
-        mContext.orCreateTestableResources
-                .addOverride(R.array.config_controlsPreferredPackages, arrayOf<String>())
-
-        val serviceInfo = ServiceInfo(
-                componentName,
-                activityName
+        mContext.orCreateTestableResources.addOverride(
+            R.array.config_controlsPreferredPackages,
+            arrayOf<String>()
         )
 
-        `when`(packageManager.getComponentEnabledSetting(eq(activityName)))
-                .thenReturn(PackageManager.COMPONENT_ENABLED_STATE_ENABLED)
+        val serviceInfo = ServiceInfo(componentName, activityName)
 
-        setUpQueryResult(listOf(
+        `when`(packageManager.getComponentEnabledSetting(eq(activityName)))
+            .thenReturn(PackageManager.COMPONENT_ENABLED_STATE_ENABLED)
+
+        setUpQueryResult(
+            listOf(
                 ActivityInfo(
-                        activityName,
-                        exported = true,
-                        permission = Manifest.permission.BIND_CONTROLS
+                    activityName,
+                    exported = true,
+                    permission = Manifest.permission.BIND_CONTROLS
                 )
-        ))
+            )
+        )
 
         val list = listOf(serviceInfo)
         serviceListingCallbackCaptor.value.onServicesReloaded(list)
@@ -512,16 +494,19 @@
         `when`(userTracker.userHandle).thenReturn(UserHandle.of(user))
 
         controller.forceReload()
-        verify(packageManager).queryIntentServicesAsUser(
+        verify(packageManager)
+            .queryIntentServicesAsUser(
                 argThat(IntentMatcherAction(ControlsProviderService.SERVICE_CONTROLS)),
-                argThat(FlagsMatcher(
+                argThat(
+                    FlagsMatcher(
                         PackageManager.GET_META_DATA.toLong() or
-                                PackageManager.GET_SERVICES.toLong() or
-                                PackageManager.MATCH_DIRECT_BOOT_AWARE.toLong() or
-                                PackageManager.MATCH_DIRECT_BOOT_UNAWARE.toLong()
-                )),
+                            PackageManager.GET_SERVICES.toLong() or
+                            PackageManager.MATCH_DIRECT_BOOT_AWARE.toLong() or
+                            PackageManager.MATCH_DIRECT_BOOT_UNAWARE.toLong()
+                    )
+                ),
                 eq(UserHandle.of(user))
-        )
+            )
     }
 
     @Test
@@ -529,16 +514,21 @@
         val resolveInfo = ResolveInfo()
         resolveInfo.serviceInfo = ServiceInfo(componentName)
 
-        `when`(packageManager.queryIntentServicesAsUser(
-                argThat(IntentMatcherAction(ControlsProviderService.SERVICE_CONTROLS)),
-                argThat(FlagsMatcher(
-                        PackageManager.GET_META_DATA.toLong() or
+        `when`(
+                packageManager.queryIntentServicesAsUser(
+                    argThat(IntentMatcherAction(ControlsProviderService.SERVICE_CONTROLS)),
+                    argThat(
+                        FlagsMatcher(
+                            PackageManager.GET_META_DATA.toLong() or
                                 PackageManager.GET_SERVICES.toLong() or
                                 PackageManager.MATCH_DIRECT_BOOT_AWARE.toLong() or
                                 PackageManager.MATCH_DIRECT_BOOT_UNAWARE.toLong()
-                )),
-                any<UserHandle>()
-        )).thenReturn(listOf(resolveInfo))
+                        )
+                    ),
+                    any<UserHandle>()
+                )
+            )
+            .thenReturn(listOf(resolveInfo))
 
         controller.forceReload()
 
@@ -554,17 +544,19 @@
 
         @Suppress("unchecked_cast")
         val captor: ArgumentCaptor<List<ControlsServiceInfo>> =
-                ArgumentCaptor.forClass(List::class.java)
-                        as ArgumentCaptor<List<ControlsServiceInfo>>
+            ArgumentCaptor.forClass(List::class.java) as ArgumentCaptor<List<ControlsServiceInfo>>
 
         val resolveInfo = ResolveInfo()
         resolveInfo.serviceInfo = ServiceInfo(componentName)
 
-        `when`(packageManager.queryIntentServicesAsUser(
-                any(),
-                any<PackageManager.ResolveInfoFlags>(),
-                any<UserHandle>()
-        )).thenReturn(listOf(resolveInfo))
+        `when`(
+                packageManager.queryIntentServicesAsUser(
+                    any(),
+                    any<PackageManager.ResolveInfoFlags>(),
+                    any<UserHandle>()
+                )
+            )
+            .thenReturn(listOf(resolveInfo))
 
         reset(mockCallback)
         controller.forceReload()
@@ -581,22 +573,21 @@
     fun testNoPanelIfMultiWindowNotSupported() {
         `when`(activityTaskManagerProxy.supportsMultiWindow(any())).thenReturn(false)
 
-        val serviceInfo = ServiceInfo(
-            componentName,
-            activityName
-        )
+        val serviceInfo = ServiceInfo(componentName, activityName)
 
         `when`(packageManager.getComponentEnabledSetting(eq(activityName)))
             .thenReturn(PackageManager.COMPONENT_ENABLED_STATE_DEFAULT)
 
-        setUpQueryResult(listOf(
-            ActivityInfo(
-                activityName,
-                enabled = true,
-                exported = true,
-                permission = Manifest.permission.BIND_CONTROLS
+        setUpQueryResult(
+            listOf(
+                ActivityInfo(
+                    activityName,
+                    enabled = true,
+                    exported = true,
+                    permission = Manifest.permission.BIND_CONTROLS
+                )
             )
-        ))
+        )
 
         val list = listOf(serviceInfo)
         serviceListingCallbackCaptor.value.onServicesReloaded(list)
@@ -606,20 +597,77 @@
         assertNull(controller.getCurrentServices()[0].panelActivity)
     }
 
+    @Test
+    fun dumpAndAddRemoveCallback_willNotThrowConcurrentModificationException() {
+        val repeat = 100
+        controller.addCallback(mockCallback) // 1 extra callback increases the duration of iteration
+
+        // the goal of these two barriers is to make the modify and iterate run concurrently
+        val startSignal = CountDownLatch(2)
+        val doneSignal = CountDownLatch(2)
+        val modifyRunnable = Runnable {
+            for (i in 1..repeat) {
+                controller.addCallback(mockCallbackOther)
+                executor.runAllReady()
+                controller.removeCallback(mockCallbackOther)
+                executor.runAllReady()
+            }
+        }
+        val printWriter = mock<PrintWriter>()
+        val arr = arrayOf<String>()
+        val iterateRunnable = Runnable {
+            for (i in 1..repeat) {
+                controller.dump(printWriter, arr)
+            }
+        }
+
+        val workerThread = Thread(Worker(startSignal, doneSignal, modifyRunnable))
+        workerThread.start()
+        val workerThreadOther = Thread(Worker(startSignal, doneSignal, iterateRunnable))
+        workerThreadOther.start()
+        doneSignal.await()
+        workerThread.interrupt()
+        workerThreadOther.interrupt()
+    }
+
+    class Worker : Runnable {
+        private val startSignal: CountDownLatch
+        private val doneSignal: CountDownLatch
+        private val runnable: Runnable
+
+        constructor(start: CountDownLatch, done: CountDownLatch, run: Runnable) {
+            startSignal = start
+            doneSignal = done
+            runnable = run
+        }
+
+        override fun run() {
+            try {
+                startSignal.countDown()
+                startSignal.await()
+                runnable.run()
+                doneSignal.countDown()
+            } catch (ex: InterruptedException) {
+                return
+            }
+        }
+    }
+
     private fun ServiceInfo(
-            componentName: ComponentName,
-            panelActivityComponentName: ComponentName? = null
+        componentName: ComponentName,
+        panelActivityComponentName: ComponentName? = null
     ): ServiceInfo {
         return ServiceInfo().apply {
             packageName = componentName.packageName
             name = componentName.className
             panelActivityComponentName?.let {
-                metaData = Bundle().apply {
-                    putString(
+                metaData =
+                    Bundle().apply {
+                        putString(
                             ControlsProviderService.META_DATA_PANEL_ACTIVITY,
                             it.flattenToShortString()
-                    )
-                }
+                        )
+                    }
             }
         }
     }
@@ -642,34 +690,29 @@
     private fun setUpQueryResult(infos: List<ActivityInfo>) {
         `when`(
                 packageManager.queryIntentActivitiesAsUser(
-                        argThat(IntentMatcherComponent(activityName)),
-                        argThat(FlagsMatcher(FLAGS)),
-                        eq(UserHandle.of(user))
+                    argThat(IntentMatcherComponent(activityName)),
+                    argThat(FlagsMatcher(FLAGS)),
+                    eq(UserHandle.of(user))
                 )
-        ).thenReturn(infos.map {
-            ResolveInfo().apply { activityInfo = it }
-        })
+            )
+            .thenReturn(infos.map { ResolveInfo().apply { activityInfo = it } })
     }
 
-    private class IntentMatcherComponent(
-            private val componentName: ComponentName
-    ) : ArgumentMatcher<Intent> {
+    private class IntentMatcherComponent(private val componentName: ComponentName) :
+        ArgumentMatcher<Intent> {
         override fun matches(argument: Intent?): Boolean {
             return argument?.component == componentName
         }
     }
 
-    private class IntentMatcherAction(
-            private val action: String
-    ) : ArgumentMatcher<Intent> {
+    private class IntentMatcherAction(private val action: String) : ArgumentMatcher<Intent> {
         override fun matches(argument: Intent?): Boolean {
             return argument?.action == action
         }
     }
 
-    private class FlagsMatcher(
-            private val flags: Long
-    ) : ArgumentMatcher<PackageManager.ResolveInfoFlags> {
+    private class FlagsMatcher(private val flags: Long) :
+        ArgumentMatcher<PackageManager.ResolveInfoFlags> {
         override fun matches(argument: PackageManager.ResolveInfoFlags?): Boolean {
             return flags == argument?.value
         }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/display/domain/interactor/ConnectedDisplayInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/display/domain/interactor/ConnectedDisplayInteractorTest.kt
index fd9964f..a2b50fd 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/display/domain/interactor/ConnectedDisplayInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/display/domain/interactor/ConnectedDisplayInteractorTest.kt
@@ -17,8 +17,6 @@
 package com.android.systemui.display.domain.interactor
 
 import android.companion.virtual.VirtualDeviceManager
-import android.companion.virtual.flags.Flags.FLAG_INTERACTIVE_SCREEN_MIRROR
-import android.platform.test.annotations.EnableFlags
 import android.testing.TestableLooper
 import android.view.Display
 import android.view.Display.TYPE_EXTERNAL
@@ -160,7 +158,6 @@
         }
 
     @Test
-    @EnableFlags(FLAG_INTERACTIVE_SCREEN_MIRROR)
     fun displayState_virtualDeviceOwnedMirrorVirtualDisplay_connected() =
         testScope.runTest {
             whenever(virtualDeviceManager.isVirtualDeviceOwnedMirrorDisplay(anyInt()))
@@ -183,7 +180,6 @@
         }
 
     @Test
-    @EnableFlags(FLAG_INTERACTIVE_SCREEN_MIRROR)
     fun virtualDeviceOwnedMirrorVirtualDisplay_emitsConnectedDisplayAddition() =
         testScope.runTest {
             whenever(virtualDeviceManager.isVirtualDeviceOwnedMirrorDisplay(anyInt()))
diff --git a/packages/SystemUI/tests/src/com/android/systemui/inputdevice/data/repository/TutorialSchedulerRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/inputdevice/data/repository/TutorialSchedulerRepositoryTest.kt
new file mode 100644
index 0000000..1d96c4d
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/inputdevice/data/repository/TutorialSchedulerRepositoryTest.kt
@@ -0,0 +1,90 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.inputdevice.data.repository
+
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.inputdevice.tutorial.data.repository.DeviceType.KEYBOARD
+import com.android.systemui.inputdevice.tutorial.data.repository.DeviceType.TOUCHPAD
+import com.android.systemui.inputdevice.tutorial.data.repository.TutorialSchedulerRepository
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.testScope
+import com.google.common.truth.Truth.assertThat
+import java.time.Instant
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.test.runTest
+import org.junit.After
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class TutorialSchedulerRepositoryTest : SysuiTestCase() {
+
+    private lateinit var underTest: TutorialSchedulerRepository
+    private val kosmos = Kosmos()
+    private val testScope = kosmos.testScope
+
+    @Before
+    fun setup() {
+        underTest =
+            TutorialSchedulerRepository(
+                context,
+                testScope.backgroundScope,
+                "TutorialSchedulerRepositoryTest"
+            )
+    }
+
+    @After
+    fun clear() {
+        testScope.launch { underTest.clearDataStore() }
+    }
+
+    @Test
+    fun initialState() =
+        testScope.runTest {
+            assertThat(underTest.wasEverConnected(KEYBOARD)).isFalse()
+            assertThat(underTest.wasEverConnected(TOUCHPAD)).isFalse()
+            assertThat(underTest.isLaunched(KEYBOARD)).isFalse()
+            assertThat(underTest.isLaunched(TOUCHPAD)).isFalse()
+        }
+
+    @Test
+    fun connectKeyboard() =
+        testScope.runTest {
+            val now = Instant.now()
+            underTest.updateFirstConnectionTime(KEYBOARD, now)
+
+            assertThat(underTest.wasEverConnected(KEYBOARD)).isTrue()
+            assertThat(underTest.firstConnectionTime(KEYBOARD)!!.epochSecond)
+                .isEqualTo(now.epochSecond)
+            assertThat(underTest.wasEverConnected(TOUCHPAD)).isFalse()
+        }
+
+    @Test
+    fun launchKeyboard() =
+        testScope.runTest {
+            val now = Instant.now()
+            underTest.updateLaunchTime(KEYBOARD, now)
+
+            assertThat(underTest.isLaunched(KEYBOARD)).isTrue()
+            assertThat(underTest.launchTime(KEYBOARD)!!.epochSecond).isEqualTo(now.epochSecond)
+            assertThat(underTest.isLaunched(TOUCHPAD)).isFalse()
+        }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/inputdevice/tutorial/ui/viewmodel/KeyboardTouchpadTutorialViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/inputdevice/tutorial/ui/viewmodel/KeyboardTouchpadTutorialViewModelTest.kt
new file mode 100644
index 0000000..0c716137
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/inputdevice/tutorial/ui/viewmodel/KeyboardTouchpadTutorialViewModelTest.kt
@@ -0,0 +1,316 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.inputdevice.tutorial.ui.viewmodel
+
+import androidx.lifecycle.Lifecycle.Event
+import androidx.lifecycle.LifecycleOwner
+import androidx.lifecycle.LifecycleRegistry
+import androidx.lifecycle.SavedStateHandle
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.coroutines.collectValues
+import com.android.systemui.inputdevice.tutorial.domain.interactor.KeyboardTouchpadConnectionInteractor
+import com.android.systemui.inputdevice.tutorial.ui.view.KeyboardTouchpadTutorialActivity.Companion.INTENT_TUTORIAL_TYPE_KEY
+import com.android.systemui.inputdevice.tutorial.ui.view.KeyboardTouchpadTutorialActivity.Companion.INTENT_TUTORIAL_TYPE_KEYBOARD
+import com.android.systemui.inputdevice.tutorial.ui.view.KeyboardTouchpadTutorialActivity.Companion.INTENT_TUTORIAL_TYPE_TOUCHPAD
+import com.android.systemui.inputdevice.tutorial.ui.viewmodel.Screen.ACTION_KEY
+import com.android.systemui.inputdevice.tutorial.ui.viewmodel.Screen.BACK_GESTURE
+import com.android.systemui.inputdevice.tutorial.ui.viewmodel.Screen.HOME_GESTURE
+import com.android.systemui.keyboard.data.repository.keyboardRepository
+import com.android.systemui.kosmos.testDispatcher
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.model.sysUiState
+import com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_TOUCHPAD_GESTURES_DISABLED
+import com.android.systemui.testKosmos
+import com.android.systemui.touchpad.data.repository.TouchpadRepository
+import com.android.systemui.touchpad.tutorial.touchpadGesturesInteractor
+import com.android.systemui.util.coroutines.MainDispatcherRule
+import com.google.common.truth.Truth.assertThat
+import java.util.Optional
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mockito.mock
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class KeyboardTouchpadTutorialViewModelTest : SysuiTestCase() {
+
+    private val kosmos = testKosmos()
+    private val testScope = kosmos.testScope
+    private val sysUiState = kosmos.sysUiState
+    private val touchpadRepo = PrettyFakeTouchpadRepository()
+    private val keyboardRepo = kosmos.keyboardRepository
+    private var startingPeripheral = INTENT_TUTORIAL_TYPE_TOUCHPAD
+    private val viewModel by lazy { createViewModel(startingPeripheral) }
+
+    // createUnsafe so its methods don't have to be called on Main thread
+    private val lifecycle = LifecycleRegistry.createUnsafe(mock(LifecycleOwner::class.java))
+
+    @get:Rule val mainDispatcherRule = MainDispatcherRule(kosmos.testDispatcher)
+
+    private fun createViewModel(
+        startingPeripheral: String = INTENT_TUTORIAL_TYPE_TOUCHPAD,
+        hasTouchpadTutorialScreens: Boolean = true,
+    ): KeyboardTouchpadTutorialViewModel {
+        val viewModel =
+            KeyboardTouchpadTutorialViewModel(
+                Optional.of(kosmos.touchpadGesturesInteractor),
+                KeyboardTouchpadConnectionInteractor(keyboardRepo, touchpadRepo),
+                hasTouchpadTutorialScreens,
+                SavedStateHandle(mapOf(INTENT_TUTORIAL_TYPE_KEY to startingPeripheral))
+            )
+        lifecycle.addObserver(viewModel)
+        return viewModel
+    }
+
+    @Test
+    fun screensOrder_whenTouchpadAndKeyboardConnected() =
+        testScope.runTest {
+            val screens by collectValues(viewModel.screen)
+            val closeActivity by collectLastValue(viewModel.closeActivity)
+            peripheralsState(keyboardConnected = true, touchpadConnected = true)
+
+            goToNextScreen()
+            goToNextScreen()
+            // reached the last screen
+
+            assertThat(screens).containsExactly(BACK_GESTURE, HOME_GESTURE, ACTION_KEY).inOrder()
+            assertThat(closeActivity).isFalse()
+        }
+
+    @Test
+    fun screensOrder_whenKeyboardDisconnectsDuringTutorial() =
+        testScope.runTest {
+            val screens by collectValues(viewModel.screen)
+            val closeActivity by collectLastValue(viewModel.closeActivity)
+            peripheralsState(keyboardConnected = true, touchpadConnected = true)
+
+            // back gesture screen
+            goToNextScreen()
+            // home gesture screen
+            peripheralsState(keyboardConnected = false, touchpadConnected = true)
+            goToNextScreen()
+            // no action key screen because keyboard disconnected
+
+            assertThat(screens).containsExactly(BACK_GESTURE, HOME_GESTURE).inOrder()
+            assertThat(closeActivity).isTrue()
+        }
+
+    @Test
+    fun screensOrderUntilFinish_whenTouchpadAndKeyboardConnected() =
+        testScope.runTest {
+            val screens by collectValues(viewModel.screen)
+            val closeActivity by collectLastValue(viewModel.closeActivity)
+
+            peripheralsState(keyboardConnected = true, touchpadConnected = true)
+
+            goToNextScreen()
+            goToNextScreen()
+            // we're at the last screen so "next screen" should be actually closing activity
+            goToNextScreen()
+
+            assertThat(screens).containsExactly(BACK_GESTURE, HOME_GESTURE, ACTION_KEY).inOrder()
+            assertThat(closeActivity).isTrue()
+        }
+
+    @Test
+    fun screensOrder_whenGoingBackToPreviousScreens() =
+        testScope.runTest {
+            val screens by collectValues(viewModel.screen)
+            val closeActivity by collectLastValue(viewModel.closeActivity)
+            peripheralsState(keyboardConnected = true, touchpadConnected = true)
+
+            // back gesture
+            goToNextScreen()
+            // home gesture
+            goToNextScreen()
+            // action key
+
+            goBack()
+            // home gesture
+            goBack()
+            // back gesture
+            goBack()
+            // finish activity
+
+            assertThat(screens)
+                .containsExactly(BACK_GESTURE, HOME_GESTURE, ACTION_KEY, HOME_GESTURE, BACK_GESTURE)
+                .inOrder()
+            assertThat(closeActivity).isTrue()
+        }
+
+    @Test
+    fun screensOrder_whenGoingBackAndOnlyKeyboardConnected() =
+        testScope.runTest {
+            startingPeripheral = INTENT_TUTORIAL_TYPE_KEYBOARD
+            val screens by collectValues(viewModel.screen)
+            val closeActivity by collectLastValue(viewModel.closeActivity)
+            peripheralsState(keyboardConnected = true, touchpadConnected = false)
+
+            // action key screen
+            goBack()
+            // activity finished
+
+            assertThat(screens).containsExactly(ACTION_KEY).inOrder()
+            assertThat(closeActivity).isTrue()
+        }
+
+    @Test
+    fun screensOrder_whenTouchpadConnected() =
+        testScope.runTest {
+            startingPeripheral = INTENT_TUTORIAL_TYPE_TOUCHPAD
+            val screens by collectValues(viewModel.screen)
+            val closeActivity by collectLastValue(viewModel.closeActivity)
+
+            peripheralsState(keyboardConnected = false, touchpadConnected = true)
+
+            goToNextScreen()
+            goToNextScreen()
+            goToNextScreen()
+
+            assertThat(screens).containsExactly(BACK_GESTURE, HOME_GESTURE).inOrder()
+            assertThat(closeActivity).isTrue()
+        }
+
+    @Test
+    fun screensOrder_whenKeyboardConnected() =
+        testScope.runTest {
+            startingPeripheral = INTENT_TUTORIAL_TYPE_KEYBOARD
+            val screens by collectValues(viewModel.screen)
+            val closeActivity by collectLastValue(viewModel.closeActivity)
+
+            peripheralsState(keyboardConnected = true)
+
+            goToNextScreen()
+            goToNextScreen()
+
+            assertThat(screens).containsExactly(ACTION_KEY).inOrder()
+            assertThat(closeActivity).isTrue()
+        }
+
+    @Test
+    fun touchpadGesturesDisabled_onlyDuringTouchpadTutorial() =
+        testScope.runTest {
+            startingPeripheral = INTENT_TUTORIAL_TYPE_TOUCHPAD
+            collectValues(viewModel.screen) // just to initialize viewModel
+            peripheralsState(keyboardConnected = true, touchpadConnected = true)
+
+            assertGesturesDisabled()
+            goToNextScreen()
+            goToNextScreen()
+            // end of touchpad tutorial, keyboard tutorial starts
+            assertGesturesNotDisabled()
+        }
+
+    @Test
+    fun activityFinishes_ifTouchpadModuleIsNotPresent() =
+        testScope.runTest {
+            val viewModel =
+                createViewModel(
+                    startingPeripheral = INTENT_TUTORIAL_TYPE_TOUCHPAD,
+                    hasTouchpadTutorialScreens = false
+                )
+            val screens by collectValues(viewModel.screen)
+            val closeActivity by collectLastValue(viewModel.closeActivity)
+            peripheralsState(touchpadConnected = true)
+
+            assertThat(screens).isEmpty()
+            assertThat(closeActivity).isTrue()
+        }
+
+    @Test
+    fun touchpadGesturesDisabled_whenTutorialGoesToForeground() =
+        testScope.runTest {
+            startingPeripheral = INTENT_TUTORIAL_TYPE_TOUCHPAD
+            collectValues(viewModel.screen) // just to initialize viewModel
+            peripheralsState(touchpadConnected = true)
+
+            lifecycle.handleLifecycleEvent(Event.ON_START)
+
+            assertGesturesDisabled()
+        }
+
+    @Test
+    fun touchpadGesturesNotDisabled_whenTutorialGoesToBackground() =
+        testScope.runTest {
+            startingPeripheral = INTENT_TUTORIAL_TYPE_TOUCHPAD
+            collectValues(viewModel.screen)
+            peripheralsState(touchpadConnected = true)
+
+            lifecycle.handleLifecycleEvent(Event.ON_START)
+            lifecycle.handleLifecycleEvent(Event.ON_STOP)
+
+            assertGesturesNotDisabled()
+        }
+
+    @Test
+    fun keyboardShortcutsDisabled_onlyDuringKeyboardTutorial() =
+        testScope.runTest {
+            // TODO(b/358587037)
+        }
+
+    private fun TestScope.goToNextScreen() {
+        viewModel.onDoneButtonClicked()
+        runCurrent()
+    }
+
+    private fun TestScope.goBack() {
+        viewModel.onBack()
+        runCurrent()
+    }
+
+    private fun TestScope.peripheralsState(
+        keyboardConnected: Boolean = false,
+        touchpadConnected: Boolean = false
+    ) {
+        keyboardRepo.setIsAnyKeyboardConnected(keyboardConnected)
+        touchpadRepo.setIsAnyTouchpadConnected(touchpadConnected)
+        runCurrent()
+    }
+
+    private fun TestScope.assertGesturesNotDisabled() = assertFlagEnabled(enabled = false)
+
+    private fun TestScope.assertGesturesDisabled() = assertFlagEnabled(enabled = true)
+
+    private fun TestScope.assertFlagEnabled(enabled: Boolean) {
+        // sysui state is changed on background scope so let's make sure it's executed
+        runCurrent()
+        assertThat(sysUiState.isFlagEnabled(SYSUI_STATE_TOUCHPAD_GESTURES_DISABLED))
+            .isEqualTo(enabled)
+    }
+
+    // replace below when we have better fake
+    internal class PrettyFakeTouchpadRepository : TouchpadRepository {
+
+        private val _isAnyTouchpadConnected = MutableStateFlow(false)
+        override val isAnyTouchpadConnected: Flow<Boolean> = _isAnyTouchpadConnected
+
+        fun setIsAnyTouchpadConnected(connected: Boolean) {
+            _isAnyTouchpadConnected.value = connected
+        }
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardQuickAffordancesCombinedViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardQuickAffordancesCombinedViewModelTest.kt
index 24bea2c..73b9f57 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardQuickAffordancesCombinedViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardQuickAffordancesCombinedViewModelTest.kt
@@ -20,6 +20,7 @@
 import android.app.admin.DevicePolicyManager
 import android.content.Intent
 import android.os.UserHandle
+import android.platform.test.annotations.EnableFlags
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import com.android.internal.widget.LockPatternUtils
@@ -402,6 +403,67 @@
         }
 
     @Test
+    @EnableFlags(com.android.systemui.Flags.FLAG_NEW_PICKER_UI)
+    fun startButton_inPreviewMode_onPreviewQuickAffordanceSelected() =
+        testScope.runTest {
+            underTest.onPreviewSlotSelected(KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_START)
+            underTest.enablePreviewMode(KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_START, true)
+
+            repository.setKeyguardShowing(false)
+            val latest = collectLastValue(underTest.startButton)
+
+            val icon: Icon = mock()
+            val testConfig =
+                TestConfig(
+                    isVisible = true,
+                    isClickable = true,
+                    isActivated = true,
+                    icon = icon,
+                    canShowWhileLocked = false,
+                    intent = null,
+                    slotId = KeyguardQuickAffordancePosition.BOTTOM_START.toSlotId(),
+                )
+            val defaultConfigKey =
+                setUpQuickAffordanceModel(
+                    position = KeyguardQuickAffordancePosition.BOTTOM_START,
+                    testConfig = testConfig,
+                )
+
+            // Set up the quick access wallet config
+            val quickAccessWalletAffordanceConfigKey =
+                quickAccessWalletAffordanceConfig
+                    .apply {
+                        onTriggeredResult =
+                            KeyguardQuickAffordanceConfig.OnTriggeredResult.StartActivity(
+                                intent = Intent("action"),
+                                canShowWhileLocked = false,
+                            )
+                        setState(
+                            KeyguardQuickAffordanceConfig.LockScreenState.Visible(
+                                icon = icon,
+                                activationState = ActivationState.Active,
+                            )
+                        )
+                    }
+                    .let {
+                        KeyguardQuickAffordancePosition.BOTTOM_START.toSlotId() +
+                            "::${quickAccessWalletAffordanceConfig.key}"
+                    }
+
+            // onPreviewQuickAffordanceSelected should trigger the override with the quick access
+            // wallet quick affordance
+            underTest.onPreviewQuickAffordanceSelected(
+                KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_START,
+                BuiltInKeyguardQuickAffordanceKeys.QUICK_ACCESS_WALLET,
+            )
+            Truth.assertThat(latest()?.configKey).isEqualTo(quickAccessWalletAffordanceConfigKey)
+
+            // onClearPreviewQuickAffordances should make the default quick affordance shows again
+            underTest.onClearPreviewQuickAffordances()
+            Truth.assertThat(latest()?.configKey).isEqualTo(defaultConfigKey)
+        }
+
+    @Test
     fun startButton_inPreviewMode_visibleEvenWhenKeyguardNotShowing() =
         testScope.runTest {
             underTest.onPreviewSlotSelected(KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_START)
@@ -445,7 +507,7 @@
         }
 
     @Test
-    fun endButton_inHiglightedPreviewMode_dimmedWhenOtherIsSelected() =
+    fun endButton_inHighlightedPreviewMode_dimmedWhenOtherIsSelected() =
         testScope.runTest {
             underTest.onPreviewSlotSelected(KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_START)
             underTest.enablePreviewMode(KeyguardQuickAffordanceSlots.SLOT_ID_BOTTOM_START, true)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/data/ShellRecentTaskListProviderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/data/ShellRecentTaskListProviderTest.kt
index 7211620..f531a3f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/data/ShellRecentTaskListProviderTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/data/ShellRecentTaskListProviderTest.kt
@@ -15,8 +15,8 @@
 import com.android.systemui.util.mockito.any
 import com.android.systemui.util.mockito.mock
 import com.android.systemui.util.mockito.whenever
-import com.android.wm.shell.common.split.SplitScreenConstants.SNAP_TO_50_50
 import com.android.wm.shell.recents.RecentTasks
+import com.android.wm.shell.shared.split.SplitScreenConstants.SNAP_TO_50_50
 import com.android.wm.shell.util.GroupedRecentTaskInfo
 import com.android.wm.shell.util.SplitBounds
 import com.google.common.truth.Truth.assertThat
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/AutoAddTrackerTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/AutoAddTrackerTest.java
deleted file mode 100644
index 1eeaef7..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/AutoAddTrackerTest.java
+++ /dev/null
@@ -1,311 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
- * except in compliance with the License. You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software distributed under the
- * License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the specific language governing
- * permissions and limitations under the License.
- */
-
-package com.android.systemui.qs;
-
-import static com.android.systemui.statusbar.phone.AutoTileManager.SAVER;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertTrue;
-import static org.mockito.ArgumentMatchers.eq;
-import static org.mockito.Mockito.any;
-import static org.mockito.Mockito.anyInt;
-import static org.mockito.Mockito.never;
-import static org.mockito.Mockito.verify;
-
-import android.content.BroadcastReceiver;
-import android.content.Intent;
-import android.content.IntentFilter;
-import android.os.UserHandle;
-import android.provider.Settings.Secure;
-import android.testing.TestableLooper.RunWithLooper;
-
-import androidx.test.ext.junit.runners.AndroidJUnit4;
-import androidx.test.filters.SmallTest;
-
-import com.android.systemui.SysuiTestCase;
-import com.android.systemui.broadcast.BroadcastDispatcher;
-import com.android.systemui.dump.DumpManager;
-import com.android.systemui.util.settings.FakeSettings;
-import com.android.systemui.util.settings.SecureSettings;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.ArgumentCaptor;
-import org.mockito.Captor;
-import org.mockito.InOrder;
-import org.mockito.Mock;
-import org.mockito.Mockito;
-import org.mockito.MockitoAnnotations;
-
-import java.util.List;
-import java.util.concurrent.Executor;
-
-@RunWith(AndroidJUnit4.class)
-@RunWithLooper
-@SmallTest
-public class AutoAddTrackerTest extends SysuiTestCase {
-
-    private static final int END_POSITION = -1;
-    private static final int USER = 0;
-
-    @Mock
-    private BroadcastDispatcher mBroadcastDispatcher;
-    @Mock
-    private QSHost mQSHost;
-    @Mock
-    private DumpManager mDumpManager;
-    @Captor
-    private ArgumentCaptor<BroadcastReceiver> mBroadcastReceiverArgumentCaptor;
-    @Captor
-    private ArgumentCaptor<IntentFilter> mIntentFilterArgumentCaptor;
-
-    private Executor mBackgroundExecutor = Runnable::run; // Direct executor
-    private AutoAddTracker mAutoTracker;
-    private SecureSettings mSecureSettings;
-
-    @Before
-    public void setUp() {
-        MockitoAnnotations.initMocks(this);
-
-        mSecureSettings = new FakeSettings();
-
-        mSecureSettings.putStringForUser(Secure.QS_AUTO_ADDED_TILES, null, USER);
-
-        mAutoTracker = createAutoAddTracker(USER);
-        mAutoTracker.initialize();
-    }
-
-    @Test
-    public void testChangeFromBackup() {
-        assertFalse(mAutoTracker.isAdded(SAVER));
-
-        mSecureSettings.putStringForUser(Secure.QS_AUTO_ADDED_TILES, SAVER, USER);
-
-        assertTrue(mAutoTracker.isAdded(SAVER));
-
-        mAutoTracker.destroy();
-    }
-
-    @Test
-    public void testSetAdded() {
-        assertFalse(mAutoTracker.isAdded(SAVER));
-        mAutoTracker.setTileAdded(SAVER);
-
-        assertTrue(mAutoTracker.isAdded(SAVER));
-
-        mAutoTracker.destroy();
-    }
-
-    @Test
-    public void testPersist() {
-        assertFalse(mAutoTracker.isAdded(SAVER));
-        mAutoTracker.setTileAdded(SAVER);
-
-        mAutoTracker.destroy();
-        mAutoTracker = createAutoAddTracker(USER);
-        mAutoTracker.initialize();
-
-        assertTrue(mAutoTracker.isAdded(SAVER));
-
-        mAutoTracker.destroy();
-    }
-
-    @Test
-    public void testIndependentUsers() {
-        mAutoTracker.setTileAdded(SAVER);
-
-        mAutoTracker = createAutoAddTracker(USER + 1);
-        mAutoTracker.initialize();
-        assertFalse(mAutoTracker.isAdded(SAVER));
-    }
-
-    @Test
-    public void testChangeUser() {
-        mAutoTracker.setTileAdded(SAVER);
-
-        mAutoTracker = createAutoAddTracker(USER + 1);
-        mAutoTracker.changeUser(UserHandle.of(USER));
-        assertTrue(mAutoTracker.isAdded(SAVER));
-    }
-
-    @Test
-    public void testRestoredTilePositionPreserved() {
-        verify(mBroadcastDispatcher).registerReceiver(
-                mBroadcastReceiverArgumentCaptor.capture(), any(), any(), any(), anyInt(), any());
-        String restoredTiles = "saver,internet,work,cast";
-        Intent restoreTilesIntent = makeRestoreIntent(Secure.QS_TILES, null, restoredTiles);
-
-        mBroadcastReceiverArgumentCaptor.getValue().onReceive(mContext, restoreTilesIntent);
-
-        assertEquals(2, mAutoTracker.getRestoredTilePosition("work"));
-    }
-
-    @Test
-    public void testNoRestoredTileReturnsEndPosition() {
-        verify(mBroadcastDispatcher).registerReceiver(
-                mBroadcastReceiverArgumentCaptor.capture(), any(), any(), any(), anyInt(), any());
-        Intent restoreTilesIntent = makeRestoreIntent(Secure.QS_TILES, null, null);
-
-        mBroadcastReceiverArgumentCaptor.getValue().onReceive(mContext, restoreTilesIntent);
-
-        assertEquals(END_POSITION, mAutoTracker.getRestoredTilePosition("work"));
-    }
-
-    @Test
-    public void testBroadcastReceiverRegistered() {
-        verify(mBroadcastDispatcher).registerReceiver(
-                any(), mIntentFilterArgumentCaptor.capture(), any(), eq(UserHandle.of(USER)),
-                anyInt(), any());
-
-        assertTrue(
-                mIntentFilterArgumentCaptor.getValue().hasAction(Intent.ACTION_SETTING_RESTORED));
-    }
-
-    @Test
-    public void testBroadcastReceiverChangesWithUser() {
-        mAutoTracker.changeUser(UserHandle.of(USER + 1));
-
-        InOrder inOrder = Mockito.inOrder(mBroadcastDispatcher);
-        inOrder.verify(mBroadcastDispatcher).unregisterReceiver(any());
-        inOrder.verify(mBroadcastDispatcher)
-                .registerReceiver(any(), any(), any(), eq(UserHandle.of(USER + 1)), anyInt(),
-                        any());
-    }
-
-    @Test
-    public void testSettingRestoredWithTilesNotRemovedInSource_noAutoAddedInTarget() {
-        verify(mBroadcastDispatcher).registerReceiver(
-                mBroadcastReceiverArgumentCaptor.capture(), any(), any(), any(), anyInt(), any());
-
-        // These tiles were present in the original device
-        String restoredTiles = "saver,work,internet,cast";
-        Intent restoreTilesIntent = makeRestoreIntent(Secure.QS_TILES, null, restoredTiles);
-        mBroadcastReceiverArgumentCaptor.getValue().onReceive(mContext, restoreTilesIntent);
-
-        // And these tiles have been auto-added in the original device
-        // (no auto-added before restore)
-        String restoredAutoAddTiles = "work";
-        Intent restoreAutoAddTilesIntent =
-                makeRestoreIntent(Secure.QS_AUTO_ADDED_TILES, null, restoredAutoAddTiles);
-        mBroadcastReceiverArgumentCaptor.getValue().onReceive(mContext, restoreAutoAddTilesIntent);
-
-        // Then, don't remove any current tiles
-        verify(mQSHost, never()).removeTiles(any());
-        assertEquals(restoredAutoAddTiles,
-                mSecureSettings.getStringForUser(Secure.QS_AUTO_ADDED_TILES, USER));
-    }
-
-    @Test
-    public void testSettingRestoredWithTilesRemovedInSource_noAutoAddedInTarget() {
-        verify(mBroadcastDispatcher)
-                .registerReceiver(mBroadcastReceiverArgumentCaptor.capture(), any(), any(), any(),
-                        anyInt(), any());
-
-        // These tiles were present in the original device
-        String restoredTiles = "saver,internet,cast";
-        Intent restoreTilesIntent = makeRestoreIntent(Secure.QS_TILES, null, restoredTiles);
-        mBroadcastReceiverArgumentCaptor.getValue().onReceive(mContext, restoreTilesIntent);
-
-        // And these tiles have been auto-added in the original device
-        // (no auto-added before restore)
-        String restoredAutoAddTiles = "work";
-        Intent restoreAutoAddTilesIntent =
-                makeRestoreIntent(Secure.QS_AUTO_ADDED_TILES, null, restoredAutoAddTiles);
-        mBroadcastReceiverArgumentCaptor.getValue().onReceive(mContext, restoreAutoAddTilesIntent);
-
-        // Then, remove work tile
-        verify(mQSHost).removeTiles(List.of("work"));
-        assertEquals(restoredAutoAddTiles,
-                mSecureSettings.getStringForUser(Secure.QS_AUTO_ADDED_TILES, USER));
-    }
-
-    @Test
-    public void testSettingRestoredWithTilesRemovedInSource_sameAutoAddedinTarget() {
-        verify(mBroadcastDispatcher)
-                .registerReceiver(mBroadcastReceiverArgumentCaptor.capture(), any(), any(), any(),
-                        anyInt(), any());
-
-        // These tiles were present in the original device
-        String restoredTiles = "saver,internet,cast";
-        Intent restoreTilesIntent =
-                makeRestoreIntent(Secure.QS_TILES, "saver, internet, cast, work", restoredTiles);
-        mBroadcastReceiverArgumentCaptor.getValue().onReceive(mContext, restoreTilesIntent);
-
-        // And these tiles have been auto-added in the original device
-        // (no auto-added before restore)
-        String restoredAutoAddTiles = "work";
-        Intent restoreAutoAddTilesIntent =
-                makeRestoreIntent(Secure.QS_AUTO_ADDED_TILES, "work", restoredAutoAddTiles);
-        mBroadcastReceiverArgumentCaptor.getValue().onReceive(mContext, restoreAutoAddTilesIntent);
-
-        // Then, remove work tile
-        verify(mQSHost).removeTiles(List.of("work"));
-        assertEquals(restoredAutoAddTiles,
-                mSecureSettings.getStringForUser(Secure.QS_AUTO_ADDED_TILES, USER));
-    }
-
-    @Test
-    public void testSettingRestoredWithTilesRemovedInSource_othersAutoAddedinTarget() {
-        verify(mBroadcastDispatcher)
-                .registerReceiver(mBroadcastReceiverArgumentCaptor.capture(), any(), any(), any(),
-                        anyInt(), any());
-
-        // These tiles were present in the original device
-        String restoredTiles = "saver,internet,cast";
-        Intent restoreTilesIntent =
-                makeRestoreIntent(Secure.QS_TILES, "saver, internet, cast, work", restoredTiles);
-        mBroadcastReceiverArgumentCaptor.getValue().onReceive(mContext, restoreTilesIntent);
-
-        // And these tiles have been auto-added in the original device
-        // (no auto-added before restore)
-        String restoredAutoAddTiles = "work";
-        Intent restoreAutoAddTilesIntent =
-                makeRestoreIntent(Secure.QS_AUTO_ADDED_TILES, "inversion", restoredAutoAddTiles);
-        mBroadcastReceiverArgumentCaptor.getValue().onReceive(mContext, restoreAutoAddTilesIntent);
-
-        // Then, remove work tile
-        verify(mQSHost).removeTiles(List.of("work"));
-
-        String setting = mSecureSettings.getStringForUser(Secure.QS_AUTO_ADDED_TILES, USER);
-        assertEquals(2, setting.split(",").length);
-        assertTrue(setting.contains("work"));
-        assertTrue(setting.contains("inversion"));
-    }
-
-
-    private Intent makeRestoreIntent(
-            String settingName, String previousValue, String restoredValue) {
-        Intent intent = new Intent(Intent.ACTION_SETTING_RESTORED);
-        intent.putExtra(Intent.EXTRA_SETTING_NAME, settingName);
-        intent.putExtra(Intent.EXTRA_SETTING_PREVIOUS_VALUE, previousValue);
-        intent.putExtra(Intent.EXTRA_SETTING_NEW_VALUE, restoredValue);
-        return intent;
-    }
-
-    private AutoAddTracker createAutoAddTracker(int user) {
-        // Null handler wil dispatch sync.
-        return new AutoAddTracker(
-                mSecureSettings,
-                mBroadcastDispatcher,
-                mQSHost,
-                mDumpManager,
-                null,
-                mBackgroundExecutor,
-                user
-        );
-    }
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QSImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/QSImplTest.java
index 206bbbf..4ce2d7c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/QSImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/QSImplTest.java
@@ -51,6 +51,7 @@
 
 import androidx.compose.ui.platform.ComposeView;
 import androidx.lifecycle.Lifecycle;
+import androidx.lifecycle.LifecycleOwner;
 import androidx.test.ext.junit.runners.AndroidJUnit4;
 import androidx.test.filters.SmallTest;
 
@@ -611,7 +612,8 @@
         when(mQSContainerImplController.getView()).thenReturn(mContainer);
         when(mQSPanelController.getTileLayout()).thenReturn(mQQsTileLayout);
         when(mQuickQSPanelController.getTileLayout()).thenReturn(mQsTileLayout);
-        when(mFooterActionsViewModelFactory.create(any())).thenReturn(mFooterActionsViewModel);
+        when(mFooterActionsViewModelFactory.create(any(LifecycleOwner.class)))
+                .thenReturn(mFooterActionsViewModel);
     }
 
     private void setUpMedia() {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/QSTileHostTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/QSTileHostTest.java
deleted file mode 100644
index 6d1bc82..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/QSTileHostTest.java
+++ /dev/null
@@ -1,786 +0,0 @@
-/*
- * Copyright (C) 2018 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.qs;
-
-
-import static com.android.systemui.Flags.FLAG_QS_NEW_PIPELINE;
-import static com.android.systemui.Flags.FLAG_QS_NEW_TILES;
-
-import static junit.framework.Assert.assertEquals;
-import static junit.framework.Assert.assertFalse;
-import static junit.framework.Assert.assertTrue;
-
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.anyInt;
-import static org.mockito.ArgumentMatchers.anyString;
-import static org.mockito.ArgumentMatchers.isNull;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.never;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
-
-import android.content.ComponentName;
-import android.content.Context;
-import android.content.Intent;
-import android.content.SharedPreferences;
-import android.database.ContentObserver;
-import android.os.Handler;
-import android.os.Looper;
-import android.os.UserHandle;
-import android.util.SparseArray;
-
-import androidx.annotation.Nullable;
-import androidx.test.ext.junit.runners.AndroidJUnit4;
-import androidx.test.filters.SmallTest;
-
-import com.android.internal.logging.MetricsLogger;
-import com.android.internal.util.CollectionUtils;
-import com.android.systemui.SysuiTestCase;
-import com.android.systemui.animation.Expandable;
-import com.android.systemui.classifier.FalsingManagerFake;
-import com.android.systemui.dump.nano.SystemUIProtoDump;
-import com.android.systemui.plugins.ActivityStarter;
-import com.android.systemui.plugins.PluginManager;
-import com.android.systemui.plugins.qs.QSFactory;
-import com.android.systemui.plugins.qs.QSTile;
-import com.android.systemui.plugins.statusbar.StatusBarStateController;
-import com.android.systemui.qs.external.CustomTile;
-import com.android.systemui.qs.external.CustomTileStatePersister;
-import com.android.systemui.qs.external.TileLifecycleManager;
-import com.android.systemui.qs.external.TileServiceKey;
-import com.android.systemui.qs.logging.QSLogger;
-import com.android.systemui.qs.pipeline.shared.QSPipelineFlagsRepository;
-import com.android.systemui.qs.tileimpl.QSTileImpl;
-import com.android.systemui.qs.tiles.di.NewQSTileFactory;
-import com.android.systemui.res.R;
-import com.android.systemui.settings.UserFileManager;
-import com.android.systemui.settings.UserTracker;
-import com.android.systemui.shade.ShadeController;
-import com.android.systemui.statusbar.phone.AutoTileManager;
-import com.android.systemui.tuner.TunerService;
-import com.android.systemui.util.FakeSharedPreferences;
-import com.android.systemui.util.concurrency.FakeExecutor;
-import com.android.systemui.util.settings.FakeSettings;
-import com.android.systemui.util.settings.SecureSettings;
-import com.android.systemui.util.time.FakeSystemClock;
-
-import dagger.Lazy;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
-import org.mockito.stubbing.Answer;
-
-import java.io.PrintWriter;
-import java.io.StringWriter;
-import java.util.List;
-import java.util.concurrent.Executor;
-
-import javax.inject.Provider;
-
-@RunWith(AndroidJUnit4.class)
-@SmallTest
-public class QSTileHostTest extends SysuiTestCase {
-
-    private static String MOCK_STATE_STRING = "MockState";
-    private static ComponentName CUSTOM_TILE =
-            ComponentName.unflattenFromString("TEST_PKG/.TEST_CLS");
-    private static final String CUSTOM_TILE_SPEC = CustomTile.toSpec(CUSTOM_TILE);
-    private static final String SETTING = QSHost.TILES_SETTING;
-    @Mock
-    private PluginManager mPluginManager;
-    @Mock
-    private TunerService mTunerService;
-    @Mock
-    private AutoTileManager mAutoTiles;
-    @Mock
-    private ShadeController mShadeController;
-    @Mock
-    private QSLogger mQSLogger;
-    @Mock
-    private CustomTile mCustomTile;
-    @Mock
-    private UserTracker mUserTracker;
-    @Mock
-    private CustomTileStatePersister mCustomTileStatePersister;
-    @Mock
-    private TileLifecycleManager.Factory mTileLifecycleManagerFactory;
-    @Mock
-    private TileLifecycleManager mTileLifecycleManager;
-    @Mock
-    private UserFileManager mUserFileManager;
-
-    private SecureSettings mSecureSettings;
-
-    private QSFactory mDefaultFactory;
-
-    private SparseArray<SharedPreferences> mSharedPreferencesByUser;
-
-    private QSPipelineFlagsRepository mQSPipelineFlagsRepository;
-
-    private FakeExecutor mMainExecutor;
-
-    private QSTileHost mQSTileHost;
-
-    @Before
-    public void setUp() {
-        MockitoAnnotations.initMocks(this);
-
-        mSetFlagsRule.disableFlags(FLAG_QS_NEW_PIPELINE);
-        mSetFlagsRule.disableFlags(FLAG_QS_NEW_TILES);
-        mQSPipelineFlagsRepository = new QSPipelineFlagsRepository();
-
-        mMainExecutor = new FakeExecutor(new FakeSystemClock());
-
-        mSharedPreferencesByUser = new SparseArray<>();
-        when(mTileLifecycleManagerFactory
-                .create(any(Intent.class), any(UserHandle.class)))
-                .thenReturn(mTileLifecycleManager);
-        when(mUserFileManager.getSharedPreferences(anyString(), anyInt(), anyInt()))
-                .thenAnswer((Answer<SharedPreferences>) invocation -> {
-                    assertEquals(QSTileHost.TILES, invocation.getArgument(0));
-                    int userId = invocation.getArgument(2);
-                    if (!mSharedPreferencesByUser.contains(userId)) {
-                        mSharedPreferencesByUser.put(userId, new FakeSharedPreferences());
-                    }
-                    return mSharedPreferencesByUser.get(userId);
-                });
-
-        mSecureSettings = new FakeSettings();
-        saveSetting("");
-        setUpTileFactory();
-        mQSTileHost = new TestQSTileHost(mContext, () -> null, mDefaultFactory, mMainExecutor,
-                mPluginManager, mTunerService, () -> mAutoTiles, () -> mShadeController,
-                mQSLogger, mUserTracker, mSecureSettings, mCustomTileStatePersister,
-                mTileLifecycleManagerFactory, mUserFileManager, mQSPipelineFlagsRepository);
-        mMainExecutor.runAllReady();
-
-        mSecureSettings.registerContentObserverForUserSync(SETTING, new ContentObserver(null) {
-            @Override
-            public void onChange(boolean selfChange) {
-                super.onChange(selfChange);
-                mMainExecutor.execute(() -> mQSTileHost.onTuningChanged(SETTING, getSetting()));
-                mMainExecutor.runAllReady();
-            }
-        }, mUserTracker.getUserId());
-    }
-
-    private void saveSetting(String value) {
-        mSecureSettings.putStringForUser(
-                SETTING, value, "", false, mUserTracker.getUserId(), false);
-    }
-
-    private String getSetting() {
-        return mSecureSettings.getStringForUser(SETTING, mUserTracker.getUserId());
-    }
-
-    private void setUpTileFactory() {
-        mDefaultFactory = new FakeQSFactory(spec -> {
-            if ("spec1".equals(spec)) {
-                return new TestTile1(mQSTileHost);
-            } else if ("spec2".equals(spec)) {
-                return new TestTile2(mQSTileHost);
-            } else if ("spec3".equals(spec)) {
-                return new TestTile3(mQSTileHost);
-            } else if ("na".equals(spec)) {
-                return new NotAvailableTile(mQSTileHost);
-            } else if (CUSTOM_TILE_SPEC.equals(spec)) {
-                QSTile tile = mCustomTile;
-                QSTile.State s = mock(QSTile.State.class);
-                s.spec = spec;
-                when(mCustomTile.getState()).thenReturn(s);
-                return tile;
-            } else if ("internet".equals(spec)
-                    || "wifi".equals(spec)
-                    || "cell".equals(spec)) {
-                return new TestTile1(mQSTileHost);
-            } else {
-                return null;
-            }
-        });
-        when(mCustomTile.isAvailable()).thenReturn(true);
-    }
-
-    @Test
-    public void testLoadTileSpecs_emptySetting() {
-        List<String> tiles = QSTileHost.loadTileSpecs(mContext, "");
-        assertFalse(tiles.isEmpty());
-    }
-
-    @Test
-    public void testLoadTileSpecs_nullSetting() {
-        List<String> tiles = QSTileHost.loadTileSpecs(mContext, null);
-        assertFalse(tiles.isEmpty());
-    }
-
-    @Test
-    public void testInvalidSpecUsesDefault() {
-        mContext.getOrCreateTestableResources()
-                .addOverride(R.string.quick_settings_tiles, "spec1,spec2");
-        saveSetting("not-valid");
-
-        assertEquals(2, mQSTileHost.getTiles().size());
-    }
-
-    @Test
-    public void testRemoveWifiAndCellularWithoutInternet() {
-        saveSetting("wifi, spec1, cell, spec2");
-
-        assertEquals("internet", mQSTileHost.getSpecs().get(0));
-        assertEquals("spec1", mQSTileHost.getSpecs().get(1));
-        assertEquals("spec2", mQSTileHost.getSpecs().get(2));
-    }
-
-    @Test
-    public void testRemoveWifiAndCellularWithInternet() {
-        saveSetting("wifi, spec1, cell, spec2, internet");
-
-        assertEquals("spec1", mQSTileHost.getSpecs().get(0));
-        assertEquals("spec2", mQSTileHost.getSpecs().get(1));
-        assertEquals("internet", mQSTileHost.getSpecs().get(2));
-    }
-
-    @Test
-    public void testRemoveWifiWithoutInternet() {
-        saveSetting("spec1, wifi, spec2");
-
-        assertEquals("spec1", mQSTileHost.getSpecs().get(0));
-        assertEquals("internet", mQSTileHost.getSpecs().get(1));
-        assertEquals("spec2", mQSTileHost.getSpecs().get(2));
-    }
-
-    @Test
-    public void testRemoveCellWithInternet() {
-        saveSetting("spec1, spec2, cell, internet");
-
-        assertEquals("spec1", mQSTileHost.getSpecs().get(0));
-        assertEquals("spec2", mQSTileHost.getSpecs().get(1));
-        assertEquals("internet", mQSTileHost.getSpecs().get(2));
-    }
-
-    @Test
-    public void testNoWifiNoCellularNoInternet() {
-        saveSetting("spec1,spec2");
-
-        assertEquals("spec1", mQSTileHost.getSpecs().get(0));
-        assertEquals("spec2", mQSTileHost.getSpecs().get(1));
-    }
-
-    @Test
-    public void testSpecWithInvalidDoesNotUseDefault() {
-        mContext.getOrCreateTestableResources()
-                .addOverride(R.string.quick_settings_tiles, "spec1,spec2");
-        saveSetting("spec2,not-valid");
-
-        assertEquals(1, mQSTileHost.getTiles().size());
-        QSTile element = CollectionUtils.firstOrNull(mQSTileHost.getTiles());
-        assertTrue(element instanceof TestTile2);
-    }
-
-    @Test
-    public void testDump() {
-        saveSetting("spec1,spec2");
-        StringWriter w = new StringWriter();
-        PrintWriter pw = new PrintWriter(w);
-        mQSTileHost.dump(pw, new String[]{});
-
-        String output = "QSTileHost:" + "\n"
-                + "tile specs: [spec1, spec2]" + "\n"
-                + "current user: 0" + "\n"
-                + "is dirty: false" + "\n"
-                + "tiles:" + "\n"
-                + "TestTile1:" + "\n"
-                + "    MockState" + "\n"
-                + "TestTile2:" + "\n"
-                + "    MockState" + "\n";
-
-        System.out.println(output);
-        System.out.println(w.getBuffer().toString());
-
-        assertEquals(output, w.getBuffer().toString());
-    }
-
-    @Test
-    public void testDefault() {
-        mContext.getOrCreateTestableResources()
-                .addOverride(R.string.quick_settings_tiles_default, "spec1");
-        saveSetting("default");
-        assertEquals(1, mQSTileHost.getTiles().size());
-        QSTile element = CollectionUtils.firstOrNull(mQSTileHost.getTiles());
-        assertTrue(element instanceof TestTile1);
-        verify(mQSLogger).logTileAdded("spec1");
-    }
-
-    @Test
-    public void testNoRepeatedSpecs_addTile() {
-        mContext.getOrCreateTestableResources()
-                .addOverride(R.string.quick_settings_tiles, "spec1,spec2");
-        saveSetting("spec1,spec2");
-
-        mQSTileHost.addTile("spec1");
-
-        assertEquals(2, mQSTileHost.getSpecs().size());
-        assertEquals("spec1", mQSTileHost.getSpecs().get(0));
-        assertEquals("spec2", mQSTileHost.getSpecs().get(1));
-    }
-
-    @Test
-    public void testAddTileAtValidPosition() {
-        mContext.getOrCreateTestableResources()
-                .addOverride(R.string.quick_settings_tiles, "spec1,spec3");
-        saveSetting("spec1,spec3");
-
-        mQSTileHost.addTile("spec2", 1);
-        mMainExecutor.runAllReady();
-
-        assertEquals(3, mQSTileHost.getSpecs().size());
-        assertEquals("spec1", mQSTileHost.getSpecs().get(0));
-        assertEquals("spec2", mQSTileHost.getSpecs().get(1));
-        assertEquals("spec3", mQSTileHost.getSpecs().get(2));
-    }
-
-    @Test
-    public void testAddTileAtInvalidPositionAddsToEnd() {
-        mContext.getOrCreateTestableResources()
-                .addOverride(R.string.quick_settings_tiles, "spec1,spec3");
-        saveSetting("spec1,spec3");
-
-        mQSTileHost.addTile("spec2", 100);
-        mMainExecutor.runAllReady();
-
-        assertEquals(3, mQSTileHost.getSpecs().size());
-        assertEquals("spec1", mQSTileHost.getSpecs().get(0));
-        assertEquals("spec3", mQSTileHost.getSpecs().get(1));
-        assertEquals("spec2", mQSTileHost.getSpecs().get(2));
-    }
-
-    @Test
-    public void testAddTileAtEnd() {
-        mContext.getOrCreateTestableResources()
-                .addOverride(R.string.quick_settings_tiles, "spec1,spec3");
-        saveSetting("spec1,spec3");
-
-        mQSTileHost.addTile("spec2", QSTileHost.POSITION_AT_END);
-        mMainExecutor.runAllReady();
-
-        assertEquals(3, mQSTileHost.getSpecs().size());
-        assertEquals("spec1", mQSTileHost.getSpecs().get(0));
-        assertEquals("spec3", mQSTileHost.getSpecs().get(1));
-        assertEquals("spec2", mQSTileHost.getSpecs().get(2));
-    }
-
-    @Test
-    public void testNoRepeatedSpecs_customTile() {
-        saveSetting(CUSTOM_TILE_SPEC);
-
-        mQSTileHost.addTile(CUSTOM_TILE, /* end */ false);
-        mMainExecutor.runAllReady();
-
-        assertEquals(1, mQSTileHost.getSpecs().size());
-        assertEquals(CUSTOM_TILE_SPEC, mQSTileHost.getSpecs().get(0));
-    }
-
-    @Test
-    public void testAddedAtBeginningOnDefault_customTile() {
-        saveSetting("spec1"); // seed
-
-        mQSTileHost.addTile(CUSTOM_TILE);
-        mMainExecutor.runAllReady();
-
-        assertEquals(2, mQSTileHost.getSpecs().size());
-        assertEquals(CUSTOM_TILE_SPEC, mQSTileHost.getSpecs().get(0));
-    }
-
-    @Test
-    public void testAddedAtBeginning_customTile() {
-        saveSetting("spec1"); // seed
-
-        mQSTileHost.addTile(CUSTOM_TILE, /* end */ false);
-        mMainExecutor.runAllReady();
-
-        assertEquals(2, mQSTileHost.getSpecs().size());
-        assertEquals(CUSTOM_TILE_SPEC, mQSTileHost.getSpecs().get(0));
-    }
-
-    @Test
-    public void testAddedAtEnd_customTile() {
-        saveSetting("spec1"); // seed
-
-        mQSTileHost.addTile(CUSTOM_TILE, /* end */ true);
-        mMainExecutor.runAllReady();
-
-        assertEquals(2, mQSTileHost.getSpecs().size());
-        assertEquals(CUSTOM_TILE_SPEC, mQSTileHost.getSpecs().get(1));
-    }
-
-    @Test
-    public void testLoadTileSpec_repeated() {
-        List<String> specs = QSTileHost.loadTileSpecs(mContext, "spec1,spec1,spec2");
-
-        assertEquals(2, specs.size());
-        assertEquals("spec1", specs.get(0));
-        assertEquals("spec2", specs.get(1));
-    }
-
-    @Test
-    public void testLoadTileSpec_repeatedInDefault() {
-        mContext.getOrCreateTestableResources()
-                .addOverride(R.string.quick_settings_tiles_default, "spec1,spec1");
-        List<String> specs = QSTileHost.loadTileSpecs(mContext, "default");
-    }
-
-    @Test
-    public void testLoadTileSpec_repeatedDefaultAndSetting() {
-        mContext.getOrCreateTestableResources()
-                .addOverride(R.string.quick_settings_tiles_default, "spec1");
-        List<String> specs = QSTileHost.loadTileSpecs(mContext, "default,spec1");
-    }
-
-    @Test
-    public void testNotAvailableTile_specNotNull() {
-        saveSetting("na");
-        verify(mQSLogger, never()).logTileDestroyed(isNull(), anyString());
-    }
-
-    @Test
-    public void testCustomTileRemoved_stateDeleted() {
-        mQSTileHost.changeTilesByUser(List.of(CUSTOM_TILE_SPEC), List.of());
-
-        verify(mCustomTileStatePersister)
-                .removeState(new TileServiceKey(CUSTOM_TILE, mQSTileHost.getUserId()));
-    }
-
-    @Test
-    public void testRemoveTiles() {
-        saveSetting("spec1,spec2,spec3");
-
-        mQSTileHost.removeTiles(List.of("spec1", "spec2"));
-
-        mMainExecutor.runAllReady();
-        assertEquals(List.of("spec3"), mQSTileHost.getSpecs());
-    }
-
-    @Test
-    public void testTilesRemovedInQuickSuccession() {
-        saveSetting("spec1,spec2,spec3");
-        mQSTileHost.removeTile("spec1");
-        mQSTileHost.removeTile("spec3");
-
-        mMainExecutor.runAllReady();
-        assertEquals(List.of("spec2"), mQSTileHost.getSpecs());
-        assertEquals("spec2", getSetting());
-    }
-
-    @Test
-    public void testAddTileInMainThread() {
-        saveSetting("spec1,spec2");
-
-        mQSTileHost.addTile("spec3");
-        assertEquals(List.of("spec1", "spec2"), mQSTileHost.getSpecs());
-
-        mMainExecutor.runAllReady();
-        assertEquals(List.of("spec1", "spec2", "spec3"), mQSTileHost.getSpecs());
-    }
-
-    @Test
-    public void testRemoveTileInMainThread() {
-        saveSetting("spec1,spec2");
-
-        mQSTileHost.removeTile("spec1");
-        assertEquals(List.of("spec1", "spec2"), mQSTileHost.getSpecs());
-
-        mMainExecutor.runAllReady();
-        assertEquals(List.of("spec2"), mQSTileHost.getSpecs());
-    }
-
-    @Test
-    public void testRemoveTilesInMainThread() {
-        saveSetting("spec1,spec2,spec3");
-
-        mQSTileHost.removeTiles(List.of("spec3", "spec1"));
-        assertEquals(List.of("spec1", "spec2", "spec3"), mQSTileHost.getSpecs());
-
-        mMainExecutor.runAllReady();
-        assertEquals(List.of("spec2"), mQSTileHost.getSpecs());
-    }
-
-    @Test
-    public void testRemoveTileByUserInMainThread() {
-        saveSetting("spec1," + CUSTOM_TILE_SPEC);
-
-        mQSTileHost.removeTileByUser(CUSTOM_TILE);
-        assertEquals(List.of("spec1", CUSTOM_TILE_SPEC), mQSTileHost.getSpecs());
-
-        mMainExecutor.runAllReady();
-        assertEquals(List.of("spec1"), mQSTileHost.getSpecs());
-    }
-
-    @Test
-    public void testNonValidTileNotStoredInSettings() {
-        saveSetting("spec1,not-valid");
-
-        assertEquals(List.of("spec1"), mQSTileHost.getSpecs());
-        assertEquals("spec1", getSetting());
-    }
-
-    @Test
-    public void testNotAvailableTileNotStoredInSettings() {
-        saveSetting("spec1,na");
-
-        assertEquals(List.of("spec1"), mQSTileHost.getSpecs());
-        assertEquals("spec1", getSetting());
-    }
-
-    @Test
-    public void testIsTileAdded_true() {
-        int user = mUserTracker.getUserId();
-        getSharedPreferencesForUser(user)
-                .edit()
-                .putBoolean(CUSTOM_TILE.flattenToString(), true)
-                .apply();
-
-        assertTrue(mQSTileHost.isTileAdded(CUSTOM_TILE, user));
-    }
-
-    @Test
-    public void testIsTileAdded_false() {
-        int user = mUserTracker.getUserId();
-        getSharedPreferencesForUser(user)
-                .edit()
-                .putBoolean(CUSTOM_TILE.flattenToString(), false)
-                .apply();
-
-        assertFalse(mQSTileHost.isTileAdded(CUSTOM_TILE, user));
-    }
-
-    @Test
-    public void testIsTileAdded_notSet() {
-        int user = mUserTracker.getUserId();
-
-        assertFalse(mQSTileHost.isTileAdded(CUSTOM_TILE, user));
-    }
-
-    @Test
-    public void testIsTileAdded_differentUser() {
-        int user = mUserTracker.getUserId();
-        mUserFileManager.getSharedPreferences(QSTileHost.TILES, 0, user)
-                .edit()
-                .putBoolean(CUSTOM_TILE.flattenToString(), true)
-                .apply();
-
-        assertFalse(mQSTileHost.isTileAdded(CUSTOM_TILE, user + 1));
-    }
-
-    @Test
-    public void testSetTileAdded_true() {
-        int user = mUserTracker.getUserId();
-        mQSTileHost.setTileAdded(CUSTOM_TILE, user, true);
-
-        assertTrue(getSharedPreferencesForUser(user)
-                .getBoolean(CUSTOM_TILE.flattenToString(), false));
-    }
-
-    @Test
-    public void testSetTileAdded_false() {
-        int user = mUserTracker.getUserId();
-        mQSTileHost.setTileAdded(CUSTOM_TILE, user, false);
-
-        assertFalse(getSharedPreferencesForUser(user)
-                .getBoolean(CUSTOM_TILE.flattenToString(), false));
-    }
-
-    @Test
-    public void testSetTileAdded_differentUser() {
-        int user = mUserTracker.getUserId();
-        mQSTileHost.setTileAdded(CUSTOM_TILE, user, true);
-
-        assertFalse(getSharedPreferencesForUser(user + 1)
-                .getBoolean(CUSTOM_TILE.flattenToString(), false));
-    }
-
-    @Test
-    public void testSetTileRemoved_afterCustomTileChangedByUser() {
-        int user = mUserTracker.getUserId();
-        saveSetting(CUSTOM_TILE_SPEC);
-
-        // This will be done by TileServiceManager
-        mQSTileHost.setTileAdded(CUSTOM_TILE, user, true);
-
-        mQSTileHost.changeTilesByUser(mQSTileHost.getSpecs(), List.of("spec1"));
-        assertFalse(getSharedPreferencesForUser(user)
-                .getBoolean(CUSTOM_TILE.flattenToString(), false));
-    }
-
-    @Test
-    public void testSetTileRemoved_removedByUser() {
-        int user = mUserTracker.getUserId();
-        saveSetting(CUSTOM_TILE_SPEC);
-
-        // This will be done by TileServiceManager
-        mQSTileHost.setTileAdded(CUSTOM_TILE, user, true);
-
-        mQSTileHost.removeTileByUser(CUSTOM_TILE);
-        mMainExecutor.runAllReady();
-        assertFalse(getSharedPreferencesForUser(user)
-                .getBoolean(CUSTOM_TILE.flattenToString(), false));
-    }
-
-    @Test
-    public void testSetTileRemoved_removedBySystem() {
-        int user = mUserTracker.getUserId();
-        saveSetting("spec1," + CUSTOM_TILE_SPEC);
-
-        // This will be done by TileServiceManager
-        mQSTileHost.setTileAdded(CUSTOM_TILE, user, true);
-
-        mQSTileHost.removeTile(CUSTOM_TILE_SPEC);
-        mMainExecutor.runAllReady();
-        assertFalse(getSharedPreferencesForUser(user)
-                .getBoolean(CUSTOM_TILE.flattenToString(), false));
-    }
-
-    @Test
-    public void testProtoDump_noTiles() {
-        SystemUIProtoDump proto = new SystemUIProtoDump();
-        mQSTileHost.dumpProto(proto, new String[0]);
-
-        assertEquals(0, proto.tiles.length);
-    }
-
-    @Test
-    public void testTilesInOrder() {
-        saveSetting("spec1," + CUSTOM_TILE_SPEC);
-
-        SystemUIProtoDump proto = new SystemUIProtoDump();
-        mQSTileHost.dumpProto(proto, new String[0]);
-
-        assertEquals(2, proto.tiles.length);
-        assertEquals("spec1", proto.tiles[0].getSpec());
-        assertEquals(CUSTOM_TILE.getPackageName(), proto.tiles[1].getComponentName().packageName);
-        assertEquals(CUSTOM_TILE.getClassName(), proto.tiles[1].getComponentName().className);
-    }
-
-    private SharedPreferences getSharedPreferencesForUser(int user) {
-        return mUserFileManager.getSharedPreferences(QSTileHost.TILES, 0, user);
-    }
-
-    private class TestQSTileHost extends QSTileHost {
-        TestQSTileHost(Context context, Lazy<NewQSTileFactory> newQSTileFactoryProvider,
-                QSFactory defaultFactory, Executor mainExecutor,
-                PluginManager pluginManager, TunerService tunerService,
-                Provider<AutoTileManager> autoTiles,
-                Lazy<ShadeController> shadeController, QSLogger qsLogger,
-                UserTracker userTracker, SecureSettings secureSettings,
-                CustomTileStatePersister customTileStatePersister,
-                TileLifecycleManager.Factory tileLifecycleManagerFactory,
-                UserFileManager userFileManager, QSPipelineFlagsRepository featureFlags) {
-            super(context, newQSTileFactoryProvider, defaultFactory, mainExecutor, pluginManager,
-                    tunerService, autoTiles, shadeController, qsLogger,
-                    userTracker, secureSettings, customTileStatePersister,
-                    tileLifecycleManagerFactory, userFileManager, featureFlags);
-        }
-
-        @Override
-        public void onPluginConnected(QSFactory plugin, Context pluginContext) {
-        }
-
-        @Override
-        public void onPluginDisconnected(QSFactory plugin) {
-        }
-    }
-
-
-    private class TestTile extends QSTileImpl<QSTile.State> {
-
-        protected TestTile(QSHost host) {
-            super(
-                    host,
-                    mock(QsEventLogger.class),
-                    mock(Looper.class),
-                    mock(Handler.class),
-                    new FalsingManagerFake(),
-                    mock(MetricsLogger.class),
-                    mock(StatusBarStateController.class),
-                    mock(ActivityStarter.class),
-                    QSTileHostTest.this.mQSLogger
-            );
-        }
-
-        @Override
-        public State newTileState() {
-            State s = mock(QSTile.State.class);
-            when(s.toString()).thenReturn(MOCK_STATE_STRING);
-            return s;
-        }
-
-        @Override
-        protected void handleClick(@Nullable Expandable expandable) {}
-
-        @Override
-        protected void handleUpdateState(State state, Object arg) {}
-
-        @Override
-        public int getMetricsCategory() {
-            return 0;
-        }
-
-        @Override
-        public Intent getLongClickIntent() {
-            return null;
-        }
-
-        @Override
-        public CharSequence getTileLabel() {
-            return null;
-        }
-    }
-
-    private class TestTile1 extends TestTile {
-
-        protected TestTile1(QSHost host) {
-            super(host);
-        }
-    }
-
-    private class TestTile2 extends TestTile {
-
-        protected TestTile2(QSHost host) {
-            super(host);
-        }
-    }
-
-    private class TestTile3 extends TestTile {
-
-        protected TestTile3(QSHost host) {
-            super(host);
-        }
-    }
-
-    private class NotAvailableTile extends TestTile {
-
-        protected NotAvailableTile(QSHost host) {
-            super(host);
-        }
-
-        @Override
-        public boolean isAvailable() {
-            return false;
-        }
-    }
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileLifecycleManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileLifecycleManagerTest.java
index 68307b1..c1cf91d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileLifecycleManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/external/TileLifecycleManagerTest.java
@@ -23,6 +23,8 @@
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession;
 import static com.android.systemui.Flags.FLAG_QS_CUSTOM_TILE_CLICK_GUARANTEED_BUG_FIX;
 
+import static com.google.common.truth.Truth.assertThat;
+
 import static junit.framework.Assert.assertFalse;
 import static junit.framework.Assert.assertTrue;
 
@@ -50,6 +52,7 @@
 import android.content.IntentFilter;
 import android.content.ServiceConnection;
 import android.content.pm.PackageInfo;
+import android.content.pm.PackageManager;
 import android.content.pm.ServiceInfo;
 import android.net.Uri;
 import android.os.Bundle;
@@ -183,6 +186,40 @@
                 .thenReturn(defaultPackageInfo);
     }
 
+    private void setPackageInstalledForUser(
+            boolean installed,
+            boolean active,
+            boolean toggleable,
+            int user
+    ) throws Exception {
+        ServiceInfo defaultServiceInfo = null;
+        if (installed) {
+            defaultServiceInfo = new ServiceInfo();
+            defaultServiceInfo.metaData = new Bundle();
+            defaultServiceInfo.metaData.putBoolean(TileService.META_DATA_ACTIVE_TILE, active);
+            defaultServiceInfo.metaData
+                    .putBoolean(TileService.META_DATA_TOGGLEABLE_TILE, toggleable);
+            when(mMockPackageManagerAdapter.getServiceInfo(any(), anyInt(), eq(user)))
+                    .thenReturn(defaultServiceInfo);
+            if (user == 0) {
+                when(mMockPackageManagerAdapter.getServiceInfo(any(), anyInt()))
+                        .thenReturn(defaultServiceInfo);
+            }
+            PackageInfo defaultPackageInfo = new PackageInfo();
+            when(mMockPackageManagerAdapter.getPackageInfoAsUser(anyString(), anyInt(), eq(user)))
+                    .thenReturn(defaultPackageInfo);
+        } else {
+            when(mMockPackageManagerAdapter.getServiceInfo(any(), anyInt(), eq(user)))
+                    .thenReturn(null);
+            if (user == 0) {
+                when(mMockPackageManagerAdapter.getServiceInfo(any(), anyInt()))
+                        .thenThrow(new PackageManager.NameNotFoundException());
+            }
+            when(mMockPackageManagerAdapter.getPackageInfoAsUser(anyString(), anyInt(), eq(user)))
+                    .thenThrow(new PackageManager.NameNotFoundException());
+        }
+    }
+
     private void verifyBind(int times) {
         assertEquals(times > 0, mContext.isBound(mTileServiceComponentName));
     }
@@ -557,6 +594,100 @@
         verify(mockContext).unbindService(captor.getValue());
     }
 
+    @Test
+    public void testIsActive_user0_packageInstalled() throws Exception {
+        setPackageInstalledForUser(true, true, false, 0);
+        mUser = UserHandle.of(0);
+
+        TileLifecycleManager manager = new TileLifecycleManager(mHandler, mWrappedContext,
+                mock(IQSService.class),
+                mMockPackageManagerAdapter,
+                mMockBroadcastDispatcher,
+                mTileServiceIntent,
+                mUser,
+                mActivityManager,
+                mDeviceIdleController,
+                mExecutor);
+
+        assertThat(manager.isActiveTile()).isTrue();
+    }
+
+    @Test
+    public void testIsActive_user10_packageInstalled_notForUser0() throws Exception {
+        setPackageInstalledForUser(true, true, false, 10);
+        setPackageInstalledForUser(false, false, false, 0);
+        mUser = UserHandle.of(10);
+
+        TileLifecycleManager manager = new TileLifecycleManager(mHandler, mWrappedContext,
+                mock(IQSService.class),
+                mMockPackageManagerAdapter,
+                mMockBroadcastDispatcher,
+                mTileServiceIntent,
+                mUser,
+                mActivityManager,
+                mDeviceIdleController,
+                mExecutor);
+
+        assertThat(manager.isActiveTile()).isTrue();
+    }
+
+    @Test
+    public void testIsToggleable_user0_packageInstalled() throws Exception {
+        setPackageInstalledForUser(true, false, true, 0);
+        mUser = UserHandle.of(0);
+
+        TileLifecycleManager manager = new TileLifecycleManager(mHandler, mWrappedContext,
+                mock(IQSService.class),
+                mMockPackageManagerAdapter,
+                mMockBroadcastDispatcher,
+                mTileServiceIntent,
+                mUser,
+                mActivityManager,
+                mDeviceIdleController,
+                mExecutor);
+
+        assertThat(manager.isToggleableTile()).isTrue();
+    }
+
+    @Test
+    public void testIsToggleable_user10_packageInstalled_notForUser0() throws Exception {
+        setPackageInstalledForUser(true, false, true, 10);
+        setPackageInstalledForUser(false, false, false, 0);
+        mUser = UserHandle.of(10);
+
+        TileLifecycleManager manager = new TileLifecycleManager(mHandler, mWrappedContext,
+                mock(IQSService.class),
+                mMockPackageManagerAdapter,
+                mMockBroadcastDispatcher,
+                mTileServiceIntent,
+                mUser,
+                mActivityManager,
+                mDeviceIdleController,
+                mExecutor);
+
+        assertThat(manager.isToggleableTile()).isTrue();
+    }
+
+    @Test
+    public void testIsToggleableActive_installedForDifferentUser() throws Exception {
+        setPackageInstalledForUser(true, false, false, 10);
+        setPackageInstalledForUser(false, true, true, 0);
+        mUser = UserHandle.of(10);
+
+        TileLifecycleManager manager = new TileLifecycleManager(mHandler, mWrappedContext,
+                mock(IQSService.class),
+                mMockPackageManagerAdapter,
+                mMockBroadcastDispatcher,
+                mTileServiceIntent,
+                mUser,
+                mActivityManager,
+                mDeviceIdleController,
+                mExecutor);
+
+        assertThat(manager.isToggleableTile()).isFalse();
+        assertThat(manager.isActiveTile()).isFalse();
+    }
+
     private void mockChangeEnabled(long changeId, boolean enabled) {
         doReturn(enabled).when(() -> CompatChanges.isChangeEnabled(eq(changeId), anyString(),
                 any(UserHandle.class)));
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/shared/QSPipelineFlagsRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/shared/QSPipelineFlagsRepositoryTest.kt
index 970cd17..090a85b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/shared/QSPipelineFlagsRepositoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/shared/QSPipelineFlagsRepositoryTest.kt
@@ -15,20 +15,6 @@
     private val underTest = QSPipelineFlagsRepository()
 
     @Test
-    fun pipelineFlagDisabled() {
-        mSetFlagsRule.disableFlags(Flags.FLAG_QS_NEW_PIPELINE)
-
-        assertThat(underTest.pipelineEnabled).isFalse()
-    }
-
-    @Test
-    fun pipelineFlagEnabled() {
-        mSetFlagsRule.enableFlags(Flags.FLAG_QS_NEW_PIPELINE)
-
-        assertThat(underTest.pipelineEnabled).isTrue()
-    }
-
-    @Test
     fun tilesFlagDisabled() {
         mSetFlagsRule.disableFlags(Flags.FLAG_QS_NEW_TILES)
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogDelegateTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogDelegateTest.java
index ff8c448..643debf 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogDelegateTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/dialog/InternetDialogDelegateTest.java
@@ -6,20 +6,20 @@
 
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyInt;
-import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.clearInvocations;
 import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
-import android.app.AlertDialog;
-import android.content.DialogInterface;
 import android.content.Intent;
 import android.os.Handler;
+import android.telephony.SubscriptionManager;
 import android.telephony.TelephonyManager;
 import android.testing.TestableLooper;
+import android.testing.UiThreadTest;
 import android.view.View;
+import android.view.Window;
 import android.widget.LinearLayout;
 import android.widget.Switch;
 import android.widget.TextView;
@@ -44,20 +44,18 @@
 
 import org.junit.After;
 import org.junit.Before;
-import org.junit.Ignore;
 import org.junit.Test;
 import org.junit.runner.RunWith;
-import org.mockito.ArgumentCaptor;
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
 import org.mockito.MockitoSession;
 
 import java.util.List;
 
-@Ignore("b/257089187")
 @SmallTest
 @RunWith(AndroidJUnit4.class)
 @TestableLooper.RunWithLooper(setAsMainLooper = true)
+@UiThreadTest
 public class InternetDialogDelegateTest extends SysuiTestCase {
 
     private static final String MOBILE_NETWORK_TITLE = "Mobile Title";
@@ -87,6 +85,8 @@
     private SystemUIDialog.Factory mSystemUIDialogFactory;
     @Mock
     private SystemUIDialog mSystemUIDialog;
+    @Mock
+    private Window mWindow;
 
     private FakeExecutor mBgExecutor = new FakeExecutor(new FakeSystemClock());
     private InternetDialogDelegate mInternetDialogDelegate;
@@ -121,13 +121,16 @@
         when(mInternetDialogController.getMobileNetworkSummary(anyInt()))
                 .thenReturn(MOBILE_NETWORK_SUMMARY);
         when(mInternetDialogController.isWifiEnabled()).thenReturn(true);
-
+        when(mInternetDialogController.getActiveAutoSwitchNonDdsSubId()).thenReturn(
+                SubscriptionManager.INVALID_SUBSCRIPTION_ID);
         mMockitoSession = ExtendedMockito.mockitoSession()
                 .spyStatic(WifiEnterpriseRestrictionUtils.class)
                 .startMocking();
         when(WifiEnterpriseRestrictionUtils.isChangeWifiStateAllowed(mContext)).thenReturn(true);
         when(mSystemUIDialogFactory.create(any(SystemUIDialog.Delegate.class)))
                 .thenReturn(mSystemUIDialog);
+        when(mSystemUIDialog.getContext()).thenReturn(mContext);
+        when(mSystemUIDialog.getWindow()).thenReturn(mWindow);
         createInternetDialog();
     }
 
@@ -146,6 +149,8 @@
                 mBgExecutor,
                 mKeyguard,
                 mSystemUIDialogFactory);
+        mInternetDialogDelegate.createDialog();
+        mInternetDialogDelegate.onCreate(mSystemUIDialog, null);
         mInternetDialogDelegate.mAdapter = mInternetAdapter;
         mInternetDialogDelegate.mConnectedWifiEntry = mInternetWifiEntry;
         mInternetDialogDelegate.mWifiEntriesCount = mWifiEntries.size();
@@ -163,10 +168,12 @@
         mSeeAll = mDialogView.requireViewById(R.id.see_all_layout);
         mWifiScanNotify = mDialogView.requireViewById(R.id.wifi_scan_notify_layout);
         mAirplaneModeSummaryText = mDialogView.requireViewById(R.id.airplane_mode_summary);
+        mInternetDialogDelegate.onStart(mSystemUIDialog);
     }
 
     @After
     public void tearDown() {
+        mInternetDialogDelegate.onStop(mSystemUIDialog);
         mInternetDialogDelegate.dismissDialog();
         mMockitoSession.finishMocking();
     }
@@ -191,59 +198,77 @@
     @Test
     public void updateDialog_withApmOn_internetDialogSubTitleGone() {
         when(mInternetDialogController.isAirplaneModeEnabled()).thenReturn(true);
-
         mInternetDialogDelegate.updateDialog(true);
+        mBgExecutor.runAllReady();
 
-        assertThat(mSubTitle.getVisibility()).isEqualTo(View.VISIBLE);
+        mInternetDialogDelegate.mDataInternetContent.observe(
+                mInternetDialogDelegate.mLifecycleOwner, i -> {
+                    assertThat(mSubTitle.getVisibility()).isEqualTo(View.VISIBLE);
+                });
     }
 
     @Test
     public void updateDialog_withApmOff_internetDialogSubTitleVisible() {
         when(mInternetDialogController.isAirplaneModeEnabled()).thenReturn(false);
-
         mInternetDialogDelegate.updateDialog(true);
+        mBgExecutor.runAllReady();
 
-        assertThat(mSubTitle.getVisibility()).isEqualTo(View.VISIBLE);
+        mInternetDialogDelegate.mDataInternetContent.observe(
+                mInternetDialogDelegate.mLifecycleOwner, i -> {
+                    assertThat(mSubTitle.getVisibility()).isEqualTo(View.VISIBLE);
+                });
     }
 
     @Test
     public void updateDialog_apmOffAndHasEthernet_showEthernet() {
         when(mInternetDialogController.isAirplaneModeEnabled()).thenReturn(false);
         when(mInternetDialogController.hasEthernet()).thenReturn(true);
-
         mInternetDialogDelegate.updateDialog(true);
+        mBgExecutor.runAllReady();
 
-        assertThat(mEthernet.getVisibility()).isEqualTo(View.VISIBLE);
+        mInternetDialogDelegate.mDataInternetContent.observe(
+                mInternetDialogDelegate.mLifecycleOwner, i -> {
+                    assertThat(mEthernet.getVisibility()).isEqualTo(View.VISIBLE);
+                });
     }
 
     @Test
     public void updateDialog_apmOffAndNoEthernet_hideEthernet() {
         when(mInternetDialogController.isAirplaneModeEnabled()).thenReturn(false);
         when(mInternetDialogController.hasEthernet()).thenReturn(false);
-
         mInternetDialogDelegate.updateDialog(true);
+        mBgExecutor.runAllReady();
 
-        assertThat(mEthernet.getVisibility()).isEqualTo(View.GONE);
+        mInternetDialogDelegate.mDataInternetContent.observe(
+                mInternetDialogDelegate.mLifecycleOwner, i -> {
+                    assertThat(mEthernet.getVisibility()).isEqualTo(View.GONE);
+                });
     }
 
     @Test
     public void updateDialog_apmOnAndHasEthernet_showEthernet() {
         when(mInternetDialogController.isAirplaneModeEnabled()).thenReturn(true);
         when(mInternetDialogController.hasEthernet()).thenReturn(true);
-
         mInternetDialogDelegate.updateDialog(true);
+        mBgExecutor.runAllReady();
 
-        assertThat(mEthernet.getVisibility()).isEqualTo(View.VISIBLE);
+        mInternetDialogDelegate.mDataInternetContent.observe(
+                mInternetDialogDelegate.mLifecycleOwner, i -> {
+                    assertThat(mEthernet.getVisibility()).isEqualTo(View.VISIBLE);
+                });
     }
 
     @Test
     public void updateDialog_apmOnAndNoEthernet_hideEthernet() {
         when(mInternetDialogController.isAirplaneModeEnabled()).thenReturn(true);
         when(mInternetDialogController.hasEthernet()).thenReturn(false);
-
         mInternetDialogDelegate.updateDialog(true);
+        mBgExecutor.runAllReady();
 
-        assertThat(mEthernet.getVisibility()).isEqualTo(View.GONE);
+        mInternetDialogDelegate.mDataInternetContent.observe(
+                mInternetDialogDelegate.mLifecycleOwner, i -> {
+                    assertThat(mEthernet.getVisibility()).isEqualTo(View.GONE);
+                });
     }
 
     @Test
@@ -252,41 +277,56 @@
         when(mInternetDialogController.isCarrierNetworkActive()).thenReturn(false);
         when(mInternetDialogController.isAirplaneModeEnabled()).thenReturn(false);
         when(mInternetDialogController.hasActiveSubIdOnDds()).thenReturn(false);
-
         mInternetDialogDelegate.updateDialog(true);
+        mBgExecutor.runAllReady();
 
-        assertThat(mMobileDataLayout.getVisibility()).isEqualTo(View.GONE);
+        mInternetDialogDelegate.mDataInternetContent.observe(
+                mInternetDialogDelegate.mLifecycleOwner, i -> {
+                    assertThat(mMobileDataLayout.getVisibility()).isEqualTo(View.GONE);
+                });
     }
 
     @Test
-    public void updateDialog_apmOnWithCarrierNetworkAndWifiStatus_mobileDataLayout() {
-        // Carrier network should be gone if airplane mode ON and Wi-Fi is off.
-        when(mInternetDialogController.isCarrierNetworkActive()).thenReturn(true);
-        when(mInternetDialogController.isAirplaneModeEnabled()).thenReturn(true);
-        when(mInternetDialogController.isWifiEnabled()).thenReturn(false);
-
-        mInternetDialogDelegate.updateDialog(true);
-
-        assertThat(mMobileDataLayout.getVisibility()).isEqualTo(View.GONE);
-
+    public void updateDialog_apmOnWithCarrierNetworkAndWifiStatus_mobileDataLayoutVisible() {
         // Carrier network should be visible if airplane mode ON and Wi-Fi is ON.
         when(mInternetDialogController.isCarrierNetworkActive()).thenReturn(true);
         when(mInternetDialogController.isAirplaneModeEnabled()).thenReturn(true);
         when(mInternetDialogController.isWifiEnabled()).thenReturn(true);
-
         mInternetDialogDelegate.updateDialog(true);
+        mBgExecutor.runAllReady();
 
-        assertThat(mMobileDataLayout.getVisibility()).isEqualTo(View.VISIBLE);
+        mInternetDialogDelegate.mDataInternetContent.observe(
+                mInternetDialogDelegate.mLifecycleOwner, i -> {
+                    assertThat(mMobileDataLayout.getVisibility()).isEqualTo(View.VISIBLE);
+                });
+    }
+
+    @Test
+    public void updateDialog_apmOnWithCarrierNetworkAndWifiStatus_mobileDataLayoutGone() {
+        // Carrier network should be gone if airplane mode ON and Wi-Fi is off.
+        when(mInternetDialogController.isCarrierNetworkActive()).thenReturn(true);
+        when(mInternetDialogController.isAirplaneModeEnabled()).thenReturn(true);
+        when(mInternetDialogController.isWifiEnabled()).thenReturn(false);
+        mInternetDialogDelegate.updateDialog(true);
+        mBgExecutor.runAllReady();
+
+        mInternetDialogDelegate.mDataInternetContent.observe(
+                mInternetDialogDelegate.mLifecycleOwner, i -> {
+                    assertThat(mMobileDataLayout.getVisibility()).isEqualTo(View.GONE);
+                });
     }
 
     @Test
     public void updateDialog_apmOnAndNoCarrierNetwork_mobileDataLayoutGone() {
         when(mInternetDialogController.isCarrierNetworkActive()).thenReturn(false);
         when(mInternetDialogController.isAirplaneModeEnabled()).thenReturn(true);
-
         mInternetDialogDelegate.updateDialog(true);
+        mBgExecutor.runAllReady();
 
-        assertThat(mMobileDataLayout.getVisibility()).isEqualTo(View.GONE);
+        mInternetDialogDelegate.mDataInternetContent.observe(
+                mInternetDialogDelegate.mLifecycleOwner, i -> {
+                    assertThat(mMobileDataLayout.getVisibility()).isEqualTo(View.GONE);
+                });
     }
 
     @Test
@@ -295,11 +335,14 @@
         when(mInternetDialogController.isAirplaneModeEnabled()).thenReturn(true);
         mInternetDialogDelegate.mConnectedWifiEntry = null;
         doReturn(false).when(mInternetDialogController).activeNetworkIsCellular();
-
         mInternetDialogDelegate.updateDialog(true);
+        mBgExecutor.runAllReady();
 
-        assertThat(mMobileDataLayout.getVisibility()).isEqualTo(View.VISIBLE);
-        assertThat(mAirplaneModeSummaryText.getVisibility()).isEqualTo(View.VISIBLE);
+        mInternetDialogDelegate.mDataInternetContent.observe(
+                mInternetDialogDelegate.mLifecycleOwner, i -> {
+                    assertThat(mMobileDataLayout.getVisibility()).isEqualTo(View.VISIBLE);
+                    assertThat(mAirplaneModeSummaryText.getVisibility()).isEqualTo(View.VISIBLE);
+                });
     }
 
     @Test
@@ -308,30 +351,39 @@
         when(mInternetDialogController.isAirplaneModeEnabled()).thenReturn(false);
         mInternetDialogDelegate.mConnectedWifiEntry = null;
         doReturn(false).when(mInternetDialogController).activeNetworkIsCellular();
-
         mInternetDialogDelegate.updateDialog(true);
+        mBgExecutor.runAllReady();
 
-        assertThat(mAirplaneModeSummaryText.getVisibility()).isEqualTo(View.GONE);
+        mInternetDialogDelegate.mDataInternetContent.observe(
+                mInternetDialogDelegate.mLifecycleOwner, i -> {
+                    assertThat(mAirplaneModeSummaryText.getVisibility()).isEqualTo(View.GONE);
+                });
     }
 
     @Test
     public void updateDialog_apmOffAndHasCarrierNetwork_notShowApmSummary() {
         when(mInternetDialogController.isCarrierNetworkActive()).thenReturn(true);
         when(mInternetDialogController.isAirplaneModeEnabled()).thenReturn(false);
-
         mInternetDialogDelegate.updateDialog(true);
+        mBgExecutor.runAllReady();
 
-        assertThat(mAirplaneModeSummaryText.getVisibility()).isEqualTo(View.GONE);
+        mInternetDialogDelegate.mDataInternetContent.observe(
+                mInternetDialogDelegate.mLifecycleOwner, i -> {
+                    assertThat(mAirplaneModeSummaryText.getVisibility()).isEqualTo(View.GONE);
+                });
     }
 
     @Test
     public void updateDialog_apmOnAndNoCarrierNetwork_notShowApmSummary() {
         when(mInternetDialogController.isCarrierNetworkActive()).thenReturn(false);
         when(mInternetDialogController.isAirplaneModeEnabled()).thenReturn(true);
-
         mInternetDialogDelegate.updateDialog(true);
+        mBgExecutor.runAllReady();
 
-        assertThat(mAirplaneModeSummaryText.getVisibility()).isEqualTo(View.GONE);
+        mInternetDialogDelegate.mDataInternetContent.observe(
+                mInternetDialogDelegate.mLifecycleOwner, i -> {
+                    assertThat(mAirplaneModeSummaryText.getVisibility()).isEqualTo(View.GONE);
+                });
     }
 
     @Test
@@ -340,10 +392,13 @@
         when(mInternetDialogController.isCarrierNetworkActive()).thenReturn(true);
         when(mInternetDialogController.isMobileDataEnabled()).thenReturn(true);
         mMobileToggleSwitch.setChecked(false);
-
         mInternetDialogDelegate.updateDialog(true);
+        mBgExecutor.runAllReady();
 
-        assertThat(mMobileToggleSwitch.isChecked()).isTrue();
+        mInternetDialogDelegate.mDataInternetContent.observe(
+                mInternetDialogDelegate.mLifecycleOwner, i -> {
+                    assertThat(mMobileToggleSwitch.isChecked()).isTrue();
+                });
     }
 
     @Test
@@ -352,26 +407,32 @@
         when(mInternetDialogController.isCarrierNetworkActive()).thenReturn(true);
         when(mInternetDialogController.isMobileDataEnabled()).thenReturn(false);
         mMobileToggleSwitch.setChecked(false);
-
         mInternetDialogDelegate.updateDialog(true);
+        mBgExecutor.runAllReady();
 
-        assertThat(mMobileToggleSwitch.isChecked()).isFalse();
+        mInternetDialogDelegate.mDataInternetContent.observe(
+                mInternetDialogDelegate.mLifecycleOwner, i -> {
+                    assertThat(mMobileToggleSwitch.isChecked()).isFalse();
+                });
     }
 
     @Test
     public void updateDialog_wifiOnAndHasInternetWifi_showConnectedWifi() {
-        mInternetDialogDelegate.dismissDialog();
+        when(mInternetDialogController.getActiveAutoSwitchNonDdsSubId()).thenReturn(1);
         doReturn(true).when(mInternetDialogController).hasActiveSubIdOnDds();
-        createInternetDialog();
         // The preconditions WiFi ON and Internet WiFi are already in setUp()
         doReturn(false).when(mInternetDialogController).activeNetworkIsCellular();
 
         mInternetDialogDelegate.updateDialog(true);
+        mBgExecutor.runAllReady();
 
-        assertThat(mConnectedWifi.getVisibility()).isEqualTo(View.VISIBLE);
-        LinearLayout secondaryLayout = mDialogView.requireViewById(
-                R.id.secondary_mobile_network_layout);
-        assertThat(secondaryLayout.getVisibility()).isEqualTo(View.GONE);
+        mInternetDialogDelegate.mDataInternetContent.observe(
+                mInternetDialogDelegate.mLifecycleOwner, i -> {
+                    assertThat(mConnectedWifi.getVisibility()).isEqualTo(View.VISIBLE);
+                    LinearLayout secondaryLayout = mDialogView.requireViewById(
+                            R.id.secondary_mobile_network_layout);
+                    assertThat(secondaryLayout.getVisibility()).isEqualTo(View.GONE);
+                });
     }
 
     @Test
@@ -379,10 +440,13 @@
         // The precondition WiFi ON is already in setUp()
         mInternetDialogDelegate.mConnectedWifiEntry = null;
         doReturn(false).when(mInternetDialogController).activeNetworkIsCellular();
-
         mInternetDialogDelegate.updateDialog(false);
+        mBgExecutor.runAllReady();
 
-        assertThat(mConnectedWifi.getVisibility()).isEqualTo(View.GONE);
+        mInternetDialogDelegate.mDataInternetContent.observe(
+                mInternetDialogDelegate.mLifecycleOwner, i -> {
+                    assertThat(mConnectedWifi.getVisibility()).isEqualTo(View.GONE);
+                });
     }
 
     @Test
@@ -390,14 +454,17 @@
         // The precondition WiFi ON is already in setUp()
         mInternetDialogDelegate.mConnectedWifiEntry = null;
         mInternetDialogDelegate.mWifiEntriesCount = 0;
-
         mInternetDialogDelegate.updateDialog(false);
+        mBgExecutor.runAllReady();
 
-        assertThat(mConnectedWifi.getVisibility()).isEqualTo(View.GONE);
-        // Show a blank block to fix the dialog height even if there is no WiFi list
-        assertThat(mWifiList.getVisibility()).isEqualTo(View.VISIBLE);
-        verify(mInternetAdapter).setMaxEntriesCount(3);
-        assertThat(mSeeAll.getVisibility()).isEqualTo(View.INVISIBLE);
+        mInternetDialogDelegate.mDataInternetContent.observe(
+                mInternetDialogDelegate.mLifecycleOwner, i -> {
+                    assertThat(mConnectedWifi.getVisibility()).isEqualTo(View.GONE);
+                    // Show a blank block to fix the dialog height even if there is no WiFi list
+                    assertThat(mWifiList.getVisibility()).isEqualTo(View.VISIBLE);
+                    verify(mInternetAdapter).setMaxEntriesCount(3);
+                    assertThat(mSeeAll.getVisibility()).isEqualTo(View.INVISIBLE);
+                });
     }
 
     @Test
@@ -405,28 +472,34 @@
         // The precondition WiFi ON is already in setUp()
         mInternetDialogDelegate.mConnectedWifiEntry = null;
         mInternetDialogDelegate.mWifiEntriesCount = 1;
-
         mInternetDialogDelegate.updateDialog(false);
+        mBgExecutor.runAllReady();
 
-        assertThat(mConnectedWifi.getVisibility()).isEqualTo(View.GONE);
-        // Show a blank block to fix the dialog height even if there is no WiFi list
-        assertThat(mWifiList.getVisibility()).isEqualTo(View.VISIBLE);
-        verify(mInternetAdapter).setMaxEntriesCount(3);
-        assertThat(mSeeAll.getVisibility()).isEqualTo(View.INVISIBLE);
+        mInternetDialogDelegate.mDataInternetContent.observe(
+                mInternetDialogDelegate.mLifecycleOwner, i -> {
+                    assertThat(mConnectedWifi.getVisibility()).isEqualTo(View.GONE);
+                    // Show a blank block to fix the dialog height even if there is no WiFi list
+                    assertThat(mWifiList.getVisibility()).isEqualTo(View.VISIBLE);
+                    verify(mInternetAdapter).setMaxEntriesCount(3);
+                    assertThat(mSeeAll.getVisibility()).isEqualTo(View.INVISIBLE);
+                });
     }
 
     @Test
     public void updateDialog_wifiOnAndHasConnectedWifi_showAllWifiAndSeeAllArea() {
         // The preconditions WiFi ON and WiFi entries are already in setUp()
         mInternetDialogDelegate.mWifiEntriesCount = 0;
-
         mInternetDialogDelegate.updateDialog(false);
+        mBgExecutor.runAllReady();
 
-        assertThat(mConnectedWifi.getVisibility()).isEqualTo(View.VISIBLE);
-        // Show a blank block to fix the dialog height even if there is no WiFi list
-        assertThat(mWifiList.getVisibility()).isEqualTo(View.VISIBLE);
-        verify(mInternetAdapter).setMaxEntriesCount(2);
-        assertThat(mSeeAll.getVisibility()).isEqualTo(View.INVISIBLE);
+        mInternetDialogDelegate.mDataInternetContent.observe(
+                mInternetDialogDelegate.mLifecycleOwner, i -> {
+                    assertThat(mConnectedWifi.getVisibility()).isEqualTo(View.VISIBLE);
+                    // Show a blank block to fix the dialog height even if there is no WiFi list
+                    assertThat(mWifiList.getVisibility()).isEqualTo(View.VISIBLE);
+                    verify(mInternetAdapter).setMaxEntriesCount(2);
+                    assertThat(mSeeAll.getVisibility()).isEqualTo(View.INVISIBLE);
+                });
     }
 
     @Test
@@ -435,13 +508,16 @@
         mInternetDialogDelegate.mConnectedWifiEntry = null;
         mInternetDialogDelegate.mWifiEntriesCount = MAX_WIFI_ENTRY_COUNT;
         mInternetDialogDelegate.mHasMoreWifiEntries = true;
-
         mInternetDialogDelegate.updateDialog(false);
+        mBgExecutor.runAllReady();
 
-        assertThat(mConnectedWifi.getVisibility()).isEqualTo(View.GONE);
-        assertThat(mWifiList.getVisibility()).isEqualTo(View.VISIBLE);
-        verify(mInternetAdapter).setMaxEntriesCount(3);
-        assertThat(mSeeAll.getVisibility()).isEqualTo(View.VISIBLE);
+        mInternetDialogDelegate.mDataInternetContent.observe(
+                mInternetDialogDelegate.mLifecycleOwner, i -> {
+                    assertThat(mConnectedWifi.getVisibility()).isEqualTo(View.GONE);
+                    assertThat(mWifiList.getVisibility()).isEqualTo(View.VISIBLE);
+                    verify(mInternetAdapter).setMaxEntriesCount(3);
+                    assertThat(mSeeAll.getVisibility()).isEqualTo(View.VISIBLE);
+                });
     }
 
     @Test
@@ -449,13 +525,16 @@
         // The preconditions WiFi ON and WiFi entries are already in setUp()
         mInternetDialogDelegate.mWifiEntriesCount = MAX_WIFI_ENTRY_COUNT - 1;
         mInternetDialogDelegate.mHasMoreWifiEntries = true;
-
         mInternetDialogDelegate.updateDialog(false);
+        mBgExecutor.runAllReady();
 
-        assertThat(mConnectedWifi.getVisibility()).isEqualTo(View.VISIBLE);
-        assertThat(mWifiList.getVisibility()).isEqualTo(View.VISIBLE);
-        verify(mInternetAdapter).setMaxEntriesCount(2);
-        assertThat(mSeeAll.getVisibility()).isEqualTo(View.VISIBLE);
+        mInternetDialogDelegate.mDataInternetContent.observe(
+                mInternetDialogDelegate.mLifecycleOwner, i -> {
+                    assertThat(mConnectedWifi.getVisibility()).isEqualTo(View.VISIBLE);
+                    assertThat(mWifiList.getVisibility()).isEqualTo(View.VISIBLE);
+                    verify(mInternetAdapter).setMaxEntriesCount(2);
+                    assertThat(mSeeAll.getVisibility()).isEqualTo(View.VISIBLE);
+                });
     }
 
     @Test
@@ -463,32 +542,38 @@
         // The preconditions WiFi entries are already in setUp()
         when(mInternetDialogController.isDeviceLocked()).thenReturn(true);
         mInternetDialogDelegate.mConnectedWifiEntry = null;
-
         mInternetDialogDelegate.updateDialog(false);
+        mBgExecutor.runAllReady();
 
-        // Show WiFi Toggle without background
-        assertThat(mWifiToggle.getVisibility()).isEqualTo(View.VISIBLE);
-        assertThat(mWifiToggle.getBackground()).isNull();
-        // Hide Wi-Fi networks and See all
-        assertThat(mConnectedWifi.getVisibility()).isEqualTo(View.GONE);
-        assertThat(mWifiList.getVisibility()).isEqualTo(View.GONE);
-        assertThat(mSeeAll.getVisibility()).isEqualTo(View.GONE);
+        mInternetDialogDelegate.mDataInternetContent.observe(
+                mInternetDialogDelegate.mLifecycleOwner, i -> {
+                    // Show WiFi Toggle without background
+                    assertThat(mWifiToggle.getVisibility()).isEqualTo(View.VISIBLE);
+                    assertThat(mWifiToggle.getBackground()).isNull();
+                    // Hide Wi-Fi networks and See all
+                    assertThat(mConnectedWifi.getVisibility()).isEqualTo(View.GONE);
+                    assertThat(mWifiList.getVisibility()).isEqualTo(View.GONE);
+                    assertThat(mSeeAll.getVisibility()).isEqualTo(View.GONE);
+                });
     }
 
     @Test
     public void updateDialog_deviceLockedAndHasConnectedWifi_showWifiToggleWithBackground() {
         // The preconditions WiFi ON and WiFi entries are already in setUp()
         when(mInternetDialogController.isDeviceLocked()).thenReturn(true);
-
         mInternetDialogDelegate.updateDialog(false);
+        mBgExecutor.runAllReady();
 
-        // Show WiFi Toggle with highlight background
-        assertThat(mWifiToggle.getVisibility()).isEqualTo(View.VISIBLE);
-        assertThat(mWifiToggle.getBackground()).isNotNull();
-        // Hide Wi-Fi networks and See all
-        assertThat(mConnectedWifi.getVisibility()).isEqualTo(View.GONE);
-        assertThat(mWifiList.getVisibility()).isEqualTo(View.GONE);
-        assertThat(mSeeAll.getVisibility()).isEqualTo(View.GONE);
+        mInternetDialogDelegate.mDataInternetContent.observe(
+                mInternetDialogDelegate.mLifecycleOwner, i -> {
+                    // Show WiFi Toggle with highlight background
+                    assertThat(mWifiToggle.getVisibility()).isEqualTo(View.VISIBLE);
+                    assertThat(mWifiToggle.getBackground()).isNotNull();
+                    // Hide Wi-Fi networks and See all
+                    assertThat(mConnectedWifi.getVisibility()).isEqualTo(View.GONE);
+                    assertThat(mWifiList.getVisibility()).isEqualTo(View.GONE);
+                    assertThat(mSeeAll.getVisibility()).isEqualTo(View.GONE);
+                });
     }
 
     @Test
@@ -496,13 +581,16 @@
         mInternetDialogDelegate.dismissDialog();
         when(WifiEnterpriseRestrictionUtils.isChangeWifiStateAllowed(mContext)).thenReturn(false);
         createInternetDialog();
-
         mInternetDialogDelegate.updateDialog(false);
+        mBgExecutor.runAllReady();
 
-        // Disable Wi-Fi switch and show restriction message in summary.
-        assertThat(mWifiToggleSwitch.isEnabled()).isFalse();
-        assertThat(mWifiToggleSummary.getVisibility()).isEqualTo(View.VISIBLE);
-        assertThat(mWifiToggleSummary.getText().length()).isNotEqualTo(0);
+        mInternetDialogDelegate.mDataInternetContent.observe(
+                mInternetDialogDelegate.mLifecycleOwner, i -> {
+                    // Disable Wi-Fi switch and show restriction message in summary.
+                    assertThat(mWifiToggleSwitch.isEnabled()).isFalse();
+                    assertThat(mWifiToggleSummary.getVisibility()).isEqualTo(View.VISIBLE);
+                    assertThat(mWifiToggleSummary.getText().length()).isNotEqualTo(0);
+                });
     }
 
     @Test
@@ -510,50 +598,38 @@
         mInternetDialogDelegate.dismissDialog();
         when(WifiEnterpriseRestrictionUtils.isChangeWifiStateAllowed(mContext)).thenReturn(true);
         createInternetDialog();
-
         mInternetDialogDelegate.updateDialog(false);
+        mBgExecutor.runAllReady();
 
-        // Enable Wi-Fi switch and hide restriction message in summary.
-        assertThat(mWifiToggleSwitch.isEnabled()).isTrue();
-        assertThat(mWifiToggleSummary.getVisibility()).isEqualTo(View.GONE);
+        mInternetDialogDelegate.mDataInternetContent.observe(
+                mInternetDialogDelegate.mLifecycleOwner, i -> {
+                    // Enable Wi-Fi switch and hide restriction message in summary.
+                    assertThat(mWifiToggleSwitch.isEnabled()).isTrue();
+                    assertThat(mWifiToggleSummary.getVisibility()).isEqualTo(View.GONE);
+                });
     }
 
     @Test
     public void updateDialog_showSecondaryDataSub() {
-        mInternetDialogDelegate.dismissDialog();
+        when(mInternetDialogController.getActiveAutoSwitchNonDdsSubId()).thenReturn(1);
         doReturn(1).when(mInternetDialogController).getActiveAutoSwitchNonDdsSubId();
         doReturn(true).when(mInternetDialogController).hasActiveSubIdOnDds();
         doReturn(false).when(mInternetDialogController).isAirplaneModeEnabled();
-        createInternetDialog();
-
         clearInvocations(mInternetDialogController);
         mInternetDialogDelegate.updateDialog(true);
+        mBgExecutor.runAllReady();
 
-        LinearLayout primaryLayout = mDialogView.requireViewById(
-                R.id.mobile_network_layout);
-        LinearLayout secondaryLayout = mDialogView.requireViewById(
-                R.id.secondary_mobile_network_layout);
+        mInternetDialogDelegate.mDataInternetContent.observe(
+                mInternetDialogDelegate.mLifecycleOwner, i -> {
+                    LinearLayout primaryLayout = mDialogView.requireViewById(
+                            R.id.mobile_network_layout);
+                    LinearLayout secondaryLayout = mDialogView.requireViewById(
+                            R.id.secondary_mobile_network_layout);
 
-        verify(mInternetDialogController).getMobileNetworkSummary(1);
-        assertThat(primaryLayout.getBackground()).isNotEqualTo(secondaryLayout.getBackground());
-
-        // Tap the primary sub info
-        primaryLayout.performClick();
-        ArgumentCaptor<AlertDialog> dialogArgumentCaptor =
-                ArgumentCaptor.forClass(AlertDialog.class);
-        verify(mDialogTransitionAnimator).showFromDialog(dialogArgumentCaptor.capture(),
-                eq(mSystemUIDialog), eq(null), eq(false));
-        AlertDialog dialog = dialogArgumentCaptor.getValue();
-        dialog.show();
-        dialog.getButton(DialogInterface.BUTTON_POSITIVE).performClick();
-        TestableLooper.get(this).processAllMessages();
-        verify(mInternetDialogController).setAutoDataSwitchMobileDataPolicy(1, false);
-
-        // Tap the secondary sub info
-        secondaryLayout.performClick();
-        verify(mInternetDialogController).launchMobileNetworkSettings(any(View.class));
-
-        dialog.dismiss();
+                    verify(mInternetDialogController).getMobileNetworkSummary(1);
+                    assertThat(primaryLayout.getBackground()).isNotEqualTo(
+                            secondaryLayout.getBackground());
+                });
     }
 
     @Test
@@ -561,6 +637,12 @@
         // The preconditions WiFi ON and WiFi entries are already in setUp()
 
         mInternetDialogDelegate.updateDialog(false);
+        mBgExecutor.runAllReady();
+
+        mInternetDialogDelegate.mDataInternetContent.observe(
+                mInternetDialogDelegate.mLifecycleOwner, i -> {
+                    assertThat(mWifiScanNotify.getVisibility()).isEqualTo(View.GONE);
+                });
 
         assertThat(mWifiScanNotify.getVisibility()).isEqualTo(View.GONE);
     }
@@ -569,8 +651,13 @@
     public void updateDialog_wifiOffAndWifiScanOff_hideWifiScanNotify() {
         when(mInternetDialogController.isWifiEnabled()).thenReturn(false);
         when(mInternetDialogController.isWifiScanEnabled()).thenReturn(false);
-
         mInternetDialogDelegate.updateDialog(false);
+        mBgExecutor.runAllReady();
+
+        mInternetDialogDelegate.mDataInternetContent.observe(
+                mInternetDialogDelegate.mLifecycleOwner, i -> {
+                    assertThat(mWifiScanNotify.getVisibility()).isEqualTo(View.GONE);
+                });
 
         assertThat(mWifiScanNotify.getVisibility()).isEqualTo(View.GONE);
     }
@@ -580,8 +667,13 @@
         when(mInternetDialogController.isWifiEnabled()).thenReturn(false);
         when(mInternetDialogController.isWifiScanEnabled()).thenReturn(true);
         when(mInternetDialogController.isDeviceLocked()).thenReturn(true);
-
         mInternetDialogDelegate.updateDialog(false);
+        mBgExecutor.runAllReady();
+
+        mInternetDialogDelegate.mDataInternetContent.observe(
+                mInternetDialogDelegate.mLifecycleOwner, i -> {
+                    assertThat(mWifiScanNotify.getVisibility()).isEqualTo(View.GONE);
+                });
 
         assertThat(mWifiScanNotify.getVisibility()).isEqualTo(View.GONE);
     }
@@ -591,33 +683,43 @@
         when(mInternetDialogController.isWifiEnabled()).thenReturn(false);
         when(mInternetDialogController.isWifiScanEnabled()).thenReturn(true);
         when(mInternetDialogController.isDeviceLocked()).thenReturn(false);
-
         mInternetDialogDelegate.updateDialog(false);
+        mBgExecutor.runAllReady();
 
-        assertThat(mWifiScanNotify.getVisibility()).isEqualTo(View.VISIBLE);
-        TextView wifiScanNotifyText = mDialogView.requireViewById(R.id.wifi_scan_notify_text);
-        assertThat(wifiScanNotifyText.getText().length()).isNotEqualTo(0);
-        assertThat(wifiScanNotifyText.getMovementMethod()).isNotNull();
+        mInternetDialogDelegate.mDataInternetContent.observe(
+                mInternetDialogDelegate.mLifecycleOwner, i -> {
+                    assertThat(mWifiScanNotify.getVisibility()).isEqualTo(View.VISIBLE);
+                    TextView wifiScanNotifyText = mDialogView.requireViewById(
+                            R.id.wifi_scan_notify_text);
+                    assertThat(wifiScanNotifyText.getText().length()).isNotEqualTo(0);
+                    assertThat(wifiScanNotifyText.getMovementMethod()).isNotNull();
+                });
     }
 
     @Test
     public void updateDialog_wifiIsDisabled_uncheckWifiSwitch() {
         when(mInternetDialogController.isWifiEnabled()).thenReturn(false);
         mWifiToggleSwitch.setChecked(true);
-
         mInternetDialogDelegate.updateDialog(false);
+        mBgExecutor.runAllReady();
 
-        assertThat(mWifiToggleSwitch.isChecked()).isFalse();
+        mInternetDialogDelegate.mDataInternetContent.observe(
+                mInternetDialogDelegate.mLifecycleOwner, i -> {
+                    assertThat(mWifiToggleSwitch.isChecked()).isFalse();
+                });
     }
 
     @Test
-    public void updateDialog_wifiIsEnabled_checkWifiSwitch() {
+    public void updateDialog_wifiIsEnabled_checkWifiSwitch() throws Exception {
         when(mInternetDialogController.isWifiEnabled()).thenReturn(true);
         mWifiToggleSwitch.setChecked(false);
-
         mInternetDialogDelegate.updateDialog(false);
+        mBgExecutor.runAllReady();
 
-        assertThat(mWifiToggleSwitch.isChecked()).isTrue();
+        mInternetDialogDelegate.mDataInternetContent.observe(
+                mInternetDialogDelegate.mLifecycleOwner, i -> {
+                    assertThat(mWifiToggleSwitch.isChecked()).isTrue();
+                });
     }
 
     @Test
@@ -699,21 +801,28 @@
     public void updateDialog_shareWifiIntentNull_hideButton() {
         when(mInternetDialogController.getConfiguratorQrCodeGeneratorIntentOrNull(any()))
                 .thenReturn(null);
-
         mInternetDialogDelegate.updateDialog(false);
+        mBgExecutor.runAllReady();
 
-        assertThat(mInternetDialogDelegate.mShareWifiButton.getVisibility()).isEqualTo(View.GONE);
+        mInternetDialogDelegate.mDataInternetContent.observe(
+                mInternetDialogDelegate.mLifecycleOwner, i -> {
+                    assertThat(mInternetDialogDelegate.mShareWifiButton.getVisibility()).isEqualTo(
+                            View.GONE);
+                });
     }
 
     @Test
     public void updateDialog_shareWifiShareable_showButton() {
         when(mInternetDialogController.getConfiguratorQrCodeGeneratorIntentOrNull(any()))
                 .thenReturn(new Intent());
-
         mInternetDialogDelegate.updateDialog(false);
+        mBgExecutor.runAllReady();
 
-        assertThat(mInternetDialogDelegate.mShareWifiButton.getVisibility())
-                .isEqualTo(View.VISIBLE);
+        mInternetDialogDelegate.mDataInternetContent.observe(
+                mInternetDialogDelegate.mLifecycleOwner, i -> {
+                    assertThat(mInternetDialogDelegate.mShareWifiButton.getVisibility())
+                            .isEqualTo(View.VISIBLE);
+                });
     }
 
     private void setNetworkVisible(boolean ethernetVisible, boolean mobileDataVisible,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shared/system/RemoteTransitionTest.java b/packages/SystemUI/tests/src/com/android/systemui/shared/system/RemoteTransitionTest.java
index 660e8da..39e4fc9 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shared/system/RemoteTransitionTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/shared/system/RemoteTransitionTest.java
@@ -30,7 +30,7 @@
 import static android.window.TransitionInfo.FLAG_TRANSLUCENT;
 
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
-import static com.android.wm.shell.common.split.SplitScreenConstants.FLAG_IS_DIVIDER_BAR;
+import static com.android.wm.shell.shared.split.SplitScreenConstants.FLAG_IS_DIVIDER_BAR;
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertTrue;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationTransitionAnimatorControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationTransitionAnimatorControllerTest.kt
index 3abdf62..cb92b77 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationTransitionAnimatorControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/NotificationTransitionAnimatorControllerTest.kt
@@ -91,7 +91,12 @@
         assertFalse(isExpandAnimationRunning!!)
 
         verify(headsUpManager)
-            .removeNotification(notificationKey, true /* releaseImmediately */, true /* animate */)
+            .removeNotification(
+                notificationKey,
+                /* releaseImmediately= */ true,
+                /* animate= */ true,
+                /* reason= */ "onIntentStarted(willAnimate=false)"
+            )
         verify(onFinishAnimationCallback).run()
     }
 
@@ -109,7 +114,12 @@
         assertFalse(isExpandAnimationRunning!!)
 
         verify(headsUpManager)
-            .removeNotification(notificationKey, true /* releaseImmediately */, true /* animate */)
+            .removeNotification(
+                notificationKey,
+                /* releaseImmediately= */ true,
+                /* animate= */ true,
+                /* reason= */ "onLaunchAnimationCancelled()"
+            )
         verify(onFinishAnimationCallback).run()
     }
 
@@ -127,7 +137,12 @@
         assertFalse(isExpandAnimationRunning!!)
 
         verify(headsUpManager)
-            .removeNotification(notificationKey, true /* releaseImmediately */, false /* animate */)
+            .removeNotification(
+                notificationKey,
+                /* releaseImmediately= */ true,
+                /* animate= */ false,
+                /* reason= */ "onLaunchAnimationEnd()"
+            )
         verify(onFinishAnimationCallback).run()
     }
 
@@ -161,12 +176,18 @@
         controller.onTransitionAnimationEnd(isExpandingFullyAbove = true)
 
         verify(headsUpManager)
-            .removeNotification(summary.key, true /* releaseImmediately */, false /* animate */)
+            .removeNotification(
+                summary.key,
+                /* releaseImmediately= */ true,
+                /* animate= */ false,
+                /* reason= */ "onLaunchAnimationEnd()"
+            )
         verify(headsUpManager, never())
             .removeNotification(
                 notification.entry.key,
-                true /* releaseImmediately */,
-                false /* animate */
+                /* releaseImmediately= */ true,
+                /* animate= */ false,
+                /* reason= */ "onLaunchAnimationEnd()"
             )
     }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinatorTest.kt
index 8e9323f..b4f4138 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinatorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/HeadsUpCoordinatorTest.kt
@@ -108,30 +108,31 @@
     private val executor = FakeExecutor(systemClock)
     private val huns: ArrayList<NotificationEntry> = ArrayList()
     private lateinit var helper: NotificationGroupTestHelper
+
     @Before
     fun setUp() {
         MockitoAnnotations.initMocks(this)
         helper = NotificationGroupTestHelper(mContext)
-        coordinator = HeadsUpCoordinator(
-            logger,
-            systemClock,
-            headsUpManager,
-            headsUpViewBinder,
-            visualInterruptionDecisionProvider,
-            remoteInputManager,
-            launchFullScreenIntentProvider,
-            flags,
-            headerController,
-            executor)
+        coordinator =
+            HeadsUpCoordinator(
+                logger,
+                systemClock,
+                headsUpManager,
+                headsUpViewBinder,
+                visualInterruptionDecisionProvider,
+                remoteInputManager,
+                launchFullScreenIntentProvider,
+                flags,
+                headerController,
+                executor
+            )
         coordinator.attach(notifPipeline)
 
         // capture arguments:
         collectionListener = withArgCaptor {
             verify(notifPipeline).addCollectionListener(capture())
         }
-        notifPromoter = withArgCaptor {
-            verify(notifPipeline).addPromoter(capture())
-        }
+        notifPromoter = withArgCaptor { verify(notifPipeline).addPromoter(capture()) }
         notifLifetimeExtender = withArgCaptor {
             verify(notifPipeline).addNotificationLifetimeExtender(capture())
         }
@@ -141,9 +142,7 @@
         beforeFinalizeFilterListener = withArgCaptor {
             verify(notifPipeline).addOnBeforeFinalizeFilterListener(capture())
         }
-        onHeadsUpChangedListener = withArgCaptor {
-            verify(headsUpManager).addListener(capture())
-        }
+        onHeadsUpChangedListener = withArgCaptor { verify(headsUpManager).addListener(capture()) }
         actionPressListener = withArgCaptor {
             verify(remoteInputManager).addActionPressListener(capture())
         }
@@ -187,8 +186,8 @@
         assertTrue(notifLifetimeExtender.maybeExtendLifetime(entry, 0))
         executor.advanceClockToLast()
         executor.runAllReady()
-        verify(headsUpManager, times(0)).removeNotification(anyString(), eq(false))
-        verify(headsUpManager, times(1)).removeNotification(anyString(), eq(true))
+        verify(headsUpManager, times(0)).removeNotification(anyString(), eq(false), anyString())
+        verify(headsUpManager, times(1)).removeNotification(anyString(), eq(true), anyString())
     }
 
     @Test
@@ -203,8 +202,8 @@
         executor.advanceClockToLast()
         executor.runAllReady()
         assertTrue(notifLifetimeExtender.maybeExtendLifetime(entry, 0))
-        verify(headsUpManager, times(0)).removeNotification(anyString(), eq(false))
-        verify(headsUpManager, times(0)).removeNotification(anyString(), eq(true))
+        verify(headsUpManager, times(0)).removeNotification(anyString(), eq(false), anyString())
+        verify(headsUpManager, times(0)).removeNotification(anyString(), eq(true), anyString())
     }
 
     @Test
@@ -217,7 +216,7 @@
         notifLifetimeExtender.cancelLifetimeExtension(entry)
         executor.advanceClockToLast()
         executor.runAllReady()
-        verify(headsUpManager, times(0)).removeNotification(anyString(), any())
+        verify(headsUpManager, never()).removeNotification(anyString(), any(), anyString())
     }
 
     @Test
@@ -227,14 +226,14 @@
 
         whenever(headsUpManager.canRemoveImmediately(anyString())).thenReturn(false)
         whenever(headsUpManager.getEarliestRemovalTime(anyString())).thenReturn(1000L)
-        assertTrue(notifLifetimeExtender.maybeExtendLifetime(entry, /* reason = */ 0))
+        assertTrue(notifLifetimeExtender.maybeExtendLifetime(entry, /* reason= */ 0))
 
         actionPressListener.accept(entry)
         executor.runAllReady()
         verify(endLifetimeExtension, times(1)).onEndLifetimeExtension(notifLifetimeExtender, entry)
 
-        collectionListener.onEntryRemoved(entry, /* reason = */ 0)
-        verify(headsUpManager, times(1)).removeNotification(eq(entry.key), any())
+        collectionListener.onEntryRemoved(entry, /* reason= */ 0)
+        verify(headsUpManager, times(1)).removeNotification(eq(entry.key), any(), anyString())
     }
 
     @Test
@@ -248,8 +247,8 @@
         whenever(headsUpManager.canRemoveImmediately(anyString())).thenReturn(true)
         assertFalse(notifLifetimeExtender.maybeExtendLifetime(entry, 0))
 
-        collectionListener.onEntryRemoved(entry, /* reason = */ 0)
-        verify(headsUpManager, times(1)).removeNotification(eq(entry.key), any())
+        collectionListener.onEntryRemoved(entry, /* reason= */ 0)
+        verify(headsUpManager, times(1)).removeNotification(eq(entry.key), any(), anyString())
     }
 
     @Test
@@ -261,8 +260,8 @@
         addHUN(entry)
         executor.advanceClockToLast()
         executor.runAllReady()
-        verify(headsUpManager, times(0)).removeNotification(anyString(), eq(false))
-        verify(headsUpManager, times(0)).removeNotification(anyString(), eq(true))
+        verify(headsUpManager, never()).removeNotification(anyString(), eq(false), anyString())
+        verify(headsUpManager, never()).removeNotification(anyString(), eq(true), anyString())
     }
 
     @Test
@@ -273,8 +272,8 @@
         assertTrue(notifLifetimeExtender.maybeExtendLifetime(entry, 0))
         executor.advanceClockToLast()
         executor.runAllReady()
-        verify(headsUpManager, times(1)).removeNotification(anyString(), eq(false))
-        verify(headsUpManager, times(0)).removeNotification(anyString(), eq(true))
+        verify(headsUpManager, times(1)).removeNotification(anyString(), eq(false), anyString())
+        verify(headsUpManager, never()).removeNotification(anyString(), eq(true), anyString())
     }
 
     @Test
@@ -326,9 +325,8 @@
 
         // THEN only promote the current HUN, mEntry
         assertTrue(notifPromoter.shouldPromoteToTopLevel(entry))
-        assertFalse(notifPromoter.shouldPromoteToTopLevel(NotificationEntryBuilder()
-            .setPkg("test-package2")
-            .build()))
+        val testPackage2 = NotificationEntryBuilder().setPkg("test-package2").build()
+        assertFalse(notifPromoter.shouldPromoteToTopLevel(testPackage2))
     }
 
     @Test
@@ -338,9 +336,9 @@
 
         // THEN only section the current HUN, mEntry
         assertTrue(notifSectioner.isInSection(entry))
-        assertFalse(notifSectioner.isInSection(NotificationEntryBuilder()
-            .setPkg("test-package")
-            .build()))
+        assertFalse(
+            notifSectioner.isInSection(NotificationEntryBuilder().setPkg("test-package").build())
+        )
     }
 
     @Test
@@ -350,10 +348,12 @@
 
         // THEN only the current HUN, mEntry, should be lifetimeExtended
         assertTrue(notifLifetimeExtender.maybeExtendLifetime(entry, /* cancellationReason */ 0))
-        assertFalse(notifLifetimeExtender.maybeExtendLifetime(
-            NotificationEntryBuilder()
-                .setPkg("test-package")
-                .build(), /* cancellationReason */ 0))
+        assertFalse(
+            notifLifetimeExtender.maybeExtendLifetime(
+                NotificationEntryBuilder().setPkg("test-package").build(),
+                /* reason= */ 0
+            )
+        )
     }
 
     @Test
@@ -366,8 +366,9 @@
         beforeFinalizeFilterListener.onBeforeFinalizeFilter(listOf(entry))
         verify(headsUpManager, never()).showNotification(entry)
         withArgCaptor<BindCallback> {
-            verify(headsUpViewBinder).bindHeadsUpView(eq(entry), capture())
-        }.onBindFinished(entry)
+                verify(headsUpViewBinder).bindHeadsUpView(eq(entry), capture())
+            }
+            .onBindFinished(entry)
 
         // THEN we tell the HeadsUpManager to show the notification
         verify(headsUpManager).showNotification(entry)
@@ -430,7 +431,7 @@
         whenever(remoteInputManager.isSpinning(any())).thenReturn(false)
 
         // THEN heads up manager should remove the entry
-        verify(headsUpManager).removeNotification(entry.key, false)
+        verify(headsUpManager).removeNotification(eq(entry.key), eq(false), anyString())
     }
 
     private fun addHUN(entry: NotificationEntry) {
@@ -545,19 +546,22 @@
         collectionListener.onEntryAdded(groupSibling1)
         collectionListener.onEntryAdded(groupSibling2)
 
-        val beforeTransformGroup = GroupEntryBuilder()
-            .setSummary(groupSummary)
-            .setChildren(listOf(groupSibling1, groupPriority, groupSibling2))
-            .build()
+        val beforeTransformGroup =
+            GroupEntryBuilder()
+                .setSummary(groupSummary)
+                .setChildren(listOf(groupSibling1, groupPriority, groupSibling2))
+                .build()
         beforeTransformGroupsListener.onBeforeTransformGroups(listOf(beforeTransformGroup))
         verify(headsUpViewBinder, never()).bindHeadsUpView(any(), any())
 
-        val afterTransformGroup = GroupEntryBuilder()
-            .setSummary(groupSummary)
-            .setChildren(listOf(groupSibling1, groupSibling2))
-            .build()
-        beforeFinalizeFilterListener
-            .onBeforeFinalizeFilter(listOf(groupPriority, afterTransformGroup))
+        val afterTransformGroup =
+            GroupEntryBuilder()
+                .setSummary(groupSummary)
+                .setChildren(listOf(groupSibling1, groupSibling2))
+                .build()
+        beforeFinalizeFilterListener.onBeforeFinalizeFilter(
+            listOf(groupPriority, afterTransformGroup)
+        )
 
         verify(headsUpViewBinder, never()).bindHeadsUpView(eq(groupSummary), any())
         finishBind(groupPriority)
@@ -583,19 +587,22 @@
         collectionListener.onEntryUpdated(groupSibling1)
         collectionListener.onEntryUpdated(groupSibling2)
 
-        val beforeTransformGroup = GroupEntryBuilder()
-            .setSummary(groupSummary)
-            .setChildren(listOf(groupSibling1, groupPriority, groupSibling2))
-            .build()
+        val beforeTransformGroup =
+            GroupEntryBuilder()
+                .setSummary(groupSummary)
+                .setChildren(listOf(groupSibling1, groupPriority, groupSibling2))
+                .build()
         beforeTransformGroupsListener.onBeforeTransformGroups(listOf(beforeTransformGroup))
         verify(headsUpViewBinder, never()).bindHeadsUpView(any(), any())
 
-        val afterTransformGroup = GroupEntryBuilder()
-            .setSummary(groupSummary)
-            .setChildren(listOf(groupSibling1, groupSibling2))
-            .build()
-        beforeFinalizeFilterListener
-            .onBeforeFinalizeFilter(listOf(groupPriority, afterTransformGroup))
+        val afterTransformGroup =
+            GroupEntryBuilder()
+                .setSummary(groupSummary)
+                .setChildren(listOf(groupSibling1, groupSibling2))
+                .build()
+        beforeFinalizeFilterListener.onBeforeFinalizeFilter(
+            listOf(groupPriority, afterTransformGroup)
+        )
 
         verify(headsUpViewBinder, never()).bindHeadsUpView(eq(groupSummary), any())
         finishBind(groupPriority)
@@ -618,19 +625,22 @@
         collectionListener.onEntryUpdated(groupSummary)
         collectionListener.onEntryUpdated(groupPriority)
 
-        val beforeTransformGroup = GroupEntryBuilder()
-            .setSummary(groupSummary)
-            .setChildren(listOf(groupSibling1, groupPriority, groupSibling2))
-            .build()
+        val beforeTransformGroup =
+            GroupEntryBuilder()
+                .setSummary(groupSummary)
+                .setChildren(listOf(groupSibling1, groupPriority, groupSibling2))
+                .build()
         beforeTransformGroupsListener.onBeforeTransformGroups(listOf(beforeTransformGroup))
         verify(headsUpViewBinder, never()).bindHeadsUpView(any(), any())
 
-        val afterTransformGroup = GroupEntryBuilder()
-            .setSummary(groupSummary)
-            .setChildren(listOf(groupSibling1, groupSibling2))
-            .build()
-        beforeFinalizeFilterListener
-            .onBeforeFinalizeFilter(listOf(groupPriority, afterTransformGroup))
+        val afterTransformGroup =
+            GroupEntryBuilder()
+                .setSummary(groupSummary)
+                .setChildren(listOf(groupSibling1, groupSibling2))
+                .build()
+        beforeFinalizeFilterListener.onBeforeFinalizeFilter(
+            listOf(groupPriority, afterTransformGroup)
+        )
 
         verify(headsUpViewBinder, never()).bindHeadsUpView(eq(groupSummary), any())
         finishBind(groupPriority)
@@ -654,19 +664,22 @@
         collectionListener.onEntryUpdated(groupSibling1)
         collectionListener.onEntryUpdated(groupSibling2)
 
-        val beforeTransformGroup = GroupEntryBuilder()
-            .setSummary(groupSummary)
-            .setChildren(listOf(groupSibling1, groupPriority, groupSibling2))
-            .build()
+        val beforeTransformGroup =
+            GroupEntryBuilder()
+                .setSummary(groupSummary)
+                .setChildren(listOf(groupSibling1, groupPriority, groupSibling2))
+                .build()
         beforeTransformGroupsListener.onBeforeTransformGroups(listOf(beforeTransformGroup))
         verify(headsUpViewBinder, never()).bindHeadsUpView(any(), any())
 
-        val afterTransformGroup = GroupEntryBuilder()
-            .setSummary(groupSummary)
-            .setChildren(listOf(groupSibling1, groupSibling2))
-            .build()
-        beforeFinalizeFilterListener
-            .onBeforeFinalizeFilter(listOf(groupPriority, afterTransformGroup))
+        val afterTransformGroup =
+            GroupEntryBuilder()
+                .setSummary(groupSummary)
+                .setChildren(listOf(groupSibling1, groupSibling2))
+                .build()
+        beforeFinalizeFilterListener.onBeforeFinalizeFilter(
+            listOf(groupPriority, afterTransformGroup)
+        )
 
         finishBind(groupSummary)
         verify(headsUpViewBinder, never()).bindHeadsUpView(eq(groupPriority), any())
@@ -688,10 +701,11 @@
         collectionListener.onEntryAdded(groupSummary)
         collectionListener.onEntryAdded(groupSibling1)
         collectionListener.onEntryAdded(groupSibling2)
-        val groupEntry = GroupEntryBuilder()
-            .setSummary(groupSummary)
-            .setChildren(listOf(groupSibling1, groupSibling2))
-            .build()
+        val groupEntry =
+            GroupEntryBuilder()
+                .setSummary(groupSummary)
+                .setChildren(listOf(groupSibling1, groupSibling2))
+                .build()
         beforeTransformGroupsListener.onBeforeTransformGroups(listOf(groupEntry))
         verify(headsUpViewBinder, never()).bindHeadsUpView(any(), any())
         beforeFinalizeFilterListener.onBeforeFinalizeFilter(listOf(groupEntry))
@@ -708,16 +722,16 @@
     @Test
     fun testNoTransferTwoChildAlert_withGroupAlertAll() {
         setShouldHeadsUp(groupSummary)
-        whenever(notifPipeline.allNotifs)
-            .thenReturn(listOf(groupSummary, groupChild1, groupChild2))
+        whenever(notifPipeline.allNotifs).thenReturn(listOf(groupSummary, groupChild1, groupChild2))
 
         collectionListener.onEntryAdded(groupSummary)
         collectionListener.onEntryAdded(groupChild1)
         collectionListener.onEntryAdded(groupChild2)
-        val groupEntry = GroupEntryBuilder()
-            .setSummary(groupSummary)
-            .setChildren(listOf(groupChild1, groupChild2))
-            .build()
+        val groupEntry =
+            GroupEntryBuilder()
+                .setSummary(groupSummary)
+                .setChildren(listOf(groupChild1, groupChild2))
+                .build()
         beforeTransformGroupsListener.onBeforeTransformGroups(listOf(groupEntry))
         verify(headsUpViewBinder, never()).bindHeadsUpView(any(), any())
         beforeFinalizeFilterListener.onBeforeFinalizeFilter(listOf(groupEntry))
@@ -742,10 +756,11 @@
         collectionListener.onEntryAdded(groupSummary)
         collectionListener.onEntryAdded(groupChild1)
         collectionListener.onEntryAdded(groupChild2)
-        val groupEntry = GroupEntryBuilder()
-            .setSummary(groupSummary)
-            .setChildren(listOf(groupChild1, groupChild2))
-            .build()
+        val groupEntry =
+            GroupEntryBuilder()
+                .setSummary(groupSummary)
+                .setChildren(listOf(groupChild1, groupChild2))
+                .build()
         beforeTransformGroupsListener.onBeforeTransformGroups(listOf(groupEntry))
         verify(headsUpViewBinder, never()).bindHeadsUpView(any(), any())
         beforeFinalizeFilterListener.onBeforeFinalizeFilter(listOf(groupEntry))
@@ -1045,9 +1060,7 @@
             .thenReturn(DecisionImpl.of(should))
     }
 
-    private fun setDefaultShouldFullScreen(
-        originalDecision: FullScreenIntentDecision
-    ) {
+    private fun setDefaultShouldFullScreen(originalDecision: FullScreenIntentDecision) {
         val provider = visualInterruptionDecisionProvider
         whenever(provider.makeUnloggedFullScreenIntentDecision(any())).thenAnswer {
             val entry: NotificationEntry = it.getArgument(0)
@@ -1059,11 +1072,8 @@
         entry: NotificationEntry,
         originalDecision: FullScreenIntentDecision
     ) {
-        whenever(
-            visualInterruptionDecisionProvider.makeUnloggedFullScreenIntentDecision(entry)
-        ).thenAnswer {
-            FullScreenIntentDecisionImpl(entry, originalDecision)
-        }
+        whenever(visualInterruptionDecisionProvider.makeUnloggedFullScreenIntentDecision(entry))
+            .thenAnswer { FullScreenIntentDecisionImpl(entry, originalDecision) }
     }
 
     private fun verifyLoggedFullScreenIntentDecision(
@@ -1089,7 +1099,8 @@
     private fun finishBind(entry: NotificationEntry) {
         verify(headsUpManager, never()).showNotification(entry)
         withArgCaptor<BindCallback> {
-            verify(headsUpViewBinder).bindHeadsUpView(eq(entry), capture())
-        }.onBindFinished(entry)
+                verify(headsUpViewBinder).bindHeadsUpView(eq(entry), capture())
+            }
+            .onBindFinished(entry)
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationSwipeHelperTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationSwipeHelperTest.java
index 2d11917..95db95c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationSwipeHelperTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationSwipeHelperTest.java
@@ -36,6 +36,7 @@
 import android.animation.Animator;
 import android.animation.ValueAnimator.AnimatorUpdateListener;
 import android.os.Handler;
+import android.platform.test.annotations.EnableFlags;
 import android.service.notification.StatusBarNotification;
 import android.testing.TestableLooper;
 import android.view.MotionEvent;
@@ -52,6 +53,7 @@
 import com.android.systemui.plugins.statusbar.NotificationMenuRowPlugin;
 import com.android.systemui.plugins.statusbar.NotificationSwipeActionHelper.SnoozeOption;
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
+import com.android.systemui.statusbar.notification.shared.NotificationContentAlphaOptimization;
 
 import org.junit.Before;
 import org.junit.Rule;
@@ -672,17 +674,32 @@
     }
 
     @Test
-    public void testForceResetSwipeStateDoesNothingIfTranslationIsZero() {
+    @EnableFlags(NotificationContentAlphaOptimization.FLAG_NAME)
+    public void testForceResetSwipeStateDoesNothingIfTranslationIsZeroAndAlphaIsOne() {
         doReturn(FAKE_ROW_WIDTH).when(mNotificationRow).getMeasuredWidth();
         doReturn(0f).when(mNotificationRow).getTranslationX();
+        doReturn(1f).when(mNotificationRow).getAlpha();
 
         mSwipeHelper.forceResetSwipeState(mNotificationRow);
 
         verify(mNotificationRow).getTranslationX();
+        verify(mNotificationRow).getAlpha();
         verifyNoMoreInteractions(mNotificationRow);
     }
 
     @Test
+    @EnableFlags(NotificationContentAlphaOptimization.FLAG_NAME)
+    public void testForceResetSwipeStateResetsAlphaIfTranslationIsZeroAndAlphaNotOne() {
+        doReturn(FAKE_ROW_WIDTH).when(mNotificationRow).getMeasuredWidth();
+        doReturn(0f).when(mNotificationRow).getTranslationX();
+        doReturn(0.5f).when(mNotificationRow).getAlpha();
+
+        mSwipeHelper.forceResetSwipeState(mNotificationRow);
+
+        verify(mNotificationRow).setContentAlpha(eq(1f));
+    }
+
+    @Test
     public void testForceResetSwipeStateResetsTranslationAndAlpha() {
         doReturn(FAKE_ROW_WIDTH).when(mNotificationRow).getMeasuredWidth();
         doReturn(10f).when(mNotificationRow).getTranslationX();
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/AutoTileManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/AutoTileManagerTest.java
deleted file mode 100644
index 665544d..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/AutoTileManagerTest.java
+++ /dev/null
@@ -1,648 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package com.android.systemui.statusbar.phone;
-
-import static com.android.systemui.Flags.FLAG_QS_NEW_PIPELINE;
-import static com.android.systemui.qs.dagger.QSFlagsModule.RBC_AVAILABLE;
-import static com.android.systemui.statusbar.phone.AutoTileManager.DEVICE_CONTROLS;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertNull;
-import static org.junit.Assert.assertTrue;
-import static org.junit.Assert.fail;
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.eq;
-import static org.mockito.ArgumentMatchers.isNotNull;
-import static org.mockito.ArgumentMatchers.isNull;
-import static org.mockito.Mockito.doReturn;
-import static org.mockito.Mockito.inOrder;
-import static org.mockito.Mockito.mock;
-import static org.mockito.Mockito.never;
-import static org.mockito.Mockito.times;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.verifyNoMoreInteractions;
-import static org.mockito.Mockito.when;
-
-import android.content.ComponentName;
-import android.content.Context;
-import android.content.pm.PackageManager;
-import android.hardware.display.ColorDisplayManager;
-import android.hardware.display.NightDisplayListener;
-import android.os.Handler;
-import android.os.UserHandle;
-import android.testing.TestableLooper;
-import android.testing.TestableLooper.RunWithLooper;
-
-import androidx.test.ext.junit.runners.AndroidJUnit4;
-import androidx.test.filters.SmallTest;
-
-import com.android.systemui.SysuiTestCase;
-import com.android.systemui.dagger.NightDisplayListenerModule;
-import com.android.systemui.plugins.qs.QSTile;
-import com.android.systemui.qs.AutoAddTracker;
-import com.android.systemui.qs.QSHost;
-import com.android.systemui.qs.ReduceBrightColorsController;
-import com.android.systemui.qs.UserSettingObserver;
-import com.android.systemui.qs.external.CustomTile;
-import com.android.systemui.res.R;
-import com.android.systemui.statusbar.policy.CastController;
-import com.android.systemui.statusbar.policy.CastDevice;
-import com.android.systemui.statusbar.policy.DataSaverController;
-import com.android.systemui.statusbar.policy.DeviceControlsController;
-import com.android.systemui.statusbar.policy.HotspotController;
-import com.android.systemui.statusbar.policy.SafetyController;
-import com.android.systemui.statusbar.policy.WalletController;
-import com.android.systemui.util.settings.FakeSettings;
-import com.android.systemui.util.settings.SecureSettings;
-
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.Answers;
-import org.mockito.ArgumentCaptor;
-import org.mockito.InOrder;
-import org.mockito.Mock;
-import org.mockito.Mockito;
-import org.mockito.MockitoAnnotations;
-import org.mockito.Spy;
-import org.mockito.stubbing.Answer;
-
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.List;
-
-import javax.inject.Named;
-
-@RunWith(AndroidJUnit4.class)
-@RunWithLooper
-@SmallTest
-public class AutoTileManagerTest extends SysuiTestCase {
-
-    private static final String TEST_SETTING = "setting";
-    private static final String TEST_SPEC = "spec";
-    private static final String TEST_SETTING_COMPONENT = "setting_component";
-    private static final String TEST_COMPONENT = "test_pkg/test_cls";
-    private static final String TEST_CUSTOM_SPEC = "custom(" + TEST_COMPONENT + ")";
-    private static final String TEST_CUSTOM_SAFETY_CLASS = "safety_cls";
-    private static final String TEST_CUSTOM_SAFETY_PKG = "safety_pkg";
-    private static final String TEST_CUSTOM_SAFETY_SPEC = CustomTile.toSpec(new ComponentName(
-            TEST_CUSTOM_SAFETY_PKG, TEST_CUSTOM_SAFETY_CLASS));
-    private static final String SEPARATOR = AutoTileManager.SETTING_SEPARATOR;
-
-    private static final int USER = 0;
-
-    @Mock private QSHost mQsHost;
-    @Mock private AutoAddTracker mAutoAddTracker;
-    @Mock private CastController mCastController;
-    @Mock private HotspotController mHotspotController;
-    @Mock private DataSaverController mDataSaverController;
-    @Mock private ManagedProfileController mManagedProfileController;
-    @Mock private NightDisplayListener mNightDisplayListener;
-    @Mock(answer = Answers.RETURNS_SELF)
-    private NightDisplayListenerModule.Builder mNightDisplayListenerBuilder;
-    @Mock private ReduceBrightColorsController mReduceBrightColorsController;
-    @Mock private DeviceControlsController mDeviceControlsController;
-    @Mock private WalletController mWalletController;
-    @Mock private SafetyController mSafetyController;
-    @Mock(answer = Answers.RETURNS_SELF)
-    private AutoAddTracker.Builder mAutoAddTrackerBuilder;
-    @Mock private Context mUserContext;
-    @Spy private PackageManager mPackageManager;
-    private final boolean mIsReduceBrightColorsAvailable = true;
-
-    private AutoTileManager mAutoTileManager; // under test
-
-    private SecureSettings mSecureSettings;
-    private ManagedProfileController.Callback mManagedProfileCallback;
-
-    @Before
-    public void setUp() throws Exception {
-        MockitoAnnotations.initMocks(this);
-        mSecureSettings = new FakeSettings();
-
-        mSetFlagsRule.disableFlags(FLAG_QS_NEW_PIPELINE);
-
-        mContext.getOrCreateTestableResources().addOverride(
-                R.array.config_quickSettingsAutoAdd,
-                new String[] {
-                        TEST_SETTING + SEPARATOR + TEST_SPEC,
-                        TEST_SETTING_COMPONENT + SEPARATOR + TEST_CUSTOM_SPEC
-                }
-        );
-        mContext.getOrCreateTestableResources().addOverride(
-                com.android.internal.R.bool.config_nightDisplayAvailable, true);
-        mContext.getOrCreateTestableResources().addOverride(
-                R.string.safety_quick_settings_tile_class, TEST_CUSTOM_SAFETY_CLASS);
-
-        when(mAutoAddTrackerBuilder.build()).thenReturn(mAutoAddTracker);
-        when(mQsHost.getUserContext()).thenReturn(mUserContext);
-        when(mUserContext.getUser()).thenReturn(UserHandle.of(USER));
-        mPackageManager = Mockito.spy(mContext.getPackageManager());
-        when(mPackageManager.getPermissionControllerPackageName())
-                .thenReturn(TEST_CUSTOM_SAFETY_PKG);
-        Context context = Mockito.spy(mContext);
-        when(context.getPackageManager()).thenReturn(mPackageManager);
-        when(mNightDisplayListenerBuilder.build()).thenReturn(mNightDisplayListener);
-
-        mAutoTileManager = createAutoTileManager(context);
-        mAutoTileManager.init();
-    }
-
-    @After
-    public void tearDown() {
-        mAutoTileManager.destroy();
-    }
-
-    private AutoTileManager createAutoTileManager(
-            Context context,
-            AutoAddTracker.Builder autoAddTrackerBuilder,
-            HotspotController hotspotController,
-            DataSaverController dataSaverController,
-            ManagedProfileController managedProfileController,
-            NightDisplayListenerModule.Builder nightDisplayListenerBuilder,
-            CastController castController,
-            ReduceBrightColorsController reduceBrightColorsController,
-            DeviceControlsController deviceControlsController,
-            WalletController walletController,
-            SafetyController safetyController,
-            @Named(RBC_AVAILABLE) boolean isReduceBrightColorsAvailable) {
-        return new AutoTileManager(context, autoAddTrackerBuilder, mQsHost,
-                Handler.createAsync(TestableLooper.get(this).getLooper()),
-                mSecureSettings,
-                hotspotController,
-                dataSaverController,
-                managedProfileController,
-                mNightDisplayListenerBuilder,
-                castController,
-                reduceBrightColorsController,
-                deviceControlsController,
-                walletController,
-                safetyController,
-                isReduceBrightColorsAvailable);
-    }
-
-    private AutoTileManager createAutoTileManager(Context context) {
-        return createAutoTileManager(context, mAutoAddTrackerBuilder, mHotspotController,
-                mDataSaverController, mManagedProfileController, mNightDisplayListenerBuilder,
-                mCastController, mReduceBrightColorsController, mDeviceControlsController,
-                mWalletController, mSafetyController, mIsReduceBrightColorsAvailable);
-    }
-
-    @Test
-    public void testCreatedAutoTileManagerIsNotInitialized() {
-        AutoAddTracker.Builder builder = mock(AutoAddTracker.Builder.class, Answers.RETURNS_SELF);
-        AutoAddTracker tracker = mock(AutoAddTracker.class);
-        when(builder.build()).thenReturn(tracker);
-        HotspotController hC = mock(HotspotController.class);
-        DataSaverController dSC = mock(DataSaverController.class);
-        ManagedProfileController mPC = mock(ManagedProfileController.class);
-        NightDisplayListenerModule.Builder nDSB = mock(NightDisplayListenerModule.Builder.class);
-        CastController cC = mock(CastController.class);
-        ReduceBrightColorsController rBC = mock(ReduceBrightColorsController.class);
-        DeviceControlsController dCC = mock(DeviceControlsController.class);
-        WalletController wC = mock(WalletController.class);
-        SafetyController sC = mock(SafetyController.class);
-
-        AutoTileManager manager =
-                createAutoTileManager(mock(Context.class), builder, hC, dSC, mPC, nDSB, cC, rBC,
-                        dCC, wC, sC, true);
-
-        verify(tracker, never()).initialize();
-        verify(hC, never()).addCallback(any());
-        verify(dSC, never()).addCallback(any());
-        verify(mPC, never()).addCallback(any());
-        verifyNoMoreInteractions(nDSB);
-        verify(cC, never()).addCallback(any());
-        verify(rBC, never()).addCallback(any());
-        verify(dCC, never()).setCallback(any());
-        verify(wC, never()).getWalletPosition();
-        verify(sC, never()).addCallback(any());
-        assertNull(manager.getSecureSettingForKey(TEST_SETTING));
-        assertNull(manager.getSecureSettingForKey(TEST_SETTING_COMPONENT));
-    }
-
-    @Test
-    public void testChangeUserWhenNotInitializedThrows() {
-        AutoTileManager manager = createAutoTileManager(mock(Context.class));
-
-        try {
-            manager.changeUser(UserHandle.of(USER + 1));
-            fail();
-        } catch (Exception e) {
-            // This should throw and take this path
-        }
-    }
-
-    @Test
-    public void testChangeUserCallbacksStoppedAndStarted() throws Exception {
-        TestableLooper.get(this).runWithLooper(() ->
-                mAutoTileManager.changeUser(UserHandle.of(USER + 1))
-        );
-
-        InOrder inOrderHotspot = inOrder(mHotspotController);
-        inOrderHotspot.verify(mHotspotController).removeCallback(any());
-        inOrderHotspot.verify(mHotspotController).addCallback(any());
-
-        InOrder inOrderDataSaver = inOrder(mDataSaverController);
-        inOrderDataSaver.verify(mDataSaverController).removeCallback(any());
-        inOrderDataSaver.verify(mDataSaverController).addCallback(any());
-
-        InOrder inOrderManagedProfile = inOrder(mManagedProfileController);
-        inOrderManagedProfile.verify(mManagedProfileController).removeCallback(any());
-        inOrderManagedProfile.verify(mManagedProfileController).addCallback(any());
-
-        if (ColorDisplayManager.isNightDisplayAvailable(mContext)) {
-            InOrder inOrderNightDisplay = inOrder(mNightDisplayListener);
-            inOrderNightDisplay.verify(mNightDisplayListener).setCallback(isNull());
-            inOrderNightDisplay.verify(mNightDisplayListener).setCallback(isNotNull());
-        }
-
-        InOrder inOrderReduceBrightColors = inOrder(mReduceBrightColorsController);
-        inOrderReduceBrightColors.verify(mReduceBrightColorsController).removeCallback(any());
-        inOrderReduceBrightColors.verify(mReduceBrightColorsController).addCallback(any());
-
-        InOrder inOrderCast = inOrder(mCastController);
-        inOrderCast.verify(mCastController).removeCallback(any());
-        inOrderCast.verify(mCastController).addCallback(any());
-
-        InOrder inOrderDevices = inOrder(mDeviceControlsController);
-        inOrderDevices.verify(mDeviceControlsController).removeCallback();
-        inOrderDevices.verify(mDeviceControlsController).setCallback(any());
-
-        verify(mWalletController, times(2)).getWalletPosition();
-
-        InOrder inOrderSafety = inOrder(mSafetyController);
-        inOrderSafety.verify(mSafetyController).removeCallback(any());
-        inOrderSafety.verify(mSafetyController).addCallback(any());
-
-        UserSettingObserver setting = mAutoTileManager.getSecureSettingForKey(TEST_SETTING);
-        assertEquals(USER + 1, setting.getCurrentUser());
-        assertTrue(setting.isListening());
-    }
-
-    @Test
-    public void testChangeUserSomeCallbacksNotAdded() throws Exception {
-        when(mAutoAddTracker.isAdded("hotspot")).thenReturn(true);
-        when(mAutoAddTracker.isAdded("work")).thenReturn(true);
-        when(mAutoAddTracker.isAdded("cast")).thenReturn(true);
-        when(mAutoAddTracker.isAdded(TEST_SPEC)).thenReturn(true);
-
-        TestableLooper.get(this).runWithLooper(() ->
-                mAutoTileManager.changeUser(UserHandle.of(USER + 1))
-        );
-
-        verify(mAutoAddTracker).changeUser(UserHandle.of(USER + 1));
-
-        InOrder inOrderHotspot = inOrder(mHotspotController);
-        inOrderHotspot.verify(mHotspotController).removeCallback(any());
-        inOrderHotspot.verify(mHotspotController, never()).addCallback(any());
-
-        InOrder inOrderDataSaver = inOrder(mDataSaverController);
-        inOrderDataSaver.verify(mDataSaverController).removeCallback(any());
-        inOrderDataSaver.verify(mDataSaverController).addCallback(any());
-
-        InOrder inOrderManagedProfile = inOrder(mManagedProfileController);
-        inOrderManagedProfile.verify(mManagedProfileController).removeCallback(any());
-        inOrderManagedProfile.verify(mManagedProfileController).addCallback(any());
-
-        if (ColorDisplayManager.isNightDisplayAvailable(mContext)) {
-            InOrder inOrderNightDisplay = inOrder(mNightDisplayListener);
-            inOrderNightDisplay.verify(mNightDisplayListener).setCallback(isNull());
-            inOrderNightDisplay.verify(mNightDisplayListener).setCallback(isNotNull());
-        }
-
-        InOrder inOrderReduceBrightColors = inOrder(mReduceBrightColorsController);
-        inOrderReduceBrightColors.verify(mReduceBrightColorsController).removeCallback(any());
-        inOrderReduceBrightColors.verify(mReduceBrightColorsController).addCallback(any());
-
-        InOrder inOrderCast = inOrder(mCastController);
-        inOrderCast.verify(mCastController).removeCallback(any());
-        inOrderCast.verify(mCastController, never()).addCallback(any());
-
-        InOrder inOrderDevices = inOrder(mDeviceControlsController);
-        inOrderDevices.verify(mDeviceControlsController).removeCallback();
-        inOrderDevices.verify(mDeviceControlsController).setCallback(any());
-
-        verify(mWalletController, times(2)).getWalletPosition();
-
-        InOrder inOrderSafety = inOrder(mSafetyController);
-        inOrderSafety.verify(mSafetyController).removeCallback(any());
-        inOrderSafety.verify(mSafetyController).addCallback(any());
-
-        UserSettingObserver setting = mAutoTileManager.getSecureSettingForKey(TEST_SETTING);
-        assertEquals(USER + 1, setting.getCurrentUser());
-        assertFalse(setting.isListening());
-    }
-
-    @Test
-    public void testGetCurrentUserId() throws Exception {
-        assertEquals(USER, mAutoTileManager.getCurrentUserId());
-
-        TestableLooper.get(this).runWithLooper(() ->
-                mAutoTileManager.changeUser(UserHandle.of(USER + 100))
-        );
-
-        assertEquals(USER + 100, mAutoTileManager.getCurrentUserId());
-    }
-
-    @Test
-    public void nightTileAdded_whenActivated() {
-        if (!ColorDisplayManager.isNightDisplayAvailable(mContext)) {
-            return;
-        }
-        mAutoTileManager.mNightDisplayCallback.onActivated(true);
-        verify(mQsHost).addTile("night");
-    }
-
-    @Test
-    public void nightTileNotAdded_whenDeactivated() {
-        if (!ColorDisplayManager.isNightDisplayAvailable(mContext)) {
-            return;
-        }
-        mAutoTileManager.mNightDisplayCallback.onActivated(false);
-        verify(mQsHost, never()).addTile("night");
-    }
-
-    @Test
-    public void nightTileAdded_whenNightModeTwilight() {
-        if (!ColorDisplayManager.isNightDisplayAvailable(mContext)) {
-            return;
-        }
-        mAutoTileManager.mNightDisplayCallback.onAutoModeChanged(
-                ColorDisplayManager.AUTO_MODE_TWILIGHT);
-        verify(mQsHost).addTile("night");
-    }
-
-    @Test
-    public void nightTileAdded_whenNightModeCustom() {
-        if (!ColorDisplayManager.isNightDisplayAvailable(mContext)) {
-            return;
-        }
-        mAutoTileManager.mNightDisplayCallback.onAutoModeChanged(
-                ColorDisplayManager.AUTO_MODE_CUSTOM_TIME);
-        verify(mQsHost).addTile("night");
-    }
-
-    @Test
-    public void nightTileNotAdded_whenNightModeDisabled() {
-        if (!ColorDisplayManager.isNightDisplayAvailable(mContext)) {
-            return;
-        }
-        mAutoTileManager.mNightDisplayCallback.onAutoModeChanged(
-                ColorDisplayManager.AUTO_MODE_DISABLED);
-        verify(mQsHost, never()).addTile("night");
-    }
-
-    @Test
-    public void reduceBrightColorsTileAdded_whenActivated() {
-        mAutoTileManager.mReduceBrightColorsCallback.onActivated(true);
-        verify(mQsHost).addTile("reduce_brightness");
-    }
-
-    @Test
-    public void reduceBrightColorsTileNotAdded_whenDeactivated() {
-        mAutoTileManager.mReduceBrightColorsCallback.onActivated(false);
-        verify(mQsHost, never()).addTile("reduce_brightness");
-    }
-
-    private static List<CastDevice> buildFakeCastDevice(boolean isCasting) {
-        CastDevice.CastState state = isCasting
-                ? CastDevice.CastState.Connected
-                : CastDevice.CastState.Disconnected;
-        return Collections.singletonList(
-                new CastDevice(
-                        "id",
-                        /* name= */ null,
-                        /* description= */ null,
-                        /* state= */ state,
-                        /* origin= */ CastDevice.CastOrigin.MediaProjection,
-                        /* tag= */ null));
-    }
-
-    @Test
-    public void castTileAdded_whenDeviceIsCasting() {
-        doReturn(buildFakeCastDevice(true)).when(mCastController).getCastDevices();
-        mAutoTileManager.mCastCallback.onCastDevicesChanged();
-        verify(mQsHost).addTile("cast");
-    }
-
-    @Test
-    public void castTileNotAdded_whenDeviceIsNotCasting() {
-        doReturn(buildFakeCastDevice(false)).when(mCastController).getCastDevices();
-        mAutoTileManager.mCastCallback.onCastDevicesChanged();
-        verify(mQsHost, never()).addTile("cast");
-    }
-
-    @Test
-    public void testSettingTileAdded_onChanged() {
-        changeValue(TEST_SETTING, 1);
-        verify(mAutoAddTracker).setTileAdded(TEST_SPEC);
-        verify(mQsHost).addTile(TEST_SPEC);
-    }
-
-    @Test
-    public void testSettingTileAddedComponentAtEnd_onChanged() {
-        changeValue(TEST_SETTING_COMPONENT, 1);
-        verify(mAutoAddTracker).setTileAdded(TEST_CUSTOM_SPEC);
-        verify(mQsHost).addTile(ComponentName.unflattenFromString(TEST_COMPONENT)
-            , /* end */ true);
-    }
-
-    @Test
-    public void testSettingTileAdded_onlyOnce() {
-        changeValue(TEST_SETTING, 1);
-        changeValue(TEST_SETTING, 2);
-        verify(mAutoAddTracker).setTileAdded(TEST_SPEC);
-        verify(mQsHost).addTile(TEST_SPEC);
-    }
-
-    @Test
-    public void testSettingTileNotAdded_onChangedTo0() {
-        changeValue(TEST_SETTING, 0);
-        verify(mAutoAddTracker, never()).setTileAdded(TEST_SPEC);
-        verify(mQsHost, never()).addTile(TEST_SPEC);
-    }
-
-    @Test
-    public void testSettingTileNotAdded_ifPreviouslyAdded() {
-        when(mAutoAddTracker.isAdded(TEST_SPEC)).thenReturn(true);
-
-        changeValue(TEST_SETTING, 1);
-        verify(mAutoAddTracker, never()).setTileAdded(TEST_SPEC);
-        verify(mQsHost, never()).addTile(TEST_SPEC);
-    }
-
-    @Test
-    public void testSafetyTileNotAdded_ifPreviouslyAdded() {
-        ComponentName safetyComponent = CustomTile.getComponentFromSpec(TEST_CUSTOM_SAFETY_SPEC);
-        mAutoTileManager.init();
-        verify(mQsHost, times(1)).addTile(safetyComponent, true);
-        when(mAutoAddTracker.isAdded(TEST_CUSTOM_SAFETY_SPEC)).thenReturn(true);
-        mAutoTileManager.init();
-        verify(mQsHost, times(1)).addTile(safetyComponent, true);
-    }
-
-    @Test
-    public void testSafetyTileAdded_onUserChange() {
-        ComponentName safetyComponent = CustomTile.getComponentFromSpec(TEST_CUSTOM_SAFETY_SPEC);
-        mAutoTileManager.init();
-        verify(mQsHost, times(1)).addTile(safetyComponent, true);
-        when(mAutoAddTracker.isAdded(TEST_CUSTOM_SAFETY_SPEC)).thenReturn(false);
-        mAutoTileManager.changeUser(UserHandle.of(USER + 1));
-        verify(mQsHost, times(2)).addTile(safetyComponent, true);
-    }
-
-    @Test
-    public void testSafetyTileRemoved_onSafetyCenterDisable() {
-        ComponentName safetyComponent = CustomTile.getComponentFromSpec(TEST_CUSTOM_SAFETY_SPEC);
-        mAutoTileManager.init();
-        when(mAutoAddTracker.isAdded(TEST_CUSTOM_SAFETY_SPEC)).thenReturn(true);
-        mAutoTileManager.mSafetyCallback.onSafetyCenterEnableChanged(false);
-        verify(mQsHost, times(1)).removeTile(TEST_CUSTOM_SAFETY_SPEC);
-    }
-
-    @Test
-    public void testSafetyTileAdded_onSafetyCenterEnable() {
-        ComponentName safetyComponent = CustomTile.getComponentFromSpec(TEST_CUSTOM_SAFETY_SPEC);
-        mAutoTileManager.init();
-        verify(mQsHost, times(1)).addTile(safetyComponent, true);
-        mAutoTileManager.mSafetyCallback.onSafetyCenterEnableChanged(false);
-        mAutoTileManager.mSafetyCallback.onSafetyCenterEnableChanged(true);
-        verify(mQsHost, times(2)).addTile(safetyComponent, true);
-    }
-
-    @Test
-    public void managedProfileAdded_tileAdded() {
-        when(mAutoAddTracker.isAdded(eq("work"))).thenReturn(false);
-        when(mAutoAddTracker.getRestoredTilePosition(eq("work"))).thenReturn(2);
-        mAutoTileManager = createAutoTileManager(mContext);
-        Mockito.doAnswer((Answer<Object>) invocation -> {
-            mManagedProfileCallback = invocation.getArgument(0);
-            return null;
-        }).when(mManagedProfileController).addCallback(any());
-        mAutoTileManager.init();
-        when(mManagedProfileController.hasActiveProfile()).thenReturn(true);
-
-        mManagedProfileCallback.onManagedProfileChanged();
-
-        verify(mQsHost, times(1)).addTile(eq("work"), eq(2));
-        verify(mAutoAddTracker, times(1)).setTileAdded(eq("work"));
-    }
-
-    @Test
-    public void managedProfileRemoved_tileRemoved() {
-        when(mAutoAddTracker.isAdded(eq("work"))).thenReturn(true);
-        mAutoTileManager = createAutoTileManager(mContext);
-        Mockito.doAnswer((Answer<Object>) invocation -> {
-            mManagedProfileCallback = invocation.getArgument(0);
-            return null;
-        }).when(mManagedProfileController).addCallback(any());
-        mAutoTileManager.init();
-        when(mManagedProfileController.hasActiveProfile()).thenReturn(false);
-
-        mManagedProfileCallback.onManagedProfileChanged();
-
-        verify(mQsHost, times(1)).removeTile(eq("work"));
-        verify(mAutoAddTracker, times(1)).setTileRemoved(eq("work"));
-    }
-
-    @Test
-    public void testAddControlsTileIfNotPresent() {
-        String spec = DEVICE_CONTROLS;
-        when(mAutoAddTracker.isAdded(eq(spec))).thenReturn(false);
-        when(mQsHost.getTiles()).thenReturn(new ArrayList<>());
-
-        mAutoTileManager.init();
-        ArgumentCaptor<DeviceControlsController.Callback> captor =
-                ArgumentCaptor.forClass(DeviceControlsController.Callback.class);
-
-        verify(mDeviceControlsController).setCallback(captor.capture());
-
-        captor.getValue().onControlsUpdate(3);
-        verify(mQsHost).addTile(spec, 3);
-        verify(mAutoAddTracker).setTileAdded(spec);
-    }
-
-    @Test
-    public void testDontAddControlsTileIfPresent() {
-        String spec = DEVICE_CONTROLS;
-        when(mAutoAddTracker.isAdded(eq(spec))).thenReturn(false);
-        when(mQsHost.getTiles()).thenReturn(new ArrayList<>());
-
-        mAutoTileManager.init();
-        ArgumentCaptor<DeviceControlsController.Callback> captor =
-                ArgumentCaptor.forClass(DeviceControlsController.Callback.class);
-
-        verify(mDeviceControlsController).setCallback(captor.capture());
-
-        captor.getValue().removeControlsAutoTracker();
-        verify(mQsHost, never()).addTile(spec, 3);
-        verify(mAutoAddTracker, never()).setTileAdded(spec);
-        verify(mAutoAddTracker).setTileRemoved(spec);
-    }
-
-    @Test
-    public void testRemoveControlsTileFromTrackerWhenRequested() {
-        String spec = "controls";
-        when(mAutoAddTracker.isAdded(eq(spec))).thenReturn(true);
-        QSTile mockTile = mock(QSTile.class);
-        when(mockTile.getTileSpec()).thenReturn(spec);
-        when(mQsHost.getTiles()).thenReturn(List.of(mockTile));
-
-        mAutoTileManager.init();
-        ArgumentCaptor<DeviceControlsController.Callback> captor =
-                ArgumentCaptor.forClass(DeviceControlsController.Callback.class);
-
-        verify(mDeviceControlsController).setCallback(captor.capture());
-
-        captor.getValue().onControlsUpdate(3);
-        verify(mQsHost, never()).addTile(spec, 3);
-        verify(mAutoAddTracker, never()).setTileAdded(spec);
-    }
-
-
-    @Test
-    public void testEmptyArray_doesNotCrash() {
-        mContext.getOrCreateTestableResources().addOverride(
-                R.array.config_quickSettingsAutoAdd, new String[0]);
-        createAutoTileManager(mContext).destroy();
-    }
-
-    @Test
-    public void testMissingConfig_doesNotCrash() {
-        mContext.getOrCreateTestableResources().addOverride(
-                R.array.config_quickSettingsAutoAdd, null);
-        createAutoTileManager(mContext).destroy();
-    }
-
-    @Test
-    public void testUserChange_newNightDisplayListenerCreated() {
-        UserHandle newUser = UserHandle.of(1000);
-        mAutoTileManager.changeUser(newUser);
-        InOrder inOrder = inOrder(mNightDisplayListenerBuilder);
-        inOrder.verify(mNightDisplayListenerBuilder).setUser(newUser.getIdentifier());
-        inOrder.verify(mNightDisplayListenerBuilder).build();
-    }
-
-    // Will only notify if it's listening
-    private void changeValue(String key, int value) {
-        mSecureSettings.putIntForUser(key, value, USER);
-        TestableLooper.get(this).processAllMessages();
-    }
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewControllerTest.kt
index 5b45781..30e7247 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewControllerTest.kt
@@ -33,8 +33,11 @@
 import androidx.test.filters.SmallTest
 import androidx.test.platform.app.InstrumentationRegistry
 import com.android.systemui.SysuiTestCase
+import com.android.systemui.battery.BatteryMeterView
 import com.android.systemui.flags.FeatureFlags
 import com.android.systemui.flags.Flags
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.plugins.fakeDarkIconDispatcher
 import com.android.systemui.res.R
 import com.android.systemui.scene.ui.view.WindowRootView
 import com.android.systemui.shade.ShadeControllerImpl
@@ -42,6 +45,7 @@
 import com.android.systemui.shade.ShadeViewController
 import com.android.systemui.shade.domain.interactor.PanelExpansionInteractor
 import com.android.systemui.statusbar.CommandQueue
+import com.android.systemui.statusbar.policy.Clock
 import com.android.systemui.statusbar.policy.ConfigurationController
 import com.android.systemui.statusbar.window.StatusBarWindowStateController
 import com.android.systemui.unfold.SysUIUnfoldComponent
@@ -70,7 +74,9 @@
 @SmallTest
 @RunWith(AndroidJUnit4::class)
 class PhoneStatusBarViewControllerTest : SysuiTestCase() {
+    private val kosmos = Kosmos()
 
+    private val fakeDarkIconDispatcher = kosmos.fakeDarkIconDispatcher
     @Mock private lateinit var shadeViewController: ShadeViewController
     @Mock private lateinit var panelExpansionInteractor: PanelExpansionInteractor
     @Mock private lateinit var featureFlags: FeatureFlags
@@ -91,6 +97,12 @@
     private lateinit var view: PhoneStatusBarView
     private lateinit var controller: PhoneStatusBarViewController
 
+    private val clockView: Clock
+        get() = view.requireViewById(R.id.clock)
+
+    private val batteryView: BatteryMeterView
+        get() = view.requireViewById(R.id.battery)
+
     private val unfoldConfig = UnfoldConfig()
 
     @Before
@@ -114,16 +126,25 @@
     @Test
     fun onViewAttachedAndDrawn_startListeningConfigurationControllerCallback() {
         val view = createViewMock()
-        val argumentCaptor =
-            ArgumentCaptor.forClass(ConfigurationController.ConfigurationListener::class.java)
+
         InstrumentationRegistry.getInstrumentation().runOnMainSync {
             controller = createAndInitController(view)
         }
 
-        verify(configurationController).addCallback(argumentCaptor.capture())
-        argumentCaptor.value.onDensityOrFontScaleChanged()
+        verify(configurationController).addCallback(any())
+    }
 
-        verify(view).onDensityOrFontScaleChanged()
+    @Test
+    fun onViewAttachedAndDrawn_darkReceiversRegistered() {
+        val view = createViewMock()
+
+        InstrumentationRegistry.getInstrumentation().runOnMainSync {
+            controller = createAndInitController(view)
+        }
+
+        assertThat(fakeDarkIconDispatcher.receivers.size).isEqualTo(2)
+        assertThat(fakeDarkIconDispatcher.receivers).contains(clockView)
+        assertThat(fakeDarkIconDispatcher.receivers).contains(batteryView)
     }
 
     @Test
@@ -158,6 +179,21 @@
     }
 
     @Test
+    fun onViewDetached_darkReceiversUnregistered() {
+        val view = createViewMock()
+
+        InstrumentationRegistry.getInstrumentation().runOnMainSync {
+            controller = createAndInitController(view)
+        }
+
+        assertThat(fakeDarkIconDispatcher.receivers).isNotEmpty()
+
+        controller.onViewDetached()
+
+        assertThat(fakeDarkIconDispatcher.receivers).isEmpty()
+    }
+
+    @Test
     fun handleTouchEventFromStatusBar_panelsNotEnabled_returnsFalseAndNoViewEvent() {
         `when`(centralSurfacesImpl.commandQueuePanelsEnabled).thenReturn(false)
         val returnVal =
@@ -353,7 +389,8 @@
                 shadeLogger,
                 viewUtil,
                 configurationController,
-                mStatusOverlayHoverListenerFactory
+                mStatusOverlayHoverListenerFactory,
+                fakeDarkIconDispatcher,
             )
             .create(view)
             .also { it.init() }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewTest.kt
index abc50bc..575b051 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewTest.kt
@@ -32,10 +32,10 @@
 import android.view.WindowInsets
 import android.widget.FrameLayout
 import androidx.test.filters.SmallTest
+import com.android.systemui.Flags.FLAG_STATUS_BAR_STOP_UPDATING_WINDOW_HEIGHT
 import com.android.systemui.Flags.FLAG_STATUS_BAR_SWIPE_OVER_CHIP
 import com.android.systemui.Gefingerpoken
 import com.android.systemui.SysuiTestCase
-import com.android.systemui.plugins.DarkIconDispatcher
 import com.android.systemui.res.R
 import com.android.systemui.statusbar.window.StatusBarWindowController
 import com.android.systemui.util.mockito.mock
@@ -43,6 +43,7 @@
 import com.google.common.truth.Truth.assertThat
 import org.junit.Before
 import org.junit.Test
+import org.mockito.Mockito.never
 import org.mockito.Mockito.spy
 import org.mockito.Mockito.times
 import org.mockito.Mockito.verify
@@ -64,7 +65,6 @@
             StatusBarContentInsetsProvider::class.java,
             contentInsetsProvider
         )
-        mDependency.injectTestDependency(DarkIconDispatcher::class.java, mock<DarkIconDispatcher>())
         mDependency.injectTestDependency(StatusBarWindowController::class.java, windowController)
         context.ensureTestableResources()
         view = spy(createStatusBarView())
@@ -185,21 +185,40 @@
     }
 
     @Test
-    fun onAttachedToWindow_updatesWindowHeight() {
+    @DisableFlags(FLAG_STATUS_BAR_STOP_UPDATING_WINDOW_HEIGHT)
+    fun onAttachedToWindow_flagOff_updatesWindowHeight() {
         view.onAttachedToWindow()
 
         verify(windowController).refreshStatusBarHeight()
     }
 
     @Test
-    fun onConfigurationChanged_updatesWindowHeight() {
+    @EnableFlags(FLAG_STATUS_BAR_STOP_UPDATING_WINDOW_HEIGHT)
+    fun onAttachedToWindow_flagOn_doesNotUpdateWindowHeight() {
+        view.onAttachedToWindow()
+
+        verify(windowController, never()).refreshStatusBarHeight()
+    }
+
+    @Test
+    @DisableFlags(FLAG_STATUS_BAR_STOP_UPDATING_WINDOW_HEIGHT)
+    fun onConfigurationChanged_flagOff_updatesWindowHeight() {
         view.onConfigurationChanged(Configuration())
 
         verify(windowController).refreshStatusBarHeight()
     }
 
     @Test
-    fun onConfigurationChanged_multipleCalls_updatesWindowHeightMultipleTimes() {
+    @EnableFlags(FLAG_STATUS_BAR_STOP_UPDATING_WINDOW_HEIGHT)
+    fun onConfigurationChanged_flagOn_doesNotUpdateWindowHeight() {
+        view.onConfigurationChanged(Configuration())
+
+        verify(windowController, never()).refreshStatusBarHeight()
+    }
+
+    @Test
+    @DisableFlags(FLAG_STATUS_BAR_STOP_UPDATING_WINDOW_HEIGHT)
+    fun onConfigurationChanged_multipleCalls_flagOff_updatesWindowHeightMultipleTimes() {
         view.onConfigurationChanged(Configuration())
         view.onConfigurationChanged(Configuration())
         view.onConfigurationChanged(Configuration())
@@ -209,6 +228,17 @@
     }
 
     @Test
+    @EnableFlags(FLAG_STATUS_BAR_STOP_UPDATING_WINDOW_HEIGHT)
+    fun onConfigurationChanged_multipleCalls_flagOn_neverUpdatesWindowHeight() {
+        view.onConfigurationChanged(Configuration())
+        view.onConfigurationChanged(Configuration())
+        view.onConfigurationChanged(Configuration())
+        view.onConfigurationChanged(Configuration())
+
+        verify(windowController, never()).refreshStatusBarHeight()
+    }
+
+    @Test
     fun onAttachedToWindow_updatesLeftTopRightPaddingsBasedOnInsets() {
         val insets = Insets.of(/* left= */ 10, /* top= */ 20, /* right= */ 30, /* bottom= */ 40)
         whenever(contentInsetsProvider.getStatusBarContentInsetsForCurrentRotation())
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 9fa392f..7a34e94 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
@@ -434,7 +434,11 @@
         // Then
         verify(mBubblesManager).onUserChangedBubble(entry, false);
 
-        verify(mHeadsUpManager).removeNotification(entry.getKey(), true);
+        verify(mHeadsUpManager).removeNotification(
+                entry.getKey(),
+                /* releaseImmediately= */ true,
+                /* reason= */ "onNotificationBubbleIconClicked"
+        );
 
         verifyNoMoreInteractions(mContentIntent);
         verifyNoMoreInteractions(mShadeController);
@@ -456,7 +460,11 @@
         // Then
         verify(mBubblesManager).onUserChangedBubble(entry, true);
 
-        verify(mHeadsUpManager).removeNotification(entry.getKey(), true);
+        verify(mHeadsUpManager).removeNotification(
+                entry.getKey(),
+                /* releaseImmediately= */ true,
+                /* reason= */ "onNotificationBubbleIconClicked"
+        );
 
         verify(mContentIntent, atLeastOnce()).isActivity();
         verifyNoMoreInteractions(mContentIntent);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/ui/dialog/ModesDialogDelegateTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/ui/dialog/ModesDialogDelegateTest.kt
index bf0a39b..06b3b57 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/ui/dialog/ModesDialogDelegateTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/ui/dialog/ModesDialogDelegateTest.kt
@@ -57,6 +57,7 @@
     private val activityStarter = kosmos.activityStarter
     private val mockDialogTransitionAnimator = kosmos.mockDialogTransitionAnimator
     private val mockAnimationController = kosmos.mockActivityTransitionAnimatorController
+    private val mockDialogEventLogger = kosmos.mockModesDialogEventLogger
     private lateinit var underTest: ModesDialogDelegate
 
     @Before
@@ -75,6 +76,7 @@
                 mockDialogTransitionAnimator,
                 activityStarter,
                 { kosmos.modesDialogViewModel },
+                mockDialogEventLogger,
                 kosmos.mainCoroutineContext,
             )
     }
@@ -121,4 +123,12 @@
 
         assertThat(underTest.currentDialog).isNull()
     }
+
+    @Test
+    fun openSettings_logsEvent() =
+        testScope.runTest {
+            val dialog: SystemUIDialog = mock()
+            underTest.openSettings(dialog)
+            verify(mockDialogEventLogger).logDialogSettings()
+        }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogControllerImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogControllerImplTest.java
index 8b7d921..4ea1a0c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogControllerImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/volume/VolumeDialogControllerImplTest.java
@@ -229,6 +229,32 @@
     }
 
     @Test
+    public void testVolumeChangeW_inAudioSharing_doStateChanged() {
+        ArgumentCaptor<VolumeDialogController.State> stateCaptor =
+                ArgumentCaptor.forClass(VolumeDialogController.State.class);
+        mVolumeController.setDeviceInteractive(false);
+        when(mWakefullnessLifcycle.getWakefulness())
+                .thenReturn(WakefulnessLifecycle.WAKEFULNESS_AWAKE);
+        // For now, mAudioManager.getDevicesForStream returns DEVICE_NONE during audio sharing
+        when(mAudioManager.getDevicesForStream(AudioManager.STREAM_MUSIC))
+                .thenReturn(AudioManager.DEVICE_NONE);
+
+        mVolumeController.mInAudioSharing = true;
+        mVolumeController.onVolumeChangedW(AudioManager.STREAM_MUSIC, AudioManager.FLAG_SHOW_UI);
+        verify(mCallback).onStateChanged(stateCaptor.capture());
+        assertThat(stateCaptor.getValue().states.contains(AudioManager.STREAM_MUSIC)).isTrue();
+        assertThat(stateCaptor.getValue().states.get(AudioManager.STREAM_MUSIC).routedToBluetooth)
+                .isTrue();
+
+        mVolumeController.mInAudioSharing = false;
+        mVolumeController.onVolumeChangedW(AudioManager.STREAM_MUSIC, AudioManager.FLAG_SHOW_UI);
+        verify(mCallback, times(2)).onStateChanged(stateCaptor.capture());
+        assertThat(stateCaptor.getValue().states.contains(AudioManager.STREAM_MUSIC)).isTrue();
+        assertThat(stateCaptor.getValue().states.get(AudioManager.STREAM_MUSIC).routedToBluetooth)
+                .isFalse();
+    }
+
+    @Test
     public void testOnRemoteVolumeChanged_newStream_noNullPointer() {
         MediaSession.Token token = new MediaSession.Token(Process.myUid(), null);
         mVolumeController.mMediaSessionsCallbacksW.onRemoteVolumeChanged(token, 0);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/wallpapers/ImageWallpaperTest.java b/packages/SystemUI/tests/src/com/android/systemui/wallpapers/ImageWallpaperTest.java
index 6fb70de..60a15915f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/wallpapers/ImageWallpaperTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/wallpapers/ImageWallpaperTest.java
@@ -223,8 +223,11 @@
     }
 
     private void setBitmapDimensions(int bitmapWidth, int bitmapHeight) {
+        // TODO(b/281648899) remove the when(mWallpaperManager.peekBitmapDimensions(...))
         when(mWallpaperManager.peekBitmapDimensions(anyInt(), anyBoolean()))
                 .thenReturn(new Rect(0, 0, bitmapWidth, bitmapHeight));
+        when(mWallpaperManager.peekBitmapDimensionsAsUser(anyInt(), anyBoolean(), anyInt()))
+                .thenReturn(new Rect(0, 0, bitmapWidth, bitmapHeight));
         when(mWallpaperBitmap.getWidth()).thenReturn(bitmapWidth);
         when(mWallpaperBitmap.getHeight()).thenReturn(bitmapHeight);
     }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubbleEducationControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubbleEducationControllerTest.kt
index 2021f02..55aff13 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubbleEducationControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubbleEducationControllerTest.kt
@@ -123,11 +123,12 @@
                 /* taskId= */ 0,
                 "locus",
                 /* isDismissable= */ true,
+                directExecutor(),
                 directExecutor()
             ) {}
         } else {
             val intent = Intent(Intent.ACTION_VIEW).setPackage(mContext.packageName)
-            Bubble.createAppBubble(intent, UserHandle(1), null, directExecutor())
+            Bubble.createAppBubble(intent, UserHandle(1), null, directExecutor(), directExecutor())
         }
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
index e5e04dc..9dd3e53 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
@@ -459,7 +459,7 @@
                 mContext.getSystemService(WindowManager.class));
         mPositioner.setMaxBubbles(5);
         mBubbleData = new BubbleData(mContext, mBubbleLogger, mPositioner, mEducationController,
-                syncExecutor);
+                syncExecutor, syncExecutor);
 
         when(mUserManager.getProfiles(ActivityManager.getCurrentUser())).thenReturn(
                 Collections.singletonList(mock(UserInfo.class)));
@@ -2465,9 +2465,10 @@
         workEntry.setBubbleMetadata(getMetadata());
         workEntry.setFlagBubble(true);
 
+        SyncExecutor executor = new SyncExecutor();
         return new Bubble(mBubblesManager.notifToBubbleEntry(workEntry),
                 null,
-                mock(Bubbles.PendingIntentCanceledListener.class), new SyncExecutor());
+                mock(Bubbles.PendingIntentCanceledListener.class), executor, executor);
     }
 
     private BubbleEntry createBubbleEntry(boolean isConversation) {
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/bouncer/ui/viewmodel/BouncerMessageViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/bouncer/ui/viewmodel/BouncerMessageViewModelKosmos.kt
index e70631e..e8612d08 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/bouncer/ui/viewmodel/BouncerMessageViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/bouncer/ui/viewmodel/BouncerMessageViewModelKosmos.kt
@@ -14,6 +14,8 @@
  * limitations under the License.
  */
 
+@file:OptIn(ExperimentalCoroutinesApi::class)
+
 package com.android.systemui.bouncer.ui.viewmodel
 
 import android.content.applicationContext
@@ -26,26 +28,31 @@
 import com.android.systemui.deviceentry.domain.interactor.deviceEntryFaceAuthInteractor
 import com.android.systemui.deviceentry.domain.interactor.deviceUnlockedInteractor
 import com.android.systemui.kosmos.Kosmos
-import com.android.systemui.kosmos.testScope
+import com.android.systemui.kosmos.Kosmos.Fixture
 import com.android.systemui.user.ui.viewmodel.userSwitcherViewModel
 import com.android.systemui.util.time.systemClock
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 
-@ExperimentalCoroutinesApi
-val Kosmos.bouncerMessageViewModel by
-    Kosmos.Fixture {
-        BouncerMessageViewModel(
-            applicationContext = applicationContext,
-            applicationScope = testScope.backgroundScope,
-            bouncerInteractor = bouncerInteractor,
-            simBouncerInteractor = simBouncerInteractor,
-            authenticationInteractor = authenticationInteractor,
-            selectedUser = userSwitcherViewModel.selectedUser,
-            clock = systemClock,
-            biometricMessageInteractor = biometricMessageInteractor,
-            faceAuthInteractor = deviceEntryFaceAuthInteractor,
-            deviceUnlockedInteractor = deviceUnlockedInteractor,
-            deviceEntryBiometricsAllowedInteractor = deviceEntryBiometricsAllowedInteractor,
-            flags = composeBouncerFlags,
-        )
+val Kosmos.bouncerMessageViewModel by Fixture {
+    BouncerMessageViewModel(
+        applicationContext = applicationContext,
+        bouncerInteractor = bouncerInteractor,
+        simBouncerInteractor = simBouncerInteractor,
+        authenticationInteractor = authenticationInteractor,
+        userSwitcherViewModel = userSwitcherViewModel,
+        clock = systemClock,
+        biometricMessageInteractor = biometricMessageInteractor,
+        faceAuthInteractor = deviceEntryFaceAuthInteractor,
+        deviceUnlockedInteractor = deviceUnlockedInteractor,
+        deviceEntryBiometricsAllowedInteractor = deviceEntryBiometricsAllowedInteractor,
+        flags = composeBouncerFlags,
+    )
+}
+
+val Kosmos.bouncerMessageViewModelFactory by Fixture {
+    object : BouncerMessageViewModel.Factory {
+        override fun create(): BouncerMessageViewModel {
+            return bouncerMessageViewModel
+        }
     }
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModelKosmos.kt
index c3dad74..e405d17 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModelKosmos.kt
@@ -21,6 +21,7 @@
 import android.app.admin.devicePolicyManager
 import android.content.applicationContext
 import com.android.systemui.authentication.domain.interactor.authenticationInteractor
+import com.android.systemui.authentication.shared.model.AuthenticationMethodModel
 import com.android.systemui.bouncer.domain.interactor.bouncerActionButtonInteractor
 import com.android.systemui.bouncer.domain.interactor.bouncerInteractor
 import com.android.systemui.bouncer.domain.interactor.simBouncerInteractor
@@ -28,28 +29,97 @@
 import com.android.systemui.inputmethod.domain.interactor.inputMethodInteractor
 import com.android.systemui.kosmos.Kosmos
 import com.android.systemui.kosmos.Kosmos.Fixture
-import com.android.systemui.kosmos.testDispatcher
-import com.android.systemui.kosmos.testScope
 import com.android.systemui.user.domain.interactor.selectedUserInteractor
 import com.android.systemui.user.ui.viewmodel.userSwitcherViewModel
 import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.StateFlow
 
-val Kosmos.bouncerViewModel by Fixture {
-    BouncerViewModel(
-        applicationContext = applicationContext,
-        applicationScope = testScope.backgroundScope,
-        mainDispatcher = testDispatcher,
+val Kosmos.bouncerSceneActionsViewModel by Fixture {
+    BouncerSceneActionsViewModel(
         bouncerInteractor = bouncerInteractor,
-        inputMethodInteractor = inputMethodInteractor,
-        simBouncerInteractor = simBouncerInteractor,
-        authenticationInteractor = authenticationInteractor,
-        selectedUserInteractor = selectedUserInteractor,
-        devicePolicyManager = devicePolicyManager,
-        bouncerMessageViewModel = bouncerMessageViewModel,
-        flags = composeBouncerFlags,
-        selectedUser = userSwitcherViewModel.selectedUser,
-        users = userSwitcherViewModel.users,
-        userSwitcherMenu = userSwitcherViewModel.menu,
-        actionButton = bouncerActionButtonInteractor.actionButton,
     )
 }
+
+val Kosmos.bouncerSceneActionsViewModelFactory by Fixture {
+    object : BouncerSceneActionsViewModel.Factory {
+        override fun create(): BouncerSceneActionsViewModel {
+            return bouncerSceneActionsViewModel
+        }
+    }
+}
+
+val Kosmos.bouncerSceneContentViewModel by Fixture {
+    BouncerSceneContentViewModel(
+        applicationContext = applicationContext,
+        bouncerInteractor = bouncerInteractor,
+        authenticationInteractor = authenticationInteractor,
+        devicePolicyManager = devicePolicyManager,
+        bouncerMessageViewModelFactory = bouncerMessageViewModelFactory,
+        flags = composeBouncerFlags,
+        userSwitcher = userSwitcherViewModel,
+        actionButtonInteractor = bouncerActionButtonInteractor,
+        pinViewModelFactory = pinBouncerViewModelFactory,
+        patternViewModelFactory = patternBouncerViewModelFactory,
+        passwordViewModelFactory = passwordBouncerViewModelFactory,
+    )
+}
+
+val Kosmos.bouncerSceneContentViewModelFactory by Fixture {
+    object : BouncerSceneContentViewModel.Factory {
+        override fun create(): BouncerSceneContentViewModel {
+            return bouncerSceneContentViewModel
+        }
+    }
+}
+
+val Kosmos.pinBouncerViewModelFactory by Fixture {
+    object : PinBouncerViewModel.Factory {
+        override fun create(
+            isInputEnabled: StateFlow<Boolean>,
+            onIntentionalUserInput: () -> Unit,
+            authenticationMethod: AuthenticationMethodModel,
+        ): PinBouncerViewModel {
+            return PinBouncerViewModel(
+                applicationContext = applicationContext,
+                interactor = bouncerInteractor,
+                simBouncerInteractor = simBouncerInteractor,
+                isInputEnabled = isInputEnabled,
+                onIntentionalUserInput = onIntentionalUserInput,
+                authenticationMethod = authenticationMethod,
+            )
+        }
+    }
+}
+
+val Kosmos.patternBouncerViewModelFactory by Fixture {
+    object : PatternBouncerViewModel.Factory {
+        override fun create(
+            isInputEnabled: StateFlow<Boolean>,
+            onIntentionalUserInput: () -> Unit,
+        ): PatternBouncerViewModel {
+            return PatternBouncerViewModel(
+                applicationContext = applicationContext,
+                interactor = bouncerInteractor,
+                isInputEnabled = isInputEnabled,
+                onIntentionalUserInput = onIntentionalUserInput,
+            )
+        }
+    }
+}
+
+val Kosmos.passwordBouncerViewModelFactory by Fixture {
+    object : PasswordBouncerViewModel.Factory {
+        override fun create(
+            isInputEnabled: StateFlow<Boolean>,
+            onIntentionalUserInput: () -> Unit,
+        ): PasswordBouncerViewModel {
+            return PasswordBouncerViewModel(
+                interactor = bouncerInteractor,
+                inputMethodInteractor = inputMethodInteractor,
+                selectedUserInteractor = selectedUserInteractor,
+                isInputEnabled = isInputEnabled,
+                onIntentionalUserInput = onIntentionalUserInput,
+            )
+        }
+    }
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalSceneInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalSceneInteractorKosmos.kt
index ee48c10..2ab8221 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalSceneInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/CommunalSceneInteractorKosmos.kt
@@ -17,6 +17,7 @@
 package com.android.systemui.communal.domain.interactor
 
 import com.android.systemui.communal.data.repository.communalSceneRepository
+import com.android.systemui.communal.shared.log.communalSceneLogger
 import com.android.systemui.kosmos.Kosmos
 import com.android.systemui.kosmos.applicationCoroutineScope
 
@@ -24,6 +25,7 @@
     Kosmos.Fixture {
         CommunalSceneInteractor(
             applicationScope = applicationCoroutineScope,
-            communalSceneRepository = communalSceneRepository,
+            repository = communalSceneRepository,
+            logger = communalSceneLogger,
         )
     }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/WidgetTrampolineInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/WidgetTrampolineInteractorKosmos.kt
new file mode 100644
index 0000000..8124224
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/domain/interactor/WidgetTrampolineInteractorKosmos.kt
@@ -0,0 +1,37 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.communal.domain.interactor
+
+import com.android.systemui.common.usagestats.domain.interactor.usageStatsInteractor
+import com.android.systemui.keyguard.domain.interactor.keyguardTransitionInteractor
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.log.logcatLogBuffer
+import com.android.systemui.plugins.activityStarter
+import com.android.systemui.shared.system.taskStackChangeListeners
+import com.android.systemui.util.time.fakeSystemClock
+
+val Kosmos.widgetTrampolineInteractor: WidgetTrampolineInteractor by
+    Kosmos.Fixture {
+        WidgetTrampolineInteractor(
+            activityStarter = activityStarter,
+            systemClock = fakeSystemClock,
+            keyguardTransitionInteractor = keyguardTransitionInteractor,
+            taskStackChangeListeners = taskStackChangeListeners,
+            usageStatsInteractor = usageStatsInteractor,
+            logBuffer = logcatLogBuffer("WidgetTrampolineInteractor"),
+        )
+    }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/communal/shared/log/CommunalSceneLoggerKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/shared/log/CommunalSceneLoggerKosmos.kt
new file mode 100644
index 0000000..b560ee8
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/communal/shared/log/CommunalSceneLoggerKosmos.kt
@@ -0,0 +1,23 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.communal.shared.log
+
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.log.logcatLogBuffer
+
+val Kosmos.communalSceneLogger: CommunalSceneLogger by
+    Kosmos.Fixture { CommunalSceneLogger(logcatLogBuffer("CommunalSceneLogger")) }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/education/data/repository/FakeContextualEducationRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/education/data/repository/FakeContextualEducationRepository.kt
index aa1968a..cdfb297 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/education/data/repository/FakeContextualEducationRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/education/data/repository/FakeContextualEducationRepository.kt
@@ -25,13 +25,13 @@
 class FakeContextualEducationRepository : ContextualEducationRepository {
 
     private val userGestureMap = mutableMapOf<Int, GestureEduModel>()
-    private val _gestureEduModels = MutableStateFlow(GestureEduModel())
+    private val _gestureEduModels = MutableStateFlow(GestureEduModel(userId = 0))
     private val gestureEduModelsFlow = _gestureEduModels.asStateFlow()
     private var currentUser: Int = 0
 
     override fun setUser(userId: Int) {
         if (!userGestureMap.contains(userId)) {
-            userGestureMap[userId] = GestureEduModel()
+            userGestureMap[userId] = GestureEduModel(userId = userId)
         }
         // save data of current user to the map
         userGestureMap[currentUser] = _gestureEduModels.value
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt
index 4571c19..54a6c0c 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeKeyguardRepository.kt
@@ -60,13 +60,13 @@
     override val bottomAreaAlpha: StateFlow<Float> = _bottomAreaAlpha
 
     private val _isKeyguardShowing = MutableStateFlow(false)
-    override val isKeyguardShowing: Flow<Boolean> = _isKeyguardShowing
+    override val isKeyguardShowing: StateFlow<Boolean> = _isKeyguardShowing
 
     private val _isKeyguardUnlocked = MutableStateFlow(false)
     override val isKeyguardDismissible: StateFlow<Boolean> = _isKeyguardUnlocked.asStateFlow()
 
     private val _isKeyguardOccluded = MutableStateFlow(false)
-    override val isKeyguardOccluded: Flow<Boolean> = _isKeyguardOccluded
+    override val isKeyguardOccluded: StateFlow<Boolean> = _isKeyguardOccluded
 
     private val _isDozing = MutableStateFlow(false)
     override val isDozing: StateFlow<Boolean> = _isDozing
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/FromDreamingTransitionInteractorKosmos.kt
index f162594..64ae051 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
@@ -16,6 +16,7 @@
 
 package com.android.systemui.keyguard.domain.interactor
 
+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
@@ -38,6 +39,7 @@
             mainDispatcher = testDispatcher,
             keyguardInteractor = keyguardInteractor,
             glanceableHubTransitions = glanceableHubTransitions,
+            communalSceneInteractor = communalSceneInteractor,
             communalSettingsInteractor = communalSettingsInteractor,
             powerInteractor = powerInteractor,
             keyguardOcclusionInteractor = keyguardOcclusionInteractor,
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToLockscreenTransitionViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToLockscreenTransitionViewModelKosmos.kt
index 450dcc2..d06bab2 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToLockscreenTransitionViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToLockscreenTransitionViewModelKosmos.kt
@@ -19,13 +19,11 @@
 import com.android.systemui.keyguard.ui.keyguardTransitionAnimationFlow
 import com.android.systemui.kosmos.Kosmos
 import com.android.systemui.kosmos.Kosmos.Fixture
-import com.android.systemui.util.mockito.mock
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 
 @ExperimentalCoroutinesApi
 val Kosmos.dreamingToLockscreenTransitionViewModel by Fixture {
     DreamingToLockscreenTransitionViewModel(
-        fromDreamingTransitionInteractor = mock(),
         animationFlow = keyguardTransitionAnimationFlow,
     )
 }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/lifecycle/FakeActivatable.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/lifecycle/FakeActivatable.kt
index e8b2dd2..bcc7393 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/lifecycle/FakeActivatable.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/lifecycle/FakeActivatable.kt
@@ -25,7 +25,7 @@
     var activationCount = 0
     var cancellationCount = 0
 
-    override suspend fun onActivated() {
+    override suspend fun onActivated(): Nothing {
         activationCount++
         onActivation()
         try {
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/lifecycle/FakeSysUiViewModel.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/lifecycle/FakeSysUiViewModel.kt
index 9a56f24..c0bb9a6 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/lifecycle/FakeSysUiViewModel.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/lifecycle/FakeSysUiViewModel.kt
@@ -25,7 +25,7 @@
     var activationCount = 0
     var cancellationCount = 0
 
-    override suspend fun onActivated() {
+    override suspend fun onActivated(): Nothing {
         activationCount++
         onActivation()
         try {
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/plugins/DarkIconDispatcherKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/plugins/DarkIconDispatcherKosmos.kt
new file mode 100644
index 0000000..3d125e9
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/plugins/DarkIconDispatcherKosmos.kt
@@ -0,0 +1,22 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.plugins
+
+import com.android.systemui.kosmos.Kosmos
+
+var Kosmos.fakeDarkIconDispatcher: FakeDarkIconDispatcher by
+    Kosmos.Fixture { FakeDarkIconDispatcher() }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/plugins/FakeDarkIconDispatcher.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/plugins/FakeDarkIconDispatcher.kt
new file mode 100644
index 0000000..102a853
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/plugins/FakeDarkIconDispatcher.kt
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.plugins
+
+import android.graphics.Rect
+import java.util.ArrayList
+
+class FakeDarkIconDispatcher : DarkIconDispatcher {
+    val receivers = mutableListOf<DarkIconDispatcher.DarkReceiver>()
+
+    override fun setIconsDarkArea(r: ArrayList<Rect>) {}
+
+    override fun addDarkReceiver(receiver: DarkIconDispatcher.DarkReceiver) {
+        receivers.add(receiver)
+    }
+
+    override fun removeDarkReceiver(receiver: DarkIconDispatcher.DarkReceiver) {
+        receivers.remove(receiver)
+    }
+
+    override fun applyDark(`object`: DarkIconDispatcher.DarkReceiver) {}
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/FakeFgsManagerController.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/FakeFgsManagerController.kt
index 9ff7dd5..ffe6918 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/FakeFgsManagerController.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/FakeFgsManagerController.kt
@@ -27,6 +27,9 @@
     numRunningPackages: Int = 0,
 ) : FgsManagerController {
 
+    var initialized = false
+        private set
+
     override var numRunningPackages = numRunningPackages
         set(value) {
             if (value != field) {
@@ -53,7 +56,9 @@
         dialogDismissedListeners.forEach { it.onDialogDismissed() }
     }
 
-    override fun init() {}
+    override fun init() {
+        initialized = true
+    }
 
     override fun showDialog(expandable: Expandable?) {}
 
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/composefragment/viewmodel/QSFragmentComposeViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/composefragment/viewmodel/QSFragmentComposeViewModelKosmos.kt
new file mode 100644
index 0000000..d37d8f3
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/composefragment/viewmodel/QSFragmentComposeViewModelKosmos.kt
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.composefragment.viewmodel
+
+import android.content.res.mainResources
+import androidx.lifecycle.LifecycleCoroutineScope
+import com.android.systemui.common.ui.domain.interactor.configurationInteractor
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.qs.footerActionsController
+import com.android.systemui.qs.footerActionsViewModelFactory
+import com.android.systemui.qs.ui.viewmodel.quickSettingsContainerViewModel
+import com.android.systemui.shade.largeScreenHeaderHelper
+import com.android.systemui.shade.transition.largeScreenShadeInterpolator
+import com.android.systemui.statusbar.disableflags.data.repository.disableFlagsRepository
+import com.android.systemui.statusbar.phone.keyguardBypassController
+import com.android.systemui.statusbar.sysuiStatusBarStateController
+
+val Kosmos.qsFragmentComposeViewModelFactory by
+    Kosmos.Fixture {
+        object : QSFragmentComposeViewModel.Factory {
+            override fun create(
+                lifecycleScope: LifecycleCoroutineScope
+            ): QSFragmentComposeViewModel {
+                return QSFragmentComposeViewModel(
+                    quickSettingsContainerViewModel,
+                    mainResources,
+                    footerActionsViewModelFactory,
+                    footerActionsController,
+                    sysuiStatusBarStateController,
+                    keyguardBypassController,
+                    disableFlagsRepository,
+                    largeScreenShadeInterpolator,
+                    configurationInteractor,
+                    largeScreenHeaderHelper,
+                    lifecycleScope,
+                )
+            }
+        }
+    }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/impl/custom/data/repository/FakePackageManagerAdapterFacade.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/impl/custom/data/repository/FakePackageManagerAdapterFacade.kt
index fa8d363..5ac7c39 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/impl/custom/data/repository/FakePackageManagerAdapterFacade.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/impl/custom/data/repository/FakePackageManagerAdapterFacade.kt
@@ -18,14 +18,19 @@
 
 import android.content.ComponentName
 import android.content.pm.PackageInfo
+import android.content.pm.PackageManager
 import android.content.pm.ServiceInfo
 import android.os.Bundle
+import android.os.UserHandle
 import com.android.systemui.qs.external.PackageManagerAdapter
 import com.android.systemui.util.mockito.any
 import com.android.systemui.util.mockito.eq
 import com.android.systemui.util.mockito.mock
 import com.android.systemui.util.mockito.whenever
+import org.hamcrest.BaseMatcher
+import org.hamcrest.Description
 import org.mockito.ArgumentMatchers.anyInt
+import org.mockito.hamcrest.MockitoHamcrest.intThat
 
 /**
  * Facade for [PackageManagerAdapter] to provide a fake-like behaviour. You can create this class
@@ -36,29 +41,26 @@
  * [com.android.systemui.qs.external.TileServiceManager.isToggleableTile] or
  * [com.android.systemui.qs.external.TileServiceManager.isActiveTile] when the real objects are
  * used.
+ *
+ * The user this is set up must be a real user (`user >= 0`) or [UserHandle.USER_ALL].
  */
 class FakePackageManagerAdapterFacade(
     val componentName: ComponentName,
     val packageManagerAdapter: PackageManagerAdapter = mock {},
+    user: Int = UserHandle.USER_ALL,
 ) {
 
     private var isToggleable: Boolean = false
     private var isActive: Boolean = false
 
     init {
-        whenever(packageManagerAdapter.getServiceInfo(eq(componentName), any())).thenAnswer {
-            createServiceInfo()
+        if (user == UserHandle.USER_ALL) {
+            setForAllUsers()
+        } else if (user >= 0) {
+            setExclusiveForUser(user)
+        } else {
+            throw IllegalArgumentException("User must be a real user or UserHandle.USER_ALL")
         }
-        whenever(
-                packageManagerAdapter.getPackageInfoAsUser(
-                    eq(componentName.packageName),
-                    anyInt(),
-                    anyInt()
-                )
-            )
-            .thenAnswer { PackageInfo().apply { packageName = componentName.packageName } }
-        whenever(packageManagerAdapter.getServiceInfo(eq(componentName), anyInt(), anyInt()))
-            .thenAnswer { createServiceInfo() }
     }
 
     private fun createServiceInfo(): ServiceInfo {
@@ -84,4 +86,67 @@
     fun setIsToggleable(isToggleable: Boolean) {
         this.isToggleable = isToggleable
     }
+
+    fun setExclusiveForUser(newUser: Int) {
+        check(newUser >= 0)
+        val notEqualMatcher = NotEqualMatcher(newUser)
+        if (newUser == 0) {
+            whenever(packageManagerAdapter.getServiceInfo(eq(componentName), any())).thenAnswer {
+                createServiceInfo()
+            }
+        }
+        whenever(
+                packageManagerAdapter.getPackageInfoAsUser(
+                    eq(componentName.packageName),
+                    anyInt(),
+                    eq(newUser)
+                )
+            )
+            .thenAnswer { PackageInfo().apply { packageName = componentName.packageName } }
+        whenever(
+                packageManagerAdapter.getPackageInfoAsUser(
+                    eq(componentName.packageName),
+                    anyInt(),
+                    intThat(notEqualMatcher),
+                )
+            )
+            .thenThrow(PackageManager.NameNotFoundException())
+
+        whenever(
+                packageManagerAdapter.getServiceInfo(
+                    eq(componentName),
+                    anyInt(),
+                    intThat(notEqualMatcher)
+                )
+            )
+            .thenAnswer { null }
+        whenever(packageManagerAdapter.getServiceInfo(eq(componentName), anyInt(), eq(newUser)))
+            .thenAnswer { createServiceInfo() }
+    }
+
+    fun setForAllUsers() {
+        whenever(packageManagerAdapter.getServiceInfo(eq(componentName), any())).thenAnswer {
+            createServiceInfo()
+        }
+        whenever(
+                packageManagerAdapter.getPackageInfoAsUser(
+                    eq(componentName.packageName),
+                    anyInt(),
+                    anyInt()
+                )
+            )
+            .thenAnswer { PackageInfo().apply { packageName = componentName.packageName } }
+        whenever(packageManagerAdapter.getServiceInfo(eq(componentName), anyInt(), anyInt()))
+            .thenAnswer { createServiceInfo() }
+    }
+}
+
+private class NotEqualMatcher(private val notEqualValue: Int) : BaseMatcher<Int>() {
+    override fun describeTo(description: Description?) {
+        description?.appendText("!= $notEqualValue")
+    }
+
+    override fun matches(item: Any?): Boolean {
+        return (item as? Int)?.equals(notEqualValue)?.not() ?: true
+    }
 }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/shared/system/TaskStackChangeListenersKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/shared/system/TaskStackChangeListenersKosmos.kt
new file mode 100644
index 0000000..67f611a
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/shared/system/TaskStackChangeListenersKosmos.kt
@@ -0,0 +1,22 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.shared.system
+
+import com.android.systemui.kosmos.Kosmos
+
+val Kosmos.taskStackChangeListeners: TaskStackChangeListeners by
+    Kosmos.Fixture { TaskStackChangeListeners.getTestInstance() }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowBuilder.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowBuilder.kt
index 0b309b5..4dd3ae7 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowBuilder.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRowBuilder.kt
@@ -62,7 +62,9 @@
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow.CoordinateOnClickListener
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow.ExpandableNotificationRowLogger
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow.OnExpandClickListener
-import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.FLAG_CONTENT_VIEW_ALL
+import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.FLAG_CONTENT_VIEW_CONTRACTED
+import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.FLAG_CONTENT_VIEW_EXPANDED
+import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.FLAG_CONTENT_VIEW_HEADS_UP
 import com.android.systemui.statusbar.notification.row.NotificationRowContentBinder.InflationFlag
 import com.android.systemui.statusbar.notification.row.shared.NotificationRowContentBinderRefactor
 import com.android.systemui.statusbar.notification.stack.NotificationChildrenContainerLogger
@@ -80,8 +82,6 @@
 import com.android.systemui.util.mockito.whenever
 import com.android.systemui.util.time.FakeSystemClock
 import com.android.systemui.wmshell.BubblesManager
-import com.google.common.util.concurrent.MoreExecutors
-import com.google.common.util.concurrent.SettableFuture
 import java.util.Optional
 import java.util.concurrent.CountDownLatch
 import java.util.concurrent.TimeUnit
@@ -309,7 +309,7 @@
             entry.ranking = rb.build()
         }
 
-        return generateRow(entry, FLAG_CONTENT_VIEW_ALL)
+        return generateRow(entry, INFLATION_FLAGS)
     }
 
     private fun generateRow(
@@ -374,7 +374,8 @@
         private const val PKG = "com.android.systemui"
         private const val UID = 1000
         private val USER_HANDLE = UserHandle.of(ActivityManager.getCurrentUser())
-
+        private val INFLATION_FLAGS =
+            FLAG_CONTENT_VIEW_CONTRACTED or FLAG_CONTENT_VIEW_EXPANDED or FLAG_CONTENT_VIEW_HEADS_UP
         private const val IS_CONVERSATION_FLAG = "test.isConversation"
 
         private val Notification.isConversationStyleNotification
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationsPlaceholderViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationsPlaceholderViewModelKosmos.kt
index 1542bb34..3247525 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationsPlaceholderViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/NotificationsPlaceholderViewModelKosmos.kt
@@ -16,6 +16,7 @@
 
 package com.android.systemui.statusbar.notification.stack.ui.viewmodel
 
+import com.android.systemui.dump.dumpManager
 import com.android.systemui.flags.featureFlagsClassic
 import com.android.systemui.kosmos.Kosmos
 import com.android.systemui.kosmos.Kosmos.Fixture
@@ -29,5 +30,6 @@
         shadeInteractor = shadeInteractor,
         headsUpNotificationInteractor = headsUpNotificationInteractor,
         featureFlags = featureFlagsClassic,
+        dumpManager = dumpManager,
     )
 }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/ui/dialog/ModesDialogDelegateKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/ui/dialog/ModesDialogDelegateKosmos.kt
index 99bb479..932e768 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/ui/dialog/ModesDialogDelegateKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/ui/dialog/ModesDialogDelegateKosmos.kt
@@ -33,6 +33,7 @@
             dialogTransitionAnimator,
             activityStarter,
             { modesDialogViewModel },
+            modesDialogEventLogger,
             mainCoroutineContext,
         )
     }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/ui/dialog/ModesDialogEventLoggerKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/ui/dialog/ModesDialogEventLoggerKosmos.kt
new file mode 100644
index 0000000..24e7a87
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/ui/dialog/ModesDialogEventLoggerKosmos.kt
@@ -0,0 +1,24 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.policy.ui.dialog
+
+import com.android.internal.logging.uiEventLogger
+import com.android.systemui.kosmos.Kosmos
+import org.mockito.kotlin.mock
+
+var Kosmos.modesDialogEventLogger by Kosmos.Fixture { ModesDialogEventLogger(uiEventLogger) }
+var Kosmos.mockModesDialogEventLogger by Kosmos.Fixture { mock<ModesDialogEventLogger>() }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/ui/dialog/ModesDialogEventLoggerTest.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/ui/dialog/ModesDialogEventLoggerTest.kt
new file mode 100644
index 0000000..5146f77
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/ui/dialog/ModesDialogEventLoggerTest.kt
@@ -0,0 +1,116 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.policy.ui.dialog
+
+import android.testing.TestableLooper
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.internal.logging.testing.UiEventLoggerFake
+import com.android.settingslib.notification.modes.TestModeBuilder
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.qs.QSModesEvent
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+@TestableLooper.RunWithLooper
+class ModesDialogEventLoggerTest : SysuiTestCase() {
+
+    private val uiEventLogger = UiEventLoggerFake()
+    private val underTest = ModesDialogEventLogger(uiEventLogger)
+
+    @Test
+    fun testLogModeOn_manual() {
+        underTest.logModeOn(TestModeBuilder.MANUAL_DND_INACTIVE)
+
+        assertThat(uiEventLogger.numLogs()).isEqualTo(1)
+        uiEventLogger[0].match(QSModesEvent.QS_MODES_DND_ON, "android")
+    }
+
+    @Test
+    fun testLogModeOff_manual() {
+        underTest.logModeOff(TestModeBuilder.MANUAL_DND_ACTIVE)
+
+        assertThat(uiEventLogger.numLogs()).isEqualTo(1)
+        uiEventLogger[0].match(QSModesEvent.QS_MODES_DND_OFF, "android")
+    }
+
+    @Test
+    fun testLogModeSettings_manual() {
+        underTest.logModeSettings(TestModeBuilder.MANUAL_DND_ACTIVE)
+
+        assertThat(uiEventLogger.numLogs()).isEqualTo(1)
+        uiEventLogger[0].match(QSModesEvent.QS_MODES_DND_SETTINGS, "android")
+    }
+
+    @Test
+    fun testLogModeOn_automatic() {
+        underTest.logModeOn(TestModeBuilder().setActive(true).setPackage("pkg1").build())
+
+        assertThat(uiEventLogger.numLogs()).isEqualTo(1)
+        uiEventLogger[0].match(QSModesEvent.QS_MODES_MODE_ON, "pkg1")
+    }
+
+    @Test
+    fun testLogModeOff_automatic() {
+        underTest.logModeOff(TestModeBuilder().setActive(false).setPackage("pkg2").build())
+
+        assertThat(uiEventLogger.numLogs()).isEqualTo(1)
+        uiEventLogger[0].match(QSModesEvent.QS_MODES_MODE_OFF, "pkg2")
+    }
+
+    @Test
+    fun testLogModeSettings_automatic() {
+        underTest.logModeSettings(TestModeBuilder().setPackage("pkg3").build())
+
+        assertThat(uiEventLogger.numLogs()).isEqualTo(1)
+        uiEventLogger[0].match(QSModesEvent.QS_MODES_MODE_SETTINGS, "pkg3")
+    }
+
+    @Test
+    fun testLogOpenDurationDialog_manual() {
+        underTest.logOpenDurationDialog(TestModeBuilder.MANUAL_DND_INACTIVE)
+
+        assertThat(uiEventLogger.numLogs()).isEqualTo(1)
+        // package not logged for duration dialog as it only applies to manual mode
+        uiEventLogger[0].match(QSModesEvent.QS_MODES_DURATION_DIALOG, null)
+    }
+
+    @Test
+    fun testLogOpenDurationDialog_automatic_doesNotLog() {
+        underTest.logOpenDurationDialog(
+            TestModeBuilder().setActive(false).setPackage("mypkg").build()
+        )
+
+        // ignore calls to open dialog on something other than the manual rule (shouldn't happen)
+        assertThat(uiEventLogger.numLogs()).isEqualTo(0)
+    }
+
+    @Test
+    fun testLogDialogSettings() {
+        underTest.logDialogSettings()
+        assertThat(uiEventLogger.numLogs()).isEqualTo(1)
+        uiEventLogger[0].match(QSModesEvent.QS_MODES_SETTINGS, null)
+    }
+
+    private fun UiEventLoggerFake.FakeUiEvent.match(event: QSModesEvent, modePackage: String?) {
+        assertThat(eventId).isEqualTo(event.id)
+        assertThat(packageName).isEqualTo(modePackage)
+    }
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/ui/dialog/viewmodel/ModesDialogViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/ui/dialog/viewmodel/ModesDialogViewModelKosmos.kt
index 00020f8..3571a73 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/ui/dialog/viewmodel/ModesDialogViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/policy/ui/dialog/viewmodel/ModesDialogViewModelKosmos.kt
@@ -21,6 +21,7 @@
 import com.android.systemui.kosmos.testDispatcher
 import com.android.systemui.statusbar.policy.domain.interactor.zenModeInteractor
 import com.android.systemui.statusbar.policy.ui.dialog.modesDialogDelegate
+import com.android.systemui.statusbar.policy.ui.dialog.modesDialogEventLogger
 import javax.inject.Provider
 
 val Kosmos.modesDialogViewModel: ModesDialogViewModel by
@@ -30,5 +31,6 @@
             zenModeInteractor,
             testDispatcher,
             Provider { modesDialogDelegate }.get(),
+            modesDialogEventLogger,
         )
     }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/touchpad/tutorial/TouchpadTutorialKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/touchpad/tutorial/TouchpadTutorialKosmos.kt
new file mode 100644
index 0000000..f502df0
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/touchpad/tutorial/TouchpadTutorialKosmos.kt
@@ -0,0 +1,28 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.touchpad.tutorial
+
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.model.sysUiState
+import com.android.systemui.settings.displayTracker
+import com.android.systemui.touchpad.tutorial.domain.interactor.TouchpadGesturesInteractor
+
+var Kosmos.touchpadGesturesInteractor: TouchpadGesturesInteractor by
+    Kosmos.Fixture {
+        TouchpadGesturesInteractor(sysUiState, displayTracker, testScope.backgroundScope)
+    }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/util/coroutines/MainDispatcherRule.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/util/coroutines/MainDispatcherRule.kt
new file mode 100644
index 0000000..5776203
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/util/coroutines/MainDispatcherRule.kt
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.util.coroutines
+
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.TestDispatcher
+import kotlinx.coroutines.test.resetMain
+import kotlinx.coroutines.test.setMain
+import org.junit.rules.TestWatcher
+import org.junit.runner.Description
+
+/**
+ * Overrides main dispatcher to passed testDispatcher. You probably want to use it when using
+ * viewModelScope which has hardcoded main dispatcher.
+ */
+@OptIn(ExperimentalCoroutinesApi::class)
+class MainDispatcherRule(val testDispatcher: TestDispatcher) : TestWatcher() {
+    override fun starting(description: Description) {
+        Dispatchers.setMain(testDispatcher)
+    }
+
+    override fun finished(description: Description) {
+        Dispatchers.resetMain()
+    }
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/util/mockito/KotlinMockitoHelpers.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/util/mockito/KotlinMockitoHelpers.kt
index 295e150..2e1ecfd 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/util/mockito/KotlinMockitoHelpers.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/util/mockito/KotlinMockitoHelpers.kt
@@ -219,7 +219,7 @@
  *
  * NOTE: this uses the KotlinArgumentCaptor to avoid the NullPointerException.
  */
-@Deprecated("Replace with mockito-kotlin", level = WARNING)
+// TODO(359670968): rewrite this to use mockito-kotlin
 inline fun <reified T : Any> withArgCaptor(block: KotlinArgumentCaptor<T>.() -> Unit): T =
     kotlinArgumentCaptor<T>().apply { block() }.value
 
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/OWNERS b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/OWNERS
new file mode 100644
index 0000000..1f07df9
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/OWNERS
@@ -0,0 +1 @@
+include /packages/SystemUI/src/com/android/systemui/volume/OWNERS
\ No newline at end of file
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/data/repository/FakeAudioSharingRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/data/repository/FakeAudioSharingRepository.kt
index 0a617d1..a4719e5 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/data/repository/FakeAudioSharingRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/data/repository/FakeAudioSharingRepository.kt
@@ -18,7 +18,6 @@
 
 import com.android.settingslib.volume.data.repository.AudioSharingRepository
 import com.android.settingslib.volume.data.repository.GroupIdToVolumes
-import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.MutableStateFlow
 import kotlinx.coroutines.flow.StateFlow
 
@@ -30,7 +29,7 @@
         MutableStateFlow(TEST_GROUP_ID_INVALID)
     private val mutableVolumeMap: MutableStateFlow<GroupIdToVolumes> = MutableStateFlow(emptyMap())
 
-    override val inAudioSharing: Flow<Boolean> = mutableInAudioSharing
+    override val inAudioSharing: StateFlow<Boolean> = mutableInAudioSharing
     override val primaryGroupId: StateFlow<Int> = mutablePrimaryGroupId
     override val secondaryGroupId: StateFlow<Int> = mutableSecondaryGroupId
     override val volumeMap: StateFlow<GroupIdToVolumes> = mutableVolumeMap
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/domain/interactor/AudioOutputInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/domain/interactor/AudioOutputInteractorKosmos.kt
index e2d414e..3ac565a 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/domain/interactor/AudioOutputInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/domain/interactor/AudioOutputInteractorKosmos.kt
@@ -22,7 +22,6 @@
 import com.android.systemui.kosmos.Kosmos
 import com.android.systemui.kosmos.testScope
 import com.android.systemui.volume.data.repository.audioRepository
-import com.android.systemui.volume.data.repository.audioSharingRepository
 import com.android.systemui.volume.mediaOutputInteractor
 
 val Kosmos.audioOutputInteractor by
@@ -37,6 +36,5 @@
             bluetoothAdapter,
             deviceIconInteractor,
             mediaOutputInteractor,
-            audioSharingRepository,
         )
     }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/component/mediaoutput/domain/interactor/MediaOutputComponentInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/component/mediaoutput/domain/interactor/MediaOutputComponentInteractorKosmos.kt
index 9f11822..63a1325 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/component/mediaoutput/domain/interactor/MediaOutputComponentInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/component/mediaoutput/domain/interactor/MediaOutputComponentInteractorKosmos.kt
@@ -20,6 +20,7 @@
 import com.android.systemui.kosmos.testScope
 import com.android.systemui.volume.domain.interactor.audioModeInteractor
 import com.android.systemui.volume.domain.interactor.audioOutputInteractor
+import com.android.systemui.volume.domain.interactor.audioSharingInteractor
 import com.android.systemui.volume.mediaDeviceSessionInteractor
 import com.android.systemui.volume.mediaOutputInteractor
 
@@ -31,5 +32,6 @@
             audioOutputInteractor,
             audioModeInteractor,
             mediaOutputInteractor,
+            audioSharingInteractor,
         )
     }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/component/volume/domain/interactor/AudioSlidersInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/component/volume/domain/interactor/AudioSlidersInteractorKosmos.kt
index a0a39d1..63386d0 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/component/volume/domain/interactor/AudioSlidersInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/component/volume/domain/interactor/AudioSlidersInteractorKosmos.kt
@@ -17,15 +17,17 @@
 package com.android.systemui.volume.panel.component.volume.domain.interactor
 
 import com.android.systemui.kosmos.Kosmos
-import com.android.systemui.kosmos.testScope
+import com.android.systemui.kosmos.applicationCoroutineScope
 import com.android.systemui.volume.data.repository.audioRepository
+import com.android.systemui.volume.domain.interactor.audioModeInteractor
 import com.android.systemui.volume.mediaOutputInteractor
 
 val Kosmos.audioSlidersInteractor by
     Kosmos.Fixture {
         AudioSlidersInteractor(
-            testScope.backgroundScope,
+            applicationCoroutineScope,
             mediaOutputInteractor,
             audioRepository,
+            audioModeInteractor,
         )
     }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/component/volume/ui/viewmodel/VolumeSlidersViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/component/volume/ui/viewmodel/VolumeSlidersViewModelKosmos.kt
index 45a291e..6e848ce 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/component/volume/ui/viewmodel/VolumeSlidersViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/volume/panel/component/volume/ui/viewmodel/VolumeSlidersViewModelKosmos.kt
@@ -18,6 +18,7 @@
 
 import com.android.systemui.kosmos.Kosmos
 import com.android.systemui.kosmos.testScope
+import com.android.systemui.volume.domain.interactor.audioModeInteractor
 import com.android.systemui.volume.mediaDeviceSessionInteractor
 import com.android.systemui.volume.mediaOutputInteractor
 import com.android.systemui.volume.panel.component.volume.domain.interactor.audioSlidersInteractor
@@ -32,6 +33,7 @@
             mediaDeviceSessionInteractor,
             audioStreamSliderViewModelFactory,
             castVolumeSliderViewModelFactory,
+            audioModeInteractor,
             audioSlidersInteractor,
         )
     }
diff --git a/packages/services/CameraExtensionsProxy/src/com/android/cameraextensions/CameraExtensionsProxyService.java b/packages/services/CameraExtensionsProxy/src/com/android/cameraextensions/CameraExtensionsProxyService.java
index 2c4bc7c..09068d5 100644
--- a/packages/services/CameraExtensionsProxy/src/com/android/cameraextensions/CameraExtensionsProxyService.java
+++ b/packages/services/CameraExtensionsProxy/src/com/android/cameraextensions/CameraExtensionsProxyService.java
@@ -78,8 +78,8 @@
 import android.util.Size;
 import android.view.Surface;
 
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
 import androidx.camera.extensions.impl.AutoImageCaptureExtenderImpl;
 import androidx.camera.extensions.impl.AutoPreviewExtenderImpl;
 import androidx.camera.extensions.impl.BeautyImageCaptureExtenderImpl;
@@ -171,7 +171,7 @@
             EXTENSIONS_VERSION.startsWith(LATENCY_VERSION_PREFIX) ||
             EXTENSIONS_VERSION.startsWith(EFV_VERSION_PREFIX));
 
-    private HashMap<String, Long> mMetadataVendorIdMap = new HashMap<>();
+    private static HashMap<String, Long> mMetadataVendorIdMap = new HashMap<>();
     private CameraManager mCameraManager;
 
     private static boolean checkForLatencyAPI() {
@@ -820,7 +820,7 @@
             mCameraManager = getSystemService(CameraManager.class);
 
             String [] cameraIds = mCameraManager.getCameraIdListNoLazy();
-            if (cameraIds != null) {
+            if (cameraIds != null && mMetadataVendorIdMap.isEmpty()) {
                 for (String cameraId : cameraIds) {
                     CameraCharacteristics chars = mCameraManager.getCameraCharacteristics(cameraId);
                     Object thisClass = CameraCharacteristics.Key.class;
diff --git a/ravenwood/Android.bp b/ravenwood/Android.bp
index 6150343..58cd2e4 100644
--- a/ravenwood/Android.bp
+++ b/ravenwood/Android.bp
@@ -166,6 +166,14 @@
     jarjar_rules: ":ravenwood-services-jarjar-rules",
 }
 
+java_device_for_host {
+    name: "ravenwood-junit-impl-for-ravenizer",
+    libs: [
+        "ravenwood-junit-impl",
+    ],
+    visibility: [":__subpackages__"],
+}
+
 // Separated out from ravenwood-junit-impl since it needs to compile
 // against `module_current`
 java_library {
diff --git a/ravenwood/texts/ravenwood-annotation-allowed-classes.txt b/ravenwood/texts/ravenwood-annotation-allowed-classes.txt
index cc9b70e..639ebab 100644
--- a/ravenwood/texts/ravenwood-annotation-allowed-classes.txt
+++ b/ravenwood/texts/ravenwood-annotation-allowed-classes.txt
@@ -311,6 +311,7 @@
 com.android.internal.util.QuickSelect
 com.android.internal.util.RingBuffer
 com.android.internal.util.SizedInputStream
+com.android.internal.util.RateLimitingCache
 com.android.internal.util.StringPool
 com.android.internal.util.TokenBucket
 com.android.internal.util.XmlPullParserWrapper
diff --git a/ravenwood/tools/ravenizer/Android.bp b/ravenwood/tools/ravenizer/Android.bp
new file mode 100644
index 0000000..2892d07
--- /dev/null
+++ b/ravenwood/tools/ravenizer/Android.bp
@@ -0,0 +1,25 @@
+package {
+    // See: http://go/android-license-faq
+    // A large-scale-change added 'default_applicable_licenses' to import
+    // all of the 'license_kinds' from "frameworks_base_license"
+    // to get the below license kinds:
+    //   SPDX-license-identifier-Apache-2.0
+    default_applicable_licenses: ["frameworks_base_license"],
+}
+
+java_binary_host {
+    name: "ravenizer",
+    main_class: "com.android.platform.test.ravenwood.ravenizer.RavenizerMain",
+    srcs: ["src/**/*.kt"],
+    static_libs: [
+        "hoststubgen-lib",
+        "ow2-asm",
+        "ow2-asm-analysis",
+        "ow2-asm-commons",
+        "ow2-asm-tree",
+        "ow2-asm-util",
+        "junit",
+        "ravenwood-junit-impl-for-ravenizer",
+    ],
+    visibility: ["//visibility:public"],
+}
diff --git a/ravenwood/tools/ravenizer/src/com/android/platform/test/ravenwood/ravenizer/Ravenizer.kt b/ravenwood/tools/ravenizer/src/com/android/platform/test/ravenwood/ravenizer/Ravenizer.kt
new file mode 100644
index 0000000..da9c7d9
--- /dev/null
+++ b/ravenwood/tools/ravenizer/src/com/android/platform/test/ravenwood/ravenizer/Ravenizer.kt
@@ -0,0 +1,212 @@
+/*
+ * 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.platform.test.ravenwood.ravenizer
+
+import com.android.hoststubgen.GeneralUserErrorException
+import com.android.hoststubgen.asm.ClassNodes
+import com.android.hoststubgen.asm.zipEntryNameToClassName
+import com.android.hoststubgen.executableName
+import com.android.hoststubgen.log
+import com.android.platform.test.ravenwood.ravenizer.adapter.TestRunnerRewritingAdapter
+import org.objectweb.asm.ClassReader
+import org.objectweb.asm.ClassVisitor
+import org.objectweb.asm.ClassWriter
+import org.objectweb.asm.util.CheckClassAdapter
+import java.io.BufferedInputStream
+import java.io.BufferedOutputStream
+import java.io.FileOutputStream
+import java.io.InputStream
+import java.io.OutputStream
+import java.util.zip.ZipEntry
+import java.util.zip.ZipFile
+import java.util.zip.ZipOutputStream
+
+/**
+ * Various stats on Ravenizer.
+ */
+data class RavenizerStats(
+    /** Total end-to-end time. */
+    var totalTime: Double = .0,
+
+    /** Time took to build [ClasNodes] */
+    var loadStructureTime: Double = .0,
+
+    /** Total real time spent for converting the jar file */
+    var totalProcessTime: Double = .0,
+
+    /** Total real time spent for converting class files (except for I/O time). */
+    var totalConversionTime: Double = .0,
+
+    /** Total real time spent for copying class files without modification. */
+    var totalCopyTime: Double = .0,
+
+    /** # of entries in the input jar file */
+    var totalEntiries: Int = 0,
+
+    /** # of *.class files in the input jar file */
+    var totalClasses: Int = 0,
+
+    /** # of *.class files that have been processed. */
+    var processedClasses: Int = 0,
+) {
+    override fun toString(): String {
+        return """
+            RavenizerStats{
+              totalTime=$totalTime,
+              loadStructureTime=$loadStructureTime,
+              totalProcessTime=$totalProcessTime,
+              totalConversionTime=$totalConversionTime,
+              totalCopyTime=$totalCopyTime,
+              totalEntiries=$totalEntiries,
+              totalClasses=$totalClasses,
+              processedClasses=$processedClasses,
+            }
+            """.trimIndent()
+    }
+}
+
+/**
+ * Main class.
+ */
+class Ravenizer(val options: RavenizerOptions) {
+    fun run() {
+        val stats = RavenizerStats()
+        stats.totalTime = log.nTime {
+            process(options.inJar.get, options.outJar.get, stats)
+        }
+        log.i(stats.toString())
+    }
+
+    private fun process(inJar: String, outJar: String, stats: RavenizerStats) {
+        var allClasses = ClassNodes.loadClassStructures(inJar) {
+            time -> stats.loadStructureTime = time
+        }
+
+        stats.totalProcessTime = log.iTime("$executableName processing $inJar") {
+            ZipFile(inJar).use { inZip ->
+                val inEntries = inZip.entries()
+
+                stats.totalEntiries = inZip.size()
+
+                ZipOutputStream(BufferedOutputStream(FileOutputStream(outJar))).use { outZip ->
+                    while (inEntries.hasMoreElements()) {
+                        val entry = inEntries.nextElement()
+
+                        if (entry.name.endsWith(".dex")) {
+                            // Seems like it's an ART jar file. We can't process it.
+                            // It's a fatal error.
+                            throw GeneralUserErrorException(
+                                "$inJar is not a desktop jar file. It contains a *.dex file."
+                            )
+                        }
+
+                        val className = zipEntryNameToClassName(entry.name)
+
+                        if (className != null) {
+                            stats.totalClasses += 1
+                        }
+
+                        if (className != null && shouldProcessClass(allClasses, className)) {
+                            stats.processedClasses += 1
+                            processSingleClass(inZip, entry, outZip, allClasses, stats)
+                        } else {
+                            // Too slow, let's use merge_zips to bring back the original classes.
+                            copyZipEntry(inZip, entry, outZip, stats)
+                        }
+                    }
+                }
+            }
+        }
+    }
+
+    /**
+     * Copy a single ZIP entry to the output.
+     */
+    private fun copyZipEntry(
+        inZip: ZipFile,
+        entry: ZipEntry,
+        out: ZipOutputStream,
+        stats: RavenizerStats,
+    ) {
+        stats.totalCopyTime += log.nTime {
+            inZip.getInputStream(entry).use { ins ->
+                // Copy unknown entries as is to the impl out. (but not to the stub out.)
+                val outEntry = ZipEntry(entry.name)
+                outEntry.method = 0
+                outEntry.size = entry.size
+                outEntry.crc = entry.crc
+                out.putNextEntry(outEntry)
+
+                ins.transferTo(out)
+
+                out.closeEntry()
+            }
+        }
+    }
+
+    private fun processSingleClass(
+        inZip: ZipFile,
+        entry: ZipEntry,
+        outZip: ZipOutputStream,
+        allClasses: ClassNodes,
+        stats: RavenizerStats,
+    ) {
+        val newEntry = ZipEntry(entry.name)
+        outZip.putNextEntry(newEntry)
+
+        BufferedInputStream(inZip.getInputStream(entry)).use { bis ->
+            processSingleClass(entry, bis, outZip, allClasses, stats)
+        }
+        outZip.closeEntry()
+    }
+
+    /**
+     * Whether a class needs to be processed. This must be kept in sync with [processSingleClass].
+     */
+    private fun shouldProcessClass(classes: ClassNodes, classInternalName: String): Boolean {
+        return TestRunnerRewritingAdapter.shouldProcess(classes, classInternalName)
+    }
+
+    private fun processSingleClass(
+        entry: ZipEntry,
+        input: InputStream,
+        output: OutputStream,
+        allClasses: ClassNodes,
+        stats: RavenizerStats,
+    ) {
+        val cr = ClassReader(input)
+
+        lateinit var data: ByteArray
+        stats.totalConversionTime += log.vTime("Modify ${entry.name}") {
+            val flags = ClassWriter.COMPUTE_MAXS
+            val cw = ClassWriter(flags)
+            var outVisitor: ClassVisitor = cw
+
+            val enableChecker = false
+            if (enableChecker) {
+                outVisitor = CheckClassAdapter(outVisitor)
+            }
+
+            // This must be kept in sync with shouldProcessClass.
+            outVisitor = TestRunnerRewritingAdapter(allClasses, outVisitor)
+
+            cr.accept(outVisitor, ClassReader.EXPAND_FRAMES)
+
+            data = cw.toByteArray()
+        }
+        output.write(data)
+    }
+}
diff --git a/ravenwood/tools/ravenizer/src/com/android/platform/test/ravenwood/ravenizer/RavenizerMain.kt b/ravenwood/tools/ravenizer/src/com/android/platform/test/ravenwood/ravenizer/RavenizerMain.kt
new file mode 100644
index 0000000..ff41818
--- /dev/null
+++ b/ravenwood/tools/ravenizer/src/com/android/platform/test/ravenwood/ravenizer/RavenizerMain.kt
@@ -0,0 +1,41 @@
+/*
+ * 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.
+ */
+@file:JvmName("RavenizerMain")
+
+package com.android.platform.test.ravenwood.ravenizer
+
+import com.android.hoststubgen.LogLevel
+import com.android.hoststubgen.executableName
+import com.android.hoststubgen.log
+import com.android.hoststubgen.runMainWithBoilerplate
+
+/**
+ * Entry point.
+ */
+fun main(args: Array<String>) {
+    executableName = "Ravenizer"
+    log.setConsoleLogLevel(LogLevel.Info)
+
+    runMainWithBoilerplate {
+        val options = RavenizerOptions.parseArgs(args)
+
+        log.i("$executableName started")
+        log.v("Options: $options")
+
+        // Run.
+        Ravenizer(options).run()
+    }
+}
diff --git a/ravenwood/tools/ravenizer/src/com/android/platform/test/ravenwood/ravenizer/RavenizerOptions.kt b/ravenwood/tools/ravenizer/src/com/android/platform/test/ravenwood/ravenizer/RavenizerOptions.kt
new file mode 100644
index 0000000..e85e3be
--- /dev/null
+++ b/ravenwood/tools/ravenizer/src/com/android/platform/test/ravenwood/ravenizer/RavenizerOptions.kt
@@ -0,0 +1,80 @@
+/*
+ * 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.platform.test.ravenwood.ravenizer
+
+import com.android.hoststubgen.ArgIterator
+import com.android.hoststubgen.ArgumentsException
+import com.android.hoststubgen.SetOnce
+import com.android.hoststubgen.ensureFileExists
+import com.android.hoststubgen.log
+
+class RavenizerOptions(
+    /** Input jar file*/
+    var inJar: SetOnce<String> = SetOnce(""),
+
+    /** Output jar file */
+    var outJar: SetOnce<String> = SetOnce(""),
+) {
+    companion object {
+        fun parseArgs(args: Array<String>): RavenizerOptions {
+            val ret = RavenizerOptions()
+            val ai = ArgIterator.withAtFiles(args)
+
+            while (true) {
+                val arg = ai.nextArgOptional()
+                if (arg == null) {
+                    break
+                }
+
+                fun nextArg(): String = ai.nextArgRequired(arg)
+
+                if (log.maybeHandleCommandLineArg(arg) { nextArg() }) {
+                    continue
+                }
+                try {
+                    when (arg) {
+                        // TODO: Write help
+                        "-h", "--help" -> TODO("Help is not implemented yet")
+
+                        "--in-jar" -> ret.inJar.set(nextArg()).ensureFileExists()
+                        "--out-jar" -> ret.outJar.set(nextArg())
+
+                        else -> throw ArgumentsException("Unknown option: $arg")
+                    }
+                } catch (e: SetOnce.SetMoreThanOnceException) {
+                    throw ArgumentsException("Duplicate or conflicting argument found: $arg")
+                }
+            }
+
+            if (!ret.inJar.isSet) {
+                throw ArgumentsException("Required option missing: --in-jar")
+            }
+            if (!ret.outJar.isSet) {
+                throw ArgumentsException("Required option missing: --out-jar")
+            }
+           return ret
+        }
+    }
+
+    override fun toString(): String {
+        return """
+            RavenizerOptions{
+              inJar=$inJar,
+              outJar=$outJar,
+            }
+            """.trimIndent()
+    }
+}
diff --git a/ravenwood/tools/ravenizer/src/com/android/platform/test/ravenwood/ravenizer/Utils.kt b/ravenwood/tools/ravenizer/src/com/android/platform/test/ravenwood/ravenizer/Utils.kt
new file mode 100644
index 0000000..0018648
--- /dev/null
+++ b/ravenwood/tools/ravenizer/src/com/android/platform/test/ravenwood/ravenizer/Utils.kt
@@ -0,0 +1,54 @@
+/*
+ * 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.platform.test.ravenwood.ravenizer
+
+import com.android.hoststubgen.asm.ClassNodes
+import com.android.hoststubgen.asm.findAnyAnnotation
+import org.objectweb.asm.Type
+
+val junitTestMethodType = Type.getType(org.junit.Test::class.java)
+val junitRunWithType = Type.getType(org.junit.runner.RunWith::class.java)
+
+val junitTestMethodDescriptor = junitTestMethodType.descriptor
+val junitRunWithDescriptor = junitRunWithType.descriptor
+
+val junitTestMethodDescriptors = setOf<String>(junitTestMethodDescriptor)
+val junitRunWithDescriptors = setOf<String>(junitRunWithDescriptor)
+
+/**
+ * Returns true, if a test looks like it's a test class which needs to be processed.
+ */
+fun isTestLookingClass(classes: ClassNodes, className: String): Boolean {
+    // Similar to  com.android.tradefed.lite.HostUtils.testLoadClass(), except it's more lenient,
+    // and accept non-public and/or abstract classes.
+    // HostUtils also checks "Suppress" or "SuiteClasses" but this one doesn't.
+    // TODO: SuiteClasses may need to be supported.
+
+    val cn = classes.findClass(className) ?: return false
+
+    if (cn.findAnyAnnotation(junitRunWithDescriptors) != null) {
+        return true
+    }
+    cn.methods?.forEach { method ->
+        if (method.findAnyAnnotation(junitTestMethodDescriptors) != null) {
+            return true
+        }
+    }
+    if (cn.superName == null) {
+        return false
+    }
+    return isTestLookingClass(classes, cn.superName)
+}
diff --git a/ravenwood/tools/ravenizer/src/com/android/platform/test/ravenwood/ravenizer/adapter/TestRunnerRewritingAdapter.kt b/ravenwood/tools/ravenizer/src/com/android/platform/test/ravenwood/ravenizer/adapter/TestRunnerRewritingAdapter.kt
new file mode 100644
index 0000000..c539908
--- /dev/null
+++ b/ravenwood/tools/ravenizer/src/com/android/platform/test/ravenwood/ravenizer/adapter/TestRunnerRewritingAdapter.kt
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.platform.test.ravenwood.ravenizer.adapter
+
+import com.android.hoststubgen.asm.ClassNodes
+import com.android.hoststubgen.visitors.OPCODE_VERSION
+import com.android.platform.test.ravenwood.ravenizer.isTestLookingClass
+import org.objectweb.asm.ClassVisitor
+
+/**
+ * Class visitor to rewrite the test runner for Ravenwood
+ *
+ * TODO: Implement it.
+ */
+class TestRunnerRewritingAdapter(
+    protected val classes: ClassNodes,
+    nextVisitor: ClassVisitor,
+) : ClassVisitor(OPCODE_VERSION, nextVisitor) {
+   companion object {
+       /**
+        * Returns true if a target class is interesting to this adapter.
+        */
+       fun shouldProcess(classes: ClassNodes, className: String): Boolean {
+            return isTestLookingClass(classes, className)
+       }
+    }
+}
diff --git a/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandler.java b/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandler.java
index b052d23..aa57e0b 100644
--- a/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandler.java
+++ b/services/accessibility/java/com/android/server/accessibility/magnification/FullScreenMagnificationGestureHandler.java
@@ -16,8 +16,6 @@
 
 package com.android.server.accessibility.magnification;
 
-import static android.view.InputDevice.SOURCE_MOUSE;
-import static android.view.InputDevice.SOURCE_STYLUS;
 import static android.view.InputDevice.SOURCE_TOUCHSCREEN;
 import static android.view.MotionEvent.ACTION_CANCEL;
 import static android.view.MotionEvent.ACTION_DOWN;
@@ -307,12 +305,8 @@
         }
 
         mDelegatingState = new DelegatingState();
-        mDetectingState = Flags.enableMagnificationMultipleFingerMultipleTapGesture()
-                ? new DetectingStateWithMultiFinger(context)
-                : new DetectingState(context);
-        mViewportDraggingState = Flags.enableMagnificationMultipleFingerMultipleTapGesture()
-                ? new ViewportDraggingStateWithMultiFinger()
-                : new ViewportDraggingState();
+        mDetectingState = new DetectingState(context);
+        mViewportDraggingState = new ViewportDraggingState();
         mPanningScalingState = new PanningScalingState(context);
         mSinglePanningState = new SinglePanningState(context);
         mFullScreenMagnificationVibrationHelper = fullScreenMagnificationVibrationHelper;
@@ -342,8 +336,12 @@
                 cancelFling();
             }
             handleTouchEventWith(mCurrentState, event, rawEvent, policyFlags);
-        } else if (Flags.enableMagnificationFollowsMouse()
-                && (event.getSource() == SOURCE_MOUSE || event.getSource() == SOURCE_STYLUS)) {
+        }
+    }
+
+    @Override
+    void handleMouseOrStylusEvent(MotionEvent event, MotionEvent rawEvent, int policyFlags) {
+        if (Flags.enableMagnificationFollowsMouse()) {
             if (mFullScreenMagnificationController.isActivated(mDisplayId)) {
                 // TODO(b/354696546): Allow mouse/stylus to activate whichever display they are
                 // over, rather than only interacting with the current display.
@@ -351,8 +349,6 @@
                 // Send through the mouse/stylus event handler.
                 mMouseEventHandler.onEvent(event, mDisplayId);
             }
-            // Dispatch to normal event handling flow.
-            dispatchTransformedEvent(event, rawEvent, policyFlags);
         }
     }
 
@@ -701,62 +697,6 @@
         }
     }
 
-    final class ViewportDraggingStateWithMultiFinger extends ViewportDraggingState {
-        // LINT.IfChange(viewport_dragging_state_with_multi_finger)
-        @Override
-        public void onMotionEvent(MotionEvent event, MotionEvent rawEvent, int policyFlags)
-                throws GestureException {
-            final int action = event.getActionMasked();
-            switch (action) {
-                case ACTION_POINTER_DOWN: {
-                    clearAndTransitToPanningScalingState();
-                }
-                break;
-                case ACTION_MOVE: {
-                    if (event.getPointerCount() > 2) {
-                        throw new GestureException("Should have one pointer down.");
-                    }
-                    final float eventX = event.getX();
-                    final float eventY = event.getY();
-                    if (mFullScreenMagnificationController.magnificationRegionContains(
-                            mDisplayId, eventX, eventY)) {
-                        mFullScreenMagnificationController.setCenter(mDisplayId, eventX, eventY,
-                                /* animate */ mLastMoveOutsideMagnifiedRegion,
-                                AccessibilityManagerService.MAGNIFICATION_GESTURE_HANDLER_ID);
-                        mLastMoveOutsideMagnifiedRegion = false;
-                    } else {
-                        mLastMoveOutsideMagnifiedRegion = true;
-                    }
-                }
-                break;
-
-                case ACTION_UP:
-                case ACTION_CANCEL: {
-                    // If mScaleToRecoverAfterDraggingEnd >= 1.0, the dragging state is triggered
-                    // by zoom in temporary, and the magnifier needs to recover to original scale
-                    // after exiting dragging state.
-                    // Otherwise, the magnifier should be disabled.
-                    if (mScaleToRecoverAfterDraggingEnd >= 1.0f) {
-                        zoomToScale(mScaleToRecoverAfterDraggingEnd, event.getX(),
-                                event.getY());
-                    } else {
-                        zoomOff();
-                    }
-                    clear();
-                    mScaleToRecoverAfterDraggingEnd = Float.NaN;
-                    transitionTo(mDetectingState);
-                }
-                    break;
-
-                case ACTION_DOWN: {
-                    throw new GestureException(
-                            "Unexpected event type: " + MotionEvent.actionToString(action));
-                }
-            }
-        }
-        // LINT.ThenChange(:viewport_dragging_state)
-    }
-
     /**
      * This class handles motion events when the event dispatcher has
      * determined that the user is performing a single-finger drag of the
@@ -777,7 +717,6 @@
 
         protected boolean mLastMoveOutsideMagnifiedRegion;
 
-        // LINT.IfChange(viewport_dragging_state)
         @Override
         public void onMotionEvent(MotionEvent event, MotionEvent rawEvent, int policyFlags)
                 throws GestureException {
@@ -788,7 +727,11 @@
                 }
                 break;
                 case ACTION_MOVE: {
-                    if (event.getPointerCount() != 1) {
+                    if (Flags.enableMagnificationMultipleFingerMultipleTapGesture()) {
+                        if (event.getPointerCount() > 2) {
+                            throw new GestureException("Should have at most two pointers down.");
+                        }
+                    } else if (event.getPointerCount() != 1) {
                         throw new GestureException("Should have one pointer down.");
                     }
                     final float eventX = event.getX();
@@ -823,14 +766,20 @@
                 }
                     break;
 
-                case ACTION_DOWN:
                 case ACTION_POINTER_UP: {
+                    if (!Flags.enableMagnificationMultipleFingerMultipleTapGesture()) {
+                        throw new GestureException(
+                                "Unexpected event type: " + MotionEvent.actionToString(action));
+                    }
+                }
+                break;
+
+                case ACTION_DOWN: {
                     throw new GestureException(
                             "Unexpected event type: " + MotionEvent.actionToString(action));
                 }
             }
         }
-        // LINT.ThenChange(:viewport_dragging_state_with_multi_finger)
 
         private boolean isAlwaysOnMagnificationEnabled() {
             return mFullScreenMagnificationController.isAlwaysOnMagnificationEnabled();
@@ -916,270 +865,31 @@
         }
     }
 
-    final class DetectingStateWithMultiFinger extends DetectingState {
-        private static final int TWO_FINGER_GESTURE_MAX_TAPS = 2;
-        // A flag set to true when two fingers have touched down.
-        // Used to indicate what next finger action should be.
-        private boolean mIsTwoFingerCountReached = false;
-        // A tap counts when two fingers are down and up once.
-        private int mCompletedTapCount = 0;
-        DetectingStateWithMultiFinger(Context context) {
-            super(context);
-        }
-
-        // LINT.IfChange(detecting_state_with_multi_finger)
-        @Override
-        public void onMotionEvent(MotionEvent event, MotionEvent rawEvent, int policyFlags) {
-            cacheDelayedMotionEvent(event, rawEvent, policyFlags);
-            switch (event.getActionMasked()) {
-                case MotionEvent.ACTION_DOWN: {
-                    mLastDetectingDownEventTime = event.getDownTime();
-                    mHandler.removeMessages(MESSAGE_TRANSITION_TO_DELEGATING_STATE);
-
-                    mFirstPointerDownLocation.set(event.getX(), event.getY());
-
-                    if (!mFullScreenMagnificationController.magnificationRegionContains(
-                            mDisplayId, event.getX(), event.getY())) {
-
-                        transitionToDelegatingStateAndClear();
-
-                    } else if (isMultiTapTriggered(2 /* taps */)) {
-
-                        // 3tap and hold
-                        afterLongTapTimeoutTransitionToDraggingState(event);
-
-                    } else if (isTapOutOfDistanceSlop()) {
-
-                        transitionToDelegatingStateAndClear();
-
-                    } else if (mDetectSingleFingerTripleTap
-                            || mDetectTwoFingerTripleTap
-                            // If activated, delay an ACTION_DOWN for mMultiTapMaxDelay
-                            // to ensure reachability of
-                            // STATE_PANNING_SCALING(triggerable with ACTION_POINTER_DOWN)
-                            || isActivated()) {
-
-                        afterMultiTapTimeoutTransitionToDelegatingState();
-
-                    } else {
-
-                        // Delegate pending events without delay
-                        transitionToDelegatingStateAndClear();
-                    }
-                }
-                break;
-                case ACTION_POINTER_DOWN: {
-                    mIsTwoFingerCountReached = mDetectTwoFingerTripleTap
-                            && event.getPointerCount() == 2;
-                    mHandler.removeMessages(MESSAGE_TRANSITION_TO_DELEGATING_STATE);
-
-                    if (event.getPointerCount() == 2) {
-                        if (isMultiFingerMultiTapTriggered(
-                                TWO_FINGER_GESTURE_MAX_TAPS - 1, event)) {
-                            // 3tap and hold
-                            afterLongTapTimeoutTransitionToDraggingState(event);
-                        } else {
-                            if (mDetectTwoFingerTripleTap) {
-                                // If mDetectTwoFingerTripleTap, delay transition to the delegating
-                                // state for mMultiTapMaxDelay to ensure reachability of
-                                // multi finger multi tap
-                                afterMultiTapTimeoutTransitionToDelegatingState();
-                            }
-
-                            if (isActivated()) {
-                                // If activated, delay transition to the panning scaling
-                                // state for tap timeout to ensure reachability of
-                                // multi finger multi tap
-                                storePointerDownLocation(mSecondPointerDownLocation, event);
-                                mHandler.sendEmptyMessageDelayed(
-                                        MESSAGE_TRANSITION_TO_PANNINGSCALING_STATE,
-                                        ViewConfiguration.getTapTimeout());
-                            }
-                        }
-                    } else {
-                        transitionToDelegatingStateAndClear();
-                    }
-                }
-                break;
-                case ACTION_POINTER_UP: {
-                    // If it is a two-finger gesture, do not transition to the delegating state
-                    // to ensure the reachability of
-                    // the two-finger triple tap (triggerable with ACTION_MOVE and ACTION_UP)
-                    if (!mIsTwoFingerCountReached) {
-                        transitionToDelegatingStateAndClear();
-                    }
-                }
-                break;
-                case ACTION_MOVE: {
-                    if (isFingerDown()
-                            && distance(mLastDown, /* move */ event) > mSwipeMinDistance) {
-                        // Swipe detected - transition immediately
-
-                        // For convenience, viewport dragging takes precedence
-                        // over insta-delegating on 3tap&swipe
-                        // (which is a rare combo to be used aside from magnification)
-                        if (isMultiTapTriggered(2 /* taps */) && event.getPointerCount() == 1) {
-                            transitionToViewportDraggingStateAndClear(event);
-                        } else if (isMultiFingerMultiTapTriggered(
-                                TWO_FINGER_GESTURE_MAX_TAPS - 1, event)
-                                && event.getPointerCount() == 2) {
-                            transitionToViewportDraggingStateAndClear(event);
-                        } else if (isActivated() && event.getPointerCount() == 2) {
-                            if (mOverscrollHandler != null
-                                    && overscrollState(event, mFirstPointerDownLocation)
-                                    == OVERSCROLL_VERTICAL_EDGE) {
-                                transitionToDelegatingStateAndClear();
-                            } else {
-                                //Primary pointer is swiping, so transit to PanningScalingState
-                                transitToPanningScalingStateAndClear();
-                            }
-                        } else if (mOneFingerPanningSettingsProvider.isOneFingerPanningEnabled()
-                                && isActivated()
-                                && event.getPointerCount() == 1) {
-                            if (mOverscrollHandler != null
-                                    && overscrollState(event, mFirstPointerDownLocation)
-                                    == OVERSCROLL_VERTICAL_EDGE) {
-                                transitionToDelegatingStateAndClear();
-                            } else if (overscrollState(event, mFirstPointerDownLocation)
-                                    != OVERSCROLL_NONE) {
-                                transitionToDelegatingStateAndClear();
-                            } else {
-                                transitToSinglePanningStateAndClear();
-                            }
-                        } else if (!mIsTwoFingerCountReached) {
-                            // If it is a two-finger gesture, do not transition to the
-                            // delegating state to ensure the reachability of
-                            // the two-finger triple tap (triggerable with ACTION_UP)
-                            transitionToDelegatingStateAndClear();
-                        }
-                    } else if (isActivated() && pointerDownValid(mSecondPointerDownLocation)
-                            && distanceClosestPointerToPoint(
-                            mSecondPointerDownLocation, /* move */ event) > mSwipeMinDistance) {
-                        // Second pointer is swiping, so transit to PanningScalingState
-                        // Delay an ACTION_MOVE for tap timeout to ensure it is not trigger from
-                        // multi finger multi tap
-                        storePointerDownLocation(mSecondPointerDownLocation, event);
-                        mHandler.sendEmptyMessageDelayed(
-                                MESSAGE_TRANSITION_TO_PANNINGSCALING_STATE,
-                                ViewConfiguration.getTapTimeout());
-                    }
-                }
-                break;
-                case ACTION_UP: {
-
-                    mHandler.removeMessages(MESSAGE_ON_TRIPLE_TAP_AND_HOLD);
-                    mHandler.removeMessages(MESSAGE_TRANSITION_TO_PANNINGSCALING_STATE);
-
-                    if (!mFullScreenMagnificationController.magnificationRegionContains(
-                            mDisplayId, event.getX(), event.getY())) {
-                        transitionToDelegatingStateAndClear();
-
-                    } else if (isMultiFingerMultiTapTriggered(TWO_FINGER_GESTURE_MAX_TAPS, event)) {
-                        // Placing multiple fingers before a single finger, because achieving a
-                        // multi finger multi tap also means achieving a single finger triple tap
-                        onTripleTap(event);
-
-                    } else if (isMultiTapTriggered(3 /* taps */)) {
-                        onTripleTap(/* up */ event);
-
-                    } else if (
-                            // Possible to be false on: 3tap&drag -> scale -> PTR_UP -> UP
-                            isFingerDown()
-                            //TODO long tap should never happen here
-                            && ((timeBetween(mLastDown, mLastUp) >= mLongTapMinDelay)
-                                    || (distance(mLastDown, mLastUp) >= mSwipeMinDistance))
-                            // If it is a two-finger but not reach 3 tap, do not transition to the
-                            // delegating state to ensure the reachability of the triple tap
-                            && mCompletedTapCount == 0) {
-                        transitionToDelegatingStateAndClear();
-
-                    }
-                }
-                break;
-            }
-        }
-        // LINT.ThenChange(:detecting_state)
-
-        @Override
-        public void clear() {
-            mCompletedTapCount = 0;
-            setShortcutTriggered(false);
-            removePendingDelayedMessages();
-            clearDelayedMotionEvents();
-            mFirstPointerDownLocation.set(Float.NaN, Float.NaN);
-            mSecondPointerDownLocation.set(Float.NaN, Float.NaN);
-        }
-
-        private boolean isMultiFingerMultiTapTriggered(int targetTapCount, MotionEvent event) {
-            if (event.getActionMasked() == ACTION_UP && mIsTwoFingerCountReached) {
-                mCompletedTapCount++;
-                mIsTwoFingerCountReached = false;
-            }
-
-            if (mDetectTwoFingerTripleTap && mCompletedTapCount > TWO_FINGER_GESTURE_MAX_TAPS - 1) {
-                final boolean enabled = !isActivated();
-                mMagnificationLogger.logMagnificationTwoFingerTripleTap(enabled);
-            }
-            return mDetectTwoFingerTripleTap && mCompletedTapCount == targetTapCount;
-        }
-
-        void transitionToDelegatingStateAndClear() {
-            mCompletedTapCount = 0;
-            transitionTo(mDelegatingState);
-            sendDelayedMotionEvents();
-            removePendingDelayedMessages();
-            mFirstPointerDownLocation.set(Float.NaN, Float.NaN);
-            mSecondPointerDownLocation.set(Float.NaN, Float.NaN);
-        }
-
-        void transitionToViewportDraggingStateAndClear(MotionEvent down) {
-
-            if (DEBUG_DETECTING) Slog.i(mLogTag, "onTripleTapAndHold()");
-            final boolean shortcutTriggered = mShortcutTriggered;
-
-            // Only log the 3tap and hold event
-            if (!shortcutTriggered) {
-                final boolean enabled = !isActivated();
-                if (mCompletedTapCount == TWO_FINGER_GESTURE_MAX_TAPS - 1) {
-                    // Two finger triple tap and hold
-                    mMagnificationLogger.logMagnificationTwoFingerTripleTap(enabled);
-                } else {
-                    // Triple tap and hold also belongs to triple tap event
-                    mMagnificationLogger.logMagnificationTripleTap(enabled);
-                }
-            }
-            clear();
-
-            mViewportDraggingState.prepareForZoomInTemporary(shortcutTriggered);
-            zoomInTemporary(down.getX(), down.getY(), shortcutTriggered);
-            transitionTo(mViewportDraggingState);
-        }
-    }
-
     /**
      * This class handles motion events when the event dispatch has not yet
      * determined what the user is doing. It watches for various tap events.
      */
     class DetectingState implements State, Handler.Callback {
 
-        protected static final int MESSAGE_ON_TRIPLE_TAP_AND_HOLD = 1;
-        protected static final int MESSAGE_TRANSITION_TO_DELEGATING_STATE = 2;
-        protected static final int MESSAGE_TRANSITION_TO_PANNINGSCALING_STATE = 3;
+        private static final int MESSAGE_ON_TRIPLE_TAP_AND_HOLD = 1;
+        private static final int MESSAGE_TRANSITION_TO_DELEGATING_STATE = 2;
+        private static final int MESSAGE_TRANSITION_TO_PANNINGSCALING_STATE = 3;
 
         final int mLongTapMinDelay;
         final int mSwipeMinDistance;
         final int mMultiTapMaxDelay;
         final int mMultiTapMaxDistance;
+        @Nullable final TwoFingerDoubleTapHandler mTwoFingerDoubleTapHandler;
 
-        protected MotionEventInfo mDelayedEventQueue;
-        protected MotionEvent mLastDown;
-        protected MotionEvent mPreLastDown;
-        protected MotionEvent mLastUp;
-        protected MotionEvent mPreLastUp;
+        private MotionEventInfo mDelayedEventQueue;
+        private MotionEvent mLastDown;
+        private MotionEvent mPreLastDown;
+        private MotionEvent mLastUp;
+        private MotionEvent mPreLastUp;
 
-        protected PointF mFirstPointerDownLocation = new PointF(Float.NaN, Float.NaN);
-        protected PointF mSecondPointerDownLocation = new PointF(Float.NaN, Float.NaN);
-        protected long mLastDetectingDownEventTime;
+        private PointF mFirstPointerDownLocation = new PointF(Float.NaN, Float.NaN);
+        private PointF mSecondPointerDownLocation = new PointF(Float.NaN, Float.NaN);
+        private long mLastDetectingDownEventTime;
 
         @VisibleForTesting boolean mShortcutTriggered;
 
@@ -1191,6 +901,9 @@
                     MagnificationGestureMatcher.getMagnificationMultiTapTimeout(context);
             mSwipeMinDistance = ViewConfiguration.get(context).getScaledTouchSlop();
             mMultiTapMaxDistance = ViewConfiguration.get(context).getScaledDoubleTapSlop();
+            mTwoFingerDoubleTapHandler =
+                    Flags.enableMagnificationMultipleFingerMultipleTapGesture()
+                            ? new TwoFingerDoubleTapHandler() : null;
         }
 
         @Override
@@ -1218,7 +931,6 @@
             return true;
         }
 
-        // LINT.IfChange(detecting_state)
         @Override
         public void onMotionEvent(MotionEvent event, MotionEvent rawEvent, int policyFlags) {
             cacheDelayedMotionEvent(event, rawEvent, policyFlags);
@@ -1244,6 +956,7 @@
                         transitionToDelegatingStateAndClear();
 
                     } else if (mDetectSingleFingerTripleTap
+                            || (mTwoFingerDoubleTapHandler != null && mDetectTwoFingerTripleTap)
                             // If activated, delay an ACTION_DOWN for mMultiTapMaxDelay
                             // to ensure reachability of
                             // STATE_PANNING_SCALING(triggerable with ACTION_POINTER_DOWN)
@@ -1259,6 +972,12 @@
                 }
                 break;
                 case ACTION_POINTER_DOWN: {
+                    if (mTwoFingerDoubleTapHandler != null) {
+                        mTwoFingerDoubleTapHandler.onPointerDown(event);
+                        break;
+                    }
+
+                    // LINT.IfChange(action_pointer_down)
                     if (isActivated() && event.getPointerCount() == 2) {
                         storePointerDownLocation(mSecondPointerDownLocation, event);
                         mHandler.sendEmptyMessageDelayed(MESSAGE_TRANSITION_TO_PANNINGSCALING_STATE,
@@ -1266,13 +985,26 @@
                     } else {
                         transitionToDelegatingStateAndClear();
                     }
+                    // LINT.ThenChange(:action_pointer_down_with_multi_finger)
                 }
                 break;
                 case ACTION_POINTER_UP: {
+                    if (mTwoFingerDoubleTapHandler != null) {
+                        mTwoFingerDoubleTapHandler.onPointerUp();
+                        break;
+                    }
+                    // LINT.IfChange(action_pointer_up)
                     transitionToDelegatingStateAndClear();
+                    // LINT.ThenChange(:action_pointer_up_with_multi_finger)
                 }
                 break;
                 case ACTION_MOVE: {
+                    if (mTwoFingerDoubleTapHandler != null) {
+                        mTwoFingerDoubleTapHandler.onMove(event);
+                        break;
+                    }
+
+                    // LINT.IfChange(action_move)
                     if (isFingerDown()
                             && distance(mLastDown, /* move */ event) > mSwipeMinDistance) {
                         // Swipe detected - transition immediately
@@ -1313,12 +1045,20 @@
                         //Second pointer is swiping, so transit to PanningScalingState
                         transitToPanningScalingStateAndClear();
                     }
+                    // LINT.ThenChange(:action_move_with_multi_finger)
                 }
                 break;
                 case ACTION_UP: {
 
                     mHandler.removeMessages(MESSAGE_ON_TRIPLE_TAP_AND_HOLD);
 
+                    if (mTwoFingerDoubleTapHandler != null) {
+                        mHandler.removeMessages(MESSAGE_TRANSITION_TO_PANNINGSCALING_STATE);
+                        mTwoFingerDoubleTapHandler.onUp(event);
+                        break;
+                    }
+
+                    // LINT.IfChange(action_up)
                     if (!mFullScreenMagnificationController.magnificationRegionContains(
                             mDisplayId, event.getX(), event.getY())) {
                         transitionToDelegatingStateAndClear();
@@ -1335,11 +1075,11 @@
                         transitionToDelegatingStateAndClear();
 
                     }
+                    // LINT.ThenChange(:action_up_with_multi_finger)
                 }
                 break;
             }
         }
-        // LINT.ThenChange(:detecting_state_with_multi_finger)
 
         protected void storePointerDownLocation(PointF pointerDownLocation, MotionEvent event) {
             final int index = event.getActionIndex();
@@ -1425,6 +1165,9 @@
 
         @Override
         public void clear() {
+            if (mTwoFingerDoubleTapHandler != null) {
+                mTwoFingerDoubleTapHandler.mCompletedTapCount = 0;
+            }
             setShortcutTriggered(false);
             removePendingDelayedMessages();
             clearDelayedMotionEvents();
@@ -1501,9 +1244,13 @@
         }
 
         void transitionToDelegatingStateAndClear() {
+            if (mTwoFingerDoubleTapHandler != null) {
+                mTwoFingerDoubleTapHandler.mCompletedTapCount = 0;
+            }
             transitionTo(mDelegatingState);
             sendDelayedMotionEvents();
             removePendingDelayedMessages();
+            mFirstPointerDownLocation.set(Float.NaN, Float.NaN);
             mSecondPointerDownLocation.set(Float.NaN, Float.NaN);
         }
 
@@ -1543,9 +1290,15 @@
 
             // Only log the 3tap and hold event
             if (!shortcutTriggered) {
-                // Triple tap and hold also belongs to triple tap event
                 final boolean enabled = !isActivated();
-                mMagnificationLogger.logMagnificationTripleTap(enabled);
+                if (mTwoFingerDoubleTapHandler != null
+                        && mTwoFingerDoubleTapHandler.shouldLogTwoFingerDoubleTap()) {
+                    // Two finger double tap and hold
+                    mMagnificationLogger.logMagnificationTwoFingerTripleTap(enabled);
+                } else {
+                    // Triple tap and hold also belongs to triple tap event
+                    mMagnificationLogger.logMagnificationTripleTap(enabled);
+                }
             }
             clear();
 
@@ -1604,6 +1357,173 @@
             }
             return false;
         }
+
+        final class TwoFingerDoubleTapHandler {
+            private static final int TWO_FINGER_GESTURE_MAX_TAPS = 2;
+            // A tap counts when two fingers are down and up once.
+            private int mCompletedTapCount;
+            // A flag set to true when two fingers have touched down.
+            // Used to indicate what next finger action should be.
+            private boolean mIsTwoFingerCountReached;
+
+            TwoFingerDoubleTapHandler() {
+                mCompletedTapCount = 0;
+                mIsTwoFingerCountReached = false;
+            }
+
+            private void onPointerDown(MotionEvent event) {
+                mIsTwoFingerCountReached = mDetectTwoFingerTripleTap
+                        && event.getPointerCount() == 2;
+                mHandler.removeMessages(MESSAGE_TRANSITION_TO_DELEGATING_STATE);
+
+                // LINT.IfChange(action_pointer_down_with_multi_finger)
+                if (event.getPointerCount() == 2) {
+                    if (isMultiFingerMultiTapTriggered(
+                            TWO_FINGER_GESTURE_MAX_TAPS - 1, event)) {
+                        // 3tap and hold
+                        afterLongTapTimeoutTransitionToDraggingState(event);
+                    } else {
+                        if (mDetectTwoFingerTripleTap) {
+                            // If mDetectTwoFingerTripleTap, delay transition to the delegating
+                            // state for mMultiTapMaxDelay to ensure reachability of
+                            // multi finger multi tap
+                            afterMultiTapTimeoutTransitionToDelegatingState();
+                        }
+
+                        if (isActivated()) {
+                            // If activated, delay transition to the panning scaling
+                            // state for tap timeout to ensure reachability of
+                            // multi finger multi tap
+                            storePointerDownLocation(mSecondPointerDownLocation, event);
+                            mHandler.sendEmptyMessageDelayed(
+                                    MESSAGE_TRANSITION_TO_PANNINGSCALING_STATE,
+                                    ViewConfiguration.getTapTimeout());
+                        }
+                    }
+                } else {
+                    transitionToDelegatingStateAndClear();
+                }
+                // LINT.ThenChange(:action_pointer_down)
+            }
+
+            private void onMove(MotionEvent event) {
+                // LINT.IfChange(action_move_with_multi_finger)
+                if (isFingerDown()
+                        && distance(mLastDown, /* move */ event) > mSwipeMinDistance) {
+                    // Swipe detected - transition immediately
+
+                    // For convenience, viewport dragging takes precedence
+                    // over insta-delegating on 3tap&swipe
+                    // (which is a rare combo to be used aside from magnification)
+                    if (isMultiTapTriggered(2 /* taps */) && event.getPointerCount() == 1) {
+                        transitionToViewportDraggingStateAndClear(event);
+                    } else if (isMultiFingerMultiTapTriggered(
+                            TWO_FINGER_GESTURE_MAX_TAPS - 1, event)
+                            && event.getPointerCount() == 2) {
+                        transitionToViewportDraggingStateAndClear(event);
+                    } else if (isActivated() && event.getPointerCount() == 2) {
+                        if (mOverscrollHandler != null
+                                && overscrollState(event, mFirstPointerDownLocation)
+                                == OVERSCROLL_VERTICAL_EDGE) {
+                            transitionToDelegatingStateAndClear();
+                        } else {
+                            //Primary pointer is swiping, so transit to PanningScalingState
+                            transitToPanningScalingStateAndClear();
+                        }
+                    } else if (mOneFingerPanningSettingsProvider.isOneFingerPanningEnabled()
+                            && isActivated()
+                            && event.getPointerCount() == 1) {
+                        if (mOverscrollHandler != null
+                                && overscrollState(event, mFirstPointerDownLocation)
+                                == OVERSCROLL_VERTICAL_EDGE) {
+                            transitionToDelegatingStateAndClear();
+                        } else if (overscrollState(event, mFirstPointerDownLocation)
+                                != OVERSCROLL_NONE) {
+                            transitionToDelegatingStateAndClear();
+                        } else {
+                            transitToSinglePanningStateAndClear();
+                        }
+                    } else if (!mIsTwoFingerCountReached) {
+                        // If it is a two-finger gesture, do not transition to the
+                        // delegating state to ensure the reachability of
+                        // the two-finger triple tap (triggerable with ACTION_UP)
+                        transitionToDelegatingStateAndClear();
+                    }
+                } else if (isActivated() && pointerDownValid(mSecondPointerDownLocation)
+                        && distanceClosestPointerToPoint(
+                        mSecondPointerDownLocation, /* move */ event) > mSwipeMinDistance) {
+                    // Second pointer is swiping, so transit to PanningScalingState
+                    // Delay an ACTION_MOVE for tap timeout to ensure it is not trigger from
+                    // multi finger multi tap
+                    storePointerDownLocation(mSecondPointerDownLocation, event);
+                    mHandler.sendEmptyMessageDelayed(
+                            MESSAGE_TRANSITION_TO_PANNINGSCALING_STATE,
+                            ViewConfiguration.getTapTimeout());
+                }
+                // LINT.ThenChange(:action_move)
+            }
+
+            private void onPointerUp() {
+                // If it is a two-finger gesture, do not transition to the delegating state
+                // to ensure the reachability of
+                // the two-finger triple tap (triggerable with ACTION_MOVE and ACTION_UP)
+                // LINT.IfChange(action_pointer_up_with_multi_finger)
+                if (!mIsTwoFingerCountReached) {
+                    transitionToDelegatingStateAndClear();
+                }
+                // LINT.ThenChange(:action_pointer_up)
+            }
+
+            private void onUp(MotionEvent event) {
+                // LINT.IfChange(action_up_with_multi_finger)
+                if (!mFullScreenMagnificationController.magnificationRegionContains(
+                        mDisplayId, event.getX(), event.getY())) {
+                    transitionToDelegatingStateAndClear();
+
+                } else if (isMultiFingerMultiTapTriggered(
+                        TWO_FINGER_GESTURE_MAX_TAPS, event)) {
+                    // Placing multiple fingers before a single finger, because achieving a
+                    // multi finger multi tap also means achieving a single finger
+                    // triple tap
+                    onTripleTap(event);
+
+                } else if (isMultiTapTriggered(3 /* taps */)) {
+                    onTripleTap(/* up */ event);
+
+                } else if (
+                    // Possible to be false on: 3tap&drag -> scale -> PTR_UP -> UP
+                        isFingerDown()
+                                //TODO long tap should never happen here
+                                && ((timeBetween(mLastDown, mLastUp) >= mLongTapMinDelay)
+                                || (distance(mLastDown, mLastUp) >= mSwipeMinDistance))
+                                // If it is a two-finger but not reach 3 tap, do not
+                                // transition to the delegating state to ensure the
+                                // reachability of the triple tap
+                                && mCompletedTapCount == 0) {
+                    transitionToDelegatingStateAndClear();
+                }
+                // LINT.ThenChange(:action_up)
+            }
+
+            private boolean isMultiFingerMultiTapTriggered(int targetTapCount, MotionEvent event) {
+                if (event.getActionMasked() == ACTION_UP && mIsTwoFingerCountReached) {
+                    mCompletedTapCount++;
+                    mIsTwoFingerCountReached = false;
+                }
+
+                if (mDetectTwoFingerTripleTap
+                        && mCompletedTapCount > TWO_FINGER_GESTURE_MAX_TAPS - 1) {
+                    final boolean enabled = !isActivated();
+                    mMagnificationLogger.logMagnificationTwoFingerTripleTap(enabled);
+                }
+                return mDetectTwoFingerTripleTap && mCompletedTapCount == targetTapCount;
+            }
+
+            private boolean shouldLogTwoFingerDoubleTap() {
+                return mCompletedTapCount
+                        == TwoFingerDoubleTapHandler.TWO_FINGER_GESTURE_MAX_TAPS - 1;
+            }
+        }
     }
 
     private void zoomInTemporary(float centerX, float centerY, boolean shortcutTriggered) {
diff --git a/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationGestureHandler.java b/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationGestureHandler.java
index 08411c2..446123f 100644
--- a/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationGestureHandler.java
+++ b/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationGestureHandler.java
@@ -127,49 +127,41 @@
         if (DEBUG_EVENT_STREAM) {
             storeEventInto(mDebugInputEventHistory, event);
         }
-        if (shouldDispatchTransformedEvent(event)) {
-            dispatchTransformedEvent(event, rawEvent, policyFlags);
-        } else {
-            onMotionEventInternal(event, rawEvent, policyFlags);
+        switch (event.getSource()) {
+            case SOURCE_TOUCHSCREEN: {
+                if (magnificationShortcutExists()) {
+                    // Observe touchscreen events while magnification activation is detected.
+                    onMotionEventInternal(event, rawEvent, policyFlags);
 
-            final int action = event.getAction();
-            if (action == MotionEvent.ACTION_DOWN) {
-                mCallback.onTouchInteractionStart(mDisplayId, getMode());
-            } else if (action == ACTION_UP || action == ACTION_CANCEL) {
-                mCallback.onTouchInteractionEnd(mDisplayId, getMode());
+                    final int action = event.getAction();
+                    if (action == MotionEvent.ACTION_DOWN) {
+                        mCallback.onTouchInteractionStart(mDisplayId, getMode());
+                    } else if (action == ACTION_UP || action == ACTION_CANCEL) {
+                        mCallback.onTouchInteractionEnd(mDisplayId, getMode());
+                    }
+                    // Return early: Do not dispatch event through normal eventing
+                    // flow, it has been fully consumed by the magnifier.
+                    return;
+                }
+            } break;
+            case SOURCE_MOUSE:
+            case SOURCE_STYLUS: {
+                if (magnificationShortcutExists() && Flags.enableMagnificationFollowsMouse()) {
+                    handleMouseOrStylusEvent(event, rawEvent, policyFlags);
+                }
             }
+                break;
+            default:
+                break;
         }
+        // Dispatch event through normal eventing flow.
+        dispatchTransformedEvent(event, rawEvent, policyFlags);
     }
 
-    /**
-     * Some touchscreen, mouse and stylus events may modify magnifier state. Checks for whether the
-     * event should not be dispatched to the magnifier.
-     *
-     * @param event The event to check.
-     * @return `true` if the event should be sent through the normal event flow or `false` if it
-     *     should be observed by magnifier.
-     */
-    private boolean shouldDispatchTransformedEvent(MotionEvent event) {
-        if (event.getSource() == SOURCE_TOUCHSCREEN) {
-            if (mDetectSingleFingerTripleTap
-                    || mDetectTwoFingerTripleTap
-                    || mDetectShortcutTrigger) {
-                // Observe touchscreen events while magnification activation is detected.
-                return false;
-            }
-        }
-        if (Flags.enableMagnificationFollowsMouse()) {
-            if (event.isFromSource(SOURCE_MOUSE) || event.isFromSource(SOURCE_STYLUS)) {
-                // Note that mouse events include other mouse-like pointing devices
-                // such as touchpads and pointing sticks.
-                // Observe any mouse or stylus movement.
-                // We observe all movement to ensure that events continue to come in order,
-                // even though only some movement types actually move the viewport.
-                return false;
-            }
-        }
-        // Magnification dispatches (ignores) all other events
-        return true;
+    private boolean magnificationShortcutExists() {
+        return (mDetectSingleFingerTripleTap
+                || mDetectTwoFingerTripleTap
+                || mDetectShortcutTrigger);
     }
 
     final void dispatchTransformedEvent(MotionEvent event, MotionEvent rawEvent, int policyFlags) {
@@ -202,6 +194,13 @@
     abstract void onMotionEventInternal(MotionEvent event, MotionEvent rawEvent, int policyFlags);
 
     /**
+     * Called when this MagnificationGestureHandler should handle a mouse or stylus motion event,
+     * but not re-dispatch it when completed.
+     */
+    abstract void handleMouseOrStylusEvent(
+            MotionEvent event, MotionEvent rawEvent, int policyFlags);
+
+    /**
      * Called when the shortcut target is magnification.
      */
     public void notifyShortcutTriggered() {
diff --git a/services/accessibility/java/com/android/server/accessibility/magnification/WindowMagnificationGestureHandler.java b/services/accessibility/java/com/android/server/accessibility/magnification/WindowMagnificationGestureHandler.java
index 1818cdd..a841404 100644
--- a/services/accessibility/java/com/android/server/accessibility/magnification/WindowMagnificationGestureHandler.java
+++ b/services/accessibility/java/com/android/server/accessibility/magnification/WindowMagnificationGestureHandler.java
@@ -147,9 +147,13 @@
     }
 
     @Override
+    void handleMouseOrStylusEvent(MotionEvent event, MotionEvent rawEvent, int policyFlags) {
+        // Window Magnification viewport doesn't move with mouse events (yet).
+    }
+
+    @Override
     void onMotionEventInternal(MotionEvent event, MotionEvent rawEvent, int policyFlags) {
         if (event.getSource() != SOURCE_TOUCHSCREEN) {
-            // Window Magnification viewport doesn't move with mouse events (yet).
             return;
         }
         // To keep InputEventConsistencyVerifiers within GestureDetectors happy.
diff --git a/services/autofill/java/com/android/server/autofill/PresentationStatsEventLogger.java b/services/autofill/java/com/android/server/autofill/PresentationStatsEventLogger.java
index 930af5e..5044e93 100644
--- a/services/autofill/java/com/android/server/autofill/PresentationStatsEventLogger.java
+++ b/services/autofill/java/com/android/server/autofill/PresentationStatsEventLogger.java
@@ -71,6 +71,7 @@
 import android.util.Slog;
 import android.view.autofill.AutofillId;
 import android.view.autofill.AutofillManager;
+import android.view.autofill.AutofillValue;
 
 import com.android.internal.util.FrameworkStatsLog;
 
@@ -244,10 +245,18 @@
             Slog.e(TAG, "Failed to start new event because already have active event.");
             return;
         }
+        Slog.d(TAG, "Started new PresentationStatsEvent");
         mEventInternal = Optional.of(new PresentationStatsEventInternal());
     }
 
     /**
+     * Test use only, returns a copy of the events object
+     */
+    Optional<PresentationStatsEventInternal> getInternalEvent() {
+        return mEventInternal;
+    }
+
+    /**
      * Set request_id
      */
     public void maybeSetRequestId(int requestId) {
@@ -339,10 +348,16 @@
         });
     }
 
-    public void maybeSetCountShown(int datasets) {
+    /**
+     * This is called when a dataset is shown to the user. Will set the count shown,
+     * related timestamps and presentation reason.
+     */
+    public void logWhenDatasetShown(int datasets) {
         mEventInternal.ifPresent(
                 event -> {
+                    maybeSetSuggestionPresentedTimestampMs();
                     event.mCountShown = datasets;
+                    event.mNoPresentationReason = NOT_SHOWN_REASON_ANY_SHOWN;
                 });
     }
 
@@ -405,7 +420,12 @@
 
     public void maybeSetDisplayPresentationType(@UiType int uiType) {
         mEventInternal.ifPresent(event -> {
-            event.mDisplayPresentationType = getDisplayPresentationType(uiType);
+            // There are cases in which another UI type will show up after selects a dataset
+            // such as with Inline after Fill Dialog. Set as the first presentation type only.
+            if (event.mDisplayPresentationType
+                    == AUTOFILL_PRESENTATION_EVENT_REPORTED__DISPLAY_PRESENTATION_TYPE__UNKNOWN_AUTOFILL_DISPLAY_PRESENTATION_TYPE) {
+                event.mDisplayPresentationType = getDisplayPresentationType(uiType);
+            }
         });
     }
 
@@ -430,9 +450,12 @@
     }
 
     public void maybeSetSuggestionSentTimestampMs(int timestamp) {
-        mEventInternal.ifPresent(event -> {
-            event.mSuggestionSentTimestampMs = timestamp;
-        });
+        mEventInternal.ifPresent(
+                event -> {
+                    if (event.mSuggestionSentTimestampMs == DEFAULT_VALUE_INT) {
+                        event.mSuggestionSentTimestampMs = timestamp;
+                    }
+                });
     }
 
     public void maybeSetSuggestionSentTimestampMs() {
@@ -481,8 +504,6 @@
 
     public void maybeSetInlinePresentationAndSuggestionHostUid(Context context, int userId) {
         mEventInternal.ifPresent(event -> {
-            event.mDisplayPresentationType =
-                    AUTOFILL_PRESENTATION_EVENT_REPORTED__DISPLAY_PRESENTATION_TYPE__INLINE;
             String imeString = Settings.Secure.getStringForUser(context.getContentResolver(),
                     Settings.Secure.DEFAULT_INPUT_METHOD, userId);
             if (TextUtils.isEmpty(imeString)) {
@@ -602,40 +623,56 @@
     }
 
     /**
+     * Sets the field length whenever the text changes. Will keep track of the first
+     * and last modification lengths.
+     */
+    public void updateTextFieldLength(AutofillValue value) {
+        mEventInternal.ifPresent(event -> {
+            if (value == null || !value.isText()) {
+                return;
+            }
+
+            int length = value.getTextValue().length();
+
+            if (event.mFieldFirstLength == DEFAULT_VALUE_INT) {
+                event.mFieldFirstLength = length;
+            }
+            event.mFieldLastLength = length;
+        });
+    }
+
+    /**
      * Set various timestamps whenever the ViewState is modified
      *
      * <p>If the ViewState contains ViewState.STATE_AUTOFILLED, sets field_autofilled_timestamp_ms
      * else, set field_first_modified_timestamp_ms (if unset) and field_last_modified_timestamp_ms
      */
-    public void onFieldTextUpdated(ViewState state, int length) {
+    public void onFieldTextUpdated(ViewState state, AutofillValue value) {
         mEventInternal.ifPresent(event -> {
-                    int timestamp = getElapsedTime();
-                    // Focused id should be set before this is called
-                    if (state == null || state.id == null || state.id.getViewId() != event.mFocusedId) {
-                        // if these don't match, the currently field different than before
-                        Slog.w(
-                                TAG,
-                                "Bad view state for: " + event.mFocusedId);
-                        return;
-                    }
+            int timestamp = getElapsedTime();
+            // Focused id should be set before this is called
+            if (state == null || state.id == null || state.id.getViewId() != event.mFocusedId) {
+                // if these don't match, the currently field different than before
+                Slog.w(
+                        TAG,
+                        "Bad view state for: " + event.mFocusedId + ", state: " + state);
+                return;
+            }
 
-                    // Text changed because filling into form, just log Autofill timestamp
-                    if ((state.getState() & ViewState.STATE_AUTOFILLED) != 0) {
-                        event.mAutofilledTimestampMs = timestamp;
-                        return;
-                    }
+            updateTextFieldLength(value);
 
-                    // Set length variables
-                    if (event.mFieldFirstLength == DEFAULT_VALUE_INT) {
-                        event.mFieldFirstLength = length;
-                    }
-                    event.mFieldLastLength = length;
+            // Text changed because filling into form, just log Autofill timestamp
+            if ((state.getState() & ViewState.STATE_AUTOFILLED) != 0) {
+                event.mAutofilledTimestampMs = timestamp;
+                return;
+            }
 
-                    // Set timestamp variables
-                    if (event.mFieldModifiedFirstTimestampMs == DEFAULT_VALUE_INT) {
-                        event.mFieldModifiedFirstTimestampMs = timestamp;
-                    }
-                    event.mFieldModifiedLastTimestampMs = timestamp;
+
+            // Set timestamp variables
+            if (event.mFieldModifiedFirstTimestampMs == DEFAULT_VALUE_INT) {
+                event.mFieldModifiedFirstTimestampMs = timestamp;
+            }
+            event.mFieldModifiedLastTimestampMs = timestamp;
         });
     }
 
@@ -796,7 +833,10 @@
         });
     }
 
-    public void logAndEndEvent() {
+    /**
+     * Finish and log the event.
+     */
+    public void logAndEndEvent(String caller) {
         if (!mEventInternal.isPresent()) {
             Slog.w(TAG, "Shouldn't be logging AutofillPresentationEventReported again for same "
                     + "event");
@@ -804,7 +844,8 @@
         }
         PresentationStatsEventInternal event = mEventInternal.get();
         if (sVerbose) {
-            Slog.v(TAG, "Log AutofillPresentationEventReported:"
+            Slog.v(TAG, "(" + caller + ") "
+                    + "Log AutofillPresentationEventReported:"
                     + " requestId=" + event.mRequestId
                     + " sessionId=" + mSessionId
                     + " mNoPresentationEventReason=" + event.mNoPresentationReason
@@ -926,7 +967,7 @@
         mEventInternal = Optional.empty();
     }
 
-    private static final class PresentationStatsEventInternal {
+    static final class PresentationStatsEventInternal {
         int mRequestId;
         @NotShownReason int mNoPresentationReason = NOT_SHOWN_REASON_UNKNOWN;
         boolean mIsDatasetAvailable;
diff --git a/services/autofill/java/com/android/server/autofill/Session.java b/services/autofill/java/com/android/server/autofill/Session.java
index 6dea8b0..c75fd0b 100644
--- a/services/autofill/java/com/android/server/autofill/Session.java
+++ b/services/autofill/java/com/android/server/autofill/Session.java
@@ -28,7 +28,6 @@
 import static android.service.autofill.Dataset.PICK_REASON_PROVIDER_DETECTION_PREFERRED_WITH_PCC;
 import static android.service.autofill.Dataset.PICK_REASON_UNKNOWN;
 import static android.service.autofill.FillEventHistory.Event.UI_TYPE_CREDMAN_BOTTOM_SHEET;
-import static android.service.autofill.FillEventHistory.Event.UI_TYPE_DIALOG;
 import static android.service.autofill.FillEventHistory.Event.UI_TYPE_INLINE;
 import static android.service.autofill.FillEventHistory.Event.UI_TYPE_UNKNOWN;
 import static android.service.autofill.FillRequest.FLAG_MANUAL_REQUEST;
@@ -67,10 +66,10 @@
 import static com.android.server.autofill.FillResponseEventLogger.RESPONSE_STATUS_SUCCESS;
 import static com.android.server.autofill.FillResponseEventLogger.RESPONSE_STATUS_TIMEOUT;
 import static com.android.server.autofill.FillResponseEventLogger.RESPONSE_STATUS_TRANSACTION_TOO_LARGE;
+import static com.android.server.autofill.Helper.SaveInfoStats;
 import static com.android.server.autofill.Helper.containsCharsInOrder;
 import static com.android.server.autofill.Helper.createSanitizers;
 import static com.android.server.autofill.Helper.getNumericValue;
-import static com.android.server.autofill.Helper.SaveInfoStats;
 import static com.android.server.autofill.Helper.sDebug;
 import static com.android.server.autofill.Helper.sVerbose;
 import static com.android.server.autofill.Helper.toArray;
@@ -78,6 +77,7 @@
 import static com.android.server.autofill.PresentationStatsEventLogger.AUTHENTICATION_RESULT_SUCCESS;
 import static com.android.server.autofill.PresentationStatsEventLogger.AUTHENTICATION_TYPE_DATASET_AUTHENTICATION;
 import static com.android.server.autofill.PresentationStatsEventLogger.AUTHENTICATION_TYPE_FULL_AUTHENTICATION;
+import static com.android.server.autofill.PresentationStatsEventLogger.NOT_SHOWN_REASON_ANY_SHOWN;
 import static com.android.server.autofill.PresentationStatsEventLogger.NOT_SHOWN_REASON_NO_FOCUS;
 import static com.android.server.autofill.PresentationStatsEventLogger.NOT_SHOWN_REASON_REQUEST_FAILED;
 import static com.android.server.autofill.PresentationStatsEventLogger.NOT_SHOWN_REASON_REQUEST_TIMEOUT;
@@ -1288,11 +1288,11 @@
      * Clears the existing response for the partition, reads a new structure, and then requests a
      * new fill response from the fill service.
      *
-     * <p> Also asks the IME to make an inline suggestions request if it's enabled.
+     * <p>Also asks the IME to make an inline suggestions request if it's enabled.
      */
     @GuardedBy("mLock")
-    private void requestNewFillResponseLocked(@NonNull ViewState viewState, int newState,
-            int flags) {
+    private Optional<Integer> requestNewFillResponseLocked(
+            @NonNull ViewState viewState, int newState, int flags) {
         boolean isSecondary = shouldRequestSecondaryProvider(flags);
         final FillResponse existingResponse = isSecondary
                 ? viewState.getSecondaryResponse() : viewState.getResponse();
@@ -1333,7 +1333,7 @@
             mFillRequestEventLogger.maybeSetIsAugmented(true);
             mFillRequestEventLogger.logAndEndEvent();
             triggerAugmentedAutofillLocked(flags);
-            return;
+            return Optional.empty();
         }
 
         viewState.setState(newState);
@@ -1353,11 +1353,6 @@
                     + ", flags=" + flags);
         }
         boolean isCredmanRequested = (flags & FLAG_VIEW_REQUESTS_CREDMAN_SERVICE) != 0;
-        mPresentationStatsEventLogger.maybeSetRequestId(requestId);
-        mPresentationStatsEventLogger.maybeSetIsCredentialRequest(isCredmanRequested);
-        mPresentationStatsEventLogger.maybeSetFieldClassificationRequestId(
-                mFieldClassificationIdSnapshot);
-        mPresentationStatsEventLogger.maybeSetAutofillServiceUid(getAutofillServiceUid());
         mFillRequestEventLogger.maybeSetRequestId(requestId);
         mFillRequestEventLogger.maybeSetAutofillServiceUid(getAutofillServiceUid());
         mSaveEventLogger.maybeSetAutofillServiceUid(getAutofillServiceUid());
@@ -1417,6 +1412,8 @@
 
         // Now request the assist structure data.
         requestAssistStructureLocked(requestId, flags);
+
+        return Optional.of(requestId);
     }
 
     private boolean isRequestSupportFillDialog(int flags) {
@@ -1662,6 +1659,7 @@
         final LogMaker requestLog;
 
         synchronized (mLock) {
+            mPresentationStatsEventLogger.maybeSetRequestId(requestId);
             // Start a new FillResponse logger for the success case.
             mFillResponseEventLogger.startLogForNewResponse();
             mFillResponseEventLogger.maybeSetRequestId(requestId);
@@ -2419,7 +2417,7 @@
                         NOT_SHOWN_REASON_REQUEST_FAILED);
                 mFillResponseEventLogger.maybeSetResponseStatus(RESPONSE_STATUS_FAILURE);
             }
-            mPresentationStatsEventLogger.logAndEndEvent();
+            mPresentationStatsEventLogger.logAndEndEvent("fill request failure");
             mFillResponseEventLogger.maybeSetLatencyResponseProcessingMillis();
             mFillResponseEventLogger.logAndEndEvent();
         }
@@ -2642,6 +2640,8 @@
     public void onShown(int uiType, int numDatasetsShown) {
         synchronized (mLock) {
             mPresentationStatsEventLogger.maybeSetDisplayPresentationType(uiType);
+            mPresentationStatsEventLogger.maybeSetNoPresentationEventReason(
+                    NOT_SHOWN_REASON_ANY_SHOWN);
 
             if (uiType == UI_TYPE_INLINE) {
                 // Inline Suggestions are inflated one at a time
@@ -2657,7 +2657,7 @@
                 }
                 mLoggedInlineDatasetShown = true;
             } else {
-                mPresentationStatsEventLogger.maybeSetCountShown(numDatasetsShown);
+                mPresentationStatsEventLogger.logWhenDatasetShown(numDatasetsShown);
                 // Explicitly sets maybeSetSuggestionPresentedTimestampMs
                 mPresentationStatsEventLogger.maybeSetSuggestionPresentedTimestampMs();
                 mService.logDatasetShown(this.id, mClientState, uiType);
@@ -2800,7 +2800,10 @@
             if (mCurrentViewId == null) {
                 return;
             }
+            mPresentationStatsEventLogger.logAndEndEvent("fallback from fill dialog");
+            startNewEventForPresentationStatsEventLogger();
             final ViewState currentView = mViewStates.get(mCurrentViewId);
+            logPresentationStatsOnViewEnteredLocked(currentView.getResponse(), false);
             currentView.maybeCallOnFillReady(mFlags);
         }
     }
@@ -2850,7 +2853,7 @@
         if (requestId == AUGMENTED_AUTOFILL_REQUEST_ID) {
             setAuthenticationResultForAugmentedAutofillLocked(data, authenticationId);
             // Augmented autofill is not logged.
-            mPresentationStatsEventLogger.logAndEndEvent();
+            mPresentationStatsEventLogger.logAndEndEvent("authentication - augmented");
             return;
         }
         if (mResponses == null) {
@@ -2859,7 +2862,7 @@
             Slog.w(TAG, "setAuthenticationResultLocked(" + authenticationId + "): no responses");
             mPresentationStatsEventLogger.maybeSetAuthenticationResult(
                     AUTHENTICATION_RESULT_FAILURE);
-            mPresentationStatsEventLogger.logAndEndEvent();
+            mPresentationStatsEventLogger.logAndEndEvent("authentication - no response");
             removeFromService();
             return;
         }
@@ -2870,7 +2873,7 @@
             Slog.w(TAG, "no authenticated response");
             mPresentationStatsEventLogger.maybeSetAuthenticationResult(
                     AUTHENTICATION_RESULT_FAILURE);
-            mPresentationStatsEventLogger.logAndEndEvent();
+            mPresentationStatsEventLogger.logAndEndEvent("authentication - bad response");
             removeFromService();
             return;
         }
@@ -2885,7 +2888,7 @@
                 Slog.w(TAG, "no dataset with index " + datasetIdx + " on fill response");
                 mPresentationStatsEventLogger.maybeSetAuthenticationResult(
                         AUTHENTICATION_RESULT_FAILURE);
-                mPresentationStatsEventLogger.logAndEndEvent();
+                mPresentationStatsEventLogger.logAndEndEvent("authentication - no datasets");
                 removeFromService();
                 return;
             }
@@ -3330,7 +3333,7 @@
 
         mPresentationStatsEventLogger.maybeSetNoPresentationEventReason(
                 PresentationStatsEventLogger.getNoPresentationEventReason(commitReason));
-        mPresentationStatsEventLogger.logAndEndEvent();
+        mPresentationStatsEventLogger.logAndEndEvent("Context committed");
 
         final int flags = lastResponse.getFlags();
         if ((flags & FillResponse.FLAG_TRACK_CONTEXT_COMMITED) == 0) {
@@ -4299,6 +4302,7 @@
      * Starts (if necessary) a new fill request upon entering a view.
      *
      * <p>A new request will be started in 2 scenarios:
+     *
      * <ol>
      *   <li>If the user manually requested autofill.
      *   <li>If the view is part of a new partition.
@@ -4307,18 +4311,17 @@
      * @param id The id of the view that is entered.
      * @param viewState The view that is entered.
      * @param flags The flag that was passed by the AutofillManager.
-     *
      * @return {@code true} if a new fill response is requested.
      */
     @GuardedBy("mLock")
-    private boolean requestNewFillResponseOnViewEnteredIfNecessaryLocked(@NonNull AutofillId id,
-            @NonNull ViewState viewState, int flags) {
+    private Optional<Integer> requestNewFillResponseOnViewEnteredIfNecessaryLocked(
+            @NonNull AutofillId id, @NonNull ViewState viewState, int flags) {
         // Force new response for manual request
         if ((flags & FLAG_MANUAL_REQUEST) != 0) {
             mSessionFlags.mAugmentedAutofillOnly = false;
             if (sDebug) Slog.d(TAG, "Re-starting session on view " + id + " and flags " + flags);
-            requestNewFillResponseLocked(viewState, ViewState.STATE_RESTARTED_SESSION, flags);
-            return true;
+            return requestNewFillResponseLocked(
+                    viewState, ViewState.STATE_RESTARTED_SESSION, flags);
         }
 
         // If it's not, then check if it should start a partition.
@@ -4331,15 +4334,15 @@
             // Sometimes activity contain IMPORTANT_FOR_AUTOFILL_NO fields which marks session as
             // augmentedOnly, but other fields are still fillable by standard autofill.
             mSessionFlags.mAugmentedAutofillOnly = false;
-            requestNewFillResponseLocked(viewState, ViewState.STATE_STARTED_PARTITION, flags);
-            return true;
+            return requestNewFillResponseLocked(
+                    viewState, ViewState.STATE_STARTED_PARTITION, flags);
         }
 
         if (sVerbose) {
             Slog.v(TAG, "Not starting new partition for view " + id + ": "
                     + viewState.getStateAsString());
         }
-        return false;
+        return Optional.empty();
     }
 
     /**
@@ -4428,31 +4431,32 @@
     void updateLocked(AutofillId id, Rect virtualBounds, AutofillValue value, int action,
             int flags) {
         if (mDestroyed) {
-            Slog.w(TAG, "Call to Session#updateLocked() rejected - session: "
-                    + id + " destroyed");
+            Slog.w(TAG, "updateLocked(" + id + "):  rejected - session: destroyed");
             return;
         }
         if (action == ACTION_RESPONSE_EXPIRED) {
             mSessionFlags.mExpiredResponse = true;
             if (sDebug) {
-                Slog.d(TAG, "Set the response has expired.");
+                Slog.d(TAG, "updateLocked(" + id + "): Set the response has expired.");
             }
             mPresentationStatsEventLogger.maybeSetNoPresentationEventReasonIfNoReasonExists(
                         NOT_SHOWN_REASON_VIEW_CHANGED);
-            mPresentationStatsEventLogger.logAndEndEvent();
+            mPresentationStatsEventLogger.logAndEndEvent("ACTION_RESPONSE_EXPIRED");
             return;
         }
 
         id.setSessionId(this.id);
-        if (sVerbose) {
-            Slog.v(TAG, "updateLocked(" + this.id + "): id=" + id + ", action="
-                    + actionAsString(action) + ", flags=" + flags);
-        }
         ViewState viewState = mViewStates.get(id);
         if (sVerbose) {
-            Slog.v(TAG, "updateLocked(" + this.id + "): mCurrentViewId=" + mCurrentViewId
-                    + ", mExpiredResponse=" + mSessionFlags.mExpiredResponse
-                    + ", viewState=" + viewState);
+            Slog.v(
+                    TAG,
+                    "updateLocked(" + id + "): "
+                            + "id=" + this.id
+                            + ", action=" + actionAsString(action)
+                            + ", flags=" + flags
+                            + ", mCurrentViewId=" + mCurrentViewId
+                            + ", mExpiredResponse=" + mSessionFlags.mExpiredResponse
+                            + ", viewState=" + viewState);
         }
 
         if (viewState == null) {
@@ -4505,14 +4509,14 @@
                     mSessionFlags.mFillDialogDisabled = true;
                     mPreviouslyFillDialogPotentiallyStarted = false;
                 } else {
-                    // Set the default reason for now if the user doesn't trigger any focus event
-                    // on the autofillable view. This can be changed downstream when more
-                    // information is available or session is committed.
-                    mPresentationStatsEventLogger.maybeSetNoPresentationEventReason(
-                            NOT_SHOWN_REASON_NO_FOCUS);
                     mPreviouslyFillDialogPotentiallyStarted = true;
                 }
-                requestNewFillResponseLocked(viewState, ViewState.STATE_STARTED_SESSION, flags);
+                Optional<Integer> maybeRequestId =
+                        requestNewFillResponseLocked(
+                                viewState, ViewState.STATE_STARTED_SESSION, flags);
+                if (maybeRequestId.isPresent()) {
+                    mPresentationStatsEventLogger.maybeSetRequestId(maybeRequestId.get());
+                }
                 break;
             case ACTION_VALUE_CHANGED:
                 if (mCompatMode && (viewState.getState() & ViewState.STATE_URL_BAR) != 0) {
@@ -4577,8 +4581,10 @@
                 }
                 boolean isCredmanRequested = (flags & FLAG_VIEW_REQUESTS_CREDMAN_SERVICE) != 0;
                 if (shouldRequestSecondaryProvider(flags)) {
-                    if (requestNewFillResponseOnViewEnteredIfNecessaryLocked(
-                            id, viewState, flags)) {
+                    Optional<Integer> maybeRequestIdCred =
+                            requestNewFillResponseOnViewEnteredIfNecessaryLocked(
+                                    id, viewState, flags);
+                    if (maybeRequestIdCred.isPresent()) {
                         Slog.v(TAG, "Started a new fill request for secondary provider.");
                         return;
                     }
@@ -4622,17 +4628,7 @@
                     mLogViewEntered = true;
                 }
 
-                // Previously, fill request will only start whenever a view is entered.
-                // With Fill Dialog, request starts prior to view getting entered. So, we can't end
-                // the event at this moment, otherwise we will be wrongly attributing fill dialog
-                // event as concluded.
-                if (!wasPreviouslyFillDialog && !isSameViewAgain) {
-                    // TODO(b/319872477): Re-consider this logic below
-                    mPresentationStatsEventLogger.maybeSetNoPresentationEventReason(
-                            NOT_SHOWN_REASON_VIEW_FOCUS_CHANGED);
-                    mPresentationStatsEventLogger.logAndEndEvent();
-                }
-
+                // Trigger augmented autofill if applicable
                 if ((flags & FLAG_MANUAL_REQUEST) == 0) {
                     // Not a manual request
                     if (mAugmentedAutofillableIds != null && mAugmentedAutofillableIds.contains(
@@ -4658,26 +4654,25 @@
                         return;
                     }
                 }
-                // If previous request was FillDialog request, a logger event was already started
-                if (!wasPreviouslyFillDialog) {
+
+                Optional<Integer> maybeNewRequestId =
+                        requestNewFillResponseOnViewEnteredIfNecessaryLocked(id, viewState, flags);
+
+                // Previously, fill request will only start whenever a view is entered.
+                // With Fill Dialog, request starts prior to view getting entered. So, we can't end
+                // the event at this moment, otherwise we will be wrongly attributing fill dialog
+                // event as concluded.
+                if (!wasPreviouslyFillDialog
+                        && (!isSameViewEntered || maybeNewRequestId.isPresent())) {
                     startNewEventForPresentationStatsEventLogger();
-                }
-                if (requestNewFillResponseOnViewEnteredIfNecessaryLocked(id, viewState, flags)) {
-                    // If a new request was issued even if previously it was fill dialog request,
-                    // we should end the log event, and start a new one. However, it leaves us
-                    // susceptible to race condition. But since mPresentationStatsEventLogger is
-                    // lock guarded, we should be safe.
-                    if (wasPreviouslyFillDialog) {
-                        mPresentationStatsEventLogger.logAndEndEvent();
-                        startNewEventForPresentationStatsEventLogger();
+                    if (maybeNewRequestId.isPresent()) {
+                        mPresentationStatsEventLogger.maybeSetRequestId(maybeNewRequestId.get());
                     }
-                    return;
                 }
 
-                FillResponse response = viewState.getResponse();
-                if (response != null) {
-                    logPresentationStatsOnViewEnteredLocked(response, isCredmanRequested);
-                }
+                logPresentationStatsOnViewEnteredLocked(
+                        viewState.getResponse(), isCredmanRequested);
+                mPresentationStatsEventLogger.updateTextFieldLength(value);
 
                 if (isSameViewEntered) {
                     setFillDialogDisabledAndStartInput();
@@ -4719,13 +4714,17 @@
     @GuardedBy("mLock")
     private void logPresentationStatsOnViewEnteredLocked(FillResponse response,
             boolean isCredmanRequested) {
-        mPresentationStatsEventLogger.maybeSetRequestId(response.getRequestId());
         mPresentationStatsEventLogger.maybeSetIsCredentialRequest(isCredmanRequested);
         mPresentationStatsEventLogger.maybeSetFieldClassificationRequestId(
                 mFieldClassificationIdSnapshot);
-        mPresentationStatsEventLogger.maybeSetAvailableCount(
-                response.getDatasets(), mCurrentViewId);
+        mPresentationStatsEventLogger.maybeSetAutofillServiceUid(getAutofillServiceUid());
         mPresentationStatsEventLogger.maybeSetFocusedId(mCurrentViewId);
+
+        if (response != null) {
+            mPresentationStatsEventLogger.maybeSetRequestId(response.getRequestId());
+            mPresentationStatsEventLogger.maybeSetAvailableCount(
+                    response.getDatasets(), mCurrentViewId);
+        }
     }
 
     @GuardedBy("mLock")
@@ -4796,8 +4795,12 @@
 
         viewState.setCurrentValue(value);
         final String filterText = textValue;
-
         final AutofillValue filledValue = viewState.getAutofilledValue();
+
+        if (textValue != null) {
+            mPresentationStatsEventLogger.onFieldTextUpdated(viewState, value);
+        }
+
         if (filledValue != null) {
             if (filledValue.equals(value)) {
                 // When the update is caused by autofilling the view, just update the
@@ -4821,9 +4824,6 @@
                 currentView.maybeCallOnFillReady(flags);
             }
         }
-        if (textValue != null) {
-            mPresentationStatsEventLogger.onFieldTextUpdated(viewState, textValue.length());
-        }
 
         if (viewState.id.equals(this.mCurrentViewId)
                 && (viewState.getState() & ViewState.STATE_INLINE_SHOWN) != 0) {
@@ -4888,7 +4888,7 @@
                 mSaveEventLogger.logAndEndEvent();
                 mPresentationStatsEventLogger.maybeSetNoPresentationEventReason(
                     NOT_SHOWN_REASON_SESSION_COMMITTED_PREMATURELY);
-                mPresentationStatsEventLogger.logAndEndEvent();
+                mPresentationStatsEventLogger.logAndEndEvent("on fill ready");
                 return;
             }
         }
@@ -4920,7 +4920,6 @@
                 synchronized (mLock) {
                     final ViewState currentView = mViewStates.get(mCurrentViewId);
                     currentView.setState(ViewState.STATE_FILL_DIALOG_SHOWN);
-                    mPresentationStatsEventLogger.maybeSetDisplayPresentationType(UI_TYPE_DIALOG);
                 }
                 // Just show fill dialog once, so disabled after shown.
                 // Note: Cannot disable before requestShowFillDialog() because the method
@@ -6086,6 +6085,11 @@
     private void startNewEventForPresentationStatsEventLogger() {
         synchronized (mLock) {
             mPresentationStatsEventLogger.startNewEvent();
+            // Set the default reason for now if the user doesn't trigger any focus event
+            // on the autofillable view. This can be changed downstream when more
+            // information is available or session is committed.
+            mPresentationStatsEventLogger.maybeSetNoPresentationEventReason(
+                    NOT_SHOWN_REASON_NO_FOCUS);
             mPresentationStatsEventLogger.maybeSetDetectionPreference(
                     getDetectionPreferenceForLogging());
             mPresentationStatsEventLogger.maybeSetAutofillServiceUid(getAutofillServiceUid());
@@ -6724,7 +6728,7 @@
             SystemClock.elapsedRealtime() - mStartTime);
         mFillRequestEventLogger.logAndEndEvent();
         mFillResponseEventLogger.logAndEndEvent();
-        mPresentationStatsEventLogger.logAndEndEvent();
+        mPresentationStatsEventLogger.logAndEndEvent("log all events");
         mSaveEventLogger.logAndEndEvent();
         mSessionCommittedEventLogger.logAndEndEvent();
     }
diff --git a/services/autofill/java/com/android/server/autofill/ui/AutoFillUI.java b/services/autofill/java/com/android/server/autofill/ui/AutoFillUI.java
index a10039f..2446a6d 100644
--- a/services/autofill/java/com/android/server/autofill/ui/AutoFillUI.java
+++ b/services/autofill/java/com/android/server/autofill/ui/AutoFillUI.java
@@ -461,9 +461,9 @@
                         }
 
                         @Override
-                        public void onShown() {
+                        public void onShown(int datasetsShown) {
                             if (mCallback != null) {
-                                mCallback.onShown(UI_TYPE_DIALOG, response.getDatasets().size());
+                                mCallback.onShown(UI_TYPE_DIALOG, datasetsShown);
                             }
                         }
 
diff --git a/services/autofill/java/com/android/server/autofill/ui/DialogFillUi.java b/services/autofill/java/com/android/server/autofill/ui/DialogFillUi.java
index 5a71b89..c7b6be6 100644
--- a/services/autofill/java/com/android/server/autofill/ui/DialogFillUi.java
+++ b/services/autofill/java/com/android/server/autofill/ui/DialogFillUi.java
@@ -85,7 +85,7 @@
         void onDatasetPicked(@NonNull Dataset dataset);
         void onDismissed();
         void onCanceled();
-        void onShown();
+        void onShown(int datasetsShown);
         void startIntentSender(IntentSender intentSender);
     }
 
@@ -155,7 +155,8 @@
         mDialog.setContentView(decor);
         setDialogParamsAsBottomSheet();
         mDialog.setOnCancelListener((d) -> mCallback.onCanceled());
-        mDialog.setOnShowListener((d) -> mCallback.onShown());
+        int datasetsShown = (mAdapter != null) ? mAdapter.getCount() : 0;
+        mDialog.setOnShowListener((d) -> mCallback.onShown(datasetsShown));
         show();
     }
 
diff --git a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java
index 2db5443..1be352e 100644
--- a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java
+++ b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java
@@ -137,11 +137,6 @@
                     | DisplayManager.VIRTUAL_DISPLAY_FLAG_SUPPORTS_TOUCH
                     | DisplayManager.VIRTUAL_DISPLAY_FLAG_OWN_FOCUS;
 
-    private static final int DEFAULT_VIRTUAL_DISPLAY_FLAGS_PRE_VIC =
-            DisplayManager.VIRTUAL_DISPLAY_FLAG_PUBLIC
-                    | DisplayManager.VIRTUAL_DISPLAY_FLAG_ROTATES_WITH_CONTENT
-                    | DisplayManager.VIRTUAL_DISPLAY_FLAG_OWN_CONTENT_ONLY;
-
     private static final String PERSISTENT_ID_PREFIX_CDM_ASSOCIATION = "companion:";
 
     /**
@@ -373,9 +368,6 @@
         }
 
         int flags = DEFAULT_VIRTUAL_DISPLAY_FLAGS;
-        if (!Flags.consistentDisplayFlags()) {
-            flags |= DEFAULT_VIRTUAL_DISPLAY_FLAGS_PRE_VIC;
-        }
         if (mParams.getLockState() == VirtualDeviceParams.LOCK_STATE_ALWAYS_UNLOCKED) {
             flags |= DisplayManager.VIRTUAL_DISPLAY_FLAG_ALWAYS_UNLOCKED;
         }
@@ -1254,10 +1246,6 @@
     // as the virtual display doesn't have any focused windows. Hence, call this for
     // associating any input device to the source display if the input device emits any key events.
     private int getTargetDisplayIdForInput(int displayId) {
-        if (!Flags.interactiveScreenMirror()) {
-            return displayId;
-        }
-
         DisplayManagerInternal displayManager = LocalServices.getService(
                 DisplayManagerInternal.class);
         int mirroredDisplayId = displayManager.getDisplayIdToMirror(displayId);
@@ -1313,9 +1301,9 @@
         int displayId;
         displayId = mDisplayManagerInternal.createVirtualDisplay(virtualDisplayConfig, callback,
                 this, gwpc, packageName);
-        gwpc.setDisplayId(displayId, /* isMirrorDisplay= */ Flags.interactiveScreenMirror()
-                && mDisplayManagerInternal.getDisplayIdToMirror(displayId)
-                != Display.INVALID_DISPLAY);
+        boolean isMirrorDisplay =
+                mDisplayManagerInternal.getDisplayIdToMirror(displayId) != Display.INVALID_DISPLAY;
+        gwpc.setDisplayId(displayId, isMirrorDisplay);
 
         boolean showPointer;
         synchronized (mVirtualDeviceLock) {
diff --git a/services/contentcapture/java/com/android/server/contentprotection/OWNERS b/services/contentcapture/java/com/android/server/contentprotection/OWNERS
new file mode 100644
index 0000000..3d09da3
--- /dev/null
+++ b/services/contentcapture/java/com/android/server/contentprotection/OWNERS
@@ -0,0 +1,4 @@
+# Bug component: 1040349
+
+include /core/java/android/view/contentprotection/OWNERS
+
diff --git a/services/core/java/com/android/server/OWNERS b/services/core/java/com/android/server/OWNERS
index a2bbff0..37dddc6 100644
--- a/services/core/java/com/android/server/OWNERS
+++ b/services/core/java/com/android/server/OWNERS
@@ -10,9 +10,6 @@
 # Zram writeback
 per-file ZramWriteback.java = minchan@google.com, rajekumar@google.com
 
-# Userspace reboot
-per-file UserspaceRebootLogger.java = ioffe@google.com, dvander@google.com
-
 # ServiceWatcher
 per-file ServiceWatcher.java = sooniln@google.com
 
diff --git a/services/core/java/com/android/server/PackageWatchdog.java b/services/core/java/com/android/server/PackageWatchdog.java
index 0815384..e84250d 100644
--- a/services/core/java/com/android/server/PackageWatchdog.java
+++ b/services/core/java/com/android/server/PackageWatchdog.java
@@ -309,13 +309,14 @@
      */
     public void registerHealthObserver(PackageHealthObserver observer) {
         synchronized (mLock) {
-            ObserverInternal internalObserver = mAllObservers.get(observer.getName());
+            ObserverInternal internalObserver = mAllObservers.get(observer.getUniqueIdentifier());
             if (internalObserver != null) {
                 internalObserver.registeredObserver = observer;
             } else {
-                internalObserver = new ObserverInternal(observer.getName(), new ArrayList<>());
+                internalObserver = new ObserverInternal(observer.getUniqueIdentifier(),
+                        new ArrayList<>());
                 internalObserver.registeredObserver = observer;
-                mAllObservers.put(observer.getName(), internalObserver);
+                mAllObservers.put(observer.getUniqueIdentifier(), internalObserver);
                 syncState("added new observer");
             }
         }
@@ -342,12 +343,12 @@
     public void startObservingHealth(PackageHealthObserver observer, List<String> packageNames,
             long durationMs) {
         if (packageNames.isEmpty()) {
-            Slog.wtf(TAG, "No packages to observe, " + observer.getName());
+            Slog.wtf(TAG, "No packages to observe, " + observer.getUniqueIdentifier());
             return;
         }
         if (durationMs < 1) {
             Slog.wtf(TAG, "Invalid duration " + durationMs + "ms for observer "
-                    + observer.getName() + ". Not observing packages " + packageNames);
+                    + observer.getUniqueIdentifier() + ". Not observing packages " + packageNames);
             durationMs = DEFAULT_OBSERVING_DURATION_MS;
         }
 
@@ -374,14 +375,14 @@
             syncState("observing new packages");
 
             synchronized (mLock) {
-                ObserverInternal oldObserver = mAllObservers.get(observer.getName());
+                ObserverInternal oldObserver = mAllObservers.get(observer.getUniqueIdentifier());
                 if (oldObserver == null) {
-                    Slog.d(TAG, observer.getName() + " started monitoring health "
+                    Slog.d(TAG, observer.getUniqueIdentifier() + " started monitoring health "
                             + "of packages " + packageNames);
-                    mAllObservers.put(observer.getName(),
-                            new ObserverInternal(observer.getName(), packages));
+                    mAllObservers.put(observer.getUniqueIdentifier(),
+                            new ObserverInternal(observer.getUniqueIdentifier(), packages));
                 } else {
-                    Slog.d(TAG, observer.getName() + " added the following "
+                    Slog.d(TAG, observer.getUniqueIdentifier() + " added the following "
                             + "packages to monitor " + packageNames);
                     oldObserver.updatePackagesLocked(packages);
                 }
@@ -405,9 +406,9 @@
     public void unregisterHealthObserver(PackageHealthObserver observer) {
         mLongTaskHandler.post(() -> {
             synchronized (mLock) {
-                mAllObservers.remove(observer.getName());
+                mAllObservers.remove(observer.getUniqueIdentifier());
             }
-            syncState("unregistering observer: " + observer.getName());
+            syncState("unregistering observer: " + observer.getUniqueIdentifier());
         });
     }
 
@@ -781,7 +782,7 @@
          * Identifier for the observer, should not change across device updates otherwise the
          * watchdog may drop observing packages with the old name.
          */
-        String getName();
+        String getUniqueIdentifier();
 
         /**
          * An observer will not be pruned if this is set, even if the observer is not explicitly
diff --git a/services/core/java/com/android/server/RescueParty.java b/services/core/java/com/android/server/RescueParty.java
index c2cb5e9..bba97fa 100644
--- a/services/core/java/com/android/server/RescueParty.java
+++ b/services/core/java/com/android/server/RescueParty.java
@@ -917,7 +917,7 @@
         }
 
         @Override
-        public String getName() {
+        public String getUniqueIdentifier() {
             return NAME;
         }
 
diff --git a/services/core/java/com/android/server/UserspaceRebootLogger.java b/services/core/java/com/android/server/UserspaceRebootLogger.java
deleted file mode 100644
index 89327b5..0000000
--- a/services/core/java/com/android/server/UserspaceRebootLogger.java
+++ /dev/null
@@ -1,168 +0,0 @@
-/*
- * Copyright (C) 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server;
-
-import static com.android.internal.util.FrameworkStatsLog.USERSPACE_REBOOT_REPORTED__OUTCOME__FAILED_SHUTDOWN_SEQUENCE_ABORTED;
-import static com.android.internal.util.FrameworkStatsLog.USERSPACE_REBOOT_REPORTED__OUTCOME__FAILED_USERDATA_REMOUNT;
-import static com.android.internal.util.FrameworkStatsLog.USERSPACE_REBOOT_REPORTED__OUTCOME__FAILED_USERSPACE_REBOOT_WATCHDOG_TRIGGERED;
-import static com.android.internal.util.FrameworkStatsLog.USERSPACE_REBOOT_REPORTED__OUTCOME__OUTCOME_UNKNOWN;
-import static com.android.internal.util.FrameworkStatsLog.USERSPACE_REBOOT_REPORTED__OUTCOME__SUCCESS;
-import static com.android.internal.util.FrameworkStatsLog.USERSPACE_REBOOT_REPORTED__USER_ENCRYPTION_STATE__LOCKED;
-import static com.android.internal.util.FrameworkStatsLog.USERSPACE_REBOOT_REPORTED__USER_ENCRYPTION_STATE__UNLOCKED;
-
-import android.os.PowerManager;
-import android.os.SystemClock;
-import android.os.SystemProperties;
-import android.text.TextUtils;
-import android.util.Slog;
-
-import com.android.internal.util.FrameworkStatsLog;
-
-import java.util.concurrent.Executor;
-
-/**
- * Utility class to help abstract logging {@code UserspaceRebootReported} atom.
- */
-public final class UserspaceRebootLogger {
-
-    private static final String TAG = "UserspaceRebootLogger";
-
-    private static final String USERSPACE_REBOOT_SHOULD_LOG_PROPERTY =
-            "persist.sys.userspace_reboot.log.should_log";
-    private static final String USERSPACE_REBOOT_LAST_STARTED_PROPERTY =
-            "sys.userspace_reboot.log.last_started";
-    private static final String USERSPACE_REBOOT_LAST_FINISHED_PROPERTY =
-            "sys.userspace_reboot.log.last_finished";
-    private static final String LAST_BOOT_REASON_PROPERTY = "sys.boot.reason.last";
-
-    private UserspaceRebootLogger() {}
-
-    /**
-     * Modifies internal state to note that {@code UserspaceRebootReported} atom needs to be
-     * logged on the next successful boot.
-     *
-     * <p>This call should only be made on devices supporting userspace reboot.
-     */
-    public static void noteUserspaceRebootWasRequested() {
-        if (!PowerManager.isRebootingUserspaceSupportedImpl()) {
-            Slog.wtf(TAG, "noteUserspaceRebootWasRequested: Userspace reboot is not supported.");
-            return;
-        }
-
-        SystemProperties.set(USERSPACE_REBOOT_SHOULD_LOG_PROPERTY, "1");
-        SystemProperties.set(USERSPACE_REBOOT_LAST_STARTED_PROPERTY,
-                String.valueOf(SystemClock.elapsedRealtime()));
-    }
-
-    /**
-     * Updates internal state on boot after successful userspace reboot.
-     *
-     * <p>Should be called right before framework sets {@code sys.boot_completed} property.
-     *
-     * <p>This call should only be made on devices supporting userspace reboot.
-     */
-    public static void noteUserspaceRebootSuccess() {
-        if (!PowerManager.isRebootingUserspaceSupportedImpl()) {
-            Slog.wtf(TAG, "noteUserspaceRebootSuccess: Userspace reboot is not supported.");
-            return;
-        }
-
-        SystemProperties.set(USERSPACE_REBOOT_LAST_FINISHED_PROPERTY,
-                String.valueOf(SystemClock.elapsedRealtime()));
-    }
-
-    /**
-     * Returns {@code true} if {@code UserspaceRebootReported} atom should be logged.
-     *
-     * <p>On devices that do not support userspace reboot this method will always return {@code
-     * false}.
-     */
-    public static boolean shouldLogUserspaceRebootEvent() {
-        if (!PowerManager.isRebootingUserspaceSupportedImpl()) {
-            return false;
-        }
-
-        return SystemProperties.getBoolean(USERSPACE_REBOOT_SHOULD_LOG_PROPERTY, false);
-    }
-
-    /**
-     * Asynchronously logs {@code UserspaceRebootReported} on the given {@code executor}.
-     *
-     * <p>Should be called in the end of {@link
-     * com.android.server.am.ActivityManagerService#finishBooting()} method, after framework have
-     * tried to proactivelly unlock storage of the primary user.
-     *
-     * <p>This call should only be made on devices supporting userspace reboot.
-     */
-    public static void logEventAsync(boolean userUnlocked, Executor executor) {
-        if (!PowerManager.isRebootingUserspaceSupportedImpl()) {
-            Slog.wtf(TAG, "logEventAsync: Userspace reboot is not supported.");
-            return;
-        }
-
-        final int outcome = computeOutcome();
-        final long durationMillis;
-        if (outcome == USERSPACE_REBOOT_REPORTED__OUTCOME__SUCCESS) {
-            durationMillis = SystemProperties.getLong(USERSPACE_REBOOT_LAST_FINISHED_PROPERTY, 0)
-                    - SystemProperties.getLong(USERSPACE_REBOOT_LAST_STARTED_PROPERTY, 0);
-        } else {
-            durationMillis = 0;
-        }
-        final int encryptionState =
-                userUnlocked
-                    ? USERSPACE_REBOOT_REPORTED__USER_ENCRYPTION_STATE__UNLOCKED
-                    : USERSPACE_REBOOT_REPORTED__USER_ENCRYPTION_STATE__LOCKED;
-        executor.execute(
-                () -> {
-                    Slog.i(TAG, "Logging UserspaceRebootReported atom: { outcome: " + outcome
-                            + " durationMillis: " + durationMillis + " encryptionState: "
-                            + encryptionState + " }");
-                    FrameworkStatsLog.write(FrameworkStatsLog.USERSPACE_REBOOT_REPORTED, outcome,
-                            durationMillis, encryptionState);
-                    SystemProperties.set(USERSPACE_REBOOT_SHOULD_LOG_PROPERTY, "");
-                });
-    }
-
-    private static int computeOutcome() {
-        if (SystemProperties.getLong(USERSPACE_REBOOT_LAST_STARTED_PROPERTY, -1) != -1) {
-            return USERSPACE_REBOOT_REPORTED__OUTCOME__SUCCESS;
-        }
-        String reason = TextUtils.emptyIfNull(SystemProperties.get(LAST_BOOT_REASON_PROPERTY, ""));
-        if (reason.startsWith("reboot,")) {
-            reason = reason.substring("reboot".length());
-        }
-        if (reason.startsWith("userspace_failed,watchdog_fork")) {
-            return USERSPACE_REBOOT_REPORTED__OUTCOME__FAILED_SHUTDOWN_SEQUENCE_ABORTED;
-        }
-        if (reason.startsWith("userspace_failed,shutdown_aborted")) {
-            return USERSPACE_REBOOT_REPORTED__OUTCOME__FAILED_SHUTDOWN_SEQUENCE_ABORTED;
-        }
-        if (reason.startsWith("mount_userdata_failed")) {
-            return USERSPACE_REBOOT_REPORTED__OUTCOME__FAILED_USERDATA_REMOUNT;
-        }
-        if (reason.startsWith("userspace_failed,init_user0")) {
-            return USERSPACE_REBOOT_REPORTED__OUTCOME__FAILED_USERDATA_REMOUNT;
-        }
-        if (reason.startsWith("userspace_failed,enablefilecrypto")) {
-            return USERSPACE_REBOOT_REPORTED__OUTCOME__FAILED_USERDATA_REMOUNT;
-        }
-        if (reason.startsWith("userspace_failed,watchdog_triggered")) {
-            return USERSPACE_REBOOT_REPORTED__OUTCOME__FAILED_USERSPACE_REBOOT_WATCHDOG_TRIGGERED;
-        }
-        return USERSPACE_REBOOT_REPORTED__OUTCOME__OUTCOME_UNKNOWN;
-    }
-}
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 4a18cb1..68d9221 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -372,7 +372,6 @@
 import android.provider.DeviceConfig;
 import android.provider.Settings;
 import android.server.ServerProtoEnums;
-import android.sysprop.InitProperties;
 import android.system.Os;
 import android.system.OsConstants;
 import android.telephony.TelephonyManager;
@@ -451,7 +450,6 @@
 import com.android.server.SystemService;
 import com.android.server.SystemServiceManager;
 import com.android.server.ThreadPriorityBooster;
-import com.android.server.UserspaceRebootLogger;
 import com.android.server.Watchdog;
 import com.android.server.am.ComponentAliasResolver.Resolution;
 import com.android.server.am.LowMemDetector.MemFactor;
@@ -2360,20 +2358,6 @@
         }
     }
 
-    private void maybeLogUserspaceRebootEvent() {
-        if (!UserspaceRebootLogger.shouldLogUserspaceRebootEvent()) {
-            return;
-        }
-        final int userId = mUserController.getCurrentUserId();
-        if (userId != UserHandle.USER_SYSTEM) {
-            // Only log for user0.
-            return;
-        }
-        // TODO(b/148767783): should we check all profiles under user0?
-        UserspaceRebootLogger.logEventAsync(StorageManager.isCeStorageUnlocked(userId),
-                BackgroundThread.getExecutor());
-    }
-
     /**
      * Encapsulates global settings related to hidden API enforcement behaviour, including tracking
      * the latest value via a content observer.
@@ -5323,12 +5307,6 @@
             // Start looking for apps that are abusing wake locks.
             Message nmsg = mHandler.obtainMessage(CHECK_EXCESSIVE_POWER_USE_MSG);
             mHandler.sendMessageDelayed(nmsg, mConstants.POWER_CHECK_INTERVAL);
-            // Check if we are performing userspace reboot before setting sys.boot_completed to
-            // avoid race with init reseting sys.init.userspace_reboot.in_progress once sys
-            // .boot_completed is 1.
-            if (InitProperties.userspace_reboot_in_progress().orElse(false)) {
-                UserspaceRebootLogger.noteUserspaceRebootSuccess();
-            }
             // Tell anyone interested that we are done booting!
             SystemProperties.set("sys.boot_completed", "1");
             SystemProperties.set("dev.bootcomplete", "1");
@@ -5352,7 +5330,6 @@
                             }, mConstants.FULL_PSS_MIN_INTERVAL);
                         }
                     });
-            maybeLogUserspaceRebootEvent();
             mUserController.scheduleStartProfiles();
         }
         // UART is on if init's console service is running, send a warning notification.
@@ -15335,12 +15312,17 @@
         final int cookie = traceBroadcastIntentBegin(intent, resultTo, ordered, sticky,
                 callingUid, realCallingUid, userId);
         try {
+            final BroadcastSentEventRecord broadcastSentEventRecord =
+                    new BroadcastSentEventRecord();
             final int res = broadcastIntentLockedTraced(callerApp, callerPackage, callerFeatureId,
                     intent, resolvedType, resultToApp, resultTo, resultCode, resultData,
                     resultExtras, requiredPermissions, excludedPermissions, excludedPackages,
                     appOp, BroadcastOptions.fromBundleNullable(bOptions), ordered, sticky,
                     callingPid, callingUid, realCallingUid, realCallingPid, userId,
-                    backgroundStartPrivileges, broadcastAllowList, filterExtrasForReceiver);
+                    backgroundStartPrivileges, broadcastAllowList, filterExtrasForReceiver,
+                    broadcastSentEventRecord);
+            broadcastSentEventRecord.setResult(res);
+            broadcastSentEventRecord.logToStatsd();
             return res;
         } finally {
             traceBroadcastIntentEnd(cookie);
@@ -15388,7 +15370,8 @@
             int callingUid, int realCallingUid, int realCallingPid, int userId,
             BackgroundStartPrivileges backgroundStartPrivileges,
             @Nullable int[] broadcastAllowList,
-            @Nullable BiFunction<Integer, Bundle, Bundle> filterExtrasForReceiver) {
+            @Nullable BiFunction<Integer, Bundle, Bundle> filterExtrasForReceiver,
+            @NonNull BroadcastSentEventRecord broadcastSentEventRecord) {
         // Ensure all internal loopers are registered for idle checks
         BroadcastLoopers.addMyLooper();
 
@@ -15421,6 +15404,17 @@
         }
 
         intent = new Intent(intent);
+        broadcastSentEventRecord.setIntent(intent);
+        broadcastSentEventRecord.setOriginalIntentFlags(intent.getFlags());
+        broadcastSentEventRecord.setSenderUid(callingUid);
+        broadcastSentEventRecord.setRealSenderUid(realCallingUid);
+        broadcastSentEventRecord.setSticky(sticky);
+        broadcastSentEventRecord.setOrdered(ordered);
+        broadcastSentEventRecord.setResultRequested(resultTo != null);
+        final int callerAppProcessState = getRealProcessStateLocked(callerApp, realCallingPid);
+        broadcastSentEventRecord.setSenderProcState(callerAppProcessState);
+        broadcastSentEventRecord.setSenderUidState(getRealUidStateLocked(callerApp,
+                realCallingPid));
 
         final boolean callerInstantApp = isInstantApp(callerApp, callerPackage, callingUid);
         // Instant Apps cannot use FLAG_RECEIVER_VISIBLE_TO_INSTANT_APPS
@@ -15914,7 +15908,6 @@
             }
         }
 
-        final int callerAppProcessState = getRealProcessStateLocked(callerApp, realCallingPid);
         // Add to the sticky list if requested.
         if (sticky) {
             if (checkPermission(android.Manifest.permission.BROADCAST_STICKY,
@@ -16154,6 +16147,7 @@
                     ordered, sticky, false, userId,
                     backgroundStartPrivileges, timeoutExempt, filterExtrasForReceiver,
                     callerAppProcessState);
+            broadcastSentEventRecord.setBroadcastRecord(r);
 
             if (DEBUG_BROADCAST) Slog.v(TAG_BROADCAST, "Enqueueing ordered broadcast " + r);
             queue.enqueueBroadcastLocked(r);
@@ -16210,6 +16204,22 @@
         return PROCESS_STATE_NONEXISTENT;
     }
 
+    @GuardedBy("this")
+    private int getRealUidStateLocked(ProcessRecord app, int pid) {
+        if (app == null) {
+            synchronized (mPidsSelfLocked) {
+                app = mPidsSelfLocked.get(pid);
+            }
+        }
+        if (app != null && app.getThread() != null && !app.isKilled()) {
+            final UidRecord uidRecord = app.getUidRecord();
+            if (uidRecord != null) {
+                return uidRecord.getCurProcState();
+            }
+        }
+        return PROCESS_STATE_NONEXISTENT;
+    }
+
     @VisibleForTesting
     ArrayList<StickyBroadcast> getStickyBroadcastsForTest(String action, int userId) {
         synchronized (mStickyBroadcasts) {
diff --git a/services/core/java/com/android/server/am/BroadcastRecord.java b/services/core/java/com/android/server/am/BroadcastRecord.java
index edb04c5..f908c67 100644
--- a/services/core/java/com/android/server/am/BroadcastRecord.java
+++ b/services/core/java/com/android/server/am/BroadcastRecord.java
@@ -53,6 +53,7 @@
 import android.os.SystemClock;
 import android.os.UserHandle;
 import android.util.ArrayMap;
+import android.util.IntArray;
 import android.util.PrintWriterPrinter;
 import android.util.TimeUtils;
 import android.util.proto.ProtoOutputStream;
@@ -940,6 +941,46 @@
         return type;
     }
 
+    int[] calculateTypesForLogging() {
+        final IntArray types = new IntArray();
+        if (isForeground()) {
+            types.add(BROADCAST_TYPE_FOREGROUND);
+        } else {
+            types.add(BROADCAST_TYPE_BACKGROUND);
+        }
+        if (alarm) {
+            types.add(BROADCAST_TYPE_ALARM);
+        }
+        if (interactive) {
+            types.add(BROADCAST_TYPE_INTERACTIVE);
+        }
+        if (ordered) {
+            types.add(BROADCAST_TYPE_ORDERED);
+        }
+        if (prioritized) {
+            types.add(BROADCAST_TYPE_PRIORITIZED);
+        }
+        if (resultTo != null) {
+            types.add(BROADCAST_TYPE_RESULT_TO);
+        }
+        if (deferUntilActive) {
+            types.add(BROADCAST_TYPE_DEFERRABLE_UNTIL_ACTIVE);
+        }
+        if (pushMessage) {
+            types.add(BROADCAST_TYPE_PUSH_MESSAGE);
+        }
+        if (pushMessageOverQuota) {
+            types.add(BROADCAST_TYPE_PUSH_MESSAGE_OVER_QUOTA);
+        }
+        if (sticky) {
+            types.add(BROADCAST_TYPE_STICKY);
+        }
+        if (initialSticky) {
+            types.add(BROADCAST_TYPE_INITIAL_STICKY);
+        }
+        return types.toArray();
+    }
+
     public BroadcastRecord maybeStripForHistory() {
         if (!intent.canStripForHistory()) {
             return this;
diff --git a/services/core/java/com/android/server/am/BroadcastSentEventRecord.java b/services/core/java/com/android/server/am/BroadcastSentEventRecord.java
new file mode 100644
index 0000000..f2ac6d5
--- /dev/null
+++ b/services/core/java/com/android/server/am/BroadcastSentEventRecord.java
@@ -0,0 +1,134 @@
+/*
+ * 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.am;
+
+import static android.app.AppProtoEnums.BROADCAST_TYPE_ORDERED;
+import static android.app.AppProtoEnums.BROADCAST_TYPE_RESULT_TO;
+import static android.app.AppProtoEnums.BROADCAST_TYPE_STICKY;
+
+import static com.android.internal.util.FrameworkStatsLog.BROADCAST_SENT;
+import static com.android.internal.util.FrameworkStatsLog.BROADCAST_SENT__RESULT__FAILED_STICKY_CANT_HAVE_PERMISSION;
+import static com.android.internal.util.FrameworkStatsLog.BROADCAST_SENT__RESULT__FAILED_USER_STOPPED;
+import static com.android.internal.util.FrameworkStatsLog.BROADCAST_SENT__RESULT__SUCCESS;
+import static com.android.internal.util.FrameworkStatsLog.BROADCAST_SENT__RESULT__UNKNOWN;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.app.ActivityManager;
+import android.content.Intent;
+import android.util.IntArray;
+
+import com.android.internal.util.FrameworkStatsLog;
+
+final class BroadcastSentEventRecord {
+    @NonNull private Intent mIntent;
+    private int mOriginalIntentFlags;
+    private int mSenderUid;
+    private int mRealSenderUid;
+    private boolean mSticky;
+    private boolean mOrdered;
+    private boolean mResultRequested;
+    private int mSenderProcState;
+    private int mSenderUidState;
+    @Nullable private BroadcastRecord mBroadcastRecord;
+    private int mResult;
+
+    public void setIntent(@NonNull Intent intent) {
+        mIntent = intent;
+    }
+
+    public void setSenderUid(int uid) {
+        mSenderUid = uid;
+    }
+
+    public void setRealSenderUid(int uid) {
+        mRealSenderUid = uid;
+    }
+
+    public void setOriginalIntentFlags(int flags) {
+        mOriginalIntentFlags = flags;
+    }
+
+    public void setSticky(boolean sticky) {
+        mSticky = sticky;
+    }
+
+    public void setOrdered(boolean ordered) {
+        mOrdered = ordered;
+    }
+
+    public void setResultRequested(boolean resultRequested) {
+        mResultRequested = resultRequested;
+    }
+
+    public void setSenderProcState(int procState) {
+        mSenderProcState = procState;
+    }
+
+    public void setSenderUidState(int procState) {
+        mSenderUidState = procState;
+    }
+
+    public void setBroadcastRecord(@NonNull BroadcastRecord record) {
+        mBroadcastRecord = record;
+    }
+
+    public void setResult(int result) {
+        mResult = result;
+    }
+
+    public void logToStatsd() {
+        if (Flags.logBroadcastSentEvent()) {
+            int loggingResult = switch (mResult) {
+                case ActivityManager.BROADCAST_SUCCESS ->
+                        BROADCAST_SENT__RESULT__SUCCESS;
+                case ActivityManager.BROADCAST_STICKY_CANT_HAVE_PERMISSION ->
+                        BROADCAST_SENT__RESULT__FAILED_STICKY_CANT_HAVE_PERMISSION;
+                case ActivityManager.BROADCAST_FAILED_USER_STOPPED ->
+                        BROADCAST_SENT__RESULT__FAILED_USER_STOPPED;
+                default -> BROADCAST_SENT__RESULT__UNKNOWN;
+            };
+            int[] types = calculateTypesForLogging();
+            FrameworkStatsLog.write(BROADCAST_SENT, mIntent.getAction(), mIntent.getFlags(),
+                    mOriginalIntentFlags, mSenderUid, mRealSenderUid, mIntent.getPackage() != null,
+                    mIntent.getComponent() != null,
+                    mBroadcastRecord != null ? mBroadcastRecord.receivers.size() : 0,
+                    loggingResult,
+                    mBroadcastRecord != null ? mBroadcastRecord.getDeliveryGroupPolicy() : 0,
+                    ActivityManager.processStateAmToProto(mSenderProcState),
+                    ActivityManager.processStateAmToProto(mSenderUidState), types);
+        }
+    }
+
+    private int[] calculateTypesForLogging() {
+        if (mBroadcastRecord != null) {
+            return mBroadcastRecord.calculateTypesForLogging();
+        } else {
+            final IntArray types = new IntArray();
+            if (mSticky) {
+                types.add(BROADCAST_TYPE_STICKY);
+            }
+            if (mOrdered) {
+                types.add(BROADCAST_TYPE_ORDERED);
+            }
+            if (mResultRequested) {
+                types.add(BROADCAST_TYPE_RESULT_TO);
+            }
+            return types.toArray();
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/am/flags.aconfig b/services/core/java/com/android/server/am/flags.aconfig
index 5315167..3334393 100644
--- a/services/core/java/com/android/server/am/flags.aconfig
+++ b/services/core/java/com/android/server/am/flags.aconfig
@@ -184,3 +184,14 @@
     description: "Defer submitting binder calls to paused processes."
     bug: "327038797"
 }
+
+flag {
+    name: "log_broadcast_sent_event"
+    namespace: "backstage_power"
+    description: "Log the broadcast send event to Statsd"
+    bug: "355261986"
+    is_fixed_read_only: true
+    metadata {
+        purpose: PURPOSE_BUGFIX
+    }
+}
\ No newline at end of file
diff --git a/services/core/java/com/android/server/appop/AppOpsService.java b/services/core/java/com/android/server/appop/AppOpsService.java
index f61bd60..154b52b 100644
--- a/services/core/java/com/android/server/appop/AppOpsService.java
+++ b/services/core/java/com/android/server/appop/AppOpsService.java
@@ -1413,6 +1413,22 @@
     }
 
     public void uidRemoved(int uid) {
+        if (Flags.dontRemoveExistingUidStates()) {
+            // b/358365471 If apps sharing UID are installed on multiple users and only one of
+            // them is installed for a single user while keeping the others we observe this
+            // subroutine get invoked incorrectly since the UID still exists.
+            final long token = Binder.clearCallingIdentity();
+            try {
+                String uidName = getPackageManagerInternal().getNameForUid(uid);
+                if (uidName != null) {
+                    Slog.e(TAG, "Tried to remove existing UID. uid: " + uid + " name: " + uidName);
+                    return;
+                }
+            } finally {
+                Binder.restoreCallingIdentity(token);
+            }
+        }
+
         synchronized (this) {
             if (mUidStates.indexOfKey(uid) >= 0) {
                 mUidStates.get(uid).clear();
diff --git a/services/core/java/com/android/server/biometrics/PreAuthInfo.java b/services/core/java/com/android/server/biometrics/PreAuthInfo.java
index 0bd22f3..f0da67b 100644
--- a/services/core/java/com/android/server/biometrics/PreAuthInfo.java
+++ b/services/core/java/com/android/server/biometrics/PreAuthInfo.java
@@ -77,13 +77,15 @@
     private final int mBiometricStrengthRequested;
     private final BiometricCameraManager mBiometricCameraManager;
     private final boolean mOnlyMandatoryBiometricsRequested;
+    private final boolean mIsMandatoryBiometricsAuthentication;
 
     private PreAuthInfo(boolean biometricRequested, int biometricStrengthRequested,
             boolean credentialRequested, List<BiometricSensor> eligibleSensors,
             List<Pair<BiometricSensor, Integer>> ineligibleSensors, boolean credentialAvailable,
             PromptInfo promptInfo, int userId, Context context,
             BiometricCameraManager biometricCameraManager,
-            boolean isOnlyMandatoryBiometricsRequested) {
+            boolean isOnlyMandatoryBiometricsRequested,
+            boolean isMandatoryBiometricsAuthentication) {
         mBiometricRequested = biometricRequested;
         mBiometricStrengthRequested = biometricStrengthRequested;
         mBiometricCameraManager = biometricCameraManager;
@@ -97,6 +99,7 @@
         this.userId = userId;
         this.context = context;
         this.mOnlyMandatoryBiometricsRequested = isOnlyMandatoryBiometricsRequested;
+        this.mIsMandatoryBiometricsAuthentication = isMandatoryBiometricsAuthentication;
     }
 
     static PreAuthInfo create(ITrustManager trustManager,
@@ -110,10 +113,12 @@
 
         final boolean isOnlyMandatoryBiometricsRequested = promptInfo.getAuthenticators()
                 == BiometricManager.Authenticators.MANDATORY_BIOMETRICS;
+        boolean isMandatoryBiometricsAuthentication = false;
 
         if (dropCredentialFallback(promptInfo.getAuthenticators(),
                 settingObserver.getMandatoryBiometricsEnabledAndRequirementsSatisfiedForUser(
                         userId), trustManager)) {
+            isMandatoryBiometricsAuthentication = true;
             promptInfo.setAuthenticators(BiometricManager.Authenticators.BIOMETRIC_STRONG);
             promptInfo.setNegativeButtonText(context.getString(R.string.cancel));
         }
@@ -166,7 +171,8 @@
 
         return new PreAuthInfo(biometricRequested, requestedStrength, credentialRequested,
                 eligibleSensors, ineligibleSensors, credentialAvailable, promptInfo, userId,
-                context, biometricCameraManager, isOnlyMandatoryBiometricsRequested);
+                context, biometricCameraManager, isOnlyMandatoryBiometricsRequested,
+                isMandatoryBiometricsAuthentication);
     }
 
     private static boolean dropCredentialFallback(int authenticators,
@@ -387,25 +393,6 @@
                     status = CREDENTIAL_NOT_ENROLLED;
                 }
             }
-        } else if (Flags.mandatoryBiometrics() && mOnlyMandatoryBiometricsRequested) {
-            if (!eligibleSensors.isEmpty()) {
-                for (BiometricSensor sensor : eligibleSensors) {
-                    modality |= sensor.modality;
-                }
-
-                if (modality == TYPE_FACE && cameraPrivacyEnabled) {
-                    // If the only modality requested is face, credential is unavailable,
-                    // and the face sensor privacy is enabled then return
-                    // BIOMETRIC_SENSOR_PRIVACY_ENABLED.
-                    //
-                    // Note: This sensor will not be eligible for calls to authenticate.
-                    status = BIOMETRIC_SENSOR_PRIVACY_ENABLED;
-                } else {
-                    status = AUTHENTICATOR_OK;
-                }
-            } else {
-                status = MANDATORY_BIOMETRIC_UNAVAILABLE_ERROR;
-            }
         } else if (mBiometricRequested) {
             if (!eligibleSensors.isEmpty()) {
                 for (BiometricSensor sensor : eligibleSensors) {
@@ -434,6 +421,9 @@
         } else if (credentialRequested) {
             modality |= TYPE_CREDENTIAL;
             status = credentialAvailable ? AUTHENTICATOR_OK : CREDENTIAL_NOT_ENROLLED;
+        } else if (Flags.mandatoryBiometrics() && mOnlyMandatoryBiometricsRequested
+                && !mIsMandatoryBiometricsAuthentication) {
+            status = MANDATORY_BIOMETRIC_UNAVAILABLE_ERROR;
         } else {
             // This should not be possible via the public API surface and is here mainly for
             // "correctness". An exception should have been thrown before getting here.
diff --git a/services/core/java/com/android/server/biometrics/Utils.java b/services/core/java/com/android/server/biometrics/Utils.java
index fb8a81b..8711214 100644
--- a/services/core/java/com/android/server/biometrics/Utils.java
+++ b/services/core/java/com/android/server/biometrics/Utils.java
@@ -35,6 +35,7 @@
 import static com.android.server.biometrics.PreAuthInfo.BIOMETRIC_NO_HARDWARE;
 import static com.android.server.biometrics.PreAuthInfo.BIOMETRIC_SENSOR_PRIVACY_ENABLED;
 import static com.android.server.biometrics.PreAuthInfo.CREDENTIAL_NOT_ENROLLED;
+import static com.android.server.biometrics.PreAuthInfo.MANDATORY_BIOMETRIC_UNAVAILABLE_ERROR;
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
@@ -48,6 +49,7 @@
 import android.hardware.biometrics.BiometricManager;
 import android.hardware.biometrics.BiometricPrompt;
 import android.hardware.biometrics.BiometricPrompt.AuthenticationResultType;
+import android.hardware.biometrics.Flags;
 import android.hardware.biometrics.IBiometricService;
 import android.hardware.biometrics.PromptInfo;
 import android.hardware.biometrics.SensorProperties;
@@ -309,11 +311,16 @@
                 break;
             case BiometricConstants.BIOMETRIC_ERROR_LOCKOUT:
             case BiometricConstants.BIOMETRIC_ERROR_LOCKOUT_PERMANENT:
-                biometricManagerCode = BiometricManager.BIOMETRIC_SUCCESS;
+                biometricManagerCode = Flags.mandatoryBiometrics()
+                        ? BiometricManager.BIOMETRIC_ERROR_LOCKOUT
+                        : BiometricManager.BIOMETRIC_SUCCESS;
                 break;
             case BiometricConstants.BIOMETRIC_ERROR_SENSOR_PRIVACY_ENABLED:
                 biometricManagerCode = BiometricManager.BIOMETRIC_ERROR_HW_UNAVAILABLE;
                 break;
+            case BiometricConstants.BIOMETRIC_ERROR_MANDATORY_NOT_ACTIVE:
+                biometricManagerCode = BiometricManager.BIOMETRIC_ERROR_MANDATORY_NOT_ACTIVE;
+                break;
             default:
                 Slog.e(BiometricService.TAG, "Unhandled result code: " + biometricConstantsCode);
                 biometricManagerCode = BiometricManager.BIOMETRIC_ERROR_HW_UNAVAILABLE;
@@ -375,6 +382,8 @@
                 return BiometricConstants.BIOMETRIC_ERROR_LOCKOUT_PERMANENT;
             case BIOMETRIC_SENSOR_PRIVACY_ENABLED:
                 return BiometricConstants.BIOMETRIC_ERROR_SENSOR_PRIVACY_ENABLED;
+            case MANDATORY_BIOMETRIC_UNAVAILABLE_ERROR:
+                return BiometricConstants.BIOMETRIC_ERROR_MANDATORY_NOT_ACTIVE;
             case BIOMETRIC_DISABLED_BY_DEVICE_POLICY:
             case BIOMETRIC_HARDWARE_NOT_DETECTED:
             case BIOMETRIC_NOT_ENABLED_FOR_APPS:
diff --git a/services/core/java/com/android/server/biometrics/log/BiometricFrameworkStatsLogger.java b/services/core/java/com/android/server/biometrics/log/BiometricFrameworkStatsLogger.java
index f31b2e1..2c52e3d 100644
--- a/services/core/java/com/android/server/biometrics/log/BiometricFrameworkStatsLogger.java
+++ b/services/core/java/com/android/server/biometrics/log/BiometricFrameworkStatsLogger.java
@@ -241,6 +241,14 @@
                 -1 /* sensorId */);
     }
 
+    /** {@see FrameworkStatsLog.BIOMETRIC_SYSTEM_HEALTH_ISSUE_DETECTED}. */
+    public void reportFingerprintsLoe(int statsModality) {
+        FrameworkStatsLog.write(FrameworkStatsLog.BIOMETRIC_SYSTEM_HEALTH_ISSUE_DETECTED,
+                statsModality,
+                BiometricsProtoEnums.ISSUE_FINGERPRINTS_LOE,
+                -1 /* sensorId */);
+    }
+
     /** {@see FrameworkStatsLog.BIOMETRIC_FRR_NOTIFICATION}. */
     public void logFrameworkNotification(int action, int modality) {
         FrameworkStatsLog.write(FrameworkStatsLog.BIOMETRIC_FRR_NOTIFICATION,
diff --git a/services/core/java/com/android/server/biometrics/log/BiometricLogger.java b/services/core/java/com/android/server/biometrics/log/BiometricLogger.java
index ff1e5d5..9351bc0 100644
--- a/services/core/java/com/android/server/biometrics/log/BiometricLogger.java
+++ b/services/core/java/com/android/server/biometrics/log/BiometricLogger.java
@@ -296,6 +296,15 @@
         mSink.reportUnknownTemplateEnrolledFramework(mStatsModality);
     }
 
+    /** Report unknown enrollment in framework settings */
+    public void logFingerprintsLoe() {
+        if (shouldSkipLogging()) {
+            return;
+        }
+
+        mSink.reportFingerprintsLoe(mStatsModality);
+    }
+
     /**
      * Get a callback to start/stop ALS capture when the client runs. Do not create
      * multiple callbacks since there is at most one light sensor (they will all share
diff --git a/services/core/java/com/android/server/biometrics/sensors/InternalCleanupClient.java b/services/core/java/com/android/server/biometrics/sensors/InternalCleanupClient.java
index 77e27ba..7bd905b 100644
--- a/services/core/java/com/android/server/biometrics/sensors/InternalCleanupClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/InternalCleanupClient.java
@@ -161,6 +161,11 @@
 
         getLogger().logUnknownEnrollmentInHal();
 
+        if (mBiometricUtils.hasValidBiometricUserState(getContext(), getTargetUserId())
+                && Flags.notifyFingerprintsLoe()) {
+            getLogger().logFingerprintsLoe();
+        }
+
         mCurrentTask.start(mRemoveCallback);
     }
 
diff --git a/services/core/java/com/android/server/devicestate/DeviceStateManagerService.java b/services/core/java/com/android/server/devicestate/DeviceStateManagerService.java
index 38e6d82..1094bee 100644
--- a/services/core/java/com/android/server/devicestate/DeviceStateManagerService.java
+++ b/services/core/java/com/android/server/devicestate/DeviceStateManagerService.java
@@ -1002,9 +1002,9 @@
 
     /**
      * Checks if the process can cancel a device state request. If the calling process ID is not
-     * both the top app and foregrounded nor does the process ID and userID match the IDs that made
-     * the device state request, then check if this process holds the CONTROL_DEVICE_STATE
-     * permission.
+     * both the top app and foregrounded, verify that the calling process is in the foreground and
+     * that it matches the process ID and user ID that made the device state request. If neither are
+     * true, then check if this process holds the CONTROL_DEVICE_STATE permission.
      *
      * @param callingPid Process ID that is requesting this state change
      * @param callingUid UID that is requesting this state change
@@ -1018,8 +1018,8 @@
         if (Flags.deviceStateRequesterCancelState()) {
             synchronized (mLock) {
                 isAllowedToControlState =
-                        isAllowedToControlState || doCallingIdsMatchOverrideRequestIdsLocked(
-                                callingPid, callingUid);
+                        isTopApp || (isForegroundApp && doCallingIdsMatchOverrideRequestIdsLocked(
+                                callingPid, callingUid));
             }
         }
 
diff --git a/services/core/java/com/android/server/display/DisplayManagerService.java b/services/core/java/com/android/server/display/DisplayManagerService.java
index 9e905ab..55a6ce7 100644
--- a/services/core/java/com/android/server/display/DisplayManagerService.java
+++ b/services/core/java/com/android/server/display/DisplayManagerService.java
@@ -64,7 +64,6 @@
 import android.app.compat.CompatChanges;
 import android.companion.virtual.IVirtualDevice;
 import android.companion.virtual.VirtualDeviceManager;
-import android.companion.virtual.flags.Flags;
 import android.compat.annotation.ChangeId;
 import android.compat.annotation.EnabledSince;
 import android.content.BroadcastReceiver;
@@ -1633,8 +1632,7 @@
                 && (flags & VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR) != 0) {
             // Only a valid media projection or a virtual device can create a mirror virtual
             // display.
-            if (!canProjectVideo(projection)
-                    && !isMirroringSupportedByVirtualDevice(virtualDevice)) {
+            if (!canProjectVideo(projection) && virtualDevice == null) {
                 throw new SecurityException("Requires CAPTURE_VIDEO_OUTPUT or "
                         + "CAPTURE_SECURE_VIDEO_OUTPUT permission, or an appropriate "
                         + "MediaProjection token in order to create a screen sharing virtual "
@@ -1896,10 +1894,6 @@
         return -1;
     }
 
-    private static boolean isMirroringSupportedByVirtualDevice(IVirtualDevice virtualDevice) {
-        return Flags.interactiveScreenMirror() && virtualDevice != null;
-    }
-
     private void resizeVirtualDisplayInternal(IBinder appToken,
             int width, int height, int densityDpi) {
         synchronized (mSyncRoot) {
diff --git a/services/core/java/com/android/server/display/DisplayPowerController.java b/services/core/java/com/android/server/display/DisplayPowerController.java
index bd27f47..ab79713 100644
--- a/services/core/java/com/android/server/display/DisplayPowerController.java
+++ b/services/core/java/com/android/server/display/DisplayPowerController.java
@@ -492,6 +492,11 @@
     // Used to scale the brightness in doze mode
     private float mDozeScaleFactor;
 
+    // Used to keep track of the display state from the latest request to override the doze screen
+    // state.
+    @GuardedBy("mLock")
+    private int mPendingOverrideDozeScreenStateLocked;
+
     /**
      * Creates the display power controller.
      */
@@ -803,15 +808,28 @@
     @Override
     public void overrideDozeScreenState(int displayState, @Display.StateReason int reason) {
         Slog.i(TAG, "New offload doze override: " + Display.stateToString(displayState));
-        mHandler.postAtTime(() -> {
-            if (mDisplayOffloadSession == null
-                    || !(DisplayOffloadSession.isSupportedOffloadState(displayState)
-                            || displayState == Display.STATE_UNKNOWN)) {
-                return;
+        if (mDisplayOffloadSession != null
+                && (DisplayOffloadSession.isSupportedOffloadState(displayState)
+                || displayState == Display.STATE_UNKNOWN)) {
+            if (mFlags.isOffloadDozeOverrideHoldsWakelockEnabled()) {
+                mWakelockController.acquireWakelock(
+                        WakelockController.WAKE_LOCK_OVERRIDE_DOZE_SCREEN_STATE);
             }
-            mDisplayStateController.overrideDozeScreenState(displayState, reason);
-            updatePowerState();
-        }, mClock.uptimeMillis());
+            synchronized (mLock) {
+                mPendingOverrideDozeScreenStateLocked = displayState;
+            }
+            mHandler.postAtTime(() -> {
+                synchronized (mLock) {
+                    mDisplayStateController
+                            .overrideDozeScreenState(mPendingOverrideDozeScreenStateLocked, reason);
+                }
+                updatePowerState();
+                if (mFlags.isOffloadDozeOverrideHoldsWakelockEnabled()) {
+                    mWakelockController.releaseWakelock(
+                            WakelockController.WAKE_LOCK_OVERRIDE_DOZE_SCREEN_STATE);
+                }
+            }, mClock.uptimeMillis());
+        }
     }
 
     @Override
@@ -1338,30 +1356,6 @@
             initialize(readyToUpdateDisplayState() ? state : Display.STATE_UNKNOWN);
         }
 
-        if (mFlags.isOffloadDozeOverrideHoldsWakelockEnabled()) {
-            // Sometimes, a display-state change can come without an associated PowerRequest,
-            // as with DisplayOffload.  For those cases, we have to make sure to also mark the
-            // display as "not ready" so that we can inform power-manager when the state-change is
-            // complete.
-            if (mPowerState.getScreenState() != state) {
-                final boolean wasReady;
-                synchronized (mLock) {
-                    wasReady = mDisplayReadyLocked;
-                    mDisplayReadyLocked = false;
-                    mustNotify = true;
-                }
-
-                if (wasReady) {
-                    // If we went from ready to not-ready from the state-change (instead of a
-                    // PowerRequest) there's a good chance that nothing is keeping PowerManager
-                    // from suspending. Grab the unfinished business suspend blocker to keep the
-                    // device awake until the display-state change goes into effect.
-                    mWakelockController.acquireWakelock(
-                            WakelockController.WAKE_LOCK_UNFINISHED_BUSINESS);
-                }
-            }
-        }
-
         // Animate the screen state change unless already animating.
         // The transition may be deferred, so after this point we will use the
         // actual state instead of the desired one.
diff --git a/services/core/java/com/android/server/display/ExternalDisplayPolicy.java b/services/core/java/com/android/server/display/ExternalDisplayPolicy.java
index 44c8d1c..28a0b28 100644
--- a/services/core/java/com/android/server/display/ExternalDisplayPolicy.java
+++ b/services/core/java/com/android/server/display/ExternalDisplayPolicy.java
@@ -231,7 +231,7 @@
         if (!isExternalDisplayAllowed()) {
             Slog.w(TAG, "handleExternalDisplayConnectedLocked: External display can not be used"
                                 + " because it is currently not allowed.");
-            mDisplayNotificationManager.onHighTemperatureExternalDisplayNotAllowed();
+            mHandler.post(mDisplayNotificationManager::onHighTemperatureExternalDisplayNotAllowed);
             return;
         }
 
@@ -329,7 +329,7 @@
 
         if (!isExternalDisplayAllowed()) {
             Slog.w(TAG, "External display is currently not allowed and is getting disabled.");
-            mDisplayNotificationManager.onHighTemperatureExternalDisplayNotAllowed();
+            mHandler.post(mDisplayNotificationManager::onHighTemperatureExternalDisplayNotAllowed);
         }
 
         mLogicalDisplayMapper.setDisplayEnabledLocked(logicalDisplay, /*enabled=*/ false);
diff --git a/services/core/java/com/android/server/display/WakelockController.java b/services/core/java/com/android/server/display/WakelockController.java
index 7bc7971..5b0229c 100644
--- a/services/core/java/com/android/server/display/WakelockController.java
+++ b/services/core/java/com/android/server/display/WakelockController.java
@@ -20,6 +20,7 @@
 import android.hardware.display.DisplayManagerInternal;
 import android.util.Slog;
 
+import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.server.display.utils.DebugUtils;
 
@@ -37,7 +38,8 @@
     public static final int WAKE_LOCK_PROXIMITY_NEGATIVE = 2;
     public static final int WAKE_LOCK_PROXIMITY_DEBOUNCE = 3;
     public static final int WAKE_LOCK_STATE_CHANGED = 4;
-    public static final int WAKE_LOCK_UNFINISHED_BUSINESS = 5;
+    public static final int WAKE_LOCK_OVERRIDE_DOZE_SCREEN_STATE = 5;
+    public static final int WAKE_LOCK_UNFINISHED_BUSINESS = 6;
 
     @VisibleForTesting
     static final int WAKE_LOCK_MAX = WAKE_LOCK_UNFINISHED_BUSINESS;
@@ -53,18 +55,23 @@
             WAKE_LOCK_PROXIMITY_NEGATIVE,
             WAKE_LOCK_PROXIMITY_DEBOUNCE,
             WAKE_LOCK_STATE_CHANGED,
+            WAKE_LOCK_OVERRIDE_DOZE_SCREEN_STATE,
             WAKE_LOCK_UNFINISHED_BUSINESS
     })
     @Retention(RetentionPolicy.SOURCE)
     public @interface WAKE_LOCK_TYPE {
     }
 
+    private final Object mLock = new Object();
+
     // Asynchronous callbacks into the power manager service.
     // Only invoked from the handler thread while no locks are held.
     private final DisplayManagerInternal.DisplayPowerCallbacks mDisplayPowerCallbacks;
 
     // Identifiers for suspend blocker acquisition requests
     private final String mSuspendBlockerIdUnfinishedBusiness;
+    @GuardedBy("mLock")
+    private final String mSuspendBlockerOverrideDozeScreenState;
     private final String mSuspendBlockerIdOnStateChanged;
     private final String mSuspendBlockerIdProxPositive;
     private final String mSuspendBlockerIdProxNegative;
@@ -73,6 +80,10 @@
     // True if we have unfinished business and are holding a suspend-blocker.
     private boolean mUnfinishedBusiness;
 
+    // True if we have are holding a suspend-blocker to override the doze screen state.
+    @GuardedBy("mLock")
+    private boolean mIsOverrideDozeScreenStateAcquired;
+
     // True if we have have debounced the proximity change impact and are holding a suspend-blocker.
     private boolean mHasProximityDebounced;
 
@@ -108,6 +119,7 @@
         mTag = TAG + "[" + mDisplayId + "]";
         mDisplayPowerCallbacks = callbacks;
         mSuspendBlockerIdUnfinishedBusiness = "[" + displayId + "]unfinished business";
+        mSuspendBlockerOverrideDozeScreenState =  "[" + displayId + "]override doze screen state";
         mSuspendBlockerIdOnStateChanged = "[" + displayId + "]on state changed";
         mSuspendBlockerIdProxPositive = "[" + displayId + "]prox positive";
         mSuspendBlockerIdProxNegative = "[" + displayId + "]prox negative";
@@ -154,6 +166,10 @@
                 return acquireProxDebounceSuspendBlocker();
             case WAKE_LOCK_STATE_CHANGED:
                 return acquireStateChangedSuspendBlocker();
+            case WAKE_LOCK_OVERRIDE_DOZE_SCREEN_STATE:
+                synchronized (mLock) {
+                    return acquireOverrideDozeScreenStateSuspendBlockerLocked();
+                }
             case WAKE_LOCK_UNFINISHED_BUSINESS:
                 return acquireUnfinishedBusinessSuspendBlocker();
             default:
@@ -171,6 +187,10 @@
                 return releaseProxDebounceSuspendBlocker();
             case WAKE_LOCK_STATE_CHANGED:
                 return releaseStateChangedSuspendBlocker();
+            case WAKE_LOCK_OVERRIDE_DOZE_SCREEN_STATE:
+                synchronized (mLock) {
+                    return releaseOverrideDozeScreenStateSuspendBlockerLocked();
+                }
             case WAKE_LOCK_UNFINISHED_BUSINESS:
                 return releaseUnfinishedBusinessSuspendBlocker();
             default:
@@ -220,6 +240,42 @@
     }
 
     /**
+     * Acquires the suspend blocker to override the doze screen state and notifies the
+     * PowerManagerService about the changes. Note that this utility is syncronized because a
+     * request to override the doze screen state can come from a non-power thread.
+     */
+    @GuardedBy("mLock")
+    private boolean acquireOverrideDozeScreenStateSuspendBlockerLocked() {
+        // Grab a wake lock if we have unfinished business.
+        if (!mIsOverrideDozeScreenStateAcquired) {
+            if (DEBUG) {
+                Slog.d(mTag, "Acquiring suspend blocker to override the doze screen state...");
+            }
+            mDisplayPowerCallbacks.acquireSuspendBlocker(mSuspendBlockerOverrideDozeScreenState);
+            mIsOverrideDozeScreenStateAcquired = true;
+            return true;
+        }
+        return false;
+    }
+
+    /**
+     * Releases the override doze screen state suspend blocker and notifies the PowerManagerService
+     * about the changes.
+     */
+    @GuardedBy("mLock")
+    private boolean releaseOverrideDozeScreenStateSuspendBlockerLocked() {
+        if (mIsOverrideDozeScreenStateAcquired) {
+            if (DEBUG) {
+                Slog.d(mTag, "Finished overriding doze screen state...");
+            }
+            mDisplayPowerCallbacks.releaseSuspendBlocker(mSuspendBlockerOverrideDozeScreenState);
+            mIsOverrideDozeScreenStateAcquired = false;
+            return true;
+        }
+        return false;
+    }
+
+    /**
      * Acquires the unfinished business wakelock and notifies the PowerManagerService about the
      * changes.
      */
@@ -366,6 +422,7 @@
         pw.println("  mOnStateChangePending=" + isOnStateChangedPending());
         pw.println("  mOnProximityPositiveMessages=" + isProximityPositiveAcquired());
         pw.println("  mOnProximityNegativeMessages=" + isProximityNegativeAcquired());
+        pw.println("  mIsOverrideDozeScreenStateAcquired=" + isOverrideDozeScreenStateAcquired());
     }
 
     @VisibleForTesting
@@ -394,6 +451,13 @@
     }
 
     @VisibleForTesting
+    String getSuspendBlockerOverrideDozeScreenState() {
+        synchronized (mLock) {
+            return mSuspendBlockerOverrideDozeScreenState;
+        }
+    }
+
+    @VisibleForTesting
     boolean hasUnfinishedBusiness() {
         return mUnfinishedBusiness;
     }
@@ -417,4 +481,11 @@
     boolean hasProximitySensorDebounced() {
         return mHasProximityDebounced;
     }
+
+    @VisibleForTesting
+    boolean isOverrideDozeScreenStateAcquired() {
+        synchronized (mLock) {
+            return mIsOverrideDozeScreenStateAcquired;
+        }
+    }
 }
diff --git a/services/core/java/com/android/server/display/notifications/DisplayNotificationManager.java b/services/core/java/com/android/server/display/notifications/DisplayNotificationManager.java
index 280a7e1..8a8440b 100644
--- a/services/core/java/com/android/server/display/notifications/DisplayNotificationManager.java
+++ b/services/core/java/com/android/server/display/notifications/DisplayNotificationManager.java
@@ -26,6 +26,7 @@
 import android.app.NotificationManager;
 import android.content.Context;
 import android.content.res.Resources;
+import android.os.UserHandle;
 import android.util.Slog;
 
 import com.android.internal.R;
@@ -197,7 +198,8 @@
             return;
         }
 
-        mNotificationManager.cancel(DISPLAY_NOTIFICATION_TAG, DISPLAY_NOTIFICATION_ID);
+        mNotificationManager.cancelAsUser(DISPLAY_NOTIFICATION_TAG, DISPLAY_NOTIFICATION_ID,
+                UserHandle.CURRENT);
     }
 
     /**
@@ -210,8 +212,8 @@
             return;
         }
 
-        mNotificationManager.notify(DISPLAY_NOTIFICATION_TAG, DISPLAY_NOTIFICATION_ID,
-                notification);
+        mNotificationManager.notifyAsUser(DISPLAY_NOTIFICATION_TAG, DISPLAY_NOTIFICATION_ID,
+                notification, UserHandle.CURRENT);
     }
 
     /**
diff --git a/services/core/java/com/android/server/input/InputManagerInternal.java b/services/core/java/com/android/server/input/InputManagerInternal.java
index 819b9a1..73f18d1 100644
--- a/services/core/java/com/android/server/input/InputManagerInternal.java
+++ b/services/core/java/com/android/server/input/InputManagerInternal.java
@@ -21,7 +21,7 @@
 import android.annotation.UserIdInt;
 import android.graphics.PointF;
 import android.hardware.display.DisplayViewport;
-import android.hardware.input.KeyboardSystemShortcut;
+import android.hardware.input.KeyGestureEvent;
 import android.os.IBinder;
 import android.view.InputChannel;
 import android.view.inputmethod.InputMethodSubtype;
@@ -230,18 +230,14 @@
     public abstract int getLastUsedInputDeviceId();
 
     /**
-     * Notify Keyboard system shortcut was triggered by the user and handled by the framework.
+     * Notify key gesture was completed by the user.
      *
-     * NOTE: This is just to notify that a system shortcut was triggered. No further action is
-     * required to execute the said shortcut. This callback is meant for purposes of providing user
-     * hints or logging, etc.
-     *
-     * @param deviceId the device ID of the keyboard using which the shortcut was triggered
-     * @param keycodes the keys pressed for triggering the shortcut
-     * @param modifierState the modifier state of the key event that triggered the shortcut
-     * @param shortcut the shortcut that was triggered
+     * @param deviceId the device ID of the keyboard using which the event was completed
+     * @param keycodes the keys pressed for the event
+     * @param modifierState the modifier state
+     * @param event the gesture event that was completed
      *
      */
-    public abstract void notifyKeyboardShortcutTriggered(int deviceId, int[] keycodes,
-            int modifierState, @KeyboardSystemShortcut.SystemShortcut int shortcut);
+    public abstract void notifyKeyGestureCompleted(int deviceId, int[] keycodes, int modifierState,
+            @KeyGestureEvent.KeyGestureType int event);
 }
diff --git a/services/core/java/com/android/server/input/InputManagerService.java b/services/core/java/com/android/server/input/InputManagerService.java
index a69c7ef..a8fc862 100644
--- a/services/core/java/com/android/server/input/InputManagerService.java
+++ b/services/core/java/com/android/server/input/InputManagerService.java
@@ -48,18 +48,18 @@
 import android.hardware.input.IInputDevicesChangedListener;
 import android.hardware.input.IInputManager;
 import android.hardware.input.IInputSensorEventListener;
+import android.hardware.input.IKeyGestureEventListener;
 import android.hardware.input.IKeyboardBacklightListener;
-import android.hardware.input.IKeyboardSystemShortcutListener;
 import android.hardware.input.IStickyModifierStateListener;
 import android.hardware.input.ITabletModeChangedListener;
 import android.hardware.input.InputDeviceIdentifier;
 import android.hardware.input.InputManager;
 import android.hardware.input.InputSensorInfo;
 import android.hardware.input.InputSettings;
+import android.hardware.input.KeyGestureEvent;
 import android.hardware.input.KeyGlyphMap;
 import android.hardware.input.KeyboardLayout;
 import android.hardware.input.KeyboardLayoutSelectionResult;
-import android.hardware.input.KeyboardSystemShortcut;
 import android.hardware.input.TouchCalibration;
 import android.hardware.lights.Light;
 import android.hardware.lights.LightState;
@@ -162,7 +162,7 @@
     private static final int MSG_DELIVER_INPUT_DEVICES_CHANGED = 1;
     private static final int MSG_RELOAD_DEVICE_ALIASES = 2;
     private static final int MSG_DELIVER_TABLET_MODE_CHANGED = 3;
-    private static final int MSG_KEYBOARD_SYSTEM_SHORTCUT_TRIGGERED = 4;
+    private static final int MSG_KEY_GESTURE_COMPLETED = 4;
 
     private static final int DEFAULT_VIBRATION_MAGNITUDE = 192;
     private static final AdditionalDisplayInputProperties
@@ -314,9 +314,7 @@
 
     // Manages Sticky modifier state
     private final StickyModifierStateController mStickyModifierStateController;
-
-    // Manages keyboard system shortcut callbacks
-    private final KeyboardShortcutCallbackHandler mKeyboardShortcutCallbackHandler;
+    private final KeyGestureController mKeyGestureController;
 
     // Manages Keyboard microphone mute led
     private final KeyboardLedController mKeyboardLedController;
@@ -476,7 +474,7 @@
                         injector.getLooper(), injector.getUEventManager())
                 : new KeyboardBacklightControllerInterface() {};
         mStickyModifierStateController = new StickyModifierStateController();
-        mKeyboardShortcutCallbackHandler = new KeyboardShortcutCallbackHandler();
+        mKeyGestureController = new KeyGestureController();
         mKeyboardLedController = new KeyboardLedController(mContext, injector.getLooper(),
                 mNative);
         mKeyRemapper = new KeyRemapper(mContext, mNative, mDataStore, injector.getLooper());
@@ -2723,33 +2721,32 @@
     }
 
     @Override
-    @EnforcePermission(Manifest.permission.MONITOR_KEYBOARD_SYSTEM_SHORTCUTS)
-    public void registerKeyboardSystemShortcutListener(
-            @NonNull IKeyboardSystemShortcutListener listener) {
-        super.registerKeyboardSystemShortcutListener_enforcePermission();
+    @EnforcePermission(Manifest.permission.MANAGE_KEY_GESTURES)
+    public void registerKeyGestureEventListener(
+            @NonNull IKeyGestureEventListener listener) {
+        super.registerKeyGestureEventListener_enforcePermission();
         Objects.requireNonNull(listener);
-        mKeyboardShortcutCallbackHandler.registerKeyboardSystemShortcutListener(listener,
+        mKeyGestureController.registerKeyGestureEventListener(listener,
                 Binder.getCallingPid());
     }
 
     @Override
-    @EnforcePermission(Manifest.permission.MONITOR_KEYBOARD_SYSTEM_SHORTCUTS)
-    public void unregisterKeyboardSystemShortcutListener(
-            @NonNull IKeyboardSystemShortcutListener listener) {
-        super.unregisterKeyboardSystemShortcutListener_enforcePermission();
+    @EnforcePermission(Manifest.permission.MANAGE_KEY_GESTURES)
+    public void unregisterKeyGestureEventListener(
+            @NonNull IKeyGestureEventListener listener) {
+        super.unregisterKeyGestureEventListener_enforcePermission();
         Objects.requireNonNull(listener);
-        mKeyboardShortcutCallbackHandler.unregisterKeyboardSystemShortcutListener(listener,
+        mKeyGestureController.unregisterKeyGestureEventListener(listener,
                 Binder.getCallingPid());
     }
 
-    private void handleKeyboardSystemShortcutTriggered(int deviceId,
-            KeyboardSystemShortcut shortcut) {
-        InputDevice device = getInputDevice(deviceId);
-        if (device == null || device.isVirtual() || !device.isFullKeyboard()) {
+    private void handleKeyGestureCompleted(KeyGestureEvent event) {
+        InputDevice device = getInputDevice(event.getDeviceId());
+        if (device == null || device.isVirtual()) {
             return;
         }
-        KeyboardMetricsCollector.logKeyboardSystemsEventReportedAtom(device, shortcut);
-        mKeyboardShortcutCallbackHandler.onKeyboardSystemShortcutTriggered(deviceId, shortcut);
+        KeyboardMetricsCollector.logKeyboardSystemsEventReportedAtom(device, event);
+        mKeyGestureController.onKeyGestureEvent(event);
     }
 
     /**
@@ -2920,10 +2917,9 @@
                     boolean inTabletMode = (boolean) args.arg1;
                     deliverTabletModeChanged(whenNanos, inTabletMode);
                     break;
-                case MSG_KEYBOARD_SYSTEM_SHORTCUT_TRIGGERED:
-                    int deviceId = msg.arg1;
-                    KeyboardSystemShortcut shortcut = (KeyboardSystemShortcut) msg.obj;
-                    handleKeyboardSystemShortcutTriggered(deviceId, shortcut);
+                case MSG_KEY_GESTURE_COMPLETED:
+                    KeyGestureEvent event = (KeyGestureEvent) msg.obj;
+                    handleKeyGestureCompleted(event);
             }
         }
     }
@@ -3251,10 +3247,11 @@
         }
 
         @Override
-        public void notifyKeyboardShortcutTriggered(int deviceId, int[] keycodes, int modifierState,
-                @KeyboardSystemShortcut.SystemShortcut int shortcut) {
-            mHandler.obtainMessage(MSG_KEYBOARD_SYSTEM_SHORTCUT_TRIGGERED, deviceId, 0,
-                    new KeyboardSystemShortcut(keycodes, modifierState, shortcut)).sendToTarget();
+        public void notifyKeyGestureCompleted(int deviceId, int[] keycodes, int modifierState,
+                @KeyGestureEvent.KeyGestureType int gestureType) {
+            mHandler.obtainMessage(MSG_KEY_GESTURE_COMPLETED,
+                    new KeyGestureEvent(deviceId, keycodes, modifierState,
+                            gestureType)).sendToTarget();
         }
     }
 
diff --git a/services/core/java/com/android/server/input/InputSettingsObserver.java b/services/core/java/com/android/server/input/InputSettingsObserver.java
index 000f312..ef61d02 100644
--- a/services/core/java/com/android/server/input/InputSettingsObserver.java
+++ b/services/core/java/com/android/server/input/InputSettingsObserver.java
@@ -71,6 +71,8 @@
                         (reason) -> updateTouchpadTapToClickEnabled()),
                 Map.entry(Settings.System.getUriFor(Settings.System.TOUCHPAD_TAP_DRAGGING),
                         (reason) -> updateTouchpadTapDraggingEnabled()),
+                Map.entry(Settings.System.getUriFor(Settings.System.TOUCHPAD_VISUALIZER),
+                        (reason) -> updateTouchpadHardwareStateNotificationsEnabled()),
                 Map.entry(Settings.System.getUriFor(Settings.System.TOUCHPAD_RIGHT_CLICK_ZONE),
                         (reason) -> updateTouchpadRightClickZoneEnabled()),
                 Map.entry(Settings.System.getUriFor(Settings.System.SHOW_TOUCHES),
@@ -177,6 +179,10 @@
         mNative.setTouchpadTapDraggingEnabled(InputSettings.useTouchpadTapDragging(mContext));
     }
 
+    private void updateTouchpadHardwareStateNotificationsEnabled() {
+        mNative.setShouldNotifyTouchpadHardwareState(InputSettings.useTouchpadVisualizer(mContext));
+    }
+
     private void updateTouchpadRightClickZoneEnabled() {
         mNative.setTouchpadRightClickZoneEnabled(InputSettings.useTouchpadRightClickZone(mContext));
     }
diff --git a/services/core/java/com/android/server/input/KeyGestureController.java b/services/core/java/com/android/server/input/KeyGestureController.java
new file mode 100644
index 0000000..674d3c4
--- /dev/null
+++ b/services/core/java/com/android/server/input/KeyGestureController.java
@@ -0,0 +1,134 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.input;
+
+import android.annotation.BinderThread;
+import android.hardware.input.IKeyGestureEventListener;
+import android.hardware.input.KeyGestureEvent;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.util.Log;
+import android.util.Slog;
+import android.util.SparseArray;
+
+import com.android.internal.annotations.GuardedBy;
+
+/**
+ * A thread-safe component of {@link InputManagerService} responsible for managing callbacks when a
+ * key gesture event occurs.
+ */
+final class KeyGestureController {
+
+    private static final String TAG = "KeyGestureController";
+
+    // To enable these logs, run:
+    // 'adb shell setprop log.tag.KeyGestureController DEBUG' (requires restart)
+    private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
+
+    // List of currently registered key gesture event listeners keyed by process pid
+    @GuardedBy("mKeyGestureEventListenerRecords")
+    private final SparseArray<KeyGestureEventListenerRecord>
+            mKeyGestureEventListenerRecords = new SparseArray<>();
+
+    public void onKeyGestureEvent(KeyGestureEvent event) {
+        if (DEBUG) {
+            Slog.d(TAG, "Key gesture event occurred, event = " + event);
+        }
+
+        synchronized (mKeyGestureEventListenerRecords) {
+            for (int i = 0; i < mKeyGestureEventListenerRecords.size(); i++) {
+                mKeyGestureEventListenerRecords.valueAt(i).onKeyGestureEvent(event);
+            }
+        }
+    }
+
+    /** Register the key gesture event listener for a process. */
+    @BinderThread
+    public void registerKeyGestureEventListener(IKeyGestureEventListener listener,
+            int pid) {
+        synchronized (mKeyGestureEventListenerRecords) {
+            if (mKeyGestureEventListenerRecords.get(pid) != null) {
+                throw new IllegalStateException("The calling process has already registered "
+                        + "a KeyGestureEventListener.");
+            }
+            KeyGestureEventListenerRecord record = new KeyGestureEventListenerRecord(
+                    pid, listener);
+            try {
+                listener.asBinder().linkToDeath(record, 0);
+            } catch (RemoteException ex) {
+                throw new RuntimeException(ex);
+            }
+            mKeyGestureEventListenerRecords.put(pid, record);
+        }
+    }
+
+    /** Unregister the key gesture event listener for a process. */
+    @BinderThread
+    public void unregisterKeyGestureEventListener(IKeyGestureEventListener listener,
+            int pid) {
+        synchronized (mKeyGestureEventListenerRecords) {
+            KeyGestureEventListenerRecord record =
+                    mKeyGestureEventListenerRecords.get(pid);
+            if (record == null) {
+                throw new IllegalStateException("The calling process has no registered "
+                        + "KeyGestureEventListener.");
+            }
+            if (record.mListener.asBinder() != listener.asBinder()) {
+                throw new IllegalStateException("The calling process has a different registered "
+                        + "KeyGestureEventListener.");
+            }
+            record.mListener.asBinder().unlinkToDeath(record, 0);
+            mKeyGestureEventListenerRecords.remove(pid);
+        }
+    }
+
+    private void onKeyGestureEventListenerDied(int pid) {
+        synchronized (mKeyGestureEventListenerRecords) {
+            mKeyGestureEventListenerRecords.remove(pid);
+        }
+    }
+
+    // A record of a registered key gesture event listener from one process.
+    private class KeyGestureEventListenerRecord implements IBinder.DeathRecipient {
+        public final int mPid;
+        public final IKeyGestureEventListener mListener;
+
+        KeyGestureEventListenerRecord(int pid, IKeyGestureEventListener listener) {
+            mPid = pid;
+            mListener = listener;
+        }
+
+        @Override
+        public void binderDied() {
+            if (DEBUG) {
+                Slog.d(TAG, "Key gesture event listener for pid " + mPid + " died.");
+            }
+            onKeyGestureEventListenerDied(mPid);
+        }
+
+        public void onKeyGestureEvent(KeyGestureEvent event) {
+            try {
+                mListener.onKeyGestureEvent(event.getDeviceId(), event.getKeycodes(),
+                        event.getModifierState(), event.getKeyGestureType());
+            } catch (RemoteException ex) {
+                Slog.w(TAG, "Failed to notify process " + mPid
+                        + " that key gesture event occurred, assuming it died.", ex);
+                binderDied();
+            }
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/input/KeyboardMetricsCollector.java b/services/core/java/com/android/server/input/KeyboardMetricsCollector.java
index 3d2f951..1daf4db 100644
--- a/services/core/java/com/android/server/input/KeyboardMetricsCollector.java
+++ b/services/core/java/com/android/server/input/KeyboardMetricsCollector.java
@@ -24,9 +24,9 @@
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.hardware.input.KeyGestureEvent;
 import android.hardware.input.KeyboardLayout;
 import android.hardware.input.KeyboardLayoutSelectionResult.LayoutSelectionCriteria;
-import android.hardware.input.KeyboardSystemShortcut;
 import android.icu.util.ULocale;
 import android.text.TextUtils;
 import android.util.Log;
@@ -66,14 +66,17 @@
      * defined in "stats/atoms/input/input_extension_atoms.proto"
      */
     public static void logKeyboardSystemsEventReportedAtom(@NonNull InputDevice inputDevice,
-            @NonNull KeyboardSystemShortcut keyboardSystemShortcut) {
+            @NonNull KeyGestureEvent keyGestureEvent) {
+        if (inputDevice.isVirtual() || !inputDevice.isFullKeyboard()) {
+            return;
+        }
         FrameworkStatsLog.write(FrameworkStatsLog.KEYBOARD_SYSTEMS_EVENT_REPORTED,
                 inputDevice.getVendorId(), inputDevice.getProductId(),
-                keyboardSystemShortcut.getSystemShortcut(), keyboardSystemShortcut.getKeycodes(),
-                keyboardSystemShortcut.getModifierState(), inputDevice.getDeviceBus());
+                keyGestureEvent.getKeyGestureType(), keyGestureEvent.getKeycodes(),
+                keyGestureEvent.getModifierState(), inputDevice.getDeviceBus());
 
         if (DEBUG) {
-            Slog.d(TAG, "Logging Keyboard system event: " + keyboardSystemShortcut);
+            Slog.d(TAG, "Logging Keyboard system event: " + keyGestureEvent);
         }
     }
 
diff --git a/services/core/java/com/android/server/input/KeyboardShortcutCallbackHandler.java b/services/core/java/com/android/server/input/KeyboardShortcutCallbackHandler.java
deleted file mode 100644
index 092058e..0000000
--- a/services/core/java/com/android/server/input/KeyboardShortcutCallbackHandler.java
+++ /dev/null
@@ -1,137 +0,0 @@
-/*
- * Copyright 2024 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.input;
-
-import android.annotation.BinderThread;
-import android.hardware.input.IKeyboardSystemShortcutListener;
-import android.hardware.input.KeyboardSystemShortcut;
-import android.os.IBinder;
-import android.os.RemoteException;
-import android.util.Log;
-import android.util.Slog;
-import android.util.SparseArray;
-
-import com.android.internal.annotations.GuardedBy;
-
-/**
- * A thread-safe component of {@link InputManagerService} responsible for managing callbacks when a
- * keyboard shortcut is triggered.
- */
-final class KeyboardShortcutCallbackHandler {
-
-    private static final String TAG = "KeyboardShortcut";
-
-    // To enable these logs, run:
-    // 'adb shell setprop log.tag.KeyboardShortcutCallbackHandler DEBUG' (requires restart)
-    private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
-
-    // List of currently registered keyboard system shortcut listeners keyed by process pid
-    @GuardedBy("mKeyboardSystemShortcutListenerRecords")
-    private final SparseArray<KeyboardSystemShortcutListenerRecord>
-            mKeyboardSystemShortcutListenerRecords = new SparseArray<>();
-
-    public void onKeyboardSystemShortcutTriggered(int deviceId,
-            KeyboardSystemShortcut systemShortcut) {
-        if (DEBUG) {
-            Slog.d(TAG, "Keyboard system shortcut triggered, deviceId = " + deviceId
-                    + ", systemShortcut = " + systemShortcut);
-        }
-
-        synchronized (mKeyboardSystemShortcutListenerRecords) {
-            for (int i = 0; i < mKeyboardSystemShortcutListenerRecords.size(); i++) {
-                mKeyboardSystemShortcutListenerRecords.valueAt(i).onKeyboardSystemShortcutTriggered(
-                        deviceId, systemShortcut);
-            }
-        }
-    }
-
-    /** Register the keyboard system shortcut listener for a process. */
-    @BinderThread
-    public void registerKeyboardSystemShortcutListener(IKeyboardSystemShortcutListener listener,
-            int pid) {
-        synchronized (mKeyboardSystemShortcutListenerRecords) {
-            if (mKeyboardSystemShortcutListenerRecords.get(pid) != null) {
-                throw new IllegalStateException("The calling process has already registered "
-                        + "a KeyboardSystemShortcutListener.");
-            }
-            KeyboardSystemShortcutListenerRecord record = new KeyboardSystemShortcutListenerRecord(
-                    pid, listener);
-            try {
-                listener.asBinder().linkToDeath(record, 0);
-            } catch (RemoteException ex) {
-                throw new RuntimeException(ex);
-            }
-            mKeyboardSystemShortcutListenerRecords.put(pid, record);
-        }
-    }
-
-    /** Unregister the keyboard system shortcut listener for a process. */
-    @BinderThread
-    public void unregisterKeyboardSystemShortcutListener(IKeyboardSystemShortcutListener listener,
-            int pid) {
-        synchronized (mKeyboardSystemShortcutListenerRecords) {
-            KeyboardSystemShortcutListenerRecord record =
-                    mKeyboardSystemShortcutListenerRecords.get(pid);
-            if (record == null) {
-                throw new IllegalStateException("The calling process has no registered "
-                        + "KeyboardSystemShortcutListener.");
-            }
-            if (record.mListener.asBinder() != listener.asBinder()) {
-                throw new IllegalStateException("The calling process has a different registered "
-                        + "KeyboardSystemShortcutListener.");
-            }
-            record.mListener.asBinder().unlinkToDeath(record, 0);
-            mKeyboardSystemShortcutListenerRecords.remove(pid);
-        }
-    }
-
-    private void onKeyboardSystemShortcutListenerDied(int pid) {
-        synchronized (mKeyboardSystemShortcutListenerRecords) {
-            mKeyboardSystemShortcutListenerRecords.remove(pid);
-        }
-    }
-
-    // A record of a registered keyboard system shortcut listener from one process.
-    private class KeyboardSystemShortcutListenerRecord implements IBinder.DeathRecipient {
-        public final int mPid;
-        public final IKeyboardSystemShortcutListener mListener;
-
-        KeyboardSystemShortcutListenerRecord(int pid, IKeyboardSystemShortcutListener listener) {
-            mPid = pid;
-            mListener = listener;
-        }
-
-        @Override
-        public void binderDied() {
-            if (DEBUG) {
-                Slog.d(TAG, "Keyboard system shortcut listener for pid " + mPid + " died.");
-            }
-            onKeyboardSystemShortcutListenerDied(mPid);
-        }
-
-        public void onKeyboardSystemShortcutTriggered(int deviceId, KeyboardSystemShortcut data) {
-            try {
-                mListener.onKeyboardSystemShortcutTriggered(deviceId, data.getKeycodes(),
-                        data.getModifierState(), data.getSystemShortcut());
-            } catch (RemoteException ex) {
-                Slog.w(TAG, "Failed to notify process " + mPid
-                        + " that keyboard system shortcut was triggered, assuming it died.", ex);
-                binderDied();
-            }
-        }
-    }
-}
diff --git a/services/core/java/com/android/server/input/NativeInputManagerService.java b/services/core/java/com/android/server/input/NativeInputManagerService.java
index a9d40bb..69a9f4d 100644
--- a/services/core/java/com/android/server/input/NativeInputManagerService.java
+++ b/services/core/java/com/android/server/input/NativeInputManagerService.java
@@ -135,6 +135,8 @@
 
     void setTouchpadTapDraggingEnabled(boolean enabled);
 
+    void setShouldNotifyTouchpadHardwareState(boolean enabled);
+
     void setTouchpadRightClickZoneEnabled(boolean enabled);
 
     void setShowTouches(boolean enabled);
@@ -395,6 +397,9 @@
         public native void setTouchpadTapDraggingEnabled(boolean enabled);
 
         @Override
+        public native void setShouldNotifyTouchpadHardwareState(boolean enabled);
+
+        @Override
         public native void setTouchpadRightClickZoneEnabled(boolean enabled);
 
         @Override
diff --git a/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java b/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java
index 1070f2f..e1b8e9f 100644
--- a/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java
+++ b/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java
@@ -1364,14 +1364,14 @@
             if (manager == null || manager.mLastSessionCreationRequest == null) {
                 Slog.w(TAG, "requestCreateSessionWithRouter2Locked: "
                         + "Ignoring unknown request.");
-                userHandler.notifySessionCreationFailedToRouter(routerRecord, requestId);
+                routerRecord.notifySessionCreationFailed(requestId);
                 return;
             }
             if (!TextUtils.equals(manager.mLastSessionCreationRequest.mOldSession.getId(),
                     oldSession.getId())) {
                 Slog.w(TAG, "requestCreateSessionWithRouter2Locked: "
                         + "Ignoring unmatched routing session.");
-                userHandler.notifySessionCreationFailedToRouter(routerRecord, requestId);
+                routerRecord.notifySessionCreationFailed(requestId);
                 return;
             }
             if (!TextUtils.equals(manager.mLastSessionCreationRequest.mRoute.getId(),
@@ -1384,7 +1384,7 @@
                 } else {
                     Slog.w(TAG, "requestCreateSessionWithRouter2Locked: "
                             + "Ignoring unmatched route.");
-                    userHandler.notifySessionCreationFailedToRouter(routerRecord, requestId);
+                    routerRecord.notifySessionCreationFailed(requestId);
                     return;
                 }
             }
@@ -1396,7 +1396,7 @@
                     && !TextUtils.equals(route.getId(), defaultRouteId)) {
                 Slog.w(TAG, "MODIFY_AUDIO_ROUTING permission is required to transfer to"
                         + route);
-                userHandler.notifySessionCreationFailedToRouter(routerRecord, requestId);
+                routerRecord.notifySessionCreationFailed(requestId);
                 return;
             }
         }
@@ -1484,8 +1484,7 @@
                 && !TextUtils.equals(route.getId(), defaultRouteId)) {
             userHandler.sendMessage(
                     obtainMessage(
-                            UserHandler::notifySessionCreationFailedToRouter,
-                            userHandler,
+                            RouterRecord::notifySessionCreationFailed,
                             routerRecord,
                             toOriginalRequestId(DUMMY_REQUEST_ID)));
         } else {
@@ -1762,12 +1761,7 @@
         if (routerRecord == null) {
             Slog.w(TAG, "requestCreateSessionWithManagerLocked: Ignoring session creation for "
                     + "unknown router.");
-            try {
-                managerRecord.mManager.notifyRequestFailed(requestId, REASON_UNKNOWN_ERROR);
-            } catch (RemoteException ex) {
-                Slog.w(TAG, "requestCreateSessionWithManagerLocked: Failed to notify failure. "
-                        + "Manager probably died.");
-            }
+            managerRecord.notifyRequestFailed(requestId, REASON_UNKNOWN_ERROR);
             return;
         }
 
@@ -1780,10 +1774,8 @@
                             "requestCreateSessionWithManagerLocked: Notifying failure for pending"
                                 + " session creation request - oldSession: %s, route: %s",
                             lastRequest.mOldSession, lastRequest.mRoute));
-            managerRecord.mUserRecord.mHandler.notifyRequestFailedToManager(
-                    managerRecord.mManager,
-                    toOriginalRequestId(lastRequest.mManagerRequestId),
-                    REASON_UNKNOWN_ERROR);
+            managerRecord.notifyRequestFailed(
+                    toOriginalRequestId(lastRequest.mManagerRequestId), REASON_UNKNOWN_ERROR);
         }
         managerRecord.mLastSessionCreationRequest = new SessionCreationRequest(routerRecord,
                 MediaRoute2ProviderService.REQUEST_ID_NONE, uniqueRequestId,
@@ -1793,15 +1785,12 @@
         // As a return, media router will request to create a session.
         routerRecord.mUserRecord.mHandler.sendMessage(
                 obtainMessage(
-                        UserHandler::requestRouterCreateSessionOnHandler,
-                        routerRecord.mUserRecord.mHandler,
-                        uniqueRequestId,
+                        RouterRecord::requestCreateSessionByManager,
                         routerRecord,
                         managerRecord,
+                        uniqueRequestId,
                         oldSession,
-                        route,
-                        transferInitiatorUserHandle,
-                        transferInitiatorPackageName));
+                        route));
     }
 
     @GuardedBy("mLock")
@@ -2256,6 +2245,71 @@
         }
 
         /**
+         * Notifies the corresponding router of a request failure.
+         *
+         * @param requestId The id of the request that failed.
+         */
+        public void notifySessionCreationFailed(int requestId) {
+            try {
+                mRouter.notifySessionCreated(requestId, /* sessionInfo= */ null);
+            } catch (RemoteException ex) {
+                Slog.w(
+                        TAG,
+                        "Failed to notify router of the session creation failure."
+                                + " Router probably died.",
+                        ex);
+            }
+        }
+
+        /**
+         * Notifies the corresponding router of the release of the given {@link RoutingSessionInfo}.
+         */
+        public void notifySessionReleased(RoutingSessionInfo sessionInfo) {
+            try {
+                mRouter.notifySessionReleased(sessionInfo);
+            } catch (RemoteException ex) {
+                Slog.w(
+                        TAG,
+                        "Failed to notify router of the session release. Router probably died.",
+                        ex);
+            }
+        }
+
+        /**
+         * Sends the corresponding router a {@link RoutingSessionInfo session} creation request,
+         * with the given {@link MediaRoute2Info} as the initial member.
+         *
+         * <p>Must be called on the thread of the corresponding {@link UserHandler}.
+         *
+         * @param managerRecord The record of the manager that made the request.
+         * @param uniqueRequestId The id of the request.
+         * @param oldSession The session from which the transfer originated.
+         * @param route The initial route member of the session to create.
+         */
+        public void requestCreateSessionByManager(
+                ManagerRecord managerRecord,
+                long uniqueRequestId,
+                RoutingSessionInfo oldSession,
+                MediaRoute2Info route) {
+            try {
+                if (route.isSystemRoute() && !hasSystemRoutingPermission()) {
+                    // The router lacks permission to modify system routing, so we hide system
+                    // route info from them.
+                    route = mUserRecord.mHandler.mSystemProvider.getDefaultRoute();
+                }
+                mRouter.requestCreateSessionByManager(uniqueRequestId, oldSession, route);
+            } catch (RemoteException ex) {
+                Slog.w(
+                        TAG,
+                        "getSessionHintsForCreatingSessionOnHandler: "
+                                + "Failed to request. Router probably died.",
+                        ex);
+                managerRecord.notifyRequestFailed(
+                        toOriginalRequestId(uniqueRequestId), REASON_UNKNOWN_ERROR);
+            }
+        }
+
+        /**
          * Sends the corresponding router an update for the given session.
          *
          * <p>Note: These updates are not directly visible to the app.
@@ -2360,6 +2414,25 @@
             }
         }
 
+        /**
+         * Notifies the corresponding manager of a request failure.
+         *
+         * <p>Must be called on the thread of the corresponding {@link UserHandler}.
+         *
+         * @param requestId The id of the request that failed.
+         * @param reason The reason of the failure. One of
+         */
+        public void notifyRequestFailed(int requestId, int reason) {
+            try {
+                mManager.notifyRequestFailed(requestId, reason);
+            } catch (RemoteException ex) {
+                Slog.w(
+                        TAG,
+                        "Failed to notify manager of the request failure. Manager probably died.",
+                        ex);
+            }
+        }
+
         private void updateScanningState(@ScanningState int scanningState) {
             if (mScanningState == scanningState) {
                 return;
@@ -2738,30 +2811,6 @@
             return -1;
         }
 
-        private void requestRouterCreateSessionOnHandler(
-                long uniqueRequestId,
-                @NonNull RouterRecord routerRecord,
-                @NonNull ManagerRecord managerRecord,
-                @NonNull RoutingSessionInfo oldSession,
-                @NonNull MediaRoute2Info route,
-                @NonNull UserHandle transferInitiatorUserHandle,
-                @NonNull String transferInitiatorPackageName) {
-            try {
-                if (route.isSystemRoute() && !routerRecord.hasSystemRoutingPermission()) {
-                    // The router lacks permission to modify system routing, so we hide system
-                    // route info from them.
-                    route = mSystemProvider.getDefaultRoute();
-                }
-                routerRecord.mRouter.requestCreateSessionByManager(
-                        uniqueRequestId, oldSession, route);
-            } catch (RemoteException ex) {
-                Slog.w(TAG, "getSessionHintsForCreatingSessionOnHandler: "
-                        + "Failed to request. Router probably died.", ex);
-                notifyRequestFailedToManager(managerRecord.mManager,
-                        toOriginalRequestId(uniqueRequestId), REASON_UNKNOWN_ERROR);
-            }
-        }
-
         private void requestCreateSessionWithRouter2OnHandler(
                 long uniqueRequestId,
                 long managerRequestId,
@@ -2774,8 +2823,7 @@
             if (provider == null) {
                 Slog.w(TAG, "requestCreateSessionWithRouter2OnHandler: Ignoring session "
                         + "creation request since no provider found for given route=" + route);
-                notifySessionCreationFailedToRouter(routerRecord,
-                        toOriginalRequestId(uniqueRequestId));
+                routerRecord.notifySessionCreationFailed(toOriginalRequestId(uniqueRequestId));
                 return;
             }
 
@@ -3054,7 +3102,7 @@
                         + sessionInfo);
                 return;
             }
-            notifySessionReleasedToRouter(routerRecord, sessionInfo);
+            routerRecord.notifySessionReleased(sessionInfo);
         }
 
         private void onRequestFailedOnHandler(@NonNull MediaRoute2Provider provider,
@@ -3073,8 +3121,7 @@
             final int requesterId = toRequesterId(uniqueRequestId);
             ManagerRecord manager = findManagerWithId(requesterId);
             if (manager != null) {
-                notifyRequestFailedToManager(
-                        manager.mManager, toOriginalRequestId(uniqueRequestId), reason);
+                manager.notifyRequestFailed(toOriginalRequestId(uniqueRequestId), reason);
             }
 
             // Currently, only manager records can get notified of failures.
@@ -3109,40 +3156,19 @@
             // Notify the requester about the failure.
             // The call should be made by either MediaRouter2 or MediaRouter2Manager.
             if (matchingRequest.mManagerRequestId == MediaRouter2Manager.REQUEST_ID_NONE) {
-                notifySessionCreationFailedToRouter(
-                        matchingRequest.mRouterRecord, toOriginalRequestId(uniqueRequestId));
+                matchingRequest.mRouterRecord.notifySessionCreationFailed(
+                        toOriginalRequestId(uniqueRequestId));
             } else {
                 final int requesterId = toRequesterId(matchingRequest.mManagerRequestId);
                 ManagerRecord manager = findManagerWithId(requesterId);
                 if (manager != null) {
-                    notifyRequestFailedToManager(manager.mManager,
+                    manager.notifyRequestFailed(
                             toOriginalRequestId(matchingRequest.mManagerRequestId), reason);
                 }
             }
             return true;
         }
 
-        private void notifySessionCreationFailedToRouter(@NonNull RouterRecord routerRecord,
-                int requestId) {
-            try {
-                routerRecord.mRouter.notifySessionCreated(requestId,
-                        /* sessionInfo= */ null);
-            } catch (RemoteException ex) {
-                Slog.w(TAG, "Failed to notify router of the session creation failure."
-                        + " Router probably died.", ex);
-            }
-        }
-
-        private void notifySessionReleasedToRouter(@NonNull RouterRecord routerRecord,
-                @NonNull RoutingSessionInfo sessionInfo) {
-            try {
-                routerRecord.mRouter.notifySessionReleased(sessionInfo);
-            } catch (RemoteException ex) {
-                Slog.w(TAG, "Failed to notify router of the session release."
-                        + " Router probably died.", ex);
-            }
-        }
-
         private List<IMediaRouter2Manager> getManagers() {
             final List<IMediaRouter2Manager> managers = new ArrayList<>();
             MediaRouter2ServiceImpl service = mServiceRef.get();
@@ -3379,16 +3405,6 @@
             //    need to update routers other than the one making the update.
         }
 
-        private void notifyRequestFailedToManager(@NonNull IMediaRouter2Manager manager,
-                int requestId, int reason) {
-            try {
-                manager.notifyRequestFailed(requestId, reason);
-            } catch (RemoteException ex) {
-                Slog.w(TAG, "Failed to notify manager of the request failure."
-                        + " Manager probably died.", ex);
-            }
-        }
-
         private void updateDiscoveryPreferenceOnHandler() {
             MediaRouter2ServiceImpl service = mServiceRef.get();
             if (service == null) {
diff --git a/services/core/java/com/android/server/notification/GroupHelper.java b/services/core/java/com/android/server/notification/GroupHelper.java
index 1cdab44..008746c 100644
--- a/services/core/java/com/android/server/notification/GroupHelper.java
+++ b/services/core/java/com/android/server/notification/GroupHelper.java
@@ -118,11 +118,32 @@
     private final ArrayMap<FullyQualifiedGroupKey, ArrayMap<String, NotificationAttributes>>
             mAggregatedNotifications = new ArrayMap<>();
 
-    private static final List<NotificationSectioner> NOTIFICATION_SHADE_SECTIONS = List.of(
-        new NotificationSectioner("AlertingSection", 0, (record) ->
-            record.getImportance() >= NotificationManager.IMPORTANCE_DEFAULT),
-        new NotificationSectioner("SilentSection", 1, (record) ->
-            record.getImportance() < NotificationManager.IMPORTANCE_DEFAULT));
+    private static List<NotificationSectioner> NOTIFICATION_SHADE_SECTIONS =
+            getNotificationShadeSections();
+
+    private static List<NotificationSectioner> getNotificationShadeSections() {
+        if (android.service.notification.Flags.notificationClassification()) {
+            return List.of(
+                new NotificationSectioner("PromotionsSection", 0, (record) ->
+                    NotificationChannel.PROMOTIONS_ID.equals(record.getChannel().getId())),
+                new NotificationSectioner("SocialSection", 0, (record) ->
+                    NotificationChannel.SOCIAL_MEDIA_ID.equals(record.getChannel().getId())),
+                new NotificationSectioner("NewsSection", 0, (record) ->
+                    NotificationChannel.NEWS_ID.equals(record.getChannel().getId())),
+                new NotificationSectioner("RecsSection", 0, (record) ->
+                    NotificationChannel.RECS_ID.equals(record.getChannel().getId())),
+                new NotificationSectioner("AlertingSection", 0, (record) ->
+                    record.getImportance() >= NotificationManager.IMPORTANCE_DEFAULT),
+                new NotificationSectioner("SilentSection", 1, (record) ->
+                    record.getImportance() < NotificationManager.IMPORTANCE_DEFAULT));
+        } else {
+            return List.of(
+                new NotificationSectioner("AlertingSection", 0, (record) ->
+                    record.getImportance() >= NotificationManager.IMPORTANCE_DEFAULT),
+                new NotificationSectioner("SilentSection", 1, (record) ->
+                    record.getImportance() < NotificationManager.IMPORTANCE_DEFAULT));
+        }
+    }
 
     public GroupHelper(Context context, PackageManager packageManager, int autoGroupAtCount,
             int autoGroupSparseGroupsAtCount, Callback callback) {
@@ -131,6 +152,7 @@
         mContext = context;
         mPackageManager = packageManager;
         mAutogroupSparseGroupsAtCount = autoGroupSparseGroupsAtCount;
+        NOTIFICATION_SHADE_SECTIONS = getNotificationShadeSections();
     }
 
     private String generatePackageKey(int userId, String pkg) {
diff --git a/services/core/java/com/android/server/notification/NotificationAttentionHelper.java b/services/core/java/com/android/server/notification/NotificationAttentionHelper.java
index 9818916..abb2132 100644
--- a/services/core/java/com/android/server/notification/NotificationAttentionHelper.java
+++ b/services/core/java/com/android/server/notification/NotificationAttentionHelper.java
@@ -489,6 +489,7 @@
 
                 } else if ((record.getFlags() & Notification.FLAG_INSISTENT) != 0) {
                     hasValidSound = false;
+                    hasValidVibrate = false;
                 }
             }
         }
@@ -753,6 +754,13 @@
         // notifying app does not have the VIBRATE permission.
         final long identity = Binder.clearCallingIdentity();
         try {
+            // Need to explicitly cancel a previously playing vibration
+            // Otherwise a looping vibration will not be stopped when starting a new one.
+            if (mVibrateNotificationKey != null
+                    && !mVibrateNotificationKey.equals(record.getKey())) {
+                mVibrateNotificationKey = null;
+                mVibratorHelper.cancelVibration();
+            }
             final float scale = getVibrationIntensity(record);
             final VibrationEffect scaledEffect = Float.compare(scale, DEFAULT_VOLUME) != 0
                     ? mVibratorHelper.scale(effect, scale) : effect;
diff --git a/services/core/java/com/android/server/notification/NotificationChannelExtractor.java b/services/core/java/com/android/server/notification/NotificationChannelExtractor.java
index 1938642..e2889fa 100644
--- a/services/core/java/com/android/server/notification/NotificationChannelExtractor.java
+++ b/services/core/java/com/android/server/notification/NotificationChannelExtractor.java
@@ -29,8 +29,8 @@
 import android.media.AudioAttributes;
 import android.os.Binder;
 import android.os.RemoteException;
-import android.os.ServiceManager;
 import android.util.Slog;
+
 import com.android.internal.compat.IPlatformCompat;
 
 /**
@@ -79,6 +79,11 @@
         if (restrictAudioAttributesCall() || restrictAudioAttributesAlarm()
                 || restrictAudioAttributesMedia()) {
             AudioAttributes attributes = record.getChannel().getAudioAttributes();
+            if (attributes == null) {
+                if (DBG) Slog.d(TAG, "missing AudioAttributes");
+                return null;
+            }
+
             boolean updateAttributes =  false;
             if (restrictAudioAttributesCall()
                     && !record.getNotification().isStyle(Notification.CallStyle.class)
diff --git a/services/core/java/com/android/server/pm/InstallPackageHelper.java b/services/core/java/com/android/server/pm/InstallPackageHelper.java
index 22b4d5d..5105fd3 100644
--- a/services/core/java/com/android/server/pm/InstallPackageHelper.java
+++ b/services/core/java/com/android/server/pm/InstallPackageHelper.java
@@ -1399,7 +1399,29 @@
                                 "Package " + pkgName + " is a persistent app. "
                                         + "Persistent apps are not updateable.");
                     }
+                    // When updating an sdk library, make sure that the versionMajor is
+                    // changed if the targetSdkVersion and minSdkVersion have changed
+                    if (parsedPackage.isSdkLibrary() && ps.getPkg() != null
+                            && ps.getPkg().isSdkLibrary()) {
+                        final int oldMinSdk = ps.getPkg().getMinSdkVersion();
+                        final int newMinSdk = parsedPackage.getMinSdkVersion();
+                        if (oldTargetSdk != newTargetSdk || oldMinSdk != newMinSdk) {
+                            final int oldVersionMajor = ps.getPkg().getSdkLibVersionMajor();
+                            final int newVersionMajor = parsedPackage.getSdkLibVersionMajor();
+                            if (oldVersionMajor == newVersionMajor) {
+                                throw new PrepareFailure(
+                                        PackageManager.INSTALL_FAILED_UPDATE_INCOMPATIBLE,
+                                        "Failure updating " + pkgName + " as it updates"
+                                                + " an sdk library <"
+                                                + parsedPackage.getSdkLibraryName() + ">"
+                                                + " without changing the versionMajor, but the"
+                                                + " targetSdkVersion or minSdkVersion has changed."
+                                );
+                            }
+                        }
+                    }
                 }
+
             }
 
             PackageSetting signatureCheckPs = ps;
diff --git a/services/core/java/com/android/server/pm/TEST_MAPPING b/services/core/java/com/android/server/pm/TEST_MAPPING
index c95d88e..2c13bd0 100644
--- a/services/core/java/com/android/server/pm/TEST_MAPPING
+++ b/services/core/java/com/android/server/pm/TEST_MAPPING
@@ -165,7 +165,55 @@
       "name": "CtsUpdateOwnershipEnforcementTestCases"
     },
     {
-      "name": "CtsPackageInstallerCUJTestCases",
+      "name": "CtsPackageInstallerCUJInstallationTestCases",
+      "file_patterns": [
+        "core/java/.*Install.*",
+        "services/core/.*Install.*",
+        "services/core/java/com/android/server/pm/.*"
+      ],
+      "options":[
+          {
+              "exclude-annotation":"androidx.test.filters.FlakyTest"
+          },
+          {
+              "exclude-annotation":"org.junit.Ignore"
+          }
+      ]
+    },
+    {
+      "name": "CtsPackageInstallerCUJUninstallationTestCases",
+      "file_patterns": [
+        "core/java/.*Install.*",
+        "services/core/.*Install.*",
+        "services/core/java/com/android/server/pm/.*"
+      ],
+      "options":[
+          {
+              "exclude-annotation":"androidx.test.filters.FlakyTest"
+          },
+          {
+              "exclude-annotation":"org.junit.Ignore"
+          }
+      ]
+    },
+    {
+      "name": "CtsPackageInstallerCUJUpdateOwnerShipTestCases",
+      "file_patterns": [
+        "core/java/.*Install.*",
+        "services/core/.*Install.*",
+        "services/core/java/com/android/server/pm/.*"
+      ],
+      "options":[
+          {
+              "exclude-annotation":"androidx.test.filters.FlakyTest"
+          },
+          {
+              "exclude-annotation":"org.junit.Ignore"
+          }
+      ]
+    },
+    {
+      "name": "CtsPackageInstallerCUJUpdateSelfTestCases",
       "file_patterns": [
         "core/java/.*Install.*",
         "services/core/.*Install.*",
diff --git a/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java b/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java
index aaa38a3..6c78b3c 100644
--- a/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java
+++ b/services/core/java/com/android/server/pm/permission/DefaultPermissionGrantPolicy.java
@@ -887,7 +887,7 @@
             grantPermissionsToSystemPackage(pm,
                     getDefaultSystemHandlerActivityPackage(pm,
                             SearchManager.INTENT_ACTION_GLOBAL_SEARCH, userId),
-                    userId, PHONE_PERMISSIONS, CALENDAR_PERMISSIONS);
+                    userId, PHONE_PERMISSIONS, CALENDAR_PERMISSIONS, NEARBY_DEVICES_PERMISSIONS);
         }
 
         // Print Spooler
diff --git a/services/core/java/com/android/server/policy/ModifierShortcutManager.java b/services/core/java/com/android/server/policy/ModifierShortcutManager.java
index 5a45186..7ed8972 100644
--- a/services/core/java/com/android/server/policy/ModifierShortcutManager.java
+++ b/services/core/java/com/android/server/policy/ModifierShortcutManager.java
@@ -29,8 +29,7 @@
 import android.content.pm.PackageManager;
 import android.content.res.XmlResourceParser;
 import android.graphics.drawable.Icon;
-import android.hardware.input.InputManager;
-import android.hardware.input.KeyboardSystemShortcut;
+import android.hardware.input.KeyGestureEvent;
 import android.os.Handler;
 import android.os.RemoteException;
 import android.os.UserHandle;
@@ -476,7 +475,7 @@
                             + "keyCode=" + KeyEvent.keyCodeToString(keyCode) + ","
                             + " category=" + category + " role=" + role);
                 }
-                notifyKeyboardShortcutTriggered(keyEvent, getSystemShortcutFromIntent(intent));
+                notifyKeyGestureCompleted(keyEvent, getKeyGestureTypeFromIntent(intent));
                 return true;
             } else {
                 return false;
@@ -497,19 +496,19 @@
                         + "the activity to which it is registered was not found: "
                         + "META+ or SEARCH" + KeyEvent.keyCodeToString(keyCode));
             }
-            notifyKeyboardShortcutTriggered(keyEvent, getSystemShortcutFromIntent(shortcutIntent));
+            notifyKeyGestureCompleted(keyEvent, getKeyGestureTypeFromIntent(shortcutIntent));
             return true;
         }
         return false;
     }
 
-    private void notifyKeyboardShortcutTriggered(KeyEvent event,
-            @KeyboardSystemShortcut.SystemShortcut int systemShortcut) {
-        if (systemShortcut == KeyboardSystemShortcut.SYSTEM_SHORTCUT_UNSPECIFIED) {
+    private void notifyKeyGestureCompleted(KeyEvent event,
+            @KeyGestureEvent.KeyGestureType int gestureType) {
+        if (gestureType == KeyGestureEvent.KEY_GESTURE_TYPE_UNSPECIFIED) {
             return;
         }
-        mInputManagerInternal.notifyKeyboardShortcutTriggered(event.getDeviceId(),
-                new int[]{event.getKeyCode()}, event.getMetaState(), systemShortcut);
+        mInputManagerInternal.notifyKeyGestureCompleted(event.getDeviceId(),
+                new int[]{event.getKeyCode()}, event.getMetaState(), gestureType);
     }
 
     /**
@@ -710,21 +709,21 @@
 
 
     /**
-     * Find Keyboard shortcut event corresponding to intent filter category. Returns
-     * {@code SYSTEM_SHORTCUT_UNSPECIFIED if no matching event found}
+     * Find Key gesture type corresponding to intent filter category. Returns
+     * {@code KEY_GESTURE_TYPE_UNSPECIFIED if no matching event found}
      */
-    @KeyboardSystemShortcut.SystemShortcut
-    private static int getSystemShortcutFromIntent(Intent intent) {
+    @KeyGestureEvent.KeyGestureType
+    private static int getKeyGestureTypeFromIntent(Intent intent) {
         Intent selectorIntent = intent.getSelector();
         if (selectorIntent != null) {
             Set<String> selectorCategories = selectorIntent.getCategories();
             if (selectorCategories != null && !selectorCategories.isEmpty()) {
                 for (String intentCategory : selectorCategories) {
-                    int systemShortcut = getEventFromSelectorCategory(intentCategory);
-                    if (systemShortcut == KeyboardSystemShortcut.SYSTEM_SHORTCUT_UNSPECIFIED) {
+                    int keyGestureType = getKeyGestureTypeFromSelectorCategory(intentCategory);
+                    if (keyGestureType == KeyGestureEvent.KEY_GESTURE_TYPE_UNSPECIFIED) {
                         continue;
                     }
-                    return systemShortcut;
+                    return keyGestureType;
                 }
             }
         }
@@ -733,69 +732,68 @@
         // so check for that.
         String role = intent.getStringExtra(ModifierShortcutManager.EXTRA_ROLE);
         if (!TextUtils.isEmpty(role)) {
-            return getLogEventFromRole(role);
+            return getKeyGestureTypeFromRole(role);
         }
 
         Set<String> intentCategories = intent.getCategories();
         if (intentCategories == null || intentCategories.isEmpty()
                 || !intentCategories.contains(Intent.CATEGORY_LAUNCHER)) {
-            return KeyboardSystemShortcut.SYSTEM_SHORTCUT_UNSPECIFIED;
+            return KeyGestureEvent.KEY_GESTURE_TYPE_UNSPECIFIED;
         }
         if (intent.getComponent() == null) {
-            return KeyboardSystemShortcut.SYSTEM_SHORTCUT_UNSPECIFIED;
+            return KeyGestureEvent.KEY_GESTURE_TYPE_UNSPECIFIED;
         }
 
         // TODO(b/280423320): Add new field package name associated in the
         //  KeyboardShortcutEvent atom and log it accordingly.
-        return KeyboardSystemShortcut.SYSTEM_SHORTCUT_LAUNCH_APPLICATION_BY_PACKAGE_NAME;
+        return KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_APPLICATION_BY_PACKAGE_NAME;
     }
 
-    @KeyboardSystemShortcut.SystemShortcut
-    private static int getEventFromSelectorCategory(String category) {
+    @KeyGestureEvent.KeyGestureType
+    private static int getKeyGestureTypeFromSelectorCategory(String category) {
         switch (category) {
             case Intent.CATEGORY_APP_BROWSER:
-                return KeyboardSystemShortcut.SYSTEM_SHORTCUT_LAUNCH_DEFAULT_BROWSER;
+                return KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_DEFAULT_BROWSER;
             case Intent.CATEGORY_APP_EMAIL:
-                return KeyboardSystemShortcut.SYSTEM_SHORTCUT_LAUNCH_DEFAULT_EMAIL;
+                return KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_DEFAULT_EMAIL;
             case Intent.CATEGORY_APP_CONTACTS:
-                return KeyboardSystemShortcut.SYSTEM_SHORTCUT_LAUNCH_DEFAULT_CONTACTS;
+                return KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_DEFAULT_CONTACTS;
             case Intent.CATEGORY_APP_CALENDAR:
-                return KeyboardSystemShortcut.SYSTEM_SHORTCUT_LAUNCH_DEFAULT_CALENDAR;
+                return KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_DEFAULT_CALENDAR;
             case Intent.CATEGORY_APP_CALCULATOR:
-                return KeyboardSystemShortcut.SYSTEM_SHORTCUT_LAUNCH_DEFAULT_CALCULATOR;
+                return KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_DEFAULT_CALCULATOR;
             case Intent.CATEGORY_APP_MUSIC:
-                return KeyboardSystemShortcut.SYSTEM_SHORTCUT_LAUNCH_DEFAULT_MUSIC;
+                return KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_DEFAULT_MUSIC;
             case Intent.CATEGORY_APP_MAPS:
-                return KeyboardSystemShortcut.SYSTEM_SHORTCUT_LAUNCH_DEFAULT_MAPS;
+                return KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_DEFAULT_MAPS;
             case Intent.CATEGORY_APP_MESSAGING:
-                return KeyboardSystemShortcut.SYSTEM_SHORTCUT_LAUNCH_DEFAULT_MESSAGING;
+                return KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_DEFAULT_MESSAGING;
             case Intent.CATEGORY_APP_GALLERY:
-                return KeyboardSystemShortcut.SYSTEM_SHORTCUT_LAUNCH_DEFAULT_GALLERY;
+                return KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_DEFAULT_GALLERY;
             case Intent.CATEGORY_APP_FILES:
-                return KeyboardSystemShortcut.SYSTEM_SHORTCUT_LAUNCH_DEFAULT_FILES;
+                return KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_DEFAULT_FILES;
             case Intent.CATEGORY_APP_WEATHER:
-                return KeyboardSystemShortcut.SYSTEM_SHORTCUT_LAUNCH_DEFAULT_WEATHER;
+                return KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_DEFAULT_WEATHER;
             case Intent.CATEGORY_APP_FITNESS:
-                return KeyboardSystemShortcut.SYSTEM_SHORTCUT_LAUNCH_DEFAULT_FITNESS;
+                return KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_DEFAULT_FITNESS;
             default:
-                return KeyboardSystemShortcut.SYSTEM_SHORTCUT_UNSPECIFIED;
+                return KeyGestureEvent.KEY_GESTURE_TYPE_UNSPECIFIED;
         }
     }
 
     /**
-     * Find KeyboardLogEvent corresponding to the provide system role name.
-     * Returns {@code null} if no matching event found.
+     * Find KeyGestureType corresponding to the provide system role name.
+     * Returns {@code KEY_GESTURE_TYPE_UNSPECIFIED} if no matching event found.
      */
-    @KeyboardSystemShortcut.SystemShortcut
-    private static int getLogEventFromRole(String role) {
+    @KeyGestureEvent.KeyGestureType
+    private static int getKeyGestureTypeFromRole(String role) {
         if (RoleManager.ROLE_BROWSER.equals(role)) {
-            return KeyboardSystemShortcut.SYSTEM_SHORTCUT_LAUNCH_DEFAULT_BROWSER;
+            return KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_DEFAULT_BROWSER;
         } else if (RoleManager.ROLE_SMS.equals(role)) {
-            return KeyboardSystemShortcut.SYSTEM_SHORTCUT_LAUNCH_DEFAULT_MESSAGING;
+            return KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_DEFAULT_MESSAGING;
         } else {
-            Log.w(TAG, "Keyboard shortcut to launch "
-                    + role + " not supported for logging");
-            return KeyboardSystemShortcut.SYSTEM_SHORTCUT_UNSPECIFIED;
+            Log.w(TAG, "Keyboard gesture event to launch " + role + " not supported for logging");
+            return KeyGestureEvent.KEY_GESTURE_TYPE_UNSPECIFIED;
         }
     }
 
diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java
index 720c1c2..ba3de33 100644
--- a/services/core/java/com/android/server/policy/PhoneWindowManager.java
+++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java
@@ -139,7 +139,7 @@
 import android.hardware.hdmi.HdmiPlaybackClient;
 import android.hardware.hdmi.HdmiPlaybackClient.OneTouchPlayCallback;
 import android.hardware.input.InputManager;
-import android.hardware.input.KeyboardSystemShortcut;
+import android.hardware.input.KeyGestureEvent;
 import android.media.AudioManager;
 import android.media.AudioManagerInternal;
 import android.media.AudioSystem;
@@ -1819,7 +1819,7 @@
     }
 
     private void handleShortPressOnHome(KeyEvent event) {
-        notifyKeyboardShortcutTriggered(event, KeyboardSystemShortcut.SYSTEM_SHORTCUT_HOME);
+        notifyKeyGestureCompleted(event, KeyGestureEvent.KEY_GESTURE_TYPE_HOME);
 
         // Turn on the connected TV and switch HDMI input if we're a HDMI playback device.
         final HdmiControl hdmiControl = getHdmiControl();
@@ -2053,8 +2053,8 @@
             }
             switch (mDoubleTapOnHomeBehavior) {
                 case DOUBLE_TAP_HOME_RECENT_SYSTEM_UI:
-                    notifyKeyboardShortcutTriggered(event,
-                            KeyboardSystemShortcut.SYSTEM_SHORTCUT_APP_SWITCH);
+                    notifyKeyGestureCompleted(event,
+                            KeyGestureEvent.KEY_GESTURE_TYPE_APP_SWITCH);
                     mHomeConsumed = true;
                     toggleRecentApps();
                     break;
@@ -2082,23 +2082,23 @@
                 case LONG_PRESS_HOME_ALL_APPS:
                     if (mHasFeatureLeanback) {
                         launchAllAppsAction();
-                        notifyKeyboardShortcutTriggered(event,
-                                KeyboardSystemShortcut.SYSTEM_SHORTCUT_ALL_APPS);
+                        notifyKeyGestureCompleted(event,
+                                KeyGestureEvent.KEY_GESTURE_TYPE_ALL_APPS);
                     } else {
                         launchAllAppsViaA11y();
-                        notifyKeyboardShortcutTriggered(event,
-                                KeyboardSystemShortcut.SYSTEM_SHORTCUT_ACCESSIBILITY_ALL_APPS);
+                        notifyKeyGestureCompleted(event,
+                                KeyGestureEvent.KEY_GESTURE_TYPE_ACCESSIBILITY_ALL_APPS);
                     }
                     break;
                 case LONG_PRESS_HOME_ASSIST:
-                    notifyKeyboardShortcutTriggered(event,
-                            KeyboardSystemShortcut.SYSTEM_SHORTCUT_LAUNCH_ASSISTANT);
+                    notifyKeyGestureCompleted(event,
+                            KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_ASSISTANT);
                     launchAssistAction(null, event.getDeviceId(), event.getEventTime(),
                             AssistUtils.INVOCATION_TYPE_HOME_BUTTON_LONG_PRESS);
                     break;
                 case LONG_PRESS_HOME_NOTIFICATION_PANEL:
-                    notifyKeyboardShortcutTriggered(event,
-                            KeyboardSystemShortcut.SYSTEM_SHORTCUT_TOGGLE_NOTIFICATION_PANEL);
+                    notifyKeyGestureCompleted(event,
+                            KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_NOTIFICATION_PANEL);
                     toggleNotificationPanel();
                     break;
                 default:
@@ -3285,29 +3285,29 @@
             WindowManager.LayoutParams.TYPE_SYSTEM_ERROR,
         };
 
-    private void notifyKeyboardShortcutTriggeredOnActionUp(KeyEvent event,
-            @KeyboardSystemShortcut.SystemShortcut int systemShortcut) {
+    private void notifyKeyGestureCompletedOnActionUp(KeyEvent event,
+            @KeyGestureEvent.KeyGestureType int gestureType) {
         if (event.getAction() != KeyEvent.ACTION_UP) {
             return;
         }
-        notifyKeyboardShortcutTriggered(event, systemShortcut);
+        notifyKeyGestureCompleted(event, gestureType);
     }
 
-    private void notifyKeyboardShortcutTriggeredOnActionDown(KeyEvent event,
-            @KeyboardSystemShortcut.SystemShortcut int systemShortcut) {
+    private void notifyKeyGestureCompletedOnActionDown(KeyEvent event,
+            @KeyGestureEvent.KeyGestureType int gestureType) {
         if (event.getAction() != KeyEvent.ACTION_DOWN) {
             return;
         }
-        notifyKeyboardShortcutTriggered(event, systemShortcut);
+        notifyKeyGestureCompleted(event, gestureType);
     }
 
-    private void notifyKeyboardShortcutTriggered(KeyEvent event,
-            @KeyboardSystemShortcut.SystemShortcut int systemShortcut) {
-        if (systemShortcut == KeyboardSystemShortcut.SYSTEM_SHORTCUT_UNSPECIFIED) {
+    private void notifyKeyGestureCompleted(KeyEvent event,
+            @KeyGestureEvent.KeyGestureType int gestureType) {
+        if (gestureType == KeyGestureEvent.KEY_GESTURE_TYPE_UNSPECIFIED) {
             return;
         }
-        mInputManagerInternal.notifyKeyboardShortcutTriggered(event.getDeviceId(),
-                new int[]{event.getKeyCode()}, event.getMetaState(), systemShortcut);
+        mInputManagerInternal.notifyKeyGestureCompleted(event.getDeviceId(),
+                new int[]{event.getKeyCode()}, event.getMetaState(), gestureType);
     }
 
     @Override
@@ -3352,6 +3352,12 @@
             mConsumedKeysForDevice.put(deviceId, consumedKeys);
         }
 
+        // TODO(b/358569822) Remove below once we have nicer API for listening to shortcuts
+        if ((event.isMetaPressed() || KeyEvent.isMetaKey(keyCode))
+                && shouldInterceptShortcuts(focusedToken)) {
+            return keyNotConsumed;
+        }
+
         if (interceptSystemKeysAndShortcuts(focusedToken, event)
                 && event.getAction() == KeyEvent.ACTION_DOWN && event.getRepeatCount() == 0) {
             consumedKeys.add(keyCode);
@@ -3417,8 +3423,8 @@
             case KeyEvent.KEYCODE_RECENT_APPS:
                 if (firstDown) {
                     showRecentApps(false /* triggeredFromAltTab */);
-                    notifyKeyboardShortcutTriggered(event,
-                            KeyboardSystemShortcut.SYSTEM_SHORTCUT_RECENT_APPS);
+                    notifyKeyGestureCompleted(event,
+                            KeyGestureEvent.KEY_GESTURE_TYPE_RECENT_APPS);
                 }
                 return true;
             case KeyEvent.KEYCODE_APP_SWITCH:
@@ -3427,8 +3433,8 @@
                         preloadRecentApps();
                     } else if (!down) {
                         toggleRecentApps();
-                        notifyKeyboardShortcutTriggered(event,
-                                KeyboardSystemShortcut.SYSTEM_SHORTCUT_APP_SWITCH);
+                        notifyKeyGestureCompleted(event,
+                                KeyGestureEvent.KEY_GESTURE_TYPE_APP_SWITCH);
                     }
                 }
                 return true;
@@ -3437,8 +3443,8 @@
                     launchAssistAction(Intent.EXTRA_ASSIST_INPUT_HINT_KEYBOARD,
                             deviceId, event.getEventTime(),
                             AssistUtils.INVOCATION_TYPE_UNKNOWN);
-                    notifyKeyboardShortcutTriggered(event,
-                            KeyboardSystemShortcut.SYSTEM_SHORTCUT_LAUNCH_ASSISTANT);
+                    notifyKeyGestureCompleted(event,
+                            KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_ASSISTANT);
                     return true;
                 }
                 break;
@@ -3451,16 +3457,16 @@
             case KeyEvent.KEYCODE_I:
                 if (firstDown && event.isMetaPressed()) {
                     showSystemSettings();
-                    notifyKeyboardShortcutTriggered(event,
-                            KeyboardSystemShortcut.SYSTEM_SHORTCUT_LAUNCH_SYSTEM_SETTINGS);
+                    notifyKeyGestureCompleted(event,
+                            KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_SYSTEM_SETTINGS);
                     return true;
                 }
                 break;
             case KeyEvent.KEYCODE_L:
                 if (firstDown && event.isMetaPressed()) {
                     lockNow(null /* options */);
-                    notifyKeyboardShortcutTriggered(event,
-                            KeyboardSystemShortcut.SYSTEM_SHORTCUT_LOCK_SCREEN);
+                    notifyKeyGestureCompleted(event,
+                            KeyGestureEvent.KEY_GESTURE_TYPE_LOCK_SCREEN);
                     return true;
                 }
                 break;
@@ -3468,12 +3474,12 @@
                 if (firstDown && event.isMetaPressed()) {
                     if (event.isCtrlPressed()) {
                         sendSystemKeyToStatusBarAsync(event);
-                        notifyKeyboardShortcutTriggered(event,
-                                KeyboardSystemShortcut.SYSTEM_SHORTCUT_OPEN_NOTES);
+                        notifyKeyGestureCompleted(event,
+                                KeyGestureEvent.KEY_GESTURE_TYPE_OPEN_NOTES);
                     } else {
                         toggleNotificationPanel();
-                        notifyKeyboardShortcutTriggered(event,
-                                KeyboardSystemShortcut.SYSTEM_SHORTCUT_TOGGLE_NOTIFICATION_PANEL);
+                        notifyKeyGestureCompleted(event,
+                                KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_NOTIFICATION_PANEL);
                     }
                     return true;
                 }
@@ -3481,8 +3487,8 @@
             case KeyEvent.KEYCODE_S:
                 if (firstDown && event.isMetaPressed() && event.isCtrlPressed()) {
                     interceptScreenshotChord(SCREENSHOT_KEY_OTHER, 0 /*pressDelay*/);
-                    notifyKeyboardShortcutTriggered(event,
-                            KeyboardSystemShortcut.SYSTEM_SHORTCUT_TAKE_SCREENSHOT);
+                    notifyKeyGestureCompleted(event,
+                            KeyGestureEvent.KEY_GESTURE_TYPE_TAKE_SCREENSHOT);
                     return true;
                 }
                 break;
@@ -3495,16 +3501,16 @@
                         } catch (RemoteException e) {
                             Slog.d(TAG, "Error taking bugreport", e);
                         }
-                        notifyKeyboardShortcutTriggered(event,
-                                KeyboardSystemShortcut.SYSTEM_SHORTCUT_TRIGGER_BUG_REPORT);
+                        notifyKeyGestureCompleted(event,
+                                KeyGestureEvent.KEY_GESTURE_TYPE_TRIGGER_BUG_REPORT);
                         return true;
                     }
                 }
                 // fall through
             case KeyEvent.KEYCODE_ESCAPE:
                 if (firstDown && event.isMetaPressed()) {
-                    notifyKeyboardShortcutTriggered(event,
-                            KeyboardSystemShortcut.SYSTEM_SHORTCUT_BACK);
+                    notifyKeyGestureCompleted(event,
+                            KeyGestureEvent.KEY_GESTURE_TYPE_BACK);
                     injectBackGesture(event.getDownTime());
                     return true;
                 }
@@ -3513,8 +3519,8 @@
                     StatusBarManagerInternal statusbar = getStatusBarManagerInternal();
                     if (statusbar != null) {
                         statusbar.moveFocusedTaskToFullscreen(getTargetDisplayIdForKeyEvent(event));
-                        notifyKeyboardShortcutTriggered(event,
-                                KeyboardSystemShortcut.SYSTEM_SHORTCUT_MULTI_WINDOW_NAVIGATION);
+                        notifyKeyGestureCompleted(event,
+                                KeyGestureEvent.KEY_GESTURE_TYPE_MULTI_WINDOW_NAVIGATION);
                         return true;
                     }
                 }
@@ -3524,8 +3530,8 @@
                     StatusBarManagerInternal statusbar = getStatusBarManagerInternal();
                     if (statusbar != null) {
                         statusbar.moveFocusedTaskToDesktop(getTargetDisplayIdForKeyEvent(event));
-                        notifyKeyboardShortcutTriggered(event,
-                                KeyboardSystemShortcut.SYSTEM_SHORTCUT_DESKTOP_MODE);
+                        notifyKeyGestureCompleted(event,
+                                KeyGestureEvent.KEY_GESTURE_TYPE_DESKTOP_MODE);
                         return true;
                     }
                 }
@@ -3535,15 +3541,15 @@
                     if (event.isCtrlPressed()) {
                         moveFocusedTaskToStageSplit(getTargetDisplayIdForKeyEvent(event),
                                 true /* leftOrTop */);
-                        notifyKeyboardShortcutTriggered(event,
-                                KeyboardSystemShortcut.SYSTEM_SHORTCUT_SPLIT_SCREEN_NAVIGATION);
+                        notifyKeyGestureCompleted(event,
+                                KeyGestureEvent.KEY_GESTURE_TYPE_SPLIT_SCREEN_NAVIGATION);
                     } else if (event.isAltPressed()) {
                         setSplitscreenFocus(true /* leftOrTop */);
-                        notifyKeyboardShortcutTriggered(event,
-                                KeyboardSystemShortcut.SYSTEM_SHORTCUT_CHANGE_SPLITSCREEN_FOCUS);
+                        notifyKeyGestureCompleted(event,
+                                KeyGestureEvent.KEY_GESTURE_TYPE_CHANGE_SPLITSCREEN_FOCUS);
                     } else {
-                        notifyKeyboardShortcutTriggered(event,
-                                KeyboardSystemShortcut.SYSTEM_SHORTCUT_BACK);
+                        notifyKeyGestureCompleted(event,
+                                KeyGestureEvent.KEY_GESTURE_TYPE_BACK);
                         injectBackGesture(event.getDownTime());
                     }
                     return true;
@@ -3554,13 +3560,13 @@
                     if (event.isCtrlPressed()) {
                         moveFocusedTaskToStageSplit(getTargetDisplayIdForKeyEvent(event),
                                 false /* leftOrTop */);
-                        notifyKeyboardShortcutTriggered(event,
-                                KeyboardSystemShortcut.SYSTEM_SHORTCUT_SPLIT_SCREEN_NAVIGATION);
+                        notifyKeyGestureCompleted(event,
+                                KeyGestureEvent.KEY_GESTURE_TYPE_SPLIT_SCREEN_NAVIGATION);
                         return true;
                     } else if (event.isAltPressed()) {
                         setSplitscreenFocus(false /* leftOrTop */);
-                        notifyKeyboardShortcutTriggered(event,
-                                KeyboardSystemShortcut.SYSTEM_SHORTCUT_CHANGE_SPLITSCREEN_FOCUS);
+                        notifyKeyGestureCompleted(event,
+                                KeyGestureEvent.KEY_GESTURE_TYPE_CHANGE_SPLITSCREEN_FOCUS);
                         return true;
                     }
                 }
@@ -3568,8 +3574,8 @@
             case KeyEvent.KEYCODE_SLASH:
                 if (firstDown && event.isMetaPressed() && !keyguardOn) {
                     toggleKeyboardShortcutsMenu(event.getDeviceId());
-                    notifyKeyboardShortcutTriggered(event,
-                            KeyboardSystemShortcut.SYSTEM_SHORTCUT_OPEN_SHORTCUT_HELPER);
+                    notifyKeyGestureCompleted(event,
+                            KeyGestureEvent.KEY_GESTURE_TYPE_OPEN_SHORTCUT_HELPER);
                     return true;
                 }
                 break;
@@ -3622,31 +3628,31 @@
                     intent.putExtra(EXTRA_FROM_BRIGHTNESS_KEY, true);
                     startActivityAsUser(intent, UserHandle.CURRENT_OR_SELF);
 
-                    int systemShortcut = keyCode == KeyEvent.KEYCODE_BRIGHTNESS_DOWN
-                            ? KeyboardSystemShortcut.SYSTEM_SHORTCUT_BRIGHTNESS_DOWN
-                            : KeyboardSystemShortcut.SYSTEM_SHORTCUT_BRIGHTNESS_UP;
-                    notifyKeyboardShortcutTriggered(event, systemShortcut);
+                    int gestureType = keyCode == KeyEvent.KEYCODE_BRIGHTNESS_DOWN
+                            ? KeyGestureEvent.KEY_GESTURE_TYPE_BRIGHTNESS_DOWN
+                            : KeyGestureEvent.KEY_GESTURE_TYPE_BRIGHTNESS_UP;
+                    notifyKeyGestureCompleted(event, gestureType);
                 }
                 return true;
             case KeyEvent.KEYCODE_KEYBOARD_BACKLIGHT_DOWN:
                 if (down) {
                     mInputManagerInternal.decrementKeyboardBacklight(event.getDeviceId());
-                    notifyKeyboardShortcutTriggered(event,
-                            KeyboardSystemShortcut.SYSTEM_SHORTCUT_KEYBOARD_BACKLIGHT_DOWN);
+                    notifyKeyGestureCompleted(event,
+                            KeyGestureEvent.KEY_GESTURE_TYPE_KEYBOARD_BACKLIGHT_DOWN);
                 }
                 return true;
             case KeyEvent.KEYCODE_KEYBOARD_BACKLIGHT_UP:
                 if (down) {
                     mInputManagerInternal.incrementKeyboardBacklight(event.getDeviceId());
-                    notifyKeyboardShortcutTriggered(event,
-                            KeyboardSystemShortcut.SYSTEM_SHORTCUT_KEYBOARD_BACKLIGHT_UP);
+                    notifyKeyGestureCompleted(event,
+                            KeyGestureEvent.KEY_GESTURE_TYPE_KEYBOARD_BACKLIGHT_UP);
                 }
                 return true;
             case KeyEvent.KEYCODE_KEYBOARD_BACKLIGHT_TOGGLE:
                 // TODO: Add logic
                 if (!down) {
-                    notifyKeyboardShortcutTriggered(event,
-                            KeyboardSystemShortcut.SYSTEM_SHORTCUT_KEYBOARD_BACKLIGHT_TOGGLE);
+                    notifyKeyGestureCompleted(event,
+                            KeyGestureEvent.KEY_GESTURE_TYPE_KEYBOARD_BACKLIGHT_TOGGLE);
                 }
                 return true;
             case KeyEvent.KEYCODE_VOLUME_UP:
@@ -3673,8 +3679,8 @@
                 if (firstDown && !keyguardOn && isUserSetupComplete()) {
                     if (event.isMetaPressed()) {
                         showRecentApps(false);
-                        notifyKeyboardShortcutTriggered(event,
-                                KeyboardSystemShortcut.SYSTEM_SHORTCUT_RECENT_APPS);
+                        notifyKeyGestureCompleted(event,
+                                KeyGestureEvent.KEY_GESTURE_TYPE_RECENT_APPS);
                         return true;
                     } else if (mRecentAppsHeldModifiers == 0) {
                         final int shiftlessModifiers =
@@ -3683,8 +3689,8 @@
                                 shiftlessModifiers, KeyEvent.META_ALT_ON)) {
                             mRecentAppsHeldModifiers = shiftlessModifiers;
                             showRecentApps(true);
-                            notifyKeyboardShortcutTriggered(event,
-                                    KeyboardSystemShortcut.SYSTEM_SHORTCUT_RECENT_APPS);
+                            notifyKeyGestureCompleted(event,
+                                    KeyGestureEvent.KEY_GESTURE_TYPE_RECENT_APPS);
                             return true;
                         }
                     }
@@ -3697,20 +3703,20 @@
                         Message msg = mHandler.obtainMessage(MSG_HANDLE_ALL_APPS);
                         msg.setAsynchronous(true);
                         msg.sendToTarget();
-                        notifyKeyboardShortcutTriggered(event,
-                                KeyboardSystemShortcut.SYSTEM_SHORTCUT_ALL_APPS);
+                        notifyKeyGestureCompleted(event,
+                                KeyGestureEvent.KEY_GESTURE_TYPE_ALL_APPS);
                     } else {
                         launchAllAppsViaA11y();
-                        notifyKeyboardShortcutTriggered(event,
-                                KeyboardSystemShortcut.SYSTEM_SHORTCUT_ACCESSIBILITY_ALL_APPS);
+                        notifyKeyGestureCompleted(event,
+                                KeyGestureEvent.KEY_GESTURE_TYPE_ACCESSIBILITY_ALL_APPS);
                     }
                 }
                 return true;
             case KeyEvent.KEYCODE_NOTIFICATION:
                 if (!down) {
                     toggleNotificationPanel();
-                    notifyKeyboardShortcutTriggered(event,
-                            KeyboardSystemShortcut.SYSTEM_SHORTCUT_TOGGLE_NOTIFICATION_PANEL);
+                    notifyKeyGestureCompleted(event,
+                            KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_NOTIFICATION_PANEL);
                 }
                 return true;
             case KeyEvent.KEYCODE_SEARCH:
@@ -3718,8 +3724,8 @@
                     switch (mSearchKeyBehavior) {
                         case SEARCH_KEY_BEHAVIOR_TARGET_ACTIVITY: {
                             launchTargetSearchActivity();
-                            notifyKeyboardShortcutTriggered(event,
-                                    KeyboardSystemShortcut.SYSTEM_SHORTCUT_LAUNCH_SEARCH);
+                            notifyKeyGestureCompleted(event,
+                                    KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_SEARCH);
                             return true;
                         }
                         case SEARCH_KEY_BEHAVIOR_DEFAULT_SEARCH:
@@ -3732,8 +3738,8 @@
                 if (firstDown) {
                     int direction = (metaState & KeyEvent.META_SHIFT_MASK) != 0 ? -1 : 1;
                     sendSwitchKeyboardLayout(event, focusedToken, direction);
-                    notifyKeyboardShortcutTriggered(event,
-                            KeyboardSystemShortcut.SYSTEM_SHORTCUT_LANGUAGE_SWITCH);
+                    notifyKeyGestureCompleted(event,
+                            KeyGestureEvent.KEY_GESTURE_TYPE_LANGUAGE_SWITCH);
                     return true;
                 }
                 break;
@@ -3752,13 +3758,13 @@
                     if (mPendingCapsLockToggle) {
                         mInputManagerInternal.toggleCapsLock(event.getDeviceId());
                         mPendingCapsLockToggle = false;
-                        notifyKeyboardShortcutTriggered(event,
-                                KeyboardSystemShortcut.SYSTEM_SHORTCUT_TOGGLE_CAPS_LOCK);
+                        notifyKeyGestureCompleted(event,
+                                KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_CAPS_LOCK);
                     } else if (mPendingMetaAction) {
                         if (!canceled) {
                             launchAllAppsViaA11y();
-                            notifyKeyboardShortcutTriggered(event,
-                                    KeyboardSystemShortcut.SYSTEM_SHORTCUT_ACCESSIBILITY_ALL_APPS);
+                            notifyKeyGestureCompleted(event,
+                                    KeyGestureEvent.KEY_GESTURE_TYPE_ACCESSIBILITY_ALL_APPS);
                         }
                         mPendingMetaAction = false;
                     }
@@ -3786,16 +3792,16 @@
                     if (mPendingCapsLockToggle) {
                         mInputManagerInternal.toggleCapsLock(event.getDeviceId());
                         mPendingCapsLockToggle = false;
-                        notifyKeyboardShortcutTriggered(event,
-                                KeyboardSystemShortcut.SYSTEM_SHORTCUT_TOGGLE_CAPS_LOCK);
+                        notifyKeyGestureCompleted(event,
+                                KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_CAPS_LOCK);
                         return true;
                     }
                 }
                 break;
             case KeyEvent.KEYCODE_CAPS_LOCK:
                 if (!down) {
-                    notifyKeyboardShortcutTriggered(event,
-                            KeyboardSystemShortcut.SYSTEM_SHORTCUT_TOGGLE_CAPS_LOCK);
+                    notifyKeyGestureCompleted(event,
+                            KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_CAPS_LOCK);
                 }
                 break;
             case KeyEvent.KEYCODE_STYLUS_BUTTON_PRIMARY:
@@ -3809,12 +3815,12 @@
                 if (firstDown) {
                     if (mSettingsKeyBehavior == SETTINGS_KEY_BEHAVIOR_NOTIFICATION_PANEL) {
                         toggleNotificationPanel();
-                        notifyKeyboardShortcutTriggered(event,
-                                KeyboardSystemShortcut.SYSTEM_SHORTCUT_TOGGLE_NOTIFICATION_PANEL);
+                        notifyKeyGestureCompleted(event,
+                                KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_NOTIFICATION_PANEL);
                     } else if (mSettingsKeyBehavior == SETTINGS_KEY_BEHAVIOR_SETTINGS_ACTIVITY) {
                         showSystemSettings();
-                        notifyKeyboardShortcutTriggered(event,
-                                KeyboardSystemShortcut.SYSTEM_SHORTCUT_LAUNCH_SYSTEM_SETTINGS);
+                        notifyKeyGestureCompleted(event,
+                                KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_SYSTEM_SETTINGS);
                     }
                 }
                 return true;
@@ -3842,6 +3848,15 @@
         return (metaState & KeyEvent.META_META_ON) != 0;
     }
 
+    private boolean shouldInterceptShortcuts(IBinder focusedToken) {
+        KeyInterceptionInfo info =
+                mWindowManagerInternal.getKeyInterceptionInfoFromToken(focusedToken);
+        boolean hasInterceptWindowFlag = (info.layoutParamsPrivateFlags
+                & WindowManager.LayoutParams.PRIVATE_FLAG_ALLOW_ACTION_KEY_EVENTS) != 0;
+        return hasInterceptWindowFlag && mButtonOverridePermissionChecker.canAppOverrideSystemKey(
+                mContext, info.windowOwnerUid);
+    }
+
     /**
      * In this function, we check whether a system key should be sent to the application. We also
      * detect the key gesture on this key, even if the key will be sent to the app. The gesture
@@ -4760,8 +4775,8 @@
         // Handle special keys.
         switch (keyCode) {
             case KeyEvent.KEYCODE_BACK: {
-                notifyKeyboardShortcutTriggeredOnActionUp(event,
-                        KeyboardSystemShortcut.SYSTEM_SHORTCUT_BACK);
+                notifyKeyGestureCompletedOnActionUp(event,
+                        KeyGestureEvent.KEY_GESTURE_TYPE_BACK);
                 if (down) {
                     // There may have other embedded activities on the same Task. Try to move the
                     // focus before processing the back event.
@@ -4782,12 +4797,12 @@
             case KeyEvent.KEYCODE_VOLUME_DOWN:
             case KeyEvent.KEYCODE_VOLUME_UP:
             case KeyEvent.KEYCODE_VOLUME_MUTE: {
-                int systemShortcut = keyCode == KEYCODE_VOLUME_DOWN
-                        ? KeyboardSystemShortcut.SYSTEM_SHORTCUT_VOLUME_DOWN
+                int gestureType = keyCode == KEYCODE_VOLUME_DOWN
+                        ? KeyGestureEvent.KEY_GESTURE_TYPE_VOLUME_DOWN
                         : keyCode == KEYCODE_VOLUME_UP
-                                ? KeyboardSystemShortcut.SYSTEM_SHORTCUT_VOLUME_UP
-                                : KeyboardSystemShortcut.SYSTEM_SHORTCUT_VOLUME_MUTE;
-                notifyKeyboardShortcutTriggeredOnActionDown(event, systemShortcut);
+                                ? KeyGestureEvent.KEY_GESTURE_TYPE_VOLUME_UP
+                                : KeyGestureEvent.KEY_GESTURE_TYPE_VOLUME_MUTE;
+                notifyKeyGestureCompletedOnActionDown(event, gestureType);
                 if (down) {
                     sendSystemKeyToStatusBarAsync(event);
 
@@ -4888,8 +4903,8 @@
             }
 
             case KeyEvent.KEYCODE_TV_POWER: {
-                notifyKeyboardShortcutTriggeredOnActionUp(event,
-                        KeyboardSystemShortcut.SYSTEM_SHORTCUT_TOGGLE_POWER);
+                notifyKeyGestureCompletedOnActionUp(event,
+                        KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_POWER);
                 result &= ~ACTION_PASS_TO_USER;
                 isWakeKey = false; // wake-up will be handled separately
                 if (down && hdmiControlManager != null) {
@@ -4899,8 +4914,8 @@
             }
 
             case KeyEvent.KEYCODE_POWER: {
-                notifyKeyboardShortcutTriggeredOnActionUp(event,
-                        KeyboardSystemShortcut.SYSTEM_SHORTCUT_TOGGLE_POWER);
+                notifyKeyGestureCompletedOnActionUp(event,
+                        KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_POWER);
                 EventLogTags.writeInterceptPower(
                         KeyEvent.actionToString(event.getAction()),
                         mPowerKeyHandled ? 1 : 0,
@@ -4923,16 +4938,16 @@
             case KeyEvent.KEYCODE_SYSTEM_NAVIGATION_LEFT:
                 // fall through
             case KeyEvent.KEYCODE_SYSTEM_NAVIGATION_RIGHT: {
-                notifyKeyboardShortcutTriggeredOnActionUp(event,
-                        KeyboardSystemShortcut.SYSTEM_SHORTCUT_SYSTEM_NAVIGATION);
+                notifyKeyGestureCompletedOnActionUp(event,
+                        KeyGestureEvent.KEY_GESTURE_TYPE_SYSTEM_NAVIGATION);
                 result &= ~ACTION_PASS_TO_USER;
                 interceptSystemNavigationKey(event);
                 break;
             }
 
             case KeyEvent.KEYCODE_SLEEP: {
-                notifyKeyboardShortcutTriggeredOnActionUp(event,
-                        KeyboardSystemShortcut.SYSTEM_SHORTCUT_SLEEP);
+                notifyKeyGestureCompletedOnActionUp(event,
+                        KeyGestureEvent.KEY_GESTURE_TYPE_SLEEP);
                 result &= ~ACTION_PASS_TO_USER;
                 isWakeKey = false;
                 if (!mPowerManager.isInteractive()) {
@@ -4948,8 +4963,8 @@
             }
 
             case KeyEvent.KEYCODE_SOFT_SLEEP: {
-                notifyKeyboardShortcutTriggeredOnActionUp(event,
-                        KeyboardSystemShortcut.SYSTEM_SHORTCUT_SLEEP);
+                notifyKeyGestureCompletedOnActionUp(event,
+                        KeyGestureEvent.KEY_GESTURE_TYPE_SLEEP);
                 result &= ~ACTION_PASS_TO_USER;
                 isWakeKey = false;
                 if (!down) {
@@ -4960,8 +4975,8 @@
             }
 
             case KeyEvent.KEYCODE_WAKEUP: {
-                notifyKeyboardShortcutTriggeredOnActionUp(event,
-                        KeyboardSystemShortcut.SYSTEM_SHORTCUT_WAKEUP);
+                notifyKeyGestureCompletedOnActionUp(event,
+                        KeyGestureEvent.KEY_GESTURE_TYPE_WAKEUP);
                 result &= ~ACTION_PASS_TO_USER;
                 isWakeKey = true;
                 break;
@@ -4970,8 +4985,8 @@
             case KeyEvent.KEYCODE_MUTE:
                 result &= ~ACTION_PASS_TO_USER;
                 if (down && event.getRepeatCount() == 0) {
-                    notifyKeyboardShortcutTriggered(event,
-                            KeyboardSystemShortcut.SYSTEM_SHORTCUT_SYSTEM_MUTE);
+                    notifyKeyGestureCompleted(event,
+                            KeyGestureEvent.KEY_GESTURE_TYPE_SYSTEM_MUTE);
                     toggleMicrophoneMuteFromKey();
                 }
                 break;
@@ -4986,8 +5001,8 @@
             case KeyEvent.KEYCODE_MEDIA_RECORD:
             case KeyEvent.KEYCODE_MEDIA_FAST_FORWARD:
             case KeyEvent.KEYCODE_MEDIA_AUDIO_TRACK: {
-                notifyKeyboardShortcutTriggeredOnActionUp(event,
-                        KeyboardSystemShortcut.SYSTEM_SHORTCUT_MEDIA_KEY);
+                notifyKeyGestureCompletedOnActionUp(event,
+                        KeyGestureEvent.KEY_GESTURE_TYPE_MEDIA_KEY);
                 if (MediaSessionLegacyHelper.getHelper(mContext).isGlobalPriorityActive()) {
                     // If the global session is active pass all media keys to it
                     // instead of the active window.
@@ -5032,8 +5047,8 @@
                             0 /* unused */, event.getEventTime() /* eventTime */);
                     msg.setAsynchronous(true);
                     msg.sendToTarget();
-                    notifyKeyboardShortcutTriggered(event,
-                            KeyboardSystemShortcut.SYSTEM_SHORTCUT_LAUNCH_ASSISTANT);
+                    notifyKeyGestureCompleted(event,
+                            KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_ASSISTANT);
                 }
                 result &= ~ACTION_PASS_TO_USER;
                 break;
@@ -5044,8 +5059,8 @@
                     Message msg = mHandler.obtainMessage(MSG_LAUNCH_VOICE_ASSIST_WITH_WAKE_LOCK);
                     msg.setAsynchronous(true);
                     msg.sendToTarget();
-                    notifyKeyboardShortcutTriggered(event,
-                            KeyboardSystemShortcut.SYSTEM_SHORTCUT_LAUNCH_VOICE_ASSISTANT);
+                    notifyKeyGestureCompleted(event,
+                            KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_VOICE_ASSISTANT);
                 }
                 result &= ~ACTION_PASS_TO_USER;
                 break;
diff --git a/services/core/java/com/android/server/power/PowerManagerService.java b/services/core/java/com/android/server/power/PowerManagerService.java
index b22b726..27024a7 100644
--- a/services/core/java/com/android/server/power/PowerManagerService.java
+++ b/services/core/java/com/android/server/power/PowerManagerService.java
@@ -101,7 +101,6 @@
 import android.provider.Settings;
 import android.provider.Settings.SettingNotFoundException;
 import android.service.dreams.DreamManagerInternal;
-import android.sysprop.InitProperties;
 import android.sysprop.PowerProperties;
 import android.util.ArrayMap;
 import android.util.IntArray;
@@ -132,7 +131,6 @@
 import com.android.server.ServiceThread;
 import com.android.server.SystemService;
 import com.android.server.UiThread;
-import com.android.server.UserspaceRebootLogger;
 import com.android.server.Watchdog;
 import com.android.server.am.BatteryStatsService;
 import com.android.server.display.feature.DeviceConfigParameterProvider;
@@ -1274,8 +1272,7 @@
             mHalInteractiveModeEnabled = true;
 
             mWakefulnessRaw = WAKEFULNESS_AWAKE;
-            sQuiescent = mSystemProperties.get(SYSTEM_PROPERTY_QUIESCENT, "0").equals("1")
-                    || InitProperties.userspace_reboot_in_progress().orElse(false);
+            sQuiescent = mSystemProperties.get(SYSTEM_PROPERTY_QUIESCENT, "0").equals("1");
 
             mNativeWrapper.nativeInit(this);
             mNativeWrapper.nativeSetAutoSuspend(false);
@@ -4032,7 +4029,6 @@
                 throw new UnsupportedOperationException(
                         "Attempted userspace reboot on a device that doesn't support it");
             }
-            UserspaceRebootLogger.noteUserspaceRebootWasRequested();
         }
         if (mHandler == null || !mSystemReady) {
             if (RescueParty.isRecoveryTriggeredReboot()) {
diff --git a/services/core/java/com/android/server/rollback/RollbackPackageHealthObserver.java b/services/core/java/com/android/server/rollback/RollbackPackageHealthObserver.java
index 4f28e02..e91097c 100644
--- a/services/core/java/com/android/server/rollback/RollbackPackageHealthObserver.java
+++ b/services/core/java/com/android/server/rollback/RollbackPackageHealthObserver.java
@@ -16,6 +16,8 @@
 
 package com.android.server.rollback;
 
+import static android.content.pm.Flags.provideInfoOfApkInApex;
+
 import android.annotation.AnyThread;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
@@ -23,6 +25,7 @@
 import android.content.Context;
 import android.content.Intent;
 import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageInfo;
 import android.content.pm.PackageManager;
 import android.content.pm.VersionedPackage;
 import android.content.rollback.PackageRollbackInfo;
@@ -230,7 +233,7 @@
 
 
     @Override
-    public String getName() {
+    public String getUniqueIdentifier() {
         return NAME;
     }
 
@@ -486,19 +489,40 @@
      */
     @AnyThread
     private boolean isModule(String packageName) {
-        // Check if the package is an APK inside an APEX. If it is, use the parent APEX package when
-        // querying PackageManager.
-        String apexPackageName = mApexManager.getActiveApexPackageNameContainingPackage(
-                packageName);
-        if (apexPackageName != null) {
-            packageName = apexPackageName;
-        }
-
         PackageManager pm = mContext.getPackageManager();
-        try {
-            return pm.getModuleInfo(packageName, 0) != null;
-        } catch (PackageManager.NameNotFoundException ignore) {
-            return false;
+
+        if (Flags.refactorCrashrecovery() && provideInfoOfApkInApex()) {
+            // Check if the package is listed among the system modules.
+            boolean isApex = false;
+            try {
+                isApex = (pm.getModuleInfo(packageName, 0 /* flags */) != null);
+            } catch (PackageManager.NameNotFoundException e) {
+                //pass
+            }
+
+            // Check if the package is an APK inside an APEX.
+            boolean isApkInApex = false;
+            try {
+                final PackageInfo pkg = pm.getPackageInfo(packageName, 0 /* flags */);
+                isApkInApex = (pkg.getApexPackageName() != null);
+            } catch (PackageManager.NameNotFoundException e) {
+                // pass
+            }
+            return isApex || isApkInApex;
+        } else {
+            // Check if the package is an APK inside an APEX. If it is, use the parent APEX package
+            // when querying PackageManager.
+            String apexPackageName = mApexManager.getActiveApexPackageNameContainingPackage(
+                    packageName);
+            if (apexPackageName != null) {
+                packageName = apexPackageName;
+            }
+
+            try {
+                return pm.getModuleInfo(packageName, 0) != null;
+            } catch (PackageManager.NameNotFoundException ignore) {
+                return false;
+            }
         }
     }
 
diff --git a/services/core/java/com/android/server/tv/TvInputManagerService.java b/services/core/java/com/android/server/tv/TvInputManagerService.java
index 6721893..c543b6d 100644
--- a/services/core/java/com/android/server/tv/TvInputManagerService.java
+++ b/services/core/java/com/android/server/tv/TvInputManagerService.java
@@ -19,6 +19,7 @@
 import static android.media.AudioManager.DEVICE_NONE;
 import static android.media.tv.TvInputManager.INPUT_STATE_CONNECTED;
 import static android.media.tv.TvInputManager.INPUT_STATE_CONNECTED_STANDBY;
+import static android.media.tv.flags.Flags.kidsModeTvdbSharing;
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
@@ -805,6 +806,19 @@
         }
     }
 
+    private boolean isServiceSingleUser(ComponentName component) {
+        try {
+            ServiceInfo serviceInfo = getContext().getPackageManager()
+                    .getServiceInfo(component, 0);
+            // Check if the single-user flag is present
+            return (serviceInfo.flags & ServiceInfo.FLAG_SINGLE_USER) != 0;
+        } catch (PackageManager.NameNotFoundException e) {
+            // Handle the case where the service is not found
+            Slog.e(TAG, "Service not found: " + component, e);
+            return false;
+        }
+    }
+
     @GuardedBy("mLock")
     private void abortPendingCreateSessionRequestsLocked(ServiceState serviceState,
             String inputId, int userId) {
@@ -2840,6 +2854,26 @@
         }
 
         @Override
+        public int getClientUserId(String sessionId) {
+            ensureTunerResourceAccessPermission();
+            int clientUserId = TvInputManager.UNKNOWN_CLIENT_USER_ID;
+
+            final long identity = Binder.clearCallingIdentity();
+            try {
+                synchronized (mLock) {
+                    try {
+                        clientUserId = getClientUserIdLocked(sessionId);
+                    } catch (ClientUserIdNotFoundException e) {
+                        Slog.e(TAG, "error in getClientUserId", e);
+                    }
+                }
+            } finally {
+                Binder.restoreCallingIdentity(identity);
+            }
+            return clientUserId;
+        }
+
+        @Override
         public int getClientPriority(int useCase, String sessionId) {
             ensureTunerResourceAccessPermission();
             final int callingPid = Binder.getCallingPid();
@@ -2924,6 +2958,16 @@
             return mSessionIdToSessionStateMap.get(sessionId).callingPid;
         }
 
+        @GuardedBy("mLock")
+        private int getClientUserIdLocked(String sessionId) throws ClientUserIdNotFoundException {
+            SessionState sessionState = mSessionIdToSessionStateMap.get(sessionId);
+            if (sessionState == null) {
+                throw new ClientUserIdNotFoundException(
+                        "Client UserId not found with sessionId " + sessionId);
+            }
+            return sessionState.userId;
+        }
+
         private void ensureTunerResourceAccessPermission() {
             if (mContext.checkCallingPermission(
                     android.Manifest.permission.TUNER_RESOURCE_ACCESS)
@@ -3495,11 +3539,15 @@
                     "bindServiceAsUser(service=" + serviceState.component + ", userId=" + userId
                             + ")");
         }
+        int bindUserId = userId;
+        if (kidsModeTvdbSharing() && isServiceSingleUser(serviceState.component)) {
+            bindUserId = UserHandle.USER_SYSTEM;
+        }
         Intent i =
                 new Intent(TvInputService.SERVICE_INTERFACE).setComponent(serviceState.component);
         serviceState.bound = mContext.bindServiceAsUser(i, serviceState.connection,
                 Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE_WHILE_AWAKE,
-                new UserHandle(userId));
+                new UserHandle(bindUserId));
         if (!serviceState.bound) {
             Slog.e(TAG, "failed to bind " + serviceState.component + " for userId " + userId);
             mContext.unbindService(serviceState.connection);
@@ -4700,4 +4748,10 @@
             super(name);
         }
     }
+
+    private static class ClientUserIdNotFoundException extends IllegalArgumentException {
+        ClientUserIdNotFoundException(String name) {
+            super(name);
+        }
+    }
 }
diff --git a/services/core/java/com/android/server/vibrator/HapticFeedbackVibrationProvider.java b/services/core/java/com/android/server/vibrator/HapticFeedbackVibrationProvider.java
index eccbffb..0761087 100644
--- a/services/core/java/com/android/server/vibrator/HapticFeedbackVibrationProvider.java
+++ b/services/core/java/com/android/server/vibrator/HapticFeedbackVibrationProvider.java
@@ -364,10 +364,7 @@
         if ((privFlags & HapticFeedbackConstants.PRIVATE_FLAG_APPLY_INPUT_METHOD_SETTINGS) == 0) {
             return TOUCH_VIBRATION_ATTRIBUTES;
         }
-        return new VibrationAttributes.Builder(IME_FEEDBACK_VIBRATION_ATTRIBUTES)
-                // TODO(b/332661766): Remove CATEGORY_KEYBOARD logic
-                .setCategory(VibrationAttributes.CATEGORY_KEYBOARD)
-                .build();
+        return IME_FEEDBACK_VIBRATION_ATTRIBUTES;
     }
 
     @Nullable
diff --git a/services/core/java/com/android/server/vibrator/Vibration.java b/services/core/java/com/android/server/vibrator/Vibration.java
index 5c567da..aa4b9f3 100644
--- a/services/core/java/com/android/server/vibrator/Vibration.java
+++ b/services/core/java/com/android/server/vibrator/Vibration.java
@@ -282,12 +282,6 @@
                     VibrationScaler.scaleLevelToString(mScaleLevel), mAdaptiveScale,
                     Long.toBinaryString(mCallerInfo.attrs.getFlags()),
                     mCallerInfo.attrs.usageToString());
-            // Optional, most vibrations have category unknown so skip them to simplify the logs
-            String categoryStr =
-                    mCallerInfo.attrs.getCategory() != VibrationAttributes.CATEGORY_UNKNOWN
-                            ? " | category=" + VibrationAttributes.categoryToString(
-                            mCallerInfo.attrs.getCategory())
-                            : "";
             // Optional, most vibrations should not be defined via AudioAttributes
             // so skip them to simplify the logs
             String audioUsageStr =
@@ -302,7 +296,7 @@
                     " | played: %s | original: %s",
                     mPlayedEffect == null ? null : mPlayedEffect.toDebugString(),
                     mOriginalEffect == null ? null : mOriginalEffect.toDebugString());
-            pw.println(timingsStr + paramStr + categoryStr + audioUsageStr + callerStr + effectStr);
+            pw.println(timingsStr + paramStr + audioUsageStr + callerStr + effectStr);
         }
 
         /** Write this info into given {@link PrintWriter}. */
@@ -335,7 +329,6 @@
             final VibrationAttributes attrs = mCallerInfo.attrs;
             proto.write(VibrationAttributesProto.USAGE, attrs.getUsage());
             proto.write(VibrationAttributesProto.AUDIO_USAGE, attrs.getAudioUsage());
-            proto.write(VibrationAttributesProto.CATEGORY, attrs.getCategory());
             proto.write(VibrationAttributesProto.FLAGS, attrs.getFlags());
             proto.end(attrsToken);
 
diff --git a/services/core/java/com/android/server/vibrator/VibrationScaler.java b/services/core/java/com/android/server/vibrator/VibrationScaler.java
index a74c4e0..b3862cc 100644
--- a/services/core/java/com/android/server/vibrator/VibrationScaler.java
+++ b/services/core/java/com/android/server/vibrator/VibrationScaler.java
@@ -134,7 +134,8 @@
         return effect.resolve(mDefaultVibrationAmplitude)
                 .applyEffectStrength(newEffectStrength)
                 .scale(scaleFactor)
-                .scaleLinearly(adaptiveScale);
+                // Make sure this is the last one so it is applied on top of the settings scaling.
+                .applyAdaptiveScale(adaptiveScale);
     }
 
     /**
diff --git a/services/core/java/com/android/server/vibrator/VibratorController.java b/services/core/java/com/android/server/vibrator/VibratorController.java
index 8cc157c..4fc0b74 100644
--- a/services/core/java/com/android/server/vibrator/VibratorController.java
+++ b/services/core/java/com/android/server/vibrator/VibratorController.java
@@ -279,8 +279,8 @@
                 vendorEffect.getVendorData().writeToParcel(vendorData, /* flags= */ 0);
                 vendorData.setDataPosition(0);
                 long duration = mNativeWrapper.performVendorEffect(vendorData,
-                        vendorEffect.getEffectStrength(), vendorEffect.getLinearScale(),
-                        vibrationId);
+                        vendorEffect.getEffectStrength(), vendorEffect.getScale(),
+                        vendorEffect.getAdaptiveScale(), vibrationId);
                 if (duration > 0) {
                     mCurrentAmplitude = -1;
                     notifyListenerOnVibrating(true);
@@ -459,7 +459,7 @@
                 long vibrationId);
 
         private static native long performVendorEffect(long nativePtr, Parcel vendorData,
-                long strength, float scale, long vibrationId);
+                long strength, float scale, float adaptiveScale, long vibrationId);
 
         private static native long performComposedEffect(long nativePtr, PrimitiveSegment[] effect,
                 long vibrationId);
@@ -518,8 +518,9 @@
 
         /** Turns vibrator on to perform a vendor-specific effect. */
         public long performVendorEffect(Parcel vendorData, long strength, float scale,
-                long vibrationId) {
-            return performVendorEffect(mNativePtr, vendorData, strength, scale, vibrationId);
+                float adaptiveScale, long vibrationId) {
+            return performVendorEffect(mNativePtr, vendorData, strength, scale, adaptiveScale,
+                    vibrationId);
         }
 
         /** Turns vibrator on to perform effect composed of give primitives effect. */
diff --git a/services/core/java/com/android/server/vibrator/VibratorManagerService.java b/services/core/java/com/android/server/vibrator/VibratorManagerService.java
index f2ad5b9..dd16d24 100644
--- a/services/core/java/com/android/server/vibrator/VibratorManagerService.java
+++ b/services/core/java/com/android/server/vibrator/VibratorManagerService.java
@@ -487,25 +487,37 @@
     HalVibration performHapticFeedbackInternal(
             int uid, int deviceId, String opPkg, int constant, String reason,
             IBinder token, int flags, int privFlags) {
+
+        // Make sure we report the constant id in the requested haptic feedback reason.
+        reason = "performHapticFeedback(constant=" + constant + "): " + reason;
+
         HapticFeedbackVibrationProvider hapticVibrationProvider = getHapticVibrationProvider();
         if (hapticVibrationProvider == null) {
             Slog.e(TAG, "performHapticFeedback; haptic vibration provider not ready.");
+            logAndRecordPerformHapticFeedbackAttempt(uid, deviceId, opPkg, reason,
+                    Vibration.Status.IGNORED_ERROR_SCHEDULING);
             return null;
         }
+
         if (hapticVibrationProvider.isRestrictedHapticFeedback(constant)
                 && !hasPermission(android.Manifest.permission.VIBRATE_SYSTEM_CONSTANTS)) {
             Slog.w(TAG, "performHapticFeedback; no permission for system constant " + constant);
+            logAndRecordPerformHapticFeedbackAttempt(uid, deviceId, opPkg, reason,
+                    Vibration.Status.IGNORED_MISSING_PERMISSION);
             return null;
         }
+
         VibrationEffect effect = hapticVibrationProvider.getVibrationForHapticFeedback(constant);
         if (effect == null) {
             Slog.w(TAG, "performHapticFeedback; vibration absent for constant " + constant);
+            logAndRecordPerformHapticFeedbackAttempt(uid, deviceId, opPkg, reason,
+                    Vibration.Status.IGNORED_UNSUPPORTED);
             return null;
         }
+
         CombinedVibration vib = CombinedVibration.createParallel(effect);
         VibrationAttributes attrs = hapticVibrationProvider.getVibrationAttributesForHapticFeedback(
                 constant, flags, privFlags);
-        reason = "performHapticFeedback(constant=" + constant + "): " + reason;
         VibratorFrameworkStatsLogger.logPerformHapticsFeedbackIfKeyboard(uid, constant);
         return vibrateWithoutPermissionCheck(uid, deviceId, opPkg, vib, attrs, reason, token);
     }
@@ -563,22 +575,27 @@
     private HalVibration vibrateInternal(int uid, int deviceId, String opPkg,
             @NonNull CombinedVibration effect, @NonNull VibrationAttributes attrs,
             String reason, IBinder token) {
+        Vibration.CallerInfo callerInfo =
+                new Vibration.CallerInfo(attrs, uid, deviceId, opPkg, reason);
         if (token == null) {
             Slog.e(TAG, "token must not be null");
+            logAndRecordVibrationAttempt(effect, callerInfo, Vibration.Status.IGNORED_ERROR_TOKEN);
             return null;
         }
         if (effect.hasVendorEffects()
                 && !hasPermission(android.Manifest.permission.VIBRATE_VENDOR_EFFECTS)) {
-            Slog.w(TAG, "vibrate; no permission for vendor effects");
+            Slog.e(TAG, "vibrate; no permission for vendor effects");
+            logAndRecordVibrationAttempt(effect, callerInfo,
+                    Vibration.Status.IGNORED_MISSING_PERMISSION);
             return null;
         }
         enforceUpdateAppOpsStatsPermission(uid);
         if (!isEffectValid(effect)) {
+            logAndRecordVibrationAttempt(effect, callerInfo, Vibration.Status.IGNORED_UNSUPPORTED);
             return null;
         }
         // Create Vibration.Stats as close to the received request as possible, for tracking.
-        HalVibration vib = new HalVibration(token, effect,
-                new Vibration.CallerInfo(attrs, uid, deviceId, opPkg, reason));
+        HalVibration vib = new HalVibration(token, effect, callerInfo);
         fillVibrationFallbacks(vib, effect);
 
         if (attrs.isFlagSet(VibrationAttributes.FLAG_INVALIDATE_SETTINGS_CACHE)) {
@@ -973,6 +990,22 @@
         return new Vibration.EndInfo(Vibration.Status.FORWARDED_TO_INPUT_DEVICES);
     }
 
+    private void logAndRecordPerformHapticFeedbackAttempt(int uid, int deviceId, String opPkg,
+            String reason, Vibration.Status status) {
+        Vibration.CallerInfo callerInfo = new Vibration.CallerInfo(
+                VibrationAttributes.createForUsage(VibrationAttributes.USAGE_UNKNOWN),
+                uid, deviceId, opPkg, reason);
+        logAndRecordVibrationAttempt(/* effect= */ null, callerInfo, status);
+    }
+
+    private void logAndRecordVibrationAttempt(@Nullable CombinedVibration effect,
+            Vibration.CallerInfo callerInfo, Vibration.Status status) {
+        logAndRecordVibration(
+                new Vibration.DebugInfo(status, new VibrationStats(),
+                        effect, /* originalEffect= */ null, VibrationScaler.SCALE_NONE,
+                        VibrationScaler.ADAPTIVE_SCALE_NONE, callerInfo));
+    }
+
     private void logAndRecordVibration(Vibration.DebugInfo info) {
         info.logMetrics(mFrameworkStatsLogger);
         logVibrationStatus(info.mCallerInfo.uid, info.mCallerInfo.attrs, info.mStatus);
diff --git a/services/core/java/com/android/server/wm/AccessibilityController.java b/services/core/java/com/android/server/wm/AccessibilityController.java
index 2c73412..10ce8c2 100644
--- a/services/core/java/com/android/server/wm/AccessibilityController.java
+++ b/services/core/java/com/android/server/wm/AccessibilityController.java
@@ -1617,8 +1617,7 @@
             // causing the notifying, or the recents/home window is removed, then we won't need the
             // delayed notification anymore.
             void onWMTransition(@TransitionType int type, @TransitionFlags int flags) {
-                if (Flags.delayNotificationToMagnificationWhenRecentsWindowToFrontTransition()
-                        && type == WindowManager.TRANSIT_TO_FRONT
+                if (type == WindowManager.TRANSIT_TO_FRONT
                         && (flags & TRANSIT_FLAG_IS_RECENTS) != 0) {
                     // Delay the recents to front transition notification then send after if needed.
                     mHasDelayedNotificationForRecentsToFrontTransition = true;
@@ -2451,7 +2450,7 @@
 
                             long tokenInner = os.start(WINDOW_MANAGER_SERVICE);
                             synchronized (mService.mGlobalLock) {
-                                mService.dumpDebugLocked(os, WindowTraceLogLevel.ALL);
+                                mService.dumpDebugLocked(os, WindowTracingLogLevel.ALL);
                             }
                             os.end(tokenInner);
                             os.write(CPU_STATS, printCpuStats(reportedTimeStampNanos));
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index 3ac91b3..0bd8441 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -7538,6 +7538,10 @@
         if (mStartingWindow == win) {
             // This could only happen when the window is removed from hierarchy. So do not keep its
             // reference anymore.
+            if (mStartingSurface != null) {
+                // Ensure the reference in client side can be removed.
+                mStartingSurface.remove(false /* animate */, false /* hasImeSurface */);
+            }
             mStartingWindow = null;
             mStartingData = null;
             mStartingSurface = null;
@@ -10328,7 +10332,7 @@
      * Write all fields to an {@code ActivityRecordProto}. This assumes the
      * {@code ActivityRecordProto} is the outer-most proto data.
      */
-    void dumpDebug(ProtoOutputStream proto, @WindowTraceLogLevel int logLevel) {
+    void dumpDebug(ProtoOutputStream proto, @WindowTracingLogLevel int logLevel) {
         writeNameToProto(proto, NAME);
         super.dumpDebug(proto, WINDOW_TOKEN, logLevel);
         proto.write(LAST_SURFACE_SHOWING, mLastSurfaceShowing);
@@ -10406,9 +10410,9 @@
 
     @Override
     public void dumpDebug(ProtoOutputStream proto, long fieldId,
-            @WindowTraceLogLevel int logLevel) {
+            @WindowTracingLogLevel int logLevel) {
         // Critical log level logs only visible elements to mitigate performance overheard
-        if (logLevel == WindowTraceLogLevel.CRITICAL && !isVisible()) {
+        if (logLevel == WindowTracingLogLevel.CRITICAL && !isVisible()) {
             return;
         }
 
diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
index 5bb4a8a..5e03066 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
@@ -3771,6 +3771,7 @@
             // Shell calls back into Core with the entry bounds to be applied with startWCT.
             final Transition enterPipTransition = new Transition(TRANSIT_PIP,
                     0 /* flags */, getTransitionController(), mWindowManager.mSyncEngine);
+            r.setPictureInPictureParams(params);
             enterPipTransition.setPipActivity(r);
             r.mAutoEnteringPip = isAutoEnter;
             getTransitionController().startCollectOrQueue(enterPipTransition, (deferred) -> {
@@ -4625,7 +4626,12 @@
         return kept;
     }
 
-    /** Update default (global) configuration and notify listeners about changes. */
+    /**
+     * Updates default (global) configuration and notifies listeners about changes.
+     *
+     * @param values The new configuration. It must always be a new instance from the caller, and
+     *               it won't be modified after calling this method.
+     */
     int updateGlobalConfigurationLocked(@NonNull Configuration values, boolean initLocale,
             boolean persistent, int userId) {
 
@@ -4639,24 +4645,6 @@
         ProtoLog.i(WM_DEBUG_CONFIGURATION, "Updating global configuration "
                 + "to: %s", values);
         writeConfigurationChanged(changes);
-        FrameworkStatsLog.write(FrameworkStatsLog.RESOURCE_CONFIGURATION_CHANGED,
-                values.colorMode,
-                values.densityDpi,
-                values.fontScale,
-                values.hardKeyboardHidden,
-                values.keyboard,
-                values.keyboardHidden,
-                values.mcc,
-                values.mnc,
-                values.navigation,
-                values.navigationHidden,
-                values.orientation,
-                values.screenHeightDp,
-                values.screenLayout,
-                values.screenWidthDp,
-                values.smallestScreenWidthDp,
-                values.touchscreen,
-                values.uiMode);
 
         // Note: certain tests currently run as platform_app which is not allowed
         // to set debug system properties. To ensure that system properties are set
@@ -4704,13 +4692,6 @@
         // resources have that config before following boot code is executed.
         mSystemThread.applyConfigurationToResources(mTempConfig);
 
-        if (persistent && Settings.System.hasInterestingConfigurationChanges(changes)) {
-            final Message msg = PooledLambda.obtainMessage(
-                    ActivityTaskManagerService::sendPutConfigurationForUserMsg,
-                    this, userId, new Configuration(mTempConfig));
-            mH.sendMessage(msg);
-        }
-
         SparseArray<WindowProcessController> pidMap = mProcessMap.getPidMap();
         for (int i = pidMap.size() - 1; i >= 0; i--) {
             final int pid = pidMap.keyAt(i);
@@ -4720,19 +4701,32 @@
             app.onConfigurationChanged(mTempConfig);
         }
 
-        final Message msg = PooledLambda.obtainMessage(
-                ActivityManagerInternal::broadcastGlobalConfigurationChanged,
-                mAmInternal, changes, initLocale);
-        mH.sendMessage(msg);
+        final Configuration configurationForSettings =
+                persistent && Settings.System.hasInterestingConfigurationChanges(changes)
+                        ? new Configuration(mTempConfig) : null;
+        mH.post(() -> {
+            FrameworkStatsLog.write(FrameworkStatsLog.RESOURCE_CONFIGURATION_CHANGED,
+                    values.colorMode, values.densityDpi, values.fontScale,
+                    values.hardKeyboardHidden, values.keyboard, values.keyboardHidden,
+                    values.mcc, values.mnc, values.navigation, values.navigationHidden,
+                    values.orientation, values.screenHeightDp, values.screenLayout,
+                    values.screenWidthDp, values.smallestScreenWidthDp, values.touchscreen,
+                    values.uiMode);
+            if ((changes & ActivityInfo.CONFIG_ORIENTATION) != 0) {
+                FrameworkStatsLog.write(FrameworkStatsLog.DEVICE_ORIENTATION_CHANGED,
+                        values.orientation);
+            }
+            if (configurationForSettings != null) {
+                Settings.System.putConfigurationForUser(mContext.getContentResolver(),
+                        configurationForSettings, userId);
+            }
+            mAmInternal.broadcastGlobalConfigurationChanged(changes, initLocale);
+        });
 
         Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "RootConfigChange");
         // Update stored global config and notify everyone about the change.
         mRootWindowContainer.onConfigurationChanged(mTempConfig);
         Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
-        if ((changes & ActivityInfo.CONFIG_ORIENTATION) != 0) {
-            FrameworkStatsLog.write(FrameworkStatsLog.DEVICE_ORIENTATION_CHANGED,
-                    values.orientation);
-        }
 
         Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
         return changes;
@@ -4882,11 +4876,6 @@
         mWindowManager.setEventDispatching(booted && !mShuttingDown);
     }
 
-    private void sendPutConfigurationForUserMsg(int userId, Configuration config) {
-        final ContentResolver resolver = mContext.getContentResolver();
-        Settings.System.putConfigurationForUser(resolver, config, userId);
-    }
-
     boolean isActivityStartsLoggingEnabled() {
         return mAmInternal.isActivityStartsLoggingEnabled();
     }
@@ -6784,7 +6773,7 @@
             synchronized (mGlobalLock) {
                 // The output proto of "activity --proto activities"
                 mRootWindowContainer.dumpDebug(
-                        proto, ROOT_WINDOW_CONTAINER, WindowTraceLogLevel.ALL);
+                        proto, ROOT_WINDOW_CONTAINER, WindowTracingLogLevel.ALL);
             }
         }
 
diff --git a/services/core/java/com/android/server/wm/AppCompatAspectRatioOverrides.java b/services/core/java/com/android/server/wm/AppCompatAspectRatioOverrides.java
index d2f3d1d..cd795ae 100644
--- a/services/core/java/com/android/server/wm/AppCompatAspectRatioOverrides.java
+++ b/services/core/java/com/android/server/wm/AppCompatAspectRatioOverrides.java
@@ -233,7 +233,8 @@
         return mAppCompatConfiguration.getIsSplitScreenAspectRatioForUnresizableAppsEnabled();
     }
 
-    private float getDisplaySizeMinAspectRatio() {
+    @VisibleForTesting
+    float getDisplaySizeMinAspectRatio() {
         final DisplayArea displayArea = mActivityRecord.getDisplayArea();
         if (displayArea == null) {
             return mActivityRecord.info.getMinAspectRatio();
@@ -263,7 +264,13 @@
                     && cameraPolicy.isTreatmentEnabledForActivity(mActivityRecord));
     }
 
-    @VisibleForTesting
+    /**
+     * Returns the value of the user aspect ratio override property. If unset, return {@code true}.
+     */
+    boolean getAllowUserAspectRatioOverridePropertyValue() {
+        return !mAllowUserAspectRatioOverrideOptProp.isFalse();
+    }
+
     int getUserMinAspectRatioOverrideCode() {
         try {
             return mActivityRecord.mAtmService.getPackageManager()
diff --git a/services/core/java/com/android/server/wm/AppCompatController.java b/services/core/java/com/android/server/wm/AppCompatController.java
index 906ee20..3c3b773 100644
--- a/services/core/java/com/android/server/wm/AppCompatController.java
+++ b/services/core/java/com/android/server/wm/AppCompatController.java
@@ -39,6 +39,8 @@
     @NonNull
     private final AppCompatReachabilityPolicy mAppCompatReachabilityPolicy;
     @NonNull
+    private final DesktopAppCompatAspectRatioPolicy mDesktopAppCompatAspectRatioPolicy;
+    @NonNull
     private final AppCompatOverrides mAppCompatOverrides;
     @NonNull
     private final AppCompatDeviceStateQuery mAppCompatDeviceStateQuery;
@@ -63,6 +65,8 @@
                 wmService.mAppCompatConfiguration);
         mAppCompatLetterboxPolicy = new AppCompatLetterboxPolicy(mActivityRecord,
                 wmService.mAppCompatConfiguration);
+        mDesktopAppCompatAspectRatioPolicy = new DesktopAppCompatAspectRatioPolicy(activityRecord,
+                mAppCompatOverrides, mTransparentPolicy, wmService.mAppCompatConfiguration);
     }
 
     @NonNull
@@ -81,6 +85,11 @@
     }
 
     @NonNull
+    DesktopAppCompatAspectRatioPolicy getDesktopAppCompatAspectRatioPolicy() {
+        return mDesktopAppCompatAspectRatioPolicy;
+    }
+
+    @NonNull
     AppCompatOverrides getAppCompatOverrides() {
         return mAppCompatOverrides;
     }
diff --git a/services/core/java/com/android/server/wm/AppCompatUtils.java b/services/core/java/com/android/server/wm/AppCompatUtils.java
index 94ad61f..e3ff851 100644
--- a/services/core/java/com/android/server/wm/AppCompatUtils.java
+++ b/services/core/java/com/android/server/wm/AppCompatUtils.java
@@ -187,6 +187,8 @@
         appCompatTaskInfo.setTopActivityLetterboxed(top.areBoundsLetterboxed());
         appCompatTaskInfo.cameraCompatTaskInfo.freeformCameraCompatMode = top.mAppCompatController
                 .getAppCompatCameraOverrides().getFreeformCameraCompatMode();
+        appCompatTaskInfo.setHasMinAspectRatioOverride(top.mAppCompatController
+                .getDesktopAppCompatAspectRatioPolicy().hasMinAspectRatioOverride(task));
     }
 
     /**
diff --git a/services/core/java/com/android/server/wm/BackNavigationController.java b/services/core/java/com/android/server/wm/BackNavigationController.java
index 767effd..87867f6 100644
--- a/services/core/java/com/android/server/wm/BackNavigationController.java
+++ b/services/core/java/com/android/server/wm/BackNavigationController.java
@@ -283,8 +283,10 @@
                     // keyguard locked and activities are unable to show when locked.
                     backType = BackNavigationInfo.TYPE_CALLBACK;
                 }
-            } else if (currentTask.mAtmService.getLockTaskController().isTaskLocked(currentTask)) {
+            } else if (currentTask.mAtmService.getLockTaskController().isTaskLocked(currentTask)
+                    || currentTask.getWindowConfiguration().tasksAreFloating()) {
                 // Do not predict if current task is in task locked.
+                // Also, it is unable to play cross task animation for floating task.
                 backType = BackNavigationInfo.TYPE_CALLBACK;
             } else {
                 // Check back-to-home or cross-task
diff --git a/services/core/java/com/android/server/wm/BackgroundActivityStartController.java b/services/core/java/com/android/server/wm/BackgroundActivityStartController.java
index 02c8a49..20c5f02 100644
--- a/services/core/java/com/android/server/wm/BackgroundActivityStartController.java
+++ b/services/core/java/com/android/server/wm/BackgroundActivityStartController.java
@@ -1506,10 +1506,13 @@
         PackageManager pm = mService.mContext.getPackageManager();
         ApplicationInfo applicationInfo;
 
+        final int sourceUserId = UserHandle.getUserId(sourceUid);
         try {
-            applicationInfo = pm.getApplicationInfo(packageName, 0);
+            applicationInfo = pm.getApplicationInfoAsUser(packageName, /* flags= */ 0,
+                    sourceUserId);
         } catch (PackageManager.NameNotFoundException e) {
-            Slog.wtf(TAG, "Package name: " + packageName + " not found.");
+            Slog.wtf(TAG, "Package name: " + packageName + " not found for user "
+                    + sourceUserId);
             return bas.optedIn(ar);
         }
 
diff --git a/services/core/java/com/android/server/wm/ConfigurationContainer.java b/services/core/java/com/android/server/wm/ConfigurationContainer.java
index 3ebaf03..9be3f43 100644
--- a/services/core/java/com/android/server/wm/ConfigurationContainer.java
+++ b/services/core/java/com/android/server/wm/ConfigurationContainer.java
@@ -255,8 +255,18 @@
             inOutConfig.windowConfiguration.setAppBounds(
                     newParentConfiguration.windowConfiguration.getBounds());
             outAppBounds = inOutConfig.windowConfiguration.getAppBounds();
-            outAppBounds.inset(displayContent.getDisplayPolicy()
-                    .getDecorInsetsInfo(rotation, dw, dh).mOverrideNonDecorInsets);
+            if (inOutConfig.windowConfiguration.getWindowingMode() == WINDOWING_MODE_FULLSCREEN) {
+                final DisplayPolicy.DecorInsets.Info decor =
+                        displayContent.getDisplayPolicy().getDecorInsetsInfo(rotation, dw, dh);
+                if (outAppBounds.contains(decor.mOverrideNonDecorFrame)) {
+                    outAppBounds.intersect(decor.mOverrideNonDecorFrame);
+                }
+            } else {
+                // TODO(b/358509380): Handle other windowing mode like split screen and freeform
+                //  cases correctly.
+                outAppBounds.inset(displayContent.getDisplayPolicy()
+                        .getDecorInsetsInfo(rotation, dw, dh).mOverrideNonDecorInsets);
+            }
         }
         float density = inOutConfig.densityDpi;
         if (density == Configuration.DENSITY_DPI_UNDEFINED) {
@@ -807,23 +817,23 @@
      */
     @CallSuper
     protected void dumpDebug(ProtoOutputStream proto, long fieldId,
-            @WindowTraceLogLevel int logLevel) {
+            @WindowTracingLogLevel int logLevel) {
         final long token = proto.start(fieldId);
 
-        if (logLevel == WindowTraceLogLevel.ALL || mHasOverrideConfiguration) {
+        if (logLevel == WindowTracingLogLevel.ALL || mHasOverrideConfiguration) {
             mRequestedOverrideConfiguration.dumpDebug(proto, OVERRIDE_CONFIGURATION,
-                    logLevel == WindowTraceLogLevel.CRITICAL);
+                    logLevel == WindowTracingLogLevel.CRITICAL);
         }
 
         // Unless trace level is set to `WindowTraceLogLevel.ALL` don't dump anything that isn't
         // required to mitigate performance overhead
-        if (logLevel == WindowTraceLogLevel.ALL) {
+        if (logLevel == WindowTracingLogLevel.ALL) {
             mFullConfiguration.dumpDebug(proto, FULL_CONFIGURATION, false /* critical */);
             mMergedOverrideConfiguration.dumpDebug(proto, MERGED_OVERRIDE_CONFIGURATION,
                     false /* critical */);
         }
 
-        if (logLevel == WindowTraceLogLevel.TRIM) {
+        if (logLevel == WindowTracingLogLevel.TRIM) {
             // Required for Fass to automatically detect pip transitions in Winscope traces
             dumpDebugWindowingMode(proto);
         }
diff --git a/services/core/java/com/android/server/wm/DesktopAppCompatAspectRatioPolicy.java b/services/core/java/com/android/server/wm/DesktopAppCompatAspectRatioPolicy.java
new file mode 100644
index 0000000..b936556
--- /dev/null
+++ b/services/core/java/com/android/server/wm/DesktopAppCompatAspectRatioPolicy.java
@@ -0,0 +1,294 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm;
+
+import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
+import static android.content.pm.ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO_EXCLUDE_PORTRAIT_FULLSCREEN;
+import static android.content.pm.ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO_LARGE;
+import static android.content.pm.ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO_MEDIUM;
+import static android.content.pm.ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO_PORTRAIT_ONLY;
+import static android.content.pm.ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO_SMALL;
+import static android.content.pm.ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO_TO_ALIGN_WITH_SPLIT_SCREEN;
+import static android.content.pm.PackageManager.USER_MIN_ASPECT_RATIO_APP_DEFAULT;
+import static android.content.pm.PackageManager.USER_MIN_ASPECT_RATIO_FULLSCREEN;
+import static android.content.pm.PackageManager.USER_MIN_ASPECT_RATIO_UNSET;
+import static android.content.res.Configuration.ORIENTATION_PORTRAIT;
+
+import static com.android.server.wm.AppCompatConfiguration.DEFAULT_LETTERBOX_ASPECT_RATIO_FOR_MULTI_WINDOW;
+import static com.android.server.wm.AppCompatConfiguration.MIN_FIXED_ORIENTATION_LETTERBOX_ASPECT_RATIO;
+
+import android.annotation.NonNull;
+import android.app.WindowConfiguration;
+import android.content.pm.ActivityInfo;
+import android.graphics.Rect;
+
+import com.android.internal.R;
+import com.android.internal.annotations.VisibleForTesting;
+
+// TODO(b/359217664): Consider refactoring into AppCompatAspectRatioPolicy.
+/**
+ * Encapsulate app compat aspect ratio policy logic specific for desktop windowing initial bounds
+ * calculation.
+ */
+public class DesktopAppCompatAspectRatioPolicy {
+
+    @NonNull
+    private final AppCompatOverrides mAppCompatOverrides;
+    @NonNull
+    private final AppCompatConfiguration mAppCompatConfiguration;
+    @NonNull
+    private final ActivityRecord mActivityRecord;
+    @NonNull
+    private final TransparentPolicy mTransparentPolicy;
+
+    DesktopAppCompatAspectRatioPolicy(@NonNull ActivityRecord activityRecord,
+            @NonNull AppCompatOverrides appCompatOverrides,
+            @NonNull TransparentPolicy transparentPolicy,
+            @NonNull AppCompatConfiguration appCompatConfiguration) {
+        mActivityRecord = activityRecord;
+        mAppCompatOverrides = appCompatOverrides;
+        mTransparentPolicy = transparentPolicy;
+        mAppCompatConfiguration = appCompatConfiguration;
+    }
+
+    /**
+     * Calculates the final aspect ratio of an launching activity based on the task it will be
+     * launched in. Takes into account any min or max aspect ratio constraints.
+     */
+    float calculateAspectRatio(@NonNull Task task) {
+        final float maxAspectRatio = getMaxAspectRatio();
+        final float minAspectRatio = getMinAspectRatio(task);
+        float desiredAspectRatio = 0;
+        desiredAspectRatio = getDesiredAspectRatio(task);
+        if (maxAspectRatio >= 1 && desiredAspectRatio > maxAspectRatio) {
+            desiredAspectRatio = maxAspectRatio;
+        } else if (minAspectRatio >= 1 && desiredAspectRatio < minAspectRatio) {
+            desiredAspectRatio = minAspectRatio;
+        }
+        return desiredAspectRatio;
+    }
+
+    /**
+     * Returns the aspect ratio desired by the system for current activity, not taking into account
+     * any min or max aspect ratio constraints.
+     */
+    @VisibleForTesting
+    float getDesiredAspectRatio(@NonNull Task task) {
+        final float letterboxAspectRatioOverride = getFixedOrientationLetterboxAspectRatio(task);
+        // Aspect ratio as suggested by the system. Apps requested mix/max aspect ratio will
+        // be respected in #calculateAspectRatio.
+        if (isDefaultMultiWindowLetterboxAspectRatioDesired(task)) {
+            return DEFAULT_LETTERBOX_ASPECT_RATIO_FOR_MULTI_WINDOW;
+        } else if (letterboxAspectRatioOverride > MIN_FIXED_ORIENTATION_LETTERBOX_ASPECT_RATIO) {
+            return letterboxAspectRatioOverride;
+        }
+        return AppCompatUtils.computeAspectRatio(task.getDisplayArea().getBounds());
+    }
+
+    /**
+     * Determines the letterbox aspect ratio for an application based on its orientation and
+     * resizability.
+     */
+    private float getFixedOrientationLetterboxAspectRatio(@NonNull Task task) {
+        return mActivityRecord.shouldCreateCompatDisplayInsets()
+                ? getDefaultMinAspectRatioForUnresizableApps(task)
+                : getDefaultMinAspectRatio(task);
+    }
+
+    /**
+     * Calculates the aspect ratio of the available display area when an app enters split-screen on
+     * a given device, taking into account any dividers and insets.
+     */
+    private float getSplitScreenAspectRatio(@NonNull Task task) {
+        // Getting the same aspect ratio that apps get in split screen.
+        final DisplayArea displayArea = task.getDisplayArea();
+        final int dividerWindowWidth =
+                mActivityRecord.mWmService.mContext.getResources().getDimensionPixelSize(
+                        R.dimen.docked_stack_divider_thickness);
+        final int dividerInsets =
+                mActivityRecord.mWmService.mContext.getResources().getDimensionPixelSize(
+                        R.dimen.docked_stack_divider_insets);
+        final int dividerSize = dividerWindowWidth - dividerInsets * 2;
+        final Rect bounds = new Rect(displayArea.getWindowConfiguration().getAppBounds());
+        if (bounds.width() >= bounds.height()) {
+            bounds.inset(/* dx */ dividerSize / 2, /* dy */ 0);
+            bounds.right = bounds.centerX();
+        } else {
+            bounds.inset(/* dx */ 0, /* dy */ dividerSize / 2);
+            bounds.bottom = bounds.centerY();
+        }
+        return AppCompatUtils.computeAspectRatio(bounds);
+    }
+
+
+    /**
+     * Returns the minimum aspect ratio for unresizable apps as determined by the system.
+     */
+    private float getDefaultMinAspectRatioForUnresizableApps(@NonNull Task task) {
+        if (!mAppCompatConfiguration.getIsSplitScreenAspectRatioForUnresizableAppsEnabled()) {
+            return mAppCompatConfiguration.getDefaultMinAspectRatioForUnresizableApps()
+                    > MIN_FIXED_ORIENTATION_LETTERBOX_ASPECT_RATIO
+                    ? mAppCompatConfiguration.getDefaultMinAspectRatioForUnresizableApps()
+                    : getDefaultMinAspectRatio(task);
+        }
+
+        return getSplitScreenAspectRatio(task);
+    }
+
+    /**
+     * Returns the default minimum aspect ratio for apps as determined by the system.
+     */
+    private float getDefaultMinAspectRatio(@NonNull Task task) {
+        if (!mAppCompatConfiguration.getIsDisplayAspectRatioEnabledForFixedOrientationLetterbox()) {
+            return mAppCompatConfiguration.getFixedOrientationLetterboxAspectRatio();
+        }
+        return getDisplayAreaMinAspectRatio(task);
+    }
+
+    /**
+     * Calculates the aspect ratio of the available display area.
+     */
+    private float getDisplayAreaMinAspectRatio(@NonNull Task task) {
+        final DisplayArea displayArea = task.getDisplayArea();
+        final Rect bounds = new Rect(displayArea.getWindowConfiguration().getAppBounds());
+        return AppCompatUtils.computeAspectRatio(bounds);
+    }
+
+    /**
+     * Returns {@code true} if the default aspect ratio for a letterboxed app in multi-window mode
+     * should be used.
+     */
+    private boolean isDefaultMultiWindowLetterboxAspectRatioDesired(@NonNull Task task) {
+        final DisplayContent dc = task.mDisplayContent;
+        final int windowingMode = task.getDisplayArea().getWindowingMode();
+        return WindowConfiguration.inMultiWindowMode(windowingMode)
+                && !dc.getIgnoreOrientationRequest();
+    }
+
+    /**
+     * Returns the min aspect ratio of this activity.
+     */
+    private float getMinAspectRatio(@NonNull Task task) {
+        if (mTransparentPolicy.isRunning()) {
+            return mTransparentPolicy.getInheritedMinAspectRatio();
+        }
+
+        final ActivityInfo info = mActivityRecord.info;
+        if (info.applicationInfo == null) {
+            return info.getMinAspectRatio();
+        }
+
+        final AppCompatAspectRatioOverrides aspectRatioOverrides =
+                mAppCompatOverrides.getAppCompatAspectRatioOverrides();
+        if (shouldApplyUserMinAspectRatioOverride(task)) {
+            return aspectRatioOverrides.getUserMinAspectRatio();
+        }
+
+        if (!aspectRatioOverrides.shouldOverrideMinAspectRatio()
+                && !mAppCompatOverrides.getAppCompatCameraOverrides()
+                .shouldOverrideMinAspectRatioForCamera()) {
+            return info.getMinAspectRatio();
+        }
+
+        if (info.isChangeEnabled(OVERRIDE_MIN_ASPECT_RATIO_PORTRAIT_ONLY)
+                && !ActivityInfo.isFixedOrientationPortrait(
+                        mActivityRecord.getOverrideOrientation())) {
+            return info.getMinAspectRatio();
+        }
+
+        if (info.isChangeEnabled(OVERRIDE_MIN_ASPECT_RATIO_EXCLUDE_PORTRAIT_FULLSCREEN)
+                && isFullscreenPortrait(task)) {
+            return info.getMinAspectRatio();
+        }
+
+        if (info.isChangeEnabled(OVERRIDE_MIN_ASPECT_RATIO_TO_ALIGN_WITH_SPLIT_SCREEN)) {
+            return Math.max(aspectRatioOverrides.getSplitScreenAspectRatio(),
+                    info.getMinAspectRatio());
+        }
+
+        if (info.isChangeEnabled(OVERRIDE_MIN_ASPECT_RATIO_LARGE)) {
+            return Math.max(ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO_LARGE_VALUE,
+                    info.getMinAspectRatio());
+        }
+
+        if (info.isChangeEnabled(OVERRIDE_MIN_ASPECT_RATIO_MEDIUM)) {
+            return Math.max(ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO_MEDIUM_VALUE,
+                    info.getMinAspectRatio());
+        }
+
+        if (info.isChangeEnabled(OVERRIDE_MIN_ASPECT_RATIO_SMALL)) {
+            return Math.max(ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO_SMALL_VALUE,
+                    info.getMinAspectRatio());
+        }
+        return info.getMinAspectRatio();
+    }
+
+    /**
+     * Returns the max aspect ratio of this activity.
+     */
+    private float getMaxAspectRatio() {
+        if (mTransparentPolicy.isRunning()) {
+            return mTransparentPolicy.getInheritedMaxAspectRatio();
+        }
+        return mActivityRecord.info.getMaxAspectRatio();
+    }
+
+    /**
+     * Whether an applications minimum aspect ratio has been overridden.
+     */
+    boolean hasMinAspectRatioOverride(@NonNull Task task) {
+        return mActivityRecord.info.getMinAspectRatio() < getMinAspectRatio(task);
+    }
+
+    /**
+     * Whether we should apply the user aspect ratio override to the min aspect ratio for the
+     * current app.
+     */
+    boolean shouldApplyUserMinAspectRatioOverride(@NonNull Task task) {
+        if (!shouldEnableUserAspectRatioSettings(task)) {
+            return false;
+        }
+
+        final int userAspectRatioCode = mAppCompatOverrides.getAppCompatAspectRatioOverrides()
+                .getUserMinAspectRatioOverrideCode();
+
+        return userAspectRatioCode != USER_MIN_ASPECT_RATIO_UNSET
+                && userAspectRatioCode != USER_MIN_ASPECT_RATIO_APP_DEFAULT
+                && userAspectRatioCode != USER_MIN_ASPECT_RATIO_FULLSCREEN;
+    }
+
+    /**
+     * Whether we should enable users to resize the current app.
+     */
+    private boolean shouldEnableUserAspectRatioSettings(@NonNull Task task) {
+        // We use mBooleanPropertyAllowUserAspectRatioOverride to allow apps to opt-out which has
+        // effect only if explicitly false. If mBooleanPropertyAllowUserAspectRatioOverride is null,
+        // the current app doesn't opt-out so the first part of the predicate is true.
+        return mAppCompatOverrides.getAppCompatAspectRatioOverrides()
+                    .getAllowUserAspectRatioOverridePropertyValue()
+                && mAppCompatConfiguration.isUserAppAspectRatioSettingsEnabled()
+                && task.mDisplayContent.getIgnoreOrientationRequest();
+    }
+
+    /**
+     * Returns {@code true} if the task window is portrait and fullscreen.
+     */
+    private boolean isFullscreenPortrait(@NonNull Task task) {
+        return task.getConfiguration().orientation == ORIENTATION_PORTRAIT
+                && task.getWindowConfiguration().getWindowingMode() == WINDOWING_MODE_FULLSCREEN;
+    }
+}
diff --git a/services/core/java/com/android/server/wm/DesktopModeBoundsCalculator.java b/services/core/java/com/android/server/wm/DesktopModeBoundsCalculator.java
index 8f1828d..c3db7dd 100644
--- a/services/core/java/com/android/server/wm/DesktopModeBoundsCalculator.java
+++ b/services/core/java/com/android/server/wm/DesktopModeBoundsCalculator.java
@@ -16,31 +16,28 @@
 
 package com.android.server.wm;
 
+import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_LOCKED;
+import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_PORTRAIT;
+import static android.content.pm.ActivityInfo.isFixedOrientation;
 import static android.content.pm.ActivityInfo.isFixedOrientationLandscape;
 import static android.content.pm.ActivityInfo.isFixedOrientationPortrait;
 import static android.content.res.Configuration.ORIENTATION_LANDSCAPE;
 import static android.content.res.Configuration.ORIENTATION_PORTRAIT;
 
-import static com.android.server.wm.AppCompatConfiguration.DEFAULT_LETTERBOX_ASPECT_RATIO_FOR_MULTI_WINDOW;
-import static com.android.server.wm.AppCompatConfiguration.MIN_FIXED_ORIENTATION_LETTERBOX_ASPECT_RATIO;
-import static com.android.server.wm.AppCompatUtils.computeAspectRatio;
 import static com.android.server.wm.LaunchParamsUtil.applyLayoutGravity;
 import static com.android.server.wm.LaunchParamsUtil.calculateLayoutBounds;
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.app.ActivityOptions;
-import android.app.AppCompatTaskInfo;
 import android.app.TaskInfo;
-import android.content.pm.ActivityInfo;
-import android.content.res.Configuration;
+import android.content.pm.ActivityInfo.ScreenOrientation;
+import android.content.pm.ActivityInfo.WindowLayout;
 import android.graphics.Rect;
 import android.os.SystemProperties;
 import android.util.Size;
 import android.view.Gravity;
 
-import com.android.internal.R;
-import com.android.internal.annotations.VisibleForTesting;
 import com.android.server.wm.utils.DesktopModeFlagsUtil;
 
 import java.util.function.Consumer;
@@ -60,7 +57,7 @@
      * Updates launch bounds for an activity with respect to its activity options, window layout,
      * android manifest and task configuration.
      */
-    static void updateInitialBounds(@NonNull Task task, @Nullable ActivityInfo.WindowLayout layout,
+    static void updateInitialBounds(@NonNull Task task, @Nullable WindowLayout layout,
             @Nullable ActivityRecord activity, @Nullable ActivityOptions options,
             @NonNull Rect outBounds, @NonNull Consumer<String> logger) {
         // Use stable frame instead of raw frame to avoid launching freeform windows on top of
@@ -98,7 +95,8 @@
      * fullscreen size, aspect ratio, orientation and resizability to calculate an area this is
      * compatible with the applications previous configuration.
      */
-    private static @NonNull Rect calculateInitialBounds(@NonNull Task task,
+    @NonNull
+    private static Rect calculateInitialBounds(@NonNull Task task,
             @NonNull ActivityRecord activity, @NonNull Rect stableBounds
     ) {
         final TaskInfo taskInfo = task.getTaskInfo();
@@ -116,18 +114,19 @@
             // applied.
             return centerInScreen(idealSize, screenBounds);
         }
-        // TODO(b/353457301): Replace with app compat aspect ratio method when refactoring complete.
-        float appAspectRatio = calculateAspectRatio(task, activity);
+        final DesktopAppCompatAspectRatioPolicy desktopAppCompatAspectRatioPolicy =
+                activity.mAppCompatController.getDesktopAppCompatAspectRatioPolicy();
+        float appAspectRatio = desktopAppCompatAspectRatioPolicy.calculateAspectRatio(task);
         final float tdaWidth = stableBounds.width();
         final float tdaHeight = stableBounds.height();
-        final int activityOrientation = activity.getOverrideOrientation();
+        final int activityOrientation = getActivityOrientation(activity, task);
         final Size initialSize = switch (taskInfo.configuration.orientation) {
             case ORIENTATION_LANDSCAPE -> {
                 // Device in landscape orientation.
                 if (appAspectRatio == 0) {
                     appAspectRatio = 1;
                 }
-                if (taskInfo.isResizeable) {
+                if (canChangeAspectRatio(desktopAppCompatAspectRatioPolicy, taskInfo, task)) {
                     if (isFixedOrientationPortrait(activityOrientation)) {
                         // For portrait resizeable activities, respect apps fullscreen width but
                         // apply ideal size height.
@@ -139,14 +138,13 @@
                 }
                 // If activity is unresizeable, regardless of orientation, calculate maximum size
                 // (within the ideal size) maintaining original aspect ratio.
-                yield maximizeSizeGivenAspectRatio(
-                        activity.getOverrideOrientation(), idealSize, appAspectRatio);
+                yield maximizeSizeGivenAspectRatio(activityOrientation, idealSize, appAspectRatio);
             }
             case ORIENTATION_PORTRAIT -> {
                 // Device in portrait orientation.
                 final int customPortraitWidthForLandscapeApp = screenBounds.width()
                         - (DESKTOP_MODE_LANDSCAPE_APP_PADDING * 2);
-                if (taskInfo.isResizeable) {
+                if (canChangeAspectRatio(desktopAppCompatAspectRatioPolicy, taskInfo, task)) {
                     if (isFixedOrientationLandscape(activityOrientation)) {
                         if (appAspectRatio == 0) {
                             appAspectRatio = tdaWidth / (tdaWidth - 1);
@@ -180,11 +178,38 @@
     }
 
     /**
+     * Whether the activity's aspect ratio can be changed or if it should be maintained as if it was
+     * unresizeable.
+     */
+    private static boolean canChangeAspectRatio(
+            @NonNull DesktopAppCompatAspectRatioPolicy desktopAppCompatAspectRatioPolicy,
+            @NonNull TaskInfo taskInfo, @NonNull Task task) {
+        return taskInfo.isResizeable
+                && !desktopAppCompatAspectRatioPolicy.hasMinAspectRatioOverride(task);
+    }
+
+    private static @ScreenOrientation int getActivityOrientation(
+            @NonNull ActivityRecord activity, @NonNull Task task) {
+        final int activityOrientation = activity.getOverrideOrientation();
+        final DesktopAppCompatAspectRatioPolicy desktopAppCompatAspectRatioPolicy =
+                activity.mAppCompatController.getDesktopAppCompatAspectRatioPolicy();
+        if (desktopAppCompatAspectRatioPolicy.shouldApplyUserMinAspectRatioOverride(task)
+                && (!isFixedOrientation(activityOrientation)
+                    || activityOrientation == SCREEN_ORIENTATION_LOCKED)) {
+            // If a user aspect ratio override should be applied, treat the activity as portrait if
+            // it has not specified a fix orientation.
+            return SCREEN_ORIENTATION_PORTRAIT;
+        }
+        return activityOrientation;
+    }
+
+    /**
      * Calculates the largest size that can fit in a given area while maintaining a specific aspect
      * ratio.
      */
-    private static @NonNull Size maximizeSizeGivenAspectRatio(
-            @ActivityInfo.ScreenOrientation int orientation,
+    @NonNull
+    private static Size maximizeSizeGivenAspectRatio(
+            @ScreenOrientation int orientation,
             @NonNull Size targetArea,
             float aspectRatio
     ) {
@@ -229,68 +254,11 @@
     }
 
     /**
-     * Calculates the aspect ratio of an activity from its fullscreen bounds.
-     */
-    @VisibleForTesting
-    static float calculateAspectRatio(@NonNull Task task, @NonNull ActivityRecord activity) {
-        final TaskInfo taskInfo = task.getTaskInfo();
-        final float fullscreenWidth = task.getDisplayArea().getBounds().width();
-        final float fullscreenHeight = task.getDisplayArea().getBounds().height();
-        final float maxAspectRatio = activity.getMaxAspectRatio();
-        final float minAspectRatio = activity.getMinAspectRatio();
-        float desiredAspectRatio = 0;
-        if (taskInfo.isRunning) {
-            final AppCompatTaskInfo appCompatTaskInfo =  taskInfo.appCompatTaskInfo;
-            final int appLetterboxWidth =
-                    taskInfo.appCompatTaskInfo.topActivityLetterboxAppWidth;
-            final int appLetterboxHeight =
-                    taskInfo.appCompatTaskInfo.topActivityLetterboxAppHeight;
-            if (appCompatTaskInfo.isTopActivityLetterboxed()) {
-                desiredAspectRatio = (float) Math.max(appLetterboxWidth, appLetterboxHeight)
-                        / Math.min(appLetterboxWidth, appLetterboxHeight);
-            } else {
-                desiredAspectRatio = Math.max(fullscreenHeight, fullscreenWidth)
-                        / Math.min(fullscreenHeight, fullscreenWidth);
-            }
-        } else {
-            final float letterboxAspectRatioOverride =
-                    getFixedOrientationLetterboxAspectRatio(activity, task);
-            if (!task.mDisplayContent.getIgnoreOrientationRequest()) {
-                desiredAspectRatio = DEFAULT_LETTERBOX_ASPECT_RATIO_FOR_MULTI_WINDOW;
-            } else if (letterboxAspectRatioOverride
-                    > MIN_FIXED_ORIENTATION_LETTERBOX_ASPECT_RATIO) {
-                desiredAspectRatio = letterboxAspectRatioOverride;
-            }
-        }
-        // If the activity matches display orientation, the display aspect ratio should be used
-        if (activityMatchesDisplayOrientation(
-                taskInfo.configuration.orientation,
-                activity.getOverrideOrientation())) {
-            desiredAspectRatio = Math.max(fullscreenWidth, fullscreenHeight)
-                    / Math.min(fullscreenWidth, fullscreenHeight);
-        }
-        if (maxAspectRatio >= 1 && desiredAspectRatio > maxAspectRatio) {
-            desiredAspectRatio = maxAspectRatio;
-        } else if (minAspectRatio >= 1 && desiredAspectRatio < minAspectRatio) {
-            desiredAspectRatio = minAspectRatio;
-        }
-        return desiredAspectRatio;
-    }
-
-    private static boolean activityMatchesDisplayOrientation(
-            @Configuration.Orientation int deviceOrientation,
-            @ActivityInfo.ScreenOrientation int activityOrientation) {
-        if (deviceOrientation == ORIENTATION_PORTRAIT) {
-            return isFixedOrientationPortrait(activityOrientation);
-        }
-        return isFixedOrientationLandscape(activityOrientation);
-    }
-
-    /**
      * Calculates the desired initial bounds for applications in desktop windowing. This is done as
      * a scale of the screen bounds.
      */
-    private static @NonNull Size calculateIdealSize(@NonNull Rect screenBounds, float scale) {
+    @NonNull
+    private static Size calculateIdealSize(@NonNull Rect screenBounds, float scale) {
         final int width = (int) (screenBounds.width() * scale);
         final int height = (int) (screenBounds.height() * scale);
         return new Size(width, height);
@@ -299,7 +267,8 @@
     /**
      * Adjusts bounds to be positioned in the middle of the screen.
      */
-    private static @NonNull Rect centerInScreen(@NonNull Size desiredSize,
+    @NonNull
+    private static Rect centerInScreen(@NonNull Size desiredSize,
             @NonNull Rect screenBounds) {
         // TODO(b/325240051): Position apps with bottom heavy offset
         final int heightOffset = (screenBounds.height() - desiredSize.getHeight()) / 2;
@@ -309,57 +278,4 @@
         resultBounds.offset(screenBounds.left + widthOffset, screenBounds.top + heightOffset);
         return resultBounds;
     }
-
-    private static float getFixedOrientationLetterboxAspectRatio(@NonNull ActivityRecord activity,
-            @NonNull Task task) {
-        return activity.shouldCreateCompatDisplayInsets()
-                ? getDefaultMinAspectRatioForUnresizableApps(activity, task)
-                : activity.mAppCompatController.getAppCompatAspectRatioOverrides()
-                        .getDefaultMinAspectRatio();
-    }
-
-    private static float getDefaultMinAspectRatioForUnresizableApps(
-            @NonNull ActivityRecord activity,
-            @NonNull Task task) {
-        final AppCompatAspectRatioOverrides appCompatAspectRatioOverrides =
-                activity.mAppCompatController.getAppCompatAspectRatioOverrides();
-        if (appCompatAspectRatioOverrides.isSplitScreenAspectRatioForUnresizableAppsEnabled()) {
-            // Default letterbox aspect ratio for unresizable apps.
-            return getSplitScreenAspectRatio(activity, task);
-        }
-
-        if (appCompatAspectRatioOverrides.getDefaultMinAspectRatioForUnresizableAppsFromConfig()
-                > MIN_FIXED_ORIENTATION_LETTERBOX_ASPECT_RATIO) {
-            return appCompatAspectRatioOverrides
-                    .getDefaultMinAspectRatioForUnresizableAppsFromConfig();
-        }
-
-        return appCompatAspectRatioOverrides.getDefaultMinAspectRatio();
-    }
-
-    /**
-     * Calculates the aspect ratio of the available display area when an app enters split-screen on
-     * a given device, taking into account any dividers and insets.
-     */
-    private static float getSplitScreenAspectRatio(@NonNull ActivityRecord activity,
-            @NonNull Task task) {
-        final int dividerWindowWidth =
-                activity.mWmService.mContext.getResources().getDimensionPixelSize(
-                        R.dimen.docked_stack_divider_thickness);
-        final int dividerInsets =
-                activity.mWmService.mContext.getResources().getDimensionPixelSize(
-                        R.dimen.docked_stack_divider_insets);
-        final int dividerSize = dividerWindowWidth - dividerInsets * 2;
-        final Rect bounds = new Rect(0, 0,
-                task.mDisplayContent.getDisplayInfo().appWidth,
-                task.mDisplayContent.getDisplayInfo().appHeight);
-        if (bounds.width() >= bounds.height()) {
-            bounds.inset(/* dx */ dividerSize / 2, /* dy */ 0);
-            bounds.right = bounds.centerX();
-        } else {
-            bounds.inset(/* dx */ 0, /* dy */ dividerSize / 2);
-            bounds.bottom = bounds.centerY();
-        }
-        return computeAspectRatio(bounds);
-    }
 }
diff --git a/services/core/java/com/android/server/wm/DisplayArea.java b/services/core/java/com/android/server/wm/DisplayArea.java
index 86f69cd..ca5485e 100644
--- a/services/core/java/com/android/server/wm/DisplayArea.java
+++ b/services/core/java/com/android/server/wm/DisplayArea.java
@@ -356,7 +356,7 @@
 
     @Override
     public void dumpDebug(ProtoOutputStream proto, long fieldId, int logLevel) {
-        if (logLevel == WindowTraceLogLevel.CRITICAL && !isVisible()) {
+        if (logLevel == WindowTracingLogLevel.CRITICAL && !isVisible()) {
             return;
         }
 
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index c44e1b1..86285fb 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -3496,10 +3496,7 @@
      */
     void collectDisplayChange(@NonNull Transition transition) {
         if (!mLastHasContent) return;
-        if (!transition.isCollecting()) {
-            throw new IllegalArgumentException("Can only collect display change if transition"
-                    + " is collecting");
-        }
+        if (!transition.isCollecting()) return;
         if (!transition.mParticipants.contains(this)) {
             transition.collect(this);
             startAsyncRotationIfNeeded();
@@ -3565,9 +3562,9 @@
 
     @Override
     public void dumpDebug(ProtoOutputStream proto, long fieldId,
-            @WindowTraceLogLevel int logLevel) {
+            @WindowTracingLogLevel int logLevel) {
         // Critical log level logs only visible elements to mitigate performance overheard
-        if (logLevel == WindowTraceLogLevel.CRITICAL && !isVisible()) {
+        if (logLevel == WindowTracingLogLevel.CRITICAL && !isVisible()) {
             return;
         }
 
diff --git a/services/core/java/com/android/server/wm/DisplayWindowSettings.java b/services/core/java/com/android/server/wm/DisplayWindowSettings.java
index 2f0ee17..f40f2617 100644
--- a/services/core/java/com/android/server/wm/DisplayWindowSettings.java
+++ b/services/core/java/com/android/server/wm/DisplayWindowSettings.java
@@ -16,6 +16,7 @@
 
 package com.android.server.wm;
 
+import static android.view.Display.REMOVE_MODE_DESTROY_CONTENT;
 import static android.view.WindowManager.DISPLAY_IME_POLICY_FALLBACK_DISPLAY;
 import static android.view.WindowManager.DISPLAY_IME_POLICY_LOCAL;
 import static android.view.WindowManager.REMOVE_CONTENT_MODE_DESTROY;
@@ -183,7 +184,7 @@
         final DisplayInfo displayInfo = dc.getDisplayInfo();
         final SettingsProvider.SettingsEntry settings = mSettingsProvider.getSettings(displayInfo);
         if (settings.mRemoveContentMode == REMOVE_CONTENT_MODE_UNDEFINED) {
-            if (dc.isPrivate()) {
+            if (dc.isPrivate() || dc.getDisplay().getRemoveMode() == REMOVE_MODE_DESTROY_CONTENT) {
                 // For private displays by default content is destroyed on removal.
                 return REMOVE_CONTENT_MODE_DESTROY;
             }
diff --git a/services/core/java/com/android/server/wm/EmbeddedWindowController.java b/services/core/java/com/android/server/wm/EmbeddedWindowController.java
index c66d659..169a76f 100644
--- a/services/core/java/com/android/server/wm/EmbeddedWindowController.java
+++ b/services/core/java/com/android/server/wm/EmbeddedWindowController.java
@@ -403,7 +403,7 @@
 
         @Override
         public void dumpProto(ProtoOutputStream proto, long fieldId,
-                              @WindowTraceLogLevel int logLevel) {
+                              @WindowTracingLogLevel int logLevel) {
             final long token = proto.start(fieldId);
 
             final long token2 = proto.start(IDENTIFIER);
diff --git a/services/core/java/com/android/server/wm/ImeInsetsSourceProvider.java b/services/core/java/com/android/server/wm/ImeInsetsSourceProvider.java
index 3a5f9b7..6b916ef 100644
--- a/services/core/java/com/android/server/wm/ImeInsetsSourceProvider.java
+++ b/services/core/java/com/android/server/wm/ImeInsetsSourceProvider.java
@@ -726,7 +726,7 @@
     }
 
     @Override
-    void dumpDebug(ProtoOutputStream proto, long fieldId, @WindowTraceLogLevel int logLevel) {
+    void dumpDebug(ProtoOutputStream proto, long fieldId, @WindowTracingLogLevel int logLevel) {
         final long token = proto.start(fieldId);
         super.dumpDebug(proto, INSETS_SOURCE_PROVIDER, logLevel);
         final WindowState imeRequesterWindow =
diff --git a/services/core/java/com/android/server/wm/InputTarget.java b/services/core/java/com/android/server/wm/InputTarget.java
index baf0db2..0c0b794 100644
--- a/services/core/java/com/android/server/wm/InputTarget.java
+++ b/services/core/java/com/android/server/wm/InputTarget.java
@@ -65,6 +65,6 @@
     InsetsControlTarget getImeControlTarget();
 
     void dumpProto(ProtoOutputStream proto, long fieldId,
-                   @WindowTraceLogLevel int logLevel);
+                   @WindowTracingLogLevel int logLevel);
 }
 
diff --git a/services/core/java/com/android/server/wm/InsetsSourceProvider.java b/services/core/java/com/android/server/wm/InsetsSourceProvider.java
index f5c92f6..b66b8bc 100644
--- a/services/core/java/com/android/server/wm/InsetsSourceProvider.java
+++ b/services/core/java/com/android/server/wm/InsetsSourceProvider.java
@@ -725,7 +725,7 @@
         }
     }
 
-    void dumpDebug(ProtoOutputStream proto, long fieldId, @WindowTraceLogLevel int logLevel) {
+    void dumpDebug(ProtoOutputStream proto, long fieldId, @WindowTracingLogLevel int logLevel) {
         final long token = proto.start(fieldId);
         mSource.dumpDebug(proto, SOURCE);
         mTmpRect.dumpDebug(proto, FRAME);
diff --git a/services/core/java/com/android/server/wm/InsetsStateController.java b/services/core/java/com/android/server/wm/InsetsStateController.java
index 9c2a8de..098a691 100644
--- a/services/core/java/com/android/server/wm/InsetsStateController.java
+++ b/services/core/java/com/android/server/wm/InsetsStateController.java
@@ -462,7 +462,7 @@
         }
     }
 
-    void dumpDebug(ProtoOutputStream proto, @WindowTraceLogLevel int logLevel) {
+    void dumpDebug(ProtoOutputStream proto, @WindowTracingLogLevel int logLevel) {
         for (int i = mProviders.size() - 1; i >= 0; i--) {
             final InsetsSourceProvider provider = mProviders.valueAt(i);
             provider.dumpDebug(proto,
diff --git a/services/core/java/com/android/server/wm/OWNERS b/services/core/java/com/android/server/wm/OWNERS
index 60454fc..781023c 100644
--- a/services/core/java/com/android/server/wm/OWNERS
+++ b/services/core/java/com/android/server/wm/OWNERS
@@ -30,3 +30,4 @@
 
 # Files related to tracing
 per-file *TransitionTracer.java = file:platform/development:/tools/winscope/OWNERS
+per-file *WindowTracing* = file:platform/development:/tools/winscope/OWNERS
diff --git a/services/core/java/com/android/server/wm/RootWindowContainer.java b/services/core/java/com/android/server/wm/RootWindowContainer.java
index 6427c32..4ca4730 100644
--- a/services/core/java/com/android/server/wm/RootWindowContainer.java
+++ b/services/core/java/com/android/server/wm/RootWindowContainer.java
@@ -1002,6 +1002,7 @@
                 // complete configuration.
                 continue;
             }
+            win.updateSurfacePositionIfNeeded();
             win.reportResized();
             mWmService.mResizingWindows.remove(i);
         }
@@ -1189,8 +1190,8 @@
 
     @Override
     public void dumpDebug(ProtoOutputStream proto, long fieldId,
-            @WindowTraceLogLevel int logLevel) {
-        if (logLevel == WindowTraceLogLevel.CRITICAL && !isVisible()) {
+            @WindowTracingLogLevel int logLevel) {
+        if (logLevel == WindowTracingLogLevel.CRITICAL && !isVisible()) {
             return;
         }
 
@@ -3427,26 +3428,30 @@
 
     boolean allResumedActivitiesIdle() {
         for (int displayNdx = getChildCount() - 1; displayNdx >= 0; --displayNdx) {
-            // TODO(b/117135575): Check resumed activities on all visible root tasks.
             final DisplayContent display = getChildAt(displayNdx);
             if (display.isSleeping()) {
                 // No resumed activities while display is sleeping.
                 continue;
             }
 
-            // If the focused root task is not null or not empty, there should have some activities
-            // resuming or resumed. Make sure these activities are idle.
-            final Task rootTask = display.getFocusedRootTask();
-            if (rootTask == null || !rootTask.hasActivity()) {
-                continue;
-            }
-            final ActivityRecord resumedActivity = rootTask.getTopResumedActivity();
-            if (resumedActivity == null || !resumedActivity.idle) {
-                ProtoLog.d(WM_DEBUG_STATES, "allResumedActivitiesIdle: rootTask=%d %s "
-                        + "not idle", rootTask.getRootTaskId(), resumedActivity);
+            final boolean foundNotIdle = display.forAllLeafTaskFragments(tf -> {
+                if (!tf.isVisibleRequested()) {
+                    return false;
+                }
+                // Note that only activities that will be resumed can report idle.
+                final ActivityRecord r = tf.topRunningActivity();
+                if (r != null && !r.idle && (r.isState(RESUMED)
+                        // Its process is not attached yet and it may resume later.
+                        || (r.app == null && r.isFocusable()))) {
+                    ProtoLog.d(WM_DEBUG_STATES, "allResumedActivitiesIdle: %s not idle", r);
+                    return true;
+                }
+                return false;
+            });
+            if (foundNotIdle) {
                 return false;
             }
-            if (mTransitionController.isTransientLaunch(resumedActivity)) {
+            if (mTransitionController.hasTransientLaunch(display)) {
                 // Not idle if the transient transition animation is running.
                 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 3f0c9fd..efa9c53 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -4705,6 +4705,15 @@
                 // it does not follow the ActivityStarter path.
                 if (topActivity.shouldBeVisible()) {
                     mAtmService.resumeAppSwitches();
+                    // In pip1, when expanding pip to full-screen, the "behind" task is not
+                    // actually becoming invisible since task windowing mode is pinned.
+                    if (!isPip2ExperimentEnabled) {
+                        final ActivityRecord ar = mAtmService.mLastResumedActivity;
+                        if (ar != null && ar.getTask() != null) {
+                            mAtmService.takeTaskSnapshot(ar.getTask().mTaskId,
+                                    true /* updateCache */);
+                        }
+                    }
                 }
             } else if (isPip2ExperimentEnabled) {
                 super.setWindowingMode(windowingMode);
@@ -6262,8 +6271,8 @@
 
     @Override
     public void dumpDebug(ProtoOutputStream proto, long fieldId,
-            @WindowTraceLogLevel int logLevel) {
-        if (logLevel == WindowTraceLogLevel.CRITICAL && !isVisible()) {
+            @WindowTracingLogLevel int logLevel) {
+        if (logLevel == WindowTracingLogLevel.CRITICAL && !isVisible()) {
             return;
         }
 
diff --git a/services/core/java/com/android/server/wm/TaskFragment.java b/services/core/java/com/android/server/wm/TaskFragment.java
index 329d11b..2fbabc5 100644
--- a/services/core/java/com/android/server/wm/TaskFragment.java
+++ b/services/core/java/com/android/server/wm/TaskFragment.java
@@ -1780,11 +1780,6 @@
         if (resuming != null) {
             // We do not want to trigger auto-PiP upon launch of a translucent activity.
             final boolean resumingOccludesParent = resuming.occludesParent();
-            // Resuming the new resume activity only if the previous activity can't go into Pip
-            // since we want to give Pip activities a chance to enter Pip before resuming the
-            // next activity.
-            final boolean lastResumedCanPip = prev.checkEnterPictureInPictureState(
-                    "shouldAutoPipWhilePausing", userLeaving);
 
             if (ActivityTaskManagerService.isPip2ExperimentEnabled()) {
                 // If a new task is being launched, then mark the existing top activity as
@@ -1794,6 +1789,12 @@
                 Task.enableEnterPipOnTaskSwitch(prev, resuming.getTask(),
                         resuming, resuming.getOptions());
             }
+
+            // Resuming the new resume activity only if the previous activity can't go into Pip
+            // since we want to give Pip activities a chance to enter Pip before resuming the
+            // next activity.
+            final boolean lastResumedCanPip = prev.checkEnterPictureInPictureState(
+                    "shouldAutoPipWhilePausing", userLeaving);
             if (prev.supportsEnterPipOnTaskSwitch && userLeaving
                     && resumingOccludesParent && lastResumedCanPip
                     && prev.pictureInPictureArgs.isAutoEnterEnabled()) {
@@ -3334,8 +3335,8 @@
 
     @Override
     public void dumpDebug(ProtoOutputStream proto, long fieldId,
-            @WindowTraceLogLevel int logLevel) {
-        if (logLevel == WindowTraceLogLevel.CRITICAL && !isVisible()) {
+            @WindowTracingLogLevel int logLevel) {
+        if (logLevel == WindowTracingLogLevel.CRITICAL && !isVisible()) {
             return;
         }
 
diff --git a/services/core/java/com/android/server/wm/TransitionController.java b/services/core/java/com/android/server/wm/TransitionController.java
index ef25eda..1d2b693 100644
--- a/services/core/java/com/android/server/wm/TransitionController.java
+++ b/services/core/java/com/android/server/wm/TransitionController.java
@@ -408,6 +408,10 @@
      */
     @Nullable
     Transition getCollectingTransition() {
+        if (mCollectingTransition != null && !mCollectingTransition.isCollecting()) {
+            Slog.wtfStack(TAG, "Collecting Transition (#" + mCollectingTransition.getSyncId()
+                    + ") is not collecting. state=" + mCollectingTransition.getState());
+        }
         return mCollectingTransition;
     }
 
diff --git a/services/core/java/com/android/server/wm/WindowContainer.java b/services/core/java/com/android/server/wm/WindowContainer.java
index 15d67eb..9ae881b 100644
--- a/services/core/java/com/android/server/wm/WindowContainer.java
+++ b/services/core/java/com/android/server/wm/WindowContainer.java
@@ -2784,9 +2784,9 @@
     @CallSuper
     @Override
     public void dumpDebug(ProtoOutputStream proto, long fieldId,
-            @WindowTraceLogLevel int logLevel) {
+            @WindowTracingLogLevel int logLevel) {
         boolean isVisible = isVisible();
-        if (logLevel == WindowTraceLogLevel.CRITICAL && !isVisible) {
+        if (logLevel == WindowTracingLogLevel.CRITICAL && !isVisible) {
             return;
         }
 
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index 243ab3a..87c0084 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -32,6 +32,7 @@
 import static android.app.AppOpsManager.OP_SYSTEM_ALERT_WINDOW;
 import static android.app.StatusBarManager.DISABLE_MASK;
 import static android.app.WindowConfiguration.ROTATION_UNDEFINED;
+import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
 import static android.app.admin.DevicePolicyManager.ACTION_DEVICE_POLICY_MANAGER_STATE_CHANGED;
 import static android.content.pm.PackageManager.FEATURE_FREEFORM_WINDOW_MANAGEMENT;
 import static android.content.pm.PackageManager.FEATURE_PC;
@@ -6858,7 +6859,7 @@
      * @param proto     Stream to write the WindowContainer object to.
      * @param logLevel  Determines the amount of data to be written to the Protobuf.
      */
-    void dumpDebugLocked(ProtoOutputStream proto, @WindowTraceLogLevel int logLevel) {
+    void dumpDebugLocked(ProtoOutputStream proto, @WindowTracingLogLevel int logLevel) {
         mPolicy.dumpDebug(proto, POLICY);
         mRoot.dumpDebug(proto, ROOT_WINDOW_CONTAINER, logLevel);
         final DisplayContent topFocusedDisplayContent = mRoot.getTopFocusedDisplayContent();
@@ -7217,7 +7218,7 @@
         if (useProto) {
             final ProtoOutputStream proto = new ProtoOutputStream(fd);
             synchronized (mGlobalLock) {
-                dumpDebugLocked(proto, WindowTraceLogLevel.ALL);
+                dumpDebugLocked(proto, WindowTracingLogLevel.ALL);
             }
             proto.flush();
             return;
@@ -9024,14 +9025,7 @@
         }
         clearPointerDownOutsideFocusRunnable();
 
-        // For embedded activity that is showing side-by-side with another activity, delay
-        // handling the touch-outside event to prevent focus rapid changes back-n-forth.
-        // Otherwise, handle the touch-outside event directly.
-        final WindowState w = t.getWindowState();
-        final ActivityRecord activity = w != null ? w.getActivityRecord() : null;
-        if (mFocusedInputTarget != t && mFocusedInputTarget != null
-                && activity != null && activity.isEmbedded()
-                && activity.getTaskFragment().getAdjacentTaskFragment() != null) {
+        if (shouldDelayTouchOutside(t)) {
             mPointerDownOutsideFocusRunnable = () -> handlePointerDownOutsideFocus(t);
             mH.postDelayed(mPointerDownOutsideFocusRunnable, POINTER_DOWN_OUTSIDE_FOCUS_TIMEOUT_MS);
         } else if (!fromHandler) {
@@ -9044,6 +9038,33 @@
         }
     }
 
+    private boolean shouldDelayTouchOutside(InputTarget t) {
+        final WindowState w = t.getWindowState();
+        final ActivityRecord activity = w != null ? w.getActivityRecord() : null;
+        final Task task = w != null ? w.getRootTask() : null;
+
+        final boolean isInputTargetNotFocused =
+                mFocusedInputTarget != t && mFocusedInputTarget != null;
+        if (!isInputTargetNotFocused) {
+            return false;
+        }
+
+        // For embedded activity that is showing side-by-side with another activity, delay
+        // handling the touch-outside event to prevent focus rapid changes back-n-forth.
+        final boolean shouldDelayTouchForEmbeddedActivity = activity != null
+                && activity.isEmbedded()
+                && activity.getTaskFragment().getAdjacentTaskFragment() != null;
+
+        // For cases when there are multiple freeform windows where non-top windows are blocking
+        // the gesture zones, delay handling the touch-outside event to prevent refocusing the
+        // the non-top windows during the gesture.
+        final boolean shouldDelayTouchForFreeform =
+                task != null && task.getWindowingMode() == WINDOWING_MODE_FREEFORM;
+
+        // If non of the above cases are true, handle the touch-outside event directly.
+        return shouldDelayTouchForEmbeddedActivity || shouldDelayTouchForFreeform;
+    }
+
     private void handlePointerDownOutsideFocus(InputTarget t) {
         synchronized (mGlobalLock) {
             if (mPointerDownOutsideFocusRunnable != null
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index b6e8977..4568f2e 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -4072,9 +4072,9 @@
     @CallSuper
     @Override
     public void dumpDebug(ProtoOutputStream proto, long fieldId,
-            @WindowTraceLogLevel int logLevel) {
+            @WindowTracingLogLevel int logLevel) {
         boolean isVisible = isVisible();
-        if (logLevel == WindowTraceLogLevel.CRITICAL && !isVisible) {
+        if (logLevel == WindowTracingLogLevel.CRITICAL && !isVisible) {
             return;
         }
 
@@ -5333,6 +5333,14 @@
         super.prepareSurfaces();
     }
 
+    void updateSurfacePositionIfNeeded() {
+        if (mWindowFrames.mRelFrame.top == mWindowFrames.mLastRelFrame.top
+                && mWindowFrames.mRelFrame.left == mWindowFrames.mLastRelFrame.left) {
+            return;
+        }
+        updateSurfacePosition(getSyncTransaction());
+    }
+
     @Override
     @VisibleForTesting
     void updateSurfacePosition(Transaction t) {
@@ -6140,7 +6148,7 @@
 
     @Override
     public void dumpProto(ProtoOutputStream proto, long fieldId,
-                          @WindowTraceLogLevel int logLevel) {
+                          @WindowTracingLogLevel int logLevel) {
         dumpDebug(proto, fieldId, logLevel);
     }
 
diff --git a/services/core/java/com/android/server/wm/WindowToken.java b/services/core/java/com/android/server/wm/WindowToken.java
index 11ef2cd..67bd5cb 100644
--- a/services/core/java/com/android/server/wm/WindowToken.java
+++ b/services/core/java/com/android/server/wm/WindowToken.java
@@ -698,8 +698,8 @@
     @CallSuper
     @Override
     public void dumpDebug(ProtoOutputStream proto, long fieldId,
-            @WindowTraceLogLevel int logLevel) {
-        if (logLevel == WindowTraceLogLevel.CRITICAL && !isVisible()) {
+            @WindowTracingLogLevel int logLevel) {
+        if (logLevel == WindowTracingLogLevel.CRITICAL && !isVisible()) {
             return;
         }
 
diff --git a/services/core/java/com/android/server/wm/WindowTracing.java b/services/core/java/com/android/server/wm/WindowTracing.java
index 04d5c03..fe26726 100644
--- a/services/core/java/com/android/server/wm/WindowTracing.java
+++ b/services/core/java/com/android/server/wm/WindowTracing.java
@@ -112,7 +112,7 @@
         saveForBugreportInternal(pw);
     }
 
-    abstract void setLogLevel(@WindowTraceLogLevel int logLevel, PrintWriter pw);
+    abstract void setLogLevel(@WindowTracingLogLevel int logLevel, PrintWriter pw);
     abstract void setLogFrequency(boolean onFrame, PrintWriter pw);
     abstract void setBufferCapacity(int capacity, PrintWriter pw);
     abstract boolean isEnabled();
@@ -158,7 +158,7 @@
      * @param where Logging point descriptor
      * @param elapsedRealtimeNanos Timestamp
      */
-    protected void dumpToProto(ProtoOutputStream os, @WindowTraceLogLevel int logLevel,
+    protected void dumpToProto(ProtoOutputStream os, @WindowTracingLogLevel int logLevel,
             String where, long elapsedRealtimeNanos) {
         Trace.traceBegin(Trace.TRACE_TAG_WINDOW_MANAGER, "traceStateLocked");
         try {
diff --git a/services/core/java/com/android/server/wm/WindowTracingDataSource.java b/services/core/java/com/android/server/wm/WindowTracingDataSource.java
index 3d2c0d3..6984f0d 100644
--- a/services/core/java/com/android/server/wm/WindowTracingDataSource.java
+++ b/services/core/java/com/android/server/wm/WindowTracingDataSource.java
@@ -50,12 +50,14 @@
     }
 
     public static class Config {
-        public final @WindowTraceLogLevel int mLogLevel;
-        public final boolean mLogOnFrame;
+        public final @WindowTracingLogLevel int mLogLevel;
+        public final @WindowTracingLogFrequency int mLogFrequency;
 
-        private Config(@WindowTraceLogLevel int logLevel, boolean logOnFrame) {
+        private Config(
+                @WindowTracingLogLevel int logLevel,
+                @WindowTracingLogFrequency int logFrequency) {
             mLogLevel = logLevel;
-            mLogOnFrame = logOnFrame;
+            mLogFrequency = logFrequency;
         }
     }
 
@@ -68,7 +70,8 @@
         }
     }
 
-    private static final Config CONFIG_DEFAULT = new Config(WindowTraceLogLevel.TRIM, true);
+    private static final Config CONFIG_DEFAULT =
+            new Config(WindowTracingLogLevel.TRIM, WindowTracingLogFrequency.FRAME);
     private static final int CONFIG_VALUE_UNSPECIFIED = 0;
     private static final String TAG = "WindowTracingDataSource";
 
@@ -160,45 +163,48 @@
             throw new RuntimeException("Failed to parse WindowManagerConfig", e);
         }
 
-        @WindowTraceLogLevel int logLevel;
+        @WindowTracingLogLevel int logLevel;
         switch(parsedLogLevel) {
             case CONFIG_VALUE_UNSPECIFIED:
                 Log.w(TAG, "Unspecified log level. Defaulting to TRIM");
-                logLevel = WindowTraceLogLevel.TRIM;
+                logLevel = WindowTracingLogLevel.TRIM;
                 break;
             case WindowManagerConfig.LOG_LEVEL_VERBOSE:
-                logLevel = WindowTraceLogLevel.ALL;
+                logLevel = WindowTracingLogLevel.ALL;
                 break;
             case WindowManagerConfig.LOG_LEVEL_DEBUG:
-                logLevel = WindowTraceLogLevel.TRIM;
+                logLevel = WindowTracingLogLevel.TRIM;
                 break;
             case WindowManagerConfig.LOG_LEVEL_CRITICAL:
-                logLevel = WindowTraceLogLevel.CRITICAL;
+                logLevel = WindowTracingLogLevel.CRITICAL;
                 break;
             default:
                 Log.w(TAG, "Unrecognized log level. Defaulting to TRIM");
-                logLevel = WindowTraceLogLevel.TRIM;
+                logLevel = WindowTracingLogLevel.TRIM;
                 break;
         }
 
-        boolean logOnFrame;
+        @WindowTracingLogFrequency int logFrequency;
         switch(parsedLogFrequency) {
             case CONFIG_VALUE_UNSPECIFIED:
-                Log.w(TAG, "Unspecified log frequency. Defaulting to 'log on frame'");
-                logOnFrame = true;
+                Log.w(TAG, "Unspecified log frequency. Defaulting to 'frame'");
+                logFrequency = WindowTracingLogFrequency.FRAME;
                 break;
             case WindowManagerConfig.LOG_FREQUENCY_FRAME:
-                logOnFrame = true;
+                logFrequency = WindowTracingLogFrequency.FRAME;
                 break;
             case WindowManagerConfig.LOG_FREQUENCY_TRANSACTION:
-                logOnFrame = false;
+                logFrequency = WindowTracingLogFrequency.TRANSACTION;
+                break;
+            case WindowManagerConfig.LOG_FREQUENCY_SINGLE_DUMP:
+                logFrequency = WindowTracingLogFrequency.SINGLE_DUMP;
                 break;
             default:
-                Log.w(TAG, "Unrecognized log frequency. Defaulting to 'log on frame'");
-                logOnFrame = true;
+                Log.w(TAG, "Unrecognized log frequency. Defaulting to 'frame'");
+                logFrequency = WindowTracingLogFrequency.FRAME;
                 break;
         }
 
-        return new Config(logLevel, logOnFrame);
+        return new Config(logLevel, logFrequency);
     }
 }
diff --git a/services/core/java/com/android/server/wm/WindowTracingLegacy.java b/services/core/java/com/android/server/wm/WindowTracingLegacy.java
index 7a36707..34fd088 100644
--- a/services/core/java/com/android/server/wm/WindowTracingLegacy.java
+++ b/services/core/java/com/android/server/wm/WindowTracingLegacy.java
@@ -30,6 +30,7 @@
 import android.util.proto.ProtoOutputStream;
 import android.view.Choreographer;
 
+import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.util.TraceBuffer;
 
 import java.io.File;
@@ -58,7 +59,7 @@
     private boolean mEnabled;
     private volatile boolean mEnabledLockFree;
 
-    protected @WindowTraceLogLevel int mLogLevel = WindowTraceLogLevel.TRIM;
+    protected @WindowTracingLogLevel int mLogLevel = WindowTracingLogLevel.TRIM;
     protected boolean mLogOnFrame = false;
 
     WindowTracingLegacy(WindowManagerService service, Choreographer choreographer) {
@@ -66,6 +67,7 @@
                 service.mGlobalLock, BUFFER_CAPACITY_TRIM);
     }
 
+    @VisibleForTesting
     WindowTracingLegacy(File traceFile, WindowManagerService service, Choreographer choreographer,
             WindowManagerGlobalLock globalLock, int bufferSize) {
         super(service, choreographer, globalLock);
@@ -74,20 +76,20 @@
     }
 
     @Override
-    void setLogLevel(@WindowTraceLogLevel int logLevel, PrintWriter pw) {
+    void setLogLevel(@WindowTracingLogLevel int logLevel, PrintWriter pw) {
         logAndPrintln(pw, "Setting window tracing log level to " + logLevel);
         mLogLevel = logLevel;
 
         switch (logLevel) {
-            case WindowTraceLogLevel.ALL: {
+            case WindowTracingLogLevel.ALL: {
                 setBufferCapacity(BUFFER_CAPACITY_ALL, pw);
                 break;
             }
-            case WindowTraceLogLevel.TRIM: {
+            case WindowTracingLogLevel.TRIM: {
                 setBufferCapacity(BUFFER_CAPACITY_TRIM, pw);
                 break;
             }
-            case WindowTraceLogLevel.CRITICAL: {
+            case WindowTracingLogLevel.CRITICAL: {
                 setBufferCapacity(BUFFER_CAPACITY_CRITICAL, pw);
                 break;
             }
@@ -141,19 +143,19 @@
                 String logLevelStr = shell.getNextArgRequired().toLowerCase();
                 switch (logLevelStr) {
                     case "all": {
-                        setLogLevel(WindowTraceLogLevel.ALL, pw);
+                        setLogLevel(WindowTracingLogLevel.ALL, pw);
                         break;
                     }
                     case "trim": {
-                        setLogLevel(WindowTraceLogLevel.TRIM, pw);
+                        setLogLevel(WindowTracingLogLevel.TRIM, pw);
                         break;
                     }
                     case "critical": {
-                        setLogLevel(WindowTraceLogLevel.CRITICAL, pw);
+                        setLogLevel(WindowTracingLogLevel.CRITICAL, pw);
                         break;
                     }
                     default: {
-                        setLogLevel(WindowTraceLogLevel.TRIM, pw);
+                        setLogLevel(WindowTracingLogLevel.TRIM, pw);
                         break;
                     }
                 }
diff --git a/services/core/java/com/android/server/wm/WindowTracingLogFrequency.java b/services/core/java/com/android/server/wm/WindowTracingLogFrequency.java
new file mode 100644
index 0000000..8e2c308
--- /dev/null
+++ b/services/core/java/com/android/server/wm/WindowTracingLogFrequency.java
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm;
+
+import android.annotation.IntDef;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+@IntDef({
+        WindowTracingLogFrequency.FRAME,
+        WindowTracingLogFrequency.TRANSACTION,
+        WindowTracingLogFrequency.SINGLE_DUMP,
+})
+@Retention(RetentionPolicy.SOURCE)
+@interface WindowTracingLogFrequency {
+    /**
+     * Trace state snapshots when a frame is committed.
+     */
+    int FRAME = 0;
+    /**
+     * Trace state snapshots when a transaction is committed.
+     */
+    int TRANSACTION = 1;
+    /**
+     * Trace single state snapshots when the Perfetto data source is started.
+     */
+    int SINGLE_DUMP = 2;
+}
diff --git a/services/core/java/com/android/server/wm/WindowTraceLogLevel.java b/services/core/java/com/android/server/wm/WindowTracingLogLevel.java
similarity index 90%
rename from services/core/java/com/android/server/wm/WindowTraceLogLevel.java
rename to services/core/java/com/android/server/wm/WindowTracingLogLevel.java
index 2165c66..4f901c6 100644
--- a/services/core/java/com/android/server/wm/WindowTraceLogLevel.java
+++ b/services/core/java/com/android/server/wm/WindowTracingLogLevel.java
@@ -22,12 +22,12 @@
 import java.lang.annotation.RetentionPolicy;
 
 @IntDef({
-        WindowTraceLogLevel.ALL,
-        WindowTraceLogLevel.TRIM,
-        WindowTraceLogLevel.CRITICAL,
+        WindowTracingLogLevel.ALL,
+        WindowTracingLogLevel.TRIM,
+        WindowTracingLogLevel.CRITICAL,
 })
 @Retention(RetentionPolicy.SOURCE)
-@interface WindowTraceLogLevel{
+@interface WindowTracingLogLevel {
     /**
      * Logs all elements with maximum amount of information.
      *
diff --git a/services/core/java/com/android/server/wm/WindowTracingPerfetto.java b/services/core/java/com/android/server/wm/WindowTracingPerfetto.java
index 653b6da..cf948ca 100644
--- a/services/core/java/com/android/server/wm/WindowTracingPerfetto.java
+++ b/services/core/java/com/android/server/wm/WindowTracingPerfetto.java
@@ -25,6 +25,8 @@
 import android.util.proto.ProtoOutputStream;
 import android.view.Choreographer;
 
+import com.android.internal.annotations.VisibleForTesting;
+
 import java.io.PrintWriter;
 import java.util.concurrent.atomic.AtomicInteger;
 
@@ -37,11 +39,17 @@
             this::onStart, this::onStop);
 
     WindowTracingPerfetto(WindowManagerService service, Choreographer choreographer) {
-        super(service, choreographer, service.mGlobalLock);
+        this(service, choreographer, service.mGlobalLock);
+    }
+
+    @VisibleForTesting
+    WindowTracingPerfetto(WindowManagerService service, Choreographer choreographer,
+            WindowManagerGlobalLock globalLock) {
+        super(service, choreographer, globalLock);
     }
 
     @Override
-    void setLogLevel(@WindowTraceLogLevel int logLevel, PrintWriter pw) {
+    void setLogLevel(@WindowTracingLogLevel int logLevel, PrintWriter pw) {
         logAndPrintln(pw, "Log level must be configured through perfetto");
     }
 
@@ -110,7 +118,15 @@
                     if (!isDataSourceStarting) {
                         return;
                     }
-                } else if (isOnFrameLogEvent != dataSourceConfig.mLogOnFrame) {
+                } else if (isOnFrameLogEvent) {
+                    boolean isDataSourceLoggingOnFrame =
+                            dataSourceConfig.mLogFrequency == WindowTracingLogFrequency.FRAME;
+                    if (!isDataSourceLoggingOnFrame) {
+                        return;
+                    }
+                } else if (dataSourceConfig.mLogFrequency
+                        == WindowTracingLogFrequency.SINGLE_DUMP) {
+                    // If it is a dump, write only the start log event and skip the following ones
                     return;
                 }
 
@@ -141,21 +157,21 @@
     }
 
     private void onStart(WindowTracingDataSource.Config config) {
-        if (config.mLogOnFrame) {
+        if (config.mLogFrequency == WindowTracingLogFrequency.FRAME) {
             mCountSessionsOnFrame.incrementAndGet();
-        } else {
+        } else if (config.mLogFrequency == WindowTracingLogFrequency.TRANSACTION) {
             mCountSessionsOnTransaction.incrementAndGet();
         }
 
         Log.i(TAG, "Started with logLevel: " + config.mLogLevel
-                + " logOnFrame: " + config.mLogOnFrame);
+                + " logFrequency: " + config.mLogFrequency);
         log(WHERE_START_TRACING);
     }
 
     private void onStop(WindowTracingDataSource.Config config) {
-        if (config.mLogOnFrame) {
+        if (config.mLogFrequency == WindowTracingLogFrequency.FRAME) {
             mCountSessionsOnFrame.decrementAndGet();
-        } else {
+        } else if (config.mLogFrequency == WindowTracingLogFrequency.TRANSACTION) {
             mCountSessionsOnTransaction.decrementAndGet();
         }
         Log.i(TAG, "Stopped");
diff --git a/services/core/jni/com_android_server_input_InputManagerService.cpp b/services/core/jni/com_android_server_input_InputManagerService.cpp
index 4d6a90c..07d39d9 100644
--- a/services/core/jni/com_android_server_input_InputManagerService.cpp
+++ b/services/core/jni/com_android_server_input_InputManagerService.cpp
@@ -291,6 +291,7 @@
     void setTouchpadNaturalScrollingEnabled(bool enabled);
     void setTouchpadTapToClickEnabled(bool enabled);
     void setTouchpadTapDraggingEnabled(bool enabled);
+    void setShouldNotifyTouchpadHardwareState(bool enabled);
     void setTouchpadRightClickZoneEnabled(bool enabled);
     void setInputDeviceEnabled(uint32_t deviceId, bool enabled);
     void setShowTouches(bool enabled);
@@ -440,6 +441,9 @@
         // True to enable tap dragging on touchpads.
         bool touchpadTapDraggingEnabled{false};
 
+        // True if hardware state update notifications should be sent to the policy.
+        bool shouldNotifyTouchpadHardwareState{false};
+
         // True to enable a zone on the right-hand side of touchpads where clicks will be turned
         // into context (a.k.a. "right") clicks.
         bool touchpadRightClickZoneEnabled{false};
@@ -698,6 +702,7 @@
         outConfig->touchpadNaturalScrollingEnabled = mLocked.touchpadNaturalScrollingEnabled;
         outConfig->touchpadTapToClickEnabled = mLocked.touchpadTapToClickEnabled;
         outConfig->touchpadTapDraggingEnabled = mLocked.touchpadTapDraggingEnabled;
+        outConfig->shouldNotifyTouchpadHardwareState = mLocked.shouldNotifyTouchpadHardwareState;
         outConfig->touchpadRightClickZoneEnabled = mLocked.touchpadRightClickZoneEnabled;
 
         outConfig->disabledDevices = mLocked.disabledInputDevices;
@@ -1260,6 +1265,22 @@
             InputReaderConfiguration::Change::TOUCHPAD_SETTINGS);
 }
 
+void NativeInputManager::setShouldNotifyTouchpadHardwareState(bool enabled) {
+    { // acquire lock
+        std::scoped_lock _l(mLock);
+
+        if (mLocked.shouldNotifyTouchpadHardwareState == enabled) {
+            return;
+        }
+
+        ALOGI("Should touchpad hardware state be notified: %s.", toString(enabled));
+        mLocked.shouldNotifyTouchpadHardwareState = enabled;
+    } // release lock
+
+    mInputManager->getReader().requestRefreshConfiguration(
+            InputReaderConfiguration::Change::TOUCHPAD_SETTINGS);
+}
+
 void NativeInputManager::setTouchpadRightClickZoneEnabled(bool enabled) {
     { // acquire lock
         std::scoped_lock _l(mLock);
@@ -2144,6 +2165,13 @@
     im->setTouchpadTapDraggingEnabled(enabled);
 }
 
+static void nativeSetShouldNotifyTouchpadHardwareState(JNIEnv* env, jobject nativeImplObj,
+                                                       jboolean enabled) {
+    NativeInputManager* im = getNativeInputManager(env, nativeImplObj);
+
+    im->setShouldNotifyTouchpadHardwareState(enabled);
+}
+
 static void nativeSetTouchpadRightClickZoneEnabled(JNIEnv* env, jobject nativeImplObj,
                                                    jboolean enabled) {
     NativeInputManager* im = getNativeInputManager(env, nativeImplObj);
@@ -2762,6 +2790,8 @@
          (void*)nativeSetTouchpadNaturalScrollingEnabled},
         {"setTouchpadTapToClickEnabled", "(Z)V", (void*)nativeSetTouchpadTapToClickEnabled},
         {"setTouchpadTapDraggingEnabled", "(Z)V", (void*)nativeSetTouchpadTapDraggingEnabled},
+        {"setShouldNotifyTouchpadHardwareState", "(Z)V",
+         (void*)nativeSetShouldNotifyTouchpadHardwareState},
         {"setTouchpadRightClickZoneEnabled", "(Z)V", (void*)nativeSetTouchpadRightClickZoneEnabled},
         {"setShowTouches", "(Z)V", (void*)nativeSetShowTouches},
         {"setInteractive", "(Z)V", (void*)nativeSetInteractive},
diff --git a/services/core/jni/com_android_server_vibrator_VibratorController.cpp b/services/core/jni/com_android_server_vibrator_VibratorController.cpp
index f12930a..5c5ac28 100644
--- a/services/core/jni/com_android_server_vibrator_VibratorController.cpp
+++ b/services/core/jni/com_android_server_vibrator_VibratorController.cpp
@@ -198,7 +198,8 @@
 }
 
 static Aidl::VendorEffect vendorEffectFromJavaParcel(JNIEnv* env, jobject vendorData,
-                                                     jlong strength, jfloat scale) {
+                                                     jlong strength, jfloat scale,
+                                                     jfloat adaptiveScale) {
     PersistableBundle bundle;
     if (AParcel* parcel = AParcel_fromJavaParcel(env, vendorData); parcel != nullptr) {
         if (binder_status_t status = bundle.readFromParcel(parcel); status == STATUS_OK) {
@@ -217,6 +218,7 @@
     effect.vendorData = bundle;
     effect.strength = static_cast<Aidl::EffectStrength>(strength);
     effect.scale = static_cast<float>(scale);
+    effect.vendorScale = static_cast<float>(adaptiveScale);
     return effect;
 }
 
@@ -319,13 +321,14 @@
 
 static jlong vibratorPerformVendorEffect(JNIEnv* env, jclass /* clazz */, jlong ptr,
                                          jobject vendorData, jlong strength, jfloat scale,
-                                         jlong vibrationId) {
+                                         jfloat adaptiveScale, jlong vibrationId) {
     VibratorControllerWrapper* wrapper = reinterpret_cast<VibratorControllerWrapper*>(ptr);
     if (wrapper == nullptr) {
         ALOGE("vibratorPerformVendorEffect failed because native wrapper was not initialized");
         return -1;
     }
-    Aidl::VendorEffect effect = vendorEffectFromJavaParcel(env, vendorData, strength, scale);
+    Aidl::VendorEffect effect =
+            vendorEffectFromJavaParcel(env, vendorData, strength, scale, adaptiveScale);
     auto callback = wrapper->createCallback(vibrationId);
     auto performVendorEffectFn = [&effect, &callback](vibrator::HalWrapper* hal) {
         return hal->performVendorEffect(effect, callback);
@@ -511,7 +514,7 @@
         {"off", "(J)V", (void*)vibratorOff},
         {"setAmplitude", "(JF)V", (void*)vibratorSetAmplitude},
         {"performEffect", "(JJJJ)J", (void*)vibratorPerformEffect},
-        {"performVendorEffect", "(JLandroid/os/Parcel;JFJ)J", (void*)vibratorPerformVendorEffect},
+        {"performVendorEffect", "(JLandroid/os/Parcel;JFFJ)J", (void*)vibratorPerformVendorEffect},
         {"performComposedEffect", "(J[Landroid/os/vibrator/PrimitiveSegment;J)J",
          (void*)vibratorPerformComposedEffect},
         {"performPwleEffect", "(J[Landroid/os/vibrator/RampSegment;IJ)J",
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index 9ed645b..d5013517 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -12215,34 +12215,30 @@
      * permittedList or are a system app.
      */
     private boolean checkPackagesInPermittedListOrSystem(List<String> enabledPackages,
-            List<String> permittedList, int userIdToCheck) {
+            List<String> permittedList, int userId) {
         long id = mInjector.binderClearCallingIdentity();
         try {
-            // If we have an enabled packages list for a managed profile the packages
-            // we should check are installed for the parent user.
-            UserInfo user = getUserInfo(userIdToCheck);
-            if (user.isManagedProfile()) {
-                userIdToCheck = user.profileGroupId;
-            }
-
             for (String enabledPackage : enabledPackages) {
-                boolean systemService = false;
+                if (permittedList.contains(enabledPackage)) {
+                    continue;
+                }
                 try {
                     ApplicationInfo applicationInfo = mIPackageManager.getApplicationInfo(
-                            enabledPackage, PackageManager.MATCH_UNINSTALLED_PACKAGES,
-                            userIdToCheck);
+                            enabledPackage, PackageManager.MATCH_ANY_USER, userId);
 
                     if (applicationInfo == null) {
+                        Slogf.wtf(LOG_TAG, "Can't find ApplicationInfo for %s", enabledPackage);
                         return false;
                     }
 
-                    systemService = (applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0;
+                    if (!applicationInfo.isSystemApp()) {
+                        Slogf.w(LOG_TAG,
+                                "Enabled package neither permitted nor system: %s", enabledPackage);
+                        return false;
+                    }
                 } catch (RemoteException e) {
                     Slogf.i(LOG_TAG, "Can't talk to package managed", e);
                 }
-                if (!systemService && !permittedList.contains(enabledPackage)) {
-                    return false;
-                }
             }
         } finally {
             mInjector.binderRestoreCallingIdentity(id);
diff --git a/services/foldables/devicestateprovider/src/com/android/server/policy/feature/Android.bp b/services/foldables/devicestateprovider/src/com/android/server/policy/feature/Android.bp
index 6393e11..1db9e8d 100644
--- a/services/foldables/devicestateprovider/src/com/android/server/policy/feature/Android.bp
+++ b/services/foldables/devicestateprovider/src/com/android/server/policy/feature/Android.bp
@@ -1,7 +1,7 @@
 aconfig_declarations {
     name: "device_state_flags",
     package: "com.android.server.policy.feature.flags",
-    container: "system",
+    container: "system_ext",
     srcs: [
         "device_state_flags.aconfig",
     ],
diff --git a/services/foldables/devicestateprovider/src/com/android/server/policy/feature/device_state_flags.aconfig b/services/foldables/devicestateprovider/src/com/android/server/policy/feature/device_state_flags.aconfig
index 21e33dd..f827b55 100644
--- a/services/foldables/devicestateprovider/src/com/android/server/policy/feature/device_state_flags.aconfig
+++ b/services/foldables/devicestateprovider/src/com/android/server/policy/feature/device_state_flags.aconfig
@@ -1,5 +1,5 @@
 package: "com.android.server.policy.feature.flags"
-container: "system"
+container: "system_ext"
 
 flag {
     name: "enable_dual_display_blocking"
diff --git a/services/proguard.flags b/services/proguard.flags
index 35e27f8..cdd41ab 100644
--- a/services/proguard.flags
+++ b/services/proguard.flags
@@ -114,6 +114,13 @@
 -keep public class android.os.** { *; }
 -keep public class com.android.internal.util.** { *; }
 -keep public class com.android.modules.utils.build.** { *; }
+# Also suppress related duplicate type warnings for the above kept classes.
+-dontwarn android.gsi.**
+-dontwarn android.hidl.base.**
+-dontwarn android.hidl.manager.**
+-dontwarn android.os.**
+-dontwarn com.android.internal.util.**
+-dontwarn com.android.modules.utils.build.**
 
 # CoverageService guards optional jacoco class references with a runtime guard, so we can safely
 # suppress build-time warnings.
diff --git a/services/tests/displayservicetests/src/com/android/server/display/DisplayManagerServiceTest.java b/services/tests/displayservicetests/src/com/android/server/display/DisplayManagerServiceTest.java
index 2b03dc4..bbf2ecb 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/DisplayManagerServiceTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/DisplayManagerServiceTest.java
@@ -69,7 +69,6 @@
 import android.companion.virtual.IVirtualDevice;
 import android.companion.virtual.IVirtualDeviceManager;
 import android.companion.virtual.VirtualDeviceManager;
-import android.companion.virtual.flags.Flags;
 import android.compat.testing.PlatformCompatChangeRule;
 import android.content.BroadcastReceiver;
 import android.content.Context;
@@ -374,7 +373,6 @@
     public void setUp() throws Exception {
         MockitoAnnotations.initMocks(this);
         when(mMockFlags.isConnectedDisplayManagementEnabled()).thenReturn(false);
-        mSetFlagsRule.disableFlags(Flags.FLAG_INTERACTIVE_SCREEN_MIRROR);
 
         mLocalServiceKeeperRule.overrideLocalService(
                 InputManagerInternal.class, mMockInputManagerInternal);
@@ -1298,44 +1296,11 @@
     }
 
     /**
-     * Tests that it's not allowed to create an auto-mirror virtual display when display mirroring
-     * is not supported in a virtual device.
-     */
-    @Test
-    public void createAutoMirrorDisplay_virtualDeviceDoesntSupportMirroring_throwsException()
-            throws Exception {
-        mSetFlagsRule.disableFlags(Flags.FLAG_INTERACTIVE_SCREEN_MIRROR);
-        DisplayManagerService displayManager = new DisplayManagerService(mContext, mBasicInjector);
-        DisplayManagerInternal localService = displayManager.new LocalService();
-        registerDefaultDisplays(displayManager);
-        when(mMockAppToken.asBinder()).thenReturn(mMockAppToken);
-        when(mContext.checkCallingPermission(CAPTURE_VIDEO_OUTPUT)).thenReturn(
-                PackageManager.PERMISSION_DENIED);
-        IVirtualDevice virtualDevice = mock(IVirtualDevice.class);
-        when(virtualDevice.getDeviceId()).thenReturn(1);
-        when(mIVirtualDeviceManager.isValidVirtualDeviceId(1)).thenReturn(true);
-
-        final VirtualDisplayConfig.Builder builder =
-                new VirtualDisplayConfig.Builder(VIRTUAL_DISPLAY_NAME, 600, 800, 320)
-                        .setFlags(VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR)
-                        .setUniqueId("uniqueId --- mirror display");
-        assertThrows(SecurityException.class, () -> {
-            localService.createVirtualDisplay(
-                    builder.build(),
-                    mMockAppToken /* callback */,
-                    virtualDevice /* virtualDeviceToken */,
-                    mock(DisplayWindowPolicyController.class),
-                    PACKAGE_NAME);
-        });
-    }
-
-    /**
      * Tests that the virtual display is added to the default display group when created with
      * VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR using a virtual device.
      */
     @Test
     public void createAutoMirrorVirtualDisplay_addsDisplayToDefaultDisplayGroup() throws Exception {
-        mSetFlagsRule.enableFlags(Flags.FLAG_INTERACTIVE_SCREEN_MIRROR);
         DisplayManagerService displayManager = new DisplayManagerService(mContext, mBasicInjector);
         DisplayManagerInternal localService = displayManager.new LocalService();
         registerDefaultDisplays(displayManager);
@@ -1368,7 +1333,6 @@
      */
     @Test
     public void createAutoMirrorVirtualDisplay_mirrorsDefaultDisplay() throws Exception {
-        mSetFlagsRule.enableFlags(Flags.FLAG_INTERACTIVE_SCREEN_MIRROR);
         DisplayManagerService displayManager = new DisplayManagerService(mContext, mBasicInjector);
         DisplayManagerInternal localService = displayManager.new LocalService();
         registerDefaultDisplays(displayManager);
@@ -1400,7 +1364,6 @@
      */
     @Test
     public void createOwnContentOnlyVirtualDisplay_doesNotMirrorAnyDisplay() throws Exception {
-        mSetFlagsRule.enableFlags(Flags.FLAG_INTERACTIVE_SCREEN_MIRROR);
         DisplayManagerService displayManager = new DisplayManagerService(mContext, mBasicInjector);
         DisplayManagerInternal localService = displayManager.new LocalService();
         registerDefaultDisplays(displayManager);
@@ -1436,7 +1399,6 @@
      */
     @Test
     public void createAutoMirrorVirtualDisplay_flagAlwaysUnlockedNotSet() throws Exception {
-        mSetFlagsRule.enableFlags(Flags.FLAG_INTERACTIVE_SCREEN_MIRROR);
         DisplayManagerService displayManager = new DisplayManagerService(mContext, mBasicInjector);
         DisplayManagerInternal localService = displayManager.new LocalService();
         registerDefaultDisplays(displayManager);
@@ -1472,7 +1434,6 @@
      */
     @Test
     public void createAutoMirrorVirtualDisplay_flagPresentationNotSet() throws Exception {
-        mSetFlagsRule.enableFlags(Flags.FLAG_INTERACTIVE_SCREEN_MIRROR);
         DisplayManagerService displayManager = new DisplayManagerService(mContext, mBasicInjector);
         DisplayManagerInternal localService = displayManager.new LocalService();
         registerDefaultDisplays(displayManager);
diff --git a/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerControllerTest.java b/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerControllerTest.java
index 0bcc572..5840cb9 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerControllerTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerControllerTest.java
@@ -1621,16 +1621,21 @@
         advanceTime(1); // Run updatePowerState
 
         reset(mHolder.wakelockController);
+        when(mHolder.wakelockController
+                .acquireWakelock(WakelockController.WAKE_LOCK_OVERRIDE_DOZE_SCREEN_STATE))
+                .thenReturn(true);
         mHolder.dpc.overrideDozeScreenState(
                 supportedTargetState, Display.STATE_REASON_DEFAULT_POLICY);
-        advanceTime(1); // Run updatePowerState
 
         // Should get a wakelock to notify powermanager
-        verify(mHolder.wakelockController, atLeastOnce()).acquireWakelock(
-                eq(WakelockController.WAKE_LOCK_UNFINISHED_BUSINESS));
+        verify(mHolder.wakelockController).acquireWakelock(
+                eq(WakelockController.WAKE_LOCK_OVERRIDE_DOZE_SCREEN_STATE));
 
+        advanceTime(1); // Run updatePowerState
         verify(mHolder.displayPowerState)
                 .setScreenState(supportedTargetState, Display.STATE_REASON_DEFAULT_POLICY);
+        verify(mHolder.wakelockController).releaseWakelock(
+                eq(WakelockController.WAKE_LOCK_OVERRIDE_DOZE_SCREEN_STATE));
     }
 
     @Test
diff --git a/services/tests/displayservicetests/src/com/android/server/display/ExternalDisplayPolicyTest.java b/services/tests/displayservicetests/src/com/android/server/display/ExternalDisplayPolicyTest.java
index 82acaf8..f728168 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/ExternalDisplayPolicyTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/ExternalDisplayPolicyTest.java
@@ -258,6 +258,7 @@
 
         when(mMockedLogicalDisplay.isEnabledLocked()).thenReturn(false);
         mExternalDisplayPolicy.handleExternalDisplayConnectedLocked(mMockedLogicalDisplay);
+        mHandler.flush();
         verify(mMockedInjector, never()).sendExternalDisplayEventLocked(any(), anyInt());
         verify(mMockedDisplayNotificationManager, times(2))
                 .onHighTemperatureExternalDisplayNotAllowed();
diff --git a/services/tests/displayservicetests/src/com/android/server/display/WakelockControllerTest.java b/services/tests/displayservicetests/src/com/android/server/display/WakelockControllerTest.java
index c23d4b1..019b70e 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/WakelockControllerTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/WakelockControllerTest.java
@@ -64,6 +64,8 @@
                 "[" + DISPLAY_ID + "]prox negative");
         assertEquals(mWakelockController.getSuspendBlockerProxDebounceId(),
                 "[" + DISPLAY_ID + "]prox debounce");
+        assertEquals(mWakelockController.getSuspendBlockerOverrideDozeScreenState(),
+                "[" + DISPLAY_ID + "]override doze screen state");
     }
 
     @Test
@@ -162,6 +164,28 @@
     }
 
     @Test
+    public void acquireOverrideDozeScreenStateSuspendBlocker() throws Exception {
+        // Acquire the suspend blocker
+        verifyWakelockAcquisitionAndReaquisition(WakelockController
+                        .WAKE_LOCK_OVERRIDE_DOZE_SCREEN_STATE,
+                () -> mWakelockController.isOverrideDozeScreenStateAcquired());
+
+        // Verify acquire happened only once
+        verify(mDisplayPowerCallbacks, times(1))
+                .acquireSuspendBlocker(mWakelockController
+                        .getSuspendBlockerOverrideDozeScreenState());
+
+        // Release the suspend blocker
+        verifyWakelockReleaseAndRerelease(WakelockController.WAKE_LOCK_OVERRIDE_DOZE_SCREEN_STATE,
+                () -> mWakelockController.isOverrideDozeScreenStateAcquired());
+
+        // Verify suspend blocker was released only once
+        verify(mDisplayPowerCallbacks, times(1))
+                .releaseSuspendBlocker(mWakelockController
+                        .getSuspendBlockerOverrideDozeScreenState());
+    }
+
+    @Test
     public void proximityPositiveRunnableWorksAsExpected() {
         // Acquire the suspend blocker twice
         assertTrue(mWakelockController.acquireWakelock(
diff --git a/services/tests/displayservicetests/src/com/android/server/display/notifications/DisplayNotificationManagerTest.java b/services/tests/displayservicetests/src/com/android/server/display/notifications/DisplayNotificationManagerTest.java
index d6c8ceb..97c12bb 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/notifications/DisplayNotificationManagerTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/notifications/DisplayNotificationManagerTest.java
@@ -30,6 +30,7 @@
 
 import android.app.Notification;
 import android.app.NotificationManager;
+import android.os.UserHandle;
 
 import androidx.test.core.app.ApplicationProvider;
 import androidx.test.filters.SmallTest;
@@ -70,6 +71,8 @@
     private ArgumentCaptor<Integer> mNotifyNoteIdCaptor;
     @Captor
     private ArgumentCaptor<Notification> mNotifyAsUserNotificationCaptor;
+    @Captor
+    private ArgumentCaptor<UserHandle> mNotifyAsUserCaptor;
 
     /** Setup tests. */
     @Before
@@ -127,7 +130,8 @@
         dnm.onDisplayPortLinkTrainingFailure();
         dnm.onCableNotCapableDisplayPort();
         dnm.onHighTemperatureExternalDisplayNotAllowed();
-        verify(mMockedNotificationManager, never()).notify(anyString(), anyInt(), any());
+        verify(mMockedNotificationManager, never()).notifyAsUser(anyString(), anyInt(), any(),
+                any());
     }
 
     @Test
@@ -175,10 +179,11 @@
     }
 
     private void assertExpectedNotification() {
-        verify(mMockedNotificationManager).notify(
+        verify(mMockedNotificationManager).notifyAsUser(
                 mNotifyTagCaptor.capture(),
                 mNotifyNoteIdCaptor.capture(),
-                mNotifyAsUserNotificationCaptor.capture());
+                mNotifyAsUserNotificationCaptor.capture(),
+                mNotifyAsUserCaptor.capture());
         assertThat(mNotifyTagCaptor.getValue()).isEqualTo("DisplayNotificationManager");
         assertThat((int) mNotifyNoteIdCaptor.getValue()).isEqualTo(1);
         final var notification = mNotifyAsUserNotificationCaptor.getValue();
@@ -188,5 +193,7 @@
         assertThat(notification.flags & FLAG_ONGOING_EVENT).isEqualTo(0);
         assertThat(notification.when).isEqualTo(0);
         assertThat(notification.getTimeoutAfter()).isEqualTo(30000L);
+        final var user = mNotifyAsUserCaptor.getValue();
+        assertThat(user).isEqualTo(UserHandle.CURRENT);
     }
 }
diff --git a/services/tests/dreamservicetests/src/com/android/server/dreams/DreamServiceTest.java b/services/tests/dreamservicetests/src/com/android/server/dreams/DreamServiceTest.java
index b4e1abf..265b74d 100644
--- a/services/tests/dreamservicetests/src/com/android/server/dreams/DreamServiceTest.java
+++ b/services/tests/dreamservicetests/src/com/android/server/dreams/DreamServiceTest.java
@@ -23,6 +23,7 @@
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
@@ -215,4 +216,39 @@
         // Ensure service does not crash from only receiving up event.
         environment.dispatchKeyEvent(new KeyEvent(KeyEvent.ACTION_UP, KeyEvent.KEYCODE_SPACE));
     }
+
+    @Test
+    @EnableFlags(Flags.FLAG_DREAM_HANDLES_BEING_OBSCURED)
+    public void testComeToFront() throws Exception {
+        TestDreamEnvironment environment = new TestDreamEnvironment.Builder(mTestableLooper)
+                .setDreamOverlayPresent(true)
+                .build();
+        environment.advance(TestDreamEnvironment.DREAM_STATE_STARTED);
+
+        // Call comeToFront through binder.
+        environment.resetClientInvocations();
+        environment.comeToFront();
+        mTestableLooper.processAllMessages();
+
+        // Overlay client receives call.
+        verify(environment.getDreamOverlayClient()).comeToFront();
+    }
+
+    @Test
+    @EnableFlags(Flags.FLAG_DREAM_HANDLES_BEING_OBSCURED)
+    public void testComeToFront_noOverlay() throws Exception {
+        // Dream environment with no overlay present
+        TestDreamEnvironment environment = new TestDreamEnvironment.Builder(mTestableLooper)
+                .setDreamOverlayPresent(false)
+                .build();
+        environment.advance(TestDreamEnvironment.DREAM_STATE_STARTED);
+
+        // Call comeToFront through binder.
+        environment.resetClientInvocations();
+        environment.comeToFront();
+        mTestableLooper.processAllMessages();
+
+        // Overlay client receives call.
+        verify(environment.getDreamOverlayClient(), never()).comeToFront();
+    }
 }
diff --git a/services/tests/dreamservicetests/src/com/android/server/dreams/TestDreamEnvironment.java b/services/tests/dreamservicetests/src/com/android/server/dreams/TestDreamEnvironment.java
index e2b93ae..43aa7fe 100644
--- a/services/tests/dreamservicetests/src/com/android/server/dreams/TestDreamEnvironment.java
+++ b/services/tests/dreamservicetests/src/com/android/server/dreams/TestDreamEnvironment.java
@@ -398,10 +398,14 @@
         mService.dispatchKeyEvent(event);
     }
 
-    private void wakeDream() throws RemoteException {
+    private void wakeDream() {
         mService.wakeUp();
     }
 
+    void comeToFront() throws RemoteException {
+        mDreamServiceWrapper.comeToFront();
+    }
+
     /**
      * Retrieves the dream overlay callback.
      */
diff --git a/services/tests/servicestests/Android.bp b/services/tests/servicestests/Android.bp
index a86289b..701c350 100644
--- a/services/tests/servicestests/Android.bp
+++ b/services/tests/servicestests/Android.bp
@@ -272,6 +272,10 @@
         "$(location soong_zip) -o $(out) -C $(genDir)/res -D $(genDir)/res",
 }
 
+FLAKY = [
+    "androidx.test.filters.FlakyTest",
+]
+
 FLAKY_AND_IGNORED = [
     "androidx.test.filters.FlakyTest",
     "org.junit.Ignore",
@@ -328,7 +332,7 @@
     base: "FrameworksServicesTests",
     test_suites: ["device-tests"],
     include_filters: ["com.android.server.recoverysystem."],
-    exclude_annotations: ["androidx.test.filters.FlakyTest"],
+    exclude_annotations: FLAKY,
 }
 
 // server pm TEST_MAPPING
@@ -357,3 +361,319 @@
     test_suites: ["device-tests"],
     include_filters: ["com.android.server.os."],
 }
+
+test_module_config {
+    name: "FrameworksServicesTests_presubmit",
+    base: "FrameworksServicesTests",
+    test_suites: ["device-tests"],
+    include_annotations: ["android.platform.test.annotations.Presubmit"],
+    exclude_annotations: FLAKY_AND_IGNORED,
+}
+
+test_module_config {
+    name: "FrameworksServicesTests_com_android_server_job_Presubmit",
+    base: "FrameworksServicesTests",
+    test_suites: ["device-tests"],
+    include_filters: ["com.android.server.job"],
+    exclude_annotations: [
+        "androidx.test.filters.LargeTest",
+        "androidx.test.filters.FlakyTest",
+    ],
+}
+
+test_module_config {
+    name: "FrameworksServicesTests_com_android_server_job",
+    base: "FrameworksServicesTests",
+    test_suites: ["device-tests"],
+    include_filters: ["com.android.server.job"],
+}
+
+test_module_config {
+    name: "FrameworksServicesTests_com_android_server_tare_Presubmit",
+    base: "FrameworksServicesTests",
+    test_suites: ["device-tests"],
+    include_filters: ["com.android.server.tare"],
+    exclude_annotations: FLAKY,
+}
+
+test_module_config {
+    name: "FrameworksServicesTests_com_android_server_tare",
+    base: "FrameworksServicesTests",
+    test_suites: ["device-tests"],
+    include_filters: ["com.android.server.tare"],
+}
+
+test_module_config {
+    name: "FrameworksServicesTests_com_android_server_usage_Presubmit",
+    base: "FrameworksServicesTests",
+    test_suites: ["device-tests"],
+    include_filters: ["com.android.server.usage"],
+    exclude_annotations: FLAKY,
+}
+
+test_module_config {
+    name: "FrameworksServicesTests_com_android_server_usage",
+    base: "FrameworksServicesTests",
+    test_suites: ["device-tests"],
+    include_filters: ["com.android.server.usage"],
+}
+
+test_module_config {
+    name: "FrameworksServicesTests_battery_stats",
+    base: "FrameworksServicesTests",
+    test_suites: ["device-tests"],
+    include_filters: ["com.android.server.am.BatteryStatsServiceTest"],
+}
+
+test_module_config {
+    name: "FrameworksServicesTests_accessibility_Presubmit",
+    base: "FrameworksServicesTests",
+    test_suites: ["device-tests"],
+    include_filters: ["com.android.server.accessibility"],
+    exclude_annotations: FLAKY,
+}
+
+test_module_config {
+    name: "FrameworksServicesTests_accessibility",
+    base: "FrameworksServicesTests",
+    test_suites: ["device-tests"],
+    include_filters: ["com.android.server.accessibility"],
+}
+
+test_module_config {
+    name: "FrameworksServicesTests_binary_transparency",
+    base: "FrameworksServicesTests",
+    test_suites: ["device-tests"],
+    include_filters: ["com.android.server.BinaryTransparencyServiceTest"],
+}
+
+test_module_config {
+    name: "FrameworksServicesTests_pinner_service",
+    base: "FrameworksServicesTests",
+    test_suites: ["device-tests"],
+    include_filters: ["com.android.server.PinnerServiceTest"],
+    exclude_annotations: ["org.junit.Ignore"],
+}
+
+test_module_config {
+    name: "FrameworksServicesTests_android_server_am_Presubmit",
+    base: "FrameworksServicesTests",
+    test_suites: ["device-tests"],
+    include_filters: ["com.android.server.am."],
+    include_annotations: ["android.platform.test.annotations.Presubmit"],
+    exclude_annotations: FLAKY,
+}
+
+test_module_config {
+    name: "FrameworksServicesTests_android_server_am",
+    base: "FrameworksServicesTests",
+    test_suites: ["device-tests"],
+    include_filters: ["com.android.server.am."],
+}
+
+test_module_config {
+    name: "FrameworksServicesTests_android_server_appop",
+    base: "FrameworksServicesTests",
+    test_suites: ["device-tests"],
+    include_filters: ["com.android.server.appop"],
+}
+
+test_module_config {
+    name: "FrameworksServicesTests_android_server_audio",
+    base: "FrameworksServicesTests",
+    test_suites: ["device-tests"],
+    include_filters: ["com.android.server.audio"],
+    include_annotations: ["android.platform.test.annotations.Presubmit"],
+    exclude_annotations: FLAKY_AND_IGNORED,
+}
+
+test_module_config {
+    name: "FrameworksServicesTests_android_server_compat",
+    base: "FrameworksServicesTests",
+    test_suites: ["device-tests"],
+    include_filters: ["com.android.server.compat"],
+}
+
+test_module_config {
+    name: "FrameworksServicesTests_android_server_hdmi_Presubmit",
+    base: "FrameworksServicesTests",
+    test_suites: ["device-tests"],
+    include_filters: ["com.android.server.hdmi"],
+    include_annotations: ["android.platform.test.annotations.Presubmit"],
+    exclude_annotations: FLAKY_AND_IGNORED,
+}
+
+test_module_config {
+    name: "FrameworksServicesTests_android_server_hdmi",
+    base: "FrameworksServicesTests",
+    test_suites: ["device-tests"],
+    include_filters: ["com.android.server.hdmi"],
+    exclude_annotations: ["org.junit.Ignore"],
+}
+
+test_module_config {
+    name: "FrameworksServicesTests_android_server_integrity",
+    base: "FrameworksServicesTests",
+    test_suites: ["device-tests"],
+    include_filters: ["com.android.server.integrity."],
+}
+
+test_module_config {
+    name: "FrameworksServicesTests_android_server_lights",
+    base: "FrameworksServicesTests",
+    test_suites: ["device-tests"],
+    include_filters: ["com.android.server.lights"],
+    exclude_annotations: FLAKY,
+}
+
+test_module_config {
+    name: "FrameworksServicesTests_android_server_locales",
+    base: "FrameworksServicesTests",
+    test_suites: ["device-tests"],
+    include_filters: ["com.android.server.locales."],
+}
+
+test_module_config {
+    name: "FrameworksServicesTests_android_server_location_contexthub_Presubmit",
+    base: "FrameworksServicesTests",
+    test_suites: ["device-tests"],
+    include_filters: ["com.android.server.location.contexthub."],
+    include_annotations: ["android.platform.test.annotations.Presubmit"],
+    exclude_annotations: FLAKY_AND_IGNORED,
+}
+
+test_module_config {
+    name: "FrameworksServicesTests_android_server_locksettings",
+    base: "FrameworksServicesTests",
+    test_suites: ["device-tests"],
+    include_filters: ["com.android.server.locksettings."],
+    exclude_annotations: FLAKY,
+}
+
+test_module_config {
+    name: "FrameworksServicesTests_android_server_logcat_Presubmit",
+    base: "FrameworksServicesTests",
+    test_suites: ["device-tests"],
+    include_filters: ["com.android.server.logcat"],
+    exclude_annotations: FLAKY,
+}
+
+test_module_config {
+    name: "FrameworksServicesTests_android_server_logcat",
+    base: "FrameworksServicesTests",
+    test_suites: ["device-tests"],
+    include_filters: ["com.android.server.logcat"],
+}
+
+test_module_config {
+    name: "FrameworksServicesTests_android_server_net_Presubmit",
+    base: "FrameworksServicesTests",
+    test_suites: ["device-tests"],
+    include_filters: ["com.android.server.net."],
+    include_annotations: ["android.platform.test.annotations.Presubmit"],
+    exclude_annotations: FLAKY,
+}
+
+test_module_config {
+    name: "FrameworksServicesTests_android_server_om",
+    base: "FrameworksServicesTests",
+    test_suites: ["device-tests"],
+    include_filters: ["com.android.server.om."],
+}
+
+test_module_config {
+    name: "FrameworksServicesTests_android_server_pdb",
+    base: "FrameworksServicesTests",
+    test_suites: ["device-tests"],
+    include_filters: ["com.android.server.pdb.PersistentDataBlockServiceTest"],
+}
+
+test_module_config {
+    name: "FrameworksServicesTests_android_server_pm_dex",
+    base: "FrameworksServicesTests",
+    test_suites: ["device-tests"],
+    include_filters: ["com.android.server.pm.dex"],
+}
+
+test_module_config {
+    name: "FrameworksServicesTests_android_server_policy_Presubmit",
+    base: "FrameworksServicesTests",
+    test_suites: ["device-tests"],
+    include_filters: ["com.android.server.policy."],
+    include_annotations: ["android.platform.test.annotations.Presubmit"],
+    exclude_annotations: FLAKY,
+}
+
+test_module_config {
+    name: "FrameworksServicesTests_android_server_policy",
+    base: "FrameworksServicesTests",
+    test_suites: ["device-tests"],
+    include_filters: ["com.android.server.policy."],
+}
+
+test_module_config {
+    name: "FrameworksServicesTests_android_server_power",
+    base: "FrameworksServicesTests",
+    test_suites: ["device-tests"],
+    include_filters: ["com.android.server.power"],
+}
+
+test_module_config {
+    name: "FrameworksServicesTests_android_server_power_hint",
+    base: "FrameworksServicesTests",
+    test_suites: ["device-tests"],
+    include_filters: ["com.android.server.power.hint"],
+    exclude_annotations: FLAKY,
+}
+
+test_module_config {
+    name: "FrameworksServicesTests_android_server_powerstats",
+    base: "FrameworksServicesTests",
+    test_suites: ["device-tests"],
+    include_filters: ["com.android.server.powerstats"],
+}
+
+test_module_config {
+    name: "FrameworksServicesTests_android_server_rollback",
+    base: "FrameworksServicesTests",
+    test_suites: ["device-tests"],
+    include_filters: ["com.android.server.rollback"],
+}
+
+test_module_config {
+    name: "FrameworksServicesTests_android_server_uri",
+    base: "FrameworksServicesTests",
+    test_suites: ["device-tests"],
+    include_filters: ["com.android.server.uri."],
+}
+
+test_module_config {
+    name: "FrameworksServicesTests_com_android_server_location_contexthub",
+    base: "FrameworksServicesTests",
+    test_suites: ["device-tests"],
+    include_filters: ["com.android.server.location.contexthub."],
+    include_annotations: ["android.platform.test.annotations.Postsubmit"],
+    exclude_annotations: FLAKY_AND_IGNORED,
+}
+
+test_module_config {
+    name: "FrameworksServicesTests_android_server_usage",
+    base: "FrameworksServicesTests",
+    test_suites: ["device-tests"],
+    include_filters: ["com.android.server.usage"],
+    exclude_filters: ["com.android.server.usage.StorageStatsServiceTest"],
+}
+
+test_module_config {
+    name: "FrameworksServicesTests_android_server_soundtrigger_middleware",
+    base: "FrameworksServicesTests",
+    test_suites: ["device-tests"],
+    include_filters: ["com.android.server.soundtrigger_middleware"],
+}
+
+test_module_config {
+    name: "FrameworksServicesTests_android_server_input",
+    base: "FrameworksServicesTests",
+    test_suites: ["device-tests"],
+    include_filters: ["com.android.server.input"],
+}
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/magnification/MagnificationGestureHandlerTest.java b/services/tests/servicestests/src/com/android/server/accessibility/magnification/MagnificationGestureHandlerTest.java
index 3931580..d80a1f0 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/magnification/MagnificationGestureHandlerTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/magnification/MagnificationGestureHandlerTest.java
@@ -18,13 +18,20 @@
 
 import static android.view.MotionEvent.ACTION_CANCEL;
 import static android.view.MotionEvent.ACTION_DOWN;
+import static android.view.MotionEvent.ACTION_HOVER_MOVE;
 import static android.view.MotionEvent.ACTION_UP;
 
+import static junit.framework.Assert.assertFalse;
+
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.verify;
 import static org.testng.AssertJUnit.assertTrue;
 
 import android.annotation.NonNull;
+import android.platform.test.annotations.RequiresFlagsDisabled;
+import android.platform.test.annotations.RequiresFlagsEnabled;
+import android.platform.test.flag.junit.CheckFlagsRule;
+import android.platform.test.flag.junit.DeviceFlagsValueProvider;
 import android.provider.Settings;
 import android.view.InputDevice;
 import android.view.MotionEvent;
@@ -32,8 +39,10 @@
 import androidx.test.runner.AndroidJUnit4;
 
 import com.android.server.accessibility.AccessibilityTraceManager;
+import com.android.server.accessibility.Flags;
 
 import org.junit.Before;
+import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.Mock;
@@ -45,6 +54,9 @@
 @RunWith(AndroidJUnit4.class)
 public class MagnificationGestureHandlerTest {
 
+    @Rule
+    public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule();
+
     private TestMagnificationGestureHandler mMgh;
     private static final int DISPLAY_0 = 0;
     private static final int FULLSCREEN_MODE =
@@ -81,6 +93,66 @@
     }
 
     @Test
+    @RequiresFlagsEnabled(Flags.FLAG_ENABLE_MAGNIFICATION_FOLLOWS_MOUSE)
+    public void onMotionEvent_isFromMouse_handleMouseOrStylusEvent() {
+        final MotionEvent mouseEvent = MotionEvent.obtain(0, 0, ACTION_HOVER_MOVE, 0, 0, 0);
+        mouseEvent.setSource(InputDevice.SOURCE_MOUSE);
+
+        mMgh.onMotionEvent(mouseEvent, mouseEvent, /* policyFlags= */ 0);
+
+        try {
+            assertTrue(mMgh.mIsHandleMouseOrStylusEventCalled);
+        } finally {
+            mouseEvent.recycle();
+        }
+    }
+
+    @Test
+    @RequiresFlagsEnabled(Flags.FLAG_ENABLE_MAGNIFICATION_FOLLOWS_MOUSE)
+    public void onMotionEvent_isFromStylus_handleMouseOrStylusEvent() {
+        final MotionEvent stylusEvent = MotionEvent.obtain(0, 0, ACTION_HOVER_MOVE, 0, 0, 0);
+        stylusEvent.setSource(InputDevice.SOURCE_STYLUS);
+
+        mMgh.onMotionEvent(stylusEvent, stylusEvent, /* policyFlags= */ 0);
+
+        try {
+            assertTrue(mMgh.mIsHandleMouseOrStylusEventCalled);
+        } finally {
+            stylusEvent.recycle();
+        }
+    }
+
+    @Test
+    @RequiresFlagsDisabled(Flags.FLAG_ENABLE_MAGNIFICATION_FOLLOWS_MOUSE)
+    public void onMotionEvent_isFromMouse_handleMouseOrStylusEventNotCalled() {
+        final MotionEvent mouseEvent = MotionEvent.obtain(0, 0, ACTION_HOVER_MOVE, 0, 0, 0);
+        mouseEvent.setSource(InputDevice.SOURCE_MOUSE);
+
+        mMgh.onMotionEvent(mouseEvent, mouseEvent, /* policyFlags= */ 0);
+
+        try {
+            assertFalse(mMgh.mIsHandleMouseOrStylusEventCalled);
+        } finally {
+            mouseEvent.recycle();
+        }
+    }
+
+    @Test
+    @RequiresFlagsDisabled(Flags.FLAG_ENABLE_MAGNIFICATION_FOLLOWS_MOUSE)
+    public void onMotionEvent_isFromStylus_handleMouseOrStylusEventNotCalled() {
+        final MotionEvent stylusEvent = MotionEvent.obtain(0, 0, ACTION_HOVER_MOVE, 0, 0, 0);
+        stylusEvent.setSource(InputDevice.SOURCE_STYLUS);
+
+        mMgh.onMotionEvent(stylusEvent, stylusEvent, /* policyFlags= */ 0);
+
+        try {
+            assertFalse(mMgh.mIsHandleMouseOrStylusEventCalled);
+        } finally {
+            stylusEvent.recycle();
+        }
+    }
+
+    @Test
     public void onMotionEvent_downEvent_handleInteractionStart() {
         final MotionEvent downEvent = MotionEvent.obtain(0, 0, ACTION_DOWN, 0, 0, 0);
         downEvent.setSource(InputDevice.SOURCE_TOUCHSCREEN);
@@ -125,6 +197,7 @@
     private static class TestMagnificationGestureHandler extends MagnificationGestureHandler {
 
         boolean mIsInternalMethodCalled = false;
+        boolean mIsHandleMouseOrStylusEventCalled = false;
 
         TestMagnificationGestureHandler(int displayId, boolean detectSingleFingerTripleTap,
                 boolean detectTwoFingerTripleTap,
@@ -135,6 +208,11 @@
         }
 
         @Override
+        void handleMouseOrStylusEvent(MotionEvent event, MotionEvent rawEvent, int policyFlags) {
+            mIsHandleMouseOrStylusEventCalled = true;
+        }
+
+        @Override
         void onMotionEventInternal(MotionEvent event, MotionEvent rawEvent, int policyFlags) {
             mIsInternalMethodCalled = true;
         }
diff --git a/services/tests/servicestests/src/com/android/server/autofill/PresentationEventLoggerTest.java b/services/tests/servicestests/src/com/android/server/autofill/PresentationEventLoggerTest.java
new file mode 100644
index 0000000..75258f0
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/autofill/PresentationEventLoggerTest.java
@@ -0,0 +1,132 @@
+/*
+ * 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.autofill;
+
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.view.autofill.AutofillId;
+import android.view.autofill.AutofillValue;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+@RunWith(JUnit4.class)
+public class PresentationEventLoggerTest {
+
+    @Test
+    public void testViewEntered() {
+        PresentationStatsEventLogger pEventLogger =
+                PresentationStatsEventLogger.createPresentationLog(1, 1, 1);
+
+        AutofillId id = new AutofillId(13);
+        AutofillValue initialValue = AutofillValue.forText("hello");
+        AutofillValue lastValue = AutofillValue.forText("hello world");
+        ViewState vState = new ViewState(id, null, 0, false);
+
+        pEventLogger.startNewEvent();
+        pEventLogger.maybeSetFocusedId(id);
+        pEventLogger.onFieldTextUpdated(vState, initialValue);
+        pEventLogger.onFieldTextUpdated(vState, lastValue);
+
+        PresentationStatsEventLogger.PresentationStatsEventInternal event =
+                pEventLogger.getInternalEvent().get();
+        assertThat(event).isNotNull();
+        assertThat(event.mFieldFirstLength).isEqualTo(initialValue.getTextValue().length());
+        assertThat(event.mFieldLastLength).isEqualTo(lastValue.getTextValue().length());
+        assertThat(event.mFieldModifiedFirstTimestampMs).isNotEqualTo(-1);
+        assertThat(event.mFieldModifiedLastTimestampMs).isNotEqualTo(-1);
+    }
+
+    @Test
+    public void testViewAutofilled() {
+        PresentationStatsEventLogger pEventLogger =
+                PresentationStatsEventLogger.createPresentationLog(1, 1, 1);
+
+        String newTextValue = "hello";
+        AutofillValue value = AutofillValue.forText(newTextValue);
+        AutofillId id = new AutofillId(13);
+        ViewState vState = new ViewState(id, null, ViewState.STATE_AUTOFILLED, false);
+
+        pEventLogger.startNewEvent();
+        pEventLogger.maybeSetFocusedId(id);
+        pEventLogger.onFieldTextUpdated(vState, value);
+
+        PresentationStatsEventLogger.PresentationStatsEventInternal event =
+                pEventLogger.getInternalEvent().get();
+        assertThat(event).isNotNull();
+        assertThat(event.mFieldFirstLength).isEqualTo(newTextValue.length());
+        assertThat(event.mFieldLastLength).isEqualTo(newTextValue.length());
+        assertThat(event.mAutofilledTimestampMs).isNotEqualTo(-1);
+        assertThat(event.mFieldModifiedFirstTimestampMs).isEqualTo(-1);
+        assertThat(event.mFieldModifiedLastTimestampMs).isEqualTo(-1);
+    }
+
+    @Test
+    public void testModifiedOnDifferentView() {
+        PresentationStatsEventLogger pEventLogger =
+                PresentationStatsEventLogger.createPresentationLog(1, 1, 1);
+
+        String newTextValue = "hello";
+        AutofillValue value = AutofillValue.forText(newTextValue);
+        AutofillId id = new AutofillId(13);
+        ViewState vState = new ViewState(id, null, ViewState.STATE_AUTOFILLED, false);
+
+        pEventLogger.startNewEvent();
+        pEventLogger.onFieldTextUpdated(vState, value);
+
+        PresentationStatsEventLogger.PresentationStatsEventInternal event =
+                pEventLogger.getInternalEvent().get();
+        assertThat(event).isNotNull();
+        assertThat(event.mFieldFirstLength).isEqualTo(-1);
+        assertThat(event.mFieldLastLength).isEqualTo(-1);
+        assertThat(event.mFieldModifiedFirstTimestampMs).isEqualTo(-1);
+        assertThat(event.mFieldModifiedLastTimestampMs).isEqualTo(-1);
+        assertThat(event.mAutofilledTimestampMs).isEqualTo(-1);
+    }
+
+    @Test
+    public void testSetCountShown() {
+        PresentationStatsEventLogger pEventLogger =
+                PresentationStatsEventLogger.createPresentationLog(1, 1, 1);
+
+        pEventLogger.startNewEvent();
+        pEventLogger.logWhenDatasetShown(7);
+
+        PresentationStatsEventLogger.PresentationStatsEventInternal event =
+                pEventLogger.getInternalEvent().get();
+        assertThat(event).isNotNull();
+        assertThat(event.mCountShown).isEqualTo(7);
+        assertThat(event.mNoPresentationReason)
+                .isEqualTo(PresentationStatsEventLogger.NOT_SHOWN_REASON_ANY_SHOWN);
+    }
+
+    @Test
+    public void testFillDialogShownThenInline() {
+        PresentationStatsEventLogger pEventLogger =
+                PresentationStatsEventLogger.createPresentationLog(1, 1, 1);
+
+        pEventLogger.startNewEvent();
+        pEventLogger.maybeSetDisplayPresentationType(3);
+        pEventLogger.maybeSetDisplayPresentationType(2);
+
+        PresentationStatsEventLogger.PresentationStatsEventInternal event =
+                pEventLogger.getInternalEvent().get();
+        assertThat(event).isNotNull();
+        assertThat(event.mDisplayPresentationType).isEqualTo(3);
+    }
+}
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/BiometricServiceTest.java b/services/tests/servicestests/src/com/android/server/biometrics/BiometricServiceTest.java
index a4222ff..d2961bc 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/BiometricServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/BiometricServiceTest.java
@@ -79,6 +79,7 @@
 import android.os.RemoteException;
 import android.os.UserManager;
 import android.platform.test.annotations.Presubmit;
+import android.platform.test.annotations.RequiresFlagsDisabled;
 import android.platform.test.annotations.RequiresFlagsEnabled;
 import android.platform.test.flag.junit.CheckFlagsRule;
 import android.platform.test.flag.junit.DeviceFlagsValueProvider;
@@ -1488,15 +1489,30 @@
     }
 
     @Test
+    @RequiresFlagsDisabled(Flags.FLAG_MANDATORY_BIOMETRICS)
     public void testCanAuthenticate_whenLockoutTimed() throws Exception {
         testCanAuthenticate_whenLockedOut(LockoutTracker.LOCKOUT_TIMED);
     }
 
     @Test
+    @RequiresFlagsDisabled(Flags.FLAG_MANDATORY_BIOMETRICS)
     public void testCanAuthenticate_whenLockoutPermanent() throws Exception {
         testCanAuthenticate_whenLockedOut(LockoutTracker.LOCKOUT_PERMANENT);
     }
 
+    @Test
+    @RequiresFlagsEnabled(Flags.FLAG_MANDATORY_BIOMETRICS)
+    public void testCanAuthenticate_whenLockoutTimed_returnsLockoutError() throws Exception {
+        testCanAuthenticate_whenLockedOut_returnLockoutError(LockoutTracker.LOCKOUT_TIMED);
+    }
+
+    @Test
+    @RequiresFlagsEnabled(Flags.FLAG_MANDATORY_BIOMETRICS)
+    public void testCanAuthenticate_whenLockoutPermanent_returnsLockoutError() throws Exception {
+        testCanAuthenticate_whenLockedOut_returnLockoutError(LockoutTracker.LOCKOUT_PERMANENT);
+    }
+
+    @RequiresFlagsDisabled(Flags.FLAG_MANDATORY_BIOMETRICS)
     private void testCanAuthenticate_whenLockedOut(@LockoutTracker.LockoutMode int lockoutMode)
             throws Exception {
         // When only biometric is requested, and sensor is strong enough
@@ -1510,6 +1526,21 @@
                 invokeCanAuthenticate(mBiometricService, Authenticators.BIOMETRIC_STRONG));
     }
 
+    @RequiresFlagsEnabled(Flags.FLAG_MANDATORY_BIOMETRICS)
+    private void testCanAuthenticate_whenLockedOut_returnLockoutError(
+            @LockoutTracker.LockoutMode int lockoutMode)
+            throws Exception {
+        // When only biometric is requested, and sensor is strong enough
+        setupAuthForOnly(TYPE_FINGERPRINT, Authenticators.BIOMETRIC_STRONG);
+
+        when(mFingerprintAuthenticator.getLockoutModeForUser(anyInt()))
+                .thenReturn(lockoutMode);
+
+        // Lockout is not considered an error for BiometricManager#canAuthenticate
+        assertEquals(BiometricManager.BIOMETRIC_ERROR_LOCKOUT,
+                invokeCanAuthenticate(mBiometricService, Authenticators.BIOMETRIC_STRONG));
+    }
+
     @Test
     @RequiresFlagsEnabled(Flags.FLAG_MANDATORY_BIOMETRICS)
     public void testCanAuthenticate_whenMandatoryBiometricsRequested()
@@ -1529,7 +1560,7 @@
 
         when(mTrustManager.isInSignificantPlace()).thenReturn(true);
 
-        assertEquals(BiometricManager.BIOMETRIC_ERROR_HW_UNAVAILABLE,
+        assertEquals(BiometricManager.BIOMETRIC_ERROR_MANDATORY_NOT_ACTIVE,
                 invokeCanAuthenticate(mBiometricService, Authenticators.MANDATORY_BIOMETRICS));
     }
 
@@ -1572,7 +1603,7 @@
 
         setupAuthForOnly(TYPE_CREDENTIAL, Authenticators.DEVICE_CREDENTIAL);
 
-        assertEquals(BiometricManager.BIOMETRIC_ERROR_HW_UNAVAILABLE,
+        assertEquals(BiometricManager.BIOMETRIC_ERROR_NO_HARDWARE,
                 invokeCanAuthenticate(mBiometricService, Authenticators.MANDATORY_BIOMETRICS));
 
         when(mTrustManager.isInSignificantPlace()).thenReturn(true);
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/PreAuthInfoTest.java b/services/tests/servicestests/src/com/android/server/biometrics/PreAuthInfoTest.java
index 240da9f..4c3a233 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/PreAuthInfoTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/PreAuthInfoTest.java
@@ -231,7 +231,7 @@
 
     @Test
     @RequiresFlagsEnabled(Flags.FLAG_MANDATORY_BIOMETRICS)
-    public void testMandatoryBiometricsStatus_whenRequirementsNotSatisfiedAndSensorAvailable()
+    public void testMandatoryBiometricsAndStrongBiometricsStatus_whenRequirementsNotSatisfied()
             throws Exception {
         when(mTrustManager.isInSignificantPlace()).thenReturn(true);
 
@@ -246,6 +246,24 @@
         assertThat(preAuthInfo.eligibleSensors).hasSize(1);
     }
 
+    @Test
+    @RequiresFlagsEnabled(Flags.FLAG_MANDATORY_BIOMETRICS)
+    public void testMandatoryBiometricsStatus_whenRequirementsNotSatisfiedAndSensorAvailable()
+            throws Exception {
+        when(mTrustManager.isInSignificantPlace()).thenReturn(true);
+
+        final BiometricSensor sensor = getFaceSensor();
+        final PromptInfo promptInfo = new PromptInfo();
+        promptInfo.setAuthenticators(BiometricManager.Authenticators.MANDATORY_BIOMETRICS);
+        final PreAuthInfo preAuthInfo = PreAuthInfo.create(mTrustManager, mDevicePolicyManager,
+                mSettingObserver, List.of(sensor), 0 /* userId */, promptInfo, TEST_PACKAGE_NAME,
+                false /* checkDevicePolicyManager */, mContext, mBiometricCameraManager);
+
+        assertThat(preAuthInfo.getCanAuthenticateResult()).isEqualTo(
+                BiometricManager.BIOMETRIC_ERROR_MANDATORY_NOT_ACTIVE);
+        assertThat(preAuthInfo.eligibleSensors).hasSize(0);
+    }
+
     private BiometricSensor getFingerprintSensor() {
         BiometricSensor sensor = new BiometricSensor(mContext, SENSOR_ID_FINGERPRINT,
                 TYPE_FINGERPRINT, BiometricManager.Authenticators.BIOMETRIC_STRONG,
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/UtilsTest.java b/services/tests/servicestests/src/com/android/server/biometrics/UtilsTest.java
index cb75e1a..14cb22d 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/UtilsTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/UtilsTest.java
@@ -26,16 +26,25 @@
 import android.hardware.biometrics.BiometricConstants;
 import android.hardware.biometrics.BiometricManager;
 import android.hardware.biometrics.BiometricPrompt;
+import android.hardware.biometrics.Flags;
 import android.hardware.biometrics.PromptInfo;
 import android.platform.test.annotations.Presubmit;
+import android.platform.test.annotations.RequiresFlagsDisabled;
+import android.platform.test.annotations.RequiresFlagsEnabled;
+import android.platform.test.flag.junit.CheckFlagsRule;
+import android.platform.test.flag.junit.DeviceFlagsValueProvider;
 
 import androidx.test.filters.SmallTest;
 
+import org.junit.Rule;
 import org.junit.Test;
 
 @Presubmit
 @SmallTest
 public class UtilsTest {
+    @Rule
+    public final CheckFlagsRule mCheckFlagsRule =
+            DeviceFlagsValueProvider.createCheckFlagsRule();
 
     @Test
     public void testCombineAuthenticatorBundles_withKeyDeviceCredential_andKeyAuthenticators() {
@@ -215,7 +224,8 @@
     }
 
     @Test
-    public void testBiometricConstantsConversion() {
+    @RequiresFlagsDisabled(Flags.FLAG_MANDATORY_BIOMETRICS)
+    public void testBiometricConstantsConversionLegacy() {
         final int[][] testCases = {
                 {BiometricConstants.BIOMETRIC_SUCCESS,
                         BiometricManager.BIOMETRIC_SUCCESS},
@@ -240,6 +250,34 @@
     }
 
     @Test
+    @RequiresFlagsEnabled(Flags.FLAG_MANDATORY_BIOMETRICS)
+    public void testBiometricConstantsConversion() {
+        final int[][] testCases = {
+                {BiometricConstants.BIOMETRIC_SUCCESS,
+                        BiometricManager.BIOMETRIC_SUCCESS},
+                {BiometricConstants.BIOMETRIC_ERROR_NO_BIOMETRICS,
+                        BiometricManager.BIOMETRIC_ERROR_NONE_ENROLLED},
+                {BiometricConstants.BIOMETRIC_ERROR_NO_DEVICE_CREDENTIAL,
+                        BiometricManager.BIOMETRIC_ERROR_NONE_ENROLLED},
+                {BiometricConstants.BIOMETRIC_ERROR_HW_UNAVAILABLE,
+                        BiometricManager.BIOMETRIC_ERROR_HW_UNAVAILABLE},
+                {BiometricConstants.BIOMETRIC_ERROR_HW_NOT_PRESENT,
+                        BiometricManager.BIOMETRIC_ERROR_NO_HARDWARE},
+                {BiometricConstants.BIOMETRIC_ERROR_LOCKOUT,
+                        BiometricManager.BIOMETRIC_ERROR_LOCKOUT},
+                {BiometricConstants.BIOMETRIC_ERROR_LOCKOUT_PERMANENT,
+                        BiometricManager.BIOMETRIC_ERROR_LOCKOUT},
+                {BiometricConstants.BIOMETRIC_ERROR_MANDATORY_NOT_ACTIVE,
+                        BiometricManager.BIOMETRIC_ERROR_MANDATORY_NOT_ACTIVE}
+        };
+
+        for (int i = 0; i < testCases.length; i++) {
+            assertEquals(testCases[i][1],
+                    Utils.biometricConstantsToBiometricManager(testCases[i][0]));
+        }
+    }
+
+    @Test
     public void testGetAuthenticationTypeForResult_getsCorrectType() {
         assertEquals(Utils.getAuthenticationTypeForResult(
                 BiometricPrompt.DISMISSED_REASON_CREDENTIAL_CONFIRMED),
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/log/BiometricLoggerTest.java b/services/tests/servicestests/src/com/android/server/biometrics/log/BiometricLoggerTest.java
index 238a928..8f23ab9 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/log/BiometricLoggerTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/log/BiometricLoggerTest.java
@@ -236,6 +236,13 @@
     }
 
     @Test
+    public void testFingerprintsLoe() {
+        mLogger = createLogger();
+        mLogger.logFingerprintsLoe();
+        verify(mSink).reportFingerprintsLoe(eq(DEFAULT_MODALITY));
+    }
+
+    @Test
     public void testALSCallback() {
         mLogger = createLogger();
         final CallbackWithProbe<Probe> callback =
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintInternalCleanupClientTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintInternalCleanupClientTest.java
index 242880c..7dcf841 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintInternalCleanupClientTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintInternalCleanupClientTest.java
@@ -182,13 +182,22 @@
     public void invalidBiometricUserState() throws Exception {
         mClient =  createClient();
 
+        final List<Fingerprint> templates = List.of(
+                new Fingerprint("one", 1, 1),
+                new Fingerprint("two", 2, 1),
+                new Fingerprint("three", 3, 1)
+        );
+
         final List<Fingerprint> list = new ArrayList<>();
         doReturn(true).when(mFingerprintUtils)
                 .hasValidBiometricUserState(mContext, 2);
         doReturn(list).when(mFingerprintUtils).getBiometricsForUser(mContext, 2);
 
         mClient.start(mCallback);
-        mClient.onEnumerationResult(null, 0);
+        for (int i = templates.size() - 1; i >= 0; i--) {
+            mClient.getCurrentEnumerateClient().onEnumerationResult(templates.get(i), i);
+        }
+        verify(mLogger).logFingerprintsLoe();
         verify(mFingerprintUtils).deleteStateForUser(2);
     }
 
diff --git a/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java
index c288212..4d067f6 100644
--- a/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java
@@ -62,7 +62,6 @@
 import android.companion.virtual.VirtualDeviceParams;
 import android.companion.virtual.audio.IAudioConfigChangedCallback;
 import android.companion.virtual.audio.IAudioRoutingCallback;
-import android.companion.virtual.flags.Flags;
 import android.companion.virtual.sensor.VirtualSensor;
 import android.companion.virtual.sensor.VirtualSensorCallback;
 import android.companion.virtual.sensor.VirtualSensorConfig;
@@ -1686,7 +1685,6 @@
 
     @Test
     public void openNonBlockedAppOnMirrorDisplay_flagEnabled_cannotBeLaunched() {
-        mSetFlagsRule.enableFlags(Flags.FLAG_INTERACTIVE_SCREEN_MIRROR);
         when(mDisplayManagerInternalMock.getDisplayIdToMirror(anyInt()))
                 .thenReturn(Display.DEFAULT_DISPLAY);
         addVirtualDisplay(mDeviceImpl, DISPLAY_ID_1);
@@ -1711,31 +1709,6 @@
     }
 
     @Test
-    public void openNonBlockedAppOnMirrorDisplay_flagDisabled_launchesActivity() {
-        mSetFlagsRule.disableFlags(Flags.FLAG_INTERACTIVE_SCREEN_MIRROR);
-        when(mDisplayManagerInternalMock.getDisplayIdToMirror(anyInt()))
-                .thenReturn(Display.DEFAULT_DISPLAY);
-        addVirtualDisplay(mDeviceImpl, DISPLAY_ID_1);
-        GenericWindowPolicyController gwpc = mDeviceImpl.getDisplayWindowPolicyControllerForTest(
-                DISPLAY_ID_1);
-        doNothing().when(mContext).startActivityAsUser(any(), any(), any());
-
-        ActivityInfo activityInfo = getActivityInfo(
-                NONBLOCKED_APP_PACKAGE_NAME,
-                NONBLOCKED_APP_PACKAGE_NAME,
-                /* displayOnRemoteDevices */ true,
-                /* targetDisplayCategory */ null);
-        assertThat(gwpc.canActivityBeLaunched(activityInfo, null,
-                WindowConfiguration.WINDOWING_MODE_FULLSCREEN, DISPLAY_ID_1, /* isNewTask= */ false,
-                /* isResultExpected = */ false, /* intentSender= */ null))
-                .isTrue();
-        Intent blockedAppIntent = BlockedAppStreamingActivity.createIntent(
-                activityInfo, mAssociationInfo.getDisplayName());
-        verify(mContext, never()).startActivityAsUser(argThat(intent ->
-                intent.filterEquals(blockedAppIntent)), any(), any());
-    }
-
-    @Test
     public void registerRunningAppsChangedListener_onRunningAppsChanged_listenersNotified() {
         ArraySet<Integer> uids = new ArraySet<>(Arrays.asList(UID_1, UID_2));
         addVirtualDisplay(mDeviceImpl, DISPLAY_ID_1);
diff --git a/services/tests/servicestests/src/com/android/server/contentprotection/OWNERS b/services/tests/servicestests/src/com/android/server/contentprotection/OWNERS
index 24561c5..3d09da3 100644
--- a/services/tests/servicestests/src/com/android/server/contentprotection/OWNERS
+++ b/services/tests/servicestests/src/com/android/server/contentprotection/OWNERS
@@ -1,3 +1,4 @@
-# Bug component: 544200
+# Bug component: 1040349
 
-include /core/java/android/view/contentcapture/OWNERS
+include /core/java/android/view/contentprotection/OWNERS
+
diff --git a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
index e72d9e7..b7483d6 100644
--- a/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/devicepolicy/DevicePolicyManagerTest.java
@@ -70,14 +70,14 @@
 
 import static org.junit.Assert.fail;
 import static org.junit.Assume.assumeTrue;
-import static org.mockito.Matchers.any;
-import static org.mockito.Matchers.anyBoolean;
-import static org.mockito.Matchers.anyInt;
-import static org.mockito.Matchers.anyLong;
-import static org.mockito.Matchers.anyObject;
-import static org.mockito.Matchers.anyString;
-import static org.mockito.Matchers.eq;
-import static org.mockito.Matchers.isNull;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.anyLong;
+import static org.mockito.ArgumentMatchers.anyString;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.ArgumentMatchers.isNull;
+import static org.mockito.ArgumentMatchers.longThat;
 import static org.mockito.Mockito.clearInvocations;
 import static org.mockito.Mockito.doAnswer;
 import static org.mockito.Mockito.doReturn;
@@ -1733,12 +1733,20 @@
         pi.applicationInfo.flags = flags;
         doReturn(pi).when(getServices().ipackageManager).getPackageInfo(
                 eq(packageName),
-                anyLong(),
+                longThat(flg -> (flg & PackageManager.MATCH_ANY_USER) == 0),
+                eq(userId));
+        doReturn(pi).when(getServices().ipackageManager).getPackageInfo(
+                eq(packageName),
+                longThat(flg -> (flg & PackageManager.MATCH_ANY_USER) != 0),
+                anyInt());
+        doReturn(pi.applicationInfo).when(getServices().ipackageManager).getApplicationInfo(
+                eq(packageName),
+                longThat(flg -> (flg & PackageManager.MATCH_ANY_USER) == 0),
                 eq(userId));
         doReturn(pi.applicationInfo).when(getServices().ipackageManager).getApplicationInfo(
                 eq(packageName),
-                anyLong(),
-                eq(userId));
+                longThat(flg -> (flg & PackageManager.MATCH_ANY_USER) != 0),
+                anyInt());
         doReturn(true).when(getServices().ipackageManager).isPackageAvailable(packageName, userId);
         // Setup application UID with the PackageManager
         getServices().addTestPackageUid(packageName, uid);
@@ -1757,7 +1765,7 @@
         mServiceContext.packageName = mRealTestContext.getPackageName();
         mServiceContext.applicationInfo = new ApplicationInfo();
         mServiceContext.binder.callingUid = DpmMockContext.SYSTEM_UID;
-        when(mContext.resources.getColor(anyInt(), anyObject())).thenReturn(Color.WHITE);
+        when(mContext.resources.getColor(anyInt(), any())).thenReturn(Color.WHITE);
 
         StringParceledListSlice oneCert = asSlice(new String[] {"1"});
         StringParceledListSlice fourCerts = asSlice(new String[] {"1", "2", "3", "4"});
@@ -4551,7 +4559,7 @@
 
         mContext.packageName = admin1.getPackageName();
         mContext.applicationInfo = new ApplicationInfo();
-        when(mContext.resources.getColor(anyInt(), anyObject())).thenReturn(Color.WHITE);
+        when(mContext.resources.getColor(anyInt(), any())).thenReturn(Color.WHITE);
 
         // setUp() adds a secondary user for CALLER_USER_HANDLE. Remove it as otherwise the
         // feature is disabled because there are non-affiliated secondary users.
@@ -4597,12 +4605,12 @@
         setupDeviceOwner();
         mContext.packageName = admin1.getPackageName();
         mContext.applicationInfo = new ApplicationInfo();
-        when(mContext.resources.getColor(anyInt(), anyObject())).thenReturn(Color.WHITE);
+        when(mContext.resources.getColor(anyInt(), any())).thenReturn(Color.WHITE);
 
         // setUp() adds a secondary user for CALLER_USER_HANDLE. Remove it as otherwise the
         // feature is disabled because there are non-affiliated secondary users.
         getServices().removeUser(CALLER_USER_HANDLE);
-        when(getServices().iipConnectivityMetrics.addNetdEventCallback(anyInt(), anyObject()))
+        when(getServices().iipConnectivityMetrics.addNetdEventCallback(anyInt(), any()))
                 .thenReturn(true);
 
         // No logs were retrieved so far.
@@ -4667,7 +4675,7 @@
         mContext.packageName = admin1.getPackageName();
         addManagedProfile(admin1, managedProfileAdminUid, admin1, VERSION_CODES.S);
         when(getServices().iipConnectivityMetrics
-                .addNetdEventCallback(anyInt(), anyObject())).thenReturn(true);
+                .addNetdEventCallback(anyInt(), any())).thenReturn(true);
 
         // Check no logs have been retrieved so far.
         assertThat(dpm.getLastNetworkLogRetrievalTime()).isEqualTo(-1);
@@ -4699,7 +4707,7 @@
         mContext.packageName = admin1.getPackageName();
         mContext.applicationInfo = new ApplicationInfo();
         when(getServices().iipConnectivityMetrics
-                .addNetdEventCallback(anyInt(), anyObject())).thenReturn(true);
+                .addNetdEventCallback(anyInt(), any())).thenReturn(true);
 
         // Check no logs have been retrieved so far.
         assertThat(dpm.getLastNetworkLogRetrievalTime()).isEqualTo(-1);
@@ -6296,13 +6304,13 @@
 
         mContext.binder.callingUid = DpmMockContext.SYSTEM_UID;
         assertThat(dpms.isNotificationListenerServicePermitted(
-        nonSystemPackage, MANAGED_PROFILE_USER_ID)).isTrue();
+                nonSystemPackage, MANAGED_PROFILE_USER_ID)).isTrue();
         assertThat(dpms.isNotificationListenerServicePermitted(
-        systemListener, MANAGED_PROFILE_USER_ID)).isTrue();
+                systemListener, MANAGED_PROFILE_USER_ID)).isTrue();
         assertThat(dpms.isNotificationListenerServicePermitted(
-        nonSystemPackage, UserHandle.USER_SYSTEM)).isTrue();
+                nonSystemPackage, UserHandle.USER_SYSTEM)).isTrue();
         assertThat(dpms.isNotificationListenerServicePermitted(
-        systemListener, UserHandle.USER_SYSTEM)).isTrue();
+                systemListener, UserHandle.USER_SYSTEM)).isTrue();
 
         // Setting an empty allowlist - only system listeners allowed in managed profile, but
         // all allowed in primary profile
@@ -6313,13 +6321,13 @@
 
         mContext.binder.callingUid = DpmMockContext.SYSTEM_UID;
         assertThat(dpms.isNotificationListenerServicePermitted(
-        nonSystemPackage, MANAGED_PROFILE_USER_ID)).isFalse();
+                nonSystemPackage, MANAGED_PROFILE_USER_ID)).isFalse();
         assertThat(dpms.isNotificationListenerServicePermitted(
-        systemListener, MANAGED_PROFILE_USER_ID)).isTrue();
+                systemListener, MANAGED_PROFILE_USER_ID)).isTrue();
         assertThat(dpms.isNotificationListenerServicePermitted(
-        nonSystemPackage, UserHandle.USER_SYSTEM)).isTrue();
+                nonSystemPackage, UserHandle.USER_SYSTEM)).isTrue();
         assertThat(dpms.isNotificationListenerServicePermitted(
-        systemListener, UserHandle.USER_SYSTEM)).isTrue();
+                systemListener, UserHandle.USER_SYSTEM)).isTrue();
     }
 
     @Test
@@ -6455,7 +6463,7 @@
         if (admin1.getPackageName().equals(callerContext.getPackageName())) {
             admin1Context = callerContext;
         }
-        when(admin1Context.resources.getColor(anyInt(), anyObject())).thenReturn(Color.WHITE);
+        when(admin1Context.resources.getColor(anyInt(), any())).thenReturn(Color.WHITE);
 
         // caller: device admin or delegated certificate installer
         callerContext.applicationInfo = new ApplicationInfo();
@@ -6528,7 +6536,7 @@
         if (admin1.getPackageName().equals(callerContext.getPackageName())) {
             admin1Context = callerContext;
         }
-        when(admin1Context.resources.getColor(anyInt(), anyObject())).thenReturn(Color.WHITE);
+        when(admin1Context.resources.getColor(anyInt(), any())).thenReturn(Color.WHITE);
 
         // caller: device admin or delegated certificate installer
         callerContext.applicationInfo = new ApplicationInfo();
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/GroupHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/GroupHelperTest.java
index 225c1dc..51f64ba 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/GroupHelperTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/GroupHelperTest.java
@@ -31,10 +31,11 @@
 import static android.app.Notification.VISIBILITY_SECRET;
 import static android.app.NotificationManager.IMPORTANCE_DEFAULT;
 import static android.app.NotificationManager.IMPORTANCE_LOW;
+import static android.service.notification.Flags.FLAG_NOTIFICATION_CLASSIFICATION;
 import static android.service.notification.Flags.FLAG_NOTIFICATION_FORCE_GROUPING;
+import static android.service.notification.NotificationListenerService.REASON_APP_CANCEL;
 import static android.platform.test.flag.junit.SetFlagsRule.DefaultInitValueType.DEVICE_DEFAULT;
 
-import static android.service.notification.NotificationListenerService.REASON_APP_CANCEL;
 import static com.android.server.notification.GroupHelper.AGGREGATE_GROUP_KEY;
 import static com.android.server.notification.GroupHelper.AUTOGROUP_KEY;
 import static com.android.server.notification.GroupHelper.BASE_FLAGS;
@@ -2518,17 +2519,7 @@
         assertThat(cachedSummary).isNull();
     }
 
-    @Test
-    @EnableFlags(FLAG_NOTIFICATION_FORCE_GROUPING)
-    public void testGroupSectioners() {
-        final NotificationRecord notification_alerting = getNotificationRecord(mPkg, 0, "", mUser,
-            "", false, IMPORTANCE_DEFAULT);
-        assertThat(GroupHelper.getSection(notification_alerting).mName).isEqualTo("AlertingSection");
-
-        final NotificationRecord notification_silent = getNotificationRecord(mPkg, 0, "", mUser,
-            "", false, IMPORTANCE_LOW);
-        assertThat(GroupHelper.getSection(notification_silent).mName).isEqualTo("SilentSection");
-
+    private void checkNonGroupableNotifications() {
         NotificationRecord notification_conversation = mock(NotificationRecord.class);
         when(notification_conversation.isConversation()).thenReturn(true);
         assertThat(GroupHelper.getSection(notification_conversation)).isNull();
@@ -2545,7 +2536,7 @@
         assertThat(GroupHelper.getSection(notification_call)).isNull();
 
         NotificationRecord notification_colorFg = spy(getNotificationRecord(mPkg, 0, "", mUser,
-            "", false, IMPORTANCE_LOW));
+                "", false, IMPORTANCE_LOW));
         sbn = spy(getSbn("package", 0, "0", UserHandle.SYSTEM));
         n = mock(Notification.class);
         when(notification_colorFg.isConversation()).thenReturn(false);
@@ -2558,4 +2549,97 @@
         assertThat(GroupHelper.getSection(notification_colorFg)).isNull();
     }
 
+    @Test
+    @EnableFlags(FLAG_NOTIFICATION_FORCE_GROUPING)
+    @DisableFlags(FLAG_NOTIFICATION_CLASSIFICATION)
+    public void testGroupSectioners() {
+        final NotificationRecord notification_alerting = getNotificationRecord(mPkg, 0, "", mUser,
+                "", false, IMPORTANCE_DEFAULT);
+        assertThat(GroupHelper.getSection(notification_alerting).mName).isEqualTo(
+                "AlertingSection");
+
+        final NotificationRecord notification_silent = getNotificationRecord(mPkg, 0, "", mUser,
+                "", false, IMPORTANCE_LOW);
+        assertThat(GroupHelper.getSection(notification_silent).mName).isEqualTo("SilentSection");
+
+        // Check that special categories are grouped by their importance
+        final NotificationChannel promoChannel = new NotificationChannel(
+                NotificationChannel.PROMOTIONS_ID, NotificationChannel.PROMOTIONS_ID,
+                IMPORTANCE_DEFAULT);
+        final NotificationRecord notification_promotion = getNotificationRecord(mPkg, 0, "", mUser,
+                "", false, promoChannel);
+        assertThat(GroupHelper.getSection(notification_promotion).mName).isEqualTo(
+                "AlertingSection");
+
+        final NotificationChannel newsChannel = new NotificationChannel(NotificationChannel.NEWS_ID,
+                NotificationChannel.NEWS_ID, IMPORTANCE_DEFAULT);
+        final NotificationRecord notification_news = getNotificationRecord(mPkg, 0, "", mUser,
+                "", false, newsChannel);
+        assertThat(GroupHelper.getSection(notification_news).mName).isEqualTo(
+                "AlertingSection");
+
+        final NotificationChannel socialChannel = new NotificationChannel(
+                NotificationChannel.SOCIAL_MEDIA_ID, NotificationChannel.SOCIAL_MEDIA_ID,
+                IMPORTANCE_DEFAULT);
+        final NotificationRecord notification_social = getNotificationRecord(mPkg, 0, "", mUser,
+                "", false, socialChannel);
+        assertThat(GroupHelper.getSection(notification_social).mName).isEqualTo(
+                "AlertingSection");
+
+        final NotificationChannel recsChannel = new NotificationChannel(NotificationChannel.RECS_ID,
+                NotificationChannel.RECS_ID, IMPORTANCE_DEFAULT);
+        final NotificationRecord notification_recs = getNotificationRecord(mPkg, 0, "", mUser,
+                "", false, recsChannel);
+        assertThat(GroupHelper.getSection(notification_recs).mName).isEqualTo(
+                "AlertingSection");
+
+        checkNonGroupableNotifications();
+    }
+
+    @Test
+    @EnableFlags({FLAG_NOTIFICATION_FORCE_GROUPING, FLAG_NOTIFICATION_CLASSIFICATION})
+    public void testGroupSectioners_withClassificationSections() {
+        final NotificationRecord notification_alerting = getNotificationRecord(mPkg, 0, "", mUser,
+                "", false, IMPORTANCE_DEFAULT);
+        assertThat(GroupHelper.getSection(notification_alerting).mName).isEqualTo(
+                "AlertingSection");
+
+        final NotificationRecord notification_silent = getNotificationRecord(mPkg, 0, "", mUser,
+                "", false, IMPORTANCE_LOW);
+        assertThat(GroupHelper.getSection(notification_silent).mName).isEqualTo("SilentSection");
+
+        // Check that special categories are grouped in their own sections
+        final NotificationChannel promoChannel = new NotificationChannel(
+                NotificationChannel.PROMOTIONS_ID, NotificationChannel.PROMOTIONS_ID,
+                IMPORTANCE_DEFAULT);
+        final NotificationRecord notification_promotion = getNotificationRecord(mPkg, 0, "", mUser,
+                "", false, promoChannel);
+        assertThat(GroupHelper.getSection(notification_promotion).mName).isEqualTo(
+                "PromotionsSection");
+
+        final NotificationChannel newsChannel = new NotificationChannel(NotificationChannel.NEWS_ID,
+                NotificationChannel.NEWS_ID, IMPORTANCE_DEFAULT);
+        final NotificationRecord notification_news = getNotificationRecord(mPkg, 0, "", mUser,
+                "", false, newsChannel);
+        assertThat(GroupHelper.getSection(notification_news).mName).isEqualTo(
+                "NewsSection");
+
+        final NotificationChannel socialChannel = new NotificationChannel(
+                NotificationChannel.SOCIAL_MEDIA_ID, NotificationChannel.SOCIAL_MEDIA_ID,
+                IMPORTANCE_DEFAULT);
+        final NotificationRecord notification_social = getNotificationRecord(mPkg, 0, "", mUser,
+                "", false, socialChannel);
+        assertThat(GroupHelper.getSection(notification_social).mName).isEqualTo(
+                "SocialSection");
+
+        final NotificationChannel recsChannel = new NotificationChannel(NotificationChannel.RECS_ID,
+                NotificationChannel.RECS_ID, IMPORTANCE_DEFAULT);
+        final NotificationRecord notification_recs = getNotificationRecord(mPkg, 0, "", mUser,
+                "", false, recsChannel);
+        assertThat(GroupHelper.getSection(notification_recs).mName).isEqualTo(
+                "RecsSection");
+
+        checkNonGroupableNotifications();
+    }
+
 }
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationAttentionHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationAttentionHelperTest.java
index 643ee4a..62e5b9a 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationAttentionHelperTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationAttentionHelperTest.java
@@ -2007,6 +2007,25 @@
     }
 
     @Test
+    public void testCanInterruptNonRingtoneInsistentBuzzWithOtherBuzzyNotification() {
+        NotificationRecord r = getInsistentBuzzyNotification();
+        mAttentionHelper.buzzBeepBlinkLocked(r, DEFAULT_SIGNALS);
+        verifyVibrateLooped();
+        assertTrue(r.isInterruptive());
+        assertNotEquals(-1, r.getLastAudiblyAlertedMs());
+        Mockito.reset(mVibrator);
+
+        // New buzzy notification stops previous looping vibration
+        NotificationRecord interrupter = getBuzzyOtherNotification();
+        mAttentionHelper.buzzBeepBlinkLocked(interrupter, DEFAULT_SIGNALS);
+        verifyStopVibrate();
+        // And then vibrates itself
+        verifyVibrate(1);
+        assertTrue(interrupter.isInterruptive());
+        assertNotEquals(-1, interrupter.getLastAudiblyAlertedMs());
+    }
+
+    @Test
     public void testRingtoneInsistentBeep_doesNotBlockFutureSoundsOnceStopped() throws Exception {
         NotificationChannel ringtoneChannel =
             new NotificationChannel("ringtone", "", IMPORTANCE_HIGH);
diff --git a/services/tests/vibrator/src/com/android/server/vibrator/HapticFeedbackVibrationProviderTest.java b/services/tests/vibrator/src/com/android/server/vibrator/HapticFeedbackVibrationProviderTest.java
index 240bd1e..8797e63 100644
--- a/services/tests/vibrator/src/com/android/server/vibrator/HapticFeedbackVibrationProviderTest.java
+++ b/services/tests/vibrator/src/com/android/server/vibrator/HapticFeedbackVibrationProviderTest.java
@@ -16,8 +16,6 @@
 
 package com.android.server.vibrator;
 
-import static android.os.VibrationAttributes.CATEGORY_KEYBOARD;
-import static android.os.VibrationAttributes.CATEGORY_UNKNOWN;
 import static android.os.VibrationAttributes.FLAG_BYPASS_INTERRUPTION_POLICY;
 import static android.os.VibrationAttributes.FLAG_BYPASS_USER_VIBRATION_INTENSITY_OFF;
 import static android.os.VibrationAttributes.USAGE_IME_FEEDBACK;
@@ -255,7 +253,7 @@
     }
 
     @Test
-    public void testKeyboardHaptic_fixAmplitude_keyboardCategoryOn_keyboardVibrationReturned() {
+    public void testKeyboardHaptic_fixAmplitude_keyboardVibrationReturned() {
         mockVibratorPrimitiveSupport(PRIMITIVE_CLICK, PRIMITIVE_TICK);
         mockKeyboardVibrationFixedAmplitude(KEYBOARD_VIBRATION_FIXED_AMPLITUDE);
 
@@ -330,7 +328,7 @@
     }
 
     @Test
-    public void testVibrationAttribute_keyboardCategoryOn_notIme_useTouchUsage() {
+    public void testVibrationAttribute_notIme_useTouchUsage() {
         HapticFeedbackVibrationProvider hapticProvider = createProviderWithDefaultCustomizations();
 
         for (int effectId : KEYBOARD_FEEDBACK_CONSTANTS) {
@@ -338,13 +336,11 @@
                     effectId, /* flags */ 0, /* privFlags */ 0);
             assertWithMessage("Expected USAGE_TOUCH for effect " + effectId)
                     .that(attrs.getUsage()).isEqualTo(USAGE_TOUCH);
-            assertWithMessage("Expected CATEGORY_KEYBOARD for effect " + effectId)
-                    .that(attrs.getCategory()).isEqualTo(CATEGORY_UNKNOWN);
         }
     }
 
     @Test
-    public void testVibrationAttribute_keyboardCategoryOn_isIme_useImeFeedbackUsage() {
+    public void testVibrationAttribute_isIme_useImeFeedbackUsage() {
         HapticFeedbackVibrationProvider hapticProvider = createProviderWithDefaultCustomizations();
 
         for (int effectId : KEYBOARD_FEEDBACK_CONSTANTS) {
@@ -353,8 +349,6 @@
                     HapticFeedbackConstants.PRIVATE_FLAG_APPLY_INPUT_METHOD_SETTINGS);
             assertWithMessage("Expected USAGE_IME_FEEDBACK for effect " + effectId)
                     .that(attrs.getUsage()).isEqualTo(USAGE_IME_FEEDBACK);
-            assertWithMessage("Expected CATEGORY_KEYBOARD for effect " + effectId)
-                    .that(attrs.getCategory()).isEqualTo(CATEGORY_KEYBOARD);
         }
     }
 
diff --git a/services/tests/vibrator/src/com/android/server/vibrator/VibrationScalerTest.java b/services/tests/vibrator/src/com/android/server/vibrator/VibrationScalerTest.java
index 4704691..9681d74 100644
--- a/services/tests/vibrator/src/com/android/server/vibrator/VibrationScalerTest.java
+++ b/services/tests/vibrator/src/com/android/server/vibrator/VibrationScalerTest.java
@@ -281,8 +281,8 @@
 
     @Test
     @EnableFlags(android.os.vibrator.Flags.FLAG_VENDOR_VIBRATION_EFFECTS)
-    public void scale_withVendorEffect_setsEffectStrengthBasedOnSettings() {
-        setDefaultIntensity(USAGE_NOTIFICATION, VIBRATION_INTENSITY_LOW);
+    public void scale_withVendorEffect_setsEffectStrengthAndScaleBasedOnSettings() {
+        setDefaultIntensity(USAGE_NOTIFICATION, VIBRATION_INTENSITY_MEDIUM);
         setUserSetting(Settings.System.NOTIFICATION_VIBRATION_INTENSITY, VIBRATION_INTENSITY_HIGH);
         PersistableBundle vendorData = new PersistableBundle();
         vendorData.putString("key", "value");
@@ -291,20 +291,27 @@
         VibrationEffect.VendorEffect scaled =
                 (VibrationEffect.VendorEffect) mVibrationScaler.scale(effect, USAGE_NOTIFICATION);
         assertEquals(scaled.getEffectStrength(), VibrationEffect.EFFECT_STRENGTH_STRONG);
+        // Notification scales up.
+        assertTrue(scaled.getScale() > 1);
 
         setUserSetting(Settings.System.NOTIFICATION_VIBRATION_INTENSITY,
                 VIBRATION_INTENSITY_MEDIUM);
         scaled = (VibrationEffect.VendorEffect) mVibrationScaler.scale(effect, USAGE_NOTIFICATION);
         assertEquals(scaled.getEffectStrength(), VibrationEffect.EFFECT_STRENGTH_MEDIUM);
+        // Notification does not scale.
+        assertEquals(1, scaled.getScale(), TOLERANCE);
 
         setUserSetting(Settings.System.NOTIFICATION_VIBRATION_INTENSITY, VIBRATION_INTENSITY_LOW);
         scaled = (VibrationEffect.VendorEffect) mVibrationScaler.scale(effect, USAGE_NOTIFICATION);
         assertEquals(scaled.getEffectStrength(), VibrationEffect.EFFECT_STRENGTH_LIGHT);
+        // Notification scales down.
+        assertTrue(scaled.getScale() < 1);
 
         setUserSetting(Settings.System.NOTIFICATION_VIBRATION_INTENSITY, VIBRATION_INTENSITY_OFF);
         scaled = (VibrationEffect.VendorEffect) mVibrationScaler.scale(effect, USAGE_NOTIFICATION);
         // Vibration setting being bypassed will use default setting.
-        assertEquals(scaled.getEffectStrength(), VibrationEffect.EFFECT_STRENGTH_LIGHT);
+        assertEquals(scaled.getEffectStrength(), VibrationEffect.EFFECT_STRENGTH_MEDIUM);
+        assertEquals(1, scaled.getScale(), TOLERANCE);
     }
 
     @Test
@@ -348,7 +355,7 @@
         scaled = getFirstSegment(mVibrationScaler.scale(VibrationEffect.createOneShot(128, 128),
                 USAGE_TOUCH));
         // Haptic feedback does not scale.
-        assertEquals(128f / 255, scaled.getAmplitude(), 1e-5);
+        assertEquals(128f / 255, scaled.getAmplitude(), TOLERANCE);
     }
 
     @Test
@@ -373,7 +380,7 @@
 
         scaled = getFirstSegment(mVibrationScaler.scale(composed, USAGE_TOUCH));
         // Haptic feedback does not scale.
-        assertEquals(0.5, scaled.getScale(), 1e-5);
+        assertEquals(0.5, scaled.getScale(), TOLERANCE);
     }
 
     @Test
@@ -446,7 +453,7 @@
             android.os.vibrator.Flags.FLAG_ADAPTIVE_HAPTICS_ENABLED,
             android.os.vibrator.Flags.FLAG_VENDOR_VIBRATION_EFFECTS,
     })
-    public void scale_adaptiveHapticsOnVendorEffect_setsLinearScaleParameter() {
+    public void scale_adaptiveHapticsOnVendorEffect_setsAdaptiveScaleParameter() {
         setDefaultIntensity(USAGE_RINGTONE, VIBRATION_INTENSITY_HIGH);
 
         mVibrationScaler.updateAdaptiveHapticsScale(USAGE_RINGTONE, 0.5f);
@@ -457,12 +464,12 @@
 
         VibrationEffect.VendorEffect scaled =
                 (VibrationEffect.VendorEffect) mVibrationScaler.scale(effect, USAGE_RINGTONE);
-        assertEquals(scaled.getLinearScale(), 0.5f);
+        assertEquals(scaled.getAdaptiveScale(), 0.5f);
 
         mVibrationScaler.removeAdaptiveHapticsScale(USAGE_RINGTONE);
 
         scaled = (VibrationEffect.VendorEffect) mVibrationScaler.scale(effect, USAGE_RINGTONE);
-        assertEquals(scaled.getLinearScale(), 1.0f);
+        assertEquals(scaled.getAdaptiveScale(), 1.0f);
     }
 
     private void setDefaultIntensity(@VibrationAttributes.Usage int usage,
diff --git a/services/tests/vibrator/src/com/android/server/vibrator/VibrationSettingsTest.java b/services/tests/vibrator/src/com/android/server/vibrator/VibrationSettingsTest.java
index 6f06050..38cd49d 100644
--- a/services/tests/vibrator/src/com/android/server/vibrator/VibrationSettingsTest.java
+++ b/services/tests/vibrator/src/com/android/server/vibrator/VibrationSettingsTest.java
@@ -610,7 +610,6 @@
         assertVibrationIgnoredForAttributes(
                 new VibrationAttributes.Builder()
                         .setUsage(USAGE_IME_FEEDBACK)
-                        .setCategory(VibrationAttributes.CATEGORY_KEYBOARD)
                         .build(),
                 Vibration.Status.IGNORED_FOR_SETTINGS);
 
@@ -619,7 +618,6 @@
         assertVibrationNotIgnoredForAttributes(
                 new VibrationAttributes.Builder()
                         .setUsage(USAGE_IME_FEEDBACK)
-                        .setCategory(VibrationAttributes.CATEGORY_KEYBOARD)
                         .setFlags(VibrationAttributes.FLAG_BYPASS_USER_VIBRATION_INTENSITY_OFF)
                         .build());
     }
@@ -637,7 +635,6 @@
         assertVibrationNotIgnoredForAttributes(
                 new VibrationAttributes.Builder()
                         .setUsage(USAGE_IME_FEEDBACK)
-                        .setCategory(VibrationAttributes.CATEGORY_KEYBOARD)
                         .build());
     }
 
@@ -654,7 +651,6 @@
         assertVibrationIgnoredForAttributes(
                 new VibrationAttributes.Builder()
                         .setUsage(USAGE_IME_FEEDBACK)
-                        .setCategory(VibrationAttributes.CATEGORY_KEYBOARD)
                         .build(),
                 Vibration.Status.IGNORED_FOR_SETTINGS);
     }
diff --git a/services/tests/vibrator/src/com/android/server/vibrator/VibrationThreadTest.java b/services/tests/vibrator/src/com/android/server/vibrator/VibrationThreadTest.java
index 0fbdce4..bfdaa78 100644
--- a/services/tests/vibrator/src/com/android/server/vibrator/VibrationThreadTest.java
+++ b/services/tests/vibrator/src/com/android/server/vibrator/VibrationThreadTest.java
@@ -34,13 +34,15 @@
 import static org.mockito.Mockito.doAnswer;
 import static org.mockito.Mockito.inOrder;
 import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.spy;
 import static org.mockito.Mockito.timeout;
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
 import android.content.ComponentName;
-import android.content.Context;
+import android.content.ContentResolver;
+import android.content.ContextWrapper;
 import android.content.pm.PackageManagerInternal;
 import android.hardware.vibrator.Braking;
 import android.hardware.vibrator.IVibrator;
@@ -52,6 +54,7 @@
 import android.os.PowerManager;
 import android.os.Process;
 import android.os.SystemClock;
+import android.os.UserHandle;
 import android.os.VibrationAttributes;
 import android.os.VibrationEffect;
 import android.os.Vibrator;
@@ -66,11 +69,14 @@
 import android.platform.test.annotations.RequiresFlagsEnabled;
 import android.platform.test.flag.junit.CheckFlagsRule;
 import android.platform.test.flag.junit.DeviceFlagsValueProvider;
+import android.provider.Settings;
 import android.util.SparseArray;
 
 import androidx.test.InstrumentationRegistry;
 import androidx.test.filters.LargeTest;
 
+import com.android.internal.util.test.FakeSettingsProvider;
+import com.android.internal.util.test.FakeSettingsProviderRule;
 import com.android.server.LocalServices;
 
 import org.junit.After;
@@ -105,10 +111,12 @@
     private static final int TEST_DEFAULT_AMPLITUDE = 255;
     private static final float TEST_DEFAULT_SCALE_LEVEL_GAIN = 1.4f;
 
-    @Rule public MockitoRule mMockitoRule = MockitoJUnit.rule();
     @Rule
-    public final CheckFlagsRule mCheckFlagsRule =
-            DeviceFlagsValueProvider.createCheckFlagsRule();
+    public MockitoRule mMockitoRule = MockitoJUnit.rule();
+    @Rule
+    public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule();
+    @Rule
+    public FakeSettingsProviderRule mSettingsProviderRule = FakeSettingsProvider.rule();
 
     @Mock private PackageManagerInternal mPackageManagerInternalMock;
     @Mock private VibrationThread.VibratorManagerHooks mManagerHooks;
@@ -117,6 +125,7 @@
     @Mock private VibrationConfig mVibrationConfigMock;
     @Mock private VibratorFrameworkStatsLogger mStatsLoggerMock;
 
+    private ContextWrapper mContextSpy;
     private final Map<Integer, FakeVibratorControllerProvider> mVibratorProviders = new HashMap<>();
     private VibrationSettings mVibrationSettings;
     private VibrationScaler mVibrationScaler;
@@ -149,14 +158,16 @@
         LocalServices.removeServiceForTest(PackageManagerInternal.class);
         LocalServices.addService(PackageManagerInternal.class, mPackageManagerInternalMock);
 
-        Context context = InstrumentationRegistry.getContext();
-        mVibrationSettings = new VibrationSettings(context, new Handler(mTestLooper.getLooper()),
-                mVibrationConfigMock);
+        mContextSpy = spy(new ContextWrapper(InstrumentationRegistry.getContext()));
+        ContentResolver contentResolver = mSettingsProviderRule.mockContentResolver(mContextSpy);
+        when(mContextSpy.getContentResolver()).thenReturn(contentResolver);
+        mVibrationSettings = new VibrationSettings(mContextSpy,
+                new Handler(mTestLooper.getLooper()), mVibrationConfigMock);
         mVibrationScaler = new VibrationScaler(mVibrationConfigMock, mVibrationSettings);
 
         mockVibrators(VIBRATOR_ID);
 
-        PowerManager.WakeLock wakeLock = context.getSystemService(
+        PowerManager.WakeLock wakeLock = mContextSpy.getSystemService(
                 PowerManager.class).newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "*vibrator*");
         mThread = new VibrationThread(wakeLock, mManagerHooks);
         mThread.start();
@@ -254,6 +265,9 @@
     @Test
     @RequiresFlagsEnabled(Flags.FLAG_ADAPTIVE_HAPTICS_ENABLED)
     public void vibrate_singleWaveformWithAdaptiveHapticsScaling_scalesAmplitudesProperly() {
+        // No user settings scale.
+        setUserSetting(Settings.System.RING_VIBRATION_INTENSITY,
+                Vibrator.VIBRATION_INTENSITY_MEDIUM);
         mVibratorProviders.get(VIBRATOR_ID).setCapabilities(IVibrator.CAP_AMPLITUDE_CONTROL);
 
         VibrationEffect effect = VibrationEffect.createWaveform(
@@ -277,6 +291,9 @@
     @Test
     @RequiresFlagsEnabled(Flags.FLAG_ADAPTIVE_HAPTICS_ENABLED)
     public void vibrate_withVibrationParamsRequestStalling_timeoutRequestAndApplyNoScaling() {
+        // No user settings scale.
+        setUserSetting(Settings.System.RING_VIBRATION_INTENSITY,
+                Vibrator.VIBRATION_INTENSITY_MEDIUM);
         mVibratorProviders.get(VIBRATOR_ID).setCapabilities(IVibrator.CAP_AMPLITUDE_CONTROL);
         VibrationEffect effect = VibrationEffect.createWaveform(
                 new long[]{5, 5, 5}, new int[]{1, 1, 1}, -1);
@@ -1864,6 +1881,13 @@
         }
     }
 
+    private void setUserSetting(String settingName, int value) {
+        Settings.System.putIntForUser(
+                mContextSpy.getContentResolver(), settingName, value, UserHandle.USER_CURRENT);
+        // FakeSettingsProvider doesn't support testing triggering ContentObserver yet.
+        mVibrationSettings.mSettingObserver.onChange(false);
+    }
+
     private long startThreadAndDispatcher(VibrationEffect effect) {
         return startThreadAndDispatcher(CombinedVibration.createParallel(effect));
     }
diff --git a/services/tests/vibrator/src/com/android/server/vibrator/VibratorManagerServiceTest.java b/services/tests/vibrator/src/com/android/server/vibrator/VibratorManagerServiceTest.java
index f009229..4013587 100644
--- a/services/tests/vibrator/src/com/android/server/vibrator/VibratorManagerServiceTest.java
+++ b/services/tests/vibrator/src/com/android/server/vibrator/VibratorManagerServiceTest.java
@@ -1622,7 +1622,12 @@
 
         vibrateAndWaitUntilFinished(service, vendorEffect, RINGTONE_ATTRS);
 
-        assertThat(fakeVibrator.getAllVendorEffects()).containsExactly(vendorEffect);
+        // Compare vendor data only, ignore scale applied by device settings in this test.
+        assertThat(fakeVibrator.getAllVendorEffects()).hasSize(1);
+        assertThat(fakeVibrator.getAllVendorEffects().get(0).getVendorData().keySet())
+                .containsExactly("key");
+        assertThat(fakeVibrator.getAllVendorEffects().get(0).getVendorData().getString("key"))
+                .isEqualTo("value");
     }
 
     @Test
@@ -1765,7 +1770,8 @@
         assertThat(fakeVibrator.getAllVendorEffects()).hasSize(1);
         VibrationEffect.VendorEffect scaled = fakeVibrator.getAllVendorEffects().get(0);
         assertThat(scaled.getEffectStrength()).isEqualTo(VibrationEffect.EFFECT_STRENGTH_LIGHT);
-        assertThat(scaled.getLinearScale()).isEqualTo(0.4f);
+        assertThat(scaled.getScale()).isAtMost(1); // Scale down or none if default is LOW
+        assertThat(scaled.getAdaptiveScale()).isEqualTo(0.4f);
     }
 
     @Test
diff --git a/services/tests/vibrator/utils/com/android/server/vibrator/FakeVibratorControllerProvider.java b/services/tests/vibrator/utils/com/android/server/vibrator/FakeVibratorControllerProvider.java
index 96c3e97..031d1c2 100644
--- a/services/tests/vibrator/utils/com/android/server/vibrator/FakeVibratorControllerProvider.java
+++ b/services/tests/vibrator/utils/com/android/server/vibrator/FakeVibratorControllerProvider.java
@@ -140,13 +140,13 @@
 
         @Override
         public long performVendorEffect(Parcel vendorData, long strength, float scale,
-                long vibrationId) {
+                float adaptiveScale, long vibrationId) {
             if ((mCapabilities & IVibrator.CAP_PERFORM_VENDOR_EFFECTS) == 0) {
                 return 0;
             }
             PersistableBundle bundle = PersistableBundle.CREATOR.createFromParcel(vendorData);
             recordVendorEffect(vibrationId,
-                    new VibrationEffect.VendorEffect(bundle, (int) strength, scale));
+                    new VibrationEffect.VendorEffect(bundle, (int) strength, scale, adaptiveScale));
             applyLatency(mOnLatency);
             scheduleListener(mVendorEffectDuration, vibrationId);
             // HAL has unknown duration for vendor effects.
diff --git a/services/tests/wmtests/Android.bp b/services/tests/wmtests/Android.bp
index 6ba2c70..604869c 100644
--- a/services/tests/wmtests/Android.bp
+++ b/services/tests/wmtests/Android.bp
@@ -57,6 +57,7 @@
         "service-permission.stubs.system_server",
         "androidx.test.runner",
         "androidx.test.rules",
+        "flickerlib",
         "junit-params",
         "mockito-target-extended-minus-junit4",
         "platform-test-annotations",
diff --git a/services/tests/wmtests/src/com/android/server/policy/KeyboardSystemShortcutTests.java b/services/tests/wmtests/src/com/android/server/policy/KeyGestureEventTests.java
similarity index 69%
rename from services/tests/wmtests/src/com/android/server/policy/KeyboardSystemShortcutTests.java
rename to services/tests/wmtests/src/com/android/server/policy/KeyGestureEventTests.java
index e26f3e0..8f3adba 100644
--- a/services/tests/wmtests/src/com/android/server/policy/KeyboardSystemShortcutTests.java
+++ b/services/tests/wmtests/src/com/android/server/policy/KeyGestureEventTests.java
@@ -22,7 +22,7 @@
 import static com.android.server.policy.PhoneWindowManager.LONG_PRESS_HOME_NOTIFICATION_PANEL;
 import static com.android.server.policy.PhoneWindowManager.SETTINGS_KEY_BEHAVIOR_NOTIFICATION_PANEL;
 
-import android.hardware.input.KeyboardSystemShortcut;
+import android.hardware.input.KeyGestureEvent;
 import android.platform.test.annotations.Presubmit;
 import android.platform.test.annotations.RequiresFlagsEnabled;
 import android.platform.test.flag.junit.CheckFlagsRule;
@@ -44,7 +44,7 @@
 @Presubmit
 @MediumTest
 @RunWith(JUnitParamsRunner.class)
-public class KeyboardSystemShortcutTests extends ShortcutKeyTestBase {
+public class KeyGestureEventTests extends ShortcutKeyTestBase {
 
     @Rule
     public final CheckFlagsRule mCheckFlagsRule =
@@ -56,315 +56,313 @@
     private static final int ALT_ON = MODIFIER.get(KeyEvent.KEYCODE_ALT_LEFT);
     private static final int CTRL_KEY = KeyEvent.KEYCODE_CTRL_LEFT;
     private static final int CTRL_ON = MODIFIER.get(KeyEvent.KEYCODE_CTRL_LEFT);
-    private static final int SHIFT_KEY = KeyEvent.KEYCODE_SHIFT_LEFT;
-    private static final int SHIFT_ON = MODIFIER.get(KeyEvent.KEYCODE_SHIFT_LEFT);
 
     @Keep
     private static Object[][] shortcutTestArguments() {
-        // testName, testKeys, expectedSystemShortcut, expectedKey, expectedModifierState
+        // testName, testKeys, expectedKeyGestureType, expectedKey, expectedModifierState
         return new Object[][]{
                 {"Meta + H -> Open Home", new int[]{META_KEY, KeyEvent.KEYCODE_H},
-                        KeyboardSystemShortcut.SYSTEM_SHORTCUT_HOME, KeyEvent.KEYCODE_H, META_ON},
+                        KeyGestureEvent.KEY_GESTURE_TYPE_HOME, KeyEvent.KEYCODE_H, META_ON},
                 {"Meta + Enter -> Open Home", new int[]{META_KEY, KeyEvent.KEYCODE_ENTER},
-                        KeyboardSystemShortcut.SYSTEM_SHORTCUT_HOME, KeyEvent.KEYCODE_ENTER,
+                        KeyGestureEvent.KEY_GESTURE_TYPE_HOME, KeyEvent.KEYCODE_ENTER,
                         META_ON},
                 {"HOME key -> Open Home", new int[]{KeyEvent.KEYCODE_HOME},
-                        KeyboardSystemShortcut.SYSTEM_SHORTCUT_HOME,
+                        KeyGestureEvent.KEY_GESTURE_TYPE_HOME,
                         KeyEvent.KEYCODE_HOME, 0},
                 {"RECENT_APPS key -> Open Overview", new int[]{KeyEvent.KEYCODE_RECENT_APPS},
-                        KeyboardSystemShortcut.SYSTEM_SHORTCUT_RECENT_APPS,
+                        KeyGestureEvent.KEY_GESTURE_TYPE_RECENT_APPS,
                         KeyEvent.KEYCODE_RECENT_APPS, 0},
                 {"Meta + Tab -> Open Overview", new int[]{META_KEY, KeyEvent.KEYCODE_TAB},
-                        KeyboardSystemShortcut.SYSTEM_SHORTCUT_RECENT_APPS, KeyEvent.KEYCODE_TAB,
+                        KeyGestureEvent.KEY_GESTURE_TYPE_RECENT_APPS, KeyEvent.KEYCODE_TAB,
                         META_ON},
                 {"Alt + Tab -> Open Overview", new int[]{ALT_KEY, KeyEvent.KEYCODE_TAB},
-                        KeyboardSystemShortcut.SYSTEM_SHORTCUT_RECENT_APPS, KeyEvent.KEYCODE_TAB,
+                        KeyGestureEvent.KEY_GESTURE_TYPE_RECENT_APPS, KeyEvent.KEYCODE_TAB,
                         ALT_ON},
                 {"BACK key -> Go back", new int[]{KeyEvent.KEYCODE_BACK},
-                        KeyboardSystemShortcut.SYSTEM_SHORTCUT_BACK,
+                        KeyGestureEvent.KEY_GESTURE_TYPE_BACK,
                         KeyEvent.KEYCODE_BACK, 0},
                 {"Meta + Escape -> Go back", new int[]{META_KEY, KeyEvent.KEYCODE_ESCAPE},
-                        KeyboardSystemShortcut.SYSTEM_SHORTCUT_BACK, KeyEvent.KEYCODE_ESCAPE,
+                        KeyGestureEvent.KEY_GESTURE_TYPE_BACK, KeyEvent.KEYCODE_ESCAPE,
                         META_ON},
                 {"Meta + Left arrow -> Go back", new int[]{META_KEY, KeyEvent.KEYCODE_DPAD_LEFT},
-                        KeyboardSystemShortcut.SYSTEM_SHORTCUT_BACK, KeyEvent.KEYCODE_DPAD_LEFT,
+                        KeyGestureEvent.KEY_GESTURE_TYPE_BACK, KeyEvent.KEYCODE_DPAD_LEFT,
                         META_ON},
                 {"Meta + Del -> Go back", new int[]{META_KEY, KeyEvent.KEYCODE_DEL},
-                        KeyboardSystemShortcut.SYSTEM_SHORTCUT_BACK, KeyEvent.KEYCODE_DEL, META_ON},
+                        KeyGestureEvent.KEY_GESTURE_TYPE_BACK, KeyEvent.KEYCODE_DEL, META_ON},
                 {"APP_SWITCH key -> Open App switcher", new int[]{KeyEvent.KEYCODE_APP_SWITCH},
-                        KeyboardSystemShortcut.SYSTEM_SHORTCUT_APP_SWITCH,
+                        KeyGestureEvent.KEY_GESTURE_TYPE_APP_SWITCH,
                         KeyEvent.KEYCODE_APP_SWITCH, 0},
                 {"ASSIST key -> Launch assistant", new int[]{KeyEvent.KEYCODE_ASSIST},
-                        KeyboardSystemShortcut.SYSTEM_SHORTCUT_LAUNCH_ASSISTANT,
+                        KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_ASSISTANT,
                         KeyEvent.KEYCODE_ASSIST, 0},
                 {"Meta + A -> Launch assistant", new int[]{META_KEY, KeyEvent.KEYCODE_A},
-                        KeyboardSystemShortcut.SYSTEM_SHORTCUT_LAUNCH_ASSISTANT, KeyEvent.KEYCODE_A,
+                        KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_ASSISTANT, KeyEvent.KEYCODE_A,
                         META_ON},
                 {"VOICE_ASSIST key -> Launch Voice Assistant",
                         new int[]{KeyEvent.KEYCODE_VOICE_ASSIST},
-                        KeyboardSystemShortcut.SYSTEM_SHORTCUT_LAUNCH_VOICE_ASSISTANT,
+                        KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_VOICE_ASSISTANT,
                         KeyEvent.KEYCODE_VOICE_ASSIST, 0},
                 {"Meta + I -> Launch System Settings", new int[]{META_KEY, KeyEvent.KEYCODE_I},
-                        KeyboardSystemShortcut.SYSTEM_SHORTCUT_LAUNCH_SYSTEM_SETTINGS,
+                        KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_SYSTEM_SETTINGS,
                         KeyEvent.KEYCODE_I, META_ON},
                 {"Meta + N -> Toggle Notification panel", new int[]{META_KEY, KeyEvent.KEYCODE_N},
-                        KeyboardSystemShortcut.SYSTEM_SHORTCUT_TOGGLE_NOTIFICATION_PANEL,
+                        KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_NOTIFICATION_PANEL,
                         KeyEvent.KEYCODE_N, META_ON},
                 {"NOTIFICATION key -> Toggle Notification Panel",
                         new int[]{KeyEvent.KEYCODE_NOTIFICATION},
-                        KeyboardSystemShortcut.SYSTEM_SHORTCUT_TOGGLE_NOTIFICATION_PANEL,
+                        KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_NOTIFICATION_PANEL,
                         KeyEvent.KEYCODE_NOTIFICATION,
                         0},
                 {"Meta + Ctrl + S -> Take Screenshot",
                         new int[]{META_KEY, CTRL_KEY, KeyEvent.KEYCODE_S},
-                        KeyboardSystemShortcut.SYSTEM_SHORTCUT_TAKE_SCREENSHOT, KeyEvent.KEYCODE_S,
+                        KeyGestureEvent.KEY_GESTURE_TYPE_TAKE_SCREENSHOT, KeyEvent.KEYCODE_S,
                         META_ON | CTRL_ON},
                 {"Meta + / -> Open Shortcut Helper", new int[]{META_KEY, KeyEvent.KEYCODE_SLASH},
-                        KeyboardSystemShortcut.SYSTEM_SHORTCUT_OPEN_SHORTCUT_HELPER,
+                        KeyGestureEvent.KEY_GESTURE_TYPE_OPEN_SHORTCUT_HELPER,
                         KeyEvent.KEYCODE_SLASH, META_ON},
                 {"BRIGHTNESS_UP key -> Increase Brightness",
                         new int[]{KeyEvent.KEYCODE_BRIGHTNESS_UP},
-                        KeyboardSystemShortcut.SYSTEM_SHORTCUT_BRIGHTNESS_UP,
+                        KeyGestureEvent.KEY_GESTURE_TYPE_BRIGHTNESS_UP,
                         KeyEvent.KEYCODE_BRIGHTNESS_UP, 0},
                 {"BRIGHTNESS_DOWN key -> Decrease Brightness",
                         new int[]{KeyEvent.KEYCODE_BRIGHTNESS_DOWN},
-                        KeyboardSystemShortcut.SYSTEM_SHORTCUT_BRIGHTNESS_DOWN,
+                        KeyGestureEvent.KEY_GESTURE_TYPE_BRIGHTNESS_DOWN,
                         KeyEvent.KEYCODE_BRIGHTNESS_DOWN, 0},
                 {"KEYBOARD_BACKLIGHT_UP key -> Increase Keyboard Backlight",
                         new int[]{KeyEvent.KEYCODE_KEYBOARD_BACKLIGHT_UP},
-                        KeyboardSystemShortcut.SYSTEM_SHORTCUT_KEYBOARD_BACKLIGHT_UP,
+                        KeyGestureEvent.KEY_GESTURE_TYPE_KEYBOARD_BACKLIGHT_UP,
                         KeyEvent.KEYCODE_KEYBOARD_BACKLIGHT_UP, 0},
                 {"KEYBOARD_BACKLIGHT_DOWN key -> Decrease Keyboard Backlight",
                         new int[]{KeyEvent.KEYCODE_KEYBOARD_BACKLIGHT_DOWN},
-                        KeyboardSystemShortcut.SYSTEM_SHORTCUT_KEYBOARD_BACKLIGHT_DOWN,
+                        KeyGestureEvent.KEY_GESTURE_TYPE_KEYBOARD_BACKLIGHT_DOWN,
                         KeyEvent.KEYCODE_KEYBOARD_BACKLIGHT_DOWN, 0},
                 {"KEYBOARD_BACKLIGHT_TOGGLE key -> Toggle Keyboard Backlight",
                         new int[]{KeyEvent.KEYCODE_KEYBOARD_BACKLIGHT_TOGGLE},
-                        KeyboardSystemShortcut.SYSTEM_SHORTCUT_KEYBOARD_BACKLIGHT_TOGGLE,
+                        KeyGestureEvent.KEY_GESTURE_TYPE_KEYBOARD_BACKLIGHT_TOGGLE,
                         KeyEvent.KEYCODE_KEYBOARD_BACKLIGHT_TOGGLE, 0},
                 {"VOLUME_UP key -> Increase Volume", new int[]{KeyEvent.KEYCODE_VOLUME_UP},
-                        KeyboardSystemShortcut.SYSTEM_SHORTCUT_VOLUME_UP,
+                        KeyGestureEvent.KEY_GESTURE_TYPE_VOLUME_UP,
                         KeyEvent.KEYCODE_VOLUME_UP, 0},
                 {"VOLUME_DOWN key -> Decrease Volume", new int[]{KeyEvent.KEYCODE_VOLUME_DOWN},
-                        KeyboardSystemShortcut.SYSTEM_SHORTCUT_VOLUME_DOWN,
+                        KeyGestureEvent.KEY_GESTURE_TYPE_VOLUME_DOWN,
                         KeyEvent.KEYCODE_VOLUME_DOWN, 0},
                 {"VOLUME_MUTE key -> Mute Volume", new int[]{KeyEvent.KEYCODE_VOLUME_MUTE},
-                        KeyboardSystemShortcut.SYSTEM_SHORTCUT_VOLUME_MUTE,
+                        KeyGestureEvent.KEY_GESTURE_TYPE_VOLUME_MUTE,
                         KeyEvent.KEYCODE_VOLUME_MUTE, 0},
                 {"ALL_APPS key -> Open App Drawer in Accessibility mode",
                         new int[]{KeyEvent.KEYCODE_ALL_APPS},
-                        KeyboardSystemShortcut.SYSTEM_SHORTCUT_ACCESSIBILITY_ALL_APPS,
+                        KeyGestureEvent.KEY_GESTURE_TYPE_ACCESSIBILITY_ALL_APPS,
                         KeyEvent.KEYCODE_ALL_APPS, 0},
                 {"SEARCH key -> Launch Search Activity", new int[]{KeyEvent.KEYCODE_SEARCH},
-                        KeyboardSystemShortcut.SYSTEM_SHORTCUT_LAUNCH_SEARCH,
+                        KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_SEARCH,
                         KeyEvent.KEYCODE_SEARCH, 0},
                 {"LANGUAGE_SWITCH key -> Switch Keyboard Language",
                         new int[]{KeyEvent.KEYCODE_LANGUAGE_SWITCH},
-                        KeyboardSystemShortcut.SYSTEM_SHORTCUT_LANGUAGE_SWITCH,
+                        KeyGestureEvent.KEY_GESTURE_TYPE_LANGUAGE_SWITCH,
                         KeyEvent.KEYCODE_LANGUAGE_SWITCH, 0},
                 {"META key -> Open App Drawer in Accessibility mode", new int[]{META_KEY},
-                        KeyboardSystemShortcut.SYSTEM_SHORTCUT_ACCESSIBILITY_ALL_APPS, META_KEY,
+                        KeyGestureEvent.KEY_GESTURE_TYPE_ACCESSIBILITY_ALL_APPS, META_KEY,
                         META_ON},
                 {"Meta + Alt -> Toggle CapsLock", new int[]{META_KEY, ALT_KEY},
-                        KeyboardSystemShortcut.SYSTEM_SHORTCUT_TOGGLE_CAPS_LOCK, ALT_KEY,
+                        KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_CAPS_LOCK, ALT_KEY,
                         META_ON | ALT_ON},
                 {"Alt + Meta -> Toggle CapsLock", new int[]{ALT_KEY, META_KEY},
-                        KeyboardSystemShortcut.SYSTEM_SHORTCUT_TOGGLE_CAPS_LOCK, META_KEY,
+                        KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_CAPS_LOCK, META_KEY,
                         META_ON | ALT_ON},
                 {"CAPS_LOCK key -> Toggle CapsLock", new int[]{KeyEvent.KEYCODE_CAPS_LOCK},
-                        KeyboardSystemShortcut.SYSTEM_SHORTCUT_TOGGLE_CAPS_LOCK,
+                        KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_CAPS_LOCK,
                         KeyEvent.KEYCODE_CAPS_LOCK, 0},
                 {"MUTE key -> Mute System Microphone", new int[]{KeyEvent.KEYCODE_MUTE},
-                        KeyboardSystemShortcut.SYSTEM_SHORTCUT_SYSTEM_MUTE, KeyEvent.KEYCODE_MUTE,
+                        KeyGestureEvent.KEY_GESTURE_TYPE_SYSTEM_MUTE, KeyEvent.KEYCODE_MUTE,
                         0},
                 {"Meta + Ctrl + DPAD_UP -> Split screen navigation",
                         new int[]{META_KEY, CTRL_KEY, KeyEvent.KEYCODE_DPAD_UP},
-                        KeyboardSystemShortcut.SYSTEM_SHORTCUT_MULTI_WINDOW_NAVIGATION,
+                        KeyGestureEvent.KEY_GESTURE_TYPE_MULTI_WINDOW_NAVIGATION,
                         KeyEvent.KEYCODE_DPAD_UP,
                         META_ON | CTRL_ON},
                 {"Meta + Ctrl + DPAD_LEFT -> Split screen navigation",
                         new int[]{META_KEY, CTRL_KEY, KeyEvent.KEYCODE_DPAD_LEFT},
-                        KeyboardSystemShortcut.SYSTEM_SHORTCUT_SPLIT_SCREEN_NAVIGATION,
+                        KeyGestureEvent.KEY_GESTURE_TYPE_SPLIT_SCREEN_NAVIGATION,
                         KeyEvent.KEYCODE_DPAD_LEFT,
                         META_ON | CTRL_ON},
                 {"Meta + Ctrl + DPAD_RIGHT -> Split screen navigation",
                         new int[]{META_KEY, CTRL_KEY, KeyEvent.KEYCODE_DPAD_RIGHT},
-                        KeyboardSystemShortcut.SYSTEM_SHORTCUT_SPLIT_SCREEN_NAVIGATION,
+                        KeyGestureEvent.KEY_GESTURE_TYPE_SPLIT_SCREEN_NAVIGATION,
                         KeyEvent.KEYCODE_DPAD_RIGHT,
                         META_ON | CTRL_ON},
                 {"Meta + L -> Lock Homescreen", new int[]{META_KEY, KeyEvent.KEYCODE_L},
-                        KeyboardSystemShortcut.SYSTEM_SHORTCUT_LOCK_SCREEN, KeyEvent.KEYCODE_L,
+                        KeyGestureEvent.KEY_GESTURE_TYPE_LOCK_SCREEN, KeyEvent.KEYCODE_L,
                         META_ON},
                 {"Meta + Ctrl + N -> Open Notes", new int[]{META_KEY, CTRL_KEY, KeyEvent.KEYCODE_N},
-                        KeyboardSystemShortcut.SYSTEM_SHORTCUT_OPEN_NOTES, KeyEvent.KEYCODE_N,
+                        KeyGestureEvent.KEY_GESTURE_TYPE_OPEN_NOTES, KeyEvent.KEYCODE_N,
                         META_ON | CTRL_ON},
                 {"POWER key -> Toggle Power", new int[]{KeyEvent.KEYCODE_POWER},
-                        KeyboardSystemShortcut.SYSTEM_SHORTCUT_TOGGLE_POWER, KeyEvent.KEYCODE_POWER,
+                        KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_POWER, KeyEvent.KEYCODE_POWER,
                         0},
                 {"TV_POWER key -> Toggle Power", new int[]{KeyEvent.KEYCODE_TV_POWER},
-                        KeyboardSystemShortcut.SYSTEM_SHORTCUT_TOGGLE_POWER,
+                        KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_POWER,
                         KeyEvent.KEYCODE_TV_POWER, 0},
                 {"SYSTEM_NAVIGATION_DOWN key -> System Navigation",
                         new int[]{KeyEvent.KEYCODE_SYSTEM_NAVIGATION_DOWN},
-                        KeyboardSystemShortcut.SYSTEM_SHORTCUT_SYSTEM_NAVIGATION,
+                        KeyGestureEvent.KEY_GESTURE_TYPE_SYSTEM_NAVIGATION,
                         KeyEvent.KEYCODE_SYSTEM_NAVIGATION_DOWN,
                         0},
                 {"SYSTEM_NAVIGATION_UP key -> System Navigation",
                         new int[]{KeyEvent.KEYCODE_SYSTEM_NAVIGATION_UP},
-                        KeyboardSystemShortcut.SYSTEM_SHORTCUT_SYSTEM_NAVIGATION,
+                        KeyGestureEvent.KEY_GESTURE_TYPE_SYSTEM_NAVIGATION,
                         KeyEvent.KEYCODE_SYSTEM_NAVIGATION_UP,
                         0},
                 {"SYSTEM_NAVIGATION_LEFT key -> System Navigation",
                         new int[]{KeyEvent.KEYCODE_SYSTEM_NAVIGATION_LEFT},
-                        KeyboardSystemShortcut.SYSTEM_SHORTCUT_SYSTEM_NAVIGATION,
+                        KeyGestureEvent.KEY_GESTURE_TYPE_SYSTEM_NAVIGATION,
                         KeyEvent.KEYCODE_SYSTEM_NAVIGATION_LEFT,
                         0},
                 {"SYSTEM_NAVIGATION_RIGHT key -> System Navigation",
                         new int[]{KeyEvent.KEYCODE_SYSTEM_NAVIGATION_RIGHT},
-                        KeyboardSystemShortcut.SYSTEM_SHORTCUT_SYSTEM_NAVIGATION,
+                        KeyGestureEvent.KEY_GESTURE_TYPE_SYSTEM_NAVIGATION,
                         KeyEvent.KEYCODE_SYSTEM_NAVIGATION_RIGHT, 0},
                 {"SLEEP key -> System Sleep", new int[]{KeyEvent.KEYCODE_SLEEP},
-                        KeyboardSystemShortcut.SYSTEM_SHORTCUT_SLEEP, KeyEvent.KEYCODE_SLEEP, 0},
+                        KeyGestureEvent.KEY_GESTURE_TYPE_SLEEP, KeyEvent.KEYCODE_SLEEP, 0},
                 {"SOFT_SLEEP key -> System Sleep", new int[]{KeyEvent.KEYCODE_SOFT_SLEEP},
-                        KeyboardSystemShortcut.SYSTEM_SHORTCUT_SLEEP, KeyEvent.KEYCODE_SOFT_SLEEP,
+                        KeyGestureEvent.KEY_GESTURE_TYPE_SLEEP, KeyEvent.KEYCODE_SOFT_SLEEP,
                         0},
                 {"WAKEUP key -> System Wakeup", new int[]{KeyEvent.KEYCODE_WAKEUP},
-                        KeyboardSystemShortcut.SYSTEM_SHORTCUT_WAKEUP, KeyEvent.KEYCODE_WAKEUP, 0},
+                        KeyGestureEvent.KEY_GESTURE_TYPE_WAKEUP, KeyEvent.KEYCODE_WAKEUP, 0},
                 {"MEDIA_PLAY key -> Media Control", new int[]{KeyEvent.KEYCODE_MEDIA_PLAY},
-                        KeyboardSystemShortcut.SYSTEM_SHORTCUT_MEDIA_KEY,
+                        KeyGestureEvent.KEY_GESTURE_TYPE_MEDIA_KEY,
                         KeyEvent.KEYCODE_MEDIA_PLAY, 0},
                 {"MEDIA_PAUSE key -> Media Control", new int[]{KeyEvent.KEYCODE_MEDIA_PAUSE},
-                        KeyboardSystemShortcut.SYSTEM_SHORTCUT_MEDIA_KEY,
+                        KeyGestureEvent.KEY_GESTURE_TYPE_MEDIA_KEY,
                         KeyEvent.KEYCODE_MEDIA_PAUSE, 0},
                 {"MEDIA_PLAY_PAUSE key -> Media Control",
                         new int[]{KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE},
-                        KeyboardSystemShortcut.SYSTEM_SHORTCUT_MEDIA_KEY,
+                        KeyGestureEvent.KEY_GESTURE_TYPE_MEDIA_KEY,
                         KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE, 0},
                 {"Meta + B -> Launch Default Browser", new int[]{META_KEY, KeyEvent.KEYCODE_B},
-                        KeyboardSystemShortcut.SYSTEM_SHORTCUT_LAUNCH_DEFAULT_BROWSER,
+                        KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_DEFAULT_BROWSER,
                         KeyEvent.KEYCODE_B, META_ON},
                 {"EXPLORER key -> Launch Default Browser", new int[]{KeyEvent.KEYCODE_EXPLORER},
-                        KeyboardSystemShortcut.SYSTEM_SHORTCUT_LAUNCH_DEFAULT_BROWSER,
+                        KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_DEFAULT_BROWSER,
                         KeyEvent.KEYCODE_EXPLORER, 0},
                 {"Meta + C -> Launch Default Contacts", new int[]{META_KEY, KeyEvent.KEYCODE_C},
-                        KeyboardSystemShortcut.SYSTEM_SHORTCUT_LAUNCH_DEFAULT_CONTACTS,
+                        KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_DEFAULT_CONTACTS,
                         KeyEvent.KEYCODE_C, META_ON},
                 {"CONTACTS key -> Launch Default Contacts", new int[]{KeyEvent.KEYCODE_CONTACTS},
-                        KeyboardSystemShortcut.SYSTEM_SHORTCUT_LAUNCH_DEFAULT_CONTACTS,
+                        KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_DEFAULT_CONTACTS,
                         KeyEvent.KEYCODE_CONTACTS, 0},
                 {"Meta + E -> Launch Default Email", new int[]{META_KEY, KeyEvent.KEYCODE_E},
-                        KeyboardSystemShortcut.SYSTEM_SHORTCUT_LAUNCH_DEFAULT_EMAIL,
+                        KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_DEFAULT_EMAIL,
                         KeyEvent.KEYCODE_E, META_ON},
                 {"ENVELOPE key -> Launch Default Email", new int[]{KeyEvent.KEYCODE_ENVELOPE},
-                        KeyboardSystemShortcut.SYSTEM_SHORTCUT_LAUNCH_DEFAULT_EMAIL,
+                        KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_DEFAULT_EMAIL,
                         KeyEvent.KEYCODE_ENVELOPE, 0},
                 {"Meta + K -> Launch Default Calendar", new int[]{META_KEY, KeyEvent.KEYCODE_K},
-                        KeyboardSystemShortcut.SYSTEM_SHORTCUT_LAUNCH_DEFAULT_CALENDAR,
+                        KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_DEFAULT_CALENDAR,
                         KeyEvent.KEYCODE_K, META_ON},
                 {"CALENDAR key -> Launch Default Calendar", new int[]{KeyEvent.KEYCODE_CALENDAR},
-                        KeyboardSystemShortcut.SYSTEM_SHORTCUT_LAUNCH_DEFAULT_CALENDAR,
+                        KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_DEFAULT_CALENDAR,
                         KeyEvent.KEYCODE_CALENDAR, 0},
                 {"Meta + P -> Launch Default Music", new int[]{META_KEY, KeyEvent.KEYCODE_P},
-                        KeyboardSystemShortcut.SYSTEM_SHORTCUT_LAUNCH_DEFAULT_MUSIC,
+                        KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_DEFAULT_MUSIC,
                         KeyEvent.KEYCODE_P, META_ON},
                 {"MUSIC key -> Launch Default Music", new int[]{KeyEvent.KEYCODE_MUSIC},
-                        KeyboardSystemShortcut.SYSTEM_SHORTCUT_LAUNCH_DEFAULT_MUSIC,
+                        KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_DEFAULT_MUSIC,
                         KeyEvent.KEYCODE_MUSIC, 0},
                 {"Meta + U -> Launch Default Calculator", new int[]{META_KEY, KeyEvent.KEYCODE_U},
-                        KeyboardSystemShortcut.SYSTEM_SHORTCUT_LAUNCH_DEFAULT_CALCULATOR,
+                        KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_DEFAULT_CALCULATOR,
                         KeyEvent.KEYCODE_U, META_ON},
                 {"CALCULATOR key -> Launch Default Calculator",
                         new int[]{KeyEvent.KEYCODE_CALCULATOR},
-                        KeyboardSystemShortcut.SYSTEM_SHORTCUT_LAUNCH_DEFAULT_CALCULATOR,
+                        KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_DEFAULT_CALCULATOR,
                         KeyEvent.KEYCODE_CALCULATOR, 0},
                 {"Meta + M -> Launch Default Maps", new int[]{META_KEY, KeyEvent.KEYCODE_M},
-                        KeyboardSystemShortcut.SYSTEM_SHORTCUT_LAUNCH_DEFAULT_MAPS,
+                        KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_DEFAULT_MAPS,
                         KeyEvent.KEYCODE_M, META_ON},
                 {"Meta + S -> Launch Default Messaging App",
                         new int[]{META_KEY, KeyEvent.KEYCODE_S},
-                        KeyboardSystemShortcut.SYSTEM_SHORTCUT_LAUNCH_DEFAULT_MESSAGING,
+                        KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_DEFAULT_MESSAGING,
                         KeyEvent.KEYCODE_S, META_ON},
                 {"Meta + Ctrl + DPAD_DOWN -> Enter desktop mode",
                         new int[]{META_KEY, CTRL_KEY, KeyEvent.KEYCODE_DPAD_DOWN},
-                        KeyboardSystemShortcut.SYSTEM_SHORTCUT_DESKTOP_MODE,
+                        KeyGestureEvent.KEY_GESTURE_TYPE_DESKTOP_MODE,
                         KeyEvent.KEYCODE_DPAD_DOWN,
                         META_ON | CTRL_ON}};
     }
 
     @Keep
     private static Object[][] longPressOnHomeTestArguments() {
-        // testName, testKeys, longPressOnHomeBehavior, expectedSystemShortcut, expectedKey,
+        // testName, testKeys, longPressOnHomeBehavior, expectedKeyGestureType, expectedKey,
         // expectedModifierState
         return new Object[][]{
                 {"Long press HOME key -> Toggle Notification panel",
                         new int[]{KeyEvent.KEYCODE_HOME}, LONG_PRESS_HOME_NOTIFICATION_PANEL,
-                        KeyboardSystemShortcut.SYSTEM_SHORTCUT_TOGGLE_NOTIFICATION_PANEL,
+                        KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_NOTIFICATION_PANEL,
                         KeyEvent.KEYCODE_HOME, 0},
                 {"Long press META + ENTER -> Toggle Notification panel",
                         new int[]{META_KEY, KeyEvent.KEYCODE_ENTER},
                         LONG_PRESS_HOME_NOTIFICATION_PANEL,
-                        KeyboardSystemShortcut.SYSTEM_SHORTCUT_TOGGLE_NOTIFICATION_PANEL,
+                        KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_NOTIFICATION_PANEL,
                         KeyEvent.KEYCODE_ENTER,
                         META_ON},
                 {"Long press META + H -> Toggle Notification panel",
                         new int[]{META_KEY, KeyEvent.KEYCODE_H}, LONG_PRESS_HOME_NOTIFICATION_PANEL,
-                        KeyboardSystemShortcut.SYSTEM_SHORTCUT_TOGGLE_NOTIFICATION_PANEL,
+                        KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_NOTIFICATION_PANEL,
                         KeyEvent.KEYCODE_H, META_ON},
                 {"Long press HOME key -> Launch assistant",
                         new int[]{KeyEvent.KEYCODE_HOME}, LONG_PRESS_HOME_ASSIST,
-                        KeyboardSystemShortcut.SYSTEM_SHORTCUT_LAUNCH_ASSISTANT,
+                        KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_ASSISTANT,
                         KeyEvent.KEYCODE_HOME, 0},
                 {"Long press META + ENTER -> Launch assistant",
                         new int[]{META_KEY, KeyEvent.KEYCODE_ENTER}, LONG_PRESS_HOME_ASSIST,
-                        KeyboardSystemShortcut.SYSTEM_SHORTCUT_LAUNCH_ASSISTANT,
+                        KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_ASSISTANT,
                         KeyEvent.KEYCODE_ENTER, META_ON},
                 {"Long press META + H -> Launch assistant",
                         new int[]{META_KEY, KeyEvent.KEYCODE_H}, LONG_PRESS_HOME_ASSIST,
-                        KeyboardSystemShortcut.SYSTEM_SHORTCUT_LAUNCH_ASSISTANT, KeyEvent.KEYCODE_H,
+                        KeyGestureEvent.KEY_GESTURE_TYPE_LAUNCH_ASSISTANT, KeyEvent.KEYCODE_H,
                         META_ON},
                 {"Long press HOME key -> Open App Drawer in Accessibility mode",
                         new int[]{KeyEvent.KEYCODE_HOME}, LONG_PRESS_HOME_ALL_APPS,
-                        KeyboardSystemShortcut.SYSTEM_SHORTCUT_ACCESSIBILITY_ALL_APPS,
+                        KeyGestureEvent.KEY_GESTURE_TYPE_ACCESSIBILITY_ALL_APPS,
                         KeyEvent.KEYCODE_HOME, 0},
                 {"Long press META + ENTER -> Open App Drawer in Accessibility mode",
                         new int[]{META_KEY, KeyEvent.KEYCODE_ENTER}, LONG_PRESS_HOME_ALL_APPS,
-                        KeyboardSystemShortcut.SYSTEM_SHORTCUT_ACCESSIBILITY_ALL_APPS,
+                        KeyGestureEvent.KEY_GESTURE_TYPE_ACCESSIBILITY_ALL_APPS,
                         KeyEvent.KEYCODE_ENTER, META_ON},
                 {"Long press META + H -> Open App Drawer in Accessibility mode",
                         new int[]{META_KEY, KeyEvent.KEYCODE_H},
                         LONG_PRESS_HOME_ALL_APPS,
-                        KeyboardSystemShortcut.SYSTEM_SHORTCUT_ACCESSIBILITY_ALL_APPS,
+                        KeyGestureEvent.KEY_GESTURE_TYPE_ACCESSIBILITY_ALL_APPS,
                         KeyEvent.KEYCODE_H, META_ON}};
     }
 
     @Keep
     private static Object[][] doubleTapOnHomeTestArguments() {
-        // testName, testKeys, doubleTapOnHomeBehavior, expectedSystemShortcut, expectedKey,
+        // testName, testKeys, doubleTapOnHomeBehavior, expectedKeyGestureType, expectedKey,
         // expectedModifierState
         return new Object[][]{
                 {"Double tap HOME -> Open App switcher",
                         new int[]{KeyEvent.KEYCODE_HOME}, DOUBLE_TAP_HOME_RECENT_SYSTEM_UI,
-                        KeyboardSystemShortcut.SYSTEM_SHORTCUT_APP_SWITCH, KeyEvent.KEYCODE_HOME,
+                        KeyGestureEvent.KEY_GESTURE_TYPE_APP_SWITCH, KeyEvent.KEYCODE_HOME,
                         0},
                 {"Double tap META + ENTER -> Open App switcher",
                         new int[]{META_KEY, KeyEvent.KEYCODE_ENTER},
                         DOUBLE_TAP_HOME_RECENT_SYSTEM_UI,
-                        KeyboardSystemShortcut.SYSTEM_SHORTCUT_APP_SWITCH,
+                        KeyGestureEvent.KEY_GESTURE_TYPE_APP_SWITCH,
                         KeyEvent.KEYCODE_ENTER, META_ON},
                 {"Double tap META + H -> Open App switcher",
                         new int[]{META_KEY, KeyEvent.KEYCODE_H}, DOUBLE_TAP_HOME_RECENT_SYSTEM_UI,
-                        KeyboardSystemShortcut.SYSTEM_SHORTCUT_APP_SWITCH, KeyEvent.KEYCODE_H,
+                        KeyGestureEvent.KEY_GESTURE_TYPE_APP_SWITCH, KeyEvent.KEYCODE_H,
                         META_ON}};
     }
 
     @Keep
     private static Object[][] settingsKeyTestArguments() {
-        // testName, testKeys, settingsKeyBehavior, expectedSystemShortcut, expectedKey,
+        // testName, testKeys, settingsKeyBehavior, expectedKeyGestureType, expectedKey,
         // expectedModifierState
         return new Object[][]{
                 {"SETTINGS key -> Toggle Notification panel", new int[]{KeyEvent.KEYCODE_SETTINGS},
                         SETTINGS_KEY_BEHAVIOR_NOTIFICATION_PANEL,
-                        KeyboardSystemShortcut.SYSTEM_SHORTCUT_TOGGLE_NOTIFICATION_PANEL,
+                        KeyGestureEvent.KEY_GESTURE_TYPE_TOGGLE_NOTIFICATION_PANEL,
                         KeyEvent.KEYCODE_SETTINGS, 0}};
     }
 
@@ -387,21 +385,21 @@
     @Test
     @Parameters(method = "shortcutTestArguments")
     public void testShortcut(String testName, int[] testKeys,
-            @KeyboardSystemShortcut.SystemShortcut int expectedSystemShortcut, int expectedKey,
+            @KeyGestureEvent.KeyGestureType int expectedKeyGestureType, int expectedKey,
             int expectedModifierState) {
-        testShortcutInternal(testName, testKeys, expectedSystemShortcut, expectedKey,
+        testShortcutInternal(testName, testKeys, expectedKeyGestureType, expectedKey,
                 expectedModifierState);
     }
 
     @Test
     @Parameters(method = "longPressOnHomeTestArguments")
     public void testLongPressOnHome(String testName, int[] testKeys, int longPressOnHomeBehavior,
-            @KeyboardSystemShortcut.SystemShortcut int expectedSystemShortcut, int expectedKey,
+            @KeyGestureEvent.KeyGestureType int expectedKeyGestureType, int expectedKey,
             int expectedModifierState) {
         mPhoneWindowManager.overrideLongPressOnHomeBehavior(longPressOnHomeBehavior);
         sendLongPressKeyCombination(testKeys);
-        mPhoneWindowManager.assertKeyboardShortcutTriggered(
-                new int[]{expectedKey}, expectedModifierState, expectedSystemShortcut,
+        mPhoneWindowManager.assertKeyGestureCompleted(
+                new int[]{expectedKey}, expectedModifierState, expectedKeyGestureType,
                 "Failed while executing " + testName);
     }
 
@@ -409,23 +407,23 @@
     @Parameters(method = "doubleTapOnHomeTestArguments")
     public void testDoubleTapOnHomeBehavior(String testName, int[] testKeys,
             int doubleTapOnHomeBehavior,
-            @KeyboardSystemShortcut.SystemShortcut int expectedSystemShortcut, int expectedKey,
+            @KeyGestureEvent.KeyGestureType int expectedKeyGestureType, int expectedKey,
             int expectedModifierState) {
         mPhoneWindowManager.overriderDoubleTapOnHomeBehavior(doubleTapOnHomeBehavior);
         sendKeyCombination(testKeys, 0 /* duration */);
         sendKeyCombination(testKeys, 0 /* duration */);
-        mPhoneWindowManager.assertKeyboardShortcutTriggered(
-                new int[]{expectedKey}, expectedModifierState, expectedSystemShortcut,
+        mPhoneWindowManager.assertKeyGestureCompleted(
+                new int[]{expectedKey}, expectedModifierState, expectedKeyGestureType,
                 "Failed while executing " + testName);
     }
 
     @Test
     @Parameters(method = "settingsKeyTestArguments")
     public void testSettingsKey(String testName, int[] testKeys, int settingsKeyBehavior,
-            @KeyboardSystemShortcut.SystemShortcut int expectedSystemShortcut, int expectedKey,
+            @KeyGestureEvent.KeyGestureType int expectedKeyGestureType, int expectedKey,
             int expectedModifierState) {
         mPhoneWindowManager.overrideSettingsKeyBehavior(settingsKeyBehavior);
-        testShortcutInternal(testName, testKeys, expectedSystemShortcut, expectedKey,
+        testShortcutInternal(testName, testKeys, expectedKeyGestureType, expectedKey,
                 expectedModifierState);
     }
 
@@ -434,16 +432,16 @@
     public void testBugreportShortcutPress() {
         testShortcutInternal("Meta + Ctrl + Del -> Trigger bug report",
                 new int[]{META_KEY, CTRL_KEY, KeyEvent.KEYCODE_DEL},
-                KeyboardSystemShortcut.SYSTEM_SHORTCUT_TRIGGER_BUG_REPORT, KeyEvent.KEYCODE_DEL,
+                KeyGestureEvent.KEY_GESTURE_TYPE_TRIGGER_BUG_REPORT, KeyEvent.KEYCODE_DEL,
                 META_ON | CTRL_ON);
     }
 
     private void testShortcutInternal(String testName, int[] testKeys,
-            @KeyboardSystemShortcut.SystemShortcut int expectedSystemShortcut, int expectedKey,
+            @KeyGestureEvent.KeyGestureType int expectedKeyGestureType, int expectedKey,
             int expectedModifierState) {
         sendKeyCombination(testKeys, 0 /* duration */);
-        mPhoneWindowManager.assertKeyboardShortcutTriggered(
-                new int[]{expectedKey}, expectedModifierState, expectedSystemShortcut,
+        mPhoneWindowManager.assertKeyGestureCompleted(
+                new int[]{expectedKey}, expectedModifierState, expectedKeyGestureType,
                 "Failed while executing " + testName);
     }
 }
diff --git a/services/tests/wmtests/src/com/android/server/policy/MetaKeyEventsInterceptionTests.java b/services/tests/wmtests/src/com/android/server/policy/MetaKeyEventsInterceptionTests.java
new file mode 100644
index 0000000..b979335
--- /dev/null
+++ b/services/tests/wmtests/src/com/android/server/policy/MetaKeyEventsInterceptionTests.java
@@ -0,0 +1,96 @@
+/*
+ * 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.policy;
+
+import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_ALLOW_ACTION_KEY_EVENTS;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.view.KeyEvent;
+import android.view.WindowManager;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.internal.policy.KeyInterceptionInfo;
+
+import org.junit.Before;
+import org.junit.Test;
+
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ * Testing {@link PhoneWindowManager} functionality of letting app intercepting key events
+ * containing META.
+ */
+@SmallTest
+public class MetaKeyEventsInterceptionTests extends ShortcutKeyTestBase {
+
+    private static final List<KeyEvent> META_KEY_EVENTS = Arrays.asList(
+            new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_META_LEFT),
+            new KeyEvent(KeyEvent.ACTION_DOWN, KeyEvent.KEYCODE_META_RIGHT),
+            new KeyEvent(/* downTime= */ 0, /* eventTime= */
+                    0, /* action= */ 0, /* code= */ 0, /* repeat= */ 0,
+                    /* metaState= */ KeyEvent.META_META_ON));
+
+    @Before
+    public void setUp() {
+        setUpPhoneWindowManager();
+    }
+
+    @Test
+    public void doesntInterceptMetaKeyEvents_whenWindowAskedForIt() {
+        mPhoneWindowManager.overrideFocusedWindowButtonOverridePermission(/* granted= */ true);
+        setWindowKeyInterceptionWithPrivateFlags(PRIVATE_FLAG_ALLOW_ACTION_KEY_EVENTS);
+
+        META_KEY_EVENTS.forEach(keyEvent -> {
+            assertKeyInterceptionResult(keyEvent, /* intercepted= */ false);
+        });
+    }
+
+    @Test
+    public void interceptsMetaKeyEvents_whenWindowDoesntHaveFlagSet() {
+        mPhoneWindowManager.overrideFocusedWindowButtonOverridePermission(/* granted= */ true);
+        setWindowKeyInterceptionWithPrivateFlags(0);
+
+        META_KEY_EVENTS.forEach(keyEvent -> {
+            assertKeyInterceptionResult(keyEvent, /* intercepted= */ true);
+        });
+    }
+
+    @Test
+    public void interceptsMetaKeyEvents_whenWindowDoesntHavePermission() {
+        mPhoneWindowManager.overrideFocusedWindowButtonOverridePermission(/* granted= */ false);
+        setWindowKeyInterceptionWithPrivateFlags(PRIVATE_FLAG_ALLOW_ACTION_KEY_EVENTS);
+
+        META_KEY_EVENTS.forEach(keyEvent -> {
+            assertKeyInterceptionResult(keyEvent, /* intercepted= */ true);
+        });
+    }
+
+    private void setWindowKeyInterceptionWithPrivateFlags(int privateFlags) {
+        KeyInterceptionInfo info = new KeyInterceptionInfo(
+                WindowManager.LayoutParams.TYPE_APPLICATION, privateFlags, "title", 0);
+        mPhoneWindowManager.overrideWindowKeyInterceptionInfo(info);
+    }
+
+    private void assertKeyInterceptionResult(KeyEvent keyEvent, boolean intercepted) {
+        long result = mPhoneWindowManager.interceptKeyBeforeDispatching(keyEvent);
+        int expected = intercepted ? -1 : 0;
+        assertThat(result).isEqualTo(expected);
+    }
+}
diff --git a/services/tests/wmtests/src/com/android/server/policy/TestPhoneWindowManager.java b/services/tests/wmtests/src/com/android/server/policy/TestPhoneWindowManager.java
index f9b5c2a..79c7ac1 100644
--- a/services/tests/wmtests/src/com/android/server/policy/TestPhoneWindowManager.java
+++ b/services/tests/wmtests/src/com/android/server/policy/TestPhoneWindowManager.java
@@ -52,6 +52,7 @@
 import static org.mockito.Mockito.description;
 import static org.mockito.Mockito.mockingDetails;
 import static org.mockito.Mockito.timeout;
+import static org.mockito.Mockito.when;
 import static org.mockito.Mockito.withSettings;
 
 import android.app.ActivityManagerInternal;
@@ -70,7 +71,6 @@
 import android.hardware.display.DisplayManager;
 import android.hardware.display.DisplayManagerInternal;
 import android.hardware.input.InputManager;
-import android.hardware.input.KeyboardSystemShortcut;
 import android.media.AudioManagerInternal;
 import android.os.Handler;
 import android.os.HandlerThread;
@@ -614,6 +614,10 @@
                 .when(mButtonOverridePermissionChecker).canAppOverrideSystemKey(any(), anyInt());
     }
 
+    void overrideWindowKeyInterceptionInfo(KeyInterceptionInfo info) {
+        when(mWindowManagerInternal.getKeyInterceptionInfoFromToken(any())).thenReturn(info);
+    }
+
     void overrideKeyEventPolicyFlags(int flags) {
         mKeyEventPolicyFlags = flags;
     }
@@ -804,11 +808,11 @@
         Assert.assertEquals(targetActivity, intentCaptor.getValue().getComponent());
     }
 
-    void assertKeyboardShortcutTriggered(int[] keycodes, int modifierState, int systemShortcut,
+    void assertKeyGestureCompleted(int[] keycodes, int modifierState, int gestureType,
             String errorMsg) {
         mTestLooper.dispatchAll();
-        verify(mInputManagerInternal, description(errorMsg)).notifyKeyboardShortcutTriggered(
-                anyInt(), eq(keycodes), eq(modifierState), eq(systemShortcut));
+        verify(mInputManagerInternal, description(errorMsg)).notifyKeyGestureCompleted(
+                anyInt(), eq(keycodes), eq(modifierState), eq(gestureType));
     }
 
     void assertSwitchToTask(int persistentId) throws RemoteException {
diff --git a/services/tests/wmtests/src/com/android/server/wm/AppCompatActivityRobot.java b/services/tests/wmtests/src/com/android/server/wm/AppCompatActivityRobot.java
index 1fa6868..fee9c6c 100644
--- a/services/tests/wmtests/src/com/android/server/wm/AppCompatActivityRobot.java
+++ b/services/tests/wmtests/src/com/android/server/wm/AppCompatActivityRobot.java
@@ -31,10 +31,10 @@
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.verify;
 
-import android.app.WindowConfiguration;
+import android.app.WindowConfiguration.WindowingMode;
 import android.content.ComponentName;
 import android.content.pm.ActivityInfo;
-import android.content.pm.PackageManager;
+import android.content.pm.PackageManager.UserMinAspectRatio;
 import android.content.res.Configuration;
 import android.graphics.Rect;
 import android.view.Surface;
@@ -176,10 +176,14 @@
         return mActivityStack.getFromTop(fromTop);
     }
 
-    void setTaskWindowingMode(@WindowConfiguration.WindowingMode int windowingMode) {
+    void setTaskWindowingMode(@WindowingMode int windowingMode) {
         mTaskStack.top().setWindowingMode(windowingMode);
     }
 
+    void setTaskDisplayAreaWindowingMode(@WindowingMode int windowingMode) {
+        mTaskStack.top().getDisplayArea().setWindowingMode(windowingMode);
+    }
+
     void setLetterboxedForFixedOrientationAndAspectRatio(boolean enabled) {
         doReturn(enabled).when(mActivityStack.top().mAppCompatController
                 .getAppCompatAspectRatioPolicy()).isLetterboxedForFixedOrientationAndAspectRatio();
@@ -222,10 +226,14 @@
                 .getAppCompatAspectRatioOverrides()).shouldApplyUserFullscreenOverride();
     }
 
-    void setGetUserMinAspectRatioOverrideCode(@PackageManager.UserMinAspectRatio int orientation) {
-        doReturn(orientation).when(mActivityStack.top()
-                .mAppCompatController.getAppCompatAspectRatioOverrides())
-                .getUserMinAspectRatioOverrideCode();
+    void setGetUserMinAspectRatioOverrideCode(@UserMinAspectRatio int overrideCode) {
+        doReturn(overrideCode).when(mActivityStack.top().mAppCompatController
+                .getAppCompatAspectRatioOverrides()).getUserMinAspectRatioOverrideCode();
+    }
+
+    void setGetUserMinAspectRatioOverrideValue(float overrideValue) {
+        doReturn(overrideValue).when(mActivityStack.top().mAppCompatController
+                .getAppCompatAspectRatioOverrides()).getUserMinAspectRatio();
     }
 
     void setIgnoreOrientationRequest(boolean enabled) {
@@ -233,8 +241,7 @@
     }
 
     void setTopTaskInMultiWindowMode(boolean inMultiWindowMode) {
-        doReturn(inMultiWindowMode).when(mTaskStack.top())
-                .inMultiWindowMode();
+        doReturn(inMultiWindowMode).when(mTaskStack.top()).inMultiWindowMode();
     }
 
     void setTopActivityAsEmbedded(boolean embedded) {
diff --git a/services/tests/wmtests/src/com/android/server/wm/AppCompatConfigurationRobot.java b/services/tests/wmtests/src/com/android/server/wm/AppCompatConfigurationRobot.java
index 40a5347..7760051 100644
--- a/services/tests/wmtests/src/com/android/server/wm/AppCompatConfigurationRobot.java
+++ b/services/tests/wmtests/src/com/android/server/wm/AppCompatConfigurationRobot.java
@@ -52,6 +52,11 @@
         doReturn(enabled).when(mAppCompatConfiguration).isCameraCompatTreatmentEnabled();
     }
 
+    void enableSplitScreenAspectRatioForUnresizableApps(boolean enabled) {
+        doReturn(enabled).when(mAppCompatConfiguration)
+                .getIsSplitScreenAspectRatioForUnresizableAppsEnabled();
+    }
+
     void enableCameraCompatTreatmentAtBuildTime(boolean enabled) {
         doReturn(enabled).when(mAppCompatConfiguration)
                 .isCameraCompatTreatmentEnabledAtBuildTime();
diff --git a/services/tests/wmtests/src/com/android/server/wm/DesktopAppCompatAspectRatioPolicyTests.java b/services/tests/wmtests/src/com/android/server/wm/DesktopAppCompatAspectRatioPolicyTests.java
new file mode 100644
index 0000000..f4e1d49
--- /dev/null
+++ b/services/tests/wmtests/src/com/android/server/wm/DesktopAppCompatAspectRatioPolicyTests.java
@@ -0,0 +1,494 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm;
+
+import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
+import static android.content.pm.ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO_LARGE_VALUE;
+import static android.content.pm.ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO_MEDIUM_VALUE;
+import static android.content.pm.ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO_SMALL_VALUE;
+import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_PORTRAIT;
+import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED;
+import static android.content.pm.PackageManager.USER_MIN_ASPECT_RATIO_16_9;
+import static android.content.pm.PackageManager.USER_MIN_ASPECT_RATIO_3_2;
+import static android.content.pm.PackageManager.USER_MIN_ASPECT_RATIO_4_3;
+import static android.content.pm.PackageManager.USER_MIN_ASPECT_RATIO_DISPLAY_SIZE;
+import static android.content.pm.PackageManager.USER_MIN_ASPECT_RATIO_SPLIT_SCREEN;
+import static android.view.WindowManager.PROPERTY_COMPAT_ALLOW_MIN_ASPECT_RATIO_OVERRIDE;
+
+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.AppCompatConfiguration.DEFAULT_LETTERBOX_ASPECT_RATIO_FOR_MULTI_WINDOW;
+
+import static org.junit.Assert.assertEquals;
+import static org.mockito.ArgumentMatchers.any;
+
+import android.compat.testing.PlatformCompatChangeRule;
+import android.content.pm.ActivityInfo;
+import android.graphics.Rect;
+import android.platform.test.annotations.Presubmit;
+
+import androidx.annotation.NonNull;
+
+import libcore.junit.util.compat.CoreCompatChangeRule.DisableCompatChanges;
+import libcore.junit.util.compat.CoreCompatChangeRule.EnableCompatChanges;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TestRule;
+import org.junit.runner.RunWith;
+
+import java.util.function.Consumer;
+
+/**
+ * Test class for {@link DesktopAppCompatAspectRatioPolicy}.
+ * <p>
+ * Build/Install/Run:
+ * atest WmTests:DesktopAppCompatAspectRatioPolicyTests
+ */
+@Presubmit
+@RunWith(WindowTestRunner.class)
+public class DesktopAppCompatAspectRatioPolicyTests extends WindowTestsBase {
+    @Rule
+    public TestRule compatChangeRule = new PlatformCompatChangeRule();
+
+    private static final float FIXED_ORIENTATION_LETTERBOX_ASPECT_RATIO = 1.33f;
+
+    @Test
+    public void testHasMinAspectRatioOverride_userAspectRatioEnabled_returnTrue() {
+        runTestScenario((robot)-> {
+            robot.conf().enableUserAppAspectRatioSettings(/* enabled */ true);
+            robot.applyOnActivity((a) -> {
+                a.createActivityWithComponent();
+                a.setIgnoreOrientationRequest(/* enabled */ true);
+                a.setGetUserMinAspectRatioOverrideValue(3 / 2f);
+                a.setGetUserMinAspectRatioOverrideCode(USER_MIN_ASPECT_RATIO_3_2);
+            });
+
+            robot.checkHasMinAspectRatioOverride(/* expected */ true);
+        });
+    }
+
+    @Test
+    @DisableCompatChanges({ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO})
+    public void testHasMinAspectRatioOverride_overrideDisabled_returnsFalse() {
+        runTestScenario((robot)-> {
+            robot.activity().createActivityWithComponent();
+
+            robot.checkHasMinAspectRatioOverride(/* expected */ false);
+        });
+    }
+
+    @Test
+    @EnableCompatChanges({ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO})
+    public void testHasMinAspectRatioOverride_overrideEnabled_propertyFalse_returnsFalse() {
+        runTestScenario((robot)-> {
+            robot.activity().createActivityWithComponent();
+            robot.prop().disable(PROPERTY_COMPAT_ALLOW_MIN_ASPECT_RATIO_OVERRIDE);
+
+            robot.checkHasMinAspectRatioOverride(/* expected */ false);
+        });
+    }
+
+    @Test
+    @DisableCompatChanges({ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO})
+    public void testHasMinAspectRatioOverride_overrideDisabled_propertyTrue_returnsFalse() {
+        runTestScenario((robot)-> {
+            robot.activity().createActivityWithComponent();
+            robot.prop().enable(PROPERTY_COMPAT_ALLOW_MIN_ASPECT_RATIO_OVERRIDE);
+
+            robot.checkHasMinAspectRatioOverride(/* expected */ false);
+        });
+    }
+
+    @Test
+    @DisableCompatChanges({ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO})
+    public void testHasMinAspectRatioOverride_overrideEnabled_nonPortraitActivity_returnsFalse() {
+        runTestScenario((robot)-> {
+            robot.applyOnActivity((a) -> {
+                a.createActivityWithComponent();
+                a.configureUnresizableTopActivity(SCREEN_ORIENTATION_UNSPECIFIED);
+            });
+
+            robot.checkHasMinAspectRatioOverride(/* expected */ false);
+        });
+    }
+
+    @Test
+    @EnableCompatChanges({ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO,
+            ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO_TO_ALIGN_WITH_SPLIT_SCREEN})
+    public void testHasMinAspectRatioOverride_splitScreenAspectRatioOverride_returnTrue() {
+        runTestScenario((robot)-> {
+            robot.applyOnActivity((a) -> {
+                a.createActivityWithComponent();
+                a.configureUnresizableTopActivity(SCREEN_ORIENTATION_PORTRAIT);
+            });
+
+            robot.checkHasMinAspectRatioOverride(/* expected */ true);
+        });
+    }
+
+    @Test
+    @EnableCompatChanges({ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO,
+            ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO_LARGE})
+    public void testHasMinAspectRatioOverride_largeMinAspectRatioOverride_returnTrue() {
+        runTestScenario((robot)-> {
+            robot.applyOnActivity((a) -> {
+                a.createActivityWithComponent();
+                a.configureUnresizableTopActivity(SCREEN_ORIENTATION_PORTRAIT);
+            });
+
+            robot.checkHasMinAspectRatioOverride(/* expected */ true);
+        });
+    }
+
+    @Test
+    @EnableCompatChanges({ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO,
+            ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO_MEDIUM})
+    public void testHasMinAspectRatioOverride_mediumMinAspectRatioOverride_returnTrue() {
+        runTestScenario((robot)-> {
+            robot.applyOnActivity((a) -> {
+                a.createActivityWithComponent();
+                a.configureUnresizableTopActivity(SCREEN_ORIENTATION_PORTRAIT);
+            });
+
+            robot.checkHasMinAspectRatioOverride(/* expected */ true);
+        });
+    }
+
+    @Test
+    @EnableCompatChanges({ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO,
+            ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO_SMALL})
+    public void testHasMinAspectRatioOverride_smallMinAspectRatioOverride_returnTrue() {
+        runTestScenario((robot)-> {
+            robot.applyOnActivity((a) -> {
+                a.createActivityWithComponent();
+                a.configureUnresizableTopActivity(SCREEN_ORIENTATION_PORTRAIT);
+            });
+
+            robot.checkHasMinAspectRatioOverride(/* expected */ true);
+        });
+    }
+
+    @Test
+    @EnableCompatChanges({ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO,
+            ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO_TO_ALIGN_WITH_SPLIT_SCREEN})
+    public void testCalculateAspectRatio_splitScreenAspectRatioOverride() {
+        runTestScenario((robot)-> {
+            robot.applyOnActivity((a) -> {
+                a.createActivityWithComponent();
+                a.setIgnoreOrientationRequest(/* enabled */ false);
+                a.configureUnresizableTopActivity(SCREEN_ORIENTATION_PORTRAIT);
+            });
+            robot.setDesiredAspectRatio(1f);
+
+            robot.checkCalculateAspectRatioSplitScreenAspectRatio();
+        });
+    }
+
+    @Test
+    @EnableCompatChanges({ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO,
+            ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO_LARGE})
+    public void testCalculateAspectRatio_largeMinAspectRatioOverride() {
+        runTestScenario((robot)-> {
+            robot.applyOnActivity((a) -> {
+                a.createActivityWithComponent();
+                a.setIgnoreOrientationRequest(/* enabled */ false);
+                a.configureUnresizableTopActivity(SCREEN_ORIENTATION_PORTRAIT);
+            });
+            robot.setDesiredAspectRatio(1f);
+
+            robot.checkCalculateAspectRatioLargeAspectRatioOverride();
+        });
+    }
+
+    @Test
+    @EnableCompatChanges({ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO,
+            ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO_MEDIUM})
+    public void testCalculateAspectRatio_mediumMinAspectRatioOverride() {
+        runTestScenario((robot)-> {
+            robot.applyOnActivity((a) -> {
+                a.createActivityWithComponent();
+                a.setIgnoreOrientationRequest(/* enabled */ false);
+                a.configureUnresizableTopActivity(SCREEN_ORIENTATION_PORTRAIT);
+            });
+            robot.setDesiredAspectRatio(1f);
+
+            robot.checkCalculateAspectRatioMediumAspectRatioOverride();
+        });
+    }
+
+    @Test
+    @EnableCompatChanges({ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO,
+            ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO_SMALL})
+    public void testCalculateAspectRatio_smallMinAspectRatioOverride() {
+        runTestScenario((robot)-> {
+            robot.applyOnActivity((a) -> {
+                a.createActivityWithComponent();
+                a.setIgnoreOrientationRequest(/* enabled */ false);
+                a.configureUnresizableTopActivity(SCREEN_ORIENTATION_PORTRAIT);
+            });
+            robot.setDesiredAspectRatio(1f);
+
+            robot.checkCalculateAspectRatioSmallAspectRatioOverride();
+        });
+    }
+
+    @Test
+    public void testCalculateAspectRatio_defaultMultiWindowLetterboxAspectRatio() {
+        runTestScenario((robot)-> {
+            robot.applyOnActivity((a) -> {
+                a.createActivityWithComponent();
+                a.setIgnoreOrientationRequest(/* enabled */ false);
+                a.configureUnresizableTopActivity(SCREEN_ORIENTATION_PORTRAIT);
+                a.setTaskDisplayAreaWindowingMode(WINDOWING_MODE_FREEFORM);
+            });
+
+            robot.checkCalculateAspectRatioDefaultLetterboxAspectRatioForMultiWindow();
+        });
+    }
+
+    @Test
+    public void testCalculateAspectRatio_displayAspectRatioEnabledForFixedOrientationLetterbox() {
+        runTestScenario((robot)-> {
+            robot.conf().enableDisplayAspectRatioEnabledForFixedOrientationLetterbox(true);
+            robot.applyOnActivity((a) -> {
+                a.createActivityWithComponent();
+                a.setIgnoreOrientationRequest(/* enabled */ true);
+                a.configureTopActivity(/* minAspect */ 0, /* maxAspect */ 0,
+                        SCREEN_ORIENTATION_PORTRAIT, /* isUnresizable */ false);
+            });
+
+            robot.checkCalculateAspectRatioDisplayAreaAspectRatio();
+        });
+    }
+
+    @Test
+    public void testCalculateAspectRatio_defaultMinAspectRatio_fixedOrientationAspectRatio() {
+        runTestScenario((robot)-> {
+            robot.applyOnConf((c) -> {
+                c.enableDisplayAspectRatioEnabledForFixedOrientationLetterbox(false);
+                c.setFixedOrientationLetterboxAspectRatio(FIXED_ORIENTATION_LETTERBOX_ASPECT_RATIO);
+            });
+            robot.applyOnActivity((a) -> {
+                a.createActivityWithComponent();
+                a.setIgnoreOrientationRequest(/* enabled */ true);
+                a.configureTopActivity(/* minAspect */ 0, /* maxAspect */ 0,
+                        SCREEN_ORIENTATION_PORTRAIT, /* isUnresizable */ false);
+            });
+
+            robot.checkCalculateAspectRatioDefaultMinFixedOrientationAspectRatio();
+        });
+    }
+
+    @Test
+    public void testCalculateAspectRatio_splitScreenForUnresizeableEnabled() {
+        runTestScenario((robot) -> {
+            robot.conf().enableSplitScreenAspectRatioForUnresizableApps(/* isEnabled */ true);
+            robot.applyOnActivity((a) -> {
+                a.createActivityWithComponent();
+                a.setIgnoreOrientationRequest(/* enabled */ true);
+                a.configureUnresizableTopActivity(SCREEN_ORIENTATION_PORTRAIT);
+            });
+
+            robot.checkCalculateAspectRatioSplitScreenAspectRatio();
+        });
+    }
+
+    @Test
+    public void testCalculateAspectRatio_user3By2AspectRatioOverride() {
+        runTestScenario((robot)-> {
+            robot.conf().enableUserAppAspectRatioSettings(/* enabled */ true);
+            robot.applyOnActivity((a) -> {
+                a.createActivityWithComponent();
+                a.setIgnoreOrientationRequest(/* enabled */ true);
+                a.setGetUserMinAspectRatioOverrideValue(3 / 2f);
+                a.setGetUserMinAspectRatioOverrideCode(USER_MIN_ASPECT_RATIO_3_2);
+            });
+            robot.setDesiredAspectRatio(1f);
+
+            robot.checkCalculateAspectRatioUser3By2AspectRatiOverride();
+        });
+    }
+
+    @Test
+    public void testCalculateAspectRatio_user4By3AspectRatioOverride() {
+        runTestScenario((robot)-> {
+            robot.conf().enableUserAppAspectRatioSettings(/* enabled */ true);
+            robot.applyOnActivity((a) -> {
+                a.createActivityWithComponent();
+                a.setIgnoreOrientationRequest(/* enabled */ true);
+                a.setGetUserMinAspectRatioOverrideValue(4 / 3f);
+                a.setGetUserMinAspectRatioOverrideCode(USER_MIN_ASPECT_RATIO_4_3);
+            });
+            robot.setDesiredAspectRatio(1f);
+
+            robot.checkCalculateAspectRatioUser4By3AspectRatiOverride();
+        });
+    }
+
+    @Test
+    public void testCalculateAspectRatio_user16By9AspectRatioOverride() {
+        runTestScenario((robot)-> {
+            robot.conf().enableUserAppAspectRatioSettings(/* enabled */ true);
+            robot.applyOnActivity((a) -> {
+                a.createActivityWithComponent();
+                a.setIgnoreOrientationRequest(/* enabled */ true);
+                a.setGetUserMinAspectRatioOverrideValue(16 / 9f);
+                a.setGetUserMinAspectRatioOverrideCode(USER_MIN_ASPECT_RATIO_16_9);
+            });
+            robot.setDesiredAspectRatio(1f);
+
+            robot.checkCalculateAspectRatioUser16By9AspectRatioOverride();
+        });
+    }
+
+    @Test
+    public void testCalculateAspectRatio_userSplitScreenAspectRatioOverride() {
+        runTestScenario((robot)-> {
+            robot.conf().enableUserAppAspectRatioSettings(/* enabled */ true);
+            robot.applyOnActivity((a) -> {
+                a.createActivityWithComponent();
+                a.setIgnoreOrientationRequest(/* enabled */ true);
+                a.setGetUserMinAspectRatioOverrideValue(robot.getSplitScreenAspectRatio());
+                a.setGetUserMinAspectRatioOverrideCode(USER_MIN_ASPECT_RATIO_SPLIT_SCREEN);
+            });
+            robot.setDesiredAspectRatio(1f);
+
+            robot.checkCalculateAspectRatioSplitScreenAspectRatio();
+        });
+    }
+
+    @Test
+    public void testCalculateAspectRatio_userDisplayAreaAspectRatioOverride() {
+        runTestScenario((robot)-> {
+            robot.conf().enableUserAppAspectRatioSettings(/* enabled */ true);
+            robot.applyOnActivity((a) -> {
+                a.createActivityWithComponent();
+                a.setIgnoreOrientationRequest(/* enabled */ true);
+                a.setGetUserMinAspectRatioOverrideValue(robot.getDisplayAreaAspectRatio());
+                a.setGetUserMinAspectRatioOverrideCode(USER_MIN_ASPECT_RATIO_DISPLAY_SIZE);
+            });
+            robot.setDesiredAspectRatio(1f);
+
+            robot.checkCalculateAspectRatioDisplayAreaAspectRatio();
+        });
+    }
+
+    /**
+     * Runs a test scenario providing a Robot.
+     */
+    void runTestScenario(@NonNull Consumer<DesktopAppCompatAspectRatioPolicyRobotTest> consumer) {
+        final DesktopAppCompatAspectRatioPolicyRobotTest robot =
+                new DesktopAppCompatAspectRatioPolicyRobotTest(mWm, mAtm, mSupervisor);
+        consumer.accept(robot);
+    }
+
+    private static class DesktopAppCompatAspectRatioPolicyRobotTest extends AppCompatRobotBase {
+        DesktopAppCompatAspectRatioPolicyRobotTest(@NonNull WindowManagerService wm,
+                @NonNull ActivityTaskManagerService atm,
+                @NonNull ActivityTaskSupervisor supervisor) {
+            super(wm, atm, supervisor);
+        }
+
+        @Override
+        void onPostActivityCreation(@NonNull ActivityRecord activity) {
+            super.onPostActivityCreation(activity);
+            spyOn(activity.mAppCompatController.getAppCompatAspectRatioOverrides());
+            spyOn(activity.mAppCompatController.getDesktopAppCompatAspectRatioPolicy());
+        }
+
+        void setDesiredAspectRatio(float aspectRatio) {
+            doReturn(aspectRatio).when(getDesktopAppCompatAspectRatioPolicy())
+                    .getDesiredAspectRatio(any());
+        }
+
+        DesktopAppCompatAspectRatioPolicy getDesktopAppCompatAspectRatioPolicy() {
+            return getTopActivity().mAppCompatController.getDesktopAppCompatAspectRatioPolicy();
+        }
+
+        float calculateAspectRatio() {
+            return getDesktopAppCompatAspectRatioPolicy().calculateAspectRatio(
+                    getTopActivity().getTask());
+        }
+
+        ActivityRecord getTopActivity() {
+            return this.activity().top();
+        }
+
+        float getSplitScreenAspectRatio() {
+            return  getTopActivity().mAppCompatController.getAppCompatAspectRatioOverrides()
+                    .getSplitScreenAspectRatio();
+        }
+
+        float getDisplayAreaAspectRatio() {
+            final Rect appBounds = getTopActivity().getDisplayArea().getWindowConfiguration()
+                    .getAppBounds();
+            return AppCompatUtils.computeAspectRatio(appBounds);
+        }
+
+        void checkHasMinAspectRatioOverride(boolean expected) {
+            assertEquals(expected, this.activity().top().mAppCompatController
+                    .getDesktopAppCompatAspectRatioPolicy().hasMinAspectRatioOverride(
+                            this.activity().top().getTask()));
+        }
+
+        void checkCalculateAspectRatioSplitScreenAspectRatio() {
+            assertEquals(getSplitScreenAspectRatio(), calculateAspectRatio(), FLOAT_TOLLERANCE);
+        }
+
+        void checkCalculateAspectRatioLargeAspectRatioOverride() {
+            assertEquals(OVERRIDE_MIN_ASPECT_RATIO_LARGE_VALUE, calculateAspectRatio(),
+                    FLOAT_TOLLERANCE);
+        }
+
+        void checkCalculateAspectRatioMediumAspectRatioOverride() {
+            assertEquals(OVERRIDE_MIN_ASPECT_RATIO_MEDIUM_VALUE, calculateAspectRatio(),
+                    FLOAT_TOLLERANCE);
+        }
+
+        void checkCalculateAspectRatioSmallAspectRatioOverride() {
+            assertEquals(OVERRIDE_MIN_ASPECT_RATIO_SMALL_VALUE, calculateAspectRatio(),
+                    FLOAT_TOLLERANCE);
+        }
+
+        void checkCalculateAspectRatioDefaultLetterboxAspectRatioForMultiWindow() {
+            assertEquals(DEFAULT_LETTERBOX_ASPECT_RATIO_FOR_MULTI_WINDOW, calculateAspectRatio(),
+                    FLOAT_TOLLERANCE);
+        }
+
+        void checkCalculateAspectRatioDisplayAreaAspectRatio() {
+            assertEquals(getDisplayAreaAspectRatio(), calculateAspectRatio(), FLOAT_TOLLERANCE);
+        }
+
+        void checkCalculateAspectRatioDefaultMinFixedOrientationAspectRatio() {
+            assertEquals(FIXED_ORIENTATION_LETTERBOX_ASPECT_RATIO, calculateAspectRatio(),
+                    FLOAT_TOLLERANCE);
+        }
+
+        void checkCalculateAspectRatioUser3By2AspectRatiOverride() {
+            assertEquals(3 / 2f, calculateAspectRatio(), FLOAT_TOLLERANCE);
+        }
+
+        void checkCalculateAspectRatioUser4By3AspectRatiOverride() {
+            assertEquals(4 / 3f, calculateAspectRatio(), FLOAT_TOLLERANCE);
+        }
+
+        void checkCalculateAspectRatioUser16By9AspectRatioOverride() {
+            assertEquals(16 / 9f, calculateAspectRatio(), FLOAT_TOLLERANCE);
+        }
+    }
+}
diff --git a/services/tests/wmtests/src/com/android/server/wm/DesktopModeLaunchParamsModifierTests.java b/services/tests/wmtests/src/com/android/server/wm/DesktopModeLaunchParamsModifierTests.java
index 07e95d8..6d508ea 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DesktopModeLaunchParamsModifierTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DesktopModeLaunchParamsModifierTests.java
@@ -21,11 +21,19 @@
 import static android.app.WindowConfiguration.ACTIVITY_TYPE_UNDEFINED;
 import static android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM;
 import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
+import static android.content.pm.ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO_LARGE_VALUE;
+import static android.content.pm.ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO_MEDIUM_VALUE;
+import static android.content.pm.ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO_SMALL_VALUE;
 import static android.content.pm.ActivityInfo.RESIZE_MODE_RESIZEABLE;
 import static android.content.pm.ActivityInfo.RESIZE_MODE_UNRESIZEABLE;
 import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE;
 import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_PORTRAIT;
 import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED;
+import static android.content.pm.PackageManager.USER_MIN_ASPECT_RATIO_16_9;
+import static android.content.pm.PackageManager.USER_MIN_ASPECT_RATIO_3_2;
+import static android.content.pm.PackageManager.USER_MIN_ASPECT_RATIO_4_3;
+import static android.content.pm.PackageManager.USER_MIN_ASPECT_RATIO_DISPLAY_SIZE;
+import static android.content.pm.PackageManager.USER_MIN_ASPECT_RATIO_SPLIT_SCREEN;
 import static android.content.res.Configuration.ORIENTATION_LANDSCAPE;
 import static android.content.res.Configuration.ORIENTATION_PORTRAIT;
 import static android.util.DisplayMetrics.DENSITY_DEFAULT;
@@ -34,7 +42,6 @@
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
 import static com.android.server.wm.DesktopModeBoundsCalculator.DESKTOP_MODE_INITIAL_BOUNDS_SCALE;
 import static com.android.server.wm.DesktopModeBoundsCalculator.DESKTOP_MODE_LANDSCAPE_APP_PADDING;
-import static com.android.server.wm.DesktopModeBoundsCalculator.calculateAspectRatio;
 import static com.android.server.wm.LaunchParamsController.LaunchParamsModifier.PHASE_DISPLAY;
 import static com.android.server.wm.LaunchParamsController.LaunchParamsModifier.RESULT_CONTINUE;
 import static com.android.server.wm.LaunchParamsController.LaunchParamsModifier.RESULT_SKIP;
@@ -44,6 +51,8 @@
 import static org.mockito.Mockito.mock;
 
 import android.app.ActivityOptions;
+import android.compat.testing.PlatformCompatChangeRule;
+import android.content.ComponentName;
 import android.content.pm.ActivityInfo;
 import android.graphics.Rect;
 import android.platform.test.annotations.DisableFlags;
@@ -55,8 +64,12 @@
 
 import com.android.window.flags.Flags;
 
+import libcore.junit.util.compat.CoreCompatChangeRule.EnableCompatChanges;
+
 import org.junit.Before;
+import org.junit.Rule;
 import org.junit.Test;
+import org.junit.rules.TestRule;
 import org.junit.runner.RunWith;
 
 /**
@@ -74,6 +87,9 @@
     private static final Rect PORTRAIT_DISPLAY_BOUNDS = new Rect(0, 0, 1600, 2560);
     private static final float LETTERBOX_ASPECT_RATIO = 1.3f;
 
+    @Rule
+    public TestRule compatChangeRule = new PlatformCompatChangeRule();
+
     @Before
     public void setUp() throws Exception {
         mActivity = new ActivityBuilder(mAtm).build();
@@ -199,14 +215,17 @@
 
         final TestDisplayContent display = createDisplayContent(ORIENTATION_LANDSCAPE,
                 LANDSCAPE_DISPLAY_BOUNDS);
-        final Task task = createTask(display, SCREEN_ORIENTATION_UNSPECIFIED, true);
+        final Task task = createTask(display, /* isResizeable */ true);
+        final ActivityRecord activity = createActivity(display, SCREEN_ORIENTATION_UNSPECIFIED,
+                task, /* ignoreOrientationRequest */ true);
 
         final int desiredWidth =
                 (int) (LANDSCAPE_DISPLAY_BOUNDS.width() * DESKTOP_MODE_INITIAL_BOUNDS_SCALE);
         final int desiredHeight =
                 (int) (LANDSCAPE_DISPLAY_BOUNDS.height() * DESKTOP_MODE_INITIAL_BOUNDS_SCALE);
 
-        assertEquals(RESULT_CONTINUE, new CalculateRequestBuilder().setTask(task).calculate());
+        assertEquals(RESULT_CONTINUE, new CalculateRequestBuilder().setTask(task)
+                .setActivity(activity).calculate());
         assertEquals(desiredWidth, mResult.mBounds.width());
         assertEquals(desiredHeight, mResult.mBounds.height());
     }
@@ -219,14 +238,17 @@
 
         final TestDisplayContent display = createDisplayContent(ORIENTATION_LANDSCAPE,
                 LANDSCAPE_DISPLAY_BOUNDS);
-        final Task task = createTask(display, SCREEN_ORIENTATION_LANDSCAPE, true);
+        final Task task = createTask(display, /* isResizeable */ true);
+        final ActivityRecord activity = createActivity(display, SCREEN_ORIENTATION_LANDSCAPE,
+                task, /* ignoreOrientationRequest */ true);
 
         final int desiredWidth =
                 (int) (LANDSCAPE_DISPLAY_BOUNDS.width() * DESKTOP_MODE_INITIAL_BOUNDS_SCALE);
         final int desiredHeight =
                 (int) (LANDSCAPE_DISPLAY_BOUNDS.height() * DESKTOP_MODE_INITIAL_BOUNDS_SCALE);
 
-        assertEquals(RESULT_CONTINUE, new CalculateRequestBuilder().setTask(task).calculate());
+        assertEquals(RESULT_CONTINUE, new CalculateRequestBuilder().setTask(task)
+                .setActivity(activity).calculate());
         assertEquals(desiredWidth, mResult.mBounds.width());
         assertEquals(desiredHeight, mResult.mBounds.height());
     }
@@ -239,7 +261,9 @@
 
         final TestDisplayContent display = createDisplayContent(ORIENTATION_LANDSCAPE,
                 LANDSCAPE_DISPLAY_BOUNDS);
-        final Task task = createTask(display, SCREEN_ORIENTATION_LANDSCAPE, true);
+        final Task task = createTask(display, /* isResizeable */ true);
+        final ActivityRecord activity = createActivity(display, SCREEN_ORIENTATION_LANDSCAPE,
+                task, /* ignoreOrientationRequest */ true);
 
         spyOn(mActivity.mAppCompatController.getAppCompatAspectRatioOverrides());
         doReturn(true).when(
@@ -251,7 +275,8 @@
         final int desiredHeight =
                 (int) (LANDSCAPE_DISPLAY_BOUNDS.height() * DESKTOP_MODE_INITIAL_BOUNDS_SCALE);
 
-        assertEquals(RESULT_CONTINUE, new CalculateRequestBuilder().setTask(task).calculate());
+        assertEquals(RESULT_CONTINUE, new CalculateRequestBuilder().setTask(task)
+                .setActivity(activity).calculate());
         assertEquals(desiredWidth, mResult.mBounds.width());
         assertEquals(desiredHeight, mResult.mBounds.height());
     }
@@ -264,7 +289,9 @@
 
         final TestDisplayContent display = createDisplayContent(ORIENTATION_LANDSCAPE,
                 LANDSCAPE_DISPLAY_BOUNDS);
-        final Task task = createTask(display, SCREEN_ORIENTATION_LANDSCAPE, true);
+        final Task task = createTask(display, /* isResizeable */ true);
+        final ActivityRecord activity = createActivity(display, SCREEN_ORIENTATION_LANDSCAPE,
+                task, /* ignoreOrientationRequest */ true);
 
         spyOn(mActivity.mAppCompatController.getAppCompatAspectRatioOverrides());
         doReturn(true).when(
@@ -276,7 +303,8 @@
         final int desiredHeight =
                 (int) (LANDSCAPE_DISPLAY_BOUNDS.height() * DESKTOP_MODE_INITIAL_BOUNDS_SCALE);
 
-        assertEquals(RESULT_CONTINUE, new CalculateRequestBuilder().setTask(task).calculate());
+        assertEquals(RESULT_CONTINUE, new CalculateRequestBuilder().setTask(task)
+                .setActivity(activity).calculate());
         assertEquals(desiredWidth, mResult.mBounds.width());
         assertEquals(desiredHeight, mResult.mBounds.height());
     }
@@ -285,19 +313,466 @@
     @EnableFlags(Flags.FLAG_ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS)
     public void testResizablePortraitBounds_landscapeDevice_resizable_portraitOrientation() {
         setupDesktopModeLaunchParamsModifier();
-        doReturn(LETTERBOX_ASPECT_RATIO).when(()
-                -> calculateAspectRatio(any(), any()));
 
         final TestDisplayContent display = createDisplayContent(ORIENTATION_LANDSCAPE,
                 LANDSCAPE_DISPLAY_BOUNDS);
-        final Task task = createTask(display, SCREEN_ORIENTATION_PORTRAIT, true);
+        final Task task = createTask(display, /* isResizeable */ true);
+        final ActivityRecord activity = createActivity(display, SCREEN_ORIENTATION_PORTRAIT,
+                task, /* ignoreOrientationRequest */ true);
+
+        spyOn(activity.mAppCompatController.getDesktopAppCompatAspectRatioPolicy());
+        doReturn(LETTERBOX_ASPECT_RATIO).when(activity.mAppCompatController
+                .getDesktopAppCompatAspectRatioPolicy()).calculateAspectRatio(any());
 
         final int desiredWidth =
                 (int) ((LANDSCAPE_DISPLAY_BOUNDS.height() / LETTERBOX_ASPECT_RATIO) + 0.5f);
         final int desiredHeight =
                 (int) (LANDSCAPE_DISPLAY_BOUNDS.height() * DESKTOP_MODE_INITIAL_BOUNDS_SCALE);
 
-        assertEquals(RESULT_CONTINUE, new CalculateRequestBuilder().setTask(task).calculate());
+        assertEquals(RESULT_CONTINUE, new CalculateRequestBuilder().setTask(task)
+                .setActivity(activity).calculate());
+        assertEquals(desiredWidth, mResult.mBounds.width());
+        assertEquals(desiredHeight, mResult.mBounds.height());
+    }
+
+    @Test
+    @EnableFlags(Flags.FLAG_ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS)
+    @EnableCompatChanges({ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO,
+            ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO_SMALL})
+    public void testSmallAspectRatioOverride_landscapeDevice() {
+        setupDesktopModeLaunchParamsModifier();
+
+        final TestDisplayContent display = createDisplayContent(ORIENTATION_LANDSCAPE,
+                LANDSCAPE_DISPLAY_BOUNDS);
+        final Task task = createTask(display, /* isResizeable */ true);
+        final ActivityRecord activity = createActivity(display, SCREEN_ORIENTATION_PORTRAIT,
+                task, /* ignoreOrientationRequest */ false);
+
+        final int desiredHeight =
+                (int) (LANDSCAPE_DISPLAY_BOUNDS.height() * DESKTOP_MODE_INITIAL_BOUNDS_SCALE);
+        final int desiredWidth =
+                (int) (desiredHeight / OVERRIDE_MIN_ASPECT_RATIO_SMALL_VALUE);
+
+        assertEquals(RESULT_CONTINUE, new CalculateRequestBuilder().setTask(task)
+                .setActivity(activity).calculate());
+        assertEquals(desiredWidth, mResult.mBounds.width());
+        assertEquals(desiredHeight, mResult.mBounds.height());
+    }
+
+    @Test
+    @EnableFlags(Flags.FLAG_ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS)
+    @EnableCompatChanges({ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO,
+            ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO_MEDIUM})
+    public void testMediumAspectRatioOverride_landscapeDevice() {
+        setupDesktopModeLaunchParamsModifier();
+
+        final TestDisplayContent display = createDisplayContent(ORIENTATION_LANDSCAPE,
+                LANDSCAPE_DISPLAY_BOUNDS);
+        final Task task = createTask(display, /* isResizeable */ true);
+        final ActivityRecord activity = createActivity(display, SCREEN_ORIENTATION_PORTRAIT,
+                task, /* ignoreOrientationRequest */ false);
+
+        final int desiredHeight =
+                (int) (LANDSCAPE_DISPLAY_BOUNDS.height() * DESKTOP_MODE_INITIAL_BOUNDS_SCALE);
+        final int desiredWidth =
+                (int) (desiredHeight / OVERRIDE_MIN_ASPECT_RATIO_MEDIUM_VALUE);
+
+        assertEquals(RESULT_CONTINUE, new CalculateRequestBuilder().setTask(task)
+                .setActivity(activity).calculate());
+        assertEquals(desiredWidth, mResult.mBounds.width());
+        assertEquals(desiredHeight, mResult.mBounds.height());
+    }
+
+    @Test
+    @EnableFlags(Flags.FLAG_ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS)
+    @EnableCompatChanges({ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO,
+            ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO_LARGE})
+    public void testLargeAspectRatioOverride_landscapeDevice() {
+        setupDesktopModeLaunchParamsModifier();
+
+        final TestDisplayContent display = createDisplayContent(ORIENTATION_LANDSCAPE,
+                LANDSCAPE_DISPLAY_BOUNDS);
+        final Task task = createTask(display, /* isResizeable */ true);
+        final ActivityRecord activity = createActivity(display, SCREEN_ORIENTATION_PORTRAIT,
+                task, /* ignoreOrientationRequest */ false);
+
+        final int desiredHeight =
+                (int) (LANDSCAPE_DISPLAY_BOUNDS.height() * DESKTOP_MODE_INITIAL_BOUNDS_SCALE);
+        final int desiredWidth =
+                (int) (desiredHeight / OVERRIDE_MIN_ASPECT_RATIO_LARGE_VALUE);
+
+        assertEquals(RESULT_CONTINUE, new CalculateRequestBuilder().setTask(task)
+                .setActivity(activity).calculate());
+        assertEquals(desiredWidth, mResult.mBounds.width());
+        assertEquals(desiredHeight, mResult.mBounds.height());
+    }
+
+    @Test
+    @EnableFlags(Flags.FLAG_ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS)
+    @EnableCompatChanges({ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO,
+            ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO_TO_ALIGN_WITH_SPLIT_SCREEN})
+    public void testSplitScreenAspectRatioOverride_landscapeDevice() {
+        setupDesktopModeLaunchParamsModifier();
+
+        final TestDisplayContent display = createDisplayContent(ORIENTATION_LANDSCAPE,
+                LANDSCAPE_DISPLAY_BOUNDS);
+        final Task task = createTask(display, /* isResizeable */ true);
+        final ActivityRecord activity = createActivity(display, SCREEN_ORIENTATION_PORTRAIT,
+                task, /* ignoreOrientationRequest */ false);
+
+        final int desiredHeight =
+                (int) (LANDSCAPE_DISPLAY_BOUNDS.height() * DESKTOP_MODE_INITIAL_BOUNDS_SCALE);
+        final int desiredWidth =
+                (int) (desiredHeight / activity.mAppCompatController
+                        .getAppCompatAspectRatioOverrides().getSplitScreenAspectRatio());
+
+        assertEquals(RESULT_CONTINUE, new CalculateRequestBuilder().setTask(task)
+                .setActivity(activity).calculate());
+        assertEquals(desiredWidth, mResult.mBounds.width());
+        assertEquals(desiredHeight, mResult.mBounds.height());
+    }
+
+    @Test
+    @EnableFlags(Flags.FLAG_ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS)
+    @EnableCompatChanges({ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO,
+            ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO_SMALL})
+    public void testSmallAspectRatioOverride_portraitDevice() {
+        setupDesktopModeLaunchParamsModifier();
+
+        final TestDisplayContent display = createDisplayContent(ORIENTATION_PORTRAIT,
+                PORTRAIT_DISPLAY_BOUNDS);
+        final Task task = createTask(display, /* isResizeable */ true);
+        final ActivityRecord activity = createActivity(display, SCREEN_ORIENTATION_PORTRAIT,
+                task, /* ignoreOrientationRequest */ false);
+        // Mock desired aspect ratio so min override can take effect.
+        setDesiredAspectRatio(activity, /* aspectRatio */ 1f);
+
+        final int desiredWidth =
+                (int) (PORTRAIT_DISPLAY_BOUNDS.width() * DESKTOP_MODE_INITIAL_BOUNDS_SCALE);
+        final int desiredHeight = (int) (desiredWidth * OVERRIDE_MIN_ASPECT_RATIO_SMALL_VALUE);
+
+        assertEquals(RESULT_CONTINUE, new CalculateRequestBuilder().setTask(task)
+                .setActivity(activity).calculate());
+        assertEquals(desiredWidth, mResult.mBounds.width());
+        assertEquals(desiredHeight, mResult.mBounds.height());
+    }
+
+    @Test
+    @EnableFlags(Flags.FLAG_ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS)
+    @EnableCompatChanges({ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO,
+            ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO_MEDIUM})
+    public void testMediumAspectRatioOverride_portraitDevice() {
+        setupDesktopModeLaunchParamsModifier();
+
+        final TestDisplayContent display = createDisplayContent(ORIENTATION_PORTRAIT,
+                PORTRAIT_DISPLAY_BOUNDS);
+        final Task task = createTask(display, /* isResizeable */ true);
+        final ActivityRecord activity = createActivity(display, SCREEN_ORIENTATION_PORTRAIT,
+                task, /* ignoreOrientationRequest */ false);
+        // Mock desired aspect ratio so min override can take effect.
+        setDesiredAspectRatio(activity, /* aspectRatio */ 1f);
+
+        final int desiredWidth =
+                (int) (PORTRAIT_DISPLAY_BOUNDS.width() * DESKTOP_MODE_INITIAL_BOUNDS_SCALE);
+        final int desiredHeight = (int) (desiredWidth * OVERRIDE_MIN_ASPECT_RATIO_MEDIUM_VALUE);
+
+        assertEquals(RESULT_CONTINUE, new CalculateRequestBuilder().setTask(task)
+                .setActivity(activity).calculate());
+        assertEquals(desiredWidth, mResult.mBounds.width());
+        assertEquals(desiredHeight, mResult.mBounds.height());
+    }
+
+    @Test
+    @EnableFlags(Flags.FLAG_ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS)
+    @EnableCompatChanges({ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO,
+            ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO_LARGE})
+    public void testLargeAspectRatioOverride_portraitDevice() {
+        setupDesktopModeLaunchParamsModifier();
+
+        final TestDisplayContent display = createDisplayContent(ORIENTATION_PORTRAIT,
+                PORTRAIT_DISPLAY_BOUNDS);
+        final Task task = createTask(display, /* isResizeable */ true);
+        final ActivityRecord activity = createActivity(display, SCREEN_ORIENTATION_PORTRAIT,
+                task, /* ignoreOrientationRequest */ false);
+        // Mock desired aspect ratio so min override can take effect.
+        setDesiredAspectRatio(activity, /* aspectRatio */ 1f);
+
+        final int desiredHeight =
+                (int) (PORTRAIT_DISPLAY_BOUNDS.height() * DESKTOP_MODE_INITIAL_BOUNDS_SCALE);
+        final int desiredWidth = (int) (desiredHeight / OVERRIDE_MIN_ASPECT_RATIO_LARGE_VALUE);
+
+        assertEquals(RESULT_CONTINUE, new CalculateRequestBuilder().setTask(task)
+                .setActivity(activity).calculate());
+        assertEquals(desiredWidth, mResult.mBounds.width());
+        assertEquals(desiredHeight, mResult.mBounds.height());
+    }
+
+    @Test
+    @EnableFlags(Flags.FLAG_ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS)
+    @EnableCompatChanges({ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO,
+            ActivityInfo.OVERRIDE_MIN_ASPECT_RATIO_TO_ALIGN_WITH_SPLIT_SCREEN})
+    public void testSplitScreenAspectRatioOverride_portraitDevice() {
+        setupDesktopModeLaunchParamsModifier();
+
+        final TestDisplayContent display = createDisplayContent(ORIENTATION_PORTRAIT,
+                PORTRAIT_DISPLAY_BOUNDS);
+        final Task task = createTask(display, true);
+        final ActivityRecord activity = createActivity(display, SCREEN_ORIENTATION_PORTRAIT,
+                task, /* ignoreOrientationRequest */ false);
+        // Mock desired aspect ratio so min override can take effect.
+        setDesiredAspectRatio(activity, /* aspectRatio */ 1f);
+
+        final int desiredWidth =
+                (int) (PORTRAIT_DISPLAY_BOUNDS.width() * DESKTOP_MODE_INITIAL_BOUNDS_SCALE);
+        final int desiredHeight = (int) (desiredWidth * activity.mAppCompatController
+                .getAppCompatAspectRatioOverrides().getSplitScreenAspectRatio());
+
+        assertEquals(RESULT_CONTINUE, new CalculateRequestBuilder().setTask(task)
+                .setActivity(activity).calculate());
+        assertEquals(desiredWidth, mResult.mBounds.width());
+        assertEquals(desiredHeight, mResult.mBounds.height());
+    }
+
+    @Test
+    @EnableFlags(Flags.FLAG_ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS)
+    public void testUserAspectRatio32Override_landscapeDevice() {
+        setupDesktopModeLaunchParamsModifier();
+
+        final TestDisplayContent display = createDisplayContent(ORIENTATION_LANDSCAPE,
+                LANDSCAPE_DISPLAY_BOUNDS);
+        final Task task = createTask(display, /* isResizeable */ true);
+        final ActivityRecord activity = createActivity(display, SCREEN_ORIENTATION_UNSPECIFIED,
+                task, /* ignoreOrientationRequest */ true);
+        final float userAspectRatioOverrideValue3_2 = 3 / 2f;
+        applyUserMinAspectRatioOverride(activity, USER_MIN_ASPECT_RATIO_3_2,
+                userAspectRatioOverrideValue3_2);
+
+        final int desiredHeight =
+                (int) (LANDSCAPE_DISPLAY_BOUNDS.height() * DESKTOP_MODE_INITIAL_BOUNDS_SCALE);
+        final int desiredWidth = (int) (desiredHeight / userAspectRatioOverrideValue3_2);
+
+        assertEquals(RESULT_CONTINUE, new CalculateRequestBuilder().setTask(task)
+                .setActivity(activity).calculate());
+        assertEquals(desiredWidth, mResult.mBounds.width());
+        assertEquals(desiredHeight, mResult.mBounds.height());
+    }
+
+    @Test
+    @EnableFlags(Flags.FLAG_ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS)
+    public void testUserAspectRatio43Override_landscapeDevice() {
+        setupDesktopModeLaunchParamsModifier();
+
+        final TestDisplayContent display = createDisplayContent(ORIENTATION_LANDSCAPE,
+                LANDSCAPE_DISPLAY_BOUNDS);
+        final Task task = createTask(display, /* isResizeable */ true);
+        final ActivityRecord activity = createActivity(display, SCREEN_ORIENTATION_UNSPECIFIED,
+                task, /* ignoreOrientationRequest */ true);
+        final float userAspectRatioOverrideValue4_3 = 4 / 3f;
+        applyUserMinAspectRatioOverride(activity, USER_MIN_ASPECT_RATIO_4_3,
+                userAspectRatioOverrideValue4_3);
+
+        final int desiredHeight =
+                (int) (LANDSCAPE_DISPLAY_BOUNDS.height() * DESKTOP_MODE_INITIAL_BOUNDS_SCALE);
+        final int desiredWidth = (int) (desiredHeight / userAspectRatioOverrideValue4_3);
+
+        assertEquals(RESULT_CONTINUE, new CalculateRequestBuilder().setTask(task)
+                .setActivity(activity).calculate());
+        assertEquals(desiredWidth, mResult.mBounds.width());
+        assertEquals(desiredHeight, mResult.mBounds.height());
+    }
+
+    @Test
+    @EnableFlags(Flags.FLAG_ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS)
+    public void testUserAspectRatio169Override_landscapeDevice() {
+        setupDesktopModeLaunchParamsModifier();
+
+        final TestDisplayContent display = createDisplayContent(ORIENTATION_LANDSCAPE,
+                LANDSCAPE_DISPLAY_BOUNDS);
+        final Task task = createTask(display, /* isResizeable */ true);
+        final ActivityRecord activity = createActivity(display, SCREEN_ORIENTATION_UNSPECIFIED,
+                task, /* ignoreOrientationRequest */ true);
+        final float userAspectRatioOverrideValue16_9 = 16 / 9f;
+        applyUserMinAspectRatioOverride(activity, USER_MIN_ASPECT_RATIO_16_9,
+                userAspectRatioOverrideValue16_9);
+
+        final int desiredHeight =
+                (int) (LANDSCAPE_DISPLAY_BOUNDS.height() * DESKTOP_MODE_INITIAL_BOUNDS_SCALE);
+        final int desiredWidth = (int) (desiredHeight / userAspectRatioOverrideValue16_9);
+
+        assertEquals(RESULT_CONTINUE, new CalculateRequestBuilder().setTask(task)
+                .setActivity(activity).calculate());
+        assertEquals(desiredWidth, mResult.mBounds.width());
+        assertEquals(desiredHeight, mResult.mBounds.height());
+    }
+
+    @Test
+    @EnableFlags(Flags.FLAG_ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS)
+    public void testUserAspectRatioSplitScreenOverride_landscapeDevice() {
+        setupDesktopModeLaunchParamsModifier();
+
+        final TestDisplayContent display = createDisplayContent(ORIENTATION_LANDSCAPE,
+                LANDSCAPE_DISPLAY_BOUNDS);
+        final Task task = createTask(display, /* isResizeable */ true);
+        final ActivityRecord activity = createActivity(display, SCREEN_ORIENTATION_UNSPECIFIED,
+                task, /* ignoreOrientationRequest */ true);
+        final float userAspectRatioOverrideValueSplitScreen = activity.mAppCompatController
+                .getAppCompatAspectRatioOverrides().getSplitScreenAspectRatio();
+        applyUserMinAspectRatioOverride(activity, USER_MIN_ASPECT_RATIO_SPLIT_SCREEN,
+                userAspectRatioOverrideValueSplitScreen);
+
+        final int desiredHeight =
+                (int) (LANDSCAPE_DISPLAY_BOUNDS.height() * DESKTOP_MODE_INITIAL_BOUNDS_SCALE);
+        final int desiredWidth = (int) (desiredHeight / userAspectRatioOverrideValueSplitScreen);
+
+        assertEquals(RESULT_CONTINUE, new CalculateRequestBuilder().setTask(task)
+                .setActivity(activity).calculate());
+        assertEquals(desiredWidth, mResult.mBounds.width());
+        assertEquals(desiredHeight, mResult.mBounds.height());
+    }
+
+    @Test
+    @EnableFlags(Flags.FLAG_ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS)
+    public void testUserAspectRatioDisplaySizeOverride_landscapeDevice() {
+        setupDesktopModeLaunchParamsModifier();
+
+        final TestDisplayContent display = createDisplayContent(ORIENTATION_LANDSCAPE,
+                LANDSCAPE_DISPLAY_BOUNDS);
+        final Task task = createTask(display, /* isResizeable */ true);
+        final ActivityRecord activity = createActivity(display, SCREEN_ORIENTATION_UNSPECIFIED,
+                task, /* ignoreOrientationRequest */ true);
+        final float userAspectRatioOverrideValueDisplaySize = activity.mAppCompatController
+                .getAppCompatAspectRatioOverrides().getDisplaySizeMinAspectRatio();
+        applyUserMinAspectRatioOverride(activity, USER_MIN_ASPECT_RATIO_DISPLAY_SIZE,
+                userAspectRatioOverrideValueDisplaySize);
+
+        final int desiredHeight =
+                (int) (LANDSCAPE_DISPLAY_BOUNDS.height() * DESKTOP_MODE_INITIAL_BOUNDS_SCALE);
+        final int desiredWidth = (int) (desiredHeight / userAspectRatioOverrideValueDisplaySize);
+
+        assertEquals(RESULT_CONTINUE, new CalculateRequestBuilder().setTask(task)
+                .setActivity(activity).calculate());
+        assertEquals(desiredWidth, mResult.mBounds.width());
+        assertEquals(desiredHeight, mResult.mBounds.height());
+    }
+
+    @Test
+    @EnableFlags(Flags.FLAG_ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS)
+    public void testUserAspectRatio32Override_portraitDevice() {
+        setupDesktopModeLaunchParamsModifier();
+
+        final TestDisplayContent display = createDisplayContent(ORIENTATION_PORTRAIT,
+                PORTRAIT_DISPLAY_BOUNDS);
+        final Task task = createTask(display, /* isResizeable */ true);
+        final ActivityRecord activity = createActivity(display, SCREEN_ORIENTATION_UNSPECIFIED,
+                task, /* ignoreOrientationRequest */ true);
+        final float userAspectRatioOverrideValue3_2 = 3 / 2f;
+        applyUserMinAspectRatioOverride(activity, USER_MIN_ASPECT_RATIO_3_2,
+                userAspectRatioOverrideValue3_2);
+
+        final int desiredWidth =
+                (int) (PORTRAIT_DISPLAY_BOUNDS.width() * DESKTOP_MODE_INITIAL_BOUNDS_SCALE);
+        final int desiredHeight = (int) (desiredWidth * userAspectRatioOverrideValue3_2);
+
+        assertEquals(RESULT_CONTINUE, new CalculateRequestBuilder().setTask(task)
+                .setActivity(activity).calculate());
+        assertEquals(desiredWidth, mResult.mBounds.width());
+        assertEquals(desiredHeight, mResult.mBounds.height());
+    }
+
+    @Test
+    @EnableFlags(Flags.FLAG_ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS)
+    public void testUserAspectRatio43Override_portraitDevice() {
+        setupDesktopModeLaunchParamsModifier();
+
+        final TestDisplayContent display = createDisplayContent(ORIENTATION_PORTRAIT,
+                PORTRAIT_DISPLAY_BOUNDS);
+        final Task task = createTask(display, /* isResizeable */ true);
+        final ActivityRecord activity = createActivity(display, SCREEN_ORIENTATION_UNSPECIFIED,
+                task, /* ignoreOrientationRequest */ true);
+        final float userAspectRatioOverrideValue4_3 = 4 / 3f;
+        applyUserMinAspectRatioOverride(activity, USER_MIN_ASPECT_RATIO_4_3,
+                userAspectRatioOverrideValue4_3);
+
+        final int desiredWidth =
+                (int) (PORTRAIT_DISPLAY_BOUNDS.width() * DESKTOP_MODE_INITIAL_BOUNDS_SCALE);
+        final int desiredHeight = (int) (desiredWidth * userAspectRatioOverrideValue4_3);
+
+        assertEquals(RESULT_CONTINUE, new CalculateRequestBuilder().setTask(task)
+                .setActivity(activity).calculate());
+        assertEquals(desiredWidth, mResult.mBounds.width());
+        assertEquals(desiredHeight, mResult.mBounds.height());
+    }
+
+    @Test
+    @EnableFlags(Flags.FLAG_ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS)
+    public void testUserAspectRatio169Override_portraitDevice() {
+        setupDesktopModeLaunchParamsModifier();
+
+        final TestDisplayContent display = createDisplayContent(ORIENTATION_PORTRAIT,
+                PORTRAIT_DISPLAY_BOUNDS);
+        final Task task = createTask(display, /* isResizeable */ true);
+        final ActivityRecord activity = createActivity(display, SCREEN_ORIENTATION_UNSPECIFIED,
+                task, /* ignoreOrientationRequest */ true);
+        final float userAspectRatioOverrideValue16_9 = 16 / 9f;
+        applyUserMinAspectRatioOverride(activity, USER_MIN_ASPECT_RATIO_16_9,
+                userAspectRatioOverrideValue16_9);
+
+        final int desiredHeight =
+                (int) (PORTRAIT_DISPLAY_BOUNDS.height() * DESKTOP_MODE_INITIAL_BOUNDS_SCALE);
+        final int desiredWidth = (int) (desiredHeight / userAspectRatioOverrideValue16_9);
+
+        assertEquals(RESULT_CONTINUE, new CalculateRequestBuilder().setTask(task)
+                .setActivity(activity).calculate());
+        assertEquals(desiredWidth, mResult.mBounds.width());
+        assertEquals(desiredHeight, mResult.mBounds.height());
+    }
+
+    @Test
+    @EnableFlags(Flags.FLAG_ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS)
+    public void testUserAspectRatioSplitScreenOverride_portraitDevice() {
+        setupDesktopModeLaunchParamsModifier();
+
+        final TestDisplayContent display = createDisplayContent(ORIENTATION_PORTRAIT,
+                PORTRAIT_DISPLAY_BOUNDS);
+        final Task task = createTask(display, /* isResizeable */ true);
+        final ActivityRecord activity = createActivity(display, SCREEN_ORIENTATION_UNSPECIFIED,
+                task, /* ignoreOrientationRequest */ true);
+        final float userAspectRatioOverrideValueSplitScreen = activity.mAppCompatController
+                .getAppCompatAspectRatioOverrides().getSplitScreenAspectRatio();
+        applyUserMinAspectRatioOverride(activity, USER_MIN_ASPECT_RATIO_SPLIT_SCREEN,
+                userAspectRatioOverrideValueSplitScreen);
+
+        final int desiredWidth =
+                (int) (PORTRAIT_DISPLAY_BOUNDS.width() * DESKTOP_MODE_INITIAL_BOUNDS_SCALE);
+        final int desiredHeight = (int) (desiredWidth * userAspectRatioOverrideValueSplitScreen);
+
+        assertEquals(RESULT_CONTINUE, new CalculateRequestBuilder().setTask(task)
+                .setActivity(activity).calculate());
+        assertEquals(desiredWidth, mResult.mBounds.width());
+        assertEquals(desiredHeight, mResult.mBounds.height());
+    }
+
+    @Test
+    @EnableFlags(Flags.FLAG_ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS)
+    public void testUserAspectRatioDisplaySizeOverride_portraitDevice() {
+        setupDesktopModeLaunchParamsModifier();
+
+        final TestDisplayContent display = createDisplayContent(ORIENTATION_PORTRAIT,
+                PORTRAIT_DISPLAY_BOUNDS);
+        final Task task = createTask(display, /* isResizeable */ true);
+        final ActivityRecord activity = createActivity(display, SCREEN_ORIENTATION_UNSPECIFIED,
+                task, /* ignoreOrientationRequest */ true);
+        final float userAspectRatioOverrideValueDisplaySize = activity.mAppCompatController
+                .getAppCompatAspectRatioOverrides().getDisplaySizeMinAspectRatio();
+        applyUserMinAspectRatioOverride(activity, USER_MIN_ASPECT_RATIO_DISPLAY_SIZE,
+                userAspectRatioOverrideValueDisplaySize);
+
+        final int desiredWidth =
+                (int) (PORTRAIT_DISPLAY_BOUNDS.width() * DESKTOP_MODE_INITIAL_BOUNDS_SCALE);
+        final int desiredHeight = (int) (desiredWidth * userAspectRatioOverrideValueDisplaySize);
+
+        assertEquals(RESULT_CONTINUE, new CalculateRequestBuilder().setTask(task)
+                .setActivity(activity).calculate());
         assertEquals(desiredWidth, mResult.mBounds.width());
         assertEquals(desiredHeight, mResult.mBounds.height());
     }
@@ -310,14 +785,18 @@
 
         final TestDisplayContent display = createDisplayContent(ORIENTATION_LANDSCAPE,
                 LANDSCAPE_DISPLAY_BOUNDS);
-        final Task task = createTask(display, SCREEN_ORIENTATION_LANDSCAPE, false);
+        final Task task = createTask(display, /* isResizeable */ false);
+        final ActivityRecord activity = createActivity(display, SCREEN_ORIENTATION_LANDSCAPE,
+                task, /* ignoreOrientationRequest */ true);
+
 
         final int desiredWidth =
                 (int) (LANDSCAPE_DISPLAY_BOUNDS.width() * DESKTOP_MODE_INITIAL_BOUNDS_SCALE);
         final int desiredHeight =
                 (int) (LANDSCAPE_DISPLAY_BOUNDS.height() * DESKTOP_MODE_INITIAL_BOUNDS_SCALE);
 
-        assertEquals(RESULT_CONTINUE, new CalculateRequestBuilder().setTask(task).calculate());
+        assertEquals(RESULT_CONTINUE, new CalculateRequestBuilder().setTask(task)
+                .setActivity(activity).calculate());
         assertEquals(desiredWidth, mResult.mBounds.width());
         assertEquals(desiredHeight, mResult.mBounds.height());
     }
@@ -326,18 +805,23 @@
     @EnableFlags(Flags.FLAG_ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS)
     public void testUnResizablePortraitBounds_landscapeDevice_unResizable_portraitOrientation() {
         setupDesktopModeLaunchParamsModifier();
-        doReturn(LETTERBOX_ASPECT_RATIO).when(()
-                -> calculateAspectRatio(any(), any()));
 
         final TestDisplayContent display = createDisplayContent(ORIENTATION_LANDSCAPE,
                 LANDSCAPE_DISPLAY_BOUNDS);
-        final Task task = createTask(display, SCREEN_ORIENTATION_PORTRAIT, false);
+        final Task task = createTask(display, /* isResizeable */ false);
+        final ActivityRecord activity = createActivity(display, SCREEN_ORIENTATION_PORTRAIT,
+                task, /* ignoreOrientationRequest */ true);
+
+        spyOn(activity.mAppCompatController.getDesktopAppCompatAspectRatioPolicy());
+        doReturn(LETTERBOX_ASPECT_RATIO).when(activity.mAppCompatController
+                .getDesktopAppCompatAspectRatioPolicy()).calculateAspectRatio(any());
 
         final int desiredHeight =
                 (int) (LANDSCAPE_DISPLAY_BOUNDS.height() * DESKTOP_MODE_INITIAL_BOUNDS_SCALE);
         final int desiredWidth = (int) (desiredHeight / LETTERBOX_ASPECT_RATIO);
 
-        assertEquals(RESULT_CONTINUE, new CalculateRequestBuilder().setTask(task).calculate());
+        assertEquals(RESULT_CONTINUE, new CalculateRequestBuilder().setTask(task)
+                .setActivity(activity).calculate());
         assertEquals(desiredWidth, mResult.mBounds.width());
         assertEquals(desiredHeight, mResult.mBounds.height());
     }
@@ -350,14 +834,17 @@
 
         final TestDisplayContent display = createDisplayContent(ORIENTATION_PORTRAIT,
                 PORTRAIT_DISPLAY_BOUNDS);
-        final Task task = createTask(display, SCREEN_ORIENTATION_UNSPECIFIED, true);
+        final Task task = createTask(display, /* isResizeable */ true);
+        final ActivityRecord activity = createActivity(display, SCREEN_ORIENTATION_UNSPECIFIED,
+                task, /* ignoreOrientationRequest */ true);
 
         final int desiredWidth =
                 (int) (PORTRAIT_DISPLAY_BOUNDS.width() * DESKTOP_MODE_INITIAL_BOUNDS_SCALE);
         final int desiredHeight =
                 (int) (PORTRAIT_DISPLAY_BOUNDS.height() * DESKTOP_MODE_INITIAL_BOUNDS_SCALE);
 
-        assertEquals(RESULT_CONTINUE, new CalculateRequestBuilder().setTask(task).calculate());
+        assertEquals(RESULT_CONTINUE, new CalculateRequestBuilder().setTask(task)
+                .setActivity(activity).calculate());
         assertEquals(desiredWidth, mResult.mBounds.width());
         assertEquals(desiredHeight, mResult.mBounds.height());
     }
@@ -370,14 +857,17 @@
 
         final TestDisplayContent display = createDisplayContent(ORIENTATION_PORTRAIT,
                 PORTRAIT_DISPLAY_BOUNDS);
-        final Task task = createTask(display, SCREEN_ORIENTATION_PORTRAIT, true);
+        final Task task = createTask(display, /* isResizeable */ true);
+        final ActivityRecord activity = createActivity(display, SCREEN_ORIENTATION_PORTRAIT,
+                task, /* ignoreOrientationRequest */ true);
 
         final int desiredWidth =
                 (int) (PORTRAIT_DISPLAY_BOUNDS.width() * DESKTOP_MODE_INITIAL_BOUNDS_SCALE);
         final int desiredHeight =
                 (int) (PORTRAIT_DISPLAY_BOUNDS.height() * DESKTOP_MODE_INITIAL_BOUNDS_SCALE);
 
-        assertEquals(RESULT_CONTINUE, new CalculateRequestBuilder().setTask(task).calculate());
+        assertEquals(RESULT_CONTINUE, new CalculateRequestBuilder().setTask(task)
+                .setActivity(activity).calculate());
         assertEquals(desiredWidth, mResult.mBounds.width());
         assertEquals(desiredHeight, mResult.mBounds.height());
     }
@@ -390,11 +880,13 @@
 
         final TestDisplayContent display = createDisplayContent(ORIENTATION_PORTRAIT,
                 PORTRAIT_DISPLAY_BOUNDS);
-        final Task task = createTask(display, SCREEN_ORIENTATION_PORTRAIT, true);
+        final Task task = createTask(display, /* isResizeable */ true);
+        final ActivityRecord activity = createActivity(display, SCREEN_ORIENTATION_PORTRAIT,
+                task, /* ignoreOrientationRequest */ true);
 
-        spyOn(mActivity.mAppCompatController.getAppCompatAspectRatioOverrides());
+        spyOn(activity.mAppCompatController.getAppCompatAspectRatioOverrides());
         doReturn(true).when(
-                        mActivity.mAppCompatController.getAppCompatAspectRatioOverrides())
+                        activity.mAppCompatController.getAppCompatAspectRatioOverrides())
                 .isUserFullscreenOverrideEnabled();
 
         final int desiredWidth =
@@ -402,7 +894,8 @@
         final int desiredHeight =
                 (int) (PORTRAIT_DISPLAY_BOUNDS.height() * DESKTOP_MODE_INITIAL_BOUNDS_SCALE);
 
-        assertEquals(RESULT_CONTINUE, new CalculateRequestBuilder().setTask(task).calculate());
+        assertEquals(RESULT_CONTINUE, new CalculateRequestBuilder().setTask(task)
+                .setActivity(activity).calculate());
         assertEquals(desiredWidth, mResult.mBounds.width());
         assertEquals(desiredHeight, mResult.mBounds.height());
     }
@@ -415,11 +908,13 @@
 
         final TestDisplayContent display = createDisplayContent(ORIENTATION_PORTRAIT,
                 PORTRAIT_DISPLAY_BOUNDS);
-        final Task task = createTask(display, SCREEN_ORIENTATION_PORTRAIT, true);
+        final Task task = createTask(display, /* isResizeable */ true);
+        final ActivityRecord activity = createActivity(display, SCREEN_ORIENTATION_PORTRAIT,
+                task, /* ignoreOrientationRequest */ true);
 
-        spyOn(mActivity.mAppCompatController.getAppCompatAspectRatioOverrides());
+        spyOn(activity.mAppCompatController.getAppCompatAspectRatioOverrides());
         doReturn(true).when(
-                        mActivity.mAppCompatController.getAppCompatAspectRatioOverrides())
+                        activity.mAppCompatController.getAppCompatAspectRatioOverrides())
                 .isSystemOverrideToFullscreenEnabled();
 
         final int desiredWidth =
@@ -427,7 +922,8 @@
         final int desiredHeight =
                 (int) (PORTRAIT_DISPLAY_BOUNDS.height() * DESKTOP_MODE_INITIAL_BOUNDS_SCALE);
 
-        assertEquals(RESULT_CONTINUE, new CalculateRequestBuilder().setTask(task).calculate());
+        assertEquals(RESULT_CONTINUE, new CalculateRequestBuilder().setTask(task)
+                .setActivity(activity).calculate());
         assertEquals(desiredWidth, mResult.mBounds.width());
         assertEquals(desiredHeight, mResult.mBounds.height());
     }
@@ -436,19 +932,24 @@
     @EnableFlags(Flags.FLAG_ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS)
     public void testResizableLandscapeBounds_portraitDevice_resizable_landscapeOrientation() {
         setupDesktopModeLaunchParamsModifier();
-        doReturn(LETTERBOX_ASPECT_RATIO).when(()
-                -> calculateAspectRatio(any(), any()));
 
         final TestDisplayContent display = createDisplayContent(ORIENTATION_PORTRAIT,
                 PORTRAIT_DISPLAY_BOUNDS);
-        final Task task = createTask(display, SCREEN_ORIENTATION_LANDSCAPE, true);
+        final Task task = createTask(display, /* isResizeable */ true);
+        final ActivityRecord activity = createActivity(display, SCREEN_ORIENTATION_LANDSCAPE,
+                task, /* ignoreOrientationRequest */ true);
+
+        spyOn(activity.mAppCompatController.getDesktopAppCompatAspectRatioPolicy());
+        doReturn(LETTERBOX_ASPECT_RATIO).when(activity.mAppCompatController
+                .getDesktopAppCompatAspectRatioPolicy()).calculateAspectRatio(any());
 
         final int desiredWidth = PORTRAIT_DISPLAY_BOUNDS.width()
                 - (DESKTOP_MODE_LANDSCAPE_APP_PADDING * 2);
         final int desiredHeight = (int)
                 ((PORTRAIT_DISPLAY_BOUNDS.width() / LETTERBOX_ASPECT_RATIO) + 0.5f);
 
-        assertEquals(RESULT_CONTINUE, new CalculateRequestBuilder().setTask(task).calculate());
+        assertEquals(RESULT_CONTINUE, new CalculateRequestBuilder().setTask(task)
+                .setActivity(activity).calculate());
         assertEquals(desiredWidth, mResult.mBounds.width());
         assertEquals(desiredHeight, mResult.mBounds.height());
     }
@@ -461,14 +962,18 @@
 
         final TestDisplayContent display = createDisplayContent(ORIENTATION_PORTRAIT,
                 PORTRAIT_DISPLAY_BOUNDS);
-        final Task task = createTask(display, SCREEN_ORIENTATION_PORTRAIT, false);
+        final Task task = createTask(display, /* isResizeable */ false);
+        final ActivityRecord activity = createActivity(display, SCREEN_ORIENTATION_PORTRAIT,
+                task, /* ignoreOrientationRequest */ true);
+
 
         final int desiredWidth =
                 (int) (PORTRAIT_DISPLAY_BOUNDS.width() * DESKTOP_MODE_INITIAL_BOUNDS_SCALE);
         final int desiredHeight =
                 (int) (PORTRAIT_DISPLAY_BOUNDS.height() * DESKTOP_MODE_INITIAL_BOUNDS_SCALE);
 
-        assertEquals(RESULT_CONTINUE, new CalculateRequestBuilder().setTask(task).calculate());
+        assertEquals(RESULT_CONTINUE, new CalculateRequestBuilder().setTask(task)
+                .setActivity(activity).calculate());
         assertEquals(desiredWidth, mResult.mBounds.width());
         assertEquals(desiredHeight, mResult.mBounds.height());
     }
@@ -477,18 +982,23 @@
     @EnableFlags(Flags.FLAG_ENABLE_WINDOWING_DYNAMIC_INITIAL_BOUNDS)
     public void testUnResizableLandscapeBounds_portraitDevice_unResizable_landscapeOrientation() {
         setupDesktopModeLaunchParamsModifier();
-        doReturn(LETTERBOX_ASPECT_RATIO).when(()
-                -> calculateAspectRatio(any(), any()));
 
         final TestDisplayContent display = createDisplayContent(ORIENTATION_PORTRAIT,
                 PORTRAIT_DISPLAY_BOUNDS);
-        final Task task = createTask(display, SCREEN_ORIENTATION_LANDSCAPE, false);
+        final Task task = createTask(display, /* isResizeable */ false);
+        final ActivityRecord activity = createActivity(display, SCREEN_ORIENTATION_LANDSCAPE,
+                task, /* ignoreOrientationRequest */ true);
+
+        spyOn(activity.mAppCompatController.getDesktopAppCompatAspectRatioPolicy());
+        doReturn(LETTERBOX_ASPECT_RATIO).when(activity.mAppCompatController
+                .getDesktopAppCompatAspectRatioPolicy()).calculateAspectRatio(any());
 
         final int desiredWidth = PORTRAIT_DISPLAY_BOUNDS.width()
                 - (DESKTOP_MODE_LANDSCAPE_APP_PADDING * 2);
         final int desiredHeight = (int) (desiredWidth / LETTERBOX_ASPECT_RATIO);
 
-        assertEquals(RESULT_CONTINUE, new CalculateRequestBuilder().setTask(task).calculate());
+        assertEquals(RESULT_CONTINUE, new CalculateRequestBuilder().setTask(task)
+                .setActivity(activity).calculate());
         assertEquals(desiredWidth, mResult.mBounds.width());
         assertEquals(desiredHeight, mResult.mBounds.height());
     }
@@ -755,24 +1265,64 @@
         assertEquals(WINDOWING_MODE_FREEFORM, mResult.mWindowingMode);
     }
 
-    private Task createTask(DisplayContent display, int orientation, Boolean isResizeable) {
+    private Task createTask(DisplayContent display, Boolean isResizeable) {
         final int resizeMode = isResizeable ? RESIZE_MODE_RESIZEABLE
                 : RESIZE_MODE_UNRESIZEABLE;
         final Task task = new TaskBuilder(mSupervisor).setActivityType(
                 ACTIVITY_TYPE_STANDARD).setDisplay(display).build();
         task.setResizeMode(resizeMode);
-        mActivity = new ActivityBuilder(task.mAtmService)
+        return task;
+    }
+
+    private ActivityRecord createActivity(DisplayContent display, int orientation, Task task,
+            boolean ignoreOrientationRequest) {
+        final ActivityRecord activity = new ActivityBuilder(task.mAtmService)
                 .setTask(task)
+                .setComponent(ComponentName.createRelative(task.mAtmService.mContext,
+                        DesktopModeLaunchParamsModifierTests.class.getName()))
+                .setUid(android.os.Process.myUid())
                 .setScreenOrientation(orientation)
                 .setOnTop(true).build();
+        activity.onDisplayChanged(display);
+        activity.setOccludesParent(true);
+        activity.setVisible(true);
+        activity.setVisibleRequested(true);
+        activity.mDisplayContent.setIgnoreOrientationRequest(ignoreOrientationRequest);
 
-        mActivity.onDisplayChanged(display);
-        mActivity.setOccludesParent(true);
-        mActivity.setVisible(true);
-        mActivity.setVisibleRequested(true);
-        mActivity.mDisplayContent.setIgnoreOrientationRequest(/* ignoreOrientationRequest */ true);
+        return activity;
+    }
 
-        return task;
+    private void setDesiredAspectRatio(ActivityRecord activity, float aspectRatio) {
+        final DesktopAppCompatAspectRatioPolicy desktopAppCompatAspectRatioPolicy =
+                activity.mAppCompatController.getDesktopAppCompatAspectRatioPolicy();
+        spyOn(desktopAppCompatAspectRatioPolicy);
+        doReturn(aspectRatio).when(desktopAppCompatAspectRatioPolicy)
+                .getDesiredAspectRatio(any());
+    }
+
+    private void applyUserMinAspectRatioOverride(ActivityRecord activity, int overrideCode,
+            float overrideValue) {
+        // Set desired aspect ratio to be below minimum so override can take effect.
+        final DesktopAppCompatAspectRatioPolicy desktopAppCompatAspectRatioPolicy =
+                activity.mAppCompatController.getDesktopAppCompatAspectRatioPolicy();
+        spyOn(desktopAppCompatAspectRatioPolicy);
+        doReturn(1f).when(desktopAppCompatAspectRatioPolicy)
+                .getDesiredAspectRatio(any());
+
+        // Enable user aspect ratio settings
+        final AppCompatConfiguration appCompatConfiguration =
+                activity.mWmService.mAppCompatConfiguration;
+        spyOn(appCompatConfiguration);
+        doReturn(true).when(appCompatConfiguration)
+                .isUserAppAspectRatioSettingsEnabled();
+
+        // Simulate user min aspect ratio override being set.
+        final AppCompatAspectRatioOverrides appCompatAspectRatioOverrides =
+                activity.mAppCompatController.getAppCompatAspectRatioOverrides();
+        spyOn(appCompatAspectRatioOverrides);
+        doReturn(overrideValue).when(appCompatAspectRatioOverrides).getUserMinAspectRatio();
+        doReturn(overrideCode).when(appCompatAspectRatioOverrides)
+                .getUserMinAspectRatioOverrideCode();
     }
 
     private TestDisplayContent createDisplayContent(int orientation, Rect displayBounds) {
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 84c2c32..4ab2fcf 100644
--- a/services/tests/wmtests/src/com/android/server/wm/RootWindowContainerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/RootWindowContainerTests.java
@@ -205,6 +205,28 @@
     }
 
     @Test
+    public void testAllResumedActivitiesIdle() {
+        final ActivityRecord activity1 = new ActivityBuilder(mAtm).setCreateTask(true).build();
+        final ActivityRecord activity2 = new ActivityBuilder(mAtm).setCreateTask(true).build();
+        final WindowProcessController proc2 = activity2.app;
+        activity1.setState(RESUMED, "test");
+        activity2.detachFromProcess();
+        assertThat(mWm.mRoot.allResumedActivitiesIdle()).isFalse();
+
+        activity1.idle = true;
+        assertThat(mWm.mRoot.allResumedActivitiesIdle()).isFalse();
+
+        activity2.setProcess(proc2);
+        activity2.setState(RESUMED, "test");
+        activity2.idle = true;
+        assertThat(mWm.mRoot.allResumedActivitiesIdle()).isTrue();
+
+        activity1.idle = false;
+        activity1.setVisibleRequested(false);
+        assertThat(mWm.mRoot.allResumedActivitiesIdle()).isTrue();
+    }
+
+    @Test
     public void testTaskLayerRank() {
         final Task rootTask = new TaskBuilder(mSupervisor).build();
         final Task task1 = new TaskBuilder(mSupervisor).setParentTask(rootTask).build();
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 65e3baa..1e1055b 100644
--- a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
@@ -2394,7 +2394,13 @@
     private void testUserOverrideAspectRatio(boolean isUnresizable, int screenOrientation,
             float expectedAspectRatio, @PackageManager.UserMinAspectRatio int aspectRatio,
             boolean enabled) {
-        final ActivityRecord activity = getActivityBuilderOnSameTask().build();
+        final ActivityRecord activity = getActivityBuilderWithoutTask().build();
+        final DesktopAppCompatAspectRatioPolicy desktopAppCompatAspectRatioPolicy =
+                activity.mAppCompatController.getDesktopAppCompatAspectRatioPolicy();
+        spyOn(desktopAppCompatAspectRatioPolicy);
+        doReturn(enabled).when(desktopAppCompatAspectRatioPolicy)
+                .hasMinAspectRatioOverride(any());
+        mTask.addChild(activity);
         activity.mDisplayContent.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */);
         spyOn(activity.mWmService.mAppCompatConfiguration);
         doReturn(enabled).when(activity.mWmService.mAppCompatConfiguration)
@@ -4944,8 +4950,11 @@
     }
 
     private ActivityBuilder getActivityBuilderOnSameTask() {
+        return getActivityBuilderWithoutTask().setTask(mTask);
+    }
+
+    private ActivityBuilder getActivityBuilderWithoutTask() {
         return new ActivityBuilder(mAtm)
-                .setTask(mTask)
                 .setComponent(ComponentName.createRelative(mContext,
                         SizeCompatTests.class.getName()))
                 .setUid(android.os.Process.myUid());
diff --git a/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java b/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java
index 49e349c..56fca31 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TransitionTests.java
@@ -1550,10 +1550,6 @@
 
         // An active transient launch overrides idle state to avoid clearing power mode before the
         // transition is finished.
-        spyOn(mRootWindowContainer.mTransitionController);
-        doAnswer(invocation -> controller.isTransientLaunch(invocation.getArgument(0))).when(
-                mRootWindowContainer.mTransitionController).isTransientLaunch(any());
-        activity2.getTask().setResumedActivity(activity2, "test");
         activity2.idle = true;
         assertFalse(mRootWindowContainer.allResumedActivitiesIdle());
 
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowTracingLegacyTest.java b/services/tests/wmtests/src/com/android/server/wm/WindowTracingLegacyTest.java
index 48a8d55..a1d35a7 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowTracingLegacyTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowTracingLegacyTest.java
@@ -125,7 +125,7 @@
     public void trace_dumpsWindowManagerState_whenTracing() throws Exception {
         mWindowTracing.startTrace(mock(PrintWriter.class));
         mWindowTracing.logState("where");
-        verify(mWmMock, times(2)).dumpDebugLocked(any(), eq(WindowTraceLogLevel.TRIM));
+        verify(mWmMock, times(2)).dumpDebugLocked(any(), eq(WindowTracingLogLevel.TRIM));
     }
 
     @Test
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowTracingPerfettoTest.java b/services/tests/wmtests/src/com/android/server/wm/WindowTracingPerfettoTest.java
new file mode 100644
index 0000000..1d567b1
--- /dev/null
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowTracingPerfettoTest.java
@@ -0,0 +1,141 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm;
+
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.times;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.verifyZeroInteractions;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
+
+import static java.io.File.createTempFile;
+import static java.nio.file.Files.createTempDirectory;
+
+import android.platform.test.annotations.Presubmit;
+import android.tools.ScenarioBuilder;
+import android.tools.traces.io.ResultWriter;
+import android.tools.traces.monitors.PerfettoTraceMonitor;
+import android.view.Choreographer;
+
+import androidx.test.filters.SmallTest;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+
+import perfetto.protos.PerfettoConfig.WindowManagerConfig.LogFrequency;
+
+/**
+ * Test class for {@link WindowTracingPerfetto}.
+ */
+@SmallTest
+@Presubmit
+public class WindowTracingPerfettoTest {
+    @Mock
+    private WindowManagerService mWmMock;
+    @Mock
+    private Choreographer mChoreographer;
+    private WindowTracing mWindowTracing;
+    private PerfettoTraceMonitor mTraceMonitor;
+    private ResultWriter mWriter;
+
+    @Before
+    public void setUp() throws Exception {
+        MockitoAnnotations.initMocks(this);
+
+        mWindowTracing = new WindowTracingPerfetto(mWmMock, mChoreographer,
+                new WindowManagerGlobalLock());
+
+        mWriter = new ResultWriter()
+            .forScenario(new ScenarioBuilder()
+                    .forClass(createTempFile("temp", "").getName()).build())
+            .withOutputDir(createTempDirectory("temp").toFile())
+            .setRunComplete();
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        stopTracing();
+    }
+
+    @Test
+    public void isEnabled_returnsFalseByDefault() {
+        assertFalse(mWindowTracing.isEnabled());
+    }
+
+    @Test
+    public void isEnabled_returnsTrueAfterStartThenFalseAfterStop() {
+        startTracing(false);
+        assertTrue(mWindowTracing.isEnabled());
+
+        stopTracing();
+        assertFalse(mWindowTracing.isEnabled());
+    }
+
+    @Test
+    public void trace_ignoresLogStateCalls_ifTracingIsDisabled() {
+        mWindowTracing.logState("where");
+        verifyZeroInteractions(mWmMock);
+    }
+
+    @Test
+    public void trace_writesInitialStateSnapshot_whenTracingStarts() throws Exception {
+        startTracing(false);
+        verify(mWmMock, times(1)).dumpDebugLocked(any(), eq(WindowTracingLogLevel.ALL));
+    }
+
+    @Test
+    public void trace_writesStateSnapshot_onLogStateCall() throws Exception {
+        startTracing(false);
+        mWindowTracing.logState("where");
+        verify(mWmMock, times(2)).dumpDebugLocked(any(), eq(WindowTracingLogLevel.ALL));
+    }
+
+    @Test
+    public void dump_writesOneSingleStateSnapshot() throws Exception {
+        startTracing(true);
+        mWindowTracing.logState("where");
+        verify(mWmMock, times(1)).dumpDebugLocked(any(), eq(WindowTracingLogLevel.ALL));
+    }
+
+    private void startTracing(boolean isDump) {
+        if (isDump) {
+            mTraceMonitor = PerfettoTraceMonitor
+                    .newBuilder()
+                    .enableWindowManagerDump()
+                    .build();
+        } else {
+            mTraceMonitor = PerfettoTraceMonitor
+                    .newBuilder()
+                    .enableWindowManagerTrace(LogFrequency.LOG_FREQUENCY_TRANSACTION)
+                    .build();
+        }
+        mTraceMonitor.start();
+    }
+
+    private void stopTracing() {
+        if (mTraceMonitor == null || !mTraceMonitor.isEnabled()) {
+            return;
+        }
+        mTraceMonitor.stop(mWriter);
+    }
+}
diff --git a/telephony/java/android/telephony/satellite/ISatelliteProvisionStateCallback.aidl b/telephony/java/android/telephony/satellite/ISatelliteProvisionStateCallback.aidl
index f981fb1..5f0d986 100644
--- a/telephony/java/android/telephony/satellite/ISatelliteProvisionStateCallback.aidl
+++ b/telephony/java/android/telephony/satellite/ISatelliteProvisionStateCallback.aidl
@@ -16,6 +16,8 @@
 
 package android.telephony.satellite;
 
+import android.telephony.satellite.SatelliteSubscriberProvisionStatus;
+
 /**
  * Interface for satellite provision state callback.
  * @hide
@@ -27,4 +29,14 @@
      * @param provisioned True means the service is provisioned and false means it is not.
      */
     void onSatelliteProvisionStateChanged(in boolean provisioned);
+
+    /**
+     * Called when the provisioning state of one or more SatelliteSubscriberInfos changes.
+     *
+     * @param satelliteSubscriberProvisionStatus The List contains the latest provisioning states of
+     * the SatelliteSubscriberInfos.
+     * @hide
+     */
+    void onSatelliteSubscriptionProvisionStateChanged(in List<SatelliteSubscriberProvisionStatus>
+        satelliteSubscriberProvisionStatus);
 }
diff --git a/telephony/java/android/telephony/satellite/SatelliteManager.java b/telephony/java/android/telephony/satellite/SatelliteManager.java
index e657d7f..6ef953c 100644
--- a/telephony/java/android/telephony/satellite/SatelliteManager.java
+++ b/telephony/java/android/telephony/satellite/SatelliteManager.java
@@ -234,7 +234,7 @@
 
     /**
      * Bundle key to get the response from
-     * {@link #requestProvisionSubscriberIds(Executor, OutcomeReceiver)}.
+     * {@link #requestSatelliteSubscriberProvisionStatus(Executor, OutcomeReceiver)}.
      * @hide
      */
     public static final String KEY_REQUEST_PROVISION_SUBSCRIBER_ID_TOKEN =
@@ -242,13 +242,6 @@
 
     /**
      * Bundle key to get the response from
-     * {@link #requestIsProvisioned(String, Executor, OutcomeReceiver)}.
-     * @hide
-     */
-    public static final String KEY_IS_SATELLITE_PROVISIONED = "request_is_satellite_provisioned";
-
-    /**
-     * Bundle key to get the response from
      * {@link #provisionSatellite(List, Executor, OutcomeReceiver)}.
      * @hide
      */
@@ -1404,6 +1397,16 @@
                                         () -> callback.onSatelliteProvisionStateChanged(
                                                 provisioned)));
                             }
+
+                            @FlaggedApi(Flags.FLAG_CARRIER_ROAMING_NB_IOT_NTN)
+                            @Override
+                            public void onSatelliteSubscriptionProvisionStateChanged(
+                                    @NonNull List<SatelliteSubscriberProvisionStatus>
+                                            satelliteSubscriberProvisionStatus) {
+                                executor.execute(() -> Binder.withCleanCallingIdentity(() ->
+                                        callback.onSatelliteSubscriptionProvisionStateChanged(
+                                                satelliteSubscriberProvisionStatus)));
+                            }
                         };
                 sSatelliteProvisionStateCallbackMap.put(callback, internalCallback);
                 return telephony.registerForSatelliteProvisionStateChanged(
@@ -2641,8 +2644,10 @@
      */
     @RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION)
     @FlaggedApi(Flags.FLAG_CARRIER_ROAMING_NB_IOT_NTN)
-    public void requestProvisionSubscriberIds(@NonNull @CallbackExecutor Executor executor,
-            @NonNull OutcomeReceiver<List<SatelliteSubscriberInfo>, SatelliteException> callback) {
+    public void requestSatelliteSubscriberProvisionStatus(
+            @NonNull @CallbackExecutor Executor executor,
+            @NonNull OutcomeReceiver<List<SatelliteSubscriberProvisionStatus>,
+                    SatelliteException> callback) {
         Objects.requireNonNull(executor);
         Objects.requireNonNull(callback);
 
@@ -2654,10 +2659,10 @@
                     protected void onReceiveResult(int resultCode, Bundle resultData) {
                         if (resultCode == SATELLITE_RESULT_SUCCESS) {
                             if (resultData.containsKey(KEY_REQUEST_PROVISION_SUBSCRIBER_ID_TOKEN)) {
-                                List<SatelliteSubscriberInfo> list =
+                                List<SatelliteSubscriberProvisionStatus> list =
                                         resultData.getParcelableArrayList(
                                                 KEY_REQUEST_PROVISION_SUBSCRIBER_ID_TOKEN,
-                                                SatelliteSubscriberInfo.class);
+                                                SatelliteSubscriberProvisionStatus.class);
                                 executor.execute(() -> Binder.withCleanCallingIdentity(() ->
                                         callback.onResult(list)));
                             } else {
@@ -2672,70 +2677,14 @@
                         }
                     }
                 };
-                telephony.requestProvisionSubscriberIds(receiver);
+                telephony.requestSatelliteSubscriberProvisionStatus(receiver);
             } else {
-                loge("requestProvisionSubscriberIds() invalid telephony");
+                loge("requestSatelliteSubscriberProvisionStatus() invalid telephony");
                 executor.execute(() -> Binder.withCleanCallingIdentity(() -> callback.onError(
                         new SatelliteException(SATELLITE_RESULT_ILLEGAL_STATE))));
             }
         } catch (RemoteException ex) {
-            loge("requestProvisionSubscriberIds() RemoteException: " + ex);
-            executor.execute(() -> Binder.withCleanCallingIdentity(() -> callback.onError(
-                    new SatelliteException(SATELLITE_RESULT_ILLEGAL_STATE))));
-        }
-    }
-
-    /**
-     * Request to get provisioned status for given a satellite subscriber id.
-     *
-     * @param satelliteSubscriberId Satellite subscriber id requiring provisioned status check.
-     * @param executor The executor on which the callback will be called.
-     * @param callback callback.
-     *
-     * @throws SecurityException if the caller doesn't have required permission.
-     * @hide
-     */
-    @RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION)
-    @FlaggedApi(Flags.FLAG_CARRIER_ROAMING_NB_IOT_NTN)
-    public void requestIsProvisioned(@NonNull String satelliteSubscriberId,
-            @NonNull @CallbackExecutor Executor executor,
-            @NonNull OutcomeReceiver<Boolean, SatelliteException> callback) {
-        Objects.requireNonNull(satelliteSubscriberId);
-        Objects.requireNonNull(executor);
-        Objects.requireNonNull(callback);
-
-        try {
-            ITelephony telephony = getITelephony();
-            if (telephony != null) {
-                ResultReceiver receiver = new ResultReceiver(null) {
-                    @Override
-                    protected void onReceiveResult(int resultCode, Bundle resultData) {
-                        if (resultCode == SATELLITE_RESULT_SUCCESS) {
-                            if (resultData.containsKey(KEY_IS_SATELLITE_PROVISIONED)) {
-                                boolean isIsProvisioned =
-                                        resultData.getBoolean(KEY_IS_SATELLITE_PROVISIONED);
-                                executor.execute(() -> Binder.withCleanCallingIdentity(() ->
-                                        callback.onResult(isIsProvisioned)));
-                            } else {
-                                loge("KEY_IS_SATELLITE_PROVISIONED does not exist.");
-                                executor.execute(() -> Binder.withCleanCallingIdentity(() ->
-                                        callback.onError(new SatelliteException(
-                                                SATELLITE_RESULT_REQUEST_FAILED))));
-                            }
-                        } else {
-                            executor.execute(() -> Binder.withCleanCallingIdentity(() ->
-                                    callback.onError(new SatelliteException(resultCode))));
-                        }
-                    }
-                };
-                telephony.requestIsProvisioned(satelliteSubscriberId, receiver);
-            } else {
-                loge("requestIsSatelliteProvisioned() invalid telephony");
-                executor.execute(() -> Binder.withCleanCallingIdentity(() -> callback.onError(
-                        new SatelliteException(SATELLITE_RESULT_ILLEGAL_STATE))));
-            }
-        } catch (RemoteException ex) {
-            loge("requestIsSatelliteProvisioned() RemoteException: " + ex);
+            loge("requestSatelliteSubscriberProvisionStatus() RemoteException: " + ex);
             executor.execute(() -> Binder.withCleanCallingIdentity(() -> callback.onError(
                     new SatelliteException(SATELLITE_RESULT_ILLEGAL_STATE))));
         }
diff --git a/telephony/java/android/telephony/satellite/SatelliteModemEnableRequestAttributes.java b/telephony/java/android/telephony/satellite/SatelliteModemEnableRequestAttributes.java
new file mode 100644
index 0000000..5e56f84
--- /dev/null
+++ b/telephony/java/android/telephony/satellite/SatelliteModemEnableRequestAttributes.java
@@ -0,0 +1,132 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.telephony.satellite;
+
+import android.annotation.NonNull;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.util.Objects;
+
+/**
+ * SatelliteModemEnableRequestAttributes is used to pack info needed by modem to allow carrier to
+ * roam to satellite.
+ *
+ * @hide
+ */
+public final class SatelliteModemEnableRequestAttributes implements Parcelable {
+
+    /** {@code true} to enable satellite and {@code false} to disable satellite */
+    private final boolean mIsEnabled;
+    /**
+     * {@code true} to enable demo mode and {@code false} to disable. When disabling satellite,
+     * {@code mIsDemoMode} is always considered as {@code false} by Telephony.
+     */
+    private final boolean mIsDemoMode;
+    /**
+     * {@code true} means satellite is enabled for emergency mode, {@code false} otherwise. When
+     * disabling satellite, {@code isEmergencyMode} is always considered as {@code false} by
+     * Telephony.
+     */
+    private final boolean mIsEmergencyMode;
+
+    /** The subscription related info */
+    @NonNull private final SatelliteSubscriptionInfo mSatelliteSubscriptionInfo;
+
+    public SatelliteModemEnableRequestAttributes(boolean isEnabled, boolean isDemoMode,
+            boolean isEmergencyMode, @NonNull SatelliteSubscriptionInfo satelliteSubscriptionInfo) {
+        mIsEnabled = isEnabled;
+        mIsDemoMode = isDemoMode;
+        mIsEmergencyMode = isEmergencyMode;
+        mSatelliteSubscriptionInfo = satelliteSubscriptionInfo;
+    }
+
+    private SatelliteModemEnableRequestAttributes(Parcel in) {
+        mIsEnabled = in.readBoolean();
+        mIsDemoMode = in.readBoolean();
+        mIsEmergencyMode = in.readBoolean();
+        mSatelliteSubscriptionInfo = in.readParcelable(
+                SatelliteSubscriptionInfo.class.getClassLoader(), SatelliteSubscriptionInfo.class);
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(@NonNull Parcel dest, int flags) {
+        dest.writeBoolean(mIsEnabled);
+        dest.writeBoolean(mIsDemoMode);
+        dest.writeBoolean(mIsEmergencyMode);
+        mSatelliteSubscriptionInfo.writeToParcel(dest, flags);
+    }
+
+    public static final Creator<SatelliteModemEnableRequestAttributes> CREATOR = new Creator<>() {
+        @Override
+        public SatelliteModemEnableRequestAttributes createFromParcel(Parcel in) {
+            return new SatelliteModemEnableRequestAttributes(in);
+        }
+
+        @Override
+        public SatelliteModemEnableRequestAttributes[] newArray(int size) {
+            return new SatelliteModemEnableRequestAttributes[size];
+        }
+    };
+
+    @Override
+    public String toString() {
+        return (new StringBuilder()).append("SatelliteModemEnableRequestAttributes{")
+                .append(", mIsEnabled=").append(mIsEnabled)
+                .append(", mIsDemoMode=").append(mIsDemoMode)
+                .append(", mIsDemoMode=").append(mIsDemoMode)
+                .append("mSatelliteSubscriptionInfo=").append(mSatelliteSubscriptionInfo)
+                .append("}")
+                .toString();
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) return true;
+        if (o == null || getClass() != o.getClass()) return false;
+        SatelliteModemEnableRequestAttributes that = (SatelliteModemEnableRequestAttributes) o;
+        return mIsEnabled == that.mIsEnabled && mIsDemoMode == that.mIsDemoMode
+                && mIsEmergencyMode == that.mIsEmergencyMode && mSatelliteSubscriptionInfo.equals(
+                that.mSatelliteSubscriptionInfo);
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(mIsEnabled, mIsDemoMode, mIsEmergencyMode, mSatelliteSubscriptionInfo);
+    }
+
+    public boolean isEnabled() {
+        return mIsEnabled;
+    }
+
+    public boolean isDemoMode() {
+        return mIsDemoMode;
+    }
+
+    public boolean isEmergencyMode() {
+        return mIsEmergencyMode;
+    }
+
+    @NonNull public SatelliteSubscriptionInfo getSatelliteSubscriptionInfo() {
+        return mSatelliteSubscriptionInfo;
+    }
+}
diff --git a/telephony/java/android/telephony/satellite/SatelliteProvisionStateCallback.java b/telephony/java/android/telephony/satellite/SatelliteProvisionStateCallback.java
index a12952b..e8ae0f5 100644
--- a/telephony/java/android/telephony/satellite/SatelliteProvisionStateCallback.java
+++ b/telephony/java/android/telephony/satellite/SatelliteProvisionStateCallback.java
@@ -17,10 +17,13 @@
 package android.telephony.satellite;
 
 import android.annotation.FlaggedApi;
+import android.annotation.NonNull;
 import android.annotation.SystemApi;
 
 import com.android.internal.telephony.flags.Flags;
 
+import java.util.List;
+
 /**
  * A callback class for monitoring satellite provision state change events.
  *
@@ -39,4 +42,16 @@
      */
     @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
     void onSatelliteProvisionStateChanged(boolean provisioned);
+
+    /**
+     * Called when the provisioning state of one or more SatelliteSubscriberInfos changes.
+     *
+     * @param satelliteSubscriberProvisionStatus The List contains the latest provisioning states
+     *                                           of the SatelliteSubscriberInfos.
+     * @hide
+     */
+    @FlaggedApi(Flags.FLAG_CARRIER_ROAMING_NB_IOT_NTN)
+    default void onSatelliteSubscriptionProvisionStateChanged(
+            @NonNull List<SatelliteSubscriberProvisionStatus>
+                    satelliteSubscriberProvisionStatus) {};
 }
diff --git a/telephony/java/android/telephony/satellite/SatelliteSubscriberInfo.java b/telephony/java/android/telephony/satellite/SatelliteSubscriberInfo.java
index f26219b..dbe5ddd 100644
--- a/telephony/java/android/telephony/satellite/SatelliteSubscriberInfo.java
+++ b/telephony/java/android/telephony/satellite/SatelliteSubscriberInfo.java
@@ -17,12 +17,15 @@
 package android.telephony.satellite;
 
 import android.annotation.FlaggedApi;
+import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.os.Parcel;
 import android.os.Parcelable;
 
 import com.android.internal.telephony.flags.Flags;
 
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
 import java.util.Objects;
 
 /**
@@ -39,27 +42,117 @@
     /** provision subscriberId */
     @NonNull
     private String mSubscriberId;
-
     /** carrier id */
     private int mCarrierId;
 
     /** apn */
     private String mNiddApn;
+    private int mSubId;
 
-    /**
-     * @hide
-     */
-    public SatelliteSubscriberInfo(@NonNull String subscriberId, @NonNull int carrierId,
-            @NonNull String niddApn) {
-        this.mCarrierId = carrierId;
-        this.mSubscriberId = subscriberId;
-        this.mNiddApn = niddApn;
-    }
+    /** SubscriberId format is the ICCID. */
+    @FlaggedApi(Flags.FLAG_CARRIER_ROAMING_NB_IOT_NTN)
+    public static final int ICCID = 0;
+    /** SubscriberId format is the 6 digit of IMSI + MSISDN. */
+    @FlaggedApi(Flags.FLAG_CARRIER_ROAMING_NB_IOT_NTN)
+    public static final int IMSI_MSISDN = 1;
+
+    /** Type of subscriber id */
+    @SubscriberIdType private int mSubscriberIdType;
+    /** @hide */
+    @IntDef(prefix = "SubscriberId_Type_", value = {
+            ICCID,
+            IMSI_MSISDN
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface SubscriberIdType {}
 
     private SatelliteSubscriberInfo(Parcel in) {
         readFromParcel(in);
     }
 
+    public SatelliteSubscriberInfo(@NonNull Builder builder) {
+        this.mSubscriberId = builder.mSubscriberId;
+        this.mCarrierId = builder.mCarrierId;
+        this.mNiddApn = builder.mNiddApn;
+        this.mSubId = builder.mSubId;
+        this.mSubscriberIdType = builder.mSubscriberIdType;
+    }
+
+    /**
+     * Builder class for constructing SatelliteSubscriberInfo objects
+     *
+     * @hide
+     */
+    @FlaggedApi(Flags.FLAG_CARRIER_ROAMING_NB_IOT_NTN)
+    public static class Builder {
+        @NonNull private String mSubscriberId;
+        private int mCarrierId;
+        @NonNull
+        private String mNiddApn;
+        private int mSubId;
+        @SubscriberIdType
+        private int mSubscriberIdType;
+
+        /**
+         * Set the SubscriberId and returns the Builder class.
+         *
+         * @hide
+         */
+        public Builder setSubscriberId(String subscriberId) {
+            mSubscriberId = subscriberId;
+            return this;
+        }
+
+        /**
+         * Set the CarrierId and returns the Builder class.
+         * @hide
+         */
+        @NonNull
+        public Builder setCarrierId(int carrierId) {
+            mCarrierId = carrierId;
+            return this;
+        }
+
+        /**
+         * Set the niddApn and returns the Builder class.
+         * @hide
+         */
+        @NonNull
+        public Builder setNiddApn(String niddApn) {
+            mNiddApn = niddApn;
+            return this;
+        }
+
+        /**
+         * Set the subId and returns the Builder class.
+         * @hide
+         */
+        @NonNull
+        public Builder setSubId(int subId) {
+            mSubId = subId;
+            return this;
+        }
+
+        /**
+         * Set the SubscriberIdType and returns the Builder class.
+         * @hide
+         */
+        @NonNull
+        public Builder setSubscriberIdType(@SubscriberIdType int subscriberIdType) {
+            mSubscriberIdType = subscriberIdType;
+            return this;
+        }
+
+        /**
+         * Returns SatelliteSubscriberInfo object.
+         * @hide
+         */
+        @NonNull
+        public SatelliteSubscriberInfo build() {
+            return new SatelliteSubscriberInfo(this);
+        }
+    }
+
     /**
      * @hide
      */
@@ -69,6 +162,8 @@
         out.writeString(mSubscriberId);
         out.writeInt(mCarrierId);
         out.writeString(mNiddApn);
+        out.writeInt(mSubId);
+        out.writeInt(mSubscriberIdType);
     }
 
     @FlaggedApi(Flags.FLAG_CARRIER_ROAMING_NB_IOT_NTN)
@@ -121,6 +216,24 @@
         return mNiddApn;
     }
 
+    /**
+     * @return subId.
+     * @hide
+     */
+    @FlaggedApi(Flags.FLAG_CARRIER_ROAMING_NB_IOT_NTN)
+    public int getSubId() {
+        return mSubId;
+    }
+
+    /**
+     * @return subscriberIdType.
+     * @hide
+     */
+    @FlaggedApi(Flags.FLAG_CARRIER_ROAMING_NB_IOT_NTN)
+    public @SubscriberIdType int getSubscriberIdType() {
+        return mSubscriberIdType;
+    }
+
     @NonNull
     @Override
     public String toString() {
@@ -136,26 +249,37 @@
 
         sb.append("NiddApn:");
         sb.append(mNiddApn);
+        sb.append(",");
+
+        sb.append("SubId:");
+        sb.append(mSubId);
+        sb.append(",");
+
+        sb.append("SubscriberIdType:");
+        sb.append(mSubscriberIdType);
         return sb.toString();
     }
 
     @Override
     public int hashCode() {
-        return Objects.hash(mSubscriberId, mCarrierId, mNiddApn);
+        return Objects.hash(mSubscriberId, mCarrierId, mNiddApn, mSubId, mSubscriberIdType);
     }
 
     @Override
     public boolean equals(Object o) {
         if (this == o) return true;
-        if (o == null || getClass() != o.getClass()) return false;
+        if (!(o instanceof SatelliteSubscriberInfo)) return false;
         SatelliteSubscriberInfo that = (SatelliteSubscriberInfo) o;
-        return mSubscriberId.equals(that.mSubscriberId) && mCarrierId
-                == that.mCarrierId && mNiddApn.equals(that.mNiddApn);
+        return Objects.equals(mSubscriberId, that.mSubscriberId) && mCarrierId == that.mCarrierId
+                && Objects.equals(mNiddApn, that.mNiddApn) && mSubId == that.mSubId
+                && mSubscriberIdType == that.mSubscriberIdType;
     }
 
     private void readFromParcel(Parcel in) {
         mSubscriberId = in.readString();
         mCarrierId = in.readInt();
         mNiddApn = in.readString();
+        mSubId = in.readInt();
+        mSubscriberIdType = in.readInt();
     }
 }
diff --git a/telephony/java/android/telephony/satellite/SatelliteSubscriberProvisionStatus.aidl b/telephony/java/android/telephony/satellite/SatelliteSubscriberProvisionStatus.aidl
new file mode 100644
index 0000000..80de779
--- /dev/null
+++ b/telephony/java/android/telephony/satellite/SatelliteSubscriberProvisionStatus.aidl
@@ -0,0 +1,19 @@
+/*
+ * Copyright 2024, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.telephony.satellite;
+
+parcelable SatelliteSubscriberProvisionStatus;
\ No newline at end of file
diff --git a/telephony/java/android/telephony/satellite/SatelliteSubscriberProvisionStatus.java b/telephony/java/android/telephony/satellite/SatelliteSubscriberProvisionStatus.java
new file mode 100644
index 0000000..08ef3f2
--- /dev/null
+++ b/telephony/java/android/telephony/satellite/SatelliteSubscriberProvisionStatus.java
@@ -0,0 +1,174 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.telephony.satellite;
+
+import android.annotation.FlaggedApi;
+import android.annotation.NonNull;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import com.android.internal.telephony.flags.Flags;
+
+import java.util.Objects;
+
+/**
+ * Represents the provisioning state of SatelliteSubscriberInfo.
+ *
+ * @hide
+ */
+@FlaggedApi(Flags.FLAG_CARRIER_ROAMING_NB_IOT_NTN)
+public class SatelliteSubscriberProvisionStatus implements Parcelable {
+    private SatelliteSubscriberInfo mSubscriberInfo;
+    /** {@code true} mean the satellite subscriber is provisioned, {@code false} otherwise. */
+    private boolean mProvisionStatus;
+
+    public SatelliteSubscriberProvisionStatus(@NonNull Builder builder) {
+        mSubscriberInfo = builder.mSubscriberInfo;
+        mProvisionStatus = builder.mProvisionStatus;
+    }
+
+    /**
+     * Builder class for constructing SatelliteSubscriberProvisionStatus objects
+     *
+     * @hide
+     */
+    @FlaggedApi(Flags.FLAG_CARRIER_ROAMING_NB_IOT_NTN)
+    public static class Builder {
+        private SatelliteSubscriberInfo mSubscriberInfo;
+        private boolean mProvisionStatus;
+
+        /**
+         * Set the SatelliteSubscriberInfo and returns the Builder class.
+         * @hide
+         */
+        public Builder setSatelliteSubscriberInfo(SatelliteSubscriberInfo satelliteSubscriberInfo) {
+            mSubscriberInfo = satelliteSubscriberInfo;
+            return this;
+        }
+
+        /**
+         * Set the SatelliteSubscriberInfo's provisionStatus and returns the Builder class.
+         * @hide
+         */
+        @NonNull
+        public Builder setProvisionStatus(boolean provisionStatus) {
+            mProvisionStatus = provisionStatus;
+            return this;
+        }
+
+        /**
+         * Returns SatelliteSubscriberProvisionStatus object.
+         * @hide
+         */
+        @NonNull
+        public SatelliteSubscriberProvisionStatus build() {
+            return new SatelliteSubscriberProvisionStatus(this);
+        }
+    }
+
+    private SatelliteSubscriberProvisionStatus(Parcel in) {
+        readFromParcel(in);
+    }
+
+    /**
+     * @hide
+     */
+    @Override
+    @FlaggedApi(Flags.FLAG_CARRIER_ROAMING_NB_IOT_NTN)
+    public void writeToParcel(@NonNull Parcel out, int flags) {
+        out.writeParcelable(mSubscriberInfo, flags);
+        out.writeBoolean(mProvisionStatus);
+    }
+
+    @FlaggedApi(Flags.FLAG_CARRIER_ROAMING_NB_IOT_NTN)
+    public static final @android.annotation.NonNull Creator<SatelliteSubscriberProvisionStatus>
+            CREATOR =
+            new Creator<SatelliteSubscriberProvisionStatus>() {
+                @Override
+                public SatelliteSubscriberProvisionStatus createFromParcel(Parcel in) {
+                    return new SatelliteSubscriberProvisionStatus(in);
+                }
+
+                @Override
+                public SatelliteSubscriberProvisionStatus[] newArray(int size) {
+                    return new SatelliteSubscriberProvisionStatus[size];
+                }
+            };
+
+    /**
+     * @hide
+     */
+    @Override
+    @FlaggedApi(Flags.FLAG_CARRIER_ROAMING_NB_IOT_NTN)
+    public int describeContents() {
+        return 0;
+    }
+
+    /**
+     * SatelliteSubscriberInfo that has a provisioning state.
+     * @return SatelliteSubscriberInfo.
+     * @hide
+     */
+    @FlaggedApi(Flags.FLAG_CARRIER_ROAMING_NB_IOT_NTN)
+    public @NonNull SatelliteSubscriberInfo getSatelliteSubscriberInfo() {
+        return mSubscriberInfo;
+    }
+
+    /**
+     * SatelliteSubscriberInfo's provisioning state.
+     * @return {@code true} means provisioning. {@code false} means deprovisioning.
+     * @hide
+     */
+    @FlaggedApi(Flags.FLAG_CARRIER_ROAMING_NB_IOT_NTN)
+    public @NonNull  boolean getProvisionStatus() {
+        return mProvisionStatus;
+    }
+
+    @NonNull
+    @Override
+    public String toString() {
+        StringBuilder sb = new StringBuilder();
+
+        sb.append("SatelliteSubscriberInfo:");
+        sb.append(mSubscriberInfo);
+        sb.append(",");
+
+        sb.append("ProvisionStatus:");
+        sb.append(mProvisionStatus);
+        return sb.toString();
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(mSubscriberInfo, mProvisionStatus);
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) return true;
+        if (!(o instanceof SatelliteSubscriberProvisionStatus)) return false;
+        SatelliteSubscriberProvisionStatus that = (SatelliteSubscriberProvisionStatus) o;
+        return Objects.equals(mSubscriberInfo, that.mSubscriberInfo)
+                && mProvisionStatus == that.mProvisionStatus;
+    }
+
+    private void readFromParcel(Parcel in) {
+        mSubscriberInfo = in.readParcelable(SatelliteSubscriberInfo.class.getClassLoader(),
+                SatelliteSubscriberInfo.class);
+        mProvisionStatus = in.readBoolean();
+    }
+}
diff --git a/telephony/java/android/telephony/satellite/SatelliteSubscriptionInfo.java b/telephony/java/android/telephony/satellite/SatelliteSubscriptionInfo.java
new file mode 100644
index 0000000..2ef19f8
--- /dev/null
+++ b/telephony/java/android/telephony/satellite/SatelliteSubscriptionInfo.java
@@ -0,0 +1,106 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.telephony.satellite;
+
+import android.annotation.NonNull;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.util.Objects;
+
+/**
+ * SatelliteSubscriptionInfo is used to pack subscription related info needed by modem to allow
+ * carrier to roam to satellite.
+ *
+ * @hide
+ */
+public final class SatelliteSubscriptionInfo implements Parcelable {
+    /**
+     * The ICC ID used for satellite attachment.
+     */
+    @NonNull private final String mIccId;
+
+    /**
+     * The NIDD(Non IP Data) APN to be used for carrier roaming to satellite attachment.
+     */
+    @NonNull private final String mNiddApn;
+
+    public SatelliteSubscriptionInfo(@NonNull String iccId, @NonNull String niddApn) {
+        mIccId = iccId;
+        mNiddApn = niddApn;
+    }
+
+    private SatelliteSubscriptionInfo(Parcel in) {
+        mIccId = in.readString8();
+        mNiddApn = in.readString8();
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(@NonNull Parcel dest, int flags) {
+        dest.writeString8(mIccId);
+        dest.writeString8(mNiddApn);
+    }
+
+    @NonNull public static final Creator<SatelliteSubscriptionInfo> CREATOR = new Creator<>() {
+        @Override
+        public SatelliteSubscriptionInfo createFromParcel(Parcel in) {
+            return new SatelliteSubscriptionInfo(in);
+        }
+
+        @Override
+        public SatelliteSubscriptionInfo[] newArray(int size) {
+            return new SatelliteSubscriptionInfo[size];
+        }
+    };
+
+    @Override
+    @NonNull public String toString() {
+        return (new StringBuilder()).append("SatelliteSubscriptionInfo{")
+                .append("IccId=").append(mIccId)
+                .append(", NiddApn=").append(mNiddApn)
+                .append("}")
+                .toString();
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) return true;
+        if (o == null || getClass() != o.getClass()) return false;
+        SatelliteSubscriptionInfo that = (SatelliteSubscriptionInfo) o;
+        return mIccId.equals(that.getIccId()) && mNiddApn.equals(that.getNiddApn());
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(getIccId(), getNiddApn());
+    }
+
+    @NonNull
+    public String getIccId() {
+        return mIccId;
+    }
+
+    @NonNull
+    public String getNiddApn() {
+        return mNiddApn;
+    }
+}
diff --git a/telephony/java/android/telephony/satellite/stub/SatelliteModemEnableRequestAttributes.aidl b/telephony/java/android/telephony/satellite/stub/SatelliteModemEnableRequestAttributes.aidl
new file mode 100644
index 0000000..0a40e15
--- /dev/null
+++ b/telephony/java/android/telephony/satellite/stub/SatelliteModemEnableRequestAttributes.aidl
@@ -0,0 +1,41 @@
+/*
+ * 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.telephony.satellite.stub;
+
+import android.telephony.satellite.stub.SatelliteSubscriptionInfo;
+
+/**
+ * {@hide}
+ */
+ parcelable SatelliteModemEnableRequestAttributes {
+    /**
+     * {@code true} to enable satellite and {@code false} to disable.
+     */
+    boolean isEnabled;
+    /**
+     * {@code true} to enable demo mode and {@code false} to disable.
+     */
+    boolean isDemoMode;
+    /**
+     * {@code true} to enable emergency modeand {@code false} to disable.
+     */
+    boolean isEmergencyMode;
+    /**
+     * The subscription related info.
+     */
+    SatelliteSubscriptionInfo satelliteSubscriptionInfo;
+ }
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropConstants.java b/telephony/java/android/telephony/satellite/stub/SatelliteSubscriptionInfo.aidl
similarity index 61%
copy from libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropConstants.java
copy to telephony/java/android/telephony/satellite/stub/SatelliteSubscriptionInfo.aidl
index 20da54e..f664dda 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/draganddrop/DragAndDropConstants.java
+++ b/telephony/java/android/telephony/satellite/stub/SatelliteSubscriptionInfo.aidl
@@ -14,14 +14,18 @@
  * limitations under the License.
  */
 
-package com.android.wm.shell.draganddrop;
+package android.telephony.satellite.stub;
 
-/** Constants that can be used by both Shell and other users of the library, e.g. Launcher */
-public class DragAndDropConstants {
-
+/**
+ * {@hide}
+ */
+ parcelable SatelliteSubscriptionInfo {
     /**
-     * An Intent extra that Launcher can use to specify a region of the screen where Shell should
-     * ignore drag events.
+     * The ICC ID used for satellite attachment.
      */
-    public static final String EXTRA_DISALLOW_HIT_REGION = "DISALLOW_HIT_REGION";
-}
+    String iccId;
+    /**
+     * The NIDD(Non IP Data) APN to be used for carrier roaming to satellite attachment.
+     */
+    String niddApn;
+ }
\ No newline at end of file
diff --git a/telephony/java/com/android/internal/telephony/ITelephony.aidl b/telephony/java/com/android/internal/telephony/ITelephony.aidl
index 8919703..0c5f30f 100644
--- a/telephony/java/com/android/internal/telephony/ITelephony.aidl
+++ b/telephony/java/com/android/internal/telephony/ITelephony.aidl
@@ -3413,19 +3413,7 @@
      */
     @JavaPassthrough(annotation="@android.annotation.RequiresPermission("
             + "android.Manifest.permission.SATELLITE_COMMUNICATION)")
-    void requestProvisionSubscriberIds(in ResultReceiver result);
-
-    /**
-     * Request to get provisioned status for given a satellite subscriber id.
-     *
-     * @param satelliteSubscriberId Satellite subscriber id requiring provisioned status check.
-     * @param result The result receiver, which returns the provisioned status of the token if the
-     * request is successful or an error code if the request failed.
-     * @hide
-     */
-    @JavaPassthrough(annotation="@android.annotation.RequiresPermission("
-            + "android.Manifest.permission.SATELLITE_COMMUNICATION)")
-    void requestIsProvisioned(in String satelliteSubscriberId, in ResultReceiver result);
+    void requestSatelliteSubscriberProvisionStatus(in ResultReceiver result);
 
     /**
      * Deliver the list of provisioned satellite subscriber infos.
diff --git a/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/ShowImeWhileEnteringOverviewTest.kt b/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/ShowImeWhileEnteringOverviewTest.kt
index 638d594..eb63e49 100644
--- a/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/ShowImeWhileEnteringOverviewTest.kt
+++ b/tests/FlickerTests/IME/src/com/android/server/wm/flicker/ime/ShowImeWhileEnteringOverviewTest.kt
@@ -28,6 +28,7 @@
 import com.android.server.wm.flicker.helpers.ImeShownOnAppStartHelper
 import com.android.server.wm.flicker.navBarLayerIsVisibleAtStartAndEnd
 import com.android.server.wm.flicker.statusBarLayerIsVisibleAtStartAndEnd
+import com.android.server.wm.flicker.taskBarLayerIsVisibleAtStartAndEnd
 import org.junit.Assume
 import org.junit.FixMethodOrder
 import org.junit.Ignore
@@ -114,28 +115,28 @@
 
     /**
      * In the legacy transitions, the nav bar is not marked as invisible. In the new transitions
-     * this is fixed and the nav bar shows as invisible
+     * this is fixed and the status bar shows as invisible
      */
     @Presubmit
     @Test
     fun statusBarLayerIsInvisibleInLandscapePhone() {
         Assume.assumeTrue(flicker.scenario.isLandscapeOrSeascapeAtStart)
         Assume.assumeTrue(flicker.scenario.isGesturalNavigation)
-        Assume.assumeFalse(usesTaskbar)
+        Assume.assumeFalse(flicker.scenario.isTablet)
         flicker.assertLayersStart { this.isVisible(ComponentNameMatcher.STATUS_BAR) }
         flicker.assertLayersEnd { this.isInvisible(ComponentNameMatcher.STATUS_BAR) }
     }
 
     /**
      * In the legacy transitions, the nav bar is not marked as invisible. In the new transitions
-     * this is fixed and the nav bar shows as invisible
+     * this is fixed and the status bar shows as invisible
      */
     @Presubmit
     @Test
     fun statusBarLayerIsInvisibleInLandscapeTablet() {
         Assume.assumeTrue(flicker.scenario.isLandscapeOrSeascapeAtStart)
         Assume.assumeTrue(flicker.scenario.isGesturalNavigation)
-        Assume.assumeTrue(usesTaskbar)
+        Assume.assumeTrue(flicker.scenario.isTablet)
         flicker.statusBarLayerIsVisibleAtStartAndEnd()
     }
 
@@ -149,6 +150,10 @@
     @Ignore("Visibility changes depending on orientation and navigation mode")
     override fun navBarLayerPositionAtStartAndEnd() {}
 
+    @Test
+    @Ignore("Visibility changes depending on orientation and navigation mode")
+    override fun taskBarLayerIsVisibleAtStartAndEnd() {}
+
     /** {@inheritDoc} */
     @Test
     @Ignore("Visibility changes depending on orientation and navigation mode")
@@ -161,7 +166,10 @@
 
     @Presubmit
     @Test
-    override fun taskBarLayerIsVisibleAtStartAndEnd() = super.taskBarLayerIsVisibleAtStartAndEnd()
+    fun taskBarLayerIsVisibleAtStartAndEndForTablets() {
+        Assume.assumeTrue(flicker.scenario.isTablet)
+        flicker.taskBarLayerIsVisibleAtStartAndEnd()
+    }
 
     @Presubmit
     @Test
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/BaseTest.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/BaseTest.kt
index 70d762e..851ce02 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/BaseTest.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/BaseTest.kt
@@ -137,8 +137,6 @@
     /**
      * Checks that the [ComponentNameMatcher.TASK_BAR] window is visible at the start and end of the
      * transition
-     *
-     * Note: Large screen only
      */
     @Presubmit
     @Test
@@ -149,8 +147,6 @@
 
     /**
      * Checks that the [ComponentNameMatcher.TASK_BAR] window is visible during the whole transition
-     *
-     * Note: Large screen only
      */
     @Presubmit
     @Test
diff --git a/tests/Input/src/android/hardware/input/KeyGestureEventListenerTest.kt b/tests/Input/src/android/hardware/input/KeyGestureEventListenerTest.kt
new file mode 100644
index 0000000..14aac66
--- /dev/null
+++ b/tests/Input/src/android/hardware/input/KeyGestureEventListenerTest.kt
@@ -0,0 +1,202 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.hardware.input
+
+import android.content.Context
+import android.content.ContextWrapper
+import android.os.Handler
+import android.os.HandlerExecutor
+import android.os.test.TestLooper
+import android.platform.test.annotations.Presubmit
+import android.platform.test.flag.junit.SetFlagsRule
+import android.view.KeyEvent
+import androidx.test.core.app.ApplicationProvider
+import com.android.server.testutils.any
+import org.junit.After
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.Mockito
+import org.mockito.Mockito.doAnswer
+import org.mockito.Mockito.`when`
+import org.mockito.junit.MockitoJUnitRunner
+import kotlin.test.assertEquals
+import kotlin.test.assertNotNull
+import kotlin.test.assertNull
+import kotlin.test.fail
+
+/**
+ * Tests for [InputManager.KeyGestureEventListener].
+ *
+ * Build/Install/Run:
+ * atest InputTests:KeyGestureEventListenerTest
+ */
+@Presubmit
+@RunWith(MockitoJUnitRunner::class)
+class KeyGestureEventListenerTest {
+
+    companion object {
+        const val DEVICE_ID = 1
+        val HOME_GESTURE_EVENT = KeyGestureEvent(
+            DEVICE_ID,
+            intArrayOf(KeyEvent.KEYCODE_H),
+            KeyEvent.META_META_ON or KeyEvent.META_META_LEFT_ON,
+            KeyGestureEvent.KEY_GESTURE_TYPE_HOME
+        )
+    }
+
+    @get:Rule
+    val rule = SetFlagsRule()
+
+    private val testLooper = TestLooper()
+    private val executor = HandlerExecutor(Handler(testLooper.looper))
+    private var registeredListener: IKeyGestureEventListener? = null
+    private lateinit var context: Context
+    private lateinit var inputManager: InputManager
+    private lateinit var inputManagerGlobalSession: InputManagerGlobal.TestSession
+
+    @Mock
+    private lateinit var iInputManagerMock: IInputManager
+
+    @Before
+    fun setUp() {
+        context = Mockito.spy(ContextWrapper(ApplicationProvider.getApplicationContext()))
+        inputManagerGlobalSession = InputManagerGlobal.createTestSession(iInputManagerMock)
+        inputManager = InputManager(context)
+        `when`(context.getSystemService(Mockito.eq(Context.INPUT_SERVICE)))
+                .thenReturn(inputManager)
+
+        // Handle key gesture event listener registration.
+        doAnswer {
+            val listener = it.getArgument(0) as IKeyGestureEventListener
+            if (registeredListener != null &&
+                    registeredListener!!.asBinder() != listener.asBinder()) {
+                // There can only be one registered key gesture event listener per process.
+                fail("Trying to register a new listener when one already exists")
+            }
+            registeredListener = listener
+            null
+        }.`when`(iInputManagerMock).registerKeyGestureEventListener(any())
+
+        // Handle key gesture event listener being unregistered.
+        doAnswer {
+            val listener = it.getArgument(0) as IKeyGestureEventListener
+            if (registeredListener == null ||
+                    registeredListener!!.asBinder() != listener.asBinder()) {
+                fail("Trying to unregister a listener that is not registered")
+            }
+            registeredListener = null
+            null
+        }.`when`(iInputManagerMock).unregisterKeyGestureEventListener(any())
+    }
+
+    @After
+    fun tearDown() {
+        if (this::inputManagerGlobalSession.isInitialized) {
+            inputManagerGlobalSession.close()
+        }
+    }
+
+    private fun notifyKeyGestureEvent(event: KeyGestureEvent) {
+        registeredListener!!.onKeyGestureEvent(
+            event.deviceId,
+            event.keycodes,
+            event.modifierState,
+            event.keyGestureType
+        )
+    }
+
+    @Test
+    fun testListenerHasCorrectGestureNotified() {
+        var callbackCount = 0
+
+        // Add a key gesture event listener
+        inputManager.registerKeyGestureEventListener(executor) {
+            event: KeyGestureEvent ->
+            assertEquals(HOME_GESTURE_EVENT, event)
+            callbackCount++
+        }
+
+        // Notifying key gesture event will notify the listener.
+        notifyKeyGestureEvent(HOME_GESTURE_EVENT)
+        testLooper.dispatchNext()
+        assertEquals(1, callbackCount)
+    }
+
+    @Test
+    fun testAddingListenersRegistersInternalCallbackListener() {
+        // Set up two callbacks.
+        val callback1 = InputManager.KeyGestureEventListener { _ -> }
+        val callback2 = InputManager.KeyGestureEventListener { _ -> }
+
+        assertNull(registeredListener)
+
+        // Adding the listener should register the callback with InputManagerService.
+        inputManager.registerKeyGestureEventListener(executor, callback1)
+        assertNotNull(registeredListener)
+
+        // Adding another listener should not register new internal listener.
+        val currListener = registeredListener
+        inputManager.registerKeyGestureEventListener(executor, callback2)
+        assertEquals(currListener, registeredListener)
+    }
+
+    @Test
+    fun testRemovingListenersUnregistersInternalCallbackListener() {
+        // Set up two callbacks.
+        val callback1 = InputManager.KeyGestureEventListener { _ -> }
+        val callback2 = InputManager.KeyGestureEventListener { _ -> }
+
+        inputManager.registerKeyGestureEventListener(executor, callback1)
+        inputManager.registerKeyGestureEventListener(executor, callback2)
+
+        // Only removing all listeners should remove the internal callback
+        inputManager.unregisterKeyGestureEventListener(callback1)
+        assertNotNull(registeredListener)
+        inputManager.unregisterKeyGestureEventListener(callback2)
+        assertNull(registeredListener)
+    }
+
+    @Test
+    fun testMultipleListeners() {
+        // Set up two callbacks.
+        var callbackCount1 = 0
+        var callbackCount2 = 0
+        val callback1 = InputManager.KeyGestureEventListener { _ -> callbackCount1++ }
+        val callback2 = InputManager.KeyGestureEventListener { _ -> callbackCount2++ }
+
+        // Add both key gesture event listeners
+        inputManager.registerKeyGestureEventListener(executor, callback1)
+        inputManager.registerKeyGestureEventListener(executor, callback2)
+
+        // Notifying key gesture event, should notify both the callbacks.
+        notifyKeyGestureEvent(HOME_GESTURE_EVENT)
+        testLooper.dispatchAll()
+        assertEquals(1, callbackCount1)
+        assertEquals(1, callbackCount2)
+
+        inputManager.unregisterKeyGestureEventListener(callback2)
+        // Notifying key gesture event, should still trigger callback1 but not
+        // callback2.
+        notifyKeyGestureEvent(HOME_GESTURE_EVENT)
+        testLooper.dispatchAll()
+        assertEquals(2, callbackCount1)
+        assertEquals(1, callbackCount2)
+    }
+}
diff --git a/tests/Input/src/android/hardware/input/KeyboardSystemShortcutListenerTest.kt b/tests/Input/src/android/hardware/input/KeyboardSystemShortcutListenerTest.kt
deleted file mode 100644
index 24d7291..0000000
--- a/tests/Input/src/android/hardware/input/KeyboardSystemShortcutListenerTest.kt
+++ /dev/null
@@ -1,202 +0,0 @@
-/*
- * Copyright 2024 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.hardware.input
-
-import android.content.Context
-import android.content.ContextWrapper
-import android.os.Handler
-import android.os.HandlerExecutor
-import android.os.test.TestLooper
-import android.platform.test.annotations.Presubmit
-import android.platform.test.flag.junit.SetFlagsRule
-import android.view.KeyEvent
-import androidx.test.core.app.ApplicationProvider
-import com.android.server.testutils.any
-import org.junit.After
-import org.junit.Before
-import org.junit.Rule
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.mockito.Mock
-import org.mockito.Mockito
-import org.mockito.Mockito.doAnswer
-import org.mockito.Mockito.`when`
-import org.mockito.junit.MockitoJUnitRunner
-import kotlin.test.assertEquals
-import kotlin.test.assertNotNull
-import kotlin.test.assertNull
-import kotlin.test.fail
-
-/**
- * Tests for [InputManager.KeyboardSystemShortcutListener].
- *
- * Build/Install/Run:
- * atest InputTests:KeyboardSystemShortcutListenerTest
- */
-@Presubmit
-@RunWith(MockitoJUnitRunner::class)
-class KeyboardSystemShortcutListenerTest {
-
-    companion object {
-        const val DEVICE_ID = 1
-        val HOME_SHORTCUT = KeyboardSystemShortcut(
-            intArrayOf(KeyEvent.KEYCODE_H),
-            KeyEvent.META_META_ON or KeyEvent.META_META_LEFT_ON,
-            KeyboardSystemShortcut.SYSTEM_SHORTCUT_HOME
-        )
-    }
-
-    @get:Rule
-    val rule = SetFlagsRule()
-
-    private val testLooper = TestLooper()
-    private val executor = HandlerExecutor(Handler(testLooper.looper))
-    private var registeredListener: IKeyboardSystemShortcutListener? = null
-    private lateinit var context: Context
-    private lateinit var inputManager: InputManager
-    private lateinit var inputManagerGlobalSession: InputManagerGlobal.TestSession
-
-    @Mock
-    private lateinit var iInputManagerMock: IInputManager
-
-    @Before
-    fun setUp() {
-        context = Mockito.spy(ContextWrapper(ApplicationProvider.getApplicationContext()))
-        inputManagerGlobalSession = InputManagerGlobal.createTestSession(iInputManagerMock)
-        inputManager = InputManager(context)
-        `when`(context.getSystemService(Mockito.eq(Context.INPUT_SERVICE)))
-                .thenReturn(inputManager)
-
-        // Handle keyboard system shortcut listener registration.
-        doAnswer {
-            val listener = it.getArgument(0) as IKeyboardSystemShortcutListener
-            if (registeredListener != null &&
-                    registeredListener!!.asBinder() != listener.asBinder()) {
-                // There can only be one registered keyboard system shortcut listener per process.
-                fail("Trying to register a new listener when one already exists")
-            }
-            registeredListener = listener
-            null
-        }.`when`(iInputManagerMock).registerKeyboardSystemShortcutListener(any())
-
-        // Handle keyboard system shortcut listener being unregistered.
-        doAnswer {
-            val listener = it.getArgument(0) as IKeyboardSystemShortcutListener
-            if (registeredListener == null ||
-                    registeredListener!!.asBinder() != listener.asBinder()) {
-                fail("Trying to unregister a listener that is not registered")
-            }
-            registeredListener = null
-            null
-        }.`when`(iInputManagerMock).unregisterKeyboardSystemShortcutListener(any())
-    }
-
-    @After
-    fun tearDown() {
-        if (this::inputManagerGlobalSession.isInitialized) {
-            inputManagerGlobalSession.close()
-        }
-    }
-
-    private fun notifyKeyboardSystemShortcutTriggered(id: Int, shortcut: KeyboardSystemShortcut) {
-        registeredListener!!.onKeyboardSystemShortcutTriggered(
-            id,
-            shortcut.keycodes,
-            shortcut.modifierState,
-            shortcut.systemShortcut
-        )
-    }
-
-    @Test
-    fun testListenerHasCorrectSystemShortcutNotified() {
-        var callbackCount = 0
-
-        // Add a keyboard system shortcut listener
-        inputManager.registerKeyboardSystemShortcutListener(executor) {
-            deviceId: Int, systemShortcut: KeyboardSystemShortcut ->
-            assertEquals(DEVICE_ID, deviceId)
-            assertEquals(HOME_SHORTCUT, systemShortcut)
-            callbackCount++
-        }
-
-        // Notifying keyboard system shortcut triggered will notify the listener.
-        notifyKeyboardSystemShortcutTriggered(DEVICE_ID, HOME_SHORTCUT)
-        testLooper.dispatchNext()
-        assertEquals(1, callbackCount)
-    }
-
-    @Test
-    fun testAddingListenersRegistersInternalCallbackListener() {
-        // Set up two callbacks.
-        val callback1 = InputManager.KeyboardSystemShortcutListener {_, _ -> }
-        val callback2 = InputManager.KeyboardSystemShortcutListener {_, _ -> }
-
-        assertNull(registeredListener)
-
-        // Adding the listener should register the callback with InputManagerService.
-        inputManager.registerKeyboardSystemShortcutListener(executor, callback1)
-        assertNotNull(registeredListener)
-
-        // Adding another listener should not register new internal listener.
-        val currListener = registeredListener
-        inputManager.registerKeyboardSystemShortcutListener(executor, callback2)
-        assertEquals(currListener, registeredListener)
-    }
-
-    @Test
-    fun testRemovingListenersUnregistersInternalCallbackListener() {
-        // Set up two callbacks.
-        val callback1 = InputManager.KeyboardSystemShortcutListener {_, _ -> }
-        val callback2 = InputManager.KeyboardSystemShortcutListener {_, _ -> }
-
-        inputManager.registerKeyboardSystemShortcutListener(executor, callback1)
-        inputManager.registerKeyboardSystemShortcutListener(executor, callback2)
-
-        // Only removing all listeners should remove the internal callback
-        inputManager.unregisterKeyboardSystemShortcutListener(callback1)
-        assertNotNull(registeredListener)
-        inputManager.unregisterKeyboardSystemShortcutListener(callback2)
-        assertNull(registeredListener)
-    }
-
-    @Test
-    fun testMultipleListeners() {
-        // Set up two callbacks.
-        var callbackCount1 = 0
-        var callbackCount2 = 0
-        val callback1 = InputManager.KeyboardSystemShortcutListener { _, _ -> callbackCount1++ }
-        val callback2 = InputManager.KeyboardSystemShortcutListener { _, _ -> callbackCount2++ }
-
-        // Add both keyboard system shortcut listeners
-        inputManager.registerKeyboardSystemShortcutListener(executor, callback1)
-        inputManager.registerKeyboardSystemShortcutListener(executor, callback2)
-
-        // Notifying keyboard system shortcut triggered, should notify both the callbacks.
-        notifyKeyboardSystemShortcutTriggered(DEVICE_ID, HOME_SHORTCUT)
-        testLooper.dispatchAll()
-        assertEquals(1, callbackCount1)
-        assertEquals(1, callbackCount2)
-
-        inputManager.unregisterKeyboardSystemShortcutListener(callback2)
-        // Notifying keyboard system shortcut triggered, should still trigger callback1 but not
-        // callback2.
-        notifyKeyboardSystemShortcutTriggered(DEVICE_ID, HOME_SHORTCUT)
-        testLooper.dispatchAll()
-        assertEquals(2, callbackCount1)
-        assertEquals(1, callbackCount2)
-    }
-}
diff --git a/tests/Input/src/com/android/server/input/InputManagerServiceTests.kt b/tests/Input/src/com/android/server/input/InputManagerServiceTests.kt
index 3c72498..8829f74 100644
--- a/tests/Input/src/com/android/server/input/InputManagerServiceTests.kt
+++ b/tests/Input/src/com/android/server/input/InputManagerServiceTests.kt
@@ -151,6 +151,7 @@
         verify(native).setTouchpadNaturalScrollingEnabled(anyBoolean())
         verify(native).setTouchpadTapToClickEnabled(anyBoolean())
         verify(native).setTouchpadTapDraggingEnabled(anyBoolean())
+        verify(native).setShouldNotifyTouchpadHardwareState(anyBoolean())
         verify(native).setTouchpadRightClickZoneEnabled(anyBoolean())
         verify(native).setShowTouches(anyBoolean())
         verify(native).setMotionClassifierEnabled(anyBoolean())
diff --git a/tests/Input/src/com/android/server/input/KeyGestureControllerTests.kt b/tests/Input/src/com/android/server/input/KeyGestureControllerTests.kt
new file mode 100644
index 0000000..3f611e0
--- /dev/null
+++ b/tests/Input/src/com/android/server/input/KeyGestureControllerTests.kt
@@ -0,0 +1,96 @@
+/*
+ * Copyright 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.input
+
+import android.content.Context
+import android.content.ContextWrapper
+import android.hardware.input.IKeyGestureEventListener
+import android.hardware.input.KeyGestureEvent
+import android.platform.test.annotations.Presubmit
+import android.view.KeyEvent
+import androidx.test.core.app.ApplicationProvider
+import org.junit.Assert.assertEquals
+import org.junit.Assert.assertNull
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.mockito.Mockito
+import org.mockito.junit.MockitoJUnit
+
+/**
+ * Tests for {@link KeyGestureController}.
+ *
+ * Build/Install/Run:
+ * atest InputTests:KeyGestureControllerTests
+ */
+@Presubmit
+class KeyGestureControllerTests {
+
+    companion object {
+        val DEVICE_ID = 1
+        val HOME_GESTURE_EVENT = KeyGestureEvent(
+            DEVICE_ID,
+            intArrayOf(KeyEvent.KEYCODE_H),
+            KeyEvent.META_META_ON or KeyEvent.META_META_LEFT_ON,
+            KeyGestureEvent.KEY_GESTURE_TYPE_HOME
+        )
+    }
+
+    @get:Rule
+    val rule = MockitoJUnit.rule()!!
+
+    private lateinit var keyGestureController: KeyGestureController
+    private lateinit var context: Context
+    private var lastEvent: KeyGestureEvent? = null
+
+    @Before
+    fun setup() {
+        context = Mockito.spy(ContextWrapper(ApplicationProvider.getApplicationContext()))
+        keyGestureController = KeyGestureController()
+    }
+
+    @Test
+    fun testKeyGestureEvent_registerUnregisterListener() {
+        val listener = KeyGestureEventListener()
+
+        // Register key gesture event listener
+        keyGestureController.registerKeyGestureEventListener(listener, 0)
+        keyGestureController.onKeyGestureEvent(HOME_GESTURE_EVENT)
+        assertEquals(
+            "Listener should get callback on key gesture event",
+            HOME_GESTURE_EVENT,
+            lastEvent!!
+        )
+
+        // Unregister listener
+        lastEvent = null
+        keyGestureController.unregisterKeyGestureEventListener(listener, 0)
+        keyGestureController.onKeyGestureEvent(HOME_GESTURE_EVENT)
+        assertNull("Listener should not get callback after being unregistered", lastEvent)
+    }
+
+    inner class KeyGestureEventListener : IKeyGestureEventListener.Stub() {
+        override fun onKeyGestureEvent(
+                deviceId: Int,
+                keycodes: IntArray,
+                modifierState: Int,
+                gestureType: Int
+        ) {
+            lastEvent = KeyGestureEvent(deviceId, keycodes, modifierState, gestureType)
+        }
+    }
+}
\ No newline at end of file
diff --git a/tests/Input/src/com/android/server/input/KeyboardShortcutCallbackHandlerTests.kt b/tests/Input/src/com/android/server/input/KeyboardShortcutCallbackHandlerTests.kt
deleted file mode 100644
index 5a40a1c..0000000
--- a/tests/Input/src/com/android/server/input/KeyboardShortcutCallbackHandlerTests.kt
+++ /dev/null
@@ -1,96 +0,0 @@
-/*
- * Copyright 2024 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.server.input
-
-import android.content.Context
-import android.content.ContextWrapper
-import android.hardware.input.IKeyboardSystemShortcutListener
-import android.hardware.input.KeyboardSystemShortcut
-import android.platform.test.annotations.Presubmit
-import android.view.KeyEvent
-import androidx.test.core.app.ApplicationProvider
-import org.junit.Assert.assertEquals
-import org.junit.Assert.assertNull
-import org.junit.Before
-import org.junit.Rule
-import org.junit.Test
-import org.mockito.Mockito
-import org.mockito.junit.MockitoJUnit
-
-/**
- * Tests for {@link KeyboardShortcutCallbackHandler}.
- *
- * Build/Install/Run:
- * atest InputTests:KeyboardShortcutCallbackHandlerTests
- */
-@Presubmit
-class KeyboardShortcutCallbackHandlerTests {
-
-    companion object {
-        val DEVICE_ID = 1
-        val HOME_SHORTCUT = KeyboardSystemShortcut(
-            intArrayOf(KeyEvent.KEYCODE_H),
-            KeyEvent.META_META_ON or KeyEvent.META_META_LEFT_ON,
-            KeyboardSystemShortcut.SYSTEM_SHORTCUT_HOME
-        )
-    }
-
-    @get:Rule
-    val rule = MockitoJUnit.rule()!!
-
-    private lateinit var keyboardShortcutCallbackHandler: KeyboardShortcutCallbackHandler
-    private lateinit var context: Context
-    private var lastShortcut: KeyboardSystemShortcut? = null
-
-    @Before
-    fun setup() {
-        context = Mockito.spy(ContextWrapper(ApplicationProvider.getApplicationContext()))
-        keyboardShortcutCallbackHandler = KeyboardShortcutCallbackHandler()
-    }
-
-    @Test
-    fun testKeyboardSystemShortcutTriggered_registerUnregisterListener() {
-        val listener = KeyboardSystemShortcutListener()
-
-        // Register keyboard system shortcut listener
-        keyboardShortcutCallbackHandler.registerKeyboardSystemShortcutListener(listener, 0)
-        keyboardShortcutCallbackHandler.onKeyboardSystemShortcutTriggered(DEVICE_ID, HOME_SHORTCUT)
-        assertEquals(
-            "Listener should get callback on keyboard system shortcut triggered",
-            HOME_SHORTCUT,
-            lastShortcut!!
-        )
-
-        // Unregister listener
-        lastShortcut = null
-        keyboardShortcutCallbackHandler.unregisterKeyboardSystemShortcutListener(listener, 0)
-        keyboardShortcutCallbackHandler.onKeyboardSystemShortcutTriggered(DEVICE_ID, HOME_SHORTCUT)
-        assertNull("Listener should not get callback after being unregistered", lastShortcut)
-    }
-
-    inner class KeyboardSystemShortcutListener : IKeyboardSystemShortcutListener.Stub() {
-        override fun onKeyboardSystemShortcutTriggered(
-                deviceId: Int,
-                keycodes: IntArray,
-                modifierState: Int,
-                shortcut: Int
-        ) {
-            assertEquals(DEVICE_ID, deviceId)
-            lastShortcut = KeyboardSystemShortcut(keycodes, modifierState, shortcut)
-        }
-    }
-}
\ No newline at end of file
diff --git a/tests/Internal/AndroidTest.xml b/tests/Internal/AndroidTest.xml
index 7b67e9e..2d6c650e 100644
--- a/tests/Internal/AndroidTest.xml
+++ b/tests/Internal/AndroidTest.xml
@@ -26,4 +26,12 @@
         <option name="package" value="com.android.internal.tests" />
         <option name="runner" value="androidx.test.runner.AndroidJUnitRunner" />
     </test>
+
+    <metrics_collector class="com.android.tradefed.device.metric.FilePullerLogCollector">
+        <option name="pull-pattern-keys" value="perfetto_file_path"/>
+        <option name="directory-keys"
+            value="/data/user/0/com.android.internal.tests/files"/>
+        <option name="collect-on-run-ended-only" value="true"/>
+        <option name="clean-up" value="true"/>
+    </metrics_collector>
 </configuration>
\ No newline at end of file
diff --git a/tests/Internal/src/com/android/internal/protolog/LegacyProtoLogImplTest.java b/tests/Internal/src/com/android/internal/protolog/LegacyProtoLogImplTest.java
index 5a48327..9657225 100644
--- a/tests/Internal/src/com/android/internal/protolog/LegacyProtoLogImplTest.java
+++ b/tests/Internal/src/com/android/internal/protolog/LegacyProtoLogImplTest.java
@@ -214,6 +214,13 @@
         verify(mReader, never()).getViewerString(anyLong());
     }
 
+    @Test
+    public void loadViewerConfigOnLogcatGroupRegistration() {
+        TestProtoLogGroup.TEST_GROUP.setLogToLogcat(true);
+        mProtoLog.registerGroups(TestProtoLogGroup.TEST_GROUP);
+        verify(mReader).loadViewerConfig(any(), any());
+    }
+
     private static class ProtoLogData {
         Long mMessageHash = null;
         Long mElapsedTime = null;
diff --git a/tests/Internal/src/com/android/internal/protolog/PerfettoProtoLogImplTest.java b/tests/Internal/src/com/android/internal/protolog/PerfettoProtoLogImplTest.java
index 7d0c596..4826f42 100644
--- a/tests/Internal/src/com/android/internal/protolog/PerfettoProtoLogImplTest.java
+++ b/tests/Internal/src/com/android/internal/protolog/PerfettoProtoLogImplTest.java
@@ -29,7 +29,6 @@
 import static org.mockito.Mockito.when;
 
 import static java.io.File.createTempFile;
-import static java.nio.file.Files.createTempDirectory;
 
 import android.content.Context;
 import android.os.SystemClock;
@@ -44,7 +43,7 @@
 import android.tracing.perfetto.DataSource;
 import android.util.proto.ProtoInputStream;
 
-import androidx.test.filters.SmallTest;
+import androidx.test.platform.app.InstrumentationRegistry;
 
 import com.android.internal.protolog.common.IProtoLogGroup;
 import com.android.internal.protolog.common.LogDataType;
@@ -67,18 +66,17 @@
 import java.io.IOException;
 import java.util.List;
 import java.util.Random;
-import java.util.TreeMap;
 import java.util.concurrent.atomic.AtomicInteger;
 
 /**
  * Test class for {@link ProtoLogImpl}.
  */
 @SuppressWarnings("ConstantConditions")
-@SmallTest
 @Presubmit
 @RunWith(JUnit4.class)
 public class PerfettoProtoLogImplTest {
-    private final File mTracingDirectory = createTempDirectory("temp").toFile();
+    private final File mTracingDirectory = InstrumentationRegistry.getInstrumentation()
+            .getTargetContext().getFilesDir();
 
     private final ResultWriter mWriter = new ResultWriter()
             .forScenario(new ScenarioBuilder()
@@ -166,8 +164,7 @@
         mReader = Mockito.spy(new ProtoLogViewerConfigReader(viewerConfigInputStreamProvider));
         mProtoLog = new PerfettoProtoLogImpl(
                 viewerConfigInputStreamProvider, mReader,
-                () -> mCacheUpdater.run());
-        mProtoLog.registerGroups(TestProtoLogGroup.values());
+                () -> mCacheUpdater.run(), TestProtoLogGroup.values());
     }
 
     @After
@@ -384,7 +381,7 @@
                 new Object[]{5});
 
         verify(implSpy).passToLogcat(eq(TestProtoLogGroup.TEST_GROUP.getTag()), eq(
-                LogLevel.INFO), eq("UNKNOWN MESSAGE#1234 (5)"));
+                LogLevel.INFO), eq("UNKNOWN MESSAGE args = (5)"));
         verify(mReader).getViewerString(eq(1234L));
     }
 
@@ -451,8 +448,8 @@
             before = SystemClock.elapsedRealtimeNanos();
             mProtoLog.log(
                     LogLevel.INFO, TestProtoLogGroup.TEST_GROUP,
-                    "My test message :: %s, %d, %o, %x, %f, %b",
-                    "test", 1, 2, 3, 0.4, true);
+                    "My test message :: %s, %d, %x, %f, %b",
+                    "test", 1, 3, 0.4, true);
             after = SystemClock.elapsedRealtimeNanos();
         } finally {
             traceMonitor.stop(mWriter);
@@ -467,7 +464,7 @@
         Truth.assertThat(protolog.messages.getFirst().getTimestamp().getElapsedNanos())
                 .isAtMost(after);
         Truth.assertThat(protolog.messages.getFirst().getMessage())
-                .isEqualTo("My test message :: test, 2, 4, 6, 0.400000, true");
+                .isEqualTo("My test message :: test, 2, 6, 0.400000, true");
     }
 
     @Test
diff --git a/tests/PackageWatchdog/src/com/android/server/PackageWatchdogTest.java b/tests/PackageWatchdog/src/com/android/server/PackageWatchdogTest.java
index ab406ef..5b17825 100644
--- a/tests/PackageWatchdog/src/com/android/server/PackageWatchdogTest.java
+++ b/tests/PackageWatchdog/src/com/android/server/PackageWatchdogTest.java
@@ -1867,7 +1867,7 @@
             return true;
         }
 
-        public String getName() {
+        public String getUniqueIdentifier() {
             return mName;
         }
 
diff --git a/tests/SurfaceControlViewHostTest/src/com/android/test/viewembed/SurfaceControlViewHostSyncTest.java b/tests/SurfaceControlViewHostTest/src/com/android/test/viewembed/SurfaceControlViewHostSyncTest.java
index 359eb35..5012c23 100644
--- a/tests/SurfaceControlViewHostTest/src/com/android/test/viewembed/SurfaceControlViewHostSyncTest.java
+++ b/tests/SurfaceControlViewHostTest/src/com/android/test/viewembed/SurfaceControlViewHostSyncTest.java
@@ -84,6 +84,7 @@
         content.addView(enableSyncButton,
                 new FrameLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT,
                         ViewGroup.LayoutParams.WRAP_CONTENT, Gravity.RIGHT | Gravity.BOTTOM));
+        content.setFitsSystemWindows(true);
         setContentView(content);
 
         mSv.setZOrderOnTop(false);
diff --git a/tests/SurfaceControlViewHostTest/src/com/android/test/viewembed/SurfaceControlViewHostTest.java b/tests/SurfaceControlViewHostTest/src/com/android/test/viewembed/SurfaceControlViewHostTest.java
index 73e0163..4119ea2 100644
--- a/tests/SurfaceControlViewHostTest/src/com/android/test/viewembed/SurfaceControlViewHostTest.java
+++ b/tests/SurfaceControlViewHostTest/src/com/android/test/viewembed/SurfaceControlViewHostTest.java
@@ -37,6 +37,7 @@
 
     protected void onCreate(Bundle savedInstanceState) {
         FrameLayout content = new FrameLayout(this);
+        content.setFitsSystemWindows(true);
         super.onCreate(savedInstanceState);
         mView = new SurfaceView(this);
         content.addView(mView, new FrameLayout.LayoutParams(
diff --git a/tests/SurfaceControlViewHostTest/src/com/android/test/viewembed/SurfaceInputTestActivity.java b/tests/SurfaceControlViewHostTest/src/com/android/test/viewembed/SurfaceInputTestActivity.java
index ac7dc9e..5287068 100644
--- a/tests/SurfaceControlViewHostTest/src/com/android/test/viewembed/SurfaceInputTestActivity.java
+++ b/tests/SurfaceControlViewHostTest/src/com/android/test/viewembed/SurfaceInputTestActivity.java
@@ -88,6 +88,7 @@
     protected void onCreate(@Nullable Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
         LinearLayout content = new LinearLayout(this);
+        content.setFitsSystemWindows(true);
         mLocalSurfaceView = new SurfaceView(this);
         content.addView(mLocalSurfaceView, new LinearLayout.LayoutParams(
                 500, 500, Gravity.CENTER_HORIZONTAL | Gravity.TOP));
diff --git a/tools/aapt2/ResourceParser.cpp b/tools/aapt2/ResourceParser.cpp
index 1c85e9f..a5aecc8 100644
--- a/tools/aapt2/ResourceParser.cpp
+++ b/tools/aapt2/ResourceParser.cpp
@@ -151,6 +151,7 @@
   }
 
   if (res->value != nullptr) {
+    res->value->SetFlagStatus(res->flag_status);
     // Attach the comment, source and config to the value.
     res->value->SetComment(std::move(res->comment));
     res->value->SetSource(std::move(res->source));
@@ -546,30 +547,11 @@
   });
 
   std::string resource_type = parser->element_name();
-  std::optional<StringPiece> flag =
-      xml::FindAttribute(parser, "http://schemas.android.com/apk/res/android", "featureFlag");
-  out_resource->flag_status = FlagStatus::NoFlag;
-  if (flag) {
-    auto flag_it = options_.feature_flag_values.find(flag.value());
-    if (flag_it == options_.feature_flag_values.end()) {
-      diag_->Error(android::DiagMessage(source_.WithLine(parser->line_number()))
-                   << "Resource flag value undefined");
-      return false;
-    }
-    const auto& flag_properties = flag_it->second;
-    if (!flag_properties.read_only) {
-      diag_->Error(android::DiagMessage(source_.WithLine(parser->line_number()))
-                   << "Only read only flags may be used with resources");
-      return false;
-    }
-    if (!flag_properties.enabled.has_value()) {
-      diag_->Error(android::DiagMessage(source_.WithLine(parser->line_number()))
-                   << "Only flags with a value may be used with resources");
-      return false;
-    }
-    out_resource->flag_status =
-        flag_properties.enabled.value() ? FlagStatus::Enabled : FlagStatus::Disabled;
+  auto flag_status = GetFlagStatus(parser);
+  if (!flag_status) {
+    return false;
   }
+  out_resource->flag_status = flag_status.value();
 
   // The value format accepted for this resource.
   uint32_t resource_format = 0u;
@@ -751,6 +733,33 @@
   return false;
 }
 
+std::optional<FlagStatus> ResourceParser::GetFlagStatus(xml::XmlPullParser* parser) {
+  auto flag_status = FlagStatus::NoFlag;
+
+  std::optional<StringPiece> flag = xml::FindAttribute(parser, xml::kSchemaAndroid, "featureFlag");
+  if (flag) {
+    auto flag_it = options_.feature_flag_values.find(flag.value());
+    if (flag_it == options_.feature_flag_values.end()) {
+      diag_->Error(android::DiagMessage(source_.WithLine(parser->line_number()))
+                   << "Resource flag value undefined");
+      return {};
+    }
+    const auto& flag_properties = flag_it->second;
+    if (!flag_properties.read_only) {
+      diag_->Error(android::DiagMessage(source_.WithLine(parser->line_number()))
+                   << "Only read only flags may be used with resources");
+      return {};
+    }
+    if (!flag_properties.enabled.has_value()) {
+      diag_->Error(android::DiagMessage(source_.WithLine(parser->line_number()))
+                   << "Only flags with a value may be used with resources");
+      return {};
+    }
+    flag_status = flag_properties.enabled.value() ? FlagStatus::Enabled : FlagStatus::Disabled;
+  }
+  return flag_status;
+}
+
 bool ResourceParser::ParseItem(xml::XmlPullParser* parser,
                                ParsedResource* out_resource,
                                const uint32_t format) {
@@ -1657,12 +1666,18 @@
     const std::string& element_namespace = parser->element_namespace();
     const std::string& element_name = parser->element_name();
     if (element_namespace.empty() && element_name == "item") {
+      auto flag_status = GetFlagStatus(parser);
+      if (!flag_status) {
+        error = true;
+        continue;
+      }
       std::unique_ptr<Item> item = ParseXml(parser, typeMask, kNoRawString);
       if (!item) {
         diag_->Error(android::DiagMessage(item_source) << "could not parse array item");
         error = true;
         continue;
       }
+      item->SetFlagStatus(flag_status.value());
       item->SetSource(item_source);
       array->elements.emplace_back(std::move(item));
 
diff --git a/tools/aapt2/ResourceParser.h b/tools/aapt2/ResourceParser.h
index 45d41c1..442dea8 100644
--- a/tools/aapt2/ResourceParser.h
+++ b/tools/aapt2/ResourceParser.h
@@ -85,6 +85,8 @@
  private:
   DISALLOW_COPY_AND_ASSIGN(ResourceParser);
 
+  std::optional<FlagStatus> GetFlagStatus(xml::XmlPullParser* parser);
+
   std::optional<FlattenedXmlSubTree> CreateFlattenSubTree(xml::XmlPullParser* parser);
 
   // Parses the XML subtree as a StyleString (flattened XML representation for strings with
diff --git a/tools/aapt2/ResourceTable.cpp b/tools/aapt2/ResourceTable.cpp
index 1cdb715..7a4f40e 100644
--- a/tools/aapt2/ResourceTable.cpp
+++ b/tools/aapt2/ResourceTable.cpp
@@ -605,12 +605,12 @@
     if (!config_value->value) {
       // Resource does not exist, add it now.
       config_value->value = std::move(res.value);
-      config_value->flag_status = res.flag_status;
     } else {
       // When validation is enabled, ensure that a resource cannot have multiple values defined for
       // the same configuration unless protected by flags.
-      auto result = validate ? ResolveFlagCollision(config_value->flag_status, res.flag_status)
-                             : CollisionResult::kKeepBoth;
+      auto result =
+          validate ? ResolveFlagCollision(config_value->value->GetFlagStatus(), res.flag_status)
+                   : CollisionResult::kKeepBoth;
       if (result == CollisionResult::kConflict) {
         result = ResolveValueCollision(config_value->value.get(), res.value.get());
       }
@@ -619,7 +619,6 @@
           // Insert the value ignoring for duplicate configurations
           entry->values.push_back(util::make_unique<ResourceConfigValue>(res.config, res.product));
           entry->values.back()->value = std::move(res.value);
-          entry->values.back()->flag_status = res.flag_status;
           break;
 
         case CollisionResult::kTakeNew:
diff --git a/tools/aapt2/ResourceTable.h b/tools/aapt2/ResourceTable.h
index 4f76e7d..cba6b70 100644
--- a/tools/aapt2/ResourceTable.h
+++ b/tools/aapt2/ResourceTable.h
@@ -104,8 +104,6 @@
   // The actual Value.
   std::unique_ptr<Value> value;
 
-  FlagStatus flag_status = FlagStatus::NoFlag;
-
   ResourceConfigValue(const android::ConfigDescription& config, android::StringPiece product)
       : config(config), product(product) {
   }
diff --git a/tools/aapt2/ResourceValues.cpp b/tools/aapt2/ResourceValues.cpp
index 166b01b..b75e87c 100644
--- a/tools/aapt2/ResourceValues.cpp
+++ b/tools/aapt2/ResourceValues.cpp
@@ -971,6 +971,16 @@
   *out << "(array) [" << util::Joiner(elements, ", ") << "]";
 }
 
+void Array::RemoveFlagDisabledElements() {
+  const auto end_iter = elements.end();
+  const auto remove_iter = std::stable_partition(
+      elements.begin(), end_iter, [](const std::unique_ptr<Item>& item) -> bool {
+        return item->GetFlagStatus() != FlagStatus::Disabled;
+      });
+
+  elements.erase(remove_iter, end_iter);
+}
+
 bool Plural::Equals(const Value* value) const {
   const Plural* other = ValueCast<Plural>(value);
   if (!other) {
@@ -1092,6 +1102,7 @@
 std::unique_ptr<T> CopyValueFields(std::unique_ptr<T> new_value, const T* value) {
   new_value->SetSource(value->GetSource());
   new_value->SetComment(value->GetComment());
+  new_value->SetFlagStatus(value->GetFlagStatus());
   return new_value;
 }
 
diff --git a/tools/aapt2/ResourceValues.h b/tools/aapt2/ResourceValues.h
index 5192c2b..a1b1839 100644
--- a/tools/aapt2/ResourceValues.h
+++ b/tools/aapt2/ResourceValues.h
@@ -65,6 +65,14 @@
     return translatable_;
   }
 
+  void SetFlagStatus(FlagStatus val) {
+    flag_status_ = val;
+  }
+
+  FlagStatus GetFlagStatus() const {
+    return flag_status_;
+  }
+
   // Returns the source where this value was defined.
   const android::Source& GetSource() const {
     return source_;
@@ -109,6 +117,10 @@
   // of brevity and readability. Default implementation just calls Print().
   virtual void PrettyPrint(text::Printer* printer) const;
 
+  // Removes any part of the value that is beind a disabled flag.
+  virtual void RemoveFlagDisabledElements() {
+  }
+
   friend std::ostream& operator<<(std::ostream& out, const Value& value);
 
  protected:
@@ -116,6 +128,7 @@
   std::string comment_;
   bool weak_ = false;
   bool translatable_ = true;
+  FlagStatus flag_status_ = FlagStatus::NoFlag;
 
  private:
   virtual Value* TransformValueImpl(ValueTransformer& transformer) const = 0;
@@ -346,6 +359,7 @@
 
   bool Equals(const Value* value) const override;
   void Print(std::ostream* out) const override;
+  void RemoveFlagDisabledElements() override;
 };
 
 struct Plural : public TransformableValue<Plural, BaseValue<Plural>> {
diff --git a/tools/aapt2/Resources.proto b/tools/aapt2/Resources.proto
index 2ecc82a..5c64089 100644
--- a/tools/aapt2/Resources.proto
+++ b/tools/aapt2/Resources.proto
@@ -246,7 +246,7 @@
 message ConfigValue {
   Configuration config = 1;
   Value value = 2;
-  uint32 flag_status = 3;
+  reserved 3;
 }
 
 // The generic meta-data for every value in a resource table.
@@ -280,6 +280,9 @@
     Id id = 6;
     Primitive prim = 7;
   }
+
+  // The status of the flag the value is behind if any
+  uint32 flag_status = 8;
 }
 
 // A CompoundValue is an abstract type. It represents a value that is a made of other values.
diff --git a/tools/aapt2/cmd/Link.cpp b/tools/aapt2/cmd/Link.cpp
index 56f5288..be63f82 100644
--- a/tools/aapt2/cmd/Link.cpp
+++ b/tools/aapt2/cmd/Link.cpp
@@ -1878,7 +1878,7 @@
       for (auto& type : package->types) {
         for (auto& entry : type->entries) {
           for (auto& config_value : entry->values) {
-            if (config_value->flag_status == FlagStatus::Disabled) {
+            if (config_value->value->GetFlagStatus() == FlagStatus::Disabled) {
               config_value->value->Accept(&visitor);
             }
           }
diff --git a/tools/aapt2/format/proto/ProtoDeserialize.cpp b/tools/aapt2/format/proto/ProtoDeserialize.cpp
index aaab315..55f5e56 100644
--- a/tools/aapt2/format/proto/ProtoDeserialize.cpp
+++ b/tools/aapt2/format/proto/ProtoDeserialize.cpp
@@ -534,8 +534,6 @@
           return false;
         }
 
-        config_value->flag_status = (FlagStatus)pb_config_value.flag_status();
-
         config_value->value = DeserializeValueFromPb(pb_config_value.value(), src_pool, config,
                                                      &out_table->string_pool, files, out_error);
         if (config_value->value == nullptr) {
@@ -877,11 +875,12 @@
   return value;
 }
 
-std::unique_ptr<Item> DeserializeItemFromPb(const pb::Item& pb_item,
-                                            const android::ResStringPool& src_pool,
-                                            const ConfigDescription& config,
-                                            android::StringPool* value_pool,
-                                            io::IFileCollection* files, std::string* out_error) {
+std::unique_ptr<Item> DeserializeItemFromPbInternal(const pb::Item& pb_item,
+                                                    const android::ResStringPool& src_pool,
+                                                    const ConfigDescription& config,
+                                                    android::StringPool* value_pool,
+                                                    io::IFileCollection* files,
+                                                    std::string* out_error) {
   switch (pb_item.value_case()) {
     case pb::Item::kRef: {
       const pb::Reference& pb_ref = pb_item.ref();
@@ -1010,6 +1009,19 @@
   return {};
 }
 
+std::unique_ptr<Item> DeserializeItemFromPb(const pb::Item& pb_item,
+                                            const android::ResStringPool& src_pool,
+                                            const ConfigDescription& config,
+                                            android::StringPool* value_pool,
+                                            io::IFileCollection* files, std::string* out_error) {
+  auto item =
+      DeserializeItemFromPbInternal(pb_item, src_pool, config, value_pool, files, out_error);
+  if (item) {
+    item->SetFlagStatus((FlagStatus)pb_item.flag_status());
+  }
+  return item;
+}
+
 std::unique_ptr<xml::XmlResource> DeserializeXmlResourceFromPb(const pb::XmlNode& pb_node,
                                                                std::string* out_error) {
   if (!pb_node.has_element()) {
diff --git a/tools/aapt2/format/proto/ProtoSerialize.cpp b/tools/aapt2/format/proto/ProtoSerialize.cpp
index c1e15bc..5772b3b 100644
--- a/tools/aapt2/format/proto/ProtoSerialize.cpp
+++ b/tools/aapt2/format/proto/ProtoSerialize.cpp
@@ -426,7 +426,6 @@
           pb_config_value->mutable_config()->set_product(config_value->product);
           SerializeValueToPb(*config_value->value, pb_config_value->mutable_value(),
                              source_pool.get());
-          pb_config_value->set_flag_status((uint32_t)config_value->flag_status);
         }
       }
     }
@@ -720,6 +719,9 @@
   if (src_pool != nullptr) {
     SerializeSourceToPb(value.GetSource(), src_pool, out_value->mutable_source());
   }
+  if (out_value->has_item()) {
+    out_value->mutable_item()->set_flag_status((uint32_t)value.GetFlagStatus());
+  }
 }
 
 void SerializeItemToPb(const Item& item, pb::Item* out_item) {
@@ -727,6 +729,7 @@
   ValueSerializer serializer(&value, nullptr);
   item.Accept(&serializer);
   out_item->MergeFrom(value.item());
+  out_item->set_flag_status((uint32_t)item.GetFlagStatus());
 }
 
 void SerializeCompiledFileToPb(const ResourceFile& file, pb::internal::CompiledFile* out_file) {
diff --git a/tools/aapt2/integration-tests/FlaggedResourcesTest/Android.bp b/tools/aapt2/integration-tests/FlaggedResourcesTest/Android.bp
index 5932271..4866d2c 100644
--- a/tools/aapt2/integration-tests/FlaggedResourcesTest/Android.bp
+++ b/tools/aapt2/integration-tests/FlaggedResourcesTest/Android.bp
@@ -28,11 +28,13 @@
     srcs: [
         "res/values/bools.xml",
         "res/values/bools2.xml",
+        "res/values/ints.xml",
         "res/values/strings.xml",
     ],
     out: [
         "values_bools.arsc.flat",
         "values_bools2.arsc.flat",
+        "values_ints.arsc.flat",
         "values_strings.arsc.flat",
     ],
     cmd: "$(location aapt2) compile $(in) -o $(genDir) " +
diff --git a/tools/aapt2/integration-tests/FlaggedResourcesTest/res/values/ints.xml b/tools/aapt2/integration-tests/FlaggedResourcesTest/res/values/ints.xml
new file mode 100644
index 0000000..26a5c40
--- /dev/null
+++ b/tools/aapt2/integration-tests/FlaggedResourcesTest/res/values/ints.xml
@@ -0,0 +1,9 @@
+<?xml version="1.0" encoding="utf-8"?>
+<resources xmlns:android="http://schemas.android.com/apk/res/android">
+    <integer-array name="intarr1">
+        <item>1</item>
+        <item>2</item>
+        <item android:featureFlag="test.package.falseFlag">666</item>
+        <item>3</item>
+    </integer-array>
+</resources>
\ No newline at end of file
diff --git a/tools/aapt2/integration-tests/FlaggedResourcesTest/res/values/strings.xml b/tools/aapt2/integration-tests/FlaggedResourcesTest/res/values/strings.xml
index 5c0fca1..3cbb928 100644
--- a/tools/aapt2/integration-tests/FlaggedResourcesTest/res/values/strings.xml
+++ b/tools/aapt2/integration-tests/FlaggedResourcesTest/res/values/strings.xml
@@ -3,4 +3,11 @@
     <string name="str">plain string</string>
 
     <string name="str1" android:featureFlag="test.package.falseFlag">DONTFIND</string>
+
+    <string-array name="strarr1">
+        <item>one</item>
+        <item>two</item>
+        <item android:featureFlag="test.package.falseFlag">remove</item>
+        <item android:featureFlag="test.package.trueFlag">three</item>
+    </string-array>
 </resources>
\ No newline at end of file
diff --git a/tools/aapt2/link/FlagDisabledResourceRemover.cpp b/tools/aapt2/link/FlagDisabledResourceRemover.cpp
index e3289e2..3ac1762 100644
--- a/tools/aapt2/link/FlagDisabledResourceRemover.cpp
+++ b/tools/aapt2/link/FlagDisabledResourceRemover.cpp
@@ -32,12 +32,17 @@
   const auto remove_iter =
       std::stable_partition(entry->values.begin(), end_iter,
                             [](const std::unique_ptr<ResourceConfigValue>& value) -> bool {
-                              return value->flag_status != FlagStatus::Disabled;
+                              return value->value->GetFlagStatus() != FlagStatus::Disabled;
                             });
 
   bool keep = remove_iter != entry->values.begin();
 
   entry->values.erase(remove_iter, end_iter);
+
+  for (auto& value : entry->values) {
+    value->value->RemoveFlagDisabledElements();
+  }
+
   return keep;
 }
 
diff --git a/tools/aapt2/link/TableMerger.cpp b/tools/aapt2/link/TableMerger.cpp
index 1942fc11..37a039e 100644
--- a/tools/aapt2/link/TableMerger.cpp
+++ b/tools/aapt2/link/TableMerger.cpp
@@ -212,8 +212,8 @@
     collision_result =
         ResolveMergeCollision(override_styles_instead_of_overlaying, dst_value, src_value, pool);
   } else {
-    collision_result = ResourceTable::ResolveFlagCollision(dst_config_value->flag_status,
-                                                           src_config_value->flag_status);
+    collision_result =
+        ResourceTable::ResolveFlagCollision(dst_value->GetFlagStatus(), src_value->GetFlagStatus());
     if (collision_result == CollisionResult::kConflict) {
       collision_result = ResourceTable::ResolveValueCollision(dst_value, src_value);
     }
@@ -295,7 +295,6 @@
         } else {
           dst_config_value =
               dst_entry->FindOrCreateValue(src_config_value->config, src_config_value->product);
-          dst_config_value->flag_status = src_config_value->flag_status;
         }
 
         // Continue if we're taking the new resource.
diff --git a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/Exceptions.kt b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/Exceptions.kt
index 910bf59..f59e143 100644
--- a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/Exceptions.kt
+++ b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/Exceptions.kt
@@ -15,6 +15,8 @@
  */
 package com.android.hoststubgen
 
+import java.io.File
+
 /**
  * We will not print the stack trace for exceptions implementing it.
  */
@@ -49,4 +51,22 @@
 /**
  * We use this for general "user" errors.
  */
-class HostStubGenUserErrorException(message: String) : Exception(message), UserErrorException
+class GeneralUserErrorException(message: String) : Exception(message), UserErrorException
+
+/** Base exception class for invalid command line arguments. */
+open class ArgumentsException(message: String?) : Exception(message), UserErrorException
+
+/** Thrown when the same annotation is used with different annotation arguments. */
+class DuplicateAnnotationException(annotationName: String?) :
+    ArgumentsException("Duplicate annotation specified: '$annotationName'")
+
+/** Thrown when an input file does not exist. */
+class InputFileNotFoundException(filename: String) :
+    ArgumentsException("File '$filename' not found")
+
+fun String.ensureFileExists(): String {
+    if (!File(this).exists()) {
+        throw InputFileNotFoundException(this)
+    }
+    return this
+}
diff --git a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/HostStubGen.kt b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/HostStubGen.kt
index 36bfbef..7b08678 100644
--- a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/HostStubGen.kt
+++ b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/HostStubGen.kt
@@ -22,6 +22,7 @@
 import com.android.hoststubgen.filters.ConstantFilter
 import com.android.hoststubgen.filters.DefaultHookInjectingFilter
 import com.android.hoststubgen.filters.FilterPolicy
+import com.android.hoststubgen.filters.FilterRemapper
 import com.android.hoststubgen.filters.ImplicitOutputFilter
 import com.android.hoststubgen.filters.OutputFilter
 import com.android.hoststubgen.filters.StubIntersectingFilter
@@ -37,6 +38,7 @@
 import org.objectweb.asm.commons.Remapper
 import org.objectweb.asm.util.CheckClassAdapter
 import java.io.BufferedInputStream
+import java.io.BufferedOutputStream
 import java.io.FileOutputStream
 import java.io.InputStream
 import java.io.OutputStream
@@ -74,7 +76,9 @@
         }
 
         // Build the filters.
-        val (filter, policyFileRemapper) = buildFilter(errors, allClasses, options)
+        val filter = buildFilter(errors, allClasses, options)
+
+        val filterRemapper = FilterRemapper(filter)
 
         // Transform the jar.
         convert(
@@ -86,7 +90,7 @@
                 allClasses,
                 errors,
                 stats,
-                policyFileRemapper,
+                filterRemapper,
                 options.numShards.get,
                 options.shard.get,
         )
@@ -116,7 +120,7 @@
             errors: HostStubGenErrors,
             allClasses: ClassNodes,
             options: HostStubGenOptions,
-            ): Pair<OutputFilter, Remapper?> {
+            ): OutputFilter {
         // We build a "chain" of multiple filters here.
         //
         // The filters are build in from "inside", meaning the first filter created here is
@@ -169,14 +173,10 @@
             filter,
         )
 
-        var policyFileRemapper: Remapper? = null
-
         // Next, "text based" filter, which allows to override polices without touching
         // the target code.
         options.policyOverrideFile.ifSet {
-            val (f, p) = createFilterFromTextPolicyFile(it, allClasses, filter)
-            filter = f
-            policyFileRemapper = p
+            filter = createFilterFromTextPolicyFile(it, allClasses, filter)
         }
 
         // If `--intersect-stub-jar` is provided, load from these jar files too.
@@ -191,7 +191,7 @@
         // Apply the implicit filter.
         filter = ImplicitOutputFilter(errors, allClasses, filter)
 
-        return Pair(filter, policyFileRemapper)
+        return filter
     }
 
     /**
@@ -273,7 +273,7 @@
         if (filename == null) {
             return block(null)
         }
-        return ZipOutputStream(FileOutputStream(filename)).use(block)
+        return ZipOutputStream(BufferedOutputStream(FileOutputStream(filename))).use(block)
     }
 
     /**
@@ -334,13 +334,14 @@
             entry: ZipEntry,
             out: ZipOutputStream,
             ) {
-        BufferedInputStream(inZip.getInputStream(entry)).use { bis ->
+        // TODO: It seems like copying entries this way is _very_ slow,
+        // even with out.setLevel(0). Look for other ways to do it.
+
+        inZip.getInputStream(entry).use { ins ->
             // Copy unknown entries as is to the impl out. (but not to the stub out.)
             val outEntry = ZipEntry(entry.name)
             out.putNextEntry(outEntry)
-            while (bis.available() > 0) {
-                out.write(bis.read())
-            }
+            ins.transferTo(out)
             out.closeEntry()
         }
     }
diff --git a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/HostStubGenErrors.kt b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/HostStubGenErrors.kt
index 6b01d48..a218c55 100644
--- a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/HostStubGenErrors.kt
+++ b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/HostStubGenErrors.kt
@@ -19,6 +19,6 @@
     open fun onErrorFound(message: String) {
         // TODO: For now, we just throw as soon as any error is found, but eventually we should keep
         // all errors and print them at the end.
-        throw HostStubGenUserErrorException(message)
+        throw GeneralUserErrorException(message)
     }
 }
\ No newline at end of file
diff --git a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/HostStubGenLogger.kt b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/HostStubGenLogger.kt
index ee4a06f..4bcee40 100644
--- a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/HostStubGenLogger.kt
+++ b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/HostStubGenLogger.kt
@@ -89,6 +89,8 @@
         addPrinter(StreamPrinter(level, PrintWriter(BufferedOutputStream(
             FileOutputStream(logFilename)))))
 
+        log.i("Log file set: $logFilename for $level")
+
         return this
     }
 
@@ -121,7 +123,10 @@
         return level.ordinal <= maxLogLevel.ordinal
     }
 
-    private fun println(level: LogLevel, message: String) {
+    fun println(level: LogLevel, message: String) {
+        if (message.isEmpty()) {
+            return // Don't print an empty message.
+        }
         printers.forEach {
             if (it.logLevel.ordinal >= level.ordinal) {
                 it.println(level, indent, message)
@@ -129,7 +134,7 @@
         }
     }
 
-    private fun println(level: LogLevel, format: String, vararg args: Any?) {
+    fun println(level: LogLevel, format: String, vararg args: Any?) {
         if (isEnabled(level)) {
             println(level, String.format(format, *args))
         }
@@ -185,16 +190,45 @@
         println(LogLevel.Debug, format, *args)
     }
 
-    inline fun <T> iTime(message: String, block: () -> T): T {
+    inline fun <T> logTime(level: LogLevel, message: String, block: () -> T): Double {
+        var ret: Double = -1.0
         val start = System.currentTimeMillis()
-        val ret = block()
-        val end = System.currentTimeMillis()
-
-        log.i("%s: took %.1f second(s).", message, (end - start) / 1000.0)
-
+        try {
+            block()
+        } finally {
+            val end = System.currentTimeMillis()
+            ret = (end - start) / 1000.0
+            if (isEnabled(level)) {
+                println(level,
+                    String.format("%s: took %.1f second(s).", message, (end - start) / 1000.0))
+            }
+        }
         return ret
     }
 
+    /** Do an "i" log with how long it took. */
+    inline fun <T> iTime(message: String, block: () -> T): Double {
+        return logTime(LogLevel.Info, message, block)
+    }
+
+    /** Do a "v" log with how long it took. */
+    inline fun <T> vTime(message: String, block: () -> T): Double {
+        return logTime(LogLevel.Verbose, message, block)
+    }
+
+    /** Do a "d" log with how long it took. */
+    inline fun <T> dTime(message: String, block: () -> T): Double {
+        return logTime(LogLevel.Debug, message, block)
+    }
+
+    /**
+     * Similar to the other "xTime" methods, but the message is not supposed to be printed.
+     * It's only used to measure the duration with the same interface as other log methods.
+     */
+    inline fun <T> nTime(block: () -> T): Double {
+        return logTime(LogLevel.Debug, "", block)
+    }
+
     inline fun forVerbose(block: () -> Unit) {
         if (isEnabled(LogLevel.Verbose)) {
             block()
@@ -238,6 +272,21 @@
             }
         }
     }
+
+    /**
+     * Handle log-related command line arguments.
+     */
+    fun maybeHandleCommandLineArg(currentArg: String, nextArgProvider: () -> String): Boolean {
+        when (currentArg) {
+            "-v", "--verbose" -> setConsoleLogLevel(LogLevel.Verbose)
+            "-d", "--debug" -> setConsoleLogLevel(LogLevel.Debug)
+            "-q", "--quiet" -> setConsoleLogLevel(LogLevel.None)
+            "--verbose-log" -> addFilePrinter(LogLevel.Verbose, nextArgProvider())
+            "--debug-log" -> addFilePrinter(LogLevel.Debug, nextArgProvider())
+            else -> return false
+        }
+        return true
+    }
 }
 
 private interface LogPrinter {
diff --git a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/HostStubGenMain.kt b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/HostStubGenMain.kt
index 45e7e30..8506466 100644
--- a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/HostStubGenMain.kt
+++ b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/HostStubGenMain.kt
@@ -24,20 +24,32 @@
  */
 fun main(args: Array<String>) {
     executableName = "HostStubGen"
+    runMainWithBoilerplate {
+        // Parse the command line arguments.
+        var clanupOnError = false
+        try {
+            val options = HostStubGenOptions.parseArgs(args)
+            clanupOnError = options.cleanUpOnError.get
 
+            log.v("$executableName started")
+            log.v("Options: $options")
+
+            // Run.
+            HostStubGen(options).run()
+        } catch (e: Throwable) {
+            if (clanupOnError) {
+                TODO("Remove output jars here")
+            }
+            throw e
+        }
+    }
+}
+
+inline fun runMainWithBoilerplate(realMain: () -> Unit) {
     var success = false
-    var clanupOnError = false
 
     try {
-        // Parse the command line arguments.
-        val options = HostStubGenOptions.parseArgs(args)
-        clanupOnError = options.cleanUpOnError.get
-
-        log.v("$executableName started")
-        log.v("Options: $options")
-
-        // Run.
-        HostStubGen(options).run()
+        realMain()
 
         success = true
     } catch (e: Throwable) {
@@ -45,9 +57,6 @@
         if (e !is UserErrorException) {
             e.printStackTrace(PrintWriter(log.getWriter(LogLevel.Error)))
         }
-        if (clanupOnError) {
-            TODO("Remove output jars here")
-        }
     } finally {
         log.i("$executableName finished")
         log.flush()
diff --git a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/HostStubGenOptions.kt b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/HostStubGenOptions.kt
index 2f833a8..f88b107 100644
--- a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/HostStubGenOptions.kt
+++ b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/HostStubGenOptions.kt
@@ -17,20 +17,17 @@
 
 import com.android.hoststubgen.filters.FilterPolicy
 import java.io.BufferedReader
-import java.io.File
 import java.io.FileReader
 
 /**
  * A single value that can only set once.
  */
-class SetOnce<T>(
-        private var value: T,
-) {
+open class SetOnce<T>(private var value: T) {
     class SetMoreThanOnceException : Exception()
 
     private var set = false
 
-    fun set(v: T) {
+    fun set(v: T): T {
         if (set) {
             throw SetMoreThanOnceException()
         }
@@ -39,6 +36,7 @@
         }
         set = true
         value = v
+        return v
     }
 
     val get: T
@@ -59,6 +57,16 @@
     }
 }
 
+class IntSetOnce(value: Int) : SetOnce<Int>(value) {
+    fun set(v: String): Int {
+        try {
+            return this.set(v.toInt())
+        } catch (e: NumberFormatException) {
+            throw ArgumentsException("Invalid integer $v")
+        }
+    }
+}
+
 /**
  * Options that can be set from command line arguments.
  */
@@ -113,18 +121,11 @@
 
         var apiListFile: SetOnce<String?> = SetOnce(null),
 
-        var numShards: SetOnce<Int> = SetOnce(1),
-        var shard: SetOnce<Int> = SetOnce(0),
+        var numShards: IntSetOnce = IntSetOnce(1),
+        var shard: IntSetOnce = IntSetOnce(0),
 ) {
     companion object {
 
-        private fun String.ensureFileExists(): String {
-            if (!File(this).exists()) {
-                throw InputFileNotFoundException(this)
-            }
-            return this
-        }
-
         private fun parsePackageRedirect(fromColonTo: String): Pair<String, String> {
             val colon = fromColonTo.indexOf(':')
             if ((colon < 1) || (colon + 1 >= fromColonTo.length)) {
@@ -137,7 +138,7 @@
         fun parseArgs(args: Array<String>): HostStubGenOptions {
             val ret = HostStubGenOptions()
 
-            val ai = ArgIterator(expandAtFiles(args))
+            val ai = ArgIterator.withAtFiles(args)
 
             var allAnnotations = mutableSetOf<String>()
 
@@ -148,11 +149,6 @@
                 return name
             }
 
-            fun setLogFile(level: LogLevel, filename: String) {
-                log.addFilePrinter(level, filename)
-                log.i("$level log file: $filename")
-            }
-
             while (true) {
                 val arg = ai.nextArgOptional()
                 if (arg == null) {
@@ -161,33 +157,23 @@
 
                 // Define some shorthands...
                 fun nextArg(): String = ai.nextArgRequired(arg)
-                fun SetOnce<String>.setNextStringArg(): String = nextArg().also { this.set(it) }
-                fun SetOnce<String?>.setNextStringArg(): String = nextArg().also { this.set(it) }
                 fun MutableSet<String>.addUniqueAnnotationArg(): String =
                         nextArg().also { this += ensureUniqueAnnotation(it) }
-                fun SetOnce<Int>.setNextIntArg(): String = nextArg().also {
-                    try {
-                        this.set(it.toInt())
-                    } catch (e: NumberFormatException) {
-                        throw ArgumentsException("Invalid integer for $arg: $it")
-                    }
-                }
 
+                if (log.maybeHandleCommandLineArg(arg) { nextArg() }) {
+                    continue
+                }
                 try {
                     when (arg) {
                         // TODO: Write help
                         "-h", "--help" -> TODO("Help is not implemented yet")
 
-                        "-v", "--verbose" -> log.setConsoleLogLevel(LogLevel.Verbose)
-                        "-d", "--debug" -> log.setConsoleLogLevel(LogLevel.Debug)
-                        "-q", "--quiet" -> log.setConsoleLogLevel(LogLevel.None)
-
-                        "--in-jar" -> ret.inJar.setNextStringArg().ensureFileExists()
-                        "--out-stub-jar" -> ret.outStubJar.setNextStringArg()
-                        "--out-impl-jar" -> ret.outImplJar.setNextStringArg()
+                        "--in-jar" -> ret.inJar.set(nextArg()).ensureFileExists()
+                        "--out-stub-jar" -> ret.outStubJar.set(nextArg())
+                        "--out-impl-jar" -> ret.outImplJar.set(nextArg())
 
                         "--policy-override-file" ->
-                            ret.policyOverrideFile.setNextStringArg().ensureFileExists()
+                            ret.policyOverrideFile.set(nextArg())!!.ensureFileExists()
 
                         "--clean-up-on-error" -> ret.cleanUpOnError.set(true)
                         "--no-clean-up-on-error" -> ret.cleanUpOnError.set(false)
@@ -231,19 +217,19 @@
                             ret.packageRedirects += parsePackageRedirect(nextArg())
 
                         "--annotation-allowed-classes-file" ->
-                            ret.annotationAllowedClassesFile.setNextStringArg()
+                            ret.annotationAllowedClassesFile.set(nextArg())
 
                         "--default-class-load-hook" ->
-                            ret.defaultClassLoadHook.setNextStringArg()
+                            ret.defaultClassLoadHook.set(nextArg())
 
                         "--default-method-call-hook" ->
-                            ret.defaultMethodCallHook.setNextStringArg()
+                            ret.defaultMethodCallHook.set(nextArg())
 
                         "--intersect-stub-jar" ->
                             ret.intersectStubJars += nextArg().ensureFileExists()
 
                         "--gen-keep-all-file" ->
-                            ret.inputJarAsKeepAllFile.setNextStringArg()
+                            ret.inputJarAsKeepAllFile.set(nextArg())
 
                         // Following options are for debugging.
                         "--enable-class-checker" -> ret.enableClassChecker.set(true)
@@ -261,16 +247,21 @@
                         "--no-non-stub-method-check" ->
                             ret.enableNonStubMethodCallDetection.set(false)
 
-                        "--gen-input-dump-file" -> ret.inputJarDumpFile.setNextStringArg()
+                        "--gen-input-dump-file" -> ret.inputJarDumpFile.set(nextArg())
 
-                        "--verbose-log" -> setLogFile(LogLevel.Verbose, nextArg())
-                        "--debug-log" -> setLogFile(LogLevel.Debug, nextArg())
+                        "--stats-file" -> ret.statsFile.set(nextArg())
+                        "--supported-api-list-file" -> ret.apiListFile.set(nextArg())
 
-                        "--stats-file" -> ret.statsFile.setNextStringArg()
-                        "--supported-api-list-file" -> ret.apiListFile.setNextStringArg()
-
-                        "--num-shards" -> ret.numShards.setNextIntArg()
-                        "--shard-index" -> ret.shard.setNextIntArg()
+                        "--num-shards" -> ret.numShards.set(nextArg()).also {
+                            if (it < 1) {
+                                throw ArgumentsException("$arg must be positive integer")
+                            }
+                        }
+                        "--shard-index" -> ret.shard.set(nextArg()).also {
+                            if (it < 0) {
+                                throw ArgumentsException("$arg must be positive integer or zero")
+                            }
+                        }
 
                         else -> throw ArgumentsException("Unknown option: $arg")
                     }
@@ -286,6 +277,15 @@
                 log.w("Neither --out-stub-jar nor --out-impl-jar is set." +
                         " $executableName will not generate jar files.")
             }
+            if (ret.numShards.isSet != ret.shard.isSet) {
+                throw ArgumentsException("--num-shards and --shard-index must be used together")
+            }
+
+            if (ret.numShards.isSet) {
+                if (ret.shard.get >= ret.numShards.get) {
+                    throw ArgumentsException("--shard-index must be smaller than --num-shards")
+                }
+            }
 
             if (ret.enableNonStubMethodCallDetection.get) {
                 log.w("--enable-non-stub-method-check is not fully implemented yet." +
@@ -294,87 +294,6 @@
 
             return ret
         }
-
-        /**
-         * Scan the arguments, and if any of them starts with an `@`, then load from the file
-         * and use its content as arguments.
-         *
-         * In this file, each line is treated as a single argument.
-         *
-         * The file can contain '#' as comments.
-         */
-        private fun expandAtFiles(args: Array<String>): List<String> {
-            val ret = mutableListOf<String>()
-
-            args.forEach { arg ->
-                if (!arg.startsWith('@')) {
-                    ret += arg
-                    return@forEach
-                }
-                // Read from the file, and add each line to the result.
-                val filename = arg.substring(1).ensureFileExists()
-
-                log.v("Expanding options file $filename")
-
-                BufferedReader(FileReader(filename)).use { reader ->
-                    while (true) {
-                        var line = reader.readLine()
-                        if (line == null) {
-                            break // EOF
-                        }
-
-                        line = normalizeTextLine(line)
-                        if (line.isNotEmpty()) {
-                            ret += line
-                        }
-                    }
-                }
-            }
-            return ret
-        }
-    }
-
-    open class ArgumentsException(message: String?) : Exception(message), UserErrorException
-
-    /** Thrown when the same annotation is used with different annotation arguments. */
-    class DuplicateAnnotationException(annotationName: String?) :
-            ArgumentsException("Duplicate annotation specified: '$annotationName'")
-
-    /** Thrown when an input file does not exist. */
-    class InputFileNotFoundException(filename: String) :
-            ArgumentsException("File '$filename' not found")
-
-    private class ArgIterator(
-            private val args: List<String>,
-            private var currentIndex: Int = -1
-    ) {
-        val current: String
-            get() = args.get(currentIndex)
-
-        /**
-         * Get the next argument, or [null] if there's no more arguments.
-         */
-        fun nextArgOptional(): String? {
-            if ((currentIndex + 1) >= args.size) {
-                return null
-            }
-            return args.get(++currentIndex)
-        }
-
-        /**
-         * Get the next argument, or throw if
-         */
-        fun nextArgRequired(argName: String): String {
-            nextArgOptional().let {
-                if (it == null) {
-                    throw ArgumentsException("Missing parameter for option $argName")
-                }
-                if (it.isEmpty()) {
-                    throw ArgumentsException("Parameter can't be empty for option $argName")
-                }
-                return it
-            }
-        }
     }
 
     override fun toString(): String {
@@ -415,3 +334,80 @@
             """.trimIndent()
     }
 }
+
+class ArgIterator(
+    private val args: List<String>,
+    private var currentIndex: Int = -1
+) {
+    val current: String
+        get() = args.get(currentIndex)
+
+    /**
+     * Get the next argument, or [null] if there's no more arguments.
+     */
+    fun nextArgOptional(): String? {
+        if ((currentIndex + 1) >= args.size) {
+            return null
+        }
+        return args.get(++currentIndex)
+    }
+
+    /**
+     * Get the next argument, or throw if
+     */
+    fun nextArgRequired(argName: String): String {
+        nextArgOptional().let {
+            if (it == null) {
+                throw ArgumentsException("Missing parameter for option $argName")
+            }
+            if (it.isEmpty()) {
+                throw ArgumentsException("Parameter can't be empty for option $argName")
+            }
+            return it
+        }
+    }
+
+    companion object {
+        fun withAtFiles(args: Array<String>): ArgIterator {
+            return ArgIterator(expandAtFiles(args))
+        }
+    }
+}
+
+/**
+ * Scan the arguments, and if any of them starts with an `@`, then load from the file
+ * and use its content as arguments.
+ *
+ * In this file, each line is treated as a single argument.
+ *
+ * The file can contain '#' as comments.
+ */
+private fun expandAtFiles(args: Array<String>): List<String> {
+    val ret = mutableListOf<String>()
+
+    args.forEach { arg ->
+        if (!arg.startsWith('@')) {
+            ret += arg
+            return@forEach
+        }
+        // Read from the file, and add each line to the result.
+        val filename = arg.substring(1).ensureFileExists()
+
+        log.v("Expanding options file $filename")
+
+        BufferedReader(FileReader(filename)).use { reader ->
+            while (true) {
+                var line = reader.readLine()
+                if (line == null) {
+                    break // EOF
+                }
+
+                line = normalizeTextLine(line)
+                if (line.isNotEmpty()) {
+                    ret += line
+                }
+            }
+        }
+    }
+    return ret
+}
diff --git a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/asm/AsmUtils.kt b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/asm/AsmUtils.kt
index 3f2b13a..6cf2143 100644
--- a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/asm/AsmUtils.kt
+++ b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/asm/AsmUtils.kt
@@ -58,7 +58,24 @@
     return null
 }
 
-fun findAnnotationValueAsString(an: AnnotationNode, propertyName: String): String? {
+fun ClassNode.findAnyAnnotation(set: Set<String>): AnnotationNode? {
+    return findAnyAnnotation(set, this.visibleAnnotations, this.invisibleAnnotations)
+}
+
+fun MethodNode.findAnyAnnotation(set: Set<String>): AnnotationNode? {
+    return findAnyAnnotation(set, this.visibleAnnotations, this.invisibleAnnotations)
+}
+
+fun FieldNode.findAnyAnnotation(set: Set<String>): AnnotationNode? {
+    return findAnyAnnotation(set, this.visibleAnnotations, this.invisibleAnnotations)
+}
+
+fun <T> findAnnotationValueAsObject(
+    an: AnnotationNode,
+    propertyName: String,
+    expectedTypeHumanReadableName: String,
+    converter: (Any?) -> T?,
+): T? {
     for (i in 0..(an.values?.size ?: 0) - 2 step 2) {
         val name = an.values[i]
 
@@ -66,16 +83,30 @@
             continue
         }
         val value = an.values[i + 1]
-        if (value is String) {
-            return value
+        if (value == null) {
+            return null
         }
-        throw ClassParseException(
-                "The type of '$name' in annotation \"${an.desc}\" must be String" +
-                        ", but is ${value?.javaClass?.canonicalName}")
+
+        try {
+            return converter(value)
+        } catch (e: ClassCastException) {
+            throw ClassParseException(
+                "The type of '$propertyName' in annotation @${an.desc} must be " +
+                        "$expectedTypeHumanReadableName, but is ${value?.javaClass?.canonicalName}")
+        }
     }
     return null
 }
 
+fun findAnnotationValueAsString(an: AnnotationNode, propertyName: String): String? {
+    return findAnnotationValueAsObject(an, propertyName, "String", {it as String})
+}
+
+fun findAnnotationValueAsType(an: AnnotationNode, propertyName: String): Type? {
+    return findAnnotationValueAsObject(an, propertyName, "Class", {it as Type})
+}
+
+
 val periodOrSlash = charArrayOf('.', '/')
 
 fun getPackageNameFromFullClassName(fullClassName: String): String {
@@ -117,6 +148,32 @@
     return "$defaultPackageName.$className"
 }
 
+fun splitWithLastPeriod(name: String): Pair<String, String>? {
+    val pos = name.lastIndexOf('.')
+    if (pos < 0) {
+        return null
+    }
+    return Pair(name.substring(0, pos), name.substring(pos + 1))
+}
+
+fun String.startsWithAny(vararg prefixes: String): Boolean {
+    prefixes.forEach {
+        if (this.startsWith(it)) {
+            return true
+        }
+    }
+    return false
+}
+
+fun String.endsWithAny(vararg suffixes: String): Boolean {
+    suffixes.forEach {
+        if (this.endsWith(it)) {
+            return true
+        }
+    }
+    return false
+}
+
 fun String.toJvmClassName(): String {
     return this.replace('.', '/')
 }
@@ -129,6 +186,14 @@
     return this.replace('/', '.')
 }
 
+fun zipEntryNameToClassName(entryFilename: String): String? {
+    val suffix = ".class"
+    if (!entryFilename.endsWith(suffix)) {
+        return null
+    }
+    return entryFilename.substring(0, entryFilename.length - suffix.length)
+}
+
 private val numericalInnerClassName = """.*\$\d+$""".toRegex()
 
 fun isAnonymousInnerClass(cn: ClassNode): Boolean {
@@ -198,11 +263,11 @@
 /**
  * Given a method descriptor, insert an [argType] as the first argument to it.
  */
-fun prependArgTypeToMethodDescriptor(methodDescriptor: String, argType: Type): String {
+fun prependArgTypeToMethodDescriptor(methodDescriptor: String, classInternalName: String): String {
     val returnType = Type.getReturnType(methodDescriptor)
     val argTypes = Type.getArgumentTypes(methodDescriptor).toMutableList()
 
-    argTypes.add(0, argType)
+    argTypes.add(0, Type.getType("L" + classInternalName + ";"))
 
     return Type.getMethodDescriptor(returnType, *argTypes.toTypedArray())
 }
@@ -270,6 +335,14 @@
     return (this.access and Opcodes.ACC_STATIC) != 0
 }
 
+fun MethodNode.isPublic(): Boolean {
+    return (this.access and Opcodes.ACC_PUBLIC) != 0
+}
+
+fun MethodNode.isSpecial(): Boolean {
+    return CTOR_NAME == this.name || CLASS_INITIALIZER_NAME == this.name
+}
+
 fun FieldNode.isEnum(): Boolean {
     return (this.access and Opcodes.ACC_ENUM) != 0
 }
diff --git a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/asm/ClassNodes.kt b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/asm/ClassNodes.kt
index 2607df6..e2647eb 100644
--- a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/asm/ClassNodes.kt
+++ b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/asm/ClassNodes.kt
@@ -27,6 +27,11 @@
 import java.io.BufferedInputStream
 import java.io.PrintWriter
 import java.util.Arrays
+import java.util.concurrent.Executors
+import java.util.concurrent.TimeUnit
+import java.util.concurrent.atomic.AtomicReference
+import java.util.function.Consumer
+import java.util.zip.ZipEntry
 import java.util.zip.ZipFile
 
 /**
@@ -183,10 +188,43 @@
         /**
          * Load all the classes, without code.
          */
-        fun loadClassStructures(inJar: String): ClassNodes {
-            log.iTime("Reading class structure from $inJar") {
-                val allClasses = ClassNodes()
+        fun loadClassStructures(
+            inJar: String,
+            timeCollector: Consumer<Double>? = null,
+        ): ClassNodes {
+            val allClasses = ClassNodes()
 
+            // Load classes in parallel.
+            val executor = Executors.newFixedThreadPool(4)
+
+            // First exception defected.
+            val exception = AtomicReference<Throwable>()
+
+            // Called on a BG thread. Read a single jar entry and add it to [allClasses].
+            fun parseClass(inZip: ZipFile, entry: ZipEntry) {
+                try {
+                    inZip.getInputStream(entry).use { ins ->
+                        val cr = ClassReader(BufferedInputStream(ins))
+                        val cn = ClassNode()
+                        cr.accept(
+                            cn, ClassReader.SKIP_CODE
+                                    or ClassReader.SKIP_DEBUG
+                                    or ClassReader.SKIP_FRAMES
+                        )
+                        synchronized(allClasses) {
+                            if (!allClasses.addClass(cn)) {
+                                log.w("Duplicate class found: ${cn.name}")
+                            }
+                        }
+                    }
+                } catch (e: Throwable) {
+                    log.e("Failed to load class: $e")
+                    exception.compareAndSet(null, e)
+                }
+            }
+
+            // Actually open the jar and read it on worker threads.
+            val time = log.iTime("Reading class structure from $inJar") {
                 log.withIndent {
                     ZipFile(inJar).use { inZip ->
                         val inEntries = inZip.entries()
@@ -194,40 +232,42 @@
                         while (inEntries.hasMoreElements()) {
                             val entry = inEntries.nextElement()
 
-                            BufferedInputStream(inZip.getInputStream(entry)).use { bis ->
-                                if (entry.name.endsWith(".class")) {
-                                    val cr = ClassReader(bis)
-                                    val cn = ClassNode()
-                                    cr.accept(
-                                        cn, ClassReader.SKIP_CODE
-                                                or ClassReader.SKIP_DEBUG
-                                                or ClassReader.SKIP_FRAMES
-                                    )
-                                    if (!allClasses.addClass(cn)) {
-                                        log.w("Duplicate class found: ${cn.name}")
-                                    }
-                                } else if (entry.name.endsWith(".dex")) {
-                                    // Seems like it's an ART jar file. We can't process it.
-                                    // It's a fatal error.
-                                    throw InvalidJarFileException(
-                                        "$inJar is not a desktop jar file."
-                                        + " It contains a *.dex file."
-                                    )
-                                } else {
-                                    // Unknown file type. Skip.
-                                    while (bis.available() > 0) {
-                                        bis.skip((1024 * 1024).toLong())
-                                    }
+                            if (entry.name.endsWith(".class")) {
+                                executor.submit {
+                                    parseClass(inZip, entry)
                                 }
+                            } else if (entry.name.endsWith(".dex")) {
+                                // Seems like it's an ART jar file. We can't process it.
+                                // It's a fatal error.
+                                throw InvalidJarFileException(
+                                    "$inJar is not a desktop jar file."
+                                            + " It contains a *.dex file."
+                                )
+                            } else {
+                                // Unknown file type. Skip.
                             }
                         }
+                        // Wait for all the work to complete. (must do it before closing the zip)
+                        log.i("Waiting for all loaders to finish...")
+                        executor.shutdown()
+                        executor.awaitTermination(5, TimeUnit.MINUTES)
+                        log.i("All loaders to finished.")
                     }
                 }
+
+                // If any exception is detected, throw it.
+                exception.get()?.let {
+                    throw it
+                }
+
                 if (allClasses.size == 0) {
                     log.w("$inJar contains no *.class files.")
+                } else {
+                    log.i("Loaded ${allClasses.size} classes from $inJar.")
                 }
-                return allClasses
             }
+            timeCollector?.accept(time)
+            return allClasses
         }
     }
 }
\ No newline at end of file
diff --git a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/DelegatingFilter.kt b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/DelegatingFilter.kt
index cdd24e8..6fcffb8 100644
--- a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/DelegatingFilter.kt
+++ b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/DelegatingFilter.kt
@@ -87,4 +87,23 @@
     ): List<String> {
         return fallback.getMethodCallHooks(className, methodName, descriptor)
     }
+
+    override fun remapType(className: String): String? {
+        return fallback.remapType(className)
+    }
+
+    override fun hasAnyMethodCallReplace(): Boolean {
+        return fallback.hasAnyMethodCallReplace()
+    }
+
+    override fun getMethodCallReplaceTo(
+        callerClassName: String,
+        callerMethodName: String,
+        className: String,
+        methodName: String,
+        descriptor: String,
+    ): MethodReplaceTarget? {
+        return fallback.getMethodCallReplaceTo(
+            callerClassName, callerMethodName, className, methodName, descriptor)
+    }
 }
diff --git a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/FilterPolicy.kt b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/FilterPolicy.kt
index 4d21106..f839444 100644
--- a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/FilterPolicy.kt
+++ b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/FilterPolicy.kt
@@ -70,7 +70,7 @@
         get() = this == SubstituteAndStub || this == SubstituteAndKeep
 
     val needsInStub: Boolean
-        get() = this == Stub || this == StubClass || this == SubstituteAndStub
+        get() = this == Stub || this == StubClass || this == SubstituteAndStub || this == Ignore
 
     val needsInImpl: Boolean
         get() = this != Remove
diff --git a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/FilterRemapper.kt b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/FilterRemapper.kt
new file mode 100644
index 0000000..c5a2f9f
--- /dev/null
+++ b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/FilterRemapper.kt
@@ -0,0 +1,41 @@
+/*
+ * 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.hoststubgen.filters
+
+import org.objectweb.asm.commons.Remapper
+
+/**
+ * A [Remapper] that uses [OutputFilter.remapType]
+ */
+class FilterRemapper(val filter: OutputFilter) : Remapper() {
+    private val cache = mutableMapOf<String, String>()
+
+    override fun mapType(typeInternalName: String?): String? {
+        if (typeInternalName == null) {
+            return null
+        }
+
+        cache[typeInternalName]?.let {
+            return it
+        }
+
+        var mapped = filter.remapType(typeInternalName) ?: typeInternalName
+        cache[typeInternalName] = mapped
+        return mapped
+    }
+
+    // TODO Do we need to implement mapPackage(), etc too?
+}
\ No newline at end of file
diff --git a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/OutputFilter.kt b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/OutputFilter.kt
index 3df16ff..1049e2b 100644
--- a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/OutputFilter.kt
+++ b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/OutputFilter.kt
@@ -89,4 +89,35 @@
             List<String> {
         return emptyList()
     }
-}
\ No newline at end of file
+
+    /**
+     * Take a class (internal) name. If the class needs to be renamed, return the new name.
+     * This is used by [FilterRemapper].
+     */
+    open fun remapType(className: String): String? {
+        return null
+    }
+
+    data class MethodReplaceTarget(val className: String, val methodName: String)
+
+    /**
+     * Return if this filter may return non-null from [getMethodCallReplaceTo].
+     * (Used for a small optimization)
+     */
+    open fun hasAnyMethodCallReplace(): Boolean {
+        return false
+    }
+
+    /**
+     * If a method call should be forwarded to another method, return the target's class / method.
+     */
+    open fun getMethodCallReplaceTo(
+        callerClassName: String,
+        callerMethodName: String,
+        className: String,
+        methodName: String,
+        descriptor: String,
+    ): MethodReplaceTarget? {
+        return null
+    }
+}
diff --git a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/TextFileFilterPolicyParser.kt b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/TextFileFilterPolicyParser.kt
index 1828003..53bcf10 100644
--- a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/TextFileFilterPolicyParser.kt
+++ b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/TextFileFilterPolicyParser.kt
@@ -17,12 +17,13 @@
 
 import com.android.hoststubgen.ParseException
 import com.android.hoststubgen.asm.ClassNodes
+import com.android.hoststubgen.asm.splitWithLastPeriod
 import com.android.hoststubgen.asm.toHumanReadableClassName
+import com.android.hoststubgen.asm.toJvmClassName
 import com.android.hoststubgen.log
 import com.android.hoststubgen.normalizeTextLine
 import com.android.hoststubgen.whitespaceRegex
 import org.objectweb.asm.Opcodes
-import org.objectweb.asm.commons.Remapper
 import org.objectweb.asm.tree.ClassNode
 import java.io.BufferedReader
 import java.io.FileReader
@@ -62,7 +63,7 @@
         filename: String,
         classes: ClassNodes,
         fallback: OutputFilter,
-        ): Pair<OutputFilter, Remapper?> {
+        ): OutputFilter {
     log.i("Loading offloaded annotations from $filename ...")
     log.withIndent {
         val subclassFilter = SubclassFilter(classes, fallback)
@@ -75,7 +76,9 @@
         var featureFlagsPolicy: FilterPolicyWithReason? = null
         var syspropsPolicy: FilterPolicyWithReason? = null
         var rFilePolicy: FilterPolicyWithReason? = null
-        val typeRenameSpec = mutableListOf<TextFilePolicyRemapper.TypeRenameSpec>()
+        val typeRenameSpec = mutableListOf<TextFilePolicyRemapperFilter.TypeRenameSpec>()
+        val methodReplaceSpec =
+            mutableListOf<TextFilePolicyMethodReplaceFilter.MethodCallReplaceSpec>()
 
         try {
             BufferedReader(FileReader(filename)).use { reader ->
@@ -250,8 +253,24 @@
                                         policy.getSubstitutionBasePolicy()
                                                 .withReason(FILTER_REASON))
 
-                                // Keep "from" -> "to" mapping.
-                                imf.setRenameTo(className, fromName, signature, name)
+                                val classAndMethod = splitWithLastPeriod(fromName)
+                                if (classAndMethod != null) {
+                                    // If the substitution target contains a ".", then
+                                    // it's a method call redirect.
+                                    methodReplaceSpec.add(
+                                        TextFilePolicyMethodReplaceFilter.MethodCallReplaceSpec(
+                                            className.toJvmClassName(),
+                                            name,
+                                            signature,
+                                            classAndMethod.first.toJvmClassName(),
+                                            classAndMethod.second,
+                                        )
+                                    )
+                                } else {
+                                    // It's an in-class replace.
+                                    // ("@RavenwoodReplace" equivalent)
+                                    imf.setRenameTo(className, fromName, signature, name)
+                                }
                             }
                         }
                         "r", "rename" -> {
@@ -267,7 +286,7 @@
                             // applied. (Which is needed for services.jar)
                             val prefix = fields[2].trimStart('/')
 
-                            typeRenameSpec += TextFilePolicyRemapper.TypeRenameSpec(
+                            typeRenameSpec += TextFilePolicyRemapperFilter.TypeRenameSpec(
                                 pattern, prefix)
                         }
 
@@ -281,16 +300,19 @@
             throw e.withSourceInfo(filename, lineNo)
         }
 
-        var remapper: TextFilePolicyRemapper? = null
+        var ret: OutputFilter = imf
         if (typeRenameSpec.isNotEmpty()) {
-            remapper = TextFilePolicyRemapper(typeRenameSpec)
+            ret = TextFilePolicyRemapperFilter(typeRenameSpec, ret)
+        }
+        if (methodReplaceSpec.isNotEmpty()) {
+            ret = TextFilePolicyMethodReplaceFilter(methodReplaceSpec, classes, ret)
         }
 
         // Wrap the in-memory-filter with AHF.
-        return Pair(
-            AndroidHeuristicsFilter(
-                classes, aidlPolicy, featureFlagsPolicy, syspropsPolicy, rFilePolicy, imf),
-            remapper)
+        ret = AndroidHeuristicsFilter(
+                classes, aidlPolicy, featureFlagsPolicy, syspropsPolicy, rFilePolicy, ret)
+
+        return ret
     }
 }
 
@@ -330,6 +352,7 @@
         "r", "remove" -> FilterPolicy.Remove
         "sc", "stubclass" -> FilterPolicy.StubClass
         "kc", "keepclass" -> FilterPolicy.KeepClass
+        "i", "ignore" -> FilterPolicy.Ignore
         else -> {
             if (s.startsWith("@")) {
                 FilterPolicy.SubstituteAndStub
diff --git a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/TextFilePolicyMethodReplaceFilter.kt b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/TextFilePolicyMethodReplaceFilter.kt
new file mode 100644
index 0000000..d45f414
--- /dev/null
+++ b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/TextFilePolicyMethodReplaceFilter.kt
@@ -0,0 +1,59 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.hoststubgen.filters
+
+import com.android.hoststubgen.asm.ClassNodes
+
+/**
+ * Filter used by TextFileFilterPolicyParser for "method call relacement".
+ */
+class TextFilePolicyMethodReplaceFilter(
+    val spec: List<MethodCallReplaceSpec>,
+    val classes: ClassNodes,
+    val fallback: OutputFilter,
+) : DelegatingFilter(fallback) {
+
+    data class MethodCallReplaceSpec(
+        val fromClass: String,
+        val fromMethod: String,
+        val fromDescriptor: String,
+        val toClass: String,
+        val toMethod: String,
+    )
+
+    override fun hasAnyMethodCallReplace(): Boolean {
+        return true
+    }
+
+    override fun getMethodCallReplaceTo(
+        callerClassName: String,
+        callerMethodName: String,
+        className: String,
+        methodName: String,
+        descriptor: String,
+    ): MethodReplaceTarget? {
+        // Maybe use 'Tri' if we end up having too many replacements.
+        spec.forEach {
+            if (className == it.fromClass &&
+                methodName == it.fromMethod &&
+                descriptor == it.fromDescriptor
+                ) {
+                return MethodReplaceTarget(it.toClass, it.toMethod)
+            }
+        }
+        return null
+    }
+}
diff --git a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/TextFilePolicyRemapper.kt b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/TextFilePolicyRemapper.kt
deleted file mode 100644
index 2d94bb4..0000000
--- a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/TextFilePolicyRemapper.kt
+++ /dev/null
@@ -1,59 +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.hoststubgen.filters
-
-import com.android.hoststubgen.log
-import org.objectweb.asm.commons.Remapper
-import java.util.regex.Pattern
-
-/**
- * A [Remapper] that provides a simple "jarjar" functionality.
- */
-class TextFilePolicyRemapper(
-    val typeRenameSpecs: List<TypeRenameSpec>
-) : Remapper() {
-    /**
-     * When a package name matches [typeInternalNamePattern], we prepend [typeInternalNamePrefix]
-     * to it.
-     */
-    data class TypeRenameSpec(
-        val typeInternalNamePattern: Pattern,
-        val typeInternalNamePrefix: String,
-    )
-
-    private val cache = mutableMapOf<String, String>()
-
-    override fun mapType(typeInternalName: String): String {
-//        if (typeInternalName == null) {
-//            return null // do we need it??
-//        }
-        cache[typeInternalName]?.let {
-            return it
-        }
-
-        var mapped: String = typeInternalName
-        typeRenameSpecs.forEach {
-            if (it.typeInternalNamePattern.matcher(typeInternalName).matches()) {
-                mapped = it.typeInternalNamePrefix + typeInternalName
-                log.d("Renaming type $typeInternalName to $mapped")
-            }
-        }
-        cache[typeInternalName] = mapped
-        return mapped
-    }
-
-    // TODO Do we need to implement mapPackage(), etc too?
-}
diff --git a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/TextFilePolicyRemapperFilter.kt b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/TextFilePolicyRemapperFilter.kt
new file mode 100644
index 0000000..a78c655
--- /dev/null
+++ b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/filters/TextFilePolicyRemapperFilter.kt
@@ -0,0 +1,50 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.hoststubgen.filters
+
+import com.android.hoststubgen.log
+import java.util.regex.Pattern
+
+/**
+ * A filter that provides a simple "jarjar" functionality via [mapType]
+ */
+class TextFilePolicyRemapperFilter(
+    val typeRenameSpecs: List<TypeRenameSpec>,
+    fallback: OutputFilter,
+) : DelegatingFilter(fallback) {
+    /**
+     * When a package name matches [typeInternalNamePattern], we prepend [typeInternalNamePrefix]
+     * to it.
+     */
+    data class TypeRenameSpec(
+        val typeInternalNamePattern: Pattern,
+        val typeInternalNamePrefix: String,
+    )
+
+    private val cache = mutableMapOf<String, String>()
+
+    override fun remapType(className: String): String? {
+        var mapped: String = className
+        typeRenameSpecs.forEach {
+            if (it.typeInternalNamePattern.matcher(className).matches()) {
+                mapped = it.typeInternalNamePrefix + className
+                log.d("Renaming type $className to $mapped")
+            }
+        }
+        cache[className] = mapped
+        return mapped
+    }
+}
diff --git a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/visitors/ImplGeneratingAdapter.kt b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/visitors/ImplGeneratingAdapter.kt
index 416b782..3d2e142 100644
--- a/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/visitors/ImplGeneratingAdapter.kt
+++ b/tools/hoststubgen/hoststubgen/src/com/android/hoststubgen/visitors/ImplGeneratingAdapter.kt
@@ -33,6 +33,9 @@
 import org.objectweb.asm.ClassVisitor
 import org.objectweb.asm.MethodVisitor
 import org.objectweb.asm.Opcodes
+import org.objectweb.asm.Opcodes.INVOKEINTERFACE
+import org.objectweb.asm.Opcodes.INVOKESTATIC
+import org.objectweb.asm.Opcodes.INVOKEVIRTUAL
 import org.objectweb.asm.Type
 
 /**
@@ -211,17 +214,14 @@
             }
 
             if (policy.policy == FilterPolicy.Ignore) {
-                when (Type.getReturnType(descriptor)) {
-                    Type.VOID_TYPE -> {
-                        log.v("Making method ignored...")
-                        return IgnoreMethodAdapter(
-                                access, name, descriptor, signature, exceptions, innerVisitor)
-                            .withAnnotation(HostStubGenProcessedAsIgnore.CLASS_DESCRIPTOR)
-                    }
-                    else -> {
-                        throw RuntimeException("Ignored policy only allowed for void methods")
-                    }
-                }
+                log.v("Making method ignored...")
+                return IgnoreMethodAdapter(
+                    access, name, descriptor, signature, exceptions, innerVisitor)
+                    .withAnnotation(HostStubGenProcessedAsIgnore.CLASS_DESCRIPTOR)
+            }
+            if (filter.hasAnyMethodCallReplace()) {
+                innerVisitor = MethodCallReplacingAdapter(
+                    access, name, descriptor, signature, exceptions, innerVisitor)
             }
         }
         if (substituted) {
@@ -290,14 +290,37 @@
      */
     private inner class IgnoreMethodAdapter(
             access: Int,
-            val name: String,
-            descriptor: String,
+            name: String,
+            val descriptor: String,
             signature: String?,
             exceptions: Array<String>?,
             next: MethodVisitor?
     ) : BodyReplacingMethodVisitor(access, name, descriptor, signature, exceptions, next) {
         override fun emitNewCode() {
-            visitInsn(Opcodes.RETURN)
+            when (Type.getReturnType(descriptor)) {
+                Type.VOID_TYPE -> visitInsn(Opcodes.RETURN)
+                Type.BOOLEAN_TYPE, Type.BYTE_TYPE, Type.CHAR_TYPE, Type.SHORT_TYPE,
+                Type.INT_TYPE -> {
+                    visitInsn(Opcodes.ICONST_0)
+                    visitInsn(Opcodes.IRETURN)
+                }
+                Type.LONG_TYPE -> {
+                    visitInsn(Opcodes.LCONST_0)
+                    visitInsn(Opcodes.LRETURN)
+                }
+                Type.FLOAT_TYPE -> {
+                    visitInsn(Opcodes.FCONST_0)
+                    visitInsn(Opcodes.FRETURN)
+                }
+                Type.DOUBLE_TYPE -> {
+                    visitInsn(Opcodes.DCONST_0)
+                    visitInsn(Opcodes.DRETURN)
+                }
+                else -> {
+                    visitInsn(Opcodes.ACONST_NULL)
+                    visitInsn(Opcodes.ARETURN)
+                }
+            }
             visitMaxs(0, 0) // We let ASM figure them out.
         }
     }
@@ -332,11 +355,9 @@
 
                 // Update the descriptor -- add this class's type as the first argument
                 // to the method descriptor.
-                val thisType = Type.getType("L" + currentClassName + ";")
-
                 targetDescriptor = prependArgTypeToMethodDescriptor(
-                        descriptor,
-                        thisType,
+                    descriptor,
+                    currentClassName,
                 )
 
                 // Shift the original arguments by one.
@@ -451,4 +472,61 @@
                     false)
         }
     }
+
+    private inner class MethodCallReplacingAdapter(
+        access: Int,
+        val callerMethodName: String,
+        val descriptor: String,
+        signature: String?,
+        exceptions: Array<String>?,
+        next: MethodVisitor?,
+    ) : MethodVisitor(OPCODE_VERSION, next) {
+        override fun visitMethodInsn(
+            opcode: Int,
+            owner: String?,
+            name: String?,
+            descriptor: String?,
+            isInterface: Boolean,
+        ) {
+            when (opcode) {
+                INVOKESTATIC, INVOKEVIRTUAL, INVOKEINTERFACE -> {}
+                else -> {
+                    // Don't touch other opcodes.
+                    super.visitMethodInsn(opcode, owner, name, descriptor, isInterface)
+                    return
+                }
+            }
+            val to = filter.getMethodCallReplaceTo(
+                currentClassName, callerMethodName, owner!!, name!!, descriptor!!)
+
+            if (to == null
+                // Don't replace if the target is the callsite.
+                || (to.className == currentClassName && to.methodName == callerMethodName)
+            ) {
+                super.visitMethodInsn(opcode, owner, name, descriptor, isInterface)
+                return
+            }
+
+            // Replace the method call with a (static) call to the target method.
+            // If it's a non-static call, the target method's first argument will receive "this".
+            // (Because of that, we don't need to manipulate the stack. Just replace the
+            // method call.)
+
+            val toDesc = if (opcode == INVOKESTATIC) {
+                // Static call to static call, no need to change the desc.
+                descriptor
+            } else {
+                // Need to prepend the "this" type to the descriptor.
+                prependArgTypeToMethodDescriptor(descriptor, owner)
+            }
+
+            mv.visitMethodInsn(
+                INVOKESTATIC,
+                to.className,
+                to.methodName,
+                toDesc,
+                false
+            )
+        }
+    }
 }
diff --git a/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/01-hoststubgen-test-tiny-framework-orig-dump.txt b/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/01-hoststubgen-test-tiny-framework-orig-dump.txt
index c127e67..c2f593c 100644
--- a/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/01-hoststubgen-test-tiny-framework-orig-dump.txt
+++ b/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/01-hoststubgen-test-tiny-framework-orig-dump.txt
@@ -1436,7 +1436,7 @@
   flags: (0x0021) ACC_PUBLIC, ACC_SUPER
   this_class: #x                          // com/android/hoststubgen/test/tinyframework/TinyFrameworkForTextPolicy
   super_class: #x                         // java/lang/Object
-  interfaces: 0, fields: 3, methods: 10, attributes: 1
+  interfaces: 0, fields: 3, methods: 19, attributes: 1
   public int stub;
     descriptor: I
     flags: (0x0001) ACC_PUBLIC
@@ -1513,6 +1513,132 @@
             0       8     0  this   Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkForTextPolicy;
             0       8     1   foo   Ljava/lang/String;
 
+  public java.lang.String toBeIgnoredObj();
+    descriptor: ()Ljava/lang/String;
+    flags: (0x0001) ACC_PUBLIC
+    Code:
+      stack=2, locals=1, args_size=1
+         x: new           #x                 // class java/lang/RuntimeException
+         x: dup
+         x: invokespecial #x                 // Method java/lang/RuntimeException."<init>":()V
+         x: athrow
+      LineNumberTable:
+      LocalVariableTable:
+        Start  Length  Slot  Name   Signature
+            0       8     0  this   Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkForTextPolicy;
+
+  public void toBeIgnoredV();
+    descriptor: ()V
+    flags: (0x0001) ACC_PUBLIC
+    Code:
+      stack=2, locals=1, args_size=1
+         x: new           #x                 // class java/lang/RuntimeException
+         x: dup
+         x: invokespecial #x                 // Method java/lang/RuntimeException."<init>":()V
+         x: athrow
+      LineNumberTable:
+      LocalVariableTable:
+        Start  Length  Slot  Name   Signature
+            0       8     0  this   Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkForTextPolicy;
+
+  public boolean toBeIgnoredZ();
+    descriptor: ()Z
+    flags: (0x0001) ACC_PUBLIC
+    Code:
+      stack=2, locals=1, args_size=1
+         x: new           #x                 // class java/lang/RuntimeException
+         x: dup
+         x: invokespecial #x                 // Method java/lang/RuntimeException."<init>":()V
+         x: athrow
+      LineNumberTable:
+      LocalVariableTable:
+        Start  Length  Slot  Name   Signature
+            0       8     0  this   Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkForTextPolicy;
+
+  public byte toBeIgnoredB();
+    descriptor: ()B
+    flags: (0x0001) ACC_PUBLIC
+    Code:
+      stack=2, locals=1, args_size=1
+         x: new           #x                 // class java/lang/RuntimeException
+         x: dup
+         x: invokespecial #x                 // Method java/lang/RuntimeException."<init>":()V
+         x: athrow
+      LineNumberTable:
+      LocalVariableTable:
+        Start  Length  Slot  Name   Signature
+            0       8     0  this   Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkForTextPolicy;
+
+  public char toBeIgnoredC();
+    descriptor: ()C
+    flags: (0x0001) ACC_PUBLIC
+    Code:
+      stack=2, locals=1, args_size=1
+         x: new           #x                 // class java/lang/RuntimeException
+         x: dup
+         x: invokespecial #x                 // Method java/lang/RuntimeException."<init>":()V
+         x: athrow
+      LineNumberTable:
+      LocalVariableTable:
+        Start  Length  Slot  Name   Signature
+            0       8     0  this   Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkForTextPolicy;
+
+  public short toBeIgnoredS();
+    descriptor: ()S
+    flags: (0x0001) ACC_PUBLIC
+    Code:
+      stack=2, locals=1, args_size=1
+         x: new           #x                 // class java/lang/RuntimeException
+         x: dup
+         x: invokespecial #x                 // Method java/lang/RuntimeException."<init>":()V
+         x: athrow
+      LineNumberTable:
+      LocalVariableTable:
+        Start  Length  Slot  Name   Signature
+            0       8     0  this   Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkForTextPolicy;
+
+  public int toBeIgnoredI();
+    descriptor: ()I
+    flags: (0x0001) ACC_PUBLIC
+    Code:
+      stack=2, locals=1, args_size=1
+         x: new           #x                 // class java/lang/RuntimeException
+         x: dup
+         x: invokespecial #x                 // Method java/lang/RuntimeException."<init>":()V
+         x: athrow
+      LineNumberTable:
+      LocalVariableTable:
+        Start  Length  Slot  Name   Signature
+            0       8     0  this   Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkForTextPolicy;
+
+  public float toBeIgnoredF();
+    descriptor: ()F
+    flags: (0x0001) ACC_PUBLIC
+    Code:
+      stack=2, locals=1, args_size=1
+         x: new           #x                 // class java/lang/RuntimeException
+         x: dup
+         x: invokespecial #x                 // Method java/lang/RuntimeException."<init>":()V
+         x: athrow
+      LineNumberTable:
+      LocalVariableTable:
+        Start  Length  Slot  Name   Signature
+            0       8     0  this   Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkForTextPolicy;
+
+  public double toBeIgnoredD();
+    descriptor: ()D
+    flags: (0x0001) ACC_PUBLIC
+    Code:
+      stack=2, locals=1, args_size=1
+         x: new           #x                 // class java/lang/RuntimeException
+         x: dup
+         x: invokespecial #x                 // Method java/lang/RuntimeException."<init>":()V
+         x: athrow
+      LineNumberTable:
+      LocalVariableTable:
+        Start  Length  Slot  Name   Signature
+            0       8     0  this   Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkForTextPolicy;
+
   public int addTwo(int);
     descriptor: (I)I
     flags: (0x0001) ACC_PUBLIC
@@ -1897,6 +2023,174 @@
 InnerClasses:
   public static #x= #x of #x;          // Nested=class com/android/hoststubgen/test/tinyframework/TinyFrameworkLambdas$Nested of class com/android/hoststubgen/test/tinyframework/TinyFrameworkLambdas
   public static final #x= #x of #x;    // Lookup=class java/lang/invoke/MethodHandles$Lookup of class java/lang/invoke/MethodHandles
+## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkMethodCallReplace$ReplaceTo.class
+  Compiled from "TinyFrameworkMethodCallReplace.java"
+public class com.android.hoststubgen.test.tinyframework.TinyFrameworkMethodCallReplace$ReplaceTo
+  minor version: 0
+  major version: 61
+  flags: (0x0021) ACC_PUBLIC, ACC_SUPER
+  this_class: #x                         // com/android/hoststubgen/test/tinyframework/TinyFrameworkMethodCallReplace$ReplaceTo
+  super_class: #x                         // java/lang/Object
+  interfaces: 0, fields: 0, methods: 3, attributes: 3
+  public com.android.hoststubgen.test.tinyframework.TinyFrameworkMethodCallReplace$ReplaceTo();
+    descriptor: ()V
+    flags: (0x0001) ACC_PUBLIC
+    Code:
+      stack=1, locals=1, args_size=1
+         x: aload_0
+         x: invokespecial #x                  // Method java/lang/Object."<init>":()V
+         x: return
+      LineNumberTable:
+      LocalVariableTable:
+        Start  Length  Slot  Name   Signature
+            0       5     0  this   Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkMethodCallReplace$ReplaceTo;
+
+  public static void startThread(java.lang.Thread);
+    descriptor: (Ljava/lang/Thread;)V
+    flags: (0x0009) ACC_PUBLIC, ACC_STATIC
+    Code:
+      stack=2, locals=1, args_size=1
+         x: aload_0
+         x: iconst_1
+         x: invokevirtual #x                  // Method java/lang/Thread.setDaemon:(Z)V
+         x: aload_0
+         x: invokevirtual #x                 // Method java/lang/Thread.start:()V
+         x: return
+      LineNumberTable:
+      LocalVariableTable:
+        Start  Length  Slot  Name   Signature
+            0      10     0 thread   Ljava/lang/Thread;
+
+  public static int add(int, int);
+    descriptor: (II)I
+    flags: (0x0009) ACC_PUBLIC, ACC_STATIC
+    Code:
+      stack=2, locals=2, args_size=2
+         x: iload_0
+         x: iload_1
+         x: iadd
+         x: ireturn
+      LineNumberTable:
+      LocalVariableTable:
+        Start  Length  Slot  Name   Signature
+            0       4     0     a   I
+            0       4     1     b   I
+}
+SourceFile: "TinyFrameworkMethodCallReplace.java"
+NestHost: class com/android/hoststubgen/test/tinyframework/TinyFrameworkMethodCallReplace
+InnerClasses:
+  public static #x= #x of #x;          // ReplaceTo=class com/android/hoststubgen/test/tinyframework/TinyFrameworkMethodCallReplace$ReplaceTo of class com/android/hoststubgen/test/tinyframework/TinyFrameworkMethodCallReplace
+## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkMethodCallReplace.class
+  Compiled from "TinyFrameworkMethodCallReplace.java"
+public class com.android.hoststubgen.test.tinyframework.TinyFrameworkMethodCallReplace
+  minor version: 0
+  major version: 61
+  flags: (0x0021) ACC_PUBLIC, ACC_SUPER
+  this_class: #x                         // com/android/hoststubgen/test/tinyframework/TinyFrameworkMethodCallReplace
+  super_class: #x                         // java/lang/Object
+  interfaces: 0, fields: 0, methods: 5, attributes: 5
+  public com.android.hoststubgen.test.tinyframework.TinyFrameworkMethodCallReplace();
+    descriptor: ()V
+    flags: (0x0001) ACC_PUBLIC
+    Code:
+      stack=1, locals=1, args_size=1
+         x: aload_0
+         x: invokespecial #x                  // Method java/lang/Object."<init>":()V
+         x: return
+      LineNumberTable:
+      LocalVariableTable:
+        Start  Length  Slot  Name   Signature
+            0       5     0  this   Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkMethodCallReplace;
+
+  public static boolean nonStaticMethodCallReplaceTester() throws java.lang.Exception;
+    descriptor: ()Z
+    flags: (0x0009) ACC_PUBLIC, ACC_STATIC
+    Code:
+      stack=3, locals=2, args_size=0
+         x: new           #x                  // class java/util/concurrent/atomic/AtomicBoolean
+         x: dup
+         x: iconst_0
+         x: invokespecial #x                  // Method java/util/concurrent/atomic/AtomicBoolean."<init>":(Z)V
+         x: astore_0
+         x: new           #x                 // class java/lang/Thread
+        x: dup
+        x: aload_0
+        x: invokedynamic #x,  0             // InvokeDynamic #x:run:(Ljava/util/concurrent/atomic/AtomicBoolean;)Ljava/lang/Runnable;
+        x: invokespecial #x                 // Method java/lang/Thread."<init>":(Ljava/lang/Runnable;)V
+        x: astore_1
+        x: aload_1
+        x: invokevirtual #x                 // Method java/lang/Thread.start:()V
+        x: aload_1
+        x: invokevirtual #x                 // Method java/lang/Thread.join:()V
+        x: aload_0
+        x: invokevirtual #x                 // Method java/util/concurrent/atomic/AtomicBoolean.get:()Z
+        x: ireturn
+      LineNumberTable:
+      LocalVariableTable:
+        Start  Length  Slot  Name   Signature
+            9      27     0    ab   Ljava/util/concurrent/atomic/AtomicBoolean;
+           23      13     1    th   Ljava/lang/Thread;
+    Exceptions:
+      throws java.lang.Exception
+
+  public static int staticMethodCallReplaceTester();
+    descriptor: ()I
+    flags: (0x0009) ACC_PUBLIC, ACC_STATIC
+    Code:
+      stack=2, locals=0, args_size=0
+         x: iconst_1
+         x: iconst_2
+         x: invokestatic  #x                 // Method originalAdd:(II)I
+         x: ireturn
+      LineNumberTable:
+
+  private static int originalAdd(int, int);
+    descriptor: (II)I
+    flags: (0x000a) ACC_PRIVATE, ACC_STATIC
+    Code:
+      stack=2, locals=2, args_size=2
+         x: iload_0
+         x: iload_1
+         x: iadd
+         x: iconst_1
+         x: isub
+         x: ireturn
+      LineNumberTable:
+      LocalVariableTable:
+        Start  Length  Slot  Name   Signature
+            0       6     0     a   I
+            0       6     1     b   I
+
+  private static void lambda$nonStaticMethodCallReplaceTester$0(java.util.concurrent.atomic.AtomicBoolean);
+    descriptor: (Ljava/util/concurrent/atomic/AtomicBoolean;)V
+    flags: (0x100a) ACC_PRIVATE, ACC_STATIC, ACC_SYNTHETIC
+    Code:
+      stack=2, locals=1, args_size=1
+         x: aload_0
+         x: invokestatic  #x                 // Method java/lang/Thread.currentThread:()Ljava/lang/Thread;
+         x: invokevirtual #x                 // Method java/lang/Thread.isDaemon:()Z
+         x: invokevirtual #x                 // Method java/util/concurrent/atomic/AtomicBoolean.set:(Z)V
+        x: return
+      LineNumberTable:
+      LocalVariableTable:
+        Start  Length  Slot  Name   Signature
+            0      11     0    ab   Ljava/util/concurrent/atomic/AtomicBoolean;
+}
+SourceFile: "TinyFrameworkMethodCallReplace.java"
+RuntimeInvisibleAnnotations:
+  x: #x()
+    android.hosttest.annotation.HostSideTestWholeClassStub
+NestMembers:
+  com/android/hoststubgen/test/tinyframework/TinyFrameworkMethodCallReplace$ReplaceTo
+BootstrapMethods:
+  x: #x REF_invokeStatic java/lang/invoke/LambdaMetafactory.metafactory:(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodHandle;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/CallSite;
+    Method arguments:
+      #x ()V
+      #x REF_invokeStatic com/android/hoststubgen/test/tinyframework/TinyFrameworkMethodCallReplace.lambda$nonStaticMethodCallReplaceTester$0:(Ljava/util/concurrent/atomic/AtomicBoolean;)V
+      #x ()V
+InnerClasses:
+  public static #x= #x of #x;          // ReplaceTo=class com/android/hoststubgen/test/tinyframework/TinyFrameworkMethodCallReplace$ReplaceTo of class com/android/hoststubgen/test/tinyframework/TinyFrameworkMethodCallReplace
+  public static final #x= #x of #x;    // Lookup=class java/lang/invoke/MethodHandles$Lookup of class java/lang/invoke/MethodHandles
 ## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkNative.class
   Compiled from "TinyFrameworkNative.java"
 public class com.android.hoststubgen.test.tinyframework.TinyFrameworkNative
diff --git a/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/02-hoststubgen-test-tiny-framework-host-stub-dump.txt b/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/02-hoststubgen-test-tiny-framework-host-stub-dump.txt
index 17ba48c..1b83d24 100644
--- a/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/02-hoststubgen-test-tiny-framework-host-stub-dump.txt
+++ b/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/02-hoststubgen-test-tiny-framework-host-stub-dump.txt
@@ -1197,7 +1197,7 @@
   flags: (0x0021) ACC_PUBLIC, ACC_SUPER
   this_class: #x                          // com/android/hoststubgen/test/tinyframework/TinyFrameworkForTextPolicy
   super_class: #x                         // java/lang/Object
-  interfaces: 0, fields: 1, methods: 5, attributes: 2
+  interfaces: 0, fields: 1, methods: 14, attributes: 2
   public int stub;
     descriptor: I
     flags: (0x0001) ACC_PUBLIC
@@ -1239,6 +1239,150 @@
       x: #x()
         com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
 
+  public java.lang.String toBeIgnoredObj();
+    descriptor: ()Ljava/lang/String;
+    flags: (0x0001) ACC_PUBLIC
+    Code:
+      stack=3, locals=1, args_size=1
+         x: new           #x                 // class java/lang/RuntimeException
+         x: dup
+         x: ldc           #x                 // String Stub!
+         x: invokespecial #x                 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
+         x: athrow
+    RuntimeVisibleAnnotations:
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+
+  public void toBeIgnoredV();
+    descriptor: ()V
+    flags: (0x0001) ACC_PUBLIC
+    Code:
+      stack=3, locals=1, args_size=1
+         x: new           #x                 // class java/lang/RuntimeException
+         x: dup
+         x: ldc           #x                 // String Stub!
+         x: invokespecial #x                 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
+         x: athrow
+    RuntimeVisibleAnnotations:
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+
+  public boolean toBeIgnoredZ();
+    descriptor: ()Z
+    flags: (0x0001) ACC_PUBLIC
+    Code:
+      stack=3, locals=1, args_size=1
+         x: new           #x                 // class java/lang/RuntimeException
+         x: dup
+         x: ldc           #x                 // String Stub!
+         x: invokespecial #x                 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
+         x: athrow
+    RuntimeVisibleAnnotations:
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+
+  public byte toBeIgnoredB();
+    descriptor: ()B
+    flags: (0x0001) ACC_PUBLIC
+    Code:
+      stack=3, locals=1, args_size=1
+         x: new           #x                 // class java/lang/RuntimeException
+         x: dup
+         x: ldc           #x                 // String Stub!
+         x: invokespecial #x                 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
+         x: athrow
+    RuntimeVisibleAnnotations:
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+
+  public char toBeIgnoredC();
+    descriptor: ()C
+    flags: (0x0001) ACC_PUBLIC
+    Code:
+      stack=3, locals=1, args_size=1
+         x: new           #x                 // class java/lang/RuntimeException
+         x: dup
+         x: ldc           #x                 // String Stub!
+         x: invokespecial #x                 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
+         x: athrow
+    RuntimeVisibleAnnotations:
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+
+  public short toBeIgnoredS();
+    descriptor: ()S
+    flags: (0x0001) ACC_PUBLIC
+    Code:
+      stack=3, locals=1, args_size=1
+         x: new           #x                 // class java/lang/RuntimeException
+         x: dup
+         x: ldc           #x                 // String Stub!
+         x: invokespecial #x                 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
+         x: athrow
+    RuntimeVisibleAnnotations:
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+
+  public int toBeIgnoredI();
+    descriptor: ()I
+    flags: (0x0001) ACC_PUBLIC
+    Code:
+      stack=3, locals=1, args_size=1
+         x: new           #x                 // class java/lang/RuntimeException
+         x: dup
+         x: ldc           #x                 // String Stub!
+         x: invokespecial #x                 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
+         x: athrow
+    RuntimeVisibleAnnotations:
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+
+  public float toBeIgnoredF();
+    descriptor: ()F
+    flags: (0x0001) ACC_PUBLIC
+    Code:
+      stack=3, locals=1, args_size=1
+         x: new           #x                 // class java/lang/RuntimeException
+         x: dup
+         x: ldc           #x                 // String Stub!
+         x: invokespecial #x                 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
+         x: athrow
+    RuntimeVisibleAnnotations:
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+
+  public double toBeIgnoredD();
+    descriptor: ()D
+    flags: (0x0001) ACC_PUBLIC
+    Code:
+      stack=3, locals=1, args_size=1
+         x: new           #x                 // class java/lang/RuntimeException
+         x: dup
+         x: ldc           #x                 // String Stub!
+         x: invokespecial #x                 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
+         x: athrow
+    RuntimeVisibleAnnotations:
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+
   public int addTwo(int);
     descriptor: (I)I
     flags: (0x0001) ACC_PUBLIC
@@ -1640,6 +1784,161 @@
     android.hosttest.annotation.HostSideTestStaticInitializerKeep
 NestMembers:
   com/android/hoststubgen/test/tinyframework/TinyFrameworkLambdas$Nested
+## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkMethodCallReplace$ReplaceTo.class
+  Compiled from "TinyFrameworkMethodCallReplace.java"
+public class com.android.hoststubgen.test.tinyframework.TinyFrameworkMethodCallReplace$ReplaceTo
+  minor version: 0
+  major version: 61
+  flags: (0x0021) ACC_PUBLIC, ACC_SUPER
+  this_class: #x                          // com/android/hoststubgen/test/tinyframework/TinyFrameworkMethodCallReplace$ReplaceTo
+  super_class: #x                         // java/lang/Object
+  interfaces: 0, fields: 0, methods: 3, attributes: 4
+  public com.android.hoststubgen.test.tinyframework.TinyFrameworkMethodCallReplace$ReplaceTo();
+    descriptor: ()V
+    flags: (0x0001) ACC_PUBLIC
+    Code:
+      stack=3, locals=1, args_size=1
+         x: new           #x                 // class java/lang/RuntimeException
+         x: dup
+         x: ldc           #x                 // String Stub!
+         x: invokespecial #x                 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
+         x: athrow
+    RuntimeVisibleAnnotations:
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+
+  public static void startThread(java.lang.Thread);
+    descriptor: (Ljava/lang/Thread;)V
+    flags: (0x0009) ACC_PUBLIC, ACC_STATIC
+    Code:
+      stack=3, locals=1, args_size=1
+         x: new           #x                 // class java/lang/RuntimeException
+         x: dup
+         x: ldc           #x                 // String Stub!
+         x: invokespecial #x                 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
+         x: athrow
+    RuntimeVisibleAnnotations:
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+
+  public static int add(int, int);
+    descriptor: (II)I
+    flags: (0x0009) ACC_PUBLIC, ACC_STATIC
+    Code:
+      stack=3, locals=2, args_size=2
+         x: new           #x                 // class java/lang/RuntimeException
+         x: dup
+         x: ldc           #x                 // String Stub!
+         x: invokespecial #x                 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
+         x: athrow
+    RuntimeVisibleAnnotations:
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+}
+InnerClasses:
+  public static #x= #x of #x;            // ReplaceTo=class com/android/hoststubgen/test/tinyframework/TinyFrameworkMethodCallReplace$ReplaceTo of class com/android/hoststubgen/test/tinyframework/TinyFrameworkMethodCallReplace
+SourceFile: "TinyFrameworkMethodCallReplace.java"
+RuntimeVisibleAnnotations:
+  x: #x()
+    com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
+  x: #x()
+    com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+NestHost: class com/android/hoststubgen/test/tinyframework/TinyFrameworkMethodCallReplace
+## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkMethodCallReplace.class
+  Compiled from "TinyFrameworkMethodCallReplace.java"
+public class com.android.hoststubgen.test.tinyframework.TinyFrameworkMethodCallReplace
+  minor version: 0
+  major version: 61
+  flags: (0x0021) ACC_PUBLIC, ACC_SUPER
+  this_class: #x                          // com/android/hoststubgen/test/tinyframework/TinyFrameworkMethodCallReplace
+  super_class: #x                         // java/lang/Object
+  interfaces: 0, fields: 0, methods: 4, attributes: 5
+  public com.android.hoststubgen.test.tinyframework.TinyFrameworkMethodCallReplace();
+    descriptor: ()V
+    flags: (0x0001) ACC_PUBLIC
+    Code:
+      stack=3, locals=1, args_size=1
+         x: new           #x                 // class java/lang/RuntimeException
+         x: dup
+         x: ldc           #x                 // String Stub!
+         x: invokespecial #x                 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
+         x: athrow
+    RuntimeVisibleAnnotations:
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+
+  public static boolean nonStaticMethodCallReplaceTester() throws java.lang.Exception;
+    descriptor: ()Z
+    flags: (0x0009) ACC_PUBLIC, ACC_STATIC
+    Code:
+      stack=3, locals=0, args_size=0
+         x: new           #x                 // class java/lang/RuntimeException
+         x: dup
+         x: ldc           #x                 // String Stub!
+         x: invokespecial #x                 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
+         x: athrow
+    Exceptions:
+      throws java.lang.Exception
+    RuntimeVisibleAnnotations:
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+
+  public static int staticMethodCallReplaceTester();
+    descriptor: ()I
+    flags: (0x0009) ACC_PUBLIC, ACC_STATIC
+    Code:
+      stack=3, locals=0, args_size=0
+         x: new           #x                 // class java/lang/RuntimeException
+         x: dup
+         x: ldc           #x                 // String Stub!
+         x: invokespecial #x                 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
+         x: athrow
+    RuntimeVisibleAnnotations:
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+
+  private static void lambda$nonStaticMethodCallReplaceTester$0(java.util.concurrent.atomic.AtomicBoolean);
+    descriptor: (Ljava/util/concurrent/atomic/AtomicBoolean;)V
+    flags: (0x100a) ACC_PRIVATE, ACC_STATIC, ACC_SYNTHETIC
+    Code:
+      stack=3, locals=1, args_size=1
+         x: new           #x                 // class java/lang/RuntimeException
+         x: dup
+         x: ldc           #x                 // String Stub!
+         x: invokespecial #x                 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
+         x: athrow
+    RuntimeVisibleAnnotations:
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+}
+InnerClasses:
+  public static #x= #x of #x;           // ReplaceTo=class com/android/hoststubgen/test/tinyframework/TinyFrameworkMethodCallReplace$ReplaceTo of class com/android/hoststubgen/test/tinyframework/TinyFrameworkMethodCallReplace
+  public static final #x= #x of #x;    // Lookup=class java/lang/invoke/MethodHandles$Lookup of class java/lang/invoke/MethodHandles
+SourceFile: "TinyFrameworkMethodCallReplace.java"
+RuntimeVisibleAnnotations:
+  x: #x()
+    com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
+  x: #x()
+    com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+RuntimeInvisibleAnnotations:
+  x: #x()
+    android.hosttest.annotation.HostSideTestWholeClassStub
+NestMembers:
+  com/android/hoststubgen/test/tinyframework/TinyFrameworkMethodCallReplace$ReplaceTo
 ## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkNative.class
   Compiled from "TinyFrameworkNative.java"
 public class com.android.hoststubgen.test.tinyframework.TinyFrameworkNative
diff --git a/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/03-hoststubgen-test-tiny-framework-host-impl-dump.txt b/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/03-hoststubgen-test-tiny-framework-host-impl-dump.txt
index 0f5f7e7..d23b450 100644
--- a/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/03-hoststubgen-test-tiny-framework-host-impl-dump.txt
+++ b/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/03-hoststubgen-test-tiny-framework-host-impl-dump.txt
@@ -1730,7 +1730,7 @@
   flags: (0x0021) ACC_PUBLIC, ACC_SUPER
   this_class: #x                          // com/android/hoststubgen/test/tinyframework/TinyFrameworkForTextPolicy
   super_class: #x                         // java/lang/Object
-  interfaces: 0, fields: 2, methods: 8, attributes: 2
+  interfaces: 0, fields: 2, methods: 17, attributes: 2
   public int stub;
     descriptor: I
     flags: (0x0001) ACC_PUBLIC
@@ -1825,6 +1825,140 @@
       x: #x()
         com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
 
+  public java.lang.String toBeIgnoredObj();
+    descriptor: ()Ljava/lang/String;
+    flags: (0x0001) ACC_PUBLIC
+    Code:
+      stack=1, locals=1, args_size=1
+         x: aconst_null
+         x: areturn
+    RuntimeVisibleAnnotations:
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsIgnore
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+
+  public void toBeIgnoredV();
+    descriptor: ()V
+    flags: (0x0001) ACC_PUBLIC
+    Code:
+      stack=0, locals=1, args_size=1
+         x: return
+    RuntimeVisibleAnnotations:
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsIgnore
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+
+  public boolean toBeIgnoredZ();
+    descriptor: ()Z
+    flags: (0x0001) ACC_PUBLIC
+    Code:
+      stack=1, locals=1, args_size=1
+         x: iconst_0
+         x: ireturn
+    RuntimeVisibleAnnotations:
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsIgnore
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+
+  public byte toBeIgnoredB();
+    descriptor: ()B
+    flags: (0x0001) ACC_PUBLIC
+    Code:
+      stack=1, locals=1, args_size=1
+         x: iconst_0
+         x: ireturn
+    RuntimeVisibleAnnotations:
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsIgnore
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+
+  public char toBeIgnoredC();
+    descriptor: ()C
+    flags: (0x0001) ACC_PUBLIC
+    Code:
+      stack=1, locals=1, args_size=1
+         x: iconst_0
+         x: ireturn
+    RuntimeVisibleAnnotations:
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsIgnore
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+
+  public short toBeIgnoredS();
+    descriptor: ()S
+    flags: (0x0001) ACC_PUBLIC
+    Code:
+      stack=1, locals=1, args_size=1
+         x: iconst_0
+         x: ireturn
+    RuntimeVisibleAnnotations:
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsIgnore
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+
+  public int toBeIgnoredI();
+    descriptor: ()I
+    flags: (0x0001) ACC_PUBLIC
+    Code:
+      stack=1, locals=1, args_size=1
+         x: iconst_0
+         x: ireturn
+    RuntimeVisibleAnnotations:
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsIgnore
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+
+  public float toBeIgnoredF();
+    descriptor: ()F
+    flags: (0x0001) ACC_PUBLIC
+    Code:
+      stack=1, locals=1, args_size=1
+         x: fconst_0
+         x: freturn
+    RuntimeVisibleAnnotations:
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsIgnore
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+
+  public double toBeIgnoredD();
+    descriptor: ()D
+    flags: (0x0001) ACC_PUBLIC
+    Code:
+      stack=2, locals=1, args_size=1
+         x: dconst_0
+         x: dreturn
+    RuntimeVisibleAnnotations:
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsIgnore
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+
   public int addTwo(int);
     descriptor: (I)I
     flags: (0x0001) ACC_PUBLIC
@@ -2330,6 +2464,202 @@
       #x ()Ljava/lang/Integer;
 NestMembers:
   com/android/hoststubgen/test/tinyframework/TinyFrameworkLambdas$Nested
+## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkMethodCallReplace$ReplaceTo.class
+  Compiled from "TinyFrameworkMethodCallReplace.java"
+public class com.android.hoststubgen.test.tinyframework.TinyFrameworkMethodCallReplace$ReplaceTo
+  minor version: 0
+  major version: 61
+  flags: (0x0021) ACC_PUBLIC, ACC_SUPER
+  this_class: #x                          // com/android/hoststubgen/test/tinyframework/TinyFrameworkMethodCallReplace$ReplaceTo
+  super_class: #x                         // java/lang/Object
+  interfaces: 0, fields: 0, methods: 3, attributes: 4
+  public com.android.hoststubgen.test.tinyframework.TinyFrameworkMethodCallReplace$ReplaceTo();
+    descriptor: ()V
+    flags: (0x0001) ACC_PUBLIC
+    Code:
+      stack=1, locals=1, args_size=1
+         x: aload_0
+         x: invokespecial #x                 // Method java/lang/Object."<init>":()V
+         x: return
+      LineNumberTable:
+      LocalVariableTable:
+        Start  Length  Slot  Name   Signature
+            0       5     0  this   Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkMethodCallReplace$ReplaceTo;
+    RuntimeVisibleAnnotations:
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+
+  public static void startThread(java.lang.Thread);
+    descriptor: (Ljava/lang/Thread;)V
+    flags: (0x0009) ACC_PUBLIC, ACC_STATIC
+    Code:
+      stack=2, locals=1, args_size=1
+         x: aload_0
+         x: iconst_1
+         x: invokevirtual #x                 // Method java/lang/Thread.setDaemon:(Z)V
+         x: aload_0
+         x: invokevirtual #x                 // Method java/lang/Thread.start:()V
+         x: return
+      LineNumberTable:
+      LocalVariableTable:
+        Start  Length  Slot  Name   Signature
+            0      10     0 thread   Ljava/lang/Thread;
+    RuntimeVisibleAnnotations:
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+
+  public static int add(int, int);
+    descriptor: (II)I
+    flags: (0x0009) ACC_PUBLIC, ACC_STATIC
+    Code:
+      stack=2, locals=2, args_size=2
+         x: iload_0
+         x: iload_1
+         x: iadd
+         x: ireturn
+      LineNumberTable:
+      LocalVariableTable:
+        Start  Length  Slot  Name   Signature
+            0       4     0     a   I
+            0       4     1     b   I
+    RuntimeVisibleAnnotations:
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+}
+InnerClasses:
+  public static #x= #x of #x;            // ReplaceTo=class com/android/hoststubgen/test/tinyframework/TinyFrameworkMethodCallReplace$ReplaceTo of class com/android/hoststubgen/test/tinyframework/TinyFrameworkMethodCallReplace
+SourceFile: "TinyFrameworkMethodCallReplace.java"
+RuntimeVisibleAnnotations:
+  x: #x()
+    com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
+  x: #x()
+    com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+NestHost: class com/android/hoststubgen/test/tinyframework/TinyFrameworkMethodCallReplace
+## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkMethodCallReplace.class
+  Compiled from "TinyFrameworkMethodCallReplace.java"
+public class com.android.hoststubgen.test.tinyframework.TinyFrameworkMethodCallReplace
+  minor version: 0
+  major version: 61
+  flags: (0x0021) ACC_PUBLIC, ACC_SUPER
+  this_class: #x                          // com/android/hoststubgen/test/tinyframework/TinyFrameworkMethodCallReplace
+  super_class: #x                         // java/lang/Object
+  interfaces: 0, fields: 0, methods: 4, attributes: 6
+  public com.android.hoststubgen.test.tinyframework.TinyFrameworkMethodCallReplace();
+    descriptor: ()V
+    flags: (0x0001) ACC_PUBLIC
+    Code:
+      stack=1, locals=1, args_size=1
+         x: aload_0
+         x: invokespecial #x                 // Method java/lang/Object."<init>":()V
+         x: return
+      LineNumberTable:
+      LocalVariableTable:
+        Start  Length  Slot  Name   Signature
+            0       5     0  this   Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkMethodCallReplace;
+    RuntimeVisibleAnnotations:
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+
+  public static boolean nonStaticMethodCallReplaceTester() throws java.lang.Exception;
+    descriptor: ()Z
+    flags: (0x0009) ACC_PUBLIC, ACC_STATIC
+    Code:
+      stack=3, locals=2, args_size=0
+         x: new           #x                 // class java/util/concurrent/atomic/AtomicBoolean
+         x: dup
+         x: iconst_0
+         x: invokespecial #x                 // Method java/util/concurrent/atomic/AtomicBoolean."<init>":(Z)V
+         x: astore_0
+         x: new           #x                 // class java/lang/Thread
+        x: dup
+        x: aload_0
+        x: invokedynamic #x,  0             // InvokeDynamic #x:run:(Ljava/util/concurrent/atomic/AtomicBoolean;)Ljava/lang/Runnable;
+        x: invokespecial #x                 // Method java/lang/Thread."<init>":(Ljava/lang/Runnable;)V
+        x: astore_1
+        x: aload_1
+        x: invokestatic  #x                 // Method com/android/hoststubgen/test/tinyframework/TinyFrameworkMethodCallReplace$ReplaceTo.startThread:(Ljava/lang/Thread;)V
+        x: aload_1
+        x: invokevirtual #x                 // Method java/lang/Thread.join:()V
+        x: aload_0
+        x: invokevirtual #x                 // Method java/util/concurrent/atomic/AtomicBoolean.get:()Z
+        x: ireturn
+      LineNumberTable:
+      LocalVariableTable:
+        Start  Length  Slot  Name   Signature
+            9      27     0    ab   Ljava/util/concurrent/atomic/AtomicBoolean;
+           23      13     1    th   Ljava/lang/Thread;
+    Exceptions:
+      throws java.lang.Exception
+    RuntimeVisibleAnnotations:
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+
+  public static int staticMethodCallReplaceTester();
+    descriptor: ()I
+    flags: (0x0009) ACC_PUBLIC, ACC_STATIC
+    Code:
+      stack=2, locals=0, args_size=0
+         x: iconst_1
+         x: iconst_2
+         x: invokestatic  #x                 // Method com/android/hoststubgen/test/tinyframework/TinyFrameworkMethodCallReplace$ReplaceTo.add:(II)I
+         x: ireturn
+      LineNumberTable:
+    RuntimeVisibleAnnotations:
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+
+  private static void lambda$nonStaticMethodCallReplaceTester$0(java.util.concurrent.atomic.AtomicBoolean);
+    descriptor: (Ljava/util/concurrent/atomic/AtomicBoolean;)V
+    flags: (0x100a) ACC_PRIVATE, ACC_STATIC, ACC_SYNTHETIC
+    Code:
+      stack=2, locals=1, args_size=1
+         x: aload_0
+         x: invokestatic  #x                 // Method java/lang/Thread.currentThread:()Ljava/lang/Thread;
+         x: invokevirtual #x                 // Method java/lang/Thread.isDaemon:()Z
+         x: invokevirtual #x                 // Method java/util/concurrent/atomic/AtomicBoolean.set:(Z)V
+        x: return
+      LineNumberTable:
+      LocalVariableTable:
+        Start  Length  Slot  Name   Signature
+            0      11     0    ab   Ljava/util/concurrent/atomic/AtomicBoolean;
+    RuntimeVisibleAnnotations:
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+}
+InnerClasses:
+  public static #x= #x of #x;           // ReplaceTo=class com/android/hoststubgen/test/tinyframework/TinyFrameworkMethodCallReplace$ReplaceTo of class com/android/hoststubgen/test/tinyframework/TinyFrameworkMethodCallReplace
+  public static final #x= #x of #x;    // Lookup=class java/lang/invoke/MethodHandles$Lookup of class java/lang/invoke/MethodHandles
+SourceFile: "TinyFrameworkMethodCallReplace.java"
+RuntimeVisibleAnnotations:
+  x: #x()
+    com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
+  x: #x()
+    com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+RuntimeInvisibleAnnotations:
+  x: #x()
+    android.hosttest.annotation.HostSideTestWholeClassStub
+BootstrapMethods:
+  x: #x REF_invokeStatic java/lang/invoke/LambdaMetafactory.metafactory:(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodHandle;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/CallSite;
+    Method arguments:
+      #x ()V
+      #x REF_invokeStatic com/android/hoststubgen/test/tinyframework/TinyFrameworkMethodCallReplace.lambda$nonStaticMethodCallReplaceTester$0:(Ljava/util/concurrent/atomic/AtomicBoolean;)V
+      #x ()V
+NestMembers:
+  com/android/hoststubgen/test/tinyframework/TinyFrameworkMethodCallReplace$ReplaceTo
 ## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkNative.class
   Compiled from "TinyFrameworkNative.java"
 public class com.android.hoststubgen.test.tinyframework.TinyFrameworkNative
diff --git a/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/12-hoststubgen-test-tiny-framework-host-ext-stub-dump.txt b/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/12-hoststubgen-test-tiny-framework-host-ext-stub-dump.txt
index 17ba48c..1b83d24 100644
--- a/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/12-hoststubgen-test-tiny-framework-host-ext-stub-dump.txt
+++ b/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/12-hoststubgen-test-tiny-framework-host-ext-stub-dump.txt
@@ -1197,7 +1197,7 @@
   flags: (0x0021) ACC_PUBLIC, ACC_SUPER
   this_class: #x                          // com/android/hoststubgen/test/tinyframework/TinyFrameworkForTextPolicy
   super_class: #x                         // java/lang/Object
-  interfaces: 0, fields: 1, methods: 5, attributes: 2
+  interfaces: 0, fields: 1, methods: 14, attributes: 2
   public int stub;
     descriptor: I
     flags: (0x0001) ACC_PUBLIC
@@ -1239,6 +1239,150 @@
       x: #x()
         com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
 
+  public java.lang.String toBeIgnoredObj();
+    descriptor: ()Ljava/lang/String;
+    flags: (0x0001) ACC_PUBLIC
+    Code:
+      stack=3, locals=1, args_size=1
+         x: new           #x                 // class java/lang/RuntimeException
+         x: dup
+         x: ldc           #x                 // String Stub!
+         x: invokespecial #x                 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
+         x: athrow
+    RuntimeVisibleAnnotations:
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+
+  public void toBeIgnoredV();
+    descriptor: ()V
+    flags: (0x0001) ACC_PUBLIC
+    Code:
+      stack=3, locals=1, args_size=1
+         x: new           #x                 // class java/lang/RuntimeException
+         x: dup
+         x: ldc           #x                 // String Stub!
+         x: invokespecial #x                 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
+         x: athrow
+    RuntimeVisibleAnnotations:
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+
+  public boolean toBeIgnoredZ();
+    descriptor: ()Z
+    flags: (0x0001) ACC_PUBLIC
+    Code:
+      stack=3, locals=1, args_size=1
+         x: new           #x                 // class java/lang/RuntimeException
+         x: dup
+         x: ldc           #x                 // String Stub!
+         x: invokespecial #x                 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
+         x: athrow
+    RuntimeVisibleAnnotations:
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+
+  public byte toBeIgnoredB();
+    descriptor: ()B
+    flags: (0x0001) ACC_PUBLIC
+    Code:
+      stack=3, locals=1, args_size=1
+         x: new           #x                 // class java/lang/RuntimeException
+         x: dup
+         x: ldc           #x                 // String Stub!
+         x: invokespecial #x                 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
+         x: athrow
+    RuntimeVisibleAnnotations:
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+
+  public char toBeIgnoredC();
+    descriptor: ()C
+    flags: (0x0001) ACC_PUBLIC
+    Code:
+      stack=3, locals=1, args_size=1
+         x: new           #x                 // class java/lang/RuntimeException
+         x: dup
+         x: ldc           #x                 // String Stub!
+         x: invokespecial #x                 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
+         x: athrow
+    RuntimeVisibleAnnotations:
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+
+  public short toBeIgnoredS();
+    descriptor: ()S
+    flags: (0x0001) ACC_PUBLIC
+    Code:
+      stack=3, locals=1, args_size=1
+         x: new           #x                 // class java/lang/RuntimeException
+         x: dup
+         x: ldc           #x                 // String Stub!
+         x: invokespecial #x                 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
+         x: athrow
+    RuntimeVisibleAnnotations:
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+
+  public int toBeIgnoredI();
+    descriptor: ()I
+    flags: (0x0001) ACC_PUBLIC
+    Code:
+      stack=3, locals=1, args_size=1
+         x: new           #x                 // class java/lang/RuntimeException
+         x: dup
+         x: ldc           #x                 // String Stub!
+         x: invokespecial #x                 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
+         x: athrow
+    RuntimeVisibleAnnotations:
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+
+  public float toBeIgnoredF();
+    descriptor: ()F
+    flags: (0x0001) ACC_PUBLIC
+    Code:
+      stack=3, locals=1, args_size=1
+         x: new           #x                 // class java/lang/RuntimeException
+         x: dup
+         x: ldc           #x                 // String Stub!
+         x: invokespecial #x                 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
+         x: athrow
+    RuntimeVisibleAnnotations:
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+
+  public double toBeIgnoredD();
+    descriptor: ()D
+    flags: (0x0001) ACC_PUBLIC
+    Code:
+      stack=3, locals=1, args_size=1
+         x: new           #x                 // class java/lang/RuntimeException
+         x: dup
+         x: ldc           #x                 // String Stub!
+         x: invokespecial #x                 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
+         x: athrow
+    RuntimeVisibleAnnotations:
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+
   public int addTwo(int);
     descriptor: (I)I
     flags: (0x0001) ACC_PUBLIC
@@ -1640,6 +1784,161 @@
     android.hosttest.annotation.HostSideTestStaticInitializerKeep
 NestMembers:
   com/android/hoststubgen/test/tinyframework/TinyFrameworkLambdas$Nested
+## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkMethodCallReplace$ReplaceTo.class
+  Compiled from "TinyFrameworkMethodCallReplace.java"
+public class com.android.hoststubgen.test.tinyframework.TinyFrameworkMethodCallReplace$ReplaceTo
+  minor version: 0
+  major version: 61
+  flags: (0x0021) ACC_PUBLIC, ACC_SUPER
+  this_class: #x                          // com/android/hoststubgen/test/tinyframework/TinyFrameworkMethodCallReplace$ReplaceTo
+  super_class: #x                         // java/lang/Object
+  interfaces: 0, fields: 0, methods: 3, attributes: 4
+  public com.android.hoststubgen.test.tinyframework.TinyFrameworkMethodCallReplace$ReplaceTo();
+    descriptor: ()V
+    flags: (0x0001) ACC_PUBLIC
+    Code:
+      stack=3, locals=1, args_size=1
+         x: new           #x                 // class java/lang/RuntimeException
+         x: dup
+         x: ldc           #x                 // String Stub!
+         x: invokespecial #x                 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
+         x: athrow
+    RuntimeVisibleAnnotations:
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+
+  public static void startThread(java.lang.Thread);
+    descriptor: (Ljava/lang/Thread;)V
+    flags: (0x0009) ACC_PUBLIC, ACC_STATIC
+    Code:
+      stack=3, locals=1, args_size=1
+         x: new           #x                 // class java/lang/RuntimeException
+         x: dup
+         x: ldc           #x                 // String Stub!
+         x: invokespecial #x                 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
+         x: athrow
+    RuntimeVisibleAnnotations:
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+
+  public static int add(int, int);
+    descriptor: (II)I
+    flags: (0x0009) ACC_PUBLIC, ACC_STATIC
+    Code:
+      stack=3, locals=2, args_size=2
+         x: new           #x                 // class java/lang/RuntimeException
+         x: dup
+         x: ldc           #x                 // String Stub!
+         x: invokespecial #x                 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
+         x: athrow
+    RuntimeVisibleAnnotations:
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+}
+InnerClasses:
+  public static #x= #x of #x;            // ReplaceTo=class com/android/hoststubgen/test/tinyframework/TinyFrameworkMethodCallReplace$ReplaceTo of class com/android/hoststubgen/test/tinyframework/TinyFrameworkMethodCallReplace
+SourceFile: "TinyFrameworkMethodCallReplace.java"
+RuntimeVisibleAnnotations:
+  x: #x()
+    com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
+  x: #x()
+    com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+NestHost: class com/android/hoststubgen/test/tinyframework/TinyFrameworkMethodCallReplace
+## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkMethodCallReplace.class
+  Compiled from "TinyFrameworkMethodCallReplace.java"
+public class com.android.hoststubgen.test.tinyframework.TinyFrameworkMethodCallReplace
+  minor version: 0
+  major version: 61
+  flags: (0x0021) ACC_PUBLIC, ACC_SUPER
+  this_class: #x                          // com/android/hoststubgen/test/tinyframework/TinyFrameworkMethodCallReplace
+  super_class: #x                         // java/lang/Object
+  interfaces: 0, fields: 0, methods: 4, attributes: 5
+  public com.android.hoststubgen.test.tinyframework.TinyFrameworkMethodCallReplace();
+    descriptor: ()V
+    flags: (0x0001) ACC_PUBLIC
+    Code:
+      stack=3, locals=1, args_size=1
+         x: new           #x                 // class java/lang/RuntimeException
+         x: dup
+         x: ldc           #x                 // String Stub!
+         x: invokespecial #x                 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
+         x: athrow
+    RuntimeVisibleAnnotations:
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+
+  public static boolean nonStaticMethodCallReplaceTester() throws java.lang.Exception;
+    descriptor: ()Z
+    flags: (0x0009) ACC_PUBLIC, ACC_STATIC
+    Code:
+      stack=3, locals=0, args_size=0
+         x: new           #x                 // class java/lang/RuntimeException
+         x: dup
+         x: ldc           #x                 // String Stub!
+         x: invokespecial #x                 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
+         x: athrow
+    Exceptions:
+      throws java.lang.Exception
+    RuntimeVisibleAnnotations:
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+
+  public static int staticMethodCallReplaceTester();
+    descriptor: ()I
+    flags: (0x0009) ACC_PUBLIC, ACC_STATIC
+    Code:
+      stack=3, locals=0, args_size=0
+         x: new           #x                 // class java/lang/RuntimeException
+         x: dup
+         x: ldc           #x                 // String Stub!
+         x: invokespecial #x                 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
+         x: athrow
+    RuntimeVisibleAnnotations:
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+
+  private static void lambda$nonStaticMethodCallReplaceTester$0(java.util.concurrent.atomic.AtomicBoolean);
+    descriptor: (Ljava/util/concurrent/atomic/AtomicBoolean;)V
+    flags: (0x100a) ACC_PRIVATE, ACC_STATIC, ACC_SYNTHETIC
+    Code:
+      stack=3, locals=1, args_size=1
+         x: new           #x                 // class java/lang/RuntimeException
+         x: dup
+         x: ldc           #x                 // String Stub!
+         x: invokespecial #x                 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
+         x: athrow
+    RuntimeVisibleAnnotations:
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+}
+InnerClasses:
+  public static #x= #x of #x;           // ReplaceTo=class com/android/hoststubgen/test/tinyframework/TinyFrameworkMethodCallReplace$ReplaceTo of class com/android/hoststubgen/test/tinyframework/TinyFrameworkMethodCallReplace
+  public static final #x= #x of #x;    // Lookup=class java/lang/invoke/MethodHandles$Lookup of class java/lang/invoke/MethodHandles
+SourceFile: "TinyFrameworkMethodCallReplace.java"
+RuntimeVisibleAnnotations:
+  x: #x()
+    com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
+  x: #x()
+    com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+RuntimeInvisibleAnnotations:
+  x: #x()
+    android.hosttest.annotation.HostSideTestWholeClassStub
+NestMembers:
+  com/android/hoststubgen/test/tinyframework/TinyFrameworkMethodCallReplace$ReplaceTo
 ## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkNative.class
   Compiled from "TinyFrameworkNative.java"
 public class com.android.hoststubgen.test.tinyframework.TinyFrameworkNative
diff --git a/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/13-hoststubgen-test-tiny-framework-host-ext-impl-dump.txt b/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/13-hoststubgen-test-tiny-framework-host-ext-impl-dump.txt
index 3beea64..d12a23d 100644
--- a/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/13-hoststubgen-test-tiny-framework-host-ext-impl-dump.txt
+++ b/tools/hoststubgen/hoststubgen/test-tiny-framework/golden-output/13-hoststubgen-test-tiny-framework-host-ext-impl-dump.txt
@@ -2141,7 +2141,7 @@
   flags: (0x0021) ACC_PUBLIC, ACC_SUPER
   this_class: #x                          // com/android/hoststubgen/test/tinyframework/TinyFrameworkForTextPolicy
   super_class: #x                         // java/lang/Object
-  interfaces: 0, fields: 2, methods: 8, attributes: 2
+  interfaces: 0, fields: 2, methods: 17, attributes: 2
   public int stub;
     descriptor: I
     flags: (0x0001) ACC_PUBLIC
@@ -2254,13 +2254,192 @@
       x: #x()
         com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
 
+  public java.lang.String toBeIgnoredObj();
+    descriptor: ()Ljava/lang/String;
+    flags: (0x0001) ACC_PUBLIC
+    Code:
+      stack=4, locals=1, args_size=1
+         x: ldc           #x                  // class com/android/hoststubgen/test/tinyframework/TinyFrameworkForTextPolicy
+         x: ldc           #x                 // String toBeIgnoredObj
+         x: ldc           #x                 // String ()Ljava/lang/String;
+         x: ldc           #x                 // String com.android.hoststubgen.hosthelper.HostTestUtils.logMethodCall
+         x: invokestatic  #x                 // Method com/android/hoststubgen/hosthelper/HostTestUtils.callMethodCallHook:(Ljava/lang/Class;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V
+        x: aconst_null
+        x: areturn
+    RuntimeVisibleAnnotations:
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsIgnore
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+
+  public void toBeIgnoredV();
+    descriptor: ()V
+    flags: (0x0001) ACC_PUBLIC
+    Code:
+      stack=4, locals=1, args_size=1
+         x: ldc           #x                  // class com/android/hoststubgen/test/tinyframework/TinyFrameworkForTextPolicy
+         x: ldc           #x                 // String toBeIgnoredV
+         x: ldc           #x                 // String ()V
+         x: ldc           #x                 // String com.android.hoststubgen.hosthelper.HostTestUtils.logMethodCall
+         x: invokestatic  #x                 // Method com/android/hoststubgen/hosthelper/HostTestUtils.callMethodCallHook:(Ljava/lang/Class;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V
+        x: return
+    RuntimeVisibleAnnotations:
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsIgnore
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+
+  public boolean toBeIgnoredZ();
+    descriptor: ()Z
+    flags: (0x0001) ACC_PUBLIC
+    Code:
+      stack=4, locals=1, args_size=1
+         x: ldc           #x                  // class com/android/hoststubgen/test/tinyframework/TinyFrameworkForTextPolicy
+         x: ldc           #x                 // String toBeIgnoredZ
+         x: ldc           #x                 // String ()Z
+         x: ldc           #x                 // String com.android.hoststubgen.hosthelper.HostTestUtils.logMethodCall
+         x: invokestatic  #x                 // Method com/android/hoststubgen/hosthelper/HostTestUtils.callMethodCallHook:(Ljava/lang/Class;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V
+        x: iconst_0
+        x: ireturn
+    RuntimeVisibleAnnotations:
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsIgnore
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+
+  public byte toBeIgnoredB();
+    descriptor: ()B
+    flags: (0x0001) ACC_PUBLIC
+    Code:
+      stack=4, locals=1, args_size=1
+         x: ldc           #x                  // class com/android/hoststubgen/test/tinyframework/TinyFrameworkForTextPolicy
+         x: ldc           #x                 // String toBeIgnoredB
+         x: ldc           #x                 // String ()B
+         x: ldc           #x                 // String com.android.hoststubgen.hosthelper.HostTestUtils.logMethodCall
+         x: invokestatic  #x                 // Method com/android/hoststubgen/hosthelper/HostTestUtils.callMethodCallHook:(Ljava/lang/Class;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V
+        x: iconst_0
+        x: ireturn
+    RuntimeVisibleAnnotations:
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsIgnore
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+
+  public char toBeIgnoredC();
+    descriptor: ()C
+    flags: (0x0001) ACC_PUBLIC
+    Code:
+      stack=4, locals=1, args_size=1
+         x: ldc           #x                  // class com/android/hoststubgen/test/tinyframework/TinyFrameworkForTextPolicy
+         x: ldc           #x                 // String toBeIgnoredC
+         x: ldc           #x                 // String ()C
+         x: ldc           #x                 // String com.android.hoststubgen.hosthelper.HostTestUtils.logMethodCall
+         x: invokestatic  #x                 // Method com/android/hoststubgen/hosthelper/HostTestUtils.callMethodCallHook:(Ljava/lang/Class;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V
+        x: iconst_0
+        x: ireturn
+    RuntimeVisibleAnnotations:
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsIgnore
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+
+  public short toBeIgnoredS();
+    descriptor: ()S
+    flags: (0x0001) ACC_PUBLIC
+    Code:
+      stack=4, locals=1, args_size=1
+         x: ldc           #x                  // class com/android/hoststubgen/test/tinyframework/TinyFrameworkForTextPolicy
+         x: ldc           #x                 // String toBeIgnoredS
+         x: ldc           #x                 // String ()S
+         x: ldc           #x                 // String com.android.hoststubgen.hosthelper.HostTestUtils.logMethodCall
+         x: invokestatic  #x                 // Method com/android/hoststubgen/hosthelper/HostTestUtils.callMethodCallHook:(Ljava/lang/Class;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V
+        x: iconst_0
+        x: ireturn
+    RuntimeVisibleAnnotations:
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsIgnore
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+
+  public int toBeIgnoredI();
+    descriptor: ()I
+    flags: (0x0001) ACC_PUBLIC
+    Code:
+      stack=4, locals=1, args_size=1
+         x: ldc           #x                  // class com/android/hoststubgen/test/tinyframework/TinyFrameworkForTextPolicy
+         x: ldc           #x                 // String toBeIgnoredI
+         x: ldc           #x                 // String ()I
+         x: ldc           #x                 // String com.android.hoststubgen.hosthelper.HostTestUtils.logMethodCall
+         x: invokestatic  #x                 // Method com/android/hoststubgen/hosthelper/HostTestUtils.callMethodCallHook:(Ljava/lang/Class;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V
+        x: iconst_0
+        x: ireturn
+    RuntimeVisibleAnnotations:
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsIgnore
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+
+  public float toBeIgnoredF();
+    descriptor: ()F
+    flags: (0x0001) ACC_PUBLIC
+    Code:
+      stack=4, locals=1, args_size=1
+         x: ldc           #x                  // class com/android/hoststubgen/test/tinyframework/TinyFrameworkForTextPolicy
+         x: ldc           #x                 // String toBeIgnoredF
+         x: ldc           #x                 // String ()F
+         x: ldc           #x                 // String com.android.hoststubgen.hosthelper.HostTestUtils.logMethodCall
+         x: invokestatic  #x                 // Method com/android/hoststubgen/hosthelper/HostTestUtils.callMethodCallHook:(Ljava/lang/Class;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V
+        x: fconst_0
+        x: freturn
+    RuntimeVisibleAnnotations:
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsIgnore
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+
+  public double toBeIgnoredD();
+    descriptor: ()D
+    flags: (0x0001) ACC_PUBLIC
+    Code:
+      stack=4, locals=1, args_size=1
+         x: ldc           #x                  // class com/android/hoststubgen/test/tinyframework/TinyFrameworkForTextPolicy
+         x: ldc           #x                 // String toBeIgnoredD
+         x: ldc           #x                 // String ()D
+         x: ldc           #x                 // String com.android.hoststubgen.hosthelper.HostTestUtils.logMethodCall
+         x: invokestatic  #x                 // Method com/android/hoststubgen/hosthelper/HostTestUtils.callMethodCallHook:(Ljava/lang/Class;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V
+        x: dconst_0
+        x: dreturn
+    RuntimeVisibleAnnotations:
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenProcessedAsIgnore
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+
   public int addTwo(int);
     descriptor: (I)I
     flags: (0x0001) ACC_PUBLIC
     Code:
       stack=4, locals=2, args_size=2
          x: ldc           #x                  // class com/android/hoststubgen/test/tinyframework/TinyFrameworkForTextPolicy
-         x: ldc           #x                 // String addTwo
+         x: ldc           #x                // String addTwo
          x: ldc           #x                 // String (I)I
          x: ldc           #x                 // String com.android.hoststubgen.hosthelper.HostTestUtils.logMethodCall
          x: invokestatic  #x                 // Method com/android/hoststubgen/hosthelper/HostTestUtils.callMethodCallHook:(Ljava/lang/Class;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V
@@ -2287,7 +2466,7 @@
     Code:
       stack=4, locals=1, args_size=1
          x: ldc           #x                  // class com/android/hoststubgen/test/tinyframework/TinyFrameworkForTextPolicy
-         x: ldc           #x                 // String nativeAddThree
+         x: ldc           #x                // String nativeAddThree
          x: ldc           #x                 // String (I)I
          x: ldc           #x                 // String com.android.hoststubgen.hosthelper.HostTestUtils.logMethodCall
          x: invokestatic  #x                 // Method com/android/hoststubgen/hosthelper/HostTestUtils.callMethodCallHook:(Ljava/lang/Class;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V
@@ -2313,21 +2492,21 @@
     Code:
       stack=4, locals=1, args_size=1
          x: ldc           #x                  // class com/android/hoststubgen/test/tinyframework/TinyFrameworkForTextPolicy
-         x: ldc           #x                 // String unsupportedMethod
+         x: ldc           #x                // String unsupportedMethod
          x: ldc           #x                 // String ()Ljava/lang/String;
          x: ldc           #x                 // String com.android.hoststubgen.hosthelper.HostTestUtils.logMethodCall
          x: invokestatic  #x                 // Method com/android/hoststubgen/hosthelper/HostTestUtils.callMethodCallHook:(Ljava/lang/Class;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V
         x: ldc           #x                 // String com/android/hoststubgen/test/tinyframework/TinyFrameworkForTextPolicy
-        x: ldc           #x                 // String unsupportedMethod
+        x: ldc           #x                // String unsupportedMethod
         x: ldc           #x                 // String ()Ljava/lang/String;
         x: invokestatic  #x                 // Method com/android/hoststubgen/hosthelper/HostTestUtils.getStackWalker:()Ljava/lang/StackWalker;
         x: invokevirtual #x                 // Method java/lang/StackWalker.getCallerClass:()Ljava/lang/Class;
         x: invokestatic  #x                 // Method com/android/hoststubgen/hosthelper/HostTestUtils.onNonStubMethodCalled:(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/Class;)V
-        x: invokestatic  #x                 // Method com/android/hoststubgen/hosthelper/HostTestUtils.onThrowMethodCalled:()V
-        x: new           #x                 // class java/lang/RuntimeException
+        x: invokestatic  #x                // Method com/android/hoststubgen/hosthelper/HostTestUtils.onThrowMethodCalled:()V
+        x: new           #x                // class java/lang/RuntimeException
         x: dup
-        x: ldc           #x                 // String Unreachable
-        x: invokespecial #x                 // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
+        x: ldc           #x                // String Unreachable
+        x: invokespecial #x                // Method java/lang/RuntimeException."<init>":(Ljava/lang/String;)V
         x: athrow
     RuntimeVisibleAnnotations:
       x: #x()
@@ -2341,12 +2520,12 @@
     Code:
       stack=4, locals=1, args_size=1
          x: ldc           #x                  // class com/android/hoststubgen/test/tinyframework/TinyFrameworkForTextPolicy
-         x: ldc           #x                 // String visibleButUsesUnsupportedMethod
+         x: ldc           #x                // String visibleButUsesUnsupportedMethod
          x: ldc           #x                 // String ()Ljava/lang/String;
          x: ldc           #x                 // String com.android.hoststubgen.hosthelper.HostTestUtils.logMethodCall
          x: invokestatic  #x                 // Method com/android/hoststubgen/hosthelper/HostTestUtils.callMethodCallHook:(Ljava/lang/Class;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V
         x: aload_0
-        x: invokevirtual #x                 // Method unsupportedMethod:()Ljava/lang/String;
+        x: invokevirtual #x                // Method unsupportedMethod:()Ljava/lang/String;
         x: areturn
       LineNumberTable:
       LocalVariableTable:
@@ -2865,6 +3044,257 @@
       #x ()Ljava/lang/Integer;
 NestMembers:
   com/android/hoststubgen/test/tinyframework/TinyFrameworkLambdas$Nested
+## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkMethodCallReplace$ReplaceTo.class
+  Compiled from "TinyFrameworkMethodCallReplace.java"
+public class com.android.hoststubgen.test.tinyframework.TinyFrameworkMethodCallReplace$ReplaceTo
+  minor version: 0
+  major version: 61
+  flags: (0x0021) ACC_PUBLIC, ACC_SUPER
+  this_class: #x                          // com/android/hoststubgen/test/tinyframework/TinyFrameworkMethodCallReplace$ReplaceTo
+  super_class: #x                         // java/lang/Object
+  interfaces: 0, fields: 0, methods: 4, attributes: 4
+  private static {};
+    descriptor: ()V
+    flags: (0x000a) ACC_PRIVATE, ACC_STATIC
+    Code:
+      stack=2, locals=0, args_size=0
+         x: ldc           #x                  // class com/android/hoststubgen/test/tinyframework/TinyFrameworkMethodCallReplace$ReplaceTo
+         x: ldc           #x                 // String com.android.hoststubgen.hosthelper.HostTestUtils.logClassLoaded
+         x: invokestatic  #x                 // Method com/android/hoststubgen/hosthelper/HostTestUtils.onClassLoaded:(Ljava/lang/Class;Ljava/lang/String;)V
+         x: return
+
+  public com.android.hoststubgen.test.tinyframework.TinyFrameworkMethodCallReplace$ReplaceTo();
+    descriptor: ()V
+    flags: (0x0001) ACC_PUBLIC
+    Code:
+      stack=4, locals=1, args_size=1
+         x: ldc           #x                  // class com/android/hoststubgen/test/tinyframework/TinyFrameworkMethodCallReplace$ReplaceTo
+         x: ldc           #x                 // String <init>
+         x: ldc           #x                 // String ()V
+         x: ldc           #x                 // String com.android.hoststubgen.hosthelper.HostTestUtils.logMethodCall
+         x: invokestatic  #x                 // Method com/android/hoststubgen/hosthelper/HostTestUtils.callMethodCallHook:(Ljava/lang/Class;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V
+        x: aload_0
+        x: invokespecial #x                 // Method java/lang/Object."<init>":()V
+        x: return
+      LineNumberTable:
+      LocalVariableTable:
+        Start  Length  Slot  Name   Signature
+           11       5     0  this   Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkMethodCallReplace$ReplaceTo;
+    RuntimeVisibleAnnotations:
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+
+  public static void startThread(java.lang.Thread);
+    descriptor: (Ljava/lang/Thread;)V
+    flags: (0x0009) ACC_PUBLIC, ACC_STATIC
+    Code:
+      stack=4, locals=1, args_size=1
+         x: ldc           #x                  // class com/android/hoststubgen/test/tinyframework/TinyFrameworkMethodCallReplace$ReplaceTo
+         x: ldc           #x                 // String startThread
+         x: ldc           #x                 // String (Ljava/lang/Thread;)V
+         x: ldc           #x                 // String com.android.hoststubgen.hosthelper.HostTestUtils.logMethodCall
+         x: invokestatic  #x                 // Method com/android/hoststubgen/hosthelper/HostTestUtils.callMethodCallHook:(Ljava/lang/Class;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V
+        x: aload_0
+        x: iconst_1
+        x: invokevirtual #x                 // Method java/lang/Thread.setDaemon:(Z)V
+        x: aload_0
+        x: invokevirtual #x                 // Method java/lang/Thread.start:()V
+        x: return
+      LineNumberTable:
+      LocalVariableTable:
+        Start  Length  Slot  Name   Signature
+           11      10     0 thread   Ljava/lang/Thread;
+    RuntimeVisibleAnnotations:
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+
+  public static int add(int, int);
+    descriptor: (II)I
+    flags: (0x0009) ACC_PUBLIC, ACC_STATIC
+    Code:
+      stack=4, locals=2, args_size=2
+         x: ldc           #x                  // class com/android/hoststubgen/test/tinyframework/TinyFrameworkMethodCallReplace$ReplaceTo
+         x: ldc           #x                 // String add
+         x: ldc           #x                 // String (II)I
+         x: ldc           #x                 // String com.android.hoststubgen.hosthelper.HostTestUtils.logMethodCall
+         x: invokestatic  #x                 // Method com/android/hoststubgen/hosthelper/HostTestUtils.callMethodCallHook:(Ljava/lang/Class;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V
+        x: iload_0
+        x: iload_1
+        x: iadd
+        x: ireturn
+      LineNumberTable:
+      LocalVariableTable:
+        Start  Length  Slot  Name   Signature
+           11       4     0     a   I
+           11       4     1     b   I
+    RuntimeVisibleAnnotations:
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+}
+InnerClasses:
+  public static #x= #x of #x;           // ReplaceTo=class com/android/hoststubgen/test/tinyframework/TinyFrameworkMethodCallReplace$ReplaceTo of class com/android/hoststubgen/test/tinyframework/TinyFrameworkMethodCallReplace
+SourceFile: "TinyFrameworkMethodCallReplace.java"
+RuntimeVisibleAnnotations:
+  x: #x()
+    com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
+  x: #x()
+    com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+NestHost: class com/android/hoststubgen/test/tinyframework/TinyFrameworkMethodCallReplace
+## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkMethodCallReplace.class
+  Compiled from "TinyFrameworkMethodCallReplace.java"
+public class com.android.hoststubgen.test.tinyframework.TinyFrameworkMethodCallReplace
+  minor version: 0
+  major version: 61
+  flags: (0x0021) ACC_PUBLIC, ACC_SUPER
+  this_class: #x                          // com/android/hoststubgen/test/tinyframework/TinyFrameworkMethodCallReplace
+  super_class: #x                         // java/lang/Object
+  interfaces: 0, fields: 0, methods: 5, attributes: 6
+  private static {};
+    descriptor: ()V
+    flags: (0x000a) ACC_PRIVATE, ACC_STATIC
+    Code:
+      stack=2, locals=0, args_size=0
+         x: ldc           #x                  // class com/android/hoststubgen/test/tinyframework/TinyFrameworkMethodCallReplace
+         x: ldc           #x                 // String com.android.hoststubgen.hosthelper.HostTestUtils.logClassLoaded
+         x: invokestatic  #x                 // Method com/android/hoststubgen/hosthelper/HostTestUtils.onClassLoaded:(Ljava/lang/Class;Ljava/lang/String;)V
+         x: return
+
+  public com.android.hoststubgen.test.tinyframework.TinyFrameworkMethodCallReplace();
+    descriptor: ()V
+    flags: (0x0001) ACC_PUBLIC
+    Code:
+      stack=4, locals=1, args_size=1
+         x: ldc           #x                  // class com/android/hoststubgen/test/tinyframework/TinyFrameworkMethodCallReplace
+         x: ldc           #x                 // String <init>
+         x: ldc           #x                 // String ()V
+         x: ldc           #x                 // String com.android.hoststubgen.hosthelper.HostTestUtils.logMethodCall
+         x: invokestatic  #x                 // Method com/android/hoststubgen/hosthelper/HostTestUtils.callMethodCallHook:(Ljava/lang/Class;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V
+        x: aload_0
+        x: invokespecial #x                 // Method java/lang/Object."<init>":()V
+        x: return
+      LineNumberTable:
+      LocalVariableTable:
+        Start  Length  Slot  Name   Signature
+           11       5     0  this   Lcom/android/hoststubgen/test/tinyframework/TinyFrameworkMethodCallReplace;
+    RuntimeVisibleAnnotations:
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+
+  public static boolean nonStaticMethodCallReplaceTester() throws java.lang.Exception;
+    descriptor: ()Z
+    flags: (0x0009) ACC_PUBLIC, ACC_STATIC
+    Code:
+      stack=4, locals=2, args_size=0
+         x: ldc           #x                  // class com/android/hoststubgen/test/tinyframework/TinyFrameworkMethodCallReplace
+         x: ldc           #x                 // String nonStaticMethodCallReplaceTester
+         x: ldc           #x                 // String ()Z
+         x: ldc           #x                 // String com.android.hoststubgen.hosthelper.HostTestUtils.logMethodCall
+         x: invokestatic  #x                 // Method com/android/hoststubgen/hosthelper/HostTestUtils.callMethodCallHook:(Ljava/lang/Class;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V
+        x: new           #x                 // class java/util/concurrent/atomic/AtomicBoolean
+        x: dup
+        x: iconst_0
+        x: invokespecial #x                 // Method java/util/concurrent/atomic/AtomicBoolean."<init>":(Z)V
+        x: astore_0
+        x: new           #x                 // class java/lang/Thread
+        x: dup
+        x: aload_0
+        x: invokedynamic #x,  0             // InvokeDynamic #x:run:(Ljava/util/concurrent/atomic/AtomicBoolean;)Ljava/lang/Runnable;
+        x: invokespecial #x                 // Method java/lang/Thread."<init>":(Ljava/lang/Runnable;)V
+        x: astore_1
+        x: aload_1
+        x: invokestatic  #x                 // Method com/android/hoststubgen/test/tinyframework/TinyFrameworkMethodCallReplace$ReplaceTo.startThread:(Ljava/lang/Thread;)V
+        x: aload_1
+        x: invokevirtual #x                 // Method java/lang/Thread.join:()V
+        x: aload_0
+        x: invokevirtual #x                 // Method java/util/concurrent/atomic/AtomicBoolean.get:()Z
+        x: ireturn
+      LineNumberTable:
+      LocalVariableTable:
+        Start  Length  Slot  Name   Signature
+           20      27     0    ab   Ljava/util/concurrent/atomic/AtomicBoolean;
+           34      13     1    th   Ljava/lang/Thread;
+    Exceptions:
+      throws java.lang.Exception
+    RuntimeVisibleAnnotations:
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+
+  public static int staticMethodCallReplaceTester();
+    descriptor: ()I
+    flags: (0x0009) ACC_PUBLIC, ACC_STATIC
+    Code:
+      stack=4, locals=0, args_size=0
+         x: ldc           #x                  // class com/android/hoststubgen/test/tinyframework/TinyFrameworkMethodCallReplace
+         x: ldc           #x                 // String staticMethodCallReplaceTester
+         x: ldc           #x                 // String ()I
+         x: ldc           #x                 // String com.android.hoststubgen.hosthelper.HostTestUtils.logMethodCall
+         x: invokestatic  #x                 // Method com/android/hoststubgen/hosthelper/HostTestUtils.callMethodCallHook:(Ljava/lang/Class;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V
+        x: iconst_1
+        x: iconst_2
+        x: invokestatic  #x                 // Method com/android/hoststubgen/test/tinyframework/TinyFrameworkMethodCallReplace$ReplaceTo.add:(II)I
+        x: ireturn
+      LineNumberTable:
+    RuntimeVisibleAnnotations:
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+
+  private static void lambda$nonStaticMethodCallReplaceTester$0(java.util.concurrent.atomic.AtomicBoolean);
+    descriptor: (Ljava/util/concurrent/atomic/AtomicBoolean;)V
+    flags: (0x100a) ACC_PRIVATE, ACC_STATIC, ACC_SYNTHETIC
+    Code:
+      stack=4, locals=1, args_size=1
+         x: ldc           #x                  // class com/android/hoststubgen/test/tinyframework/TinyFrameworkMethodCallReplace
+         x: ldc           #x                 // String lambda$nonStaticMethodCallReplaceTester$0
+         x: ldc           #x                 // String (Ljava/util/concurrent/atomic/AtomicBoolean;)V
+         x: ldc           #x                 // String com.android.hoststubgen.hosthelper.HostTestUtils.logMethodCall
+         x: invokestatic  #x                 // Method com/android/hoststubgen/hosthelper/HostTestUtils.callMethodCallHook:(Ljava/lang/Class;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V
+        x: aload_0
+        x: invokestatic  #x                // Method java/lang/Thread.currentThread:()Ljava/lang/Thread;
+        x: invokevirtual #x                // Method java/lang/Thread.isDaemon:()Z
+        x: invokevirtual #x                // Method java/util/concurrent/atomic/AtomicBoolean.set:(Z)V
+        x: return
+      LineNumberTable:
+      LocalVariableTable:
+        Start  Length  Slot  Name   Signature
+           11      11     0    ab   Ljava/util/concurrent/atomic/AtomicBoolean;
+    RuntimeVisibleAnnotations:
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
+      x: #x()
+        com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+}
+InnerClasses:
+  public static #x= #x of #x;           // ReplaceTo=class com/android/hoststubgen/test/tinyframework/TinyFrameworkMethodCallReplace$ReplaceTo of class com/android/hoststubgen/test/tinyframework/TinyFrameworkMethodCallReplace
+  public static final #x= #x of #x;    // Lookup=class java/lang/invoke/MethodHandles$Lookup of class java/lang/invoke/MethodHandles
+SourceFile: "TinyFrameworkMethodCallReplace.java"
+RuntimeVisibleAnnotations:
+  x: #x()
+    com.android.hoststubgen.hosthelper.HostStubGenKeptInStub
+  x: #x()
+    com.android.hoststubgen.hosthelper.HostStubGenKeptInImpl
+RuntimeInvisibleAnnotations:
+  x: #x()
+    android.hosttest.annotation.HostSideTestWholeClassStub
+BootstrapMethods:
+  x: #x REF_invokeStatic java/lang/invoke/LambdaMetafactory.metafactory:(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodHandle;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/CallSite;
+    Method arguments:
+      #x ()V
+      #x REF_invokeStatic com/android/hoststubgen/test/tinyframework/TinyFrameworkMethodCallReplace.lambda$nonStaticMethodCallReplaceTester$0:(Ljava/util/concurrent/atomic/AtomicBoolean;)V
+      #x ()V
+NestMembers:
+  com/android/hoststubgen/test/tinyframework/TinyFrameworkMethodCallReplace$ReplaceTo
 ## Class: com/android/hoststubgen/test/tinyframework/TinyFrameworkNative.class
   Compiled from "TinyFrameworkNative.java"
 public class com.android.hoststubgen.test.tinyframework.TinyFrameworkNative
diff --git a/tools/hoststubgen/hoststubgen/test-tiny-framework/policy-override-tiny-framework.txt b/tools/hoststubgen/hoststubgen/test-tiny-framework/policy-override-tiny-framework.txt
index 75c9721..f064433 100644
--- a/tools/hoststubgen/hoststubgen/test-tiny-framework/policy-override-tiny-framework.txt
+++ b/tools/hoststubgen/hoststubgen/test-tiny-framework/policy-override-tiny-framework.txt
@@ -12,6 +12,16 @@
   # method addThree_host	(I)I	# used as a substitute
   method unsupportedMethod	()Ljava/lang/String;	throw
   method visibleButUsesUnsupportedMethod	()Ljava/lang/String;	stub
+  method toBeIgnoredObj	()Ljava/lang/String;	ignore
+  method toBeIgnoredV	()V	ignore
+  method toBeIgnoredZ	()Z	ignore
+  method toBeIgnoredB	()B	ignore
+  method toBeIgnoredC	()C	ignore
+  method toBeIgnoredS	()S	ignore
+  method toBeIgnoredI	()I	ignore
+  method toBeIgnoredL	()L	ignore
+  method toBeIgnoredF	()F	ignore
+  method toBeIgnoredD	()D	ignore
 
 # Class load hook
 class com/android/hoststubgen/test/tinyframework/TinyFrameworkForTextPolicy	~com.android.hoststubgen.test.tinyframework.TinyFrameworkClassLoadHook.onClassLoaded
@@ -49,8 +59,18 @@
 # class com.android.hoststubgen.test.tinyframework.packagetest.A stub
 # class com.android.hoststubgen.test.tinyframework.packagetest.sub.A stub
 
+# Used to test method call replacement.
+class com.android.hoststubgen.test.tinyframework.TinyFrameworkMethodCallReplace stubclass
+  method originalAdd (II)I @com.android.hoststubgen.test.tinyframework.TinyFrameworkMethodCallReplace$ReplaceTo.add
+
+# Used to test method call replacement.
+# Note because java.lang.Thread is _not_ within the tiny-framework jar, the policy ("keep")
+# doesn't realy matter.
+class java.lang.Thread keep
+  method start ()V @com.android.hoststubgen.test.tinyframework.TinyFrameworkMethodCallReplace$ReplaceTo.startThread
+
 
 # "rename" takes a type internal name, so '/'s is used as a separator.
 # The leading / in the prefix is not needed (it'll be stripped), but it's added to make
 # sure the stripping works.
-rename ^.*/TinyFrameworkToBeRenamed$ /rename_prefix/
\ No newline at end of file
+rename ^.*/TinyFrameworkToBeRenamed$ /rename_prefix/
diff --git a/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-framework/src/com/android/hoststubgen/test/tinyframework/TinyFrameworkForTextPolicy.java b/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-framework/src/com/android/hoststubgen/test/tinyframework/TinyFrameworkForTextPolicy.java
index bde7c35..1977c90 100644
--- a/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-framework/src/com/android/hoststubgen/test/tinyframework/TinyFrameworkForTextPolicy.java
+++ b/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-framework/src/com/android/hoststubgen/test/tinyframework/TinyFrameworkForTextPolicy.java
@@ -42,6 +42,42 @@
         throw new RuntimeException();
     }
 
+    public String toBeIgnoredObj() {
+        throw new RuntimeException();
+    }
+
+    public void toBeIgnoredV() {
+        throw new RuntimeException();
+    }
+
+    public boolean toBeIgnoredZ() {
+        throw new RuntimeException();
+    }
+
+    public byte toBeIgnoredB() {
+        throw new RuntimeException();
+    }
+
+    public char toBeIgnoredC() {
+        throw new RuntimeException();
+    }
+
+    public short toBeIgnoredS() {
+        throw new RuntimeException();
+    }
+
+    public int toBeIgnoredI() {
+        throw new RuntimeException();
+    }
+
+    public float toBeIgnoredF() {
+        throw new RuntimeException();
+    }
+
+    public double toBeIgnoredD() {
+        throw new RuntimeException();
+    }
+
     public int addTwo(int value) {
         throw new RuntimeException("not supported on host side");
     }
diff --git a/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-framework/src/com/android/hoststubgen/test/tinyframework/TinyFrameworkMethodCallReplace.java b/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-framework/src/com/android/hoststubgen/test/tinyframework/TinyFrameworkMethodCallReplace.java
new file mode 100644
index 0000000..1ff3744
--- /dev/null
+++ b/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-framework/src/com/android/hoststubgen/test/tinyframework/TinyFrameworkMethodCallReplace.java
@@ -0,0 +1,58 @@
+/*
+ * 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.hoststubgen.test.tinyframework;
+
+import android.hosttest.annotation.HostSideTestWholeClassStub;
+
+import java.util.concurrent.atomic.AtomicBoolean;
+
+@HostSideTestWholeClassStub
+public class TinyFrameworkMethodCallReplace {
+    //  This method should return true.
+    public static boolean nonStaticMethodCallReplaceTester() throws Exception {
+        final AtomicBoolean ab = new AtomicBoolean(false);
+
+        Thread th = new Thread(() -> {
+            ab.set(Thread.currentThread().isDaemon());
+        });
+        // This Thread.start() call will be redirected to ReplaceTo.startThread()
+        // (because of the policy file directive) which will make the thread "daemon" and start it.
+        th.start();
+        th.join();
+
+        return ab.get(); // This should be true.
+    }
+
+    public static int staticMethodCallReplaceTester() {
+        // This method call will be replaced with ReplaceTo.add().
+        return originalAdd(1, 2);
+    }
+
+    private static int originalAdd(int a, int b) {
+        return a + b - 1; // Original is broken.
+    }
+
+    public static class ReplaceTo {
+        public static void startThread(Thread thread) {
+            thread.setDaemon(true);
+            thread.start();
+        }
+
+        public static int add(int a, int b) {
+            return a + b;
+        }
+    }
+}
diff --git a/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-test/src/com/android/hoststubgen/test/tinyframework/TinyFrameworkClassTest.java b/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-test/src/com/android/hoststubgen/test/tinyframework/TinyFrameworkClassTest.java
index bf0f654..1692c6e89 100644
--- a/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-test/src/com/android/hoststubgen/test/tinyframework/TinyFrameworkClassTest.java
+++ b/tools/hoststubgen/hoststubgen/test-tiny-framework/tiny-test/src/com/android/hoststubgen/test/tinyframework/TinyFrameworkClassTest.java
@@ -53,6 +53,20 @@
 //    }
 
     @Test
+    public void testIgnore() {
+        TinyFrameworkForTextPolicy tfc = new TinyFrameworkForTextPolicy();
+        tfc.toBeIgnoredV();
+        assertThat(tfc.toBeIgnoredZ()).isEqualTo(false);
+        assertThat(tfc.toBeIgnoredB()).isEqualTo(0);
+        assertThat(tfc.toBeIgnoredC()).isEqualTo(0);
+        assertThat(tfc.toBeIgnoredS()).isEqualTo(0);
+        assertThat(tfc.toBeIgnoredI()).isEqualTo(0);
+        assertThat(tfc.toBeIgnoredF()).isEqualTo(0);
+        assertThat(tfc.toBeIgnoredD()).isEqualTo(0);
+        assertThat(tfc.toBeIgnoredObj()).isEqualTo(null);
+    }
+
+    @Test
     public void testSubstitute() {
         TinyFrameworkForTextPolicy tfc = new TinyFrameworkForTextPolicy();
         assertThat(tfc.addTwo(1)).isEqualTo(3);
@@ -339,4 +353,16 @@
     public void testTypeRename() {
         assertThat(TinyFrameworkRenamedClassCaller.foo(1)).isEqualTo(1);
     }
+
+    @Test
+    public void testMethodCallReplaceNonStatic() throws Exception {
+        assertThat(TinyFrameworkMethodCallReplace.nonStaticMethodCallReplaceTester())
+                .isEqualTo(true);
+    }
+
+    @Test
+    public void testMethodCallReplaceStatic() throws Exception {
+        assertThat(TinyFrameworkMethodCallReplace.staticMethodCallReplaceTester())
+                .isEqualTo(3);
+    }
 }
diff --git a/tools/protologtool/src/com/android/protolog/tool/ProtoLogCallProcessorImpl.kt b/tools/protologtool/src/com/android/protolog/tool/ProtoLogCallProcessorImpl.kt
index 3c99e68..c3595b7 100644
--- a/tools/protologtool/src/com/android/protolog/tool/ProtoLogCallProcessorImpl.kt
+++ b/tools/protologtool/src/com/android/protolog/tool/ProtoLogCallProcessorImpl.kt
@@ -120,7 +120,7 @@
 
                         logCallVisitor?.processCall(call, messageString, getLevelForMethodName(
                             call.name.toString(), call, context), groupMap.getValue(groupName))
-                    } else if (call.name.id == "initialize") {
+                    } else if (call.name.id == "init") {
                         // No processing
                     } else {
                         // Process non-log message calls
diff --git a/tools/protologtool/src/com/android/protolog/tool/ProtoLogTool.kt b/tools/protologtool/src/com/android/protolog/tool/ProtoLogTool.kt
index aa53005..2285880 100644
--- a/tools/protologtool/src/com/android/protolog/tool/ProtoLogTool.kt
+++ b/tools/protologtool/src/com/android/protolog/tool/ProtoLogTool.kt
@@ -26,7 +26,6 @@
 import com.github.javaparser.ast.Modifier
 import com.github.javaparser.ast.NodeList
 import com.github.javaparser.ast.body.ClassOrInterfaceDeclaration
-import com.github.javaparser.ast.body.InitializerDeclaration
 import com.github.javaparser.ast.expr.ArrayAccessExpr
 import com.github.javaparser.ast.expr.ArrayCreationExpr
 import com.github.javaparser.ast.expr.ArrayInitializerExpr
@@ -42,7 +41,10 @@
 import com.github.javaparser.ast.expr.ObjectCreationExpr
 import com.github.javaparser.ast.expr.SimpleName
 import com.github.javaparser.ast.expr.StringLiteralExpr
+import com.github.javaparser.ast.expr.VariableDeclarationExpr
 import com.github.javaparser.ast.stmt.BlockStmt
+import com.github.javaparser.ast.stmt.ReturnStmt
+import com.github.javaparser.ast.type.ClassOrInterfaceType
 import java.io.File
 import java.io.FileInputStream
 import java.io.FileNotFoundException
@@ -195,6 +197,7 @@
         groups: Map<String, LogGroup>,
         protoLogGroupsClassName: String
     ) {
+        var needsCreateLogGroupsMap = false
         classDeclaration.fields.forEach { field ->
             field.getAnnotationByClass(ProtoLogToolInjected::class.java)
                     .ifPresent { annotationExpr ->
@@ -222,33 +225,10 @@
                                             } ?: NullLiteralExpr())
                                 }
                                 ProtoLogToolInjected.Value.LOG_GROUPS.name -> {
-                                    val initializerBlockStmt = BlockStmt()
-                                    for (group in groups) {
-                                        initializerBlockStmt.addStatement(
-                                            MethodCallExpr()
-                                                    .setName("put")
-                                                    .setArguments(
-                                                        NodeList(StringLiteralExpr(group.key),
-                                                            FieldAccessExpr()
-                                                                    .setScope(
-                                                                        NameExpr(
-                                                                            protoLogGroupsClassName
-                                                                        ))
-                                                                    .setName(group.value.name)))
-                                        )
-                                        group.key
-                                    }
-
-                                    val treeMapCreation = ObjectCreationExpr()
-                                            .setType("TreeMap<String, IProtoLogGroup>")
-                                            .setAnonymousClassBody(NodeList(
-                                                InitializerDeclaration().setBody(
-                                                    initializerBlockStmt
-                                                )
-                                            ))
-
+                                    needsCreateLogGroupsMap = true
                                     field.setFinal(true)
-                                    field.variables.first().setInitializer(treeMapCreation)
+                                    field.variables.first().setInitializer(
+                                        MethodCallExpr().setName("createLogGroupsMap"))
                                 }
                                 ProtoLogToolInjected.Value.CACHE_UPDATER.name -> {
                                     field.setFinal(true)
@@ -261,6 +241,45 @@
                         }
                     }
         }
+
+        if (needsCreateLogGroupsMap) {
+            val body = BlockStmt()
+            body.addStatement(AssignExpr(
+                VariableDeclarationExpr(
+                    ClassOrInterfaceType("TreeMap<String, IProtoLogGroup>"),
+                    "result"
+                ),
+                ObjectCreationExpr().setType("TreeMap<String, IProtoLogGroup>"),
+                AssignExpr.Operator.ASSIGN
+            ))
+            for (group in groups) {
+                body.addStatement(
+                    MethodCallExpr(
+                        NameExpr("result"),
+                        "put",
+                        NodeList(
+                                StringLiteralExpr(group.key),
+                                FieldAccessExpr()
+                                        .setScope(
+                                            NameExpr(
+                                                protoLogGroupsClassName
+                                            ))
+                                        .setName(group.value.name)
+                        )
+                    )
+                )
+            }
+            body.addStatement(ReturnStmt(NameExpr("result")))
+
+            val method = classDeclaration.addMethod(
+                "createLogGroupsMap",
+                Modifier.Keyword.PRIVATE,
+                Modifier.Keyword.STATIC,
+                Modifier.Keyword.FINAL
+            )
+            method.setType("TreeMap<String, IProtoLogGroup>")
+            method.setBody(body)
+        }
     }
 
     private fun injectCacheClass(