Merge "Setup Volume Panel infra" into main
diff --git a/AconfigFlags.bp b/AconfigFlags.bp
index c231b30..c477a75 100644
--- a/AconfigFlags.bp
+++ b/AconfigFlags.bp
@@ -249,6 +249,14 @@
     defaults: ["framework-minus-apex-aconfig-java-defaults"],
 }
 
+java_aconfig_library {
+    name: "android.app.usage.flags-aconfig-java-host",
+    aconfig_declarations: "android.app.usage.flags-aconfig",
+    host_supported: true,
+    defaults: ["framework-minus-apex-aconfig-java-defaults"],
+}
+
+
 // OS
 aconfig_declarations {
     name: "android.os.flags-aconfig",
diff --git a/Android.bp b/Android.bp
index 13b1703..f6a9328 100644
--- a/Android.bp
+++ b/Android.bp
@@ -220,6 +220,7 @@
         "updatable-driver-protos",
         "ota_metadata_proto_java",
         "android.hidl.base-V1.0-java",
+        "android.hidl.manager-V1.2-java",
         "android.hardware.cas-V1-java", // AIDL
         "android.hardware.cas-V1.0-java",
         "android.hardware.cas-V1.1-java",
diff --git a/apex/jobscheduler/service/aconfig/job.aconfig b/apex/jobscheduler/service/aconfig/job.aconfig
index c65e506..de6f023 100644
--- a/apex/jobscheduler/service/aconfig/job.aconfig
+++ b/apex/jobscheduler/service/aconfig/job.aconfig
@@ -12,4 +12,11 @@
     namespace: "backstage_power"
     description: "Throw an exception if an unsupported app uses JobInfo.setBias"
     bug: "300477393"
-}
\ No newline at end of file
+}
+
+flag {
+    name: "batch_jobs_on_network_activation"
+    namespace: "backstage_power"
+    description: "Have JobScheduler attempt to delay the start of some connectivity jobs until the network is actually active"
+    bug: "318394184"
+}
diff --git a/core/api/current.txt b/core/api/current.txt
index 7ab234a..5abb92b 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -4369,7 +4369,7 @@
     method public final android.media.session.MediaController getMediaController();
     method @NonNull public android.view.MenuInflater getMenuInflater();
     method @NonNull public android.window.OnBackInvokedDispatcher getOnBackInvokedDispatcher();
-    method public final android.app.Activity getParent();
+    method @Deprecated public final android.app.Activity getParent();
     method @Nullable public android.content.Intent getParentActivityIntent();
     method public android.content.SharedPreferences getPreferences(int);
     method @Nullable public android.net.Uri getReferrer();
@@ -4387,7 +4387,7 @@
     method public void invalidateOptionsMenu();
     method public boolean isActivityTransitionRunning();
     method public boolean isChangingConfigurations();
-    method public final boolean isChild();
+    method @Deprecated public final boolean isChild();
     method public boolean isDestroyed();
     method public boolean isFinishing();
     method public boolean isImmersive();
@@ -12368,6 +12368,7 @@
 
   public final class ModuleInfo implements android.os.Parcelable {
     method public int describeContents();
+    method @FlaggedApi("android.content.pm.provide_info_of_apk_in_apex") @NonNull public java.util.Collection<java.lang.String> getApkInApexPackageNames();
     method @Nullable public CharSequence getName();
     method @Nullable public String getPackageName();
     method public boolean isHidden();
@@ -12378,6 +12379,7 @@
   public class PackageInfo implements android.os.Parcelable {
     ctor public PackageInfo();
     method public int describeContents();
+    method @FlaggedApi("android.content.pm.provide_info_of_apk_in_apex") @Nullable public String getApexPackageName();
     method @FlaggedApi("android.content.pm.archiving") public long getArchiveTimeMillis();
     method public long getLongVersionCode();
     method public void setLongVersionCode(long);
@@ -53956,8 +53958,10 @@
     field public static final String PROPERTY_CAMERA_COMPAT_ALLOW_REFRESH = "android.window.PROPERTY_CAMERA_COMPAT_ALLOW_REFRESH";
     field public static final String PROPERTY_CAMERA_COMPAT_ENABLE_REFRESH_VIA_PAUSE = "android.window.PROPERTY_CAMERA_COMPAT_ENABLE_REFRESH_VIA_PAUSE";
     field public static final String PROPERTY_COMPAT_ALLOW_DISPLAY_ORIENTATION_OVERRIDE = "android.window.PROPERTY_COMPAT_ALLOW_DISPLAY_ORIENTATION_OVERRIDE";
+    field @FlaggedApi("com.android.window.flags.app_compat_properties_api") public static final String PROPERTY_COMPAT_ALLOW_IGNORING_ORIENTATION_REQUEST_WHEN_LOOP_DETECTED = "android.window.PROPERTY_COMPAT_ALLOW_IGNORING_ORIENTATION_REQUEST_WHEN_LOOP_DETECTED";
     field @FlaggedApi("com.android.window.flags.app_compat_properties_api") public static final String PROPERTY_COMPAT_ALLOW_MIN_ASPECT_RATIO_OVERRIDE = "android.window.PROPERTY_COMPAT_ALLOW_MIN_ASPECT_RATIO_OVERRIDE";
     field public static final String PROPERTY_COMPAT_ALLOW_ORIENTATION_OVERRIDE = "android.window.PROPERTY_COMPAT_ALLOW_ORIENTATION_OVERRIDE";
+    field @FlaggedApi("com.android.window.flags.app_compat_properties_api") public static final String PROPERTY_COMPAT_ALLOW_RESIZEABLE_ACTIVITY_OVERRIDES = "android.window.PROPERTY_COMPAT_ALLOW_RESIZEABLE_ACTIVITY_OVERRIDES";
     field public static final String PROPERTY_COMPAT_ALLOW_SANDBOXING_VIEW_BOUNDS_APIS = "android.window.PROPERTY_COMPAT_ALLOW_SANDBOXING_VIEW_BOUNDS_APIS";
     field public static final String PROPERTY_COMPAT_ENABLE_FAKE_FOCUS = "android.window.PROPERTY_COMPAT_ENABLE_FAKE_FOCUS";
     field public static final String PROPERTY_COMPAT_IGNORE_REQUESTED_ORIENTATION = "android.window.PROPERTY_COMPAT_IGNORE_REQUESTED_ORIENTATION";
diff --git a/core/api/test-current.txt b/core/api/test-current.txt
index ad8b685..572be19 100644
--- a/core/api/test-current.txt
+++ b/core/api/test-current.txt
@@ -1010,6 +1010,7 @@
     field public static final long OVERRIDE_CAMERA_COMPAT_DISABLE_REFRESH = 264304459L; // 0xfc0f74bL
     field public static final long OVERRIDE_CAMERA_COMPAT_ENABLE_REFRESH_VIA_PAUSE = 264301586L; // 0xfc0ec12L
     field public static final long OVERRIDE_ENABLE_COMPAT_FAKE_FOCUS = 263259275L; // 0xfb1048bL
+    field @FlaggedApi("com.android.window.flags.app_compat_properties_api") public static final long OVERRIDE_ENABLE_COMPAT_IGNORE_ORIENTATION_REQUEST_WHEN_LOOP_DETECTED = 273509367L; // 0x104d6bf7L
     field public static final long OVERRIDE_ENABLE_COMPAT_IGNORE_REQUESTED_ORIENTATION = 254631730L; // 0xf2d5f32L
     field public static final long OVERRIDE_LANDSCAPE_ORIENTATION_TO_REVERSE_LANDSCAPE = 266124927L; // 0xfdcbe7fL
     field public static final long OVERRIDE_MIN_ASPECT_RATIO = 174042980L; // 0xa5faf64L
diff --git a/core/java/android/accounts/AbstractAccountAuthenticator.java b/core/java/android/accounts/AbstractAccountAuthenticator.java
index 45515dd..c1c5c0e 100644
--- a/core/java/android/accounts/AbstractAccountAuthenticator.java
+++ b/core/java/android/accounts/AbstractAccountAuthenticator.java
@@ -142,10 +142,7 @@
     private static final String KEY_ACCOUNT =
             "android.accounts.AbstractAccountAuthenticator.KEY_ACCOUNT";
 
-    private final Context mContext;
-
     public AbstractAccountAuthenticator(Context context) {
-        mContext = context;
     }
 
     private class Transport extends IAccountAuthenticator.Stub {
diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java
index 5674a10..5d4d5e2 100644
--- a/core/java/android/app/Activity.java
+++ b/core/java/android/app/Activity.java
@@ -1174,12 +1174,23 @@
         return mApplication;
     }
 
-    /** Is this activity embedded inside of another activity? */
+    /**
+     * Whether this is a child {@link Activity} of an {@link ActivityGroup}.
+     *
+     * @deprecated {@link ActivityGroup} is deprecated.
+     */
+    @Deprecated
     public final boolean isChild() {
         return mParent != null;
     }
 
-    /** Return the parent activity if this view is an embedded child. */
+    /**
+     * Returns the parent {@link Activity} if this is a child {@link Activity} of an
+     * {@link ActivityGroup}.
+     *
+     * @deprecated {@link ActivityGroup} is deprecated.
+     */
+    @Deprecated
     public final Activity getParent() {
         return mParent;
     }
diff --git a/core/java/android/app/AppCompatTaskInfo.java b/core/java/android/app/AppCompatTaskInfo.java
index a998ff2..0bae5e6 100644
--- a/core/java/android/app/AppCompatTaskInfo.java
+++ b/core/java/android/app/AppCompatTaskInfo.java
@@ -97,6 +97,11 @@
     public boolean isUserFullscreenOverrideEnabled;
 
     /**
+     * Whether the system has forced the activity to be fullscreen
+     */
+    public boolean isSystemFullscreenOverrideEnabled;
+
+    /**
      * Hint about the letterbox state of the top activity.
      */
     public boolean topActivityBoundsLetterboxed;
@@ -202,7 +207,8 @@
                 && topActivityLetterboxHeight == that.topActivityLetterboxHeight
                 && topActivityLetterboxHorizontalPosition
                     == that.topActivityLetterboxHorizontalPosition
-                && isUserFullscreenOverrideEnabled == that.isUserFullscreenOverrideEnabled;
+                && isUserFullscreenOverrideEnabled == that.isUserFullscreenOverrideEnabled
+                && isSystemFullscreenOverrideEnabled == that.isSystemFullscreenOverrideEnabled;
     }
 
     /**
@@ -224,7 +230,8 @@
                 && topActivityLetterboxWidth == that.topActivityLetterboxWidth
                 && topActivityLetterboxHeight == that.topActivityLetterboxHeight
                 && cameraCompatControlState == that.cameraCompatControlState
-                && isUserFullscreenOverrideEnabled == that.isUserFullscreenOverrideEnabled;
+                && isUserFullscreenOverrideEnabled == that.isUserFullscreenOverrideEnabled
+                && isSystemFullscreenOverrideEnabled == that.isSystemFullscreenOverrideEnabled;
     }
 
     /**
@@ -243,6 +250,7 @@
         topActivityLetterboxWidth = source.readInt();
         topActivityLetterboxHeight = source.readInt();
         isUserFullscreenOverrideEnabled = source.readBoolean();
+        isSystemFullscreenOverrideEnabled = source.readBoolean();
     }
 
     /**
@@ -262,6 +270,7 @@
         dest.writeInt(topActivityLetterboxWidth);
         dest.writeInt(topActivityLetterboxHeight);
         dest.writeBoolean(isUserFullscreenOverrideEnabled);
+        dest.writeBoolean(isSystemFullscreenOverrideEnabled);
     }
 
     @Override
@@ -280,6 +289,7 @@
                 + " topActivityLetterboxWidth=" + topActivityLetterboxWidth
                 + " topActivityLetterboxHeight=" + topActivityLetterboxHeight
                 + " isUserFullscreenOverrideEnabled=" + isUserFullscreenOverrideEnabled
+                + " isSystemFullscreenOverrideEnabled=" + isSystemFullscreenOverrideEnabled
                 + " cameraCompatControlState="
                 + cameraCompatControlStateToString(cameraCompatControlState)
                 + "}";
diff --git a/core/java/android/app/AppOpsManager.java b/core/java/android/app/AppOpsManager.java
index 4b24b1f..1db1caf 100644
--- a/core/java/android/app/AppOpsManager.java
+++ b/core/java/android/app/AppOpsManager.java
@@ -71,6 +71,7 @@
 import android.provider.DeviceConfig;
 import android.util.ArrayMap;
 import android.util.ArraySet;
+import android.util.Log;
 import android.util.LongSparseArray;
 import android.util.LongSparseLongArray;
 import android.util.Pools;
@@ -7705,6 +7706,14 @@
     @RequiresPermission(android.Manifest.permission.MANAGE_APP_OPS_MODES)
     public void setUidMode(int code, int uid, @Mode int mode) {
         try {
+            // TODO(b/302609140): Remove extra logging after this issue is diagnosed.
+            if (code == OP_BLUETOOTH_CONNECT) {
+                Log.i(DEBUG_LOGGING_TAG,
+                        "setUidMode called for OP_BLUETOOTH_CONNECT with mode: " + mode
+                                + " for uid: " + uid + " calling uid: " + Binder.getCallingUid()
+                                + " trace: "
+                                + Arrays.toString(Thread.currentThread().getStackTrace()));
+            }
             mService.setUidMode(code, uid, mode);
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
@@ -7725,6 +7734,15 @@
     @RequiresPermission(android.Manifest.permission.MANAGE_APP_OPS_MODES)
     public void setUidMode(@NonNull String appOp, int uid, @Mode int mode) {
         try {
+            // TODO(b/302609140): Remove extra logging after this issue is diagnosed.
+            if (appOp.equals(OPSTR_BLUETOOTH_CONNECT)) {
+                Log.i(DEBUG_LOGGING_TAG,
+                        "setUidMode called for OPSTR_BLUETOOTH_CONNECT with mode: " + mode
+                                + " for uid: " + uid + " calling uid: " + Binder.getCallingUid()
+                                + " trace: "
+                                + Arrays.toString(Thread.currentThread().getStackTrace()));
+            }
+
             mService.setUidMode(AppOpsManager.strOpToOp(appOp), uid, mode);
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
@@ -7765,6 +7783,14 @@
     @RequiresPermission(android.Manifest.permission.MANAGE_APP_OPS_MODES)
     public void setMode(int code, int uid, String packageName, @Mode int mode) {
         try {
+            // TODO(b/302609140): Remove extra logging after this issue is diagnosed.
+            if (code == OP_BLUETOOTH_CONNECT) {
+                Log.i(DEBUG_LOGGING_TAG,
+                        "setMode called for OPSTR_BLUETOOTH_CONNECT with mode: " + mode
+                                + " for uid: " + uid + " calling uid: " + Binder.getCallingUid()
+                                + " trace: "
+                                + Arrays.toString(Thread.currentThread().getStackTrace()));
+            }
             mService.setMode(code, uid, packageName, mode);
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
@@ -7787,6 +7813,14 @@
     public void setMode(@NonNull String op, int uid, @Nullable String packageName,
             @Mode int mode) {
         try {
+            // TODO(b/302609140): Remove extra logging after this issue is diagnosed.
+            if (op.equals(OPSTR_BLUETOOTH_CONNECT)) {
+                Log.i(DEBUG_LOGGING_TAG,
+                        "setMode called for OPSTR_BLUETOOTH_CONNECT with mode: " + mode
+                                + " for uid: " + uid + " calling uid: " + Binder.getCallingUid()
+                                + " trace: "
+                                + Arrays.toString(Thread.currentThread().getStackTrace()));
+            }
             mService.setMode(strOpToOp(op), uid, packageName, mode);
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
diff --git a/core/java/android/app/AutomaticZenRule.java b/core/java/android/app/AutomaticZenRule.java
index 343348b..f9ab55e 100644
--- a/core/java/android/app/AutomaticZenRule.java
+++ b/core/java/android/app/AutomaticZenRule.java
@@ -70,6 +70,8 @@
     public static final int TYPE_SCHEDULE_CALENDAR = 2;
     /**
      * The type for rules triggered by bedtime/sleeping, like time of day, or snore detection.
+     *
+     * <p>Only the 'Wellbeing' app may own rules of this type.
      */
     @FlaggedApi(Flags.FLAG_MODES_API)
     public static final int TYPE_BEDTIME = 3;
@@ -95,6 +97,8 @@
     /**
      * The type for rules created and managed by a device owner. These rules may not be fully
      * editable by the device user.
+     *
+     * <p>Only a 'Device Owner' app may own rules of this type.
      */
     @FlaggedApi(Flags.FLAG_MODES_API)
     public static final int TYPE_MANAGED = 7;
diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java
index 4a6349b1..5c42b0e 100644
--- a/core/java/android/app/admin/DevicePolicyManager.java
+++ b/core/java/android/app/admin/DevicePolicyManager.java
@@ -2598,8 +2598,8 @@
      * There can be at most one app that has this delegation.
      * If another app already had delegated certificate selection access,
      * it will lose the delegation when a new app is delegated.
-     * <p> The delegaetd app can also call {@link #grantKeyPairToApp} and
-     * {@link #revokeKeyPairFromApp} to directly grant KeyCain keys to other apps.
+     * <p> The delegated app can also call {@link #grantKeyPairToApp} and
+     * {@link #revokeKeyPairFromApp} to directly grant KeyChain keys to other apps.
      * <p> Can be granted by Device Owner or Profile Owner.
      */
     public static final String DELEGATION_CERT_SELECTION = "delegation-cert-selection";
diff --git a/core/java/android/appwidget/flags.aconfig b/core/java/android/appwidget/flags.aconfig
index ec2e5fe..084cba3 100644
--- a/core/java/android/appwidget/flags.aconfig
+++ b/core/java/android/appwidget/flags.aconfig
@@ -20,3 +20,10 @@
   description: "Move state file IO to non-critical path"
   bug: "312949280"
 }
+
+flag {
+  name: "draw_data_parcel"
+  namespace: "app_widgets"
+  description: "Enable support for transporting draw instructions as data parcel"
+  bug: "286130467"
+}
diff --git a/core/java/android/content/pm/ActivityInfo.java b/core/java/android/content/pm/ActivityInfo.java
index 12da665..30871e9 100644
--- a/core/java/android/content/pm/ActivityInfo.java
+++ b/core/java/android/content/pm/ActivityInfo.java
@@ -16,6 +16,7 @@
 
 package android.content.pm;
 
+import android.annotation.FlaggedApi;
 import android.annotation.FloatRange;
 import android.annotation.IntDef;
 import android.annotation.NonNull;
@@ -42,6 +43,7 @@
 import android.window.OnBackInvokedCallback;
 
 import com.android.internal.util.Parcelling;
+import com.android.window.flags.Flags;
 
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
@@ -1099,6 +1101,8 @@
     @ChangeId
     @Overridable
     @Disabled
+    @TestApi
+    @FlaggedApi(Flags.FLAG_APP_COMPAT_PROPERTIES_API)
     public static final long OVERRIDE_ENABLE_COMPAT_IGNORE_ORIENTATION_REQUEST_WHEN_LOOP_DETECTED =
             273509367L; // buganizer id
 
diff --git a/core/java/android/content/pm/ModuleInfo.java b/core/java/android/content/pm/ModuleInfo.java
index a7306a3..a1c8747 100644
--- a/core/java/android/content/pm/ModuleInfo.java
+++ b/core/java/android/content/pm/ModuleInfo.java
@@ -16,10 +16,15 @@
 
 package android.content.pm;
 
+import android.annotation.FlaggedApi;
+import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.os.Parcel;
 import android.os.Parcelable;
 
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
 import java.util.Objects;
 
 /**
@@ -46,6 +51,13 @@
     /** Whether or not this module is hidden from the user. */
     private boolean mHidden;
 
+    /**
+     * The list of the package names of all APK-in-APEX apps in the module, or
+     * null if there are none.
+     */
+    @Nullable
+    private List<String> mApkInApexPackageNames;
+
     // TODO: Decide whether we need an additional metadata bundle to support out of band
     // updates to ModuleInfo.
     //
@@ -61,6 +73,9 @@
         mPackageName = orig.mPackageName;
         mHidden = orig.mHidden;
         mApexModuleName = orig.mApexModuleName;
+        if (orig.mApkInApexPackageNames != null) {
+            mApkInApexPackageNames = List.copyOf(orig.mApkInApexPackageNames);
+        }
     }
 
     /** @hide Sets the public name of this module. */
@@ -107,6 +122,25 @@
         return mApexModuleName;
     }
 
+    /** @hide Sets the list of the package name of APK-in-APEX apps in this module. */
+    public ModuleInfo setApkInApexPackageNames(@NonNull Collection<String> apkInApexPackageNames) {
+        Objects.requireNonNull(apkInApexPackageNames);
+        mApkInApexPackageNames = List.copyOf(apkInApexPackageNames);
+        return this;
+    }
+
+    /**
+     * Gets the list of the package name of all APK-in-APEX apps in the module.
+     */
+    @NonNull
+    @FlaggedApi(android.content.pm.Flags.FLAG_PROVIDE_INFO_OF_APK_IN_APEX)
+    public Collection<String> getApkInApexPackageNames() {
+        if (mApkInApexPackageNames == null) {
+            return Collections.emptyList();
+        }
+        return mApkInApexPackageNames;
+    }
+
     /** Returns a string representation of this object. */
     public String toString() {
         return "ModuleInfo{"
@@ -125,6 +159,7 @@
         hashCode = 31 * hashCode + Objects.hashCode(mName);
         hashCode = 31 * hashCode + Objects.hashCode(mPackageName);
         hashCode = 31 * hashCode + Objects.hashCode(mApexModuleName);
+        hashCode = 31 * hashCode + Objects.hashCode(mApkInApexPackageNames);
         hashCode = 31 * hashCode + Boolean.hashCode(mHidden);
         return hashCode;
     }
@@ -138,6 +173,7 @@
         return Objects.equals(mName, other.mName)
                 && Objects.equals(mPackageName, other.mPackageName)
                 && Objects.equals(mApexModuleName, other.mApexModuleName)
+                && Objects.equals(mApkInApexPackageNames, other.mApkInApexPackageNames)
                 && mHidden == other.mHidden;
     }
 
@@ -147,6 +183,8 @@
         dest.writeString(mPackageName);
         dest.writeBoolean(mHidden);
         dest.writeString(mApexModuleName);
+        // Parcel#writeStringList handles null case, we can use it directly
+        dest.writeStringList(mApkInApexPackageNames);
     }
 
     private ModuleInfo(Parcel source) {
@@ -154,6 +192,7 @@
         mPackageName = source.readString();
         mHidden = source.readBoolean();
         mApexModuleName = source.readString();
+        mApkInApexPackageNames = source.createStringArrayList();
     }
 
     public static final @android.annotation.NonNull Parcelable.Creator<ModuleInfo> CREATOR =
diff --git a/core/java/android/content/pm/PackageInfo.java b/core/java/android/content/pm/PackageInfo.java
index 4f61613..c1c9928 100644
--- a/core/java/android/content/pm/PackageInfo.java
+++ b/core/java/android/content/pm/PackageInfo.java
@@ -499,6 +499,16 @@
      */
     public boolean isActiveApex;
 
+    /**
+     * If the package is an APEX package (i.e. the value of {@link #isApex}
+     * is true), this field is the package name of the APEX. If the package
+     * is one APK-in-APEX app, this field is the package name of the parent
+     * APEX that contains the app. If the package is not one of the above
+     * two cases, this field is {@code null}.
+     */
+    @Nullable
+    private String mApexPackageName;
+
     public PackageInfo() {
     }
 
@@ -535,6 +545,26 @@
         mArchiveTimeMillis = value;
     }
 
+    /**
+     * If the package is an APEX package (i.e. the value of {@link #isApex}
+     * is true), returns the package name of the APEX. If the package
+     * is one APK-in-APEX app, returns the package name of the parent
+     * APEX that contains the app. If the package is not one of the above
+     * two cases, returns {@code null}.
+     */
+    @Nullable
+    @FlaggedApi(android.content.pm.Flags.FLAG_PROVIDE_INFO_OF_APK_IN_APEX)
+    public String getApexPackageName() {
+        return mApexPackageName;
+    }
+
+    /**
+     * @hide
+     */
+    public void setApexPackageName(@Nullable String apexPackageName) {
+        mApexPackageName = apexPackageName;
+    }
+
     @Override
     public String toString() {
         return "PackageInfo{"
@@ -603,6 +633,12 @@
         dest.writeBoolean(isApex);
         dest.writeBoolean(isActiveApex);
         dest.writeLong(mArchiveTimeMillis);
+        if (mApexPackageName != null) {
+            dest.writeInt(1);
+            dest.writeString8(mApexPackageName);
+        } else {
+            dest.writeInt(0);
+        }
         dest.restoreAllowSquashing(prevAllowSquashing);
     }
 
@@ -669,5 +705,9 @@
         isApex = source.readBoolean();
         isActiveApex = source.readBoolean();
         mArchiveTimeMillis = source.readLong();
+        int hasApexPackageName = source.readInt();
+        if (hasApexPackageName != 0) {
+            mApexPackageName = source.readString8();
+        }
     }
 }
diff --git a/core/java/android/content/rollback/OWNERS b/core/java/android/content/rollback/OWNERS
index 3093fd6..8e5a0d8 100644
--- a/core/java/android/content/rollback/OWNERS
+++ b/core/java/android/content/rollback/OWNERS
@@ -1,5 +1,5 @@
-# Bug component: 557916
+# Bug component: 819107
 
-narayan@google.com
-nandana@google.com
-olilan@google.com
+ancr@google.com
+harshitmahajan@google.com
+robertogil@google.com
diff --git a/core/java/android/credentials/CredentialManager.java b/core/java/android/credentials/CredentialManager.java
index ad3ccc4..3fcb3da 100644
--- a/core/java/android/credentials/CredentialManager.java
+++ b/core/java/android/credentials/CredentialManager.java
@@ -37,6 +37,7 @@
 import android.os.RemoteException;
 import android.provider.DeviceConfig;
 import android.util.Log;
+import android.view.autofill.IAutoFillManagerClient;
 
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
@@ -135,7 +136,8 @@
             @Nullable CancellationSignal cancellationSignal,
             @CallbackExecutor @NonNull Executor executor,
             @NonNull OutcomeReceiver<GetCandidateCredentialsResponse,
-                    GetCandidateCredentialsException> callback
+                    GetCandidateCredentialsException> callback,
+            @NonNull IAutoFillManagerClient clientCallback
     ) {
         requireNonNull(request, "request must not be null");
         requireNonNull(callingPackage, "callingPackage must not be null");
@@ -153,6 +155,7 @@
                     mService.getCandidateCredentials(
                             request,
                             new GetCandidateCredentialsTransport(executor, callback),
+                            clientCallback,
                             callingPackage);
         } catch (RemoteException e) {
             e.rethrowFromSystemServer();
diff --git a/core/java/android/credentials/GetCandidateCredentialsResponse.java b/core/java/android/credentials/GetCandidateCredentialsResponse.java
index 1b130a9..530fead 100644
--- a/core/java/android/credentials/GetCandidateCredentialsResponse.java
+++ b/core/java/android/credentials/GetCandidateCredentialsResponse.java
@@ -18,6 +18,7 @@
 
 import android.annotation.Hide;
 import android.annotation.NonNull;
+import android.app.PendingIntent;
 import android.credentials.ui.GetCredentialProviderData;
 import android.os.Parcel;
 import android.os.Parcelable;
@@ -35,22 +36,39 @@
  */
 @Hide
 public final class GetCandidateCredentialsResponse implements Parcelable {
-    // TODO(b/299321990): Add members
-
     @NonNull
     private final List<GetCredentialProviderData> mCandidateProviderDataList;
 
+    private final PendingIntent mPendingIntent;
+
+    private final GetCredentialResponse mGetCredentialResponse;
+
     /**
      * @hide
      */
     @Hide
     public GetCandidateCredentialsResponse(
-            List<GetCredentialProviderData> candidateProviderDataList
+            GetCredentialResponse getCredentialResponse
+    ) {
+        mCandidateProviderDataList = null;
+        mPendingIntent = null;
+        mGetCredentialResponse = getCredentialResponse;
+    }
+
+    /**
+     * @hide
+     */
+    @Hide
+    public GetCandidateCredentialsResponse(
+            List<GetCredentialProviderData> candidateProviderDataList,
+            PendingIntent pendingIntent
     ) {
         Preconditions.checkCollectionNotEmpty(
                 candidateProviderDataList,
                 /*valueName=*/ "candidateProviderDataList");
         mCandidateProviderDataList = new ArrayList<>(candidateProviderDataList);
+        mPendingIntent = pendingIntent;
+        mGetCredentialResponse = null;
     }
 
     /**
@@ -62,17 +80,40 @@
         return mCandidateProviderDataList;
     }
 
+    /**
+     * Returns candidate provider data list.
+     *
+     * @hide
+     */
+    public GetCredentialResponse getGetCredentialResponse() {
+        return mGetCredentialResponse;
+    }
+
+    /**
+     * Returns candidate provider data list.
+     *
+     * @hide
+     */
+    public PendingIntent getPendingIntent() {
+        return mPendingIntent;
+    }
+
     protected GetCandidateCredentialsResponse(Parcel in) {
         List<GetCredentialProviderData> candidateProviderDataList = new ArrayList<>();
         in.readTypedList(candidateProviderDataList, GetCredentialProviderData.CREATOR);
         mCandidateProviderDataList = candidateProviderDataList;
 
         AnnotationValidations.validate(NonNull.class, null, mCandidateProviderDataList);
+
+        mPendingIntent = in.readTypedObject(PendingIntent.CREATOR);
+        mGetCredentialResponse = in.readTypedObject(GetCredentialResponse.CREATOR);
     }
 
     @Override
     public void writeToParcel(Parcel dest, int flags) {
         dest.writeTypedList(mCandidateProviderDataList);
+        dest.writeTypedObject(mPendingIntent, flags);
+        dest.writeTypedObject(mGetCredentialResponse, flags);
     }
 
     @Override
diff --git a/core/java/android/credentials/ICredentialManager.aidl b/core/java/android/credentials/ICredentialManager.aidl
index d081576..726bc97 100644
--- a/core/java/android/credentials/ICredentialManager.aidl
+++ b/core/java/android/credentials/ICredentialManager.aidl
@@ -22,6 +22,7 @@
 import android.credentials.ClearCredentialStateRequest;
 import android.credentials.CreateCredentialRequest;
 import android.credentials.GetCandidateCredentialsRequest;
+import android.view.autofill.IAutoFillManagerClient;
 import android.credentials.GetCredentialRequest;
 import android.credentials.RegisterCredentialDescriptionRequest;
 import android.credentials.UnregisterCredentialDescriptionRequest;
@@ -47,7 +48,7 @@
 
     @nullable ICancellationSignal executeCreateCredential(in CreateCredentialRequest request, in ICreateCredentialCallback callback, String callingPackage);
 
-    @nullable ICancellationSignal getCandidateCredentials(in GetCredentialRequest request, in IGetCandidateCredentialsCallback callback, String callingPackage);
+    @nullable ICancellationSignal getCandidateCredentials(in GetCredentialRequest request, in IGetCandidateCredentialsCallback callback, in IAutoFillManagerClient clientCallback, String callingPackage);
 
     @nullable ICancellationSignal clearCredentialState(in ClearCredentialStateRequest request, in IClearCredentialStateCallback callback, String callingPackage);
 
diff --git a/core/java/android/credentials/ui/Constants.java b/core/java/android/credentials/ui/Constants.java
index 7092f29..37f850b 100644
--- a/core/java/android/credentials/ui/Constants.java
+++ b/core/java/android/credentials/ui/Constants.java
@@ -29,6 +29,13 @@
     public static final String EXTRA_RESULT_RECEIVER =
             "android.credentials.ui.extra.RESULT_RECEIVER";
 
+    /**
+     * The intent extra key for indicating whether the bottom sheet should be started directly
+     * on the 'All Options' screen.
+     */
+    public static final String EXTRA_REQ_FOR_ALL_OPTIONS =
+            "android.credentials.ui.extra.REQ_FOR_ALL_OPTIONS";
+
     /** The intent action for when the enabled Credential Manager providers has been updated. */
     public static final String CREDMAN_ENABLED_PROVIDERS_UPDATED =
             "android.credentials.ui.action.CREDMAN_ENABLED_PROVIDERS_UPDATED";
diff --git a/core/java/android/credentials/ui/IntentFactory.java b/core/java/android/credentials/ui/IntentFactory.java
index 5e8372d..49321d5 100644
--- a/core/java/android/credentials/ui/IntentFactory.java
+++ b/core/java/android/credentials/ui/IntentFactory.java
@@ -35,6 +35,31 @@
  */
 @TestApi
 public class IntentFactory {
+
+    /**
+     * Generate a new launch intent to the Credential Selector UI.
+     *
+     * @hide
+     */
+    @NonNull
+    public static Intent createCredentialSelectorIntent(
+            @NonNull RequestInfo requestInfo,
+            @SuppressLint("ConcreteCollection") // Concrete collection needed for marshalling.
+            @NonNull
+            ArrayList<ProviderData> enabledProviderDataList,
+            @SuppressLint("ConcreteCollection") // Concrete collection needed for marshalling.
+            @NonNull
+            ArrayList<DisabledProviderData> disabledProviderDataList,
+            @NonNull ResultReceiver resultReceiver,
+            boolean isRequestForAllOptions) {
+
+        Intent intent = createCredentialSelectorIntent(requestInfo, enabledProviderDataList,
+                disabledProviderDataList, resultReceiver);
+        intent.putExtra(Constants.EXTRA_REQ_FOR_ALL_OPTIONS, isRequestForAllOptions);
+
+        return intent;
+    }
+
     /** Generate a new launch intent to the Credential Selector UI. */
     @NonNull
     public static Intent createCredentialSelectorIntent(
diff --git a/core/java/android/hardware/HardwareBuffer.java b/core/java/android/hardware/HardwareBuffer.java
index e714887..f5b3a7b 100644
--- a/core/java/android/hardware/HardwareBuffer.java
+++ b/core/java/android/hardware/HardwareBuffer.java
@@ -114,10 +114,16 @@
     /** Format: 8 bits red */
     @FlaggedApi(com.android.graphics.hwui.flags.Flags.FLAG_REQUESTED_FORMATS_V)
     public static final int R_8           = 0x38;
-    /** Format: 16 bits red */
+    /**
+     * Format: 16 bits red. Bits should be represented in unsigned integer, instead of the
+     * implicit unsigned normalized.
+     */
     @FlaggedApi(com.android.graphics.hwui.flags.Flags.FLAG_REQUESTED_FORMATS_V)
     public static final int R_16_UINT     = 0x39;
-    /** Format: 16 bits each red, green */
+    /**
+     * Format: 16 bits each red, green. Bits should be represented in unsigned integer,
+     * instead of the implicit unsigned normalized.
+     */
     @FlaggedApi(com.android.graphics.hwui.flags.Flags.FLAG_REQUESTED_FORMATS_V)
     public static final int RG_1616_UINT  = 0x3a;
     /** Format: 10 bits each red, green, blue, alpha */
diff --git a/core/java/android/hardware/input/IInputManager.aidl b/core/java/android/hardware/input/IInputManager.aidl
index 6626baf..7bea9ae 100644
--- a/core/java/android/hardware/input/IInputManager.aidl
+++ b/core/java/android/hardware/input/IInputManager.aidl
@@ -25,6 +25,7 @@
 import android.hardware.input.IInputDeviceBatteryState;
 import android.hardware.input.IKeyboardBacklightListener;
 import android.hardware.input.IKeyboardBacklightState;
+import android.hardware.input.IStickyModifierStateListener;
 import android.hardware.input.ITabletModeChangedListener;
 import android.hardware.input.TouchCalibration;
 import android.os.CombinedVibration;
@@ -241,4 +242,14 @@
     void unregisterKeyboardBacklightListener(IKeyboardBacklightListener listener);
 
     HostUsiVersion getHostUsiVersionFromDisplayConfig(int displayId);
+
+    @EnforcePermission("MONITOR_STICKY_MODIFIER_STATE")
+    @JavaPassthrough(annotation="@android.annotation.RequiresPermission(value = "
+            + "android.Manifest.permission.MONITOR_STICKY_MODIFIER_STATE)")
+    void registerStickyModifierStateListener(IStickyModifierStateListener listener);
+
+    @EnforcePermission("MONITOR_STICKY_MODIFIER_STATE")
+    @JavaPassthrough(annotation="@android.annotation.RequiresPermission(value = "
+            + "android.Manifest.permission.MONITOR_STICKY_MODIFIER_STATE)")
+    void unregisterStickyModifierStateListener(IStickyModifierStateListener listener);
 }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/di/bound/CustomTileUser.kt b/core/java/android/hardware/input/IStickyModifierStateListener.aidl
similarity index 61%
copy from packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/di/bound/CustomTileUser.kt
copy to core/java/android/hardware/input/IStickyModifierStateListener.aidl
index efc7431..bd139ab 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/di/bound/CustomTileUser.kt
+++ b/core/java/android/hardware/input/IStickyModifierStateListener.aidl
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2023 The Android Open Source Project
+ * 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.
@@ -14,12 +14,13 @@
  * limitations under the License.
  */
 
-package com.android.systemui.qs.tiles.impl.custom.di.bound
+package android.hardware.input;
 
-import javax.inject.Qualifier
+/** @hide */
+oneway interface IStickyModifierStateListener {
 
-/** User associated with current custom tile binding. */
-@Qualifier
-@MustBeDocumented
-@Retention(AnnotationRetention.RUNTIME)
-annotation class CustomTileUser
+    /**
+     * Called when the sticky modifier state is changed when A11y Sticky keys feature is enabled
+     */
+    void onStickyModifierStateChanged(int modifierState, int lockedModifierState);
+}
diff --git a/core/java/android/hardware/input/InputManager.java b/core/java/android/hardware/input/InputManager.java
index f941ad8..4ebbde7 100644
--- a/core/java/android/hardware/input/InputManager.java
+++ b/core/java/android/hardware/input/InputManager.java
@@ -1297,6 +1297,42 @@
     }
 
     /**
+     * Registers a Sticky modifier state change listener to be notified about {@link
+     * StickyModifierState} changes.
+     *
+     * @param executor an executor on which the callback will be called
+     * @param listener the {@link StickyModifierStateListener}
+     * @throws IllegalArgumentException if {@code listener} has already been registered previously.
+     * @throws NullPointerException     if {@code listener} or {@code executor} is null.
+     * @hide
+     * @see #unregisterStickyModifierStateListener(StickyModifierStateListener)
+     */
+    @RequiresPermission(Manifest.permission.MONITOR_STICKY_MODIFIER_STATE)
+    public void registerStickyModifierStateListener(@NonNull Executor executor,
+            @NonNull StickyModifierStateListener listener) throws IllegalArgumentException {
+        if (!InputSettings.isAccessibilityStickyKeysFeatureEnabled()) {
+            return;
+        }
+        mGlobal.registerStickyModifierStateListener(executor, listener);
+    }
+
+    /**
+     * Unregisters a previously added Sticky modifier state change listener.
+     *
+     * @param listener the {@link StickyModifierStateListener}
+     * @hide
+     * @see #registerStickyModifierStateListener(Executor, StickyModifierStateListener)
+     */
+    @RequiresPermission(Manifest.permission.MONITOR_STICKY_MODIFIER_STATE)
+    public void unregisterStickyModifierStateListener(
+            @NonNull StickyModifierStateListener listener) {
+        if (!InputSettings.isAccessibilityStickyKeysFeatureEnabled()) {
+            return;
+        }
+        mGlobal.unregisterStickyModifierStateListener(listener);
+    }
+
+    /**
      * A callback used to be notified about battery state changes for an input device. The
      * {@link #onBatteryStateChanged(int, long, BatteryState)} method will be called once after the
      * listener is successfully registered to provide the initial battery state of the device.
@@ -1378,4 +1414,23 @@
         void onKeyboardBacklightChanged(
                 int deviceId, @NonNull KeyboardBacklightState state, boolean isTriggeredByKeyPress);
     }
+
+    /**
+     * A callback used to be notified about sticky modifier state changes when A11y Sticky keys
+     * feature is enabled.
+     *
+     * @see #registerStickyModifierStateListener(Executor, StickyModifierStateListener)
+     * @see #unregisterStickyModifierStateListener(StickyModifierStateListener)
+     * @hide
+     */
+    public interface StickyModifierStateListener {
+        /**
+         * Called when the sticky modifier state changes.
+         * This method will be called once after the listener is successfully registered to provide
+         * the initial modifier state.
+         *
+         * @param state the new sticky modifier state, never null.
+         */
+        void onStickyModifierStateChanged(@NonNull StickyModifierState state);
+    }
 }
diff --git a/core/java/android/hardware/input/InputManagerGlobal.java b/core/java/android/hardware/input/InputManagerGlobal.java
index 24a6911..7c104a0 100644
--- a/core/java/android/hardware/input/InputManagerGlobal.java
+++ b/core/java/android/hardware/input/InputManagerGlobal.java
@@ -27,6 +27,7 @@
 import android.hardware.input.InputManager.InputDeviceListener;
 import android.hardware.input.InputManager.KeyboardBacklightListener;
 import android.hardware.input.InputManager.OnTabletModeChangedListener;
+import android.hardware.input.InputManager.StickyModifierStateListener;
 import android.hardware.lights.Light;
 import android.hardware.lights.LightState;
 import android.hardware.lights.LightsManager;
@@ -52,6 +53,7 @@
 import android.view.InputEvent;
 import android.view.InputMonitor;
 import android.view.KeyCharacterMap;
+import android.view.KeyEvent;
 import android.view.PointerIcon;
 
 import com.android.internal.annotations.GuardedBy;
@@ -100,6 +102,14 @@
     @GuardedBy("mKeyboardBacklightListenerLock")
     @Nullable private IKeyboardBacklightListener mKeyboardBacklightListener;
 
+    private final Object mStickyModifierStateListenerLock = new Object();
+    @GuardedBy("mStickyModifierStateListenerLock")
+    @Nullable
+    private ArrayList<StickyModifierStateListenerDelegate> mStickyModifierStateListeners;
+    @GuardedBy("mStickyModifierStateListenerLock")
+    @Nullable
+    private IStickyModifierStateListener mStickyModifierStateListener;
+
     // InputDeviceSensorManager gets notified synchronously from the binder thread when input
     // devices change, so it must be synchronized with the input device listeners.
     @GuardedBy("mInputDeviceListeners")
@@ -905,6 +915,158 @@
         }
     }
 
+    private static final class StickyModifierStateListenerDelegate {
+        final InputManager.StickyModifierStateListener mListener;
+        final Executor mExecutor;
+
+        StickyModifierStateListenerDelegate(StickyModifierStateListener listener,
+                Executor executor) {
+            mListener = listener;
+            mExecutor = executor;
+        }
+
+        void notifyStickyModifierStateChange(int modifierState, int lockedModifierState) {
+            mExecutor.execute(() ->
+                    mListener.onStickyModifierStateChanged(
+                            new LocalStickyModifierState(modifierState, lockedModifierState)));
+        }
+    }
+
+    private class LocalStickyModifierStateListener extends IStickyModifierStateListener.Stub {
+
+        @Override
+        public void onStickyModifierStateChanged(int modifierState, int lockedModifierState) {
+            synchronized (mStickyModifierStateListenerLock) {
+                if (mStickyModifierStateListeners == null) return;
+                final int numListeners = mStickyModifierStateListeners.size();
+                for (int i = 0; i < numListeners; i++) {
+                    mStickyModifierStateListeners.get(i)
+                            .notifyStickyModifierStateChange(modifierState, lockedModifierState);
+                }
+            }
+        }
+    }
+
+    // Implementation of the android.hardware.input.StickyModifierState interface used to report
+    // the sticky modifier state via the StickyModifierStateListener interfaces.
+    private static final class LocalStickyModifierState extends StickyModifierState {
+
+        private final int mModifierState;
+        private final int mLockedModifierState;
+
+        LocalStickyModifierState(int modifierState, int lockedModifierState) {
+            mModifierState = modifierState;
+            mLockedModifierState = lockedModifierState;
+        }
+
+        @Override
+        public boolean isShiftModifierOn() {
+            return (mModifierState & KeyEvent.META_SHIFT_ON) != 0;
+        }
+
+        @Override
+        public boolean isShiftModifierLocked() {
+            return (mLockedModifierState & KeyEvent.META_SHIFT_ON) != 0;
+        }
+
+        @Override
+        public boolean isCtrlModifierOn() {
+            return (mModifierState & KeyEvent.META_CTRL_ON) != 0;
+        }
+
+        @Override
+        public boolean isCtrlModifierLocked() {
+            return (mLockedModifierState & KeyEvent.META_CTRL_ON) != 0;
+        }
+
+        @Override
+        public boolean isMetaModifierOn() {
+            return (mModifierState & KeyEvent.META_META_ON) != 0;
+        }
+
+        @Override
+        public boolean isMetaModifierLocked() {
+            return (mLockedModifierState & KeyEvent.META_META_ON) != 0;
+        }
+
+        @Override
+        public boolean isAltModifierOn() {
+            return (mModifierState & KeyEvent.META_ALT_LEFT_ON) != 0;
+        }
+
+        @Override
+        public boolean isAltModifierLocked() {
+            return (mLockedModifierState & KeyEvent.META_ALT_LEFT_ON) != 0;
+        }
+
+        @Override
+        public boolean isAltGrModifierOn() {
+            return (mModifierState & KeyEvent.META_ALT_RIGHT_ON) != 0;
+        }
+
+        @Override
+        public boolean isAltGrModifierLocked() {
+            return (mLockedModifierState & KeyEvent.META_ALT_RIGHT_ON) != 0;
+        }
+    }
+
+    /**
+     * @see InputManager#registerStickyModifierStateListener(Executor, StickyModifierStateListener)
+     */
+    @RequiresPermission(Manifest.permission.MONITOR_STICKY_MODIFIER_STATE)
+    void registerStickyModifierStateListener(@NonNull Executor executor,
+            @NonNull StickyModifierStateListener listener) throws IllegalArgumentException {
+        Objects.requireNonNull(executor, "executor should not be null");
+        Objects.requireNonNull(listener, "listener should not be null");
+
+        synchronized (mStickyModifierStateListenerLock) {
+            if (mStickyModifierStateListener == null) {
+                mStickyModifierStateListeners = new ArrayList<>();
+                mStickyModifierStateListener = new LocalStickyModifierStateListener();
+
+                try {
+                    mIm.registerStickyModifierStateListener(mStickyModifierStateListener);
+                } catch (RemoteException e) {
+                    throw e.rethrowFromSystemServer();
+                }
+            }
+            final int numListeners = mStickyModifierStateListeners.size();
+            for (int i = 0; i < numListeners; i++) {
+                if (mStickyModifierStateListeners.get(i).mListener == listener) {
+                    throw new IllegalArgumentException("Listener has already been registered!");
+                }
+            }
+            StickyModifierStateListenerDelegate delegate =
+                    new StickyModifierStateListenerDelegate(listener, executor);
+            mStickyModifierStateListeners.add(delegate);
+        }
+    }
+
+    /**
+     * @see InputManager#unregisterStickyModifierStateListener(StickyModifierStateListener)
+     */
+    @RequiresPermission(Manifest.permission.MONITOR_STICKY_MODIFIER_STATE)
+    void unregisterStickyModifierStateListener(
+            @NonNull StickyModifierStateListener listener) {
+        Objects.requireNonNull(listener, "listener should not be null");
+
+        synchronized (mStickyModifierStateListenerLock) {
+            if (mStickyModifierStateListeners == null) {
+                return;
+            }
+            mStickyModifierStateListeners.removeIf((delegate) -> delegate.mListener == listener);
+            if (mStickyModifierStateListeners.isEmpty()) {
+                try {
+                    mIm.unregisterStickyModifierStateListener(mStickyModifierStateListener);
+                } catch (RemoteException e) {
+                    throw e.rethrowFromSystemServer();
+                }
+                mStickyModifierStateListeners = null;
+                mStickyModifierStateListener = null;
+            }
+        }
+    }
+
     /**
      * @see InputManager#getKeyboardLayoutsForInputDevice(InputDeviceIdentifier)
      */
diff --git a/core/java/android/hardware/input/StickyModifierState.java b/core/java/android/hardware/input/StickyModifierState.java
new file mode 100644
index 0000000..a3f7a0a
--- /dev/null
+++ b/core/java/android/hardware/input/StickyModifierState.java
@@ -0,0 +1,127 @@
+/*
+ * 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;
+
+/**
+ * The StickyModifierState class is a representation of a modifier state when A11y Sticky keys
+ * feature is enabled
+ *
+ * @hide
+ */
+public abstract class StickyModifierState {
+
+    /**
+     * Represents whether current sticky modifier state includes 'Shift' modifier.
+     * <p> If {@code true} the next {@link android.view.KeyEvent} will contain 'Shift' modifier in
+     * its metaState.
+     *
+     * @return whether Shift modifier key is on.
+     */
+    public abstract boolean isShiftModifierOn();
+
+    /**
+     * Represents whether current sticky modifier state includes 'Shift' modifier, and it is
+     * locked.
+     * <p> If {@code true} any subsequent {@link android.view.KeyEvent} will contain 'Shift'
+     * modifier in its metaState and this state will remain sticky (will not be cleared), until
+     * user presses 'Shift' key again to clear the locked state.
+     *
+     * @return whether Shift modifier key is locked.
+     */
+    public abstract boolean isShiftModifierLocked();
+
+    /**
+     * Represents whether current sticky modifier state includes 'Ctrl' modifier.
+     * <p> If {@code true} the next {@link android.view.KeyEvent} will contain 'Ctrl' modifier in
+     * its metaState.
+     *
+     * @return whether Ctrl modifier key is on.
+     */
+    public abstract boolean isCtrlModifierOn();
+
+    /**
+     * Represents whether current sticky modifier state includes 'Ctrl' modifier, and it is
+     * locked.
+     * <p> If {@code true} any subsequent {@link android.view.KeyEvent} will contain 'Ctrl'
+     * modifier in its metaState and this state will remain sticky (will not be cleared), until
+     * user presses 'Ctrl' key again to clear the locked state.
+     *
+     * @return whether Ctrl modifier key is locked.
+     */
+    public abstract boolean isCtrlModifierLocked();
+
+    /**
+     * Represents whether current sticky modifier state includes 'Meta' modifier.
+     * <p> If {@code true} the next {@link android.view.KeyEvent} will contain 'Meta' modifier in
+     * its metaState.
+     *
+     * @return whether Meta modifier key is on.
+     */
+    public abstract boolean isMetaModifierOn();
+
+    /**
+     * Represents whether current sticky modifier state includes 'Meta' modifier, and it is
+     * locked.
+     * <p> If {@code true} any subsequent {@link android.view.KeyEvent} will contain 'Meta'
+     * modifier in its metaState and this state will remain sticky (will not be cleared), until
+     * user presses 'Meta' key again to clear the locked state.
+     *
+     * @return whether Meta modifier key is locked.
+     */
+    public abstract boolean isMetaModifierLocked();
+
+    /**
+     * Represents whether current sticky modifier state includes 'Alt' modifier.
+     * <p> If {@code true} the next {@link android.view.KeyEvent} will contain 'Alt' modifier in
+     * its metaState.
+     *
+     * @return whether Alt modifier key is on.
+     */
+    public abstract boolean isAltModifierOn();
+
+    /**
+     * Represents whether current sticky modifier state includes 'Alt' modifier, and it is
+     * locked.
+     * <p> If {@code true} any subsequent {@link android.view.KeyEvent} will contain 'Alt'
+     * modifier in its metaState and this state will remain sticky (will not be cleared), until
+     * user presses 'Alt' key again to clear the locked state.
+     *
+     * @return whether Alt modifier key is locked.
+     */
+    public abstract boolean isAltModifierLocked();
+
+    /**
+     * Represents whether current sticky modifier state includes 'AltGr' modifier.
+     * <p> If {@code true} the next {@link android.view.KeyEvent} will contain 'AltGr' modifier in
+     * its metaState.
+     *
+     * @return whether AltGr modifier key is on.
+     */
+    public abstract boolean isAltGrModifierOn();
+
+    /**
+     * Represents whether current sticky modifier state includes 'AltGr' modifier, and it is
+     * locked.
+     * <p> If {@code true} any subsequent {@link android.view.KeyEvent} will contain 'AltGr'
+     * modifier in its metaState and this state will remain sticky (will not be cleared), until
+     * user presses 'AltGr' key again to clear the locked state.
+     *
+     * @return whether AltGr modifier key is locked.
+     */
+    public abstract boolean isAltGrModifierLocked();
+}
+
diff --git a/core/java/android/inputmethodservice/InputMethodService.java b/core/java/android/inputmethodservice/InputMethodService.java
index 18d3e5e..71698e4 100644
--- a/core/java/android/inputmethodservice/InputMethodService.java
+++ b/core/java/android/inputmethodservice/InputMethodService.java
@@ -127,6 +127,7 @@
 import android.view.inputmethod.EditorInfo;
 import android.view.inputmethod.ExtractedText;
 import android.view.inputmethod.ExtractedTextRequest;
+import android.view.inputmethod.Flags;
 import android.view.inputmethod.ImeTracker;
 import android.view.inputmethod.InlineSuggestionsRequest;
 import android.view.inputmethod.InlineSuggestionsResponse;
@@ -388,6 +389,9 @@
     private long mStylusHwSessionsTimeout = STYLUS_HANDWRITING_IDLE_TIMEOUT_MS;
     private Runnable mStylusWindowIdleTimeoutRunnable;
     private long mStylusWindowIdleTimeoutForTest;
+    /** Tracks last {@link MotionEvent#getToolType(int)} used for {@link MotionEvent#ACTION_DOWN}.
+     **/
+    private int mLastUsedToolType;
 
     /**
      * Returns whether {@link InputMethodService} is responsible for rendering the back button and
@@ -1005,7 +1009,7 @@
          */
         @Override
         public void updateEditorToolType(@ToolType int toolType) {
-            onUpdateEditorToolType(toolType);
+            updateEditorToolTypeInternal(toolType);
         }
 
         /**
@@ -1249,6 +1253,14 @@
         rootView.setSystemGestureExclusionRects(exclusionRects);
     }
 
+    private void updateEditorToolTypeInternal(int toolType) {
+        if (Flags.useHandwritingListenerForTooltype()) {
+            mLastUsedToolType = toolType;
+            mInputEditorInfo.setInitialToolType(toolType);
+        }
+        onUpdateEditorToolType(toolType);
+    }
+
     /**
      * Concrete implementation of
      * {@link AbstractInputMethodService.AbstractInputMethodSessionImpl} that provides
@@ -3110,6 +3122,9 @@
                 null /* icProto */);
         mInputStarted = true;
         mStartedInputConnection = ic;
+        if (Flags.useHandwritingListenerForTooltype()) {
+            editorInfo.setInitialToolType(mLastUsedToolType);
+        }
         mInputEditorInfo = editorInfo;
         initialize();
         mInlineSuggestionSessionController.notifyOnStartInput(
@@ -3354,6 +3369,10 @@
      *         had not seen the event at all.
      */
     public boolean onKeyDown(int keyCode, KeyEvent event) {
+        if (Flags.useHandwritingListenerForTooltype()) {
+            // any KeyEvent keyDown should reset last toolType.
+            updateEditorToolTypeInternal(MotionEvent.TOOL_TYPE_UNKNOWN);
+        }
         if (event.getKeyCode() == KeyEvent.KEYCODE_BACK) {
             final ExtractEditText eet = getExtractEditTextIfVisible();
             if (eet != null && eet.handleBackInTextActionModeIfNeeded(event)) {
diff --git a/core/java/android/os/GraphicsEnvironment.java b/core/java/android/os/GraphicsEnvironment.java
index 8fcff78..3149de4 100644
--- a/core/java/android/os/GraphicsEnvironment.java
+++ b/core/java/android/os/GraphicsEnvironment.java
@@ -673,6 +673,7 @@
         if (anglePkg.isEmpty()) {
             return;
         }
+        intent.setPackage(anglePkg);
 
         context.sendOrderedBroadcast(intent, null, new BroadcastReceiver() {
             @Override
diff --git a/core/java/android/os/HwNoService.java b/core/java/android/os/HwNoService.java
index 117c3ad..0840314 100644
--- a/core/java/android/os/HwNoService.java
+++ b/core/java/android/os/HwNoService.java
@@ -16,37 +16,127 @@
 
 package android.os;
 
+import android.hidl.manager.V1_2.IServiceManager;
+import android.util.Log;
+
+import java.util.ArrayList;
+
 /**
  * A fake hwservicemanager that is used locally when HIDL isn't supported on the device.
  *
  * @hide
  */
-final class HwNoService implements IHwBinder, IHwInterface {
-    /** @hide */
-    @Override
-    public void transact(int code, HwParcel request, HwParcel reply, int flags) {}
+final class HwNoService extends IServiceManager.Stub implements IHwBinder, IHwInterface {
+    private static final String TAG = "HwNoService";
 
     /** @hide */
     @Override
-    public IHwInterface queryLocalInterface(String descriptor) {
-        return new HwNoService();
+    public String toString() {
+        return "[HwNoService]";
     }
 
-    /** @hide */
     @Override
-    public boolean linkToDeath(DeathRecipient recipient, long cookie) {
+    public android.hidl.base.V1_0.IBase get(String fqName, String name)
+            throws android.os.RemoteException {
+        Log.i(TAG, "get " + fqName + "/" + name + " with no hwservicemanager");
+        return null;
+    }
+
+    @Override
+    public boolean add(String name, android.hidl.base.V1_0.IBase service)
+            throws android.os.RemoteException {
+        Log.i(TAG, "get " + name + " with no hwservicemanager");
+        return false;
+    }
+
+    @Override
+    public byte getTransport(String fqName, String name) throws android.os.RemoteException {
+        Log.i(TAG, "getTransoport " + fqName + "/" + name + " with no hwservicemanager");
+        return 0x0;
+    }
+
+    @Override
+    public java.util.ArrayList<String> list() throws android.os.RemoteException {
+        Log.i(TAG, "list with no hwservicemanager");
+        return new ArrayList<String>();
+    }
+
+    @Override
+    public java.util.ArrayList<String> listByInterface(String fqName)
+            throws android.os.RemoteException {
+        Log.i(TAG, "listByInterface with no hwservicemanager");
+        return new ArrayList<String>();
+    }
+
+    @Override
+    public boolean registerForNotifications(
+            String fqName, String name, android.hidl.manager.V1_0.IServiceNotification callback)
+            throws android.os.RemoteException {
+        Log.i(TAG, "registerForNotifications with no hwservicemanager");
         return true;
     }
 
-    /** @hide */
     @Override
-    public boolean unlinkToDeath(DeathRecipient recipient) {
+    public ArrayList<android.hidl.manager.V1_0.IServiceManager.InstanceDebugInfo> debugDump()
+            throws android.os.RemoteException {
+        Log.i(TAG, "debugDump with no hwservicemanager");
+        return new ArrayList<android.hidl.manager.V1_0.IServiceManager.InstanceDebugInfo>();
+    }
+
+    @Override
+    public void registerPassthroughClient(String fqName, String name)
+            throws android.os.RemoteException {
+        Log.i(TAG, "registerPassthroughClient with no hwservicemanager");
+    }
+
+    @Override
+    public boolean unregisterForNotifications(
+            String fqName, String name, android.hidl.manager.V1_0.IServiceNotification callback)
+            throws android.os.RemoteException {
+        Log.i(TAG, "unregisterForNotifications with no hwservicemanager");
         return true;
     }
 
-    /** @hide */
     @Override
-    public IHwBinder asBinder() {
-        return this;
+    public boolean registerClientCallback(
+            String fqName,
+            String name,
+            android.hidl.base.V1_0.IBase server,
+            android.hidl.manager.V1_2.IClientCallback cb)
+            throws android.os.RemoteException {
+        Log.i(
+                TAG,
+                "registerClientCallback for " + fqName + "/" + name + " with no hwservicemanager");
+        return true;
+    }
+
+    @Override
+    public boolean unregisterClientCallback(
+            android.hidl.base.V1_0.IBase server, android.hidl.manager.V1_2.IClientCallback cb)
+            throws android.os.RemoteException {
+        Log.i(TAG, "unregisterClientCallback with no hwservicemanager");
+        return true;
+    }
+
+    @Override
+    public boolean addWithChain(
+            String name, android.hidl.base.V1_0.IBase service, java.util.ArrayList<String> chain)
+            throws android.os.RemoteException {
+        Log.i(TAG, "addWithChain with no hwservicemanager");
+        return true;
+    }
+
+    @Override
+    public java.util.ArrayList<String> listManifestByInterface(String fqName)
+            throws android.os.RemoteException {
+        Log.i(TAG, "listManifestByInterface for " + fqName + " with no hwservicemanager");
+        return new ArrayList<String>();
+    }
+
+    @Override
+    public boolean tryUnregister(String fqName, String name, android.hidl.base.V1_0.IBase service)
+            throws android.os.RemoteException {
+        Log.i(TAG, "tryUnregister for " + fqName + "/" + name + " with no hwservicemanager");
+        return true;
     }
 }
diff --git a/core/java/android/os/StrictMode.java b/core/java/android/os/StrictMode.java
index e32a8f3..8c8af0e 100644
--- a/core/java/android/os/StrictMode.java
+++ b/core/java/android/os/StrictMode.java
@@ -2373,13 +2373,29 @@
     /** Assume locked until we hear otherwise */
     private static volatile boolean sCeStorageUnlocked = false;
 
+    /**
+     * Avoid (potentially) costly and repeated lookups to the same mount service.
+     * Note that we don't use the Singleton wrapper as lookup may fail early during boot.
+     */
+    private static volatile IStorageManager sStorageManager;
+
     private static boolean isCeStorageUnlocked(int userId) {
-        final IStorageManager storage = IStorageManager.Stub
+        IStorageManager storage = sStorageManager;
+        if (storage == null) {
+            storage = IStorageManager.Stub
                 .asInterface(ServiceManager.getService("mount"));
+            // As the queried handle may be null early during boot, only stash valid handles,
+            // avoiding races with concurrent service queries.
+            if (storage != null) {
+                sStorageManager = storage;
+            }
+        }
         if (storage != null) {
             try {
                 return storage.isCeStorageUnlocked(userId);
             } catch (RemoteException ignored) {
+                // Conservatively clear the ref, allowing refresh if the remote process restarts.
+                sStorageManager = null;
             }
         }
         return false;
diff --git a/core/java/android/security/responsible_apis_flags.aconfig b/core/java/android/security/responsible_apis_flags.aconfig
index 4e5588c..fe6c4a4 100644
--- a/core/java/android/security/responsible_apis_flags.aconfig
+++ b/core/java/android/security/responsible_apis_flags.aconfig
@@ -20,3 +20,10 @@
     description: "Enables toasts when ASM restrictions are triggered"
     bug: "230590090"
 }
+
+flag {
+    name: "content_uri_permission_apis"
+    namespace: "responsible_apis"
+    description: "Enables the content URI permission APIs"
+    bug: "293467489"
+}
diff --git a/core/java/android/service/autofill/AutofillService.java b/core/java/android/service/autofill/AutofillService.java
index cb1b5d3..5ad2502 100644
--- a/core/java/android/service/autofill/AutofillService.java
+++ b/core/java/android/service/autofill/AutofillService.java
@@ -37,6 +37,7 @@
 import android.view.autofill.AutofillId;
 import android.view.autofill.AutofillManager;
 import android.view.autofill.AutofillValue;
+import android.view.autofill.IAutoFillManagerClient;
 
 import com.android.internal.os.IResultReceiver;
 
@@ -621,6 +622,23 @@
                     new FillCallback(callback, request.getId())));
         }
 
+
+        @Override
+        public void onFillCredentialRequest(FillRequest request, IFillCallback callback,
+                IAutoFillManagerClient autofillClientCallback) {
+            ICancellationSignal transport = CancellationSignal.createTransport();
+            try {
+                callback.onCancellable(transport);
+            } catch (RemoteException e) {
+                e.rethrowFromSystemServer();
+            }
+            mHandler.sendMessage(obtainMessage(
+                    AutofillService::onFillCredentialRequest,
+                    AutofillService.this, request, CancellationSignal.fromTransport(transport),
+                    new FillCallback(callback, request.getId()),
+                    autofillClientCallback));
+        }
+
         @Override
         public void onSaveRequest(SaveRequest request, ISaveCallback callback) {
             mHandler.sendMessage(obtainMessage(
@@ -683,6 +701,15 @@
             @NonNull CancellationSignal cancellationSignal, @NonNull FillCallback callback);
 
     /**
+     * Variant of onFillRequest for internal credential manager proxy autofill service only.
+     *
+     * @hide
+     */
+    public void onFillCredentialRequest(@NonNull FillRequest request,
+            @NonNull CancellationSignal cancellationSignal, @NonNull FillCallback callback,
+            IAutoFillManagerClient autofillClientCallback) {}
+
+    /**
      * Called when the user requests the service to save the contents of a screen.
      *
      * <p>If the service could not handle the request right away&mdash;for example, because it must
diff --git a/core/java/android/service/autofill/IAutoFillService.aidl b/core/java/android/service/autofill/IAutoFillService.aidl
index d88e094..03ead32 100644
--- a/core/java/android/service/autofill/IAutoFillService.aidl
+++ b/core/java/android/service/autofill/IAutoFillService.aidl
@@ -20,6 +20,7 @@
 import android.service.autofill.IFillCallback;
 import android.service.autofill.ISaveCallback;
 import android.service.autofill.SaveRequest;
+import android.view.autofill.IAutoFillManagerClient;
 import com.android.internal.os.IResultReceiver;
 
 /**
@@ -30,6 +31,8 @@
 oneway interface IAutoFillService {
     void onConnectedStateChanged(boolean connected);
     void onFillRequest(in FillRequest request, in IFillCallback callback);
+    void onFillCredentialRequest(in FillRequest request, in IFillCallback callback,
+    in IAutoFillManagerClient client);
     void onSaveRequest(in SaveRequest request, in ISaveCallback callback);
     void onSavedPasswordCountRequest(in IResultReceiver receiver);
 }
diff --git a/core/java/android/service/wallpaper/WallpaperService.java b/core/java/android/service/wallpaper/WallpaperService.java
index 54116a2..692dad4 100644
--- a/core/java/android/service/wallpaper/WallpaperService.java
+++ b/core/java/android/service/wallpaper/WallpaperService.java
@@ -26,6 +26,8 @@
 import static android.view.View.SYSTEM_UI_FLAG_VISIBLE;
 import static android.view.WindowManager.LayoutParams.TYPE_WALLPAPER;
 
+import static com.android.window.flags.Flags.noConsecutiveVisibilityEvents;
+
 import android.animation.AnimationHandler;
 import android.animation.Animator;
 import android.animation.AnimatorListenerAdapter;
@@ -1431,27 +1433,36 @@
                         }
 
                         if (didSurface && !mReportedVisible) {
-                            // This wallpaper is currently invisible, but its
-                            // surface has changed.  At this point let's tell it
-                            // again that it is invisible in case the report about
-                            // the surface caused it to start running.  We really
-                            // don't want wallpapers running when not visible.
                             if (mIsCreating) {
-                                // Some wallpapers will ignore this call if they
-                                // had previously been told they were invisble,
-                                // so if we are creating a new surface then toggle
-                                // the state to get them to notice.
-                                if (DEBUG) Log.v(TAG, "onVisibilityChanged(true) at surface: "
-                                        + this);
-                                Trace.beginSection("WPMS.Engine.onVisibilityChanged-true");
-                                onVisibilityChanged(true);
+                                // The surface has been created, but the wallpaper isn't visible.
+                                // Trigger onVisibilityChanged(true) then onVisibilityChanged(false)
+                                // to make sure the wallpaper is stopped even after the events
+                                // onSurfaceCreated() and onSurfaceChanged().
+                                if (noConsecutiveVisibilityEvents()) {
+                                    if (DEBUG) Log.v(TAG, "toggling doVisibilityChanged");
+                                    Trace.beginSection("WPMS.Engine.doVisibilityChanged-true");
+                                    doVisibilityChanged(true);
+                                    Trace.endSection();
+                                    Trace.beginSection("WPMS.Engine.doVisibilityChanged-false");
+                                    doVisibilityChanged(false);
+                                    Trace.endSection();
+                                } else {
+                                    if (DEBUG) {
+                                        Log.v(TAG, "onVisibilityChanged(true) at surface: " + this);
+                                    }
+                                    Trace.beginSection("WPMS.Engine.onVisibilityChanged-true");
+                                    onVisibilityChanged(true);
+                                    Trace.endSection();
+                                }
+                            }
+                            if (!noConsecutiveVisibilityEvents()) {
+                                if (DEBUG) {
+                                    Log.v(TAG, "onVisibilityChanged(false) at surface: " + this);
+                                }
+                                Trace.beginSection("WPMS.Engine.onVisibilityChanged-false");
+                                onVisibilityChanged(false);
                                 Trace.endSection();
                             }
-                            if (DEBUG) Log.v(TAG, "onVisibilityChanged(false) at surface: "
-                                        + this);
-                            Trace.beginSection("WPMS.Engine.onVisibilityChanged-false");
-                            onVisibilityChanged(false);
-                            Trace.endSection();
                         }
                     } finally {
                         mIsCreating = false;
diff --git a/core/java/android/view/InputEventReceiver.java b/core/java/android/view/InputEventReceiver.java
index 19e6836..9c430cd 100644
--- a/core/java/android/view/InputEventReceiver.java
+++ b/core/java/android/view/InputEventReceiver.java
@@ -54,6 +54,7 @@
             InputChannel inputChannel, MessageQueue messageQueue);
     private static native void nativeDispose(long receiverPtr);
     private static native void nativeFinishInputEvent(long receiverPtr, int seq, boolean handled);
+    private static native boolean nativeProbablyHasInput(long receiverPtr);
     private static native void nativeReportTimeline(long receiverPtr, int inputEventId,
             long gpuCompletedTime, long presentTime);
     private static native boolean nativeConsumeBatchedInputEvents(long receiverPtr,
@@ -92,6 +93,17 @@
     }
 
     /**
+     * Checks the receiver for input availability.
+     * May return false negatives.
+     */
+    public boolean probablyHasInput() {
+        if (mReceiverPtr == 0) {
+            return false;
+        }
+        return nativeProbablyHasInput(mReceiverPtr);
+    }
+
+    /**
      * Disposes the receiver.
      * Must be called on the same Looper thread to which the receiver is attached.
      */
diff --git a/core/java/android/view/Surface.java b/core/java/android/view/Surface.java
index ad0bf7c..7850554 100644
--- a/core/java/android/view/Surface.java
+++ b/core/java/android/view/Surface.java
@@ -274,7 +274,8 @@
     @Retention(RetentionPolicy.SOURCE)
     @IntDef(prefix = {"FRAME_RATE_CATEGORY_"},
             value = {FRAME_RATE_CATEGORY_DEFAULT, FRAME_RATE_CATEGORY_NO_PREFERENCE,
-                    FRAME_RATE_CATEGORY_LOW, FRAME_RATE_CATEGORY_NORMAL, FRAME_RATE_CATEGORY_HIGH})
+                    FRAME_RATE_CATEGORY_LOW, FRAME_RATE_CATEGORY_NORMAL,
+                    FRAME_RATE_CATEGORY_HIGH_HINT, FRAME_RATE_CATEGORY_HIGH})
     public @interface FrameRateCategory {}
 
     // From native_window.h or window.h. Keep these in sync.
@@ -308,11 +309,21 @@
     public static final int FRAME_RATE_CATEGORY_NORMAL = 3;
 
     /**
+     * Hints that, as a result of a user interaction, an animation is likely to start.
+     * This category is a signal that a user interaction heuristic determined the need of a
+     * high refresh rate, and is not an explicit request from the app.
+     * As opposed to {@link #FRAME_RATE_CATEGORY_HIGH}, this vote may be ignored in favor of
+     * more explicit votes.
+     * @hide
+     */
+    public static final int FRAME_RATE_CATEGORY_HIGH_HINT = 4;
+
+    /**
      * Indicates a frame rate suitable for animations that require a high frame rate, which may
      * increase smoothness but may also increase power usage.
      * @hide
      */
-    public static final int FRAME_RATE_CATEGORY_HIGH = 4;
+    public static final int FRAME_RATE_CATEGORY_HIGH = 5;
 
     /**
      * Create an empty surface, which will later be filled in by readFromParcel().
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index 442ea66..2b99e1e 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -32,6 +32,7 @@
 import static android.view.displayhash.DisplayHashResultCallback.EXTRA_DISPLAY_HASH_ERROR_CODE;
 import static android.view.flags.Flags.FLAG_TOOLKIT_SET_FRAME_RATE_READ_ONLY;
 import static android.view.flags.Flags.FLAG_VIEW_VELOCITY_API;
+import static android.view.flags.Flags.enableUseMeasureCacheDuringForceLayout;
 import static android.view.flags.Flags.toolkitMetricsForFrameRateDecision;
 import static android.view.flags.Flags.toolkitSetFrameRateReadOnly;
 import static android.view.flags.Flags.viewVelocityApi;
@@ -955,6 +956,12 @@
     private static boolean sAlwaysRemeasureExactly = false;
 
     /**
+     * When true makes it possible to use onMeasure caches also when the force layout flag is
+     * enabled. This helps avoiding multiple measures in the same frame with the same dimensions.
+     */
+    private static boolean sUseMeasureCacheDuringForceLayoutFlagValue;
+
+    /**
      * Allow setForeground/setBackground to be called (and ignored) on a textureview,
      * without throwing
      */
@@ -2396,6 +2403,7 @@
 
         sToolkitSetFrameRateReadOnlyFlagValue = toolkitSetFrameRateReadOnly();
         sToolkitMetricsForFrameRateDecisionFlagValue = toolkitMetricsForFrameRateDecision();
+        sUseMeasureCacheDuringForceLayoutFlagValue = enableUseMeasureCacheDuringForceLayout();
     }
 
     /**
@@ -22848,6 +22856,36 @@
     }
 
     /**
+     * Determines whether an unprocessed input event is available on the window.
+     *
+     * This is only a performance hint (a.k.a. the Input Hint) and may return false negative
+     * results.  Callers should not rely on availability of the input event based on the return
+     * value of this method.
+     *
+     * The Input Hint functionality is experimental, and can be removed in the future OS releases.
+     *
+     * This method only returns nontrivial results on a View that is attached to a Window. Such View
+     * can be acquired using `Activity.getWindow().getDecorView()`, and only after the view
+     * hierarchy is attached (via {@link android.app.Activity#setContentView(android.view.View)}).
+     *
+     * In multi-window mode the View can provide the Input Hint only for the window it is attached
+     * to. Therefore, checking input availability for the whole application would require asking
+     * for the hint from more than one View.
+     *
+     * The initial implementation does not return false positives, but callers should not rely on
+     * it: false positives may occur in future OS releases.
+     *
+     * @hide
+     */
+    public boolean probablyHasInput() {
+        ViewRootImpl viewRootImpl = getViewRootImpl();
+        if (viewRootImpl == null) {
+            return false;
+        }
+        return viewRootImpl.probablyHasInput();
+    }
+
+    /**
      * Destroys all hardware rendering resources. This method is invoked
      * when the system needs to reclaim resources. Upon execution of this
      * method, you should free any OpenGL resources created by the view.
@@ -27417,7 +27455,13 @@
 
             resolveRtlPropertiesIfNeeded();
 
-            int cacheIndex = forceLayout ? -1 : mMeasureCache.indexOfKey(key);
+            int cacheIndex;
+            if (sUseMeasureCacheDuringForceLayoutFlagValue) {
+                cacheIndex =  mMeasureCache.indexOfKey(key);
+            } else {
+                cacheIndex = forceLayout ? -1 : mMeasureCache.indexOfKey(key);
+            }
+
             if (cacheIndex < 0 || sIgnoreMeasureCache) {
                 if (isTraversalTracingEnabled()) {
                     Trace.beginSection(mTracingStrings.onMeasure);
diff --git a/core/java/android/view/ViewGroup.java b/core/java/android/view/ViewGroup.java
index 287c7b2..fbefbf3 100644
--- a/core/java/android/view/ViewGroup.java
+++ b/core/java/android/view/ViewGroup.java
@@ -49,6 +49,7 @@
 import android.os.Bundle;
 import android.os.Parcelable;
 import android.os.SystemClock;
+import android.service.autofill.Flags;
 import android.util.AttributeSet;
 import android.util.IntArray;
 import android.util.Log;
@@ -3752,7 +3753,16 @@
                         && !child.isActivityDeniedForAutofillForUnimportantView())
                     || (shouldIncludeAllChildrenViewWithAutofillTypeNotNone(afm)
                         && child.getAutofillType() != AUTOFILL_TYPE_NONE)
-                    || shouldIncludeAllChildrenViews(afm)){
+                    || shouldIncludeAllChildrenViews(afm)
+                    || (Flags.includeInvisibleViewGroupInAssistStructure()
+                    && child instanceof ViewGroup && child.getVisibility() != View.VISIBLE)) {
+                // If the child is a ViewGroup object and its visibility is not visible, include
+                // it as part of the assist structure. The children of these invisible ViewGroup
+                // objects are parsed and included in the assist structure. When the Autofill
+                // Provider determines the visibility of these children, it looks at their
+                // visibility as well as their parent's visibility. Omitting invisible parents
+                // will lead to the Autofill Provider incorrectly assuming that these children
+                // of invisible parents are actually visible.
                 list.add(child);
             } else if (child instanceof ViewGroup) {
                 ((ViewGroup) child).populateChildrenForAutofill(list, flags);
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index 32afe06..8529b4e 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -10554,6 +10554,18 @@
     }
 
     /**
+     * Checks the input event receiver for input availability.
+     * May return false negatives.
+     * @hide
+     */
+    public boolean probablyHasInput() {
+        if (mInputEventReceiver == null) {
+            return false;
+        }
+        return mInputEventReceiver.probablyHasInput();
+    }
+
+    /**
      * Adds a scroll capture callback to this window.
      *
      * @param callback the callback to add
diff --git a/core/java/android/view/WindowManager.java b/core/java/android/view/WindowManager.java
index f76822f..61cf126 100644
--- a/core/java/android/view/WindowManager.java
+++ b/core/java/android/view/WindowManager.java
@@ -972,10 +972,8 @@
      *     android:value="false"/&gt;
      * &lt;/application&gt;
      * </pre>
-     *
-     * @hide
      */
-    // TODO(b/274924641): Make this public API.
+    @FlaggedApi(Flags.FLAG_APP_COMPAT_PROPERTIES_API)
     String PROPERTY_COMPAT_ALLOW_IGNORING_ORIENTATION_REQUEST_WHEN_LOOP_DETECTED =
             "android.window.PROPERTY_COMPAT_ALLOW_IGNORING_ORIENTATION_REQUEST_WHEN_LOOP_DETECTED";
 
@@ -1309,9 +1307,8 @@
      *     android:value="true|false"/&gt;
      * &lt;/application&gt;
      * </pre>
-     * @hide
      */
-    // TODO(b/280052089): Make this public API.
+    @FlaggedApi(Flags.FLAG_APP_COMPAT_PROPERTIES_API)
     String PROPERTY_COMPAT_ALLOW_RESIZEABLE_ACTIVITY_OVERRIDES =
             "android.window.PROPERTY_COMPAT_ALLOW_RESIZEABLE_ACTIVITY_OVERRIDES";
 
diff --git a/core/java/android/view/accessibility/AccessibilityManager.java b/core/java/android/view/accessibility/AccessibilityManager.java
index a38092a..49d2ceb 100644
--- a/core/java/android/view/accessibility/AccessibilityManager.java
+++ b/core/java/android/view/accessibility/AccessibilityManager.java
@@ -2067,10 +2067,10 @@
     }
 
     /**
-     * Start sequence (infinite) type of flash notification. Use
-     * {@code Context.getOpPackageName()} as the identifier of this flash notification.
+     * Start sequence (infinite) type of flash notification. Use {@code Context} to retrieve the
+     * package name as the identifier of this flash notification.
      * The notification can be cancelled later by calling {@link #stopFlashNotificationSequence}
-     * with same {@code Context.getOpPackageName()}.
+     * with same {@code Context}.
      * If the binder associated with this {@link AccessibilityManager} instance dies then the
      * sequence will stop automatically. It is strongly recommended to call
      * {@link #stopFlashNotificationSequence} within a reasonable amount of time after calling
@@ -2104,8 +2104,8 @@
     }
 
     /**
-     * Stop sequence (infinite) type of flash notification. The flash notification with
-     * {@code Context.getOpPackageName()} as identifier will be stopped if exist.
+     * Stop sequence (infinite) type of flash notification. The flash notification with the
+     * package name retrieved from {@code Context} as identifier will be stopped if exist.
      * It is strongly recommended to call this method within a reasonable amount of time after
      * calling {@link #startFlashNotificationSequence} method.
      *
diff --git a/core/java/android/view/flags/view_flags.aconfig b/core/java/android/view/flags/view_flags.aconfig
new file mode 100644
index 0000000..a74b06a
--- /dev/null
+++ b/core/java/android/view/flags/view_flags.aconfig
@@ -0,0 +1,10 @@
+package: "android.view.flags"
+
+flag {
+    name: "enable_use_measure_cache_during_force_layout"
+    namespace: "toolkit"
+    description: "Enables using the measure cache during a view force layout from the second "
+      "onMeasure call onwards during the same traversal."
+    bug: "316170253"
+    is_fixed_read_only: true
+}
diff --git a/core/java/android/view/inputmethod/flags.aconfig b/core/java/android/view/inputmethod/flags.aconfig
index dc6aa6c..bb7677d6 100644
--- a/core/java/android/view/inputmethod/flags.aconfig
+++ b/core/java/android/view/inputmethod/flags.aconfig
@@ -38,3 +38,12 @@
     description: "Feature flag for supporting stylus handwriting delegation from RemoteViews on the home screen"
     bug: "279959705"
 }
+
+flag {
+    name: "use_handwriting_listener_for_tooltype"
+    namespace: "input_method"
+    description: "Feature flag for using handwriting spy for determining pointer toolType."
+    bug: "309554999"
+    is_fixed_read_only: true
+}
+
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/di/bound/CustomTileUser.kt b/core/java/android/window/ActivityWindowInfo.aidl
similarity index 64%
copy from packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/di/bound/CustomTileUser.kt
copy to core/java/android/window/ActivityWindowInfo.aidl
index efc7431..d0526bc 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/di/bound/CustomTileUser.kt
+++ b/core/java/android/window/ActivityWindowInfo.aidl
@@ -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,12 +14,10 @@
  * limitations under the License.
  */
 
-package com.android.systemui.qs.tiles.impl.custom.di.bound
+package android.window;
 
-import javax.inject.Qualifier
-
-/** User associated with current custom tile binding. */
-@Qualifier
-@MustBeDocumented
-@Retention(AnnotationRetention.RUNTIME)
-annotation class CustomTileUser
+/**
+ * Stores information about a particular Activity Window.
+ * @hide
+ */
+parcelable ActivityWindowInfo;
diff --git a/core/java/android/window/ActivityWindowInfo.java b/core/java/android/window/ActivityWindowInfo.java
new file mode 100644
index 0000000..946bb82
--- /dev/null
+++ b/core/java/android/window/ActivityWindowInfo.java
@@ -0,0 +1,147 @@
+/*
+ * 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.window;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.graphics.Rect;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * Stores the window information about a particular Activity.
+ * It contains the info that is not part of {@link android.content.res.Configuration}.
+ * @hide
+ */
+public final class ActivityWindowInfo implements Parcelable {
+
+    private boolean mIsEmbedded;
+
+    @NonNull
+    private final Rect mTaskBounds = new Rect();
+
+    @NonNull
+    private final Rect mTaskFragmentBounds = new Rect();
+
+    public ActivityWindowInfo() {}
+
+    public ActivityWindowInfo(@NonNull ActivityWindowInfo info) {
+        set(info);
+    }
+
+    /** Copies fields from {@code info}. */
+    public void set(@NonNull ActivityWindowInfo info) {
+        set(info.mIsEmbedded, info.mTaskBounds, info.mTaskFragmentBounds);
+    }
+
+    /** Sets to the given values. */
+    public void set(boolean isEmbedded, @NonNull Rect taskBounds,
+            @NonNull Rect taskFragmentBounds) {
+        mIsEmbedded = isEmbedded;
+        mTaskBounds.set(taskBounds);
+        mTaskFragmentBounds.set(taskFragmentBounds);
+    }
+
+    /**
+     * Whether this activity is embedded, which means it is a TaskFragment that doesn't fill the
+     * leaf Task.
+     */
+    public boolean isEmbedded() {
+        return mIsEmbedded;
+    }
+
+    /**
+     * The bounds of the leaf Task window in display space.
+     */
+    @NonNull
+    public Rect getTaskBounds() {
+        return mTaskBounds;
+    }
+
+    /**
+     * The bounds of the leaf TaskFragment window in display space.
+     * This can be referring to the bounds of the same window as {@link #getTaskBounds()} when
+     * the activity is not embedded.
+     */
+    @NonNull
+    public Rect getTaskFragmentBounds() {
+        return mTaskFragmentBounds;
+    }
+
+    private ActivityWindowInfo(@NonNull Parcel in) {
+        mIsEmbedded = in.readBoolean();
+        mTaskBounds.readFromParcel(in);
+        mTaskFragmentBounds.readFromParcel(in);
+    }
+
+    @Override
+    public void writeToParcel(@NonNull Parcel dest, int flags) {
+        dest.writeBoolean(mIsEmbedded);
+        mTaskBounds.writeToParcel(dest, flags);
+        mTaskFragmentBounds.writeToParcel(dest, flags);
+    }
+
+    @NonNull
+    public static final Creator<ActivityWindowInfo> CREATOR =
+            new Creator<>() {
+                @Override
+                public ActivityWindowInfo createFromParcel(@NonNull Parcel in) {
+                    return new ActivityWindowInfo(in);
+                }
+
+                @Override
+                public ActivityWindowInfo[] newArray(int size) {
+                    return new ActivityWindowInfo[size];
+                }
+            };
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public boolean equals(@Nullable Object o) {
+        if (this == o) {
+            return true;
+        }
+        if (o == null || getClass() != o.getClass()) {
+            return false;
+        }
+        final ActivityWindowInfo other = (ActivityWindowInfo) o;
+        return mIsEmbedded == other.mIsEmbedded
+                && mTaskBounds.equals(other.mTaskBounds)
+                && mTaskFragmentBounds.equals(other.mTaskFragmentBounds);
+    }
+
+    @Override
+    public int hashCode() {
+        int result = 17;
+        result = 31 * result + (mIsEmbedded ? 1 : 0);
+        result = 31 * result + mTaskBounds.hashCode();
+        result = 31 * result + mTaskFragmentBounds.hashCode();
+        return result;
+    }
+
+    @Override
+    public String toString() {
+        return "ActivityWindowInfo{isEmbedded=" + mIsEmbedded
+                + ", taskBounds=" + mTaskBounds
+                + ", taskFragmentBounds=" + mTaskFragmentBounds
+                + "}";
+    }
+}
diff --git a/core/java/android/window/TaskFragmentOperation.java b/core/java/android/window/TaskFragmentOperation.java
index 0ec9ffe..acc6a74 100644
--- a/core/java/android/window/TaskFragmentOperation.java
+++ b/core/java/android/window/TaskFragmentOperation.java
@@ -120,6 +120,11 @@
      */
     public static final int OP_TYPE_REMOVE_TASK_FRAGMENT_DECOR_SURFACE = 15;
 
+    /**
+     * Applies dimming on the parent Task which could cross two TaskFragments.
+     */
+    public static final int OP_TYPE_SET_DIM_ON_TASK = 16;
+
     @IntDef(prefix = { "OP_TYPE_" }, value = {
             OP_TYPE_UNKNOWN,
             OP_TYPE_CREATE_TASK_FRAGMENT,
@@ -138,6 +143,7 @@
             OP_TYPE_REORDER_TO_TOP_OF_TASK,
             OP_TYPE_CREATE_TASK_FRAGMENT_DECOR_SURFACE,
             OP_TYPE_REMOVE_TASK_FRAGMENT_DECOR_SURFACE,
+            OP_TYPE_SET_DIM_ON_TASK,
     })
     @Retention(RetentionPolicy.SOURCE)
     public @interface OperationType {}
@@ -165,12 +171,14 @@
 
     private final boolean mIsolatedNav;
 
+    private final boolean mDimOnTask;
+
     private TaskFragmentOperation(@OperationType int opType,
             @Nullable TaskFragmentCreationParams taskFragmentCreationParams,
             @Nullable IBinder activityToken, @Nullable Intent activityIntent,
             @Nullable Bundle bundle, @Nullable IBinder secondaryFragmentToken,
             @Nullable TaskFragmentAnimationParams animationParams,
-            boolean isolatedNav) {
+            boolean isolatedNav, boolean dimOnTask) {
         mOpType = opType;
         mTaskFragmentCreationParams = taskFragmentCreationParams;
         mActivityToken = activityToken;
@@ -179,6 +187,7 @@
         mSecondaryFragmentToken = secondaryFragmentToken;
         mAnimationParams = animationParams;
         mIsolatedNav = isolatedNav;
+        mDimOnTask = dimOnTask;
     }
 
     private TaskFragmentOperation(Parcel in) {
@@ -190,6 +199,7 @@
         mSecondaryFragmentToken = in.readStrongBinder();
         mAnimationParams = in.readTypedObject(TaskFragmentAnimationParams.CREATOR);
         mIsolatedNav = in.readBoolean();
+        mDimOnTask = in.readBoolean();
     }
 
     @Override
@@ -202,6 +212,7 @@
         dest.writeStrongBinder(mSecondaryFragmentToken);
         dest.writeTypedObject(mAnimationParams, flags);
         dest.writeBoolean(mIsolatedNav);
+        dest.writeBoolean(mDimOnTask);
     }
 
     @NonNull
@@ -282,6 +293,13 @@
         return mIsolatedNav;
     }
 
+    /**
+     * Returns whether the dim layer should apply on the parent Task.
+     */
+    public boolean isDimOnTask() {
+        return mDimOnTask;
+    }
+
     @Override
     public String toString() {
         final StringBuilder sb = new StringBuilder();
@@ -305,6 +323,7 @@
             sb.append(", animationParams=").append(mAnimationParams);
         }
         sb.append(", isolatedNav=").append(mIsolatedNav);
+        sb.append(", dimOnTask=").append(mDimOnTask);
 
         sb.append('}');
         return sb.toString();
@@ -313,7 +332,7 @@
     @Override
     public int hashCode() {
         return Objects.hash(mOpType, mTaskFragmentCreationParams, mActivityToken, mActivityIntent,
-                mBundle, mSecondaryFragmentToken, mAnimationParams, mIsolatedNav);
+                mBundle, mSecondaryFragmentToken, mAnimationParams, mIsolatedNav, mDimOnTask);
     }
 
     @Override
@@ -329,7 +348,8 @@
                 && Objects.equals(mBundle, other.mBundle)
                 && Objects.equals(mSecondaryFragmentToken, other.mSecondaryFragmentToken)
                 && Objects.equals(mAnimationParams, other.mAnimationParams)
-                && mIsolatedNav == other.mIsolatedNav;
+                && mIsolatedNav == other.mIsolatedNav
+                && mDimOnTask == other.mDimOnTask;
     }
 
     @Override
@@ -363,6 +383,8 @@
 
         private boolean mIsolatedNav;
 
+        private boolean mDimOnTask;
+
         /**
          * @param opType the {@link OperationType} of this {@link TaskFragmentOperation}.
          */
@@ -435,13 +457,22 @@
         }
 
         /**
+         * Sets the dimming to apply on the parent Task if any.
+         */
+        @NonNull
+        public Builder setDimOnTask(boolean dimOnTask) {
+            mDimOnTask = dimOnTask;
+            return this;
+        }
+
+        /**
          * Constructs the {@link TaskFragmentOperation}.
          */
         @NonNull
         public TaskFragmentOperation build() {
             return new TaskFragmentOperation(mOpType, mTaskFragmentCreationParams, mActivityToken,
                     mActivityIntent, mBundle, mSecondaryFragmentToken, mAnimationParams,
-                    mIsolatedNav);
+                    mIsolatedNav, mDimOnTask);
         }
     }
 }
diff --git a/core/java/android/window/flags/wallpaper_manager.aconfig b/core/java/android/window/flags/wallpaper_manager.aconfig
index f03c993..ea9da96 100644
--- a/core/java/android/window/flags/wallpaper_manager.aconfig
+++ b/core/java/android/window/flags/wallpaper_manager.aconfig
@@ -13,3 +13,10 @@
   description: "Support storing different wallpaper crops for different display dimensions. Only effective after rebooting."
   bug: "281648899"
 }
+
+flag {
+  name: "no_consecutive_visibility_events"
+  namespace: "systemui"
+  description: "Prevent the system from sending consecutive onVisibilityChanged(false) events."
+  bug: "285631818"
+}
\ No newline at end of file
diff --git a/core/java/android/window/flags/windowing_sdk.aconfig b/core/java/android/window/flags/windowing_sdk.aconfig
index 59d7b0e..f743ab7 100644
--- a/core/java/android/window/flags/windowing_sdk.aconfig
+++ b/core/java/android/window/flags/windowing_sdk.aconfig
@@ -38,4 +38,12 @@
     name: "activity_embedding_interactive_divider_flag"
     description: "Whether the interactive divider feature is enabled"
     bug: "293654166"
+}
+
+flag {
+    namespace: "windowing_sdk"
+    name: "activity_window_info_flag"
+    description: "To dispatch ActivityWindowInfo through ClientTransaction"
+    bug: "287582673"
+    is_fixed_read_only: true
 }
\ No newline at end of file
diff --git a/core/java/com/android/internal/foldables/FoldGracePeriodProvider.java b/core/java/com/android/internal/foldables/FoldGracePeriodProvider.java
new file mode 100644
index 0000000..53164f3
--- /dev/null
+++ b/core/java/com/android/internal/foldables/FoldGracePeriodProvider.java
@@ -0,0 +1,53 @@
+/*
+ * 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.internal.foldables;
+
+import android.os.Build;
+import android.sysprop.FoldLockBehaviorProperties;
+import android.util.Slog;
+
+import com.android.internal.foldables.flags.Flags;
+
+import java.util.function.Supplier;
+
+/**
+ * Wrapper class to access {@link FoldLockBehaviorProperties}.
+ */
+public class FoldGracePeriodProvider {
+
+    private static final String TAG = "FoldGracePeriodProvider";
+    private final Supplier<Boolean> mFoldGracePeriodEnabled = Flags::foldGracePeriodEnabled;
+
+    /**
+     * Whether the fold grace period feature is enabled.
+     */
+    public boolean isEnabled() {
+        if ((Build.IS_ENG || Build.IS_USERDEBUG)
+                && FoldLockBehaviorProperties.fold_grace_period_enabled().orElse(false)) {
+            return true;
+        }
+        try {
+            return mFoldGracePeriodEnabled.get();
+        } catch (Throwable ex) {
+            Slog.i(TAG,
+                    "Flags not ready yet. Return false for "
+                            + Flags.FLAG_FOLD_GRACE_PERIOD_ENABLED,
+                    ex);
+            return false;
+        }
+    }
+}
diff --git a/core/java/com/android/internal/foldables/fold_lock_setting_flags.aconfig b/core/java/com/android/internal/foldables/fold_lock_setting_flags.aconfig
index 44f436ea..d73e623 100644
--- a/core/java/com/android/internal/foldables/fold_lock_setting_flags.aconfig
+++ b/core/java/com/android/internal/foldables/fold_lock_setting_flags.aconfig
@@ -7,3 +7,11 @@
     bug: "274447767"
     is_fixed_read_only: true
 }
+
+flag {
+    name: "fold_grace_period_enabled"
+    namespace: "display_manager"
+    description: "Feature flag for Folding Grace Period"
+    bug: "308417021"
+    is_fixed_read_only: true
+}
diff --git a/core/java/com/android/internal/util/function/pooled/PooledLambda.java b/core/java/com/android/internal/util/function/pooled/PooledLambda.java
index bdc8a66..1d6d69c 100755
--- a/core/java/com/android/internal/util/function/pooled/PooledLambda.java
+++ b/core/java/com/android/internal/util/function/pooled/PooledLambda.java
@@ -1009,7 +1009,7 @@
             K arg11, L arg12) {
         synchronized (Message.sPoolSync) {
             PooledRunnable callback = acquire(PooledLambdaImpl.sMessageCallbacksPool,
-                    function, 11, 0, ReturnType.VOID, arg1, arg2, arg3, arg4, arg5, arg6, arg7,
+                    function, 12, 0, ReturnType.VOID, arg1, arg2, arg3, arg4, arg5, arg6, arg7,
                     arg8, arg9, arg10, arg11, arg12);
             return Message.obtain().setCallback(callback.recycleOnUse());
         }
diff --git a/core/jni/android_os_HwBinder.cpp b/core/jni/android_os_HwBinder.cpp
index 781895e..477bd09 100644
--- a/core/jni/android_os_HwBinder.cpp
+++ b/core/jni/android_os_HwBinder.cpp
@@ -258,14 +258,59 @@
     JHwBinder::SetNativeContext(env, thiz, context);
 }
 
-static void JHwBinder_native_transact(
-        JNIEnv * /* env */,
-        jobject /* thiz */,
-        jint /* code */,
-        jobject /* requestObj */,
-        jobject /* replyObj */,
-        jint /* flags */) {
-    CHECK(!"Should not be here");
+static void JHwBinder_native_transact(JNIEnv *env, jobject thiz, jint code, jobject requestObj,
+                                      jobject replyObj, jint flags) {
+    if (requestObj == NULL) {
+        jniThrowException(env, "java/lang/NullPointerException", NULL);
+        return;
+    }
+    sp<hardware::IBinder> binder = JHwBinder::GetNativeBinder(env, thiz);
+    sp<android::hidl::base::V1_0::IBase> base = new android::hidl::base::V1_0::BpHwBase(binder);
+    hidl_string desc;
+    auto ret = base->interfaceDescriptor(
+            [&desc](const hidl_string &descriptor) { desc = descriptor; });
+    ret.assertOk();
+    // Only the fake hwservicemanager is allowed to be used locally like this.
+    if (desc != "android.hidl.manager@1.2::IServiceManager" &&
+        desc != "android.hidl.manager@1.1::IServiceManager" &&
+        desc != "android.hidl.manager@1.0::IServiceManager") {
+        LOG(FATAL) << "Local binders are not supported!";
+    }
+    if (replyObj == nullptr) {
+        LOG(FATAL) << "Unexpected null replyObj. code: " << code;
+        return;
+    }
+    const hardware::Parcel *request = JHwParcel::GetNativeContext(env, requestObj)->getParcel();
+    sp<JHwParcel> replyContext = JHwParcel::GetNativeContext(env, replyObj);
+    hardware::Parcel *reply = replyContext->getParcel();
+
+    request->setDataPosition(0);
+
+    bool isOneway = (flags & IBinder::FLAG_ONEWAY) != 0;
+    if (!isOneway) {
+        replyContext->setTransactCallback([](auto &replyParcel) {});
+    }
+
+    env->CallVoidMethod(thiz, gFields.onTransactID, code, requestObj, replyObj, flags);
+
+    if (env->ExceptionCheck()) {
+        jthrowable excep = env->ExceptionOccurred();
+        env->ExceptionDescribe();
+        env->ExceptionClear();
+
+        binder_report_exception(env, excep, "Uncaught error or exception in hwbinder!");
+
+        env->DeleteLocalRef(excep);
+    }
+
+    if (!isOneway) {
+        if (!replyContext->wasSent()) {
+            // The implementation never finished the transaction.
+            LOG(ERROR) << "The reply failed to send!";
+        }
+    }
+
+    reply->setDataPosition(0);
 }
 
 static void JHwBinder_native_registerService(
diff --git a/core/jni/android_view_InputEventReceiver.cpp b/core/jni/android_view_InputEventReceiver.cpp
index 5b68e8e..f7d8152 100644
--- a/core/jni/android_view_InputEventReceiver.cpp
+++ b/core/jni/android_view_InputEventReceiver.cpp
@@ -82,6 +82,7 @@
     status_t initialize();
     void dispose();
     status_t finishInputEvent(uint32_t seq, bool handled);
+    bool probablyHasInput();
     status_t reportTimeline(int32_t inputEventId, nsecs_t gpuCompletedTime, nsecs_t presentTime);
     status_t consumeEvents(JNIEnv* env, bool consumeBatches, nsecs_t frameTime,
             bool* outConsumedBatch);
@@ -165,6 +166,10 @@
     return processOutboundEvents();
 }
 
+bool NativeInputEventReceiver::probablyHasInput() {
+    return mInputConsumer.probablyHasInput();
+}
+
 status_t NativeInputEventReceiver::reportTimeline(int32_t inputEventId, nsecs_t gpuCompletedTime,
                                                   nsecs_t presentTime) {
     if (kDebugDispatchCycle) {
@@ -547,6 +552,12 @@
     }
 }
 
+static bool nativeProbablyHasInput(JNIEnv* env, jclass clazz, jlong receiverPtr) {
+    sp<NativeInputEventReceiver> receiver =
+            reinterpret_cast<NativeInputEventReceiver*>(receiverPtr);
+    return receiver->probablyHasInput();
+}
+
 static void nativeReportTimeline(JNIEnv* env, jclass clazz, jlong receiverPtr, jint inputEventId,
                                  jlong gpuCompletedTime, jlong presentTime) {
     if (IdGenerator::getSource(inputEventId) != IdGenerator::Source::INPUT_READER) {
@@ -597,6 +608,7 @@
          (void*)nativeInit},
         {"nativeDispose", "(J)V", (void*)nativeDispose},
         {"nativeFinishInputEvent", "(JIZ)V", (void*)nativeFinishInputEvent},
+        {"nativeProbablyHasInput", "(J)Z", (void*)nativeProbablyHasInput},
         {"nativeReportTimeline", "(JIJJ)V", (void*)nativeReportTimeline},
         {"nativeConsumeBatchedInputEvents", "(JJ)Z", (void*)nativeConsumeBatchedInputEvents},
         {"nativeDump", "(JLjava/lang/String;)Ljava/lang/String;", (void*)nativeDump},
diff --git a/core/jni/com_android_internal_os_Zygote.cpp b/core/jni/com_android_internal_os_Zygote.cpp
index 7c5885a..7e325a5 100644
--- a/core/jni/com_android_internal_os_Zygote.cpp
+++ b/core/jni/com_android_internal_os_Zygote.cpp
@@ -2187,6 +2187,7 @@
   }
 
   if (multiuser_get_app_id(uid) == AID_NETWORK_STACK) {
+    capabilities |= (1LL << CAP_WAKE_ALARM);
     capabilities |= (1LL << CAP_NET_ADMIN);
     capabilities |= (1LL << CAP_NET_BROADCAST);
     capabilities |= (1LL << CAP_NET_BIND_SERVICE);
diff --git a/core/proto/android/server/windowmanagerservice.proto b/core/proto/android/server/windowmanagerservice.proto
index 382a82c..52e0124 100644
--- a/core/proto/android/server/windowmanagerservice.proto
+++ b/core/proto/android/server/windowmanagerservice.proto
@@ -395,6 +395,8 @@
     optional bool should_refresh_activity_for_camera_compat = 40;
     optional bool should_refresh_activity_via_pause_for_camera_compat = 41;
     optional bool should_override_min_aspect_ratio = 42;
+    optional bool should_ignore_orientation_request_loop = 43;
+    optional bool should_override_force_resize_app = 44;
 }
 
 /* represents WindowToken */
@@ -404,7 +406,7 @@
     optional WindowContainerProto window_container = 1;
     optional int32 hash_code = 2;
     repeated WindowStateProto windows = 3 [deprecated=true];
-    optional bool waiting_to_show = 5;
+    optional bool waiting_to_show = 5 [deprecated=true];
     optional bool paused = 6;
 }
 
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 232a36f..e65bfab 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -7692,6 +7692,13 @@
     <permission android:name="android.permission.MONITOR_KEYBOARD_BACKLIGHT"
                 android:protectionLevel="signature" />
 
+    <!-- Allows low-level access to monitor sticky modifier state changes when A11Y Sticky keys
+         feature is enabled.
+         <p>Not for use by third-party applications.
+         @hide -->
+    <permission android:name="android.permission.MONITOR_STICKY_MODIFIER_STATE"
+                android:protectionLevel="signature" />
+
     <uses-permission android:name="android.permission.HANDLE_QUERY_PACKAGE_RESTART" />
 
     <!-- Allows financed device kiosk apps to perform actions on the Device Lock service
diff --git a/core/res/res/values-ar/strings.xml b/core/res/res/values-ar/strings.xml
index 70f0c93..0cc49a5 100644
--- a/core/res/res/values-ar/strings.xml
+++ b/core/res/res/values-ar/strings.xml
@@ -2316,7 +2316,7 @@
     <string name="sensor_privacy_start_use_dialog_turn_on_button" msgid="7089318886628390827">"إزالة الحظر"</string>
     <string name="sensor_privacy_notification_channel_label" msgid="936036783155261349">"الخصوصية في جهاز الاستشعار"</string>
     <string name="splash_screen_view_icon_description" msgid="180638751260598187">"رمز التطبيق"</string>
-    <string name="splash_screen_view_branding_description" msgid="7911129347402728216">"الصورة الذهنية للعلامة التجارية للتطبيق"</string>
+    <string name="splash_screen_view_branding_description" msgid="7911129347402728216">"هوية العلامة التجارية للتطبيق"</string>
     <string name="view_and_control_notification_title" msgid="4300765399209912240">"التحقّق من إعدادات الوصول"</string>
     <string name="view_and_control_notification_content" msgid="8003766498562604034">"يمكن لخدمة <xliff:g id="SERVICE_NAME">%s</xliff:g> الاطّلاع على شاشتك والتحكّم فيها. انقر لمراجعة الإعدادات."</string>
     <string name="ui_translation_accessibility_translated_text" msgid="3197547218178944544">"<xliff:g id="MESSAGE">%1$s</xliff:g> (مُترجَم)."</string>
diff --git a/core/res/res/values-ca/strings.xml b/core/res/res/values-ca/strings.xml
index a4b01e8..ec14677 100644
--- a/core/res/res/values-ca/strings.xml
+++ b/core/res/res/values-ca/strings.xml
@@ -2340,9 +2340,9 @@
     <string name="permdesc_startForegroundServicesFromBackground" msgid="4071826571656001537">"Permet que una aplicació complementària iniciï serveis en primer pla des d\'un segon pla."</string>
     <string name="mic_access_on_toast" msgid="2666925317663845156">"El micròfon està disponible"</string>
     <string name="mic_access_off_toast" msgid="8111040892954242437">"El micròfon està bloquejat"</string>
-    <string name="connected_display_unavailable_notification_title" msgid="5265409360902073556">"No es pot projectar a la pantalla"</string>
+    <string name="connected_display_unavailable_notification_title" msgid="5265409360902073556">"No es pot duplicar a la pantalla"</string>
     <string name="connected_display_unavailable_notification_content" msgid="3845903313751217516">"Utilitza un altre cable i torna-ho a provar"</string>
-    <string name="connected_display_thermally_unavailable_notification_content" msgid="9205758199439955949">"El dispositiu està massa calent i no pot projectar a la pantalla fins que es refredi"</string>
+    <string name="connected_display_thermally_unavailable_notification_content" msgid="9205758199439955949">"El dispositiu està massa calent i no pot duplicar a la pantalla fins que es refredi"</string>
     <string name="concurrent_display_notification_name" msgid="1526911253558311131">"Pantalla dual"</string>
     <string name="concurrent_display_notification_active_title" msgid="4892473462327943673">"La pantalla dual està activada"</string>
     <string name="concurrent_display_notification_active_content" msgid="5889355473710601270">"<xliff:g id="APP_NAME">%1$s</xliff:g> està utilitzant les dues pantalles per mostrar contingut"</string>
diff --git a/core/res/res/values-da/strings.xml b/core/res/res/values-da/strings.xml
index 7119131..d3f8550 100644
--- a/core/res/res/values-da/strings.xml
+++ b/core/res/res/values-da/strings.xml
@@ -2050,7 +2050,7 @@
     <string name="autofill_save_type_password" msgid="5624528786144539944">"adgangskode"</string>
     <string name="autofill_save_type_address" msgid="3111006395818252885">"adresse"</string>
     <string name="autofill_save_type_credit_card" msgid="3583795235862046693">"kreditkort"</string>
-    <string name="autofill_save_type_debit_card" msgid="3169397504133097468">"betalingskort"</string>
+    <string name="autofill_save_type_debit_card" msgid="3169397504133097468">"debetkort"</string>
     <string name="autofill_save_type_payment_card" msgid="6555012156728690856">"betalingskort"</string>
     <string name="autofill_save_type_generic_card" msgid="1019367283921448608">"kort"</string>
     <string name="autofill_save_type_username" msgid="1018816929884640882">"brugernavn"</string>
diff --git a/core/res/res/values-de/strings.xml b/core/res/res/values-de/strings.xml
index c0e12c3..514d695 100644
--- a/core/res/res/values-de/strings.xml
+++ b/core/res/res/values-de/strings.xml
@@ -355,7 +355,7 @@
     <string name="permlab_fullScreenIntent" msgid="4310888199502509104">"Benachrichtigungen auf einem gesperrten Gerät als Vollbildaktivitäten anzeigen"</string>
     <string name="permdesc_fullScreenIntent" msgid="1100721419406643997">"Ermöglicht der App, Benachrichtigungen auf einem gesperrten Gerät als Vollbildaktivitäten anzuzeigen"</string>
     <string name="permlab_install_shortcut" msgid="7451554307502256221">"Verknüpfungen installieren"</string>
-    <string name="permdesc_install_shortcut" msgid="4476328467240212503">"ohne Zutun des Nutzers Verknüpfungen zum Startbildschirm hinzufügen."</string>
+    <string name="permdesc_install_shortcut" msgid="4476328467240212503">"Ermöglicht einer App, dem Startbildschirm ohne Zutun des Nutzers Verknüpfungen hinzuzufügen."</string>
     <string name="permlab_uninstall_shortcut" msgid="295263654781900390">"Verknüpfungen deinstallieren"</string>
     <string name="permdesc_uninstall_shortcut" msgid="1924735350988629188">"Ermöglicht einer App das Entfernen von Verknüpfungen vom Startbildschirm ohne Eingriff des Nutzers"</string>
     <string name="permlab_processOutgoingCalls" msgid="4075056020714266558">"Ausgehende Anrufe umleiten"</string>
diff --git a/core/res/res/values-eu/strings.xml b/core/res/res/values-eu/strings.xml
index cf18eb9..2b9c555 100644
--- a/core/res/res/values-eu/strings.xml
+++ b/core/res/res/values-eu/strings.xml
@@ -2331,7 +2331,7 @@
     <string name="default_card_name" msgid="9198284935962911468">"<xliff:g id="CARDNUMBER">%d</xliff:g> TXARTELA"</string>
     <string name="permlab_companionProfileWatch" msgid="2457738382085872542">"Aplikazio osagarrien erloju-profilaren baimena erlojuak kudeatzeko"</string>
     <string name="permdesc_companionProfileWatch" msgid="5655698581110449397">"Erlojuak kudeatzeko baimena ematen die aplikazio osagarriei."</string>
-    <string name="permlab_observeCompanionDevicePresence" msgid="9008994909653990465">"Begiratu gailu osagarrien presentzia"</string>
+    <string name="permlab_observeCompanionDevicePresence" msgid="9008994909653990465">"Begiratu gailu osagarrien presentziari"</string>
     <string name="permdesc_observeCompanionDevicePresence" msgid="3011699826788697852">"Gailu osagarrien presentzia begiratzeko baimena ematen die aplikazio osagarriei gailuak inguruan edo urrun daudenean."</string>
     <string name="permlab_deliverCompanionMessages" msgid="3931552294842980887">"Entregatu aplikazio osagarrien mezuak"</string>
     <string name="permdesc_deliverCompanionMessages" msgid="2170847384281412850">"Beste gailuetan mezuak entregatzeko baimena ematen die aplikazio osagarriei."</string>
diff --git a/core/res/res/values-fr/strings.xml b/core/res/res/values-fr/strings.xml
index d435c62..3b24230 100644
--- a/core/res/res/values-fr/strings.xml
+++ b/core/res/res/values-fr/strings.xml
@@ -1724,7 +1724,7 @@
     <string name="color_inversion_feature_name" msgid="2672824491933264951">"Inversion des couleurs"</string>
     <string name="color_correction_feature_name" msgid="7975133554160979214">"Correction des couleurs"</string>
     <string name="one_handed_mode_feature_name" msgid="2334330034828094891">"Mode une main"</string>
-    <string name="reduce_bright_colors_feature_name" msgid="3222994553174604132">"Encore moins lumineux"</string>
+    <string name="reduce_bright_colors_feature_name" msgid="3222994553174604132">"Luminosité ultra-réduite"</string>
     <string name="hearing_aids_feature_name" msgid="1125892105105852542">"Appareils auditifs"</string>
     <string name="accessibility_shortcut_enabling_service" msgid="5473495203759847687">"Touches de volume appuyées de manière prolongée. Service <xliff:g id="SERVICE_NAME">%1$s</xliff:g> activé."</string>
     <string name="accessibility_shortcut_disabling_service" msgid="8675244165062700619">"Touches de volume appuyées de manière prolongée. Service <xliff:g id="SERVICE_NAME">%1$s</xliff:g> désactivé."</string>
diff --git a/core/res/res/values-sw/strings.xml b/core/res/res/values-sw/strings.xml
index 2ff302d..9a063fb 100644
--- a/core/res/res/values-sw/strings.xml
+++ b/core/res/res/values-sw/strings.xml
@@ -575,7 +575,7 @@
     <string name="permdesc_changeWimaxState" product="tablet" msgid="4011097664859480108">"Inaruhusu programu kuunganisha kompyuta kibao,  na kukata kompyuta kibao kutoka mitandao ya WiMAX."</string>
     <string name="permdesc_changeWimaxState" product="tv" msgid="5373274458799425276">"Huruhusu programu iunganishe na kutenganisha kifaa chako cha Android TV na mitandao ya WiMAX."</string>
     <string name="permdesc_changeWimaxState" product="default" msgid="1551666203780202101">"Inaruhusu programu kuunganisha simu kwenye, na kukata simu kutoka mitandao ya WiMAX."</string>
-    <string name="permlab_bluetooth" msgid="586333280736937209">"oanisha na vifaa vya Bluetooth"</string>
+    <string name="permlab_bluetooth" msgid="586333280736937209">"unganisha na vifaa vya Bluetooth"</string>
     <string name="permdesc_bluetooth" product="tablet" msgid="3053222571491402635">"Huruhusu programu kuona usanidi wa Bluetooth kwenye kompyuta kibao, na kutuma na kukubali miunganisho kwa vifaa vilivyooanishwa."</string>
     <string name="permdesc_bluetooth" product="tv" msgid="8851534496561034998">"Huruhusu programu iangalie mipangilio iliyowekwa ya Bluetooth kwenye kifaa chako cha Android TV na kufanya na kukubali miunganisho na vifaa vilivyooanishwa."</string>
     <string name="permdesc_bluetooth" product="default" msgid="2779606714091276746">"Huruhusu programu kuona usanidi wa Bluetooth kwenye simu, na kutuma na kukubali miunganisho kwa vifaa vilivyooanishwa."</string>
diff --git a/core/sysprop/FoldLockBehaviorProperties.sysprop b/core/sysprop/FoldLockBehaviorProperties.sysprop
index d337954..120e4bb 100644
--- a/core/sysprop/FoldLockBehaviorProperties.sysprop
+++ b/core/sysprop/FoldLockBehaviorProperties.sysprop
@@ -22,3 +22,11 @@
     scope: Internal
     access: Readonly
 }
+
+prop {
+    api_name: "fold_grace_period_enabled"
+    type: Boolean
+    prop_name: "persist.fold_grace_period_enabled"
+    scope: Internal
+    access: Readonly
+}
diff --git a/core/tests/coretests/src/android/content/pm/ModuleInfoTest.java b/core/tests/coretests/src/android/content/pm/ModuleInfoTest.java
new file mode 100644
index 0000000..4366e02c
--- /dev/null
+++ b/core/tests/coretests/src/android/content/pm/ModuleInfoTest.java
@@ -0,0 +1,99 @@
+/*
+ * 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.content.pm;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.os.Parcel;
+import android.platform.test.annotations.AppModeFull;
+import android.text.TextUtils;
+
+import androidx.test.runner.AndroidJUnit4;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.List;
+
+@RunWith(AndroidJUnit4.class)
+@AppModeFull
+public class ModuleInfoTest {
+
+    private static final String APEX_MODULE_NAME = "apexModuleName";
+    private static final String APK_IN_APEX_PACKAGE_NAME = "apkInApexPackageName";
+    private static final String MODULE_PACKAGE_NAME = "modulePackageName";
+    private static final String MODULE_NAME = "moduleName";
+
+    @Test
+    public void testSimple() {
+        ModuleInfo info = new ModuleInfo();
+        assertThat(info.toString()).isNotNull();
+    }
+
+    @Test
+    public void testDefaultCopy() {
+        ModuleInfo oldInfo = new ModuleInfo();
+        ModuleInfo newInfo = new ModuleInfo(oldInfo);
+        assertThat(newInfo).isEqualTo(oldInfo);
+    }
+
+    @Test
+    public void testCopy() {
+        boolean isHidden = false;
+        ModuleInfo info = new ModuleInfo();
+        info.setHidden(isHidden);
+        info.setApexModuleName(APEX_MODULE_NAME);
+        info.setPackageName(MODULE_PACKAGE_NAME);
+        info.setName(MODULE_NAME);
+        info.setApkInApexPackageNames(List.of(APK_IN_APEX_PACKAGE_NAME));
+
+        ModuleInfo newInfo = new ModuleInfo(info);
+        assertThat(newInfo).isEqualTo(info);
+    }
+
+    @Test
+    public void testGetApkInApexPackageNamesReturnEmptyListInDefault() {
+        ModuleInfo info = new ModuleInfo();
+        assertThat(info.getApkInApexPackageNames()).isNotNull();
+        assertThat(info.getApkInApexPackageNames()).isEmpty();
+    }
+
+    @Test
+    public void testModuleInfoParcelizeDeparcelize() {
+        boolean isHidden = false;
+        ModuleInfo info = new ModuleInfo();
+        info.setHidden(isHidden);
+        info.setApexModuleName(APEX_MODULE_NAME);
+        info.setPackageName(MODULE_PACKAGE_NAME);
+        info.setName(MODULE_NAME);
+        info.setApkInApexPackageNames(List.of(APK_IN_APEX_PACKAGE_NAME));
+
+        final Parcel p = Parcel.obtain();
+        info.writeToParcel(p, 0);
+        p.setDataPosition(0);
+
+        final ModuleInfo targetInfo = ModuleInfo.CREATOR.createFromParcel(p);
+        p.recycle();
+
+        assertThat(info.isHidden()).isEqualTo(targetInfo.isHidden());
+        assertThat(info.getApexModuleName()).isEqualTo(targetInfo.getApexModuleName());
+        assertThat(info.getPackageName()).isEqualTo(targetInfo.getPackageName());
+        assertThat(TextUtils.equals(info.getName(), targetInfo.getName())).isTrue();
+        assertThat(info.getApkInApexPackageNames().toArray()).isEqualTo(
+                targetInfo.getApkInApexPackageNames().toArray());
+    }
+}
diff --git a/data/etc/preinstalled-packages-platform.xml b/data/etc/preinstalled-packages-platform.xml
index 421bc25..bf60944 100644
--- a/data/etc/preinstalled-packages-platform.xml
+++ b/data/etc/preinstalled-packages-platform.xml
@@ -128,4 +128,9 @@
     <install-in-user-type package="com.android.wallpaperbackup">
         <install-in user-type="FULL" />
     </install-in-user-type>
+
+    <!-- AvatarPicker (AvatarPicker app)-->
+    <install-in-user-type package="com.android.avatarpicker">
+        <install-in user-type="FULL" />
+    </install-in-user-type>
 </config>
diff --git a/data/etc/services.core.protolog.json b/data/etc/services.core.protolog.json
index 742d5a2..917a300 100644
--- a/data/etc/services.core.protolog.json
+++ b/data/etc/services.core.protolog.json
@@ -4453,6 +4453,12 @@
       "group": "WM_DEBUG_BACK_PREVIEW",
       "at": "com\/android\/server\/wm\/BackNavigationController.java"
     },
+    "1946983717": {
+      "message": "Waiting for screen on due to %s",
+      "level": "VERBOSE",
+      "group": "WM_DEBUG_STATES",
+      "at": "com\/android\/server\/wm\/TaskFragment.java"
+    },
     "1947239194": {
       "message": "Deferring rotation, still finishing previous rotation",
       "level": "VERBOSE",
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizer.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizer.java
index ca3d8d1..592f9a5 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizer.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/JetpackTaskFragmentOrganizer.java
@@ -19,6 +19,7 @@
 import static android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED;
 import static android.window.TaskFragmentOperation.OP_TYPE_REORDER_TO_FRONT;
 import static android.window.TaskFragmentOperation.OP_TYPE_SET_ANIMATION_PARAMS;
+import static android.window.TaskFragmentOperation.OP_TYPE_SET_DIM_ON_TASK;
 import static android.window.TaskFragmentOperation.OP_TYPE_SET_ISOLATED_NAVIGATION;
 
 import static androidx.window.extensions.embedding.SplitContainer.getFinishPrimaryWithSecondaryBehavior;
@@ -356,6 +357,13 @@
         wct.addTaskFragmentOperation(fragmentToken, operation);
     }
 
+    void setTaskFragmentDimOnTask(@NonNull WindowContainerTransaction wct,
+            @NonNull IBinder fragmentToken, boolean dimOnTask) {
+        final TaskFragmentOperation operation = new TaskFragmentOperation.Builder(
+                OP_TYPE_SET_DIM_ON_TASK).setDimOnTask(dimOnTask).build();
+        wct.addTaskFragmentOperation(fragmentToken, operation);
+    }
+
     void updateTaskFragmentInfo(@NonNull TaskFragmentInfo taskFragmentInfo) {
         mFragmentInfos.put(taskFragmentInfo.getFragmentToken(), taskFragmentInfo);
     }
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 543570c..6f356fa 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java
@@ -20,6 +20,8 @@
 import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
 import static android.content.pm.PackageManager.MATCH_ALL;
 
+import static androidx.window.extensions.embedding.WindowAttributes.DIM_AREA_ON_TASK;
+
 import android.app.Activity;
 import android.app.ActivityThread;
 import android.app.WindowConfiguration;
@@ -56,6 +58,7 @@
 import androidx.window.extensions.layout.WindowLayoutInfo;
 
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.window.flags.Flags;
 
 import java.util.ArrayList;
 import java.util.List;
@@ -384,6 +387,13 @@
         setCompanionTaskFragment(wct, primaryContainer.getTaskFragmentToken(),
                 secondaryContainer.getTaskFragmentToken(), splitRule, isStacked);
 
+        // Sets the dim area when the two TaskFragments are adjacent.
+        final boolean dimOnTask = !isStacked
+                && splitAttributes.getWindowAttributes().getDimArea() == DIM_AREA_ON_TASK
+                && Flags.fullscreenDimFlag();
+        setTaskFragmentDimOnTask(wct, primaryContainer.getTaskFragmentToken(), dimOnTask);
+        setTaskFragmentDimOnTask(wct, secondaryContainer.getTaskFragmentToken(), dimOnTask);
+
         // Setting isolated navigation and clear non-sticky pinned container if needed.
         final SplitPinRule splitPinRule =
                 splitRule instanceof SplitPinRule ? (SplitPinRule) splitRule : null;
@@ -578,6 +588,23 @@
                 bounds.isEmpty() ? WINDOWING_MODE_FULLSCREEN : WINDOWING_MODE_MULTI_WINDOW);
     }
 
+    @Override
+    void setTaskFragmentDimOnTask(@NonNull WindowContainerTransaction wct,
+            @NonNull IBinder fragmentToken, boolean dimOnTask) {
+        final TaskFragmentContainer container = mController.getContainer(fragmentToken);
+        if (container == null) {
+            throw new IllegalStateException("setTaskFragmentDimOnTask on TaskFragment that is"
+                    + " not registered with controller.");
+        }
+
+        if (container.isLastDimOnTask() == dimOnTask) {
+            return;
+        }
+
+        container.setLastDimOnTask(dimOnTask);
+        super.setTaskFragmentDimOnTask(wct, fragmentToken, dimOnTask);
+    }
+
     /**
      * Expands the split container if the current split bounds are smaller than the Activity or
      * Intent that is added to the container.
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 810bded..b52971a 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java
@@ -172,6 +172,11 @@
     private boolean mIsIsolatedNavigationEnabled;
 
     /**
+     * Whether to apply dimming on the parent Task that was requested last.
+     */
+    private boolean mLastDimOnTask;
+
+    /**
      * @see #TaskFragmentContainer(Activity, Intent, TaskContainer, SplitController,
      * TaskFragmentContainer, String, Bundle)
      */
@@ -836,6 +841,16 @@
         mIsIsolatedNavigationEnabled = isolatedNavigationEnabled;
     }
 
+    /** Sets whether to apply dim on the parent Task. */
+    void setLastDimOnTask(boolean lastDimOnTask) {
+        mLastDimOnTask = lastDimOnTask;
+    }
+
+    /** Returns whether to apply dim on the parent Task. */
+    boolean isLastDimOnTask() {
+        return mLastDimOnTask;
+    }
+
     /**
      * Adds the pending appeared activity that has requested to be launched in this task fragment.
      * @see android.app.ActivityClient#isRequestedToLaunchInTaskFragment
diff --git a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitPresenterTest.java b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitPresenterTest.java
index 6981d9d..941b4e1 100644
--- a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitPresenterTest.java
+++ b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitPresenterTest.java
@@ -235,6 +235,19 @@
     }
 
     @Test
+    public void testSetTaskFragmentDimOnTask() {
+        final TaskFragmentContainer container = mController.newContainer(mActivity, TASK_ID);
+
+        mPresenter.setTaskFragmentDimOnTask(mTransaction, container.getTaskFragmentToken(), true);
+        verify(mTransaction).addTaskFragmentOperation(eq(container.getTaskFragmentToken()), any());
+
+        // No request to set the same adjacent TaskFragments.
+        clearInvocations(mTransaction);
+        mPresenter.setTaskFragmentDimOnTask(mTransaction, container.getTaskFragmentToken(), true);
+        verify(mTransaction, never()).addTaskFragmentOperation(any(), any());
+    }
+
+    @Test
     public void testUpdateAnimationParams() {
         final TaskFragmentContainer container = mController.newContainer(mActivity, TASK_ID);
 
diff --git a/libs/WindowManager/Shell/res/layout/desktop_mode_focused_window_decor.xml b/libs/WindowManager/Shell/res/layout/desktop_mode_focused_window_decor.xml
index cec7ee2..ef7478c 100644
--- a/libs/WindowManager/Shell/res/layout/desktop_mode_focused_window_decor.xml
+++ b/libs/WindowManager/Shell/res/layout/desktop_mode_focused_window_decor.xml
@@ -18,13 +18,13 @@
     xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:tools="http://schemas.android.com/tools"
     android:id="@+id/desktop_mode_caption"
-    android:layout_width="match_parent"
+    android:layout_width="wrap_content"
     android:layout_height="wrap_content"
     android:gravity="center_horizontal">
 
     <ImageButton
         android:id="@+id/caption_handle"
-        android:layout_width="128dp"
+        android:layout_width="@dimen/desktop_mode_fullscreen_decor_caption_width"
         android:layout_height="@dimen/desktop_mode_fullscreen_decor_caption_height"
         android:paddingVertical="16dp"
         android:contentDescription="@string/handle_text"
diff --git a/libs/WindowManager/Shell/res/values/dimen.xml b/libs/WindowManager/Shell/res/values/dimen.xml
index 0a40cea..28e7098 100644
--- a/libs/WindowManager/Shell/res/values/dimen.xml
+++ b/libs/WindowManager/Shell/res/values/dimen.xml
@@ -413,6 +413,9 @@
     <!-- Height of desktop mode caption for fullscreen tasks. -->
     <dimen name="desktop_mode_fullscreen_decor_caption_height">36dp</dimen>
 
+    <!-- Width of desktop mode caption for fullscreen tasks. -->
+    <dimen name="desktop_mode_fullscreen_decor_caption_width">128dp</dimen>
+
     <!-- Required empty space to be visible for partially offscreen tasks. -->
     <dimen name="freeform_required_visible_empty_space_in_header">48dp</dimen>
 
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/UserAspectRatioSettingsWindowManager.java b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/UserAspectRatioSettingsWindowManager.java
index afd3b14..81d1399 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/UserAspectRatioSettingsWindowManager.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/UserAspectRatioSettingsWindowManager.java
@@ -232,6 +232,7 @@
         return taskInfo.appCompatTaskInfo.topActivityEligibleForUserAspectRatioButton
                 && (taskInfo.appCompatTaskInfo.topActivityBoundsLetterboxed
                     || taskInfo.appCompatTaskInfo.isUserFullscreenOverrideEnabled)
+                && !taskInfo.appCompatTaskInfo.isSystemFullscreenOverrideEnabled
                 && Intent.ACTION_MAIN.equals(intent.getAction())
                 && intent.hasCategory(Intent.CATEGORY_LAUNCHER)
                 && (!mUserAspectRatioButtonShownChecker.get() || isShowingButton());
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 71bf487..0ef047f 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
@@ -235,7 +235,8 @@
                 mainChoreographer,
                 taskOrganizer,
                 displayController,
-                syncQueue);
+                syncQueue,
+                transitions);
     }
 
     //
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeStatus.java b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeStatus.java
index dc82fc1..88949b2a 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeStatus.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeStatus.java
@@ -55,6 +55,26 @@
             "persist.wm.debug.desktop_stashing", false);
 
     /**
+     * Flag to indicate whether to apply shadows to windows in desktop mode.
+     */
+    private static final boolean USE_WINDOW_SHADOWS = SystemProperties.getBoolean(
+            "persist.wm.debug.desktop_use_window_shadows", true);
+
+    /**
+     * Flag to indicate whether to apply shadows to the focused window in desktop mode.
+     *
+     * Note: this flag is only relevant if USE_WINDOW_SHADOWS is false.
+     */
+    private static final boolean USE_WINDOW_SHADOWS_FOCUSED_WINDOW = SystemProperties.getBoolean(
+            "persist.wm.debug.desktop_use_window_shadows_focused_window", false);
+
+    /**
+     * Flag to indicate whether to apply shadows to windows in desktop mode.
+     */
+    private static final boolean USE_ROUNDED_CORNERS = SystemProperties.getBoolean(
+            "persist.wm.debug.desktop_use_rounded_corners", true);
+
+    /**
      * Return {@code true} is desktop windowing proto 2 is enabled
      */
     public static boolean isEnabled() {
@@ -81,4 +101,21 @@
     public static boolean isStashingEnabled() {
         return IS_STASHING_ENABLED;
     }
+
+    /**
+     * Return whether to use window shadows.
+     *
+     * @param isFocusedWindow whether the window to apply shadows to is focused
+     */
+    public static boolean useWindowShadow(boolean isFocusedWindow) {
+        return USE_WINDOW_SHADOWS
+            || (USE_WINDOW_SHADOWS_FOCUSED_WINDOW && isFocusedWindow);
+    }
+
+    /**
+     * Return whether to use rounded corners for windows.
+     */
+    public static boolean useRoundedCorners() {
+        return USE_ROUNDED_CORNERS;
+    }
 }
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 144555d..4a1bcaa 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
@@ -721,6 +721,9 @@
             finishTransaction: SurfaceControl.Transaction
     ) {
         // Add rounded corners to freeform windows
+        if (!DesktopModeStatus.useRoundedCorners()) {
+            return
+        }
         val cornerRadius = ScreenDecorationsUtils.getWindowCornerRadius(context)
         info.changes
                 .filter { it.taskInfo?.windowingMode == WINDOWING_MODE_FREEFORM }
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 48a0a46..3b0e7c1 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
@@ -19,6 +19,7 @@
 import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
 import static android.view.WindowManager.TRANSIT_OPEN;
 import static android.view.WindowManager.TRANSIT_PIP;
+import static android.view.WindowManager.TRANSIT_TO_FRONT;
 
 import static com.android.wm.shell.transition.Transitions.TRANSIT_EXIT_PIP;
 
@@ -54,6 +55,8 @@
     @Nullable
     private WindowContainerToken mPipTaskToken;
     @Nullable
+    private IBinder mEnterTransition;
+    @Nullable
     private IBinder mAutoEnterButtonNavTransition;
     @Nullable
     private IBinder mExitViaExpandTransition;
@@ -98,11 +101,8 @@
     @Override
     public WindowContainerTransaction handleRequest(@NonNull IBinder transition,
             @NonNull TransitionRequestInfo request) {
-        if (isAutoEnterInButtonNavigation(request)) {
-            mAutoEnterButtonNavTransition = transition;
-            return getEnterPipTransaction(transition, request);
-        } else if (isLegacyEnter(request)) {
-            mLegacyEnterTransition = transition;
+        if (isAutoEnterInButtonNavigation(request) || isEnterPictureInPictureModeRequest(request)) {
+            mEnterTransition = transition;
             return getEnterPipTransaction(transition, request);
         }
         return null;
@@ -111,12 +111,9 @@
     @Override
     public void augmentRequest(@NonNull IBinder transition, @NonNull TransitionRequestInfo request,
             @NonNull WindowContainerTransaction outWct) {
-        if (isAutoEnterInButtonNavigation(request)) {
+        if (isAutoEnterInButtonNavigation(request) || isEnterPictureInPictureModeRequest(request)) {
             outWct.merge(getEnterPipTransaction(transition, request), true /* transfer */);
-            mAutoEnterButtonNavTransition = transition;
-        } else if (isLegacyEnter(request)) {
-            outWct.merge(getEnterPipTransaction(transition, request), true /* transfer */);
-            mLegacyEnterTransition = transition;
+            mEnterTransition = transition;
         }
     }
 
@@ -162,7 +159,7 @@
                 && pipTask.pictureInPictureParams.isAutoEnterEnabled();
     }
 
-    private boolean isLegacyEnter(@NonNull TransitionRequestInfo requestInfo) {
+    private boolean isEnterPictureInPictureModeRequest(@NonNull TransitionRequestInfo requestInfo) {
         return requestInfo.getType() == TRANSIT_PIP;
     }
 
@@ -172,13 +169,15 @@
             @NonNull SurfaceControl.Transaction startTransaction,
             @NonNull SurfaceControl.Transaction finishTransaction,
             @NonNull Transitions.TransitionFinishCallback finishCallback) {
-        if (transition == mAutoEnterButtonNavTransition) {
-            mAutoEnterButtonNavTransition = null;
-            return startAutoEnterButtonNavAnimation(info, startTransaction, finishTransaction,
-                    finishCallback);
-        } else if (transition == mLegacyEnterTransition) {
-            mLegacyEnterTransition = null;
-            return startLegacyEnterAnimation(info, startTransaction, finishTransaction,
+        if (transition == mEnterTransition) {
+            mEnterTransition = null;
+            if (isLegacyEnter(info)) {
+                // If this is a legacy-enter-pip (auto-enter is off and PiP activity went to pause),
+                // then we should run an ALPHA type (cross-fade) animation.
+                return startAlphaTypeEnterAnimation(info, startTransaction, finishTransaction,
+                        finishCallback);
+            }
+            return startBoundsTypeEnterAnimation(info, startTransaction, finishTransaction,
                     finishCallback);
         } else if (transition == mExitViaExpandTransition) {
             mExitViaExpandTransition = null;
@@ -187,7 +186,15 @@
         return false;
     }
 
-    private boolean startAutoEnterButtonNavAnimation(@NonNull TransitionInfo info,
+    private boolean isLegacyEnter(@NonNull TransitionInfo info) {
+        TransitionInfo.Change pipChange = getPipChange(info);
+        // If the only change in the changes list is a TO_FRONT mode PiP task,
+        // then this is legacy-enter PiP.
+        return pipChange != null && pipChange.getMode() == TRANSIT_TO_FRONT
+                && info.getChanges().size() == 1;
+    }
+
+    private boolean startBoundsTypeEnterAnimation(@NonNull TransitionInfo info,
             @NonNull SurfaceControl.Transaction startTransaction,
             @NonNull SurfaceControl.Transaction finishTransaction,
             @NonNull Transitions.TransitionFinishCallback finishCallback) {
@@ -205,7 +212,7 @@
         return true;
     }
 
-    private boolean startLegacyEnterAnimation(@NonNull TransitionInfo info,
+    private boolean startAlphaTypeEnterAnimation(@NonNull TransitionInfo info,
             @NonNull SurfaceControl.Transaction startTransaction,
             @NonNull SurfaceControl.Transaction finishTransaction,
             @NonNull Transitions.TransitionFinishCallback finishCallback) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java
index cf16920..cebc400 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/CaptionWindowDecorViewModel.java
@@ -54,6 +54,7 @@
     private final Choreographer mMainChoreographer;
     private final DisplayController mDisplayController;
     private final SyncTransactionQueue mSyncQueue;
+    private final Transitions mTransitions;
     private TaskOperations mTaskOperations;
 
     private final SparseArray<CaptionWindowDecoration> mWindowDecorByTaskId = new SparseArray<>();
@@ -64,13 +65,15 @@
             Choreographer mainChoreographer,
             ShellTaskOrganizer taskOrganizer,
             DisplayController displayController,
-            SyncTransactionQueue syncQueue) {
+            SyncTransactionQueue syncQueue,
+            Transitions transitions) {
         mContext = context;
         mMainHandler = mainHandler;
         mMainChoreographer = mainChoreographer;
         mTaskOrganizer = taskOrganizer;
         mDisplayController = displayController;
         mSyncQueue = syncQueue;
+        mTransitions = transitions;
         if (!Transitions.ENABLE_SHELL_TRANSITIONS) {
             mTaskOperations = new TaskOperations(null, mContext, mSyncQueue);
         }
@@ -133,7 +136,8 @@
         if (decoration == null) {
             createWindowDecoration(taskInfo, taskSurface, startT, finishT);
         } else {
-            decoration.relayout(taskInfo, startT, finishT, false /* applyStartTransactionOnDraw */);
+            decoration.relayout(taskInfo, startT, finishT, false /* applyStartTransactionOnDraw */,
+                    false /* setTaskCropAndPosition */);
         }
     }
 
@@ -145,7 +149,8 @@
         final CaptionWindowDecoration decoration = mWindowDecorByTaskId.get(taskInfo.taskId);
         if (decoration == null) return;
 
-        decoration.relayout(taskInfo, startT, finishT, false /* applyStartTransactionOnDraw */);
+        decoration.relayout(taskInfo, startT, finishT, false /* applyStartTransactionOnDraw */,
+                false /* setTaskCropAndPosition */);
     }
 
     @Override
@@ -191,16 +196,17 @@
                         mSyncQueue);
         mWindowDecorByTaskId.put(taskInfo.taskId, windowDecoration);
 
-        final DragPositioningCallback dragPositioningCallback =
-                new FluidResizeTaskPositioner(mTaskOrganizer, windowDecoration, mDisplayController,
-                        0 /* disallowedAreaForEndBoundsHeight */);
+        final FluidResizeTaskPositioner taskPositioner =
+                new FluidResizeTaskPositioner(mTaskOrganizer, mTransitions, windowDecoration,
+                        mDisplayController, 0 /* disallowedAreaForEndBoundsHeight */);
         final CaptionTouchEventListener touchEventListener =
-                new CaptionTouchEventListener(taskInfo, dragPositioningCallback);
+                new CaptionTouchEventListener(taskInfo, taskPositioner);
         windowDecoration.setCaptionListeners(touchEventListener, touchEventListener);
-        windowDecoration.setDragPositioningCallback(dragPositioningCallback);
+        windowDecoration.setDragPositioningCallback(taskPositioner);
         windowDecoration.setDragDetector(touchEventListener.mDragDetector);
+        windowDecoration.setTaskDragResizer(taskPositioner);
         windowDecoration.relayout(taskInfo, startT, finishT,
-                false /* applyStartTransactionOnDraw */);
+                false /* applyStartTransactionOnDraw */, false /* setTaskCropAndPosition */);
         setupCaptionColor(taskInfo, windowDecoration);
     }
 
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 6e7d11d..1debb02 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
@@ -157,15 +157,21 @@
     @Override
     void relayout(RunningTaskInfo taskInfo) {
         final SurfaceControl.Transaction t = new SurfaceControl.Transaction();
+        // The crop and position of the task should only be set when a task is fluid resizing. In
+        // all other cases, it is expected that the transition handler positions and crops the task
+        // in order to allow the handler time to animate before the task before the final
+        // position and crop are set.
+        final boolean shouldSetTaskPositionAndCrop = mTaskDragResizer.isResizingOrAnimating();
         // Use |applyStartTransactionOnDraw| so that the transaction (that applies task crop) is
         // synced with the buffer transaction (that draws the View). Both will be shown on screen
         // at the same, whereas applying them independently causes flickering. See b/270202228.
-        relayout(taskInfo, t, t, true /* applyStartTransactionOnDraw */);
+        relayout(taskInfo, t, t, true /* applyStartTransactionOnDraw */,
+                shouldSetTaskPositionAndCrop);
     }
 
     void relayout(RunningTaskInfo taskInfo,
             SurfaceControl.Transaction startT, SurfaceControl.Transaction finishT,
-            boolean applyStartTransactionOnDraw) {
+            boolean applyStartTransactionOnDraw, boolean setTaskCropAndPosition) {
         final int shadowRadiusID = taskInfo.isFocused
                 ? R.dimen.freeform_decor_shadow_focused_thickness
                 : R.dimen.freeform_decor_shadow_unfocused_thickness;
@@ -183,6 +189,7 @@
         mRelayoutParams.mCaptionHeightId = getCaptionHeightId(taskInfo.getWindowingMode());
         mRelayoutParams.mShadowRadiusId = shadowRadiusID;
         mRelayoutParams.mApplyStartTransactionOnDraw = applyStartTransactionOnDraw;
+        mRelayoutParams.mSetTaskPositionAndCrop = setTaskCropAndPosition;
 
         relayout(mRelayoutParams, startT, finishT, wct, oldRootView, mResult);
         // After this line, mTaskInfo is up-to-date and should be used instead of taskInfo
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 ab29df1..61a8e9b 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
@@ -335,7 +335,8 @@
         if (decoration == null) {
             createWindowDecoration(taskInfo, taskSurface, startT, finishT);
         } else {
-            decoration.relayout(taskInfo, startT, finishT, false /* applyStartTransactionOnDraw */);
+            decoration.relayout(taskInfo, startT, finishT, false /* applyStartTransactionOnDraw */,
+                    false /* shouldSetTaskPositionAndCrop */);
         }
     }
 
@@ -347,7 +348,8 @@
         final DesktopModeWindowDecoration decoration = mWindowDecorByTaskId.get(taskInfo.taskId);
         if (decoration == null) return;
 
-        decoration.relayout(taskInfo, startT, finishT, false /* applyStartTransactionOnDraw */);
+        decoration.relayout(taskInfo, startT, finishT, false /* applyStartTransactionOnDraw */,
+                false /* shouldSetTaskPositionAndCrop */);
     }
 
     @Override
@@ -724,7 +726,7 @@
     private void handleEventOutsideFocusedCaption(MotionEvent ev,
             DesktopModeWindowDecoration relevantDecor) {
         // Returns if event occurs within caption
-        if (relevantDecor == null || relevantDecor.checkTouchEventInCaption(ev)) {
+        if (relevantDecor == null || relevantDecor.checkTouchEventInCaptionHandle(ev)) {
             return;
         }
 
@@ -759,7 +761,8 @@
                                 || windowingMode == WINDOWING_MODE_MULTI_WINDOW;
                     }
 
-                    if (dragFromStatusBarAllowed && relevantDecor.checkTouchEventInHandle(ev)) {
+                    if (dragFromStatusBarAllowed
+                            && relevantDecor.checkTouchEventInCaptionHandle(ev)) {
                         mTransitionDragActive = true;
                     }
                 }
@@ -1010,8 +1013,23 @@
         mWindowDecorByTaskId.put(taskInfo.taskId, windowDecoration);
         windowDecoration.createResizeVeil();
 
-        final DragPositioningCallback dragPositioningCallback = createDragPositioningCallback(
-                windowDecoration);
+        final DragPositioningCallback dragPositioningCallback;
+        final int transitionAreaHeight = mContext.getResources().getDimensionPixelSize(
+                R.dimen.desktop_mode_transition_area_height);
+        if (!DesktopModeStatus.isVeiledResizeEnabled()) {
+            dragPositioningCallback =  new FluidResizeTaskPositioner(
+                    mTaskOrganizer, mTransitions, windowDecoration, mDisplayController,
+                    mDragStartListener, mTransactionFactory, transitionAreaHeight);
+            windowDecoration.setTaskDragResizer(
+                    (FluidResizeTaskPositioner) dragPositioningCallback);
+        } else {
+            dragPositioningCallback =  new VeiledResizeTaskPositioner(
+                    mTaskOrganizer, windowDecoration, mDisplayController,
+                    mDragStartListener, mTransitions, transitionAreaHeight);
+            windowDecoration.setTaskDragResizer(
+                    (VeiledResizeTaskPositioner) dragPositioningCallback);
+        }
+
         final DesktopModeTouchEventListener touchEventListener =
                 new DesktopModeTouchEventListener(taskInfo, dragPositioningCallback);
 
@@ -1021,23 +1039,9 @@
         windowDecoration.setDragPositioningCallback(dragPositioningCallback);
         windowDecoration.setDragDetector(touchEventListener.mDragDetector);
         windowDecoration.relayout(taskInfo, startT, finishT,
-                false /* applyStartTransactionOnDraw */);
+                false /* applyStartTransactionOnDraw */, false /* shouldSetTaskPositionAndCrop */);
         incrementEventReceiverTasks(taskInfo.displayId);
     }
-    private DragPositioningCallback createDragPositioningCallback(
-            @NonNull DesktopModeWindowDecoration windowDecoration) {
-        final int transitionAreaHeight = mContext.getResources().getDimensionPixelSize(
-                R.dimen.desktop_mode_transition_area_height);
-        if (!DesktopModeStatus.isVeiledResizeEnabled()) {
-            return new FluidResizeTaskPositioner(mTaskOrganizer, windowDecoration,
-                    mDisplayController, mDragStartListener, mTransactionFactory,
-                    transitionAreaHeight);
-        } else {
-            return new VeiledResizeTaskPositioner(mTaskOrganizer, windowDecoration,
-                    mDisplayController, mDragStartListener, mTransitions,
-                    transitionAreaHeight);
-        }
-    }
 
     private RunningTaskInfo getOtherSplitTask(int taskId) {
         @SplitPosition int remainingTaskPosition = mSplitScreenController
@@ -1138,7 +1142,6 @@
             }
         }
     }
-
 }
 
 
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 3b6be8a..d08b655 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
@@ -46,6 +46,7 @@
 import android.widget.ImageButton;
 import android.window.WindowContainerTransaction;
 
+import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.policy.ScreenDecorationsUtils;
 import com.android.launcher3.icons.BaseIconFactory;
 import com.android.launcher3.icons.IconProvider;
@@ -186,55 +187,33 @@
         }
 
         final SurfaceControl.Transaction t = mSurfaceControlTransactionSupplier.get();
+        // The crop and position of the task should only be set when a task is fluid resizing. In
+        // all other cases, it is expected that the transition handler positions and crops the task
+        // in order to allow the handler time to animate before the task before the final
+        // position and crop are set.
+        final boolean shouldSetTaskPositionAndCrop = !DesktopModeStatus.isVeiledResizeEnabled()
+                && mTaskDragResizer.isResizingOrAnimating();
         // Use |applyStartTransactionOnDraw| so that the transaction (that applies task crop) is
         // synced with the buffer transaction (that draws the View). Both will be shown on screen
         // at the same, whereas applying them independently causes flickering. See b/270202228.
-        relayout(taskInfo, t, t, true /* applyStartTransactionOnDraw */);
+        relayout(taskInfo, t, t, true /* applyStartTransactionOnDraw */,
+                shouldSetTaskPositionAndCrop);
     }
 
     void relayout(ActivityManager.RunningTaskInfo taskInfo,
             SurfaceControl.Transaction startT, SurfaceControl.Transaction finishT,
-            boolean applyStartTransactionOnDraw) {
-        final int shadowRadiusID = taskInfo.isFocused
-                ? R.dimen.freeform_decor_shadow_focused_thickness
-                : R.dimen.freeform_decor_shadow_unfocused_thickness;
-        final boolean isFreeform =
-                taskInfo.getWindowingMode() == WINDOWING_MODE_FREEFORM;
-        final boolean isDragResizeable = isFreeform && taskInfo.isResizeable;
-
+            boolean applyStartTransactionOnDraw, boolean shouldSetTaskPositionAndCrop) {
         if (isHandleMenuActive()) {
             mHandleMenu.relayout(startT);
         }
 
+        updateRelayoutParams(mRelayoutParams, mContext, taskInfo, applyStartTransactionOnDraw,
+                shouldSetTaskPositionAndCrop);
+
         final WindowDecorLinearLayout oldRootView = mResult.mRootView;
         final SurfaceControl oldDecorationSurface = mDecorationContainerSurface;
         final WindowContainerTransaction wct = new WindowContainerTransaction();
 
-        final int windowDecorLayoutId = getDesktopModeWindowDecorLayoutId(
-                taskInfo.getWindowingMode());
-        mRelayoutParams.reset();
-        mRelayoutParams.mRunningTaskInfo = taskInfo;
-        mRelayoutParams.mLayoutResId = windowDecorLayoutId;
-        mRelayoutParams.mCaptionHeightId = getCaptionHeightId(taskInfo.getWindowingMode());
-        mRelayoutParams.mShadowRadiusId = shadowRadiusID;
-        mRelayoutParams.mApplyStartTransactionOnDraw = applyStartTransactionOnDraw;
-        // The configuration used to lay out the window decoration. The system context's config is
-        // used when the task density has been overridden to a custom density so that the resources
-        // and views of the decoration aren't affected and match the rest of the System UI, if not
-        // then just use the task's configuration. A copy is made instead of using the original
-        // reference so that the configuration isn't mutated on config changes and diff checks can
-        // be made in WindowDecoration#relayout using the pre/post-relayout configuration.
-        // See b/301119301.
-        // TODO(b/301119301): consider moving the config data needed for diffs to relayout params
-        // instead of using a whole Configuration as a parameter.
-        final Configuration windowDecorConfig = new Configuration();
-        windowDecorConfig.setTo(DesktopTasksController.isDesktopDensityOverrideSet()
-                ? mContext.getResources().getConfiguration() // Use system context.
-                : mTaskInfo.configuration); // Use task configuration.
-        mRelayoutParams.mWindowDecorConfig = windowDecorConfig;
-
-        mRelayoutParams.mCornerRadius =
-                (int) ScreenDecorationsUtils.getWindowCornerRadius(mContext);
         relayout(mRelayoutParams, startT, finishT, wct, oldRootView, mResult);
         // After this line, mTaskInfo is up-to-date and should be used instead of taskInfo
 
@@ -273,6 +252,9 @@
             closeMaximizeMenu();
         }
 
+        final boolean isFreeform =
+                taskInfo.getWindowingMode() == WINDOWING_MODE_FREEFORM;
+        final boolean isDragResizeable = isFreeform && taskInfo.isResizeable;
         if (!isDragResizeable) {
             if (!mTaskInfo.positionInParent.equals(mPositionInParent)) {
                 // We still want to track caption bar's exclusion region on a non-resizeable task.
@@ -323,6 +305,59 @@
         }
     }
 
+    @VisibleForTesting
+    static void updateRelayoutParams(
+            RelayoutParams relayoutParams,
+            Context context,
+            ActivityManager.RunningTaskInfo taskInfo,
+            boolean applyStartTransactionOnDraw,
+            boolean shouldSetTaskPositionAndCrop) {
+        relayoutParams.reset();
+        relayoutParams.mRunningTaskInfo = taskInfo;
+        relayoutParams.mLayoutResId =
+            getDesktopModeWindowDecorLayoutId(taskInfo.getWindowingMode());
+        relayoutParams.mCaptionHeightId = getCaptionHeightIdStatic(taskInfo.getWindowingMode());
+        relayoutParams.mCaptionWidthId = getCaptionWidthId(relayoutParams.mLayoutResId);
+        if (DesktopModeStatus.useWindowShadow(/* isFocusedWindow= */ taskInfo.isFocused)) {
+            relayoutParams.mShadowRadiusId = taskInfo.isFocused
+                    ? R.dimen.freeform_decor_shadow_focused_thickness
+                    : R.dimen.freeform_decor_shadow_unfocused_thickness;
+        }
+        relayoutParams.mApplyStartTransactionOnDraw = applyStartTransactionOnDraw;
+        relayoutParams.mSetTaskPositionAndCrop = shouldSetTaskPositionAndCrop;
+        // The configuration used to lay out the window decoration. The system context's config is
+        // used when the task density has been overridden to a custom density so that the resources
+        // and views of the decoration aren't affected and match the rest of the System UI, if not
+        // then just use the task's configuration. A copy is made instead of using the original
+        // reference so that the configuration isn't mutated on config changes and diff checks can
+        // be made in WindowDecoration#relayout using the pre/post-relayout configuration.
+        // See b/301119301.
+        // TODO(b/301119301): consider moving the config data needed for diffs to relayout params
+        // instead of using a whole Configuration as a parameter.
+        final Configuration windowDecorConfig = new Configuration();
+        windowDecorConfig.setTo(DesktopTasksController.isDesktopDensityOverrideSet()
+                ? context.getResources().getConfiguration() // Use system context.
+                : taskInfo.configuration); // Use task configuration.
+        relayoutParams.mWindowDecorConfig = windowDecorConfig;
+
+        if (DesktopModeStatus.useRoundedCorners()) {
+            relayoutParams.mCornerRadius =
+                    (int) ScreenDecorationsUtils.getWindowCornerRadius(context);
+        }
+    }
+
+    /**
+     * If task has focused window decor, return the caption id of the fullscreen caption size
+     * resource. Otherwise, return ID_NULL and caption width be set to task width.
+     */
+    private static int getCaptionWidthId(int layoutResId) {
+        if (layoutResId == R.layout.desktop_mode_focused_window_decor) {
+            return R.dimen.desktop_mode_fullscreen_decor_caption_width;
+        }
+        return Resources.ID_NULL;
+    }
+
+
     private PointF calculateMaximizeMenuPosition() {
         final PointF position = new PointF();
         final Resources resources = mContext.getResources();
@@ -535,7 +570,6 @@
                 .setOnClickListener(mOnCaptionButtonClickListener)
                 .setOnTouchListener(mOnCaptionTouchListener)
                 .setLayoutId(mRelayoutParams.mLayoutResId)
-                .setCaptionPosition(mRelayoutParams.mCaptionX, mRelayoutParams.mCaptionY)
                 .setWindowingButtonsVisible(DesktopModeStatus.isEnabled())
                 .setCaptionHeight(mResult.mCaptionHeight)
                 .build();
@@ -612,35 +646,25 @@
                 mTaskOrganizer.getRunningTaskInfo(mTaskInfo.taskId);
         if (taskInfo == null) return result;
         final Point positionInParent = taskInfo.positionInParent;
-        result.offset(-mRelayoutParams.mCaptionX, -mRelayoutParams.mCaptionY);
         result.offset(-positionInParent.x, -positionInParent.y);
         return result;
     }
 
     /**
-     * Determine if a passed MotionEvent is in a view in caption
+     * Checks if motion event occurs in the caption handle area. This should be used in cases where
+     * onTouchListener will not work (i.e. when caption is in status bar area).
      *
      * @param ev       the {@link MotionEvent} to check
-     * @param layoutId the id of the view
      * @return {@code true} if event is inside the specified view, {@code false} if not
      */
-    private boolean checkEventInCaptionView(MotionEvent ev, int layoutId) {
-        if (mResult.mRootView == null) return false;
+    boolean checkTouchEventInCaptionHandle(MotionEvent ev) {
+        if (isHandleMenuActive() || !(mWindowDecorViewHolder
+                instanceof DesktopModeFocusedWindowDecorationViewHolder)) {
+            return false;
+        }
         final PointF inputPoint = offsetCaptionLocation(ev);
-        final View view = mResult.mRootView.findViewById(layoutId);
-        return view != null && pointInView(view, inputPoint.x, inputPoint.y);
-    }
-
-    boolean checkTouchEventInHandle(MotionEvent ev) {
-        if (isHandleMenuActive()) return false;
-        return checkEventInCaptionView(ev, R.id.caption_handle);
-    }
-
-    /**
-     * Returns true if motion event is within the caption's root view's bounds.
-     */
-    boolean checkTouchEventInCaption(MotionEvent ev) {
-        return checkEventInCaptionView(ev, getCaptionViewId());
+        return ((DesktopModeFocusedWindowDecorationViewHolder) mWindowDecorViewHolder)
+                .pointInCaption(inputPoint, mResult.mCaptionX);
     }
 
     /**
@@ -653,24 +677,19 @@
     void checkClickEvent(MotionEvent ev) {
         if (mResult.mRootView == null) return;
         if (!isHandleMenuActive()) {
+            // Click if point in caption handle view
             final View caption = mResult.mRootView.findViewById(R.id.desktop_mode_caption);
             final View handle = caption.findViewById(R.id.caption_handle);
-            clickIfPointInView(new PointF(ev.getX(), ev.getY()), handle);
+            if (checkTouchEventInCaptionHandle(ev)) {
+                mOnCaptionButtonClickListener.onClick(handle);
+            }
         } else {
             mHandleMenu.checkClickEvent(ev);
             closeHandleMenuIfNeeded(ev);
         }
     }
 
-    private boolean clickIfPointInView(PointF inputPoint, View v) {
-        if (pointInView(v, inputPoint.x, inputPoint.y)) {
-            mOnCaptionButtonClickListener.onClick(v);
-            return true;
-        }
-        return false;
-    }
-
-    boolean pointInView(View v, float x, float y) {
+    private boolean pointInView(View v, float x, float y) {
         return v != null && v.getLeft() <= x && v.getRight() >= x
                 && v.getTop() <= y && v.getBottom() >= y;
     }
@@ -684,7 +703,7 @@
         super.close();
     }
 
-    private int getDesktopModeWindowDecorLayoutId(@WindowingMode int windowingMode) {
+    private static int getDesktopModeWindowDecorLayoutId(@WindowingMode int windowingMode) {
         return windowingMode == WINDOWING_MODE_FREEFORM
                 ? R.layout.desktop_mode_app_controls_window_decor
                 : R.layout.desktop_mode_focused_window_decor;
@@ -730,6 +749,10 @@
 
     @Override
     int getCaptionHeightId(@WindowingMode int windowingMode) {
+        return getCaptionHeightIdStatic(windowingMode);
+    }
+
+    private static int getCaptionHeightIdStatic(@WindowingMode int windowingMode) {
         return windowingMode == WINDOWING_MODE_FULLSCREEN
                 ? R.dimen.desktop_mode_fullscreen_decor_caption_height
                 : R.dimen.desktop_mode_freeform_decor_caption_height;
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 677c7f1..5afbd54 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
@@ -26,9 +26,7 @@
 import android.graphics.Rect;
 import android.util.DisplayMetrics;
 import android.view.SurfaceControl;
-import android.window.WindowContainerTransaction;
 
-import com.android.wm.shell.ShellTaskOrganizer;
 import com.android.wm.shell.common.DisplayController;
 
 /**
@@ -130,8 +128,7 @@
             Rect taskBoundsAtDragStart, PointF repositionStartPoint, SurfaceControl.Transaction t,
             float x, float y) {
         updateTaskBounds(repositionTaskBounds, taskBoundsAtDragStart, repositionStartPoint, x, y);
-        t.setPosition(decoration.mTaskSurface, repositionTaskBounds.left,
-                repositionTaskBounds.top);
+        t.setPosition(decoration.mTaskSurface, repositionTaskBounds.left, repositionTaskBounds.top);
     }
 
     private static void updateTaskBounds(Rect repositionTaskBounds, Rect taskBoundsAtDragStart,
@@ -188,18 +185,6 @@
         }
     }
 
-    /**
-     * Apply a bounds change to a task.
-     * @param windowDecoration decor of task we are changing bounds for
-     * @param taskBounds new bounds of this task
-     * @param taskOrganizer applies the provided WindowContainerTransaction
-     */
-    static void applyTaskBoundsChange(WindowContainerTransaction wct,
-            WindowDecoration windowDecoration, Rect taskBounds, ShellTaskOrganizer taskOrganizer) {
-        wct.setBounds(windowDecoration.mTaskInfo.token, taskBounds);
-        taskOrganizer.applyTransaction(wct);
-    }
-
     private static float getMinWidth(DisplayController displayController,
             WindowDecoration windowDecoration) {
         return windowDecoration.mTaskInfo.minWidth < 0 ? getDefaultMinSize(displayController,
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/FluidResizeTaskPositioner.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/FluidResizeTaskPositioner.java
index 5d006fb..6bfc7cd 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/FluidResizeTaskPositioner.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/FluidResizeTaskPositioner.java
@@ -16,23 +16,42 @@
 
 package com.android.wm.shell.windowdecor;
 
+import static android.view.WindowManager.TRANSIT_CHANGE;
+
 import android.graphics.PointF;
 import android.graphics.Rect;
+import android.os.IBinder;
 import android.view.Surface;
 import android.view.SurfaceControl;
+import android.window.TransitionInfo;
+import android.window.TransitionRequestInfo;
 import android.window.WindowContainerTransaction;
 
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
 import com.android.wm.shell.ShellTaskOrganizer;
 import com.android.wm.shell.common.DisplayController;
+import com.android.wm.shell.transition.Transitions;
 
 import java.util.function.Supplier;
 
 /**
  * A task positioner that resizes/relocates task contents as it is dragged.
  * Utilizes {@link DragPositioningCallbackUtility} to determine new task bounds.
+ *
+ * This positioner applies the final bounds after a resize or drag using a shell transition in order
+ * to utilize the startAnimation callback to set the final task position and crop. In most cases,
+ * the transition will be aborted since the final bounds are usually the same bounds set in the
+ * final {@link #onDragPositioningMove} call. In this case, the cropping and positioning would be
+ * set by {@link WindowDecoration#relayout} due to the final bounds change; however, it is important
+ * that we send the final shell transition since we still utilize the {@link #onTransitionConsumed}
+ * callback.
  */
-class FluidResizeTaskPositioner implements DragPositioningCallback {
+class FluidResizeTaskPositioner implements DragPositioningCallback,
+        TaskDragResizer, Transitions.TransitionHandler {
     private final ShellTaskOrganizer mTaskOrganizer;
+    private final Transitions mTransitions;
     private final WindowDecoration mWindowDecoration;
     private final Supplier<SurfaceControl.Transaction> mTransactionSupplier;
     private DisplayController mDisplayController;
@@ -45,21 +64,28 @@
     // finalize the bounds there using WCT#setBounds
     private final int mDisallowedAreaForEndBoundsHeight;
     private boolean mHasDragResized;
+    private boolean mIsResizingOrAnimatingResize;
     private int mCtrlType;
+    private IBinder mDragResizeEndTransition;
     @Surface.Rotation private int mRotation;
 
-    FluidResizeTaskPositioner(ShellTaskOrganizer taskOrganizer, WindowDecoration windowDecoration,
-            DisplayController displayController, int disallowedAreaForEndBoundsHeight) {
-        this(taskOrganizer, windowDecoration, displayController, dragStartListener -> {},
-                SurfaceControl.Transaction::new, disallowedAreaForEndBoundsHeight);
+    FluidResizeTaskPositioner(ShellTaskOrganizer taskOrganizer, Transitions transitions,
+            WindowDecoration windowDecoration, DisplayController displayController,
+            int disallowedAreaForEndBoundsHeight) {
+        this(taskOrganizer, transitions, windowDecoration, displayController,
+                dragStartListener -> {}, SurfaceControl.Transaction::new,
+                disallowedAreaForEndBoundsHeight);
     }
 
-    FluidResizeTaskPositioner(ShellTaskOrganizer taskOrganizer, WindowDecoration windowDecoration,
+    FluidResizeTaskPositioner(ShellTaskOrganizer taskOrganizer,
+            Transitions transitions,
+            WindowDecoration windowDecoration,
             DisplayController displayController,
             DragPositioningCallbackUtility.DragStartListener dragStartListener,
             Supplier<SurfaceControl.Transaction> supplier,
             int disallowedAreaForEndBoundsHeight) {
         mTaskOrganizer = taskOrganizer;
+        mTransitions = transitions;
         mWindowDecoration = windowDecoration;
         mDisplayController = displayController;
         mDragStartListener = dragStartListener;
@@ -103,9 +129,10 @@
                 // This is the first bounds change since drag resize operation started.
                 wct.setDragResizing(mWindowDecoration.mTaskInfo.token, true /* dragResizing */);
             }
-            DragPositioningCallbackUtility.applyTaskBoundsChange(wct, mWindowDecoration,
-                    mRepositionTaskBounds, mTaskOrganizer);
+            wct.setBounds(mWindowDecoration.mTaskInfo.token, mRepositionTaskBounds);
+            mTaskOrganizer.applyTransaction(wct);
             mHasDragResized = true;
+            mIsResizingOrAnimatingResize = true;
         } else if (mCtrlType == CTRL_TYPE_UNDEFINED) {
             final SurfaceControl.Transaction t = mTransactionSupplier.get();
             DragPositioningCallbackUtility.setPositionOnDrag(mWindowDecoration,
@@ -129,7 +156,7 @@
                     mWindowDecoration)) {
                 wct.setBounds(mWindowDecoration.mTaskInfo.token, mRepositionTaskBounds);
             }
-            mTaskOrganizer.applyTransaction(wct);
+            mDragResizeEndTransition = mTransitions.startTransition(TRANSIT_CHANGE, wct, this);
         } else if (mCtrlType == CTRL_TYPE_UNDEFINED
                 && DragPositioningCallbackUtility.isBelowDisallowedArea(
                 mDisallowedAreaForEndBoundsHeight, mTaskBoundsAtDragStart, mRepositionStartPoint,
@@ -139,7 +166,7 @@
                     mTaskBoundsAtDragStart, mRepositionStartPoint, x, y,
                     mWindowDecoration.calculateValidDragArea());
             wct.setBounds(mWindowDecoration.mTaskInfo.token, mRepositionTaskBounds);
-            mTaskOrganizer.applyTransaction(wct);
+            mTransitions.startTransition(TRANSIT_CHANGE, wct, this);
         }
 
         mTaskBoundsAtDragStart.setEmpty();
@@ -154,4 +181,51 @@
                 || (mCtrlType & CTRL_TYPE_LEFT) != 0 || (mCtrlType & CTRL_TYPE_RIGHT) != 0;
     }
 
+    @Override
+    public boolean startAnimation(@NonNull IBinder transition, @NonNull TransitionInfo info,
+            @NonNull SurfaceControl.Transaction startTransaction,
+            @NonNull SurfaceControl.Transaction finishTransaction,
+            @NonNull Transitions.TransitionFinishCallback finishCallback) {
+        for (TransitionInfo.Change change: info.getChanges()) {
+            final SurfaceControl sc = change.getLeash();
+            final Rect endBounds = change.getEndAbsBounds();
+            startTransaction.setWindowCrop(sc, endBounds.width(), endBounds.height())
+                    .setPosition(sc, endBounds.left, endBounds.top);
+            finishTransaction.setWindowCrop(sc, endBounds.width(), endBounds.height())
+                    .setPosition(sc, endBounds.left, endBounds.top);
+        }
+
+        startTransaction.apply();
+        if (transition.equals(mDragResizeEndTransition)) {
+            mIsResizingOrAnimatingResize = false;
+            mDragResizeEndTransition = null;
+        }
+        finishCallback.onTransitionFinished(null);
+        return true;
+    }
+
+    /**
+     * We should never reach this as this handler's transitions are only started from shell
+     * explicitly.
+     */
+    @Nullable
+    @Override
+    public WindowContainerTransaction handleRequest(@NonNull IBinder transition,
+            @NonNull TransitionRequestInfo request) {
+        return null;
+    }
+
+    @Override
+    public void onTransitionConsumed(@NonNull IBinder transition, boolean aborted,
+            @Nullable SurfaceControl.Transaction finishTransaction) {
+        if (transition.equals(mDragResizeEndTransition)) {
+            mIsResizingOrAnimatingResize = false;
+            mDragResizeEndTransition = null;
+        }
+    }
+
+    @Override
+    public boolean isResizingOrAnimating() {
+        return mIsResizingOrAnimatingResize;
+    }
 }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/HandleMenu.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/HandleMenu.java
index 652a2ed..b37dd0d 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/HandleMenu.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/HandleMenu.java
@@ -64,8 +64,6 @@
     private final View.OnTouchListener mOnTouchListener;
     private final RunningTaskInfo mTaskInfo;
     private final int mLayoutResId;
-    private final int mCaptionX;
-    private final int mCaptionY;
     private int mMarginMenuTop;
     private int mMarginMenuStart;
     private int mMenuHeight;
@@ -74,16 +72,13 @@
     private HandleMenuAnimator mHandleMenuAnimator;
 
 
-    HandleMenu(WindowDecoration parentDecor, int layoutResId, int captionX, int captionY,
-            View.OnClickListener onClickListener, View.OnTouchListener onTouchListener,
-            Bitmap appIcon, CharSequence appName, boolean shouldShowWindowingPill,
-            int captionHeight) {
+    HandleMenu(WindowDecoration parentDecor, int layoutResId, View.OnClickListener onClickListener,
+            View.OnTouchListener onTouchListener, Bitmap appIcon, CharSequence appName,
+            boolean shouldShowWindowingPill, int captionHeight) {
         mParentDecor = parentDecor;
         mContext = mParentDecor.mDecorWindowContext;
         mTaskInfo = mParentDecor.mTaskInfo;
         mLayoutResId = layoutResId;
-        mCaptionX = captionX;
-        mCaptionY = captionY;
         mOnClickListener = onClickListener;
         mOnTouchListener = onTouchListener;
         mAppIconBitmap = appIcon;
@@ -225,12 +220,12 @@
         if (mLayoutResId
                 == R.layout.desktop_mode_app_controls_window_decor) {
             // Align the handle menu to the left of the caption.
-            menuX = mCaptionX + mMarginMenuStart;
-            menuY = mCaptionY + mMarginMenuTop;
+            menuX = mMarginMenuStart;
+            menuY = mMarginMenuTop;
         } else {
             // Position the handle menu at the center of the caption.
-            menuX = mCaptionX + (captionWidth / 2) - (mMenuWidth / 2);
-            menuY = mCaptionY + mMarginMenuStart;
+            menuX = (captionWidth / 2) - (mMenuWidth / 2);
+            menuY = mMarginMenuStart;
         }
 
         // Handle Menu position setup.
@@ -346,8 +341,6 @@
         private View.OnClickListener mOnClickListener;
         private View.OnTouchListener mOnTouchListener;
         private int mLayoutId;
-        private int mCaptionX;
-        private int mCaptionY;
         private boolean mShowWindowingPill;
         private int mCaptionHeight;
 
@@ -381,12 +374,6 @@
             return this;
         }
 
-        Builder setCaptionPosition(int captionX, int captionY) {
-            mCaptionX = captionX;
-            mCaptionY = captionY;
-            return this;
-        }
-
         Builder setWindowingButtonsVisible(boolean windowingButtonsVisible) {
             mShowWindowingPill = windowingButtonsVisible;
             return this;
@@ -398,8 +385,8 @@
         }
 
         HandleMenu build() {
-            return new HandleMenu(mParent, mLayoutId, mCaptionX, mCaptionY, mOnClickListener,
-                    mOnTouchListener, mAppIcon, mName, mShowWindowingPill, mCaptionHeight);
+            return new HandleMenu(mParent, mLayoutId, mOnClickListener, mOnTouchListener,
+                    mAppIcon, mName, mShowWindowingPill, mCaptionHeight);
         }
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/di/bound/CustomTileUser.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/TaskDragResizer.java
similarity index 67%
copy from packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/di/bound/CustomTileUser.kt
copy to libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/TaskDragResizer.java
index efc7431..40421b5 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/di/bound/CustomTileUser.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/TaskDragResizer.java
@@ -14,12 +14,16 @@
  * limitations under the License.
  */
 
-package com.android.systemui.qs.tiles.impl.custom.di.bound
+package com.android.wm.shell.windowdecor;
 
-import javax.inject.Qualifier
+/**
+ * Holds the state of a drag resize.
+ */
+interface TaskDragResizer {
 
-/** User associated with current custom tile binding. */
-@Qualifier
-@MustBeDocumented
-@Retention(AnnotationRetention.RUNTIME)
-annotation class CustomTileUser
+    /**
+     * Returns true if task is currently being resized or animating the final transition after
+     * a resize is complete.
+     */
+    boolean isResizingOrAnimating();
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositioner.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositioner.java
index 4363558..c1b18f9 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositioner.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/VeiledResizeTaskPositioner.java
@@ -43,7 +43,7 @@
  * If the drag is repositioning, we update in the typical manner.
  */
 public class VeiledResizeTaskPositioner implements DragPositioningCallback,
-        Transitions.TransitionHandler {
+        TaskDragResizer, Transitions.TransitionHandler {
 
     private DesktopModeWindowDecoration mDesktopWindowDecoration;
     private ShellTaskOrganizer mTaskOrganizer;
@@ -59,10 +59,12 @@
     private final int mDisallowedAreaForEndBoundsHeight;
     private final Supplier<SurfaceControl.Transaction> mTransactionSupplier;
     private int mCtrlType;
+    private boolean mIsResizingOrAnimatingResize;
     @Surface.Rotation private int mRotation;
 
     public VeiledResizeTaskPositioner(ShellTaskOrganizer taskOrganizer,
-            DesktopModeWindowDecoration windowDecoration, DisplayController displayController,
+            DesktopModeWindowDecoration windowDecoration,
+            DisplayController displayController,
             DragPositioningCallbackUtility.DragStartListener dragStartListener,
             Transitions transitions,
             int disallowedAreaForEndBoundsHeight) {
@@ -71,12 +73,13 @@
     }
 
     public VeiledResizeTaskPositioner(ShellTaskOrganizer taskOrganizer,
-            DesktopModeWindowDecoration windowDecoration, DisplayController displayController,
+            DesktopModeWindowDecoration windowDecoration,
+            DisplayController displayController,
             DragPositioningCallbackUtility.DragStartListener dragStartListener,
             Supplier<SurfaceControl.Transaction> supplier, Transitions transitions,
             int disallowedAreaForEndBoundsHeight) {
-        mTaskOrganizer = taskOrganizer;
         mDesktopWindowDecoration = windowDecoration;
+        mTaskOrganizer = taskOrganizer;
         mDisplayController = displayController;
         mDragStartListener = dragStartListener;
         mTransactionSupplier = supplier;
@@ -117,6 +120,7 @@
                 mRepositionTaskBounds, mTaskBoundsAtDragStart, mStableBounds, delta,
                 mDisplayController, mDesktopWindowDecoration)) {
             mDesktopWindowDecoration.updateResizeVeil(mRepositionTaskBounds);
+            mIsResizingOrAnimatingResize = true;
         } else if (mCtrlType == CTRL_TYPE_UNDEFINED) {
             final SurfaceControl.Transaction t = mTransactionSupplier.get();
             DragPositioningCallbackUtility.setPositionOnDrag(mDesktopWindowDecoration,
@@ -138,24 +142,22 @@
                 mDesktopWindowDecoration.updateResizeVeil(mRepositionTaskBounds);
                 final WindowContainerTransaction wct = new WindowContainerTransaction();
                 wct.setBounds(mDesktopWindowDecoration.mTaskInfo.token, mRepositionTaskBounds);
-                if (Transitions.ENABLE_SHELL_TRANSITIONS) {
-                    mTransitions.startTransition(TRANSIT_CHANGE, wct, this);
-                } else {
-                    mTaskOrganizer.applyTransaction(wct);
-                }
+                mTransitions.startTransition(TRANSIT_CHANGE, wct, this);
             } else {
                 // If bounds haven't changed, perform necessary veil reset here as startAnimation
                 // won't be called.
                 mDesktopWindowDecoration.hideResizeVeil();
+                mIsResizingOrAnimatingResize = false;
             }
         } else if (DragPositioningCallbackUtility.isBelowDisallowedArea(
                 mDisallowedAreaForEndBoundsHeight, mTaskBoundsAtDragStart, mRepositionStartPoint,
                 y)) {
+            final WindowContainerTransaction wct = new WindowContainerTransaction();
             DragPositioningCallbackUtility.onDragEnd(mRepositionTaskBounds,
                     mTaskBoundsAtDragStart, mRepositionStartPoint, x, y,
                     mDesktopWindowDecoration.calculateValidDragArea());
-            DragPositioningCallbackUtility.applyTaskBoundsChange(new WindowContainerTransaction(),
-                    mDesktopWindowDecoration, mRepositionTaskBounds, mTaskOrganizer);
+            wct.setBounds(mDesktopWindowDecoration.mTaskInfo.token, mRepositionTaskBounds);
+            mTransitions.startTransition(TRANSIT_CHANGE, wct, this);
         }
 
         mCtrlType = CTRL_TYPE_UNDEFINED;
@@ -174,10 +176,20 @@
             @NonNull SurfaceControl.Transaction startTransaction,
             @NonNull SurfaceControl.Transaction finishTransaction,
             @NonNull Transitions.TransitionFinishCallback finishCallback) {
+        for (TransitionInfo.Change change: info.getChanges()) {
+            final SurfaceControl sc = change.getLeash();
+            final Rect endBounds = change.getEndAbsBounds();
+            startTransaction.setWindowCrop(sc, endBounds.width(), endBounds.height())
+                    .setPosition(sc, endBounds.left, endBounds.top);
+            finishTransaction.setWindowCrop(sc, endBounds.width(), endBounds.height())
+                    .setPosition(sc, endBounds.left, endBounds.top);
+        }
+
         startTransaction.apply();
         mDesktopWindowDecoration.hideResizeVeil();
         mCtrlType = CTRL_TYPE_UNDEFINED;
         finishCallback.onTransitionFinished(null);
+        mIsResizingOrAnimatingResize = false;
         return true;
     }
 
@@ -191,4 +203,9 @@
             @NonNull TransitionRequestInfo request) {
         return null;
     }
+
+    @Override
+    public boolean isResizingOrAnimating() {
+        return mIsResizingOrAnimatingResize;
+    }
 }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java
index ee0e31e..6a9258c 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java
@@ -124,6 +124,7 @@
     private WindowlessWindowManager mCaptionWindowManager;
     private SurfaceControlViewHost mViewHost;
     private Configuration mWindowDecorConfig;
+    TaskDragResizer mTaskDragResizer;
     private boolean mIsCaptionVisible;
 
     private final Binder mOwner = new Binder();
@@ -278,9 +279,12 @@
         }
 
         outResult.mCaptionHeight = loadDimensionPixelSize(resources, params.mCaptionHeightId);
-        final int captionWidth = taskBounds.width();
+        final int captionWidth = params.mCaptionWidthId != Resources.ID_NULL
+                ? loadDimensionPixelSize(resources, params.mCaptionWidthId) : taskBounds.width();
+        outResult.mCaptionX = (outResult.mWidth - captionWidth) / 2;
 
         startT.setWindowCrop(mCaptionContainerSurface, captionWidth, outResult.mCaptionHeight)
+                .setPosition(mCaptionContainerSurface, outResult.mCaptionX, 0 /* y */)
                 .setLayer(mCaptionContainerSurface, CAPTION_LAYER_Z_ORDER)
                 .show(mCaptionContainerSurface);
 
@@ -291,7 +295,7 @@
             mCaptionInsetsRect.set(taskBounds);
             if (mIsCaptionVisible) {
                 mCaptionInsetsRect.bottom =
-                        mCaptionInsetsRect.top + outResult.mCaptionHeight + params.mCaptionY;
+                        mCaptionInsetsRect.top + outResult.mCaptionHeight;
                 wct.addInsetsSource(mTaskInfo.token,
                         mOwner, 0 /* index */, WindowInsets.Type.captionBar(), mCaptionInsetsRect);
                 wct.addInsetsSource(mTaskInfo.token,
@@ -311,25 +315,21 @@
         float shadowRadius;
         final Point taskPosition = mTaskInfo.positionInParent;
         if (isFullscreen) {
-            // Setting the task crop to the width/height stops input events from being sent to
-            // some regions of the app window. See b/300324920
-            // TODO(b/296921174): investigate whether crop/position needs to be set by window
-            // decorations at all when transition handlers are already taking ownership of the task
-            // surface placement/crop, especially when in fullscreen where tasks cannot be
-            // drag-resized by the window decoration.
-            startT.setWindowCrop(mTaskSurface, null);
-            finishT.setWindowCrop(mTaskSurface, null);
             // Shadow is not needed for fullscreen tasks
             shadowRadius = 0;
         } else {
-            startT.setWindowCrop(mTaskSurface, outResult.mWidth, outResult.mHeight);
-            finishT.setWindowCrop(mTaskSurface, outResult.mWidth, outResult.mHeight);
             shadowRadius = loadDimension(resources, params.mShadowRadiusId);
         }
+
+        if (params.mSetTaskPositionAndCrop) {
+            startT.setWindowCrop(mTaskSurface, outResult.mWidth, outResult.mHeight);
+            finishT.setWindowCrop(mTaskSurface, outResult.mWidth, outResult.mHeight)
+                    .setPosition(mTaskSurface, taskPosition.x, taskPosition.y);
+        }
+
         startT.setShadowRadius(mTaskSurface, shadowRadius)
                 .show(mTaskSurface);
-        finishT.setPosition(mTaskSurface, taskPosition.x, taskPosition.y)
-                .setShadowRadius(mTaskSurface, shadowRadius);
+        finishT.setShadowRadius(mTaskSurface, shadowRadius);
         if (mTaskInfo.getWindowingMode() == WINDOWING_MODE_FREEFORM) {
             if (!DesktopModeStatus.isVeiledResizeEnabled()) {
                 // When fluid resize is enabled, add a background to freeform tasks
@@ -394,6 +394,10 @@
         }
     }
 
+    void setTaskDragResizer(TaskDragResizer taskDragResizer) {
+        mTaskDragResizer = taskDragResizer;
+    }
+
     private void setCaptionVisibility(View rootView, boolean visible) {
         if (rootView == null) {
             return;
@@ -553,12 +557,10 @@
 
         int mCornerRadius;
 
-        int mCaptionX;
-        int mCaptionY;
-
         Configuration mWindowDecorConfig;
 
         boolean mApplyStartTransactionOnDraw;
+        boolean mSetTaskPositionAndCrop;
 
         void reset() {
             mLayoutResId = Resources.ID_NULL;
@@ -568,16 +570,15 @@
 
             mCornerRadius = 0;
 
-            mCaptionX = 0;
-            mCaptionY = 0;
-
             mApplyStartTransactionOnDraw = false;
+            mSetTaskPositionAndCrop = false;
             mWindowDecorConfig = null;
         }
     }
 
     static class RelayoutResult<T extends View & TaskFocusStateConsumer> {
         int mCaptionHeight;
+        int mCaptionX;
         int mWidth;
         int mHeight;
         T mRootView;
@@ -586,6 +587,7 @@
             mWidth = 0;
             mHeight = 0;
             mCaptionHeight = 0;
+            mCaptionX = 0;
             mRootView = null;
         }
     }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/DesktopModeFocusedWindowDecorationViewHolder.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/DesktopModeFocusedWindowDecorationViewHolder.kt
index 4930cb7..5f77022 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/DesktopModeFocusedWindowDecorationViewHolder.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/DesktopModeFocusedWindowDecorationViewHolder.kt
@@ -5,6 +5,7 @@
 import android.app.WindowConfiguration.WINDOWING_MODE_FREEFORM
 import android.content.res.ColorStateList
 import android.graphics.Color
+import android.graphics.PointF
 import android.view.View
 import android.view.WindowInsetsController.APPEARANCE_LIGHT_STATUS_BARS
 import android.widget.ImageButton
@@ -35,9 +36,6 @@
     }
 
     override fun bindData(taskInfo: RunningTaskInfo) {
-        taskInfo.taskDescription?.statusBarColor?.let { captionColor ->
-            captionView.setBackgroundColor(captionColor)
-        }
         captionHandle.imageTintList = ColorStateList.valueOf(getCaptionHandleBarColor(taskInfo))
     }
 
@@ -49,6 +47,17 @@
         animateCaptionHandleAlpha(startValue = 0f, endValue = 1f)
     }
 
+    /**
+     * Returns true if input point is in the caption's view.
+     * @param inputPoint the input point relative to the task in full "focus" (i.e. fullscreen).
+     */
+    fun pointInCaption(inputPoint: PointF, captionX: Int): Boolean {
+        return inputPoint.x >= captionX &&
+                inputPoint.x <= captionX + captionView.width &&
+                inputPoint.y >= 0 &&
+                inputPoint.y <= captionView.height
+    }
+
     private fun getCaptionHandleBarColor(taskInfo: RunningTaskInfo): Int {
         return if (shouldUseLightCaptionColors(taskInfo)) {
             context.getColor(R.color.desktop_mode_caption_handle_bar_light)
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/DesktopModeWindowDecorationViewHolder.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/DesktopModeWindowDecorationViewHolder.kt
index 690b4e4..81bc34c 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/DesktopModeWindowDecorationViewHolder.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/viewholder/DesktopModeWindowDecorationViewHolder.kt
@@ -17,9 +17,9 @@
    */
   abstract fun bindData(taskInfo: RunningTaskInfo)
 
-    /** Callback when the handle menu is opened. */
-    abstract fun onHandleMenuOpened()
+  /** Callback when the handle menu is opened. */
+  abstract fun onHandleMenuOpened()
 
-    /** Callback when the handle menu is closed. */
-    abstract fun onHandleMenuClosed()
+  /** Callback when the handle menu is closed. */
+  abstract fun onHandleMenuClosed()
 }
diff --git a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/apps/NetflixEnterPipTest.kt b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/apps/NetflixEnterPipTest.kt
index 32f1259..143f7a7 100644
--- a/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/apps/NetflixEnterPipTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/pip/src/com/android/wm/shell/flicker/pip/apps/NetflixEnterPipTest.kt
@@ -69,7 +69,7 @@
         setup {
             standardAppHelper.launchViaIntent(
                 wmHelper,
-                NetflixAppHelper.getNetflixWatchVideoIntent("70184207"),
+                NetflixAppHelper.getNetflixWatchVideoIntent("81605060"),
                 ComponentNameMatcher(NetflixAppHelper.PACKAGE_NAME, NetflixAppHelper.WATCH_ACTIVITY)
             )
             standardAppHelper.waitForVideoPlaying()
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorationTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorationTests.java
index 18fcdd0..193f16d 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorationTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/DesktopModeWindowDecorationTests.java
@@ -16,16 +16,24 @@
 
 package com.android.wm.shell.windowdecor;
 
+import static com.google.common.truth.Truth.assertThat;
+
 import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.anyInt;
 import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.spy;
 import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
 
 import android.app.ActivityManager;
 import android.content.ComponentName;
 import android.content.res.Configuration;
+import android.content.res.Resources;
+import android.content.res.TypedArray;
 import android.os.Handler;
+import android.os.SystemProperties;
 import android.testing.AndroidTestingRunner;
+import android.testing.TestableContext;
 import android.view.Choreographer;
 import android.view.Display;
 import android.view.SurfaceControl;
@@ -34,14 +42,17 @@
 
 import androidx.test.filters.SmallTest;
 
+import com.android.internal.R;
 import com.android.wm.shell.RootTaskDisplayAreaOrganizer;
 import com.android.wm.shell.ShellTaskOrganizer;
 import com.android.wm.shell.ShellTestCase;
 import com.android.wm.shell.TestRunningTaskInfoBuilder;
 import com.android.wm.shell.common.DisplayController;
 import com.android.wm.shell.common.SyncTransactionQueue;
+import com.android.wm.shell.windowdecor.WindowDecoration.RelayoutParams;
 
 import org.junit.Before;
+import org.junit.BeforeClass;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.Mock;
@@ -57,6 +68,13 @@
 @SmallTest
 @RunWith(AndroidTestingRunner.class)
 public class DesktopModeWindowDecorationTests extends ShellTestCase {
+    private static final String USE_WINDOW_SHADOWS_SYSPROP_KEY =
+            "persist.wm.debug.desktop_use_window_shadows";
+    private static final String FOCUSED_USE_WINDOW_SHADOWS_SYSPROP_KEY =
+            "persist.wm.debug.desktop_use_window_shadows_focused_window";
+    private static final String USE_ROUNDED_CORNERS_SYSPROP_KEY =
+            "persist.wm.debug.desktop_use_rounded_corners";
+
     @Mock
     private DisplayController mMockDisplayController;
     @Mock
@@ -79,14 +97,29 @@
     private SurfaceControlViewHost mMockSurfaceControlViewHost;
     @Mock
     private WindowDecoration.SurfaceControlViewHostFactory mMockSurfaceControlViewHostFactory;
+    @Mock
+    private TypedArray mMockRoundedCornersRadiusArray;
 
     private final Configuration mConfiguration = new Configuration();
 
+    private TestableContext mTestableContext;
+
+    /** Set up run before test class. */
+    @BeforeClass
+    public static void setUpClass() {
+        // Reset the sysprop settings before running the test.
+        SystemProperties.set(USE_WINDOW_SHADOWS_SYSPROP_KEY, "");
+        SystemProperties.set(FOCUSED_USE_WINDOW_SHADOWS_SYSPROP_KEY, "");
+        SystemProperties.set(USE_ROUNDED_CORNERS_SYSPROP_KEY, "");
+    }
+
     @Before
     public void setUp() {
         doReturn(mMockSurfaceControlViewHost).when(mMockSurfaceControlViewHostFactory).create(
                 any(), any(), any());
         doReturn(mMockTransaction).when(mMockTransactionSupplier).get();
+        mTestableContext = new TestableContext(mContext);
+        mTestableContext.ensureTestableResources();
     }
 
     @Test
@@ -105,6 +138,52 @@
 
     }
 
+    @Test
+    public void updateRelayoutParams_noSysPropFlagsSet_windowShadowsAreEnabled() {
+        final ActivityManager.RunningTaskInfo taskInfo = createTaskInfo(/* visible= */ true);
+        RelayoutParams relayoutParams = new RelayoutParams();
+
+        DesktopModeWindowDecoration.updateRelayoutParams(
+                relayoutParams, mContext, taskInfo, /* applyStartTransactionOnDraw= */ true,
+                /* shouldSetTaskPositionAndCrop */ false);
+
+        assertThat(relayoutParams.mShadowRadiusId).isNotEqualTo(Resources.ID_NULL);
+    }
+
+    @Test
+    public void updateRelayoutParams_noSysPropFlagsSet_roundedCornersAreEnabled() {
+        final ActivityManager.RunningTaskInfo taskInfo = createTaskInfo(/* visible= */ true);
+        fillRoundedCornersResources(/* fillValue= */ 30);
+        RelayoutParams relayoutParams = new RelayoutParams();
+
+        DesktopModeWindowDecoration.updateRelayoutParams(
+                relayoutParams,
+                mTestableContext,
+                taskInfo,
+                /* applyStartTransactionOnDraw= */ true,
+                /* shouldSetTaskPositionAndCrop */ false);
+
+        assertThat(relayoutParams.mCornerRadius).isGreaterThan(0);
+    }
+
+    private void fillRoundedCornersResources(int fillValue) {
+        when(mMockRoundedCornersRadiusArray.getDimensionPixelSize(anyInt(), anyInt()))
+                .thenReturn(fillValue);
+        mTestableContext.getOrCreateTestableResources().addOverride(
+                R.array.config_roundedCornerRadiusArray, mMockRoundedCornersRadiusArray);
+        mTestableContext.getOrCreateTestableResources().addOverride(
+                R.dimen.rounded_corner_radius, fillValue);
+        mTestableContext.getOrCreateTestableResources().addOverride(
+                R.array.config_roundedCornerTopRadiusArray, mMockRoundedCornersRadiusArray);
+        mTestableContext.getOrCreateTestableResources().addOverride(
+                R.dimen.rounded_corner_radius_top, fillValue);
+        mTestableContext.getOrCreateTestableResources().addOverride(
+                R.array.config_roundedCornerBottomRadiusArray, mMockRoundedCornersRadiusArray);
+        mTestableContext.getOrCreateTestableResources().addOverride(
+                R.dimen.rounded_corner_radius_bottom, fillValue);
+    }
+
+
     private DesktopModeWindowDecoration createWindowDecoration(
             ActivityManager.RunningTaskInfo taskInfo) {
         return new DesktopModeWindowDecoration(mContext, mMockDisplayController,
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 2ce49cf..de6903d 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
@@ -10,6 +10,7 @@
 import android.view.Surface.ROTATION_270
 import android.view.Surface.ROTATION_90
 import android.view.SurfaceControl
+import android.view.WindowManager
 import android.window.WindowContainerToken
 import android.window.WindowContainerTransaction
 import android.window.WindowContainerTransaction.Change.CHANGE_DRAG_RESIZING
@@ -18,13 +19,17 @@
 import com.android.wm.shell.ShellTestCase
 import com.android.wm.shell.common.DisplayController
 import com.android.wm.shell.common.DisplayLayout
+import com.android.wm.shell.transition.Transitions
 import com.android.wm.shell.windowdecor.DragPositioningCallback.CTRL_TYPE_BOTTOM
 import com.android.wm.shell.windowdecor.DragPositioningCallback.CTRL_TYPE_RIGHT
 import com.android.wm.shell.windowdecor.DragPositioningCallback.CTRL_TYPE_TOP
 import com.android.wm.shell.windowdecor.DragPositioningCallback.CTRL_TYPE_UNDEFINED
+import junit.framework.Assert.assertFalse
+import junit.framework.Assert.assertTrue
 import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
+import org.mockito.ArgumentMatchers.anyInt
 import org.mockito.ArgumentMatchers.eq
 import org.mockito.Mock
 import org.mockito.Mockito
@@ -34,6 +39,7 @@
 import org.mockito.Mockito.verify
 import org.mockito.Mockito.`when`
 import org.mockito.MockitoAnnotations
+import org.mockito.kotlin.doReturn
 import java.util.function.Supplier
 import org.mockito.Mockito.`when` as whenever
 
@@ -50,6 +56,8 @@
     @Mock
     private lateinit var mockShellTaskOrganizer: ShellTaskOrganizer
     @Mock
+    private lateinit var mockTransitions: Transitions
+    @Mock
     private lateinit var mockWindowDecoration: WindowDecoration<*>
     @Mock
     private lateinit var mockDragStartListener: DragPositioningCallbackUtility.DragStartListener
@@ -69,6 +77,8 @@
     private lateinit var mockTransactionFactory: Supplier<SurfaceControl.Transaction>
     @Mock
     private lateinit var mockTransaction: SurfaceControl.Transaction
+    @Mock
+    private lateinit var mockTransitionBinder: IBinder
 
     private lateinit var taskPositioner: FluidResizeTaskPositioner
 
@@ -106,9 +116,12 @@
         `when`(mockWindowDecoration.calculateValidDragArea()).thenReturn(VALID_DRAG_AREA)
         mockWindowDecoration.mDisplay = mockDisplay
         whenever(mockDisplay.displayId).thenAnswer { DISPLAY_ID }
+        whenever(mockTransitions.startTransition(anyInt(), any(), any()))
+                .doReturn(mockTransitionBinder)
 
         taskPositioner = FluidResizeTaskPositioner(
                 mockShellTaskOrganizer,
+                mockTransitions,
                 mockWindowDecoration,
                 mockDisplayController,
                 mockDragStartListener,
@@ -118,7 +131,7 @@
     }
 
     @Test
-    fun testDragResize_notMove_skipsTransactionOnEnd() {
+    fun testDragResize_notMove_skipsTransitionOnEnd() {
         taskPositioner.onDragPositioningStart(
                 CTRL_TYPE_TOP or CTRL_TYPE_RIGHT,
                 STARTING_BOUNDS.left.toFloat(),
@@ -130,16 +143,16 @@
                 STARTING_BOUNDS.top.toFloat() + 10
         )
 
-        verify(mockShellTaskOrganizer, never()).applyTransaction(argThat { wct ->
+        verify(mockTransitions, never()).startTransition(
+                eq(WindowManager.TRANSIT_CHANGE), argThat { wct ->
             return@argThat wct.changes.any { (token, change) ->
                 token == taskBinder &&
                         ((change.windowSetMask and WindowConfiguration.WINDOW_CONFIG_BOUNDS) != 0)
-            }
-        })
+            }}, eq(taskPositioner))
     }
 
     @Test
-    fun testDragResize_noEffectiveMove_skipsTransactionOnMoveAndEnd() {
+    fun testDragResize_noEffectiveMove_skipsTransitionOnMoveAndEnd() {
         taskPositioner.onDragPositioningStart(
                 CTRL_TYPE_TOP or CTRL_TYPE_RIGHT,
                 STARTING_BOUNDS.left.toFloat(),
@@ -151,21 +164,28 @@
                 STARTING_BOUNDS.top.toFloat()
         )
 
-        taskPositioner.onDragPositioningEnd(
-                STARTING_BOUNDS.left.toFloat() + 10,
-                STARTING_BOUNDS.top.toFloat() + 10
-        )
-
         verify(mockShellTaskOrganizer, never()).applyTransaction(argThat { wct ->
             return@argThat wct.changes.any { (token, change) ->
                 token == taskBinder &&
                         ((change.windowSetMask and WindowConfiguration.WINDOW_CONFIG_BOUNDS) != 0)
             }
         })
+
+        taskPositioner.onDragPositioningEnd(
+                STARTING_BOUNDS.left.toFloat() + 10,
+                STARTING_BOUNDS.top.toFloat() + 10
+        )
+
+        verify(mockTransitions, never()).startTransition(
+                eq(WindowManager.TRANSIT_CHANGE), argThat { wct ->
+            return@argThat wct.changes.any { (token, change) ->
+                token == taskBinder &&
+                        ((change.windowSetMask and WindowConfiguration.WINDOW_CONFIG_BOUNDS) != 0)
+            }}, eq(taskPositioner))
     }
 
     @Test
-    fun testDragResize_hasEffectiveMove_issuesTransactionOnMoveAndEnd() {
+    fun testDragResize_hasEffectiveMove_issuesTransitionOnMoveAndEnd() {
         taskPositioner.onDragPositioningStart(
                 CTRL_TYPE_TOP or CTRL_TYPE_RIGHT,
                 STARTING_BOUNDS.left.toFloat(),
@@ -192,13 +212,13 @@
         )
         val rectAfterEnd = Rect(rectAfterMove)
         rectAfterEnd.top += 10
-        verify(mockShellTaskOrganizer).applyTransaction(argThat { wct ->
-            return@argThat wct.changes.any { (token, change) ->
-                token == taskBinder &&
-                        (change.windowSetMask and WindowConfiguration.WINDOW_CONFIG_BOUNDS) != 0 &&
-                        change.configuration.windowConfiguration.bounds == rectAfterEnd
-            }
-        })
+        verify(mockTransitions).startTransition(
+                eq(WindowManager.TRANSIT_CHANGE), argThat { wct ->
+        return@argThat wct.changes.any { (token, change) ->
+            token == taskBinder &&
+                    (change.windowSetMask and WindowConfiguration.WINDOW_CONFIG_BOUNDS) != 0 &&
+                    change.configuration.windowConfiguration.bounds == rectAfterEnd
+            }}, eq(taskPositioner))
     }
 
     @Test
@@ -226,6 +246,13 @@
                         change.dragResizing
             }
         })
+        verify(mockTransitions, never()).startTransition(
+                eq(WindowManager.TRANSIT_CHANGE), argThat { wct ->
+            return@argThat wct.changes.any { (token, change) ->
+                token == taskBinder &&
+                        ((change.changeMask and CHANGE_DRAG_RESIZING) != 0) &&
+                        change.dragResizing
+            }}, eq(taskPositioner))
     }
 
     @Test
@@ -253,13 +280,13 @@
                         change.dragResizing
             }
         })
-        verify(mockShellTaskOrganizer).applyTransaction(argThat { wct ->
+        verify(mockTransitions).startTransition(
+                eq(WindowManager.TRANSIT_CHANGE), argThat { wct ->
             return@argThat wct.changes.any { (token, change) ->
                 token == taskBinder &&
                         ((change.changeMask and CHANGE_DRAG_RESIZING) != 0) &&
                         !change.dragResizing
-            }
-        })
+            }}, eq(taskPositioner))
     }
 
     @Test
@@ -270,7 +297,7 @@
                 STARTING_BOUNDS.top.toFloat()
         )
 
-        // Resize to width of 95px and height of 5px with min width of 10px
+        // Resize to width of 95px and height of 5px with min height of 10px
         val newX = STARTING_BOUNDS.right.toFloat() - 5
         val newY = STARTING_BOUNDS.top.toFloat() + 95
         taskPositioner.onDragPositioningMove(
@@ -566,12 +593,12 @@
 
         taskPositioner.onDragPositioningEnd(newX, newY)
 
-        verify(mockShellTaskOrganizer, never()).applyTransaction(argThat { wct ->
+        verify(mockTransitions, never()).startTransition(
+                eq(WindowManager.TRANSIT_CHANGE), argThat { wct ->
             return@argThat wct.changes.any { (token, change) ->
                 token == taskBinder &&
                         ((change.windowSetMask and WindowConfiguration.WINDOW_CONFIG_BOUNDS) != 0)
-            }
-        })
+            }}, eq(taskPositioner))
     }
 
     private fun WindowContainerTransaction.Change.ofBounds(bounds: Rect): Boolean {
@@ -650,14 +677,14 @@
         )
         // Verify task's top bound is set to stable bounds top since dragged outside stable bounds
         // but not in disallowed end bounds area.
-        verify(mockShellTaskOrganizer).applyTransaction(argThat { wct ->
+        verify(mockTransitions).startTransition(
+                eq(WindowManager.TRANSIT_CHANGE), argThat { wct ->
             return@argThat wct.changes.any { (token, change) ->
                 token == taskBinder &&
                         (change.windowSetMask and WindowConfiguration.WINDOW_CONFIG_BOUNDS) != 0 &&
                         change.configuration.windowConfiguration.bounds.top ==
                         STABLE_BOUNDS_LANDSCAPE.top
-            }
-        })
+            }}, eq(taskPositioner))
     }
 
     @Test
@@ -680,7 +707,8 @@
             newX,
             newY
         )
-        verify(mockShellTaskOrganizer).applyTransaction(argThat { wct ->
+        verify(mockTransitions).startTransition(
+                eq(WindowManager.TRANSIT_CHANGE), argThat { wct ->
             return@argThat wct.changes.any { (token, change) ->
                 token == taskBinder &&
                         (change.windowSetMask and WindowConfiguration.WINDOW_CONFIG_BOUNDS) != 0 &&
@@ -688,8 +716,7 @@
                         VALID_DRAG_AREA.bottom &&
                         change.configuration.windowConfiguration.bounds.left ==
                         VALID_DRAG_AREA.left
-            }
-        })
+            }}, eq(taskPositioner))
     }
 
     @Test
@@ -741,6 +768,59 @@
         verify(mockDisplayLayout, Mockito.times(2)).getStableBounds(any())
     }
 
+    @Test
+    fun testIsResizingOrAnimatingResizeSet() {
+        assertFalse(taskPositioner.isResizingOrAnimating)
+
+        taskPositioner.onDragPositioningStart(
+                CTRL_TYPE_TOP or CTRL_TYPE_RIGHT,
+                STARTING_BOUNDS.left.toFloat(),
+                STARTING_BOUNDS.top.toFloat()
+        )
+
+        taskPositioner.onDragPositioningMove(
+                STARTING_BOUNDS.left.toFloat() - 20,
+                STARTING_BOUNDS.top.toFloat() - 20
+        )
+
+        // isResizingOrAnimating should be set to true after move during a resize
+        assertTrue(taskPositioner.isResizingOrAnimating)
+
+        taskPositioner.onDragPositioningEnd(
+                STARTING_BOUNDS.left.toFloat(),
+                STARTING_BOUNDS.top.toFloat()
+        )
+
+        // isResizingOrAnimating should be not be set till false until after transition animation
+        assertTrue(taskPositioner.isResizingOrAnimating)
+    }
+
+    @Test
+    fun testIsResizingOrAnimatingResizeResetAfterAbortedTransition() {
+        performDrag(STARTING_BOUNDS.left.toFloat(),
+                STARTING_BOUNDS.top.toFloat(), STARTING_BOUNDS.left.toFloat() - 20,
+                STARTING_BOUNDS.top.toFloat() - 20, CTRL_TYPE_TOP or CTRL_TYPE_RIGHT)
+
+        taskPositioner.onTransitionConsumed(mockTransitionBinder, true /* aborted */,
+                mockTransaction)
+
+        // isResizingOrAnimating should be set to false until after transition successfully consumed
+        assertFalse(taskPositioner.isResizingOrAnimating)
+    }
+
+    @Test
+    fun testIsResizingOrAnimatingResizeResetAfterNonAbortedTransition() {
+        performDrag(STARTING_BOUNDS.left.toFloat(),
+                STARTING_BOUNDS.top.toFloat(), STARTING_BOUNDS.left.toFloat() - 20,
+                STARTING_BOUNDS.top.toFloat() - 20, CTRL_TYPE_TOP or CTRL_TYPE_RIGHT)
+
+        taskPositioner.onTransitionConsumed(mockTransitionBinder, false /* aborted */,
+                mockTransaction)
+
+        // isResizingOrAnimating should be set to false until after transition successfully consumed
+        assertFalse(taskPositioner.isResizingOrAnimating)
+    }
+
     private fun performDrag(
         startX: Float,
         startY: Float,
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 a759b53..0841210 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
@@ -26,6 +26,7 @@
 import android.view.Surface.ROTATION_90
 import android.view.SurfaceControl
 import android.view.WindowManager.TRANSIT_CHANGE
+import android.window.TransitionInfo
 import android.window.WindowContainerToken
 import androidx.test.filters.SmallTest
 import com.android.wm.shell.ShellTaskOrganizer
@@ -33,10 +34,12 @@
 import com.android.wm.shell.common.DisplayController
 import com.android.wm.shell.common.DisplayLayout
 import com.android.wm.shell.transition.Transitions
+import com.android.wm.shell.transition.Transitions.TransitionFinishCallback
 import com.android.wm.shell.windowdecor.DragPositioningCallback.CTRL_TYPE_BOTTOM
 import com.android.wm.shell.windowdecor.DragPositioningCallback.CTRL_TYPE_RIGHT
 import com.android.wm.shell.windowdecor.DragPositioningCallback.CTRL_TYPE_TOP
 import com.android.wm.shell.windowdecor.DragPositioningCallback.CTRL_TYPE_UNDEFINED
+import junit.framework.Assert
 import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -85,6 +88,12 @@
     @Mock
     private lateinit var mockTransaction: SurfaceControl.Transaction
     @Mock
+    private lateinit var mockTransitionBinder: IBinder
+    @Mock
+    private lateinit var mockTransitionInfo: TransitionInfo
+    @Mock
+    private lateinit var mockFinishCallback: TransitionFinishCallback
+    @Mock
     private lateinit var mockTransitions: Transitions
 
     private lateinit var taskPositioner: VeiledResizeTaskPositioner
@@ -188,13 +197,12 @@
 
         verify(mockDesktopWindowDecoration, never()).createResizeVeil()
         verify(mockDesktopWindowDecoration, never()).hideResizeVeil()
-        verify(mockShellTaskOrganizer).applyTransaction(argThat { wct ->
+        verify(mockTransitions).startTransition(eq(TRANSIT_CHANGE), argThat { wct ->
             return@argThat wct.changes.any { (token, change) ->
                 token == taskBinder &&
                         (change.windowSetMask and WindowConfiguration.WINDOW_CONFIG_BOUNDS) != 0 &&
-                        change.configuration.windowConfiguration.bounds == rectAfterEnd
-            }
-        })
+                        change.configuration.windowConfiguration.bounds == rectAfterEnd }},
+                eq(taskPositioner))
     }
 
     @Test
@@ -369,14 +377,13 @@
         )
         // Verify task's top bound is set to stable bounds top since dragged outside stable bounds
         // but not in disallowed end bounds area.
-        verify(mockShellTaskOrganizer).applyTransaction(argThat { wct ->
+        verify(mockTransitions).startTransition(eq(TRANSIT_CHANGE), argThat { wct ->
             return@argThat wct.changes.any { (token, change) ->
                 token == taskBinder &&
                         (change.windowSetMask and WindowConfiguration.WINDOW_CONFIG_BOUNDS) != 0 &&
                         change.configuration.windowConfiguration.bounds.top ==
-                        STABLE_BOUNDS_LANDSCAPE.top
-            }
-        })
+                        STABLE_BOUNDS_LANDSCAPE.top }},
+                eq(taskPositioner))
     }
 
     @Test
@@ -399,16 +406,15 @@
             newX,
             newY
         )
-        verify(mockShellTaskOrganizer).applyTransaction(argThat { wct ->
+        verify(mockTransitions).startTransition(eq(TRANSIT_CHANGE), argThat { wct ->
             return@argThat wct.changes.any { (token, change) ->
                 token == taskBinder &&
                         (change.windowSetMask and WindowConfiguration.WINDOW_CONFIG_BOUNDS) != 0 &&
                         change.configuration.windowConfiguration.bounds.top ==
                         VALID_DRAG_AREA.bottom &&
                         change.configuration.windowConfiguration.bounds.left ==
-                        VALID_DRAG_AREA.left
-            }
-        })
+                        VALID_DRAG_AREA.left }},
+                eq(taskPositioner))
     }
 
     @Test
@@ -456,6 +462,47 @@
         verify(mockDisplayLayout, times(2)).getStableBounds(any())
     }
 
+    @Test
+    fun testIsResizingOrAnimatingResizeSet() {
+        Assert.assertFalse(taskPositioner.isResizingOrAnimating)
+
+        taskPositioner.onDragPositioningStart(
+                CTRL_TYPE_TOP or CTRL_TYPE_RIGHT,
+                STARTING_BOUNDS.left.toFloat(),
+                STARTING_BOUNDS.top.toFloat()
+        )
+
+        taskPositioner.onDragPositioningMove(
+                STARTING_BOUNDS.left.toFloat() - 20,
+                STARTING_BOUNDS.top.toFloat() - 20
+        )
+
+        // isResizingOrAnimating should be set to true after move during a resize
+        Assert.assertTrue(taskPositioner.isResizingOrAnimating)
+
+        taskPositioner.onDragPositioningEnd(
+                STARTING_BOUNDS.left.toFloat(),
+                STARTING_BOUNDS.top.toFloat()
+        )
+
+        // isResizingOrAnimating should be not be set till false until after transition animation
+        Assert.assertTrue(taskPositioner.isResizingOrAnimating)
+    }
+
+    @Test
+    fun testIsResizingOrAnimatingResizeResetAfterStartAnimation() {
+        performDrag(
+                STARTING_BOUNDS.left.toFloat(), STARTING_BOUNDS.top.toFloat(),
+                STARTING_BOUNDS.left.toFloat() - 20, STARTING_BOUNDS.top.toFloat() - 20,
+                CTRL_TYPE_TOP or CTRL_TYPE_RIGHT)
+
+        taskPositioner.startAnimation(mockTransitionBinder, mockTransitionInfo, mockTransaction,
+                mockTransaction, mockFinishCallback)
+
+        // isResizingOrAnimating should be set to false until after transition successfully consumed
+        Assert.assertFalse(taskPositioner.isResizingOrAnimating)
+    }
+
     private fun performDrag(
         startX: Float,
         startY: Float,
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/WindowDecorationTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/WindowDecorationTests.java
index fe508e2..7b53f70 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/WindowDecorationTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/windowdecor/WindowDecorationTests.java
@@ -32,6 +32,7 @@
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNull;
+import static org.mockito.ArgumentMatchers.anyFloat;
 import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.Mockito.any;
 import static org.mockito.Mockito.argThat;
@@ -261,11 +262,6 @@
                     eq(new Rect(100, 300, 400, 364)));
         }
 
-        verify(mMockSurfaceControlFinishT)
-                .setPosition(mMockTaskSurface, TASK_POSITION_IN_PARENT.x,
-                        TASK_POSITION_IN_PARENT.y);
-        verify(mMockSurfaceControlFinishT)
-                .setWindowCrop(mMockTaskSurface, 300, 100);
         verify(mMockSurfaceControlStartT).setCornerRadius(mMockTaskSurface, CORNER_RADIUS);
         verify(mMockSurfaceControlFinishT).setCornerRadius(mMockTaskSurface, CORNER_RADIUS);
         verify(mMockSurfaceControlStartT)
@@ -642,6 +638,66 @@
                 eq(0) /* index */, eq(mandatorySystemGestures()));
     }
 
+    @Test
+    public void testTaskPositionAndCropNotSetWhenFalse() {
+        final Display defaultDisplay = mock(Display.class);
+        doReturn(defaultDisplay).when(mMockDisplayController)
+                .getDisplay(Display.DEFAULT_DISPLAY);
+
+        final ActivityManager.RunningTaskInfo taskInfo = new TestRunningTaskInfoBuilder()
+                .setDisplayId(Display.DEFAULT_DISPLAY)
+                .setBounds(TASK_BOUNDS)
+                .setPositionInParent(TASK_POSITION_IN_PARENT.x, TASK_POSITION_IN_PARENT.y)
+                .setVisible(true)
+                .setWindowingMode(WINDOWING_MODE_FREEFORM)
+                .build();
+        taskInfo.isFocused = true;
+        // Density is 2. Shadow radius is 10px. Caption height is 64px.
+        taskInfo.configuration.densityDpi = DisplayMetrics.DENSITY_DEFAULT * 2;
+        final TestWindowDecoration windowDecor = createWindowDecoration(taskInfo);
+
+
+        mRelayoutParams.mSetTaskPositionAndCrop = false;
+        windowDecor.relayout(taskInfo);
+
+        verify(mMockSurfaceControlStartT, never()).setWindowCrop(
+                eq(mMockTaskSurface), anyInt(), anyInt());
+        verify(mMockSurfaceControlFinishT, never()).setPosition(
+                eq(mMockTaskSurface), anyFloat(), anyFloat());
+        verify(mMockSurfaceControlFinishT, never()).setWindowCrop(
+                eq(mMockTaskSurface), anyInt(), anyInt());
+    }
+
+    @Test
+    public void testTaskPositionAndCropSetWhenSetTrue() {
+        final Display defaultDisplay = mock(Display.class);
+        doReturn(defaultDisplay).when(mMockDisplayController)
+                .getDisplay(Display.DEFAULT_DISPLAY);
+
+        final ActivityManager.RunningTaskInfo taskInfo = new TestRunningTaskInfoBuilder()
+                .setDisplayId(Display.DEFAULT_DISPLAY)
+                .setBounds(TASK_BOUNDS)
+                .setPositionInParent(TASK_POSITION_IN_PARENT.x, TASK_POSITION_IN_PARENT.y)
+                .setVisible(true)
+                .setWindowingMode(WINDOWING_MODE_FREEFORM)
+                .build();
+        taskInfo.isFocused = true;
+        // Density is 2. Shadow radius is 10px. Caption height is 64px.
+        taskInfo.configuration.densityDpi = DisplayMetrics.DENSITY_DEFAULT * 2;
+        final TestWindowDecoration windowDecor = createWindowDecoration(taskInfo);
+
+        mRelayoutParams.mSetTaskPositionAndCrop = true;
+        windowDecor.relayout(taskInfo);
+
+        verify(mMockSurfaceControlStartT).setWindowCrop(
+                eq(mMockTaskSurface), anyInt(), anyInt());
+        verify(mMockSurfaceControlFinishT).setPosition(
+                eq(mMockTaskSurface), anyFloat(), anyFloat());
+        verify(mMockSurfaceControlFinishT).setWindowCrop(
+                eq(mMockTaskSurface), anyInt(), anyInt());
+    }
+
+
     private TestWindowDecoration createWindowDecoration(ActivityManager.RunningTaskInfo taskInfo) {
         return new TestWindowDecoration(mContext, mMockDisplayController, mMockShellTaskOrganizer,
                 taskInfo, mMockTaskSurface, mWindowConfiguration,
@@ -716,15 +772,13 @@
 
         private WindowDecoration.AdditionalWindow addTestWindow() {
             final Resources resources = mDecorWindowContext.getResources();
-            int x = mRelayoutParams.mCaptionX;
-            int y = mRelayoutParams.mCaptionY;
             int width = loadDimensionPixelSize(resources, mCaptionMenuWidthId);
             int height = loadDimensionPixelSize(resources, mRelayoutParams.mCaptionHeightId);
             String name = "Test Window";
             WindowDecoration.AdditionalWindow additionalWindow =
                     addWindow(R.layout.desktop_mode_window_decor_handle_menu, name,
-                            mMockSurfaceControlAddWindowT, mMockSurfaceSyncGroup, x, y,
-                            width, height);
+                            mMockSurfaceControlAddWindowT, mMockSurfaceSyncGroup, 0 /* x */,
+                            0 /* y */, width, height);
             return additionalWindow;
         }
     }
diff --git a/libs/hwui/Android.bp b/libs/hwui/Android.bp
index 4741170..eebf8aa 100644
--- a/libs/hwui/Android.bp
+++ b/libs/hwui/Android.bp
@@ -38,6 +38,7 @@
 
 cc_aconfig_library {
     name: "hwui_flags_cc_lib",
+    host_supported: true,
     aconfig_declarations: "hwui_flags",
 }
 
@@ -109,12 +110,15 @@
         "libbase",
         "libharfbuzz_ng",
         "libminikin",
+        "server_configurable_flags",
     ],
 
     static_libs: [
         "libui-types",
     ],
 
+    whole_static_libs: ["hwui_flags_cc_lib"],
+
     target: {
         android: {
             shared_libs: [
@@ -146,7 +150,6 @@
                 "libstatspull_lazy",
                 "libstatssocket_lazy",
                 "libtonemap",
-                "hwui_flags_cc_lib",
             ],
         },
         host: {
diff --git a/libs/hwui/aconfig/hwui_flags.aconfig b/libs/hwui/aconfig/hwui_flags.aconfig
index ca11975..c156c46 100644
--- a/libs/hwui/aconfig/hwui_flags.aconfig
+++ b/libs/hwui/aconfig/hwui_flags.aconfig
@@ -15,6 +15,13 @@
 }
 
 flag {
+  name: "high_contrast_text_luminance"
+  namespace: "accessibility"
+  description: "Use luminance to determine how to make text more high contrast, instead of RGB heuristic"
+  bug: "186567103"
+}
+
+flag {
   name: "hdr_10bit_plus"
   namespace: "core_graphics"
   description: "Use 10101010 and FP16 formats for HDR-UI when available"
diff --git a/libs/hwui/hwui/DrawTextFunctor.h b/libs/hwui/hwui/DrawTextFunctor.h
index 2e6e976..8f99990 100644
--- a/libs/hwui/hwui/DrawTextFunctor.h
+++ b/libs/hwui/hwui/DrawTextFunctor.h
@@ -16,7 +16,9 @@
 
 #include <SkFontMetrics.h>
 #include <SkRRect.h>
+#include <com_android_graphics_hwui_flags.h>
 
+#include "../utils/Color.h"
 #include "Canvas.h"
 #include "FeatureFlags.h"
 #include "MinikinUtils.h"
@@ -27,6 +29,8 @@
 #include "hwui/PaintFilter.h"
 #include "pipeline/skia/SkiaRecordingCanvas.h"
 
+namespace flags = com::android::graphics::hwui::flags;
+
 namespace android {
 
 static inline void drawStroke(SkScalar left, SkScalar right, SkScalar top, SkScalar thickness,
@@ -73,8 +77,14 @@
         if (CC_UNLIKELY(canvas->isHighContrastText() && paint.getAlpha() != 0)) {
             // high contrast draw path
             int color = paint.getColor();
-            int channelSum = SkColorGetR(color) + SkColorGetG(color) + SkColorGetB(color);
-            bool darken = channelSum < (128 * 3);
+            bool darken;
+            if (flags::high_contrast_text_luminance()) {
+                uirenderer::Lab lab = uirenderer::sRGBToLab(color);
+                darken = lab.L <= 50;
+            } else {
+                int channelSum = SkColorGetR(color) + SkColorGetG(color) + SkColorGetB(color);
+                darken = channelSum < (128 * 3);
+            }
 
             // outline
             gDrawTextBlobMode = DrawTextBlobMode::HctOutline;
diff --git a/libs/hwui/pipeline/skia/DumpOpsCanvas.h b/libs/hwui/pipeline/skia/DumpOpsCanvas.h
index 6a052db..260547c 100644
--- a/libs/hwui/pipeline/skia/DumpOpsCanvas.h
+++ b/libs/hwui/pipeline/skia/DumpOpsCanvas.h
@@ -90,11 +90,6 @@
         mOutput << mIdent << "drawTextBlob" << std::endl;
     }
 
-    void onDrawImage2(const SkImage*, SkScalar dx, SkScalar dy, const SkSamplingOptions&,
-                      const SkPaint*) override {
-        mOutput << mIdent << "drawImage" << std::endl;
-    }
-
     void onDrawImageRect2(const SkImage*, const SkRect&, const SkRect&, const SkSamplingOptions&,
                           const SkPaint*, SrcRectConstraint) override {
         mOutput << mIdent << "drawImageRect" << std::endl;
diff --git a/libs/hwui/tests/common/CallCountingCanvas.h b/libs/hwui/tests/common/CallCountingCanvas.h
index dc36a2e..df5f04f99 100644
--- a/libs/hwui/tests/common/CallCountingCanvas.h
+++ b/libs/hwui/tests/common/CallCountingCanvas.h
@@ -109,12 +109,6 @@
         drawPoints++;
     }
 
-    int drawImageCount = 0;
-    void onDrawImage2(const SkImage* image, SkScalar dx, SkScalar dy, const SkSamplingOptions&,
-                     const SkPaint* paint) override {
-        drawImageCount++;
-    }
-
     int drawImageRectCount = 0;
     void onDrawImageRect2(const SkImage*, const SkRect&, const SkRect&, const SkSamplingOptions&,
                           const SkPaint*, SkCanvas::SrcRectConstraint) override {
diff --git a/libs/hwui/tests/unit/CanvasOpTests.cpp b/libs/hwui/tests/unit/CanvasOpTests.cpp
index 18c5047..4ae76e2 100644
--- a/libs/hwui/tests/unit/CanvasOpTests.cpp
+++ b/libs/hwui/tests/unit/CanvasOpTests.cpp
@@ -492,7 +492,7 @@
     CallCountingCanvas canvas;
     EXPECT_EQ(0, canvas.sumTotalDrawCalls());
     rasterizeCanvasBuffer(buffer, &canvas);
-    EXPECT_EQ(1, canvas.drawImageCount);
+    EXPECT_EQ(1, canvas.drawImageRectCount);
     EXPECT_EQ(1, canvas.sumTotalDrawCalls());
 }
 
diff --git a/libs/hwui/tests/unit/FatalTestCanvas.h b/libs/hwui/tests/unit/FatalTestCanvas.h
index 96a0c61..8b95e0c 100644
--- a/libs/hwui/tests/unit/FatalTestCanvas.h
+++ b/libs/hwui/tests/unit/FatalTestCanvas.h
@@ -69,10 +69,6 @@
     void onDrawPath(const SkPath&, const SkPaint&) {
         ADD_FAILURE() << "onDrawPath not expected in this test";
     }
-    void onDrawImage2(const SkImage*, SkScalar dx, SkScalar dy, const SkSamplingOptions&,
-                      const SkPaint*) {
-        ADD_FAILURE() << "onDrawImage not expected in this test";
-    }
     void onDrawImageRect2(const SkImage*, const SkRect&, const SkRect&, const SkSamplingOptions&,
                           const SkPaint*, SrcRectConstraint) {
         ADD_FAILURE() << "onDrawImageRect not expected in this test";
diff --git a/libs/hwui/tests/unit/RenderNodeDrawableTests.cpp b/libs/hwui/tests/unit/RenderNodeDrawableTests.cpp
index 073a835..ca54087 100644
--- a/libs/hwui/tests/unit/RenderNodeDrawableTests.cpp
+++ b/libs/hwui/tests/unit/RenderNodeDrawableTests.cpp
@@ -941,8 +941,9 @@
         void onDrawRect(const SkRect& rect, const SkPaint& paint) override {
             EXPECT_EQ(0, mDrawCounter++);
         }
-        void onDrawImage2(const SkImage*, SkScalar dx, SkScalar dy, const SkSamplingOptions&,
-                          const SkPaint*) override {
+        void onDrawImageRect2(const SkImage*, const SkRect&, const SkRect&,
+                              const SkSamplingOptions&, const SkPaint*,
+                              SrcRectConstraint) override {
             EXPECT_EQ(1, mDrawCounter++);
         }
     };
diff --git a/libs/hwui/tests/unit/SkiaPipelineTests.cpp b/libs/hwui/tests/unit/SkiaPipelineTests.cpp
index 3ded540..785e286 100644
--- a/libs/hwui/tests/unit/SkiaPipelineTests.cpp
+++ b/libs/hwui/tests/unit/SkiaPipelineTests.cpp
@@ -303,8 +303,9 @@
     class ClippedTestCanvas : public SkCanvas {
     public:
         ClippedTestCanvas() : SkCanvas(CANVAS_WIDTH, CANVAS_HEIGHT) {}
-        void onDrawImage2(const SkImage*, SkScalar dx, SkScalar dy, const SkSamplingOptions&,
-                          const SkPaint*) override {
+        void onDrawImageRect2(const SkImage*, const SkRect&, const SkRect&,
+                              const SkSamplingOptions&, const SkPaint*,
+                              SrcRectConstraint) override {
             EXPECT_EQ(0, mDrawCounter++);
             EXPECT_EQ(SkRect::MakeLTRB(10, 20, 30, 40), TestUtils::getClipBounds(this));
             EXPECT_TRUE(getTotalMatrix().isIdentity());
@@ -338,8 +339,9 @@
     class ClippedTestCanvas : public SkCanvas {
     public:
         ClippedTestCanvas() : SkCanvas(CANVAS_WIDTH, CANVAS_HEIGHT) {}
-        void onDrawImage2(const SkImage*, SkScalar dx, SkScalar dy, const SkSamplingOptions&,
-                          const SkPaint*) override {
+        void onDrawImageRect2(const SkImage*, const SkRect&, const SkRect&,
+                              const SkSamplingOptions&, const SkPaint*,
+                              SrcRectConstraint) override {
             EXPECT_EQ(0, mDrawCounter++);
             // Expect clip to be rotated.
             EXPECT_EQ(SkRect::MakeLTRB(CANVAS_HEIGHT - dirty.fTop - dirty.height(), dirty.fLeft,
diff --git a/libs/hwui/utils/ForceDark.h b/libs/hwui/utils/ForceDark.h
index 28538c4b..ecfe41f 100644
--- a/libs/hwui/utils/ForceDark.h
+++ b/libs/hwui/utils/ForceDark.h
@@ -17,6 +17,8 @@
 #ifndef FORCEDARKUTILS_H
 #define FORCEDARKUTILS_H
 
+#include <stdint.h>
+
 namespace android {
 namespace uirenderer {
 
@@ -26,9 +28,9 @@
  * This should stay in sync with the java @IntDef in
  * frameworks/base/graphics/java/android/graphics/ForceDarkType.java
  */
-enum class ForceDarkType : __uint8_t { NONE = 0, FORCE_DARK = 1, FORCE_INVERT_COLOR_DARK = 2 };
+enum class ForceDarkType : uint8_t { NONE = 0, FORCE_DARK = 1, FORCE_INVERT_COLOR_DARK = 2 };
 
 } /* namespace uirenderer */
 } /* namespace android */
 
-#endif  // FORCEDARKUTILS_H
\ No newline at end of file
+#endif  // FORCEDARKUTILS_H
diff --git a/location/java/android/location/flags/gnss.aconfig b/location/java/android/location/flags/gnss.aconfig
index a8464d3..794a555 100644
--- a/location/java/android/location/flags/gnss.aconfig
+++ b/location/java/android/location/flags/gnss.aconfig
@@ -34,3 +34,10 @@
     description: "Flag for location validation"
     bug: "314328533"
 }
+
+flag {
+    name: "gnss_configuration_from_resource"
+    namespace: "location"
+    description: "Flag for GNSS configuration from resource"
+    bug: "317734846"
+}
diff --git a/media/java/android/media/AudioSystem.java b/media/java/android/media/AudioSystem.java
index 46a0b99..0f6cbff 100644
--- a/media/java/android/media/AudioSystem.java
+++ b/media/java/android/media/AudioSystem.java
@@ -2499,14 +2499,12 @@
      * </ul>
      */
     public static int getPlatformType(Context context) {
-        if (((TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE))
-                .isVoiceCapable()) {
+        if (context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE)) {
+            return PLATFORM_AUTOMOTIVE;
+        } else if ((context.getSystemService(TelephonyManager.class)).isVoiceCapable()) {
             return PLATFORM_VOICE;
         } else if (context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_LEANBACK)) {
             return PLATFORM_TELEVISION;
-        } else if (context.getPackageManager().hasSystemFeature(
-                PackageManager.FEATURE_AUTOMOTIVE)) {
-            return PLATFORM_AUTOMOTIVE;
         } else {
             return PLATFORM_DEFAULT;
         }
diff --git a/native/graphics/jni/libjnigraphics.map.txt b/native/graphics/jni/libjnigraphics.map.txt
index e0df794..193728a 100644
--- a/native/graphics/jni/libjnigraphics.map.txt
+++ b/native/graphics/jni/libjnigraphics.map.txt
@@ -18,21 +18,21 @@
     AImageDecoder_getRepeatCount; # introduced=31
     AImageDecoder_advanceFrame; # introduced=31
     AImageDecoder_rewind; # introduced=31
-    AImageDecoder_getFrameInfo; # introduced = 31
-    AImageDecoder_setInternallyHandleDisposePrevious; # introduced = 31
+    AImageDecoder_getFrameInfo; # introduced=31
+    AImageDecoder_setInternallyHandleDisposePrevious; # introduced=31
     AImageDecoderHeaderInfo_getWidth; # introduced=30
     AImageDecoderHeaderInfo_getHeight; # introduced=30
     AImageDecoderHeaderInfo_getMimeType; # introduced=30
     AImageDecoderHeaderInfo_getAlphaFlags; # introduced=30
     AImageDecoderHeaderInfo_getAndroidBitmapFormat; # introduced=30
     AImageDecoderHeaderInfo_getDataSpace; # introduced=30
-    AImageDecoderFrameInfo_create; # introduced = 31
-    AImageDecoderFrameInfo_delete; # introduced = 31
-    AImageDecoderFrameInfo_getDuration; # introduced = 31
-    AImageDecoderFrameInfo_getFrameRect; # introduced = 31
-    AImageDecoderFrameInfo_hasAlphaWithinBounds; # introduced = 31
-    AImageDecoderFrameInfo_getDisposeOp; # introduced = 31
-    AImageDecoderFrameInfo_getBlendOp; # introduced = 31
+    AImageDecoderFrameInfo_create; # introduced=31
+    AImageDecoderFrameInfo_delete; # introduced=31
+    AImageDecoderFrameInfo_getDuration; # introduced=31
+    AImageDecoderFrameInfo_getFrameRect; # introduced=31
+    AImageDecoderFrameInfo_hasAlphaWithinBounds; # introduced=31
+    AImageDecoderFrameInfo_getDisposeOp; # introduced=31
+    AImageDecoderFrameInfo_getBlendOp; # introduced=31
     AndroidBitmap_getInfo;
     AndroidBitmap_getDataSpace;
     AndroidBitmap_lockPixels;
diff --git a/packages/CompanionDeviceManager/res/values-ar/strings.xml b/packages/CompanionDeviceManager/res/values-ar/strings.xml
index 5b893b0..6ddd5d3 100644
--- a/packages/CompanionDeviceManager/res/values-ar/strings.xml
+++ b/packages/CompanionDeviceManager/res/values-ar/strings.xml
@@ -56,28 +56,18 @@
     <string name="permission_nearby_devices" msgid="7530973297737123481">"الأجهزة المجاورة"</string>
     <string name="permission_media_routing_control" msgid="5498639511586715253">"تغيير جهاز إخراج الوسائط"</string>
     <string name="permission_storage" msgid="6831099350839392343">"الصور والوسائط"</string>
-    <!-- no translation found for permission_notifications (4099418516590632909) -->
-    <skip />
+    <string name="permission_notifications" msgid="4099418516590632909">"الإشعارات"</string>
     <string name="permission_app_streaming" msgid="6009695219091526422">"التطبيقات"</string>
     <string name="permission_nearby_device_streaming" msgid="1023325519477349499">"البثّ"</string>
-    <!-- no translation found for permission_phone_summary (8246321093970051702) -->
-    <skip />
-    <!-- no translation found for permission_call_logs_summary (7545243592757693321) -->
-    <skip />
-    <!-- no translation found for permission_sms_summary (8499509535410068616) -->
-    <skip />
-    <!-- no translation found for permission_contacts_summary (2840800622763086808) -->
-    <skip />
-    <!-- no translation found for permission_calendar_summary (8430353935747336165) -->
-    <skip />
-    <!-- no translation found for permission_microphone_summary (4862628553869973259) -->
-    <skip />
-    <!-- no translation found for permission_nearby_devices_summary (1306752848196464817) -->
-    <skip />
-    <!-- no translation found for permission_notification_listener_access_summary (7856071768185367749) -->
-    <skip />
-    <!-- no translation found for permission_notifications_summary (2272810466047367030) -->
-    <skip />
+    <string name="permission_phone_summary" msgid="8246321093970051702">"إجراء المكالمات الهاتفية وإدارتها"</string>
+    <string name="permission_call_logs_summary" msgid="7545243592757693321">"قراءة سجلّ المكالمات الهاتفية والكتابة إليه"</string>
+    <string name="permission_sms_summary" msgid="8499509535410068616">"إرسال الرسائل القصيرة وعرضها"</string>
+    <string name="permission_contacts_summary" msgid="2840800622763086808">"الوصول إلى جهات اتصالك"</string>
+    <string name="permission_calendar_summary" msgid="8430353935747336165">"الوصول إلى تقويمك"</string>
+    <string name="permission_microphone_summary" msgid="4862628553869973259">"تسجيل الصوت"</string>
+    <string name="permission_nearby_devices_summary" msgid="1306752848196464817">"يمكن العثور على الموضع النسبي للأجهزة المجاورة والربط بها وتحديدها."</string>
+    <string name="permission_notification_listener_access_summary" msgid="7856071768185367749">"يمكن لهذا الملف الشخصي قراءة جميع الإشعارات، بما في ذلك المعلومات، مثل جهات الاتصال والرسائل والصور."</string>
+    <string name="permission_notifications_summary" msgid="2272810466047367030">"‏• قراءة كل الإشعارات بما فيها المعلومات، مثل جهات الاتصال والرسائل والصور&lt;br/&gt;• إرسال الإشعارات&lt;br/&gt;&lt;br/&gt;يمكنك إدارة الإذن الممنوح لهذا التطبيق بقراءة الإشعارات وإرسالها في أي وقت من خلال الإعدادات &gt; الإشعارات."</string>
     <string name="permission_app_streaming_summary" msgid="606923325679670624">"بث تطبيقات هاتفك"</string>
     <string name="permission_storage_summary" msgid="3918240895519506417"></string>
     <string name="permission_nearby_device_streaming_summary" msgid="8280824871197081246">"بثّ التطبيقات وميزات النظام الأخرى من هاتفك"</string>
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/CredentialManagerRepo.kt b/packages/CredentialManager/src/com/android/credentialmanager/CredentialManagerRepo.kt
index a78509d..c0d7149 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/CredentialManagerRepo.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/CredentialManagerRepo.kt
@@ -53,6 +53,7 @@
     isNewActivity: Boolean,
 ) {
     val requestInfo: RequestInfo?
+    var isReqForAllOptions: Boolean = false
     private val providerEnabledList: List<ProviderData>
     private val providerDisabledList: List<DisabledProviderData>?
     val resultReceiver: ResultReceiver?
@@ -102,6 +103,11 @@
             ResultReceiver::class.java
         )
 
+        isReqForAllOptions = intent.getBooleanExtra(
+                Constants.EXTRA_REQ_FOR_ALL_OPTIONS,
+                /*defaultValue=*/ false
+        )
+
         val cancellationRequest = getCancelUiRequest(intent)
         val cancelUiRequestState = cancellationRequest?.let {
             CancelUiRequestState(getAppLabel(context.getPackageManager(), it.appPackageName))
@@ -141,7 +147,8 @@
                 )
             }
             RequestInfo.TYPE_GET -> {
-                val getCredentialInitialUiState = getCredentialInitialUiState(originName)!!
+                val getCredentialInitialUiState = getCredentialInitialUiState(originName,
+                        isReqForAllOptions)!!
                 val autoSelectEntry =
                     findAutoSelectEntry(getCredentialInitialUiState.providerDisplayInfo)
                 UiState(
@@ -216,14 +223,18 @@
     }
 
     // IMPORTANT: new invocation should be mindful that this method can throw.
-    private fun getCredentialInitialUiState(originName: String?): GetCredentialUiState? {
+    private fun getCredentialInitialUiState(
+            originName: String?,
+            isReqForAllOptions: Boolean
+    ): GetCredentialUiState? {
         val providerEnabledList = GetFlowUtils.toProviderList(
             providerEnabledList as List<GetCredentialProviderData>, context
         )
         val requestDisplayInfo = GetFlowUtils.toRequestDisplayInfo(requestInfo, context, originName)
         return GetCredentialUiState(
-            providerEnabledList,
-            requestDisplayInfo ?: return null,
+                isReqForAllOptions,
+                providerEnabledList,
+                requestDisplayInfo ?: return null
         )
     }
 
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/autofill/CredentialAutofillService.kt b/packages/CredentialManager/src/com/android/credentialmanager/autofill/CredentialAutofillService.kt
index 0ff1c7f..dfa5735 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/autofill/CredentialAutofillService.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/autofill/CredentialAutofillService.kt
@@ -19,16 +19,18 @@
 import android.R
 import android.app.assist.AssistStructure
 import android.content.Context
-import android.credentials.CredentialManager
-import android.credentials.CredentialOption
-import android.credentials.GetCandidateCredentialsException
-import android.credentials.GetCandidateCredentialsResponse
+import android.app.PendingIntent
+import android.credentials.GetCredentialResponse
 import android.credentials.GetCredentialRequest
-import android.credentials.ui.GetCredentialProviderData
+import android.credentials.GetCandidateCredentialsResponse
+import android.credentials.GetCandidateCredentialsException
+import android.credentials.CredentialOption
 import android.graphics.drawable.Icon
+import android.credentials.ui.GetCredentialProviderData
 import android.os.Bundle
 import android.os.CancellationSignal
 import android.os.OutcomeReceiver
+import android.credentials.Credential
 import android.service.autofill.AutofillService
 import android.service.autofill.Dataset
 import android.service.autofill.Field
@@ -41,8 +43,11 @@
 import android.service.autofill.SaveRequest
 import android.service.credentials.CredentialProviderService
 import android.util.Log
+import android.view.autofill.AutofillValue
+import android.view.autofill.IAutoFillManagerClient
 import android.view.autofill.AutofillId
 import android.widget.inline.InlinePresentationSpec
+import android.credentials.CredentialManager
 import androidx.autofill.inline.v1.InlineSuggestionUi
 import androidx.credentials.provider.CustomCredentialEntry
 import androidx.credentials.provider.PasswordCredentialEntry
@@ -58,11 +63,13 @@
 import org.json.JSONObject
 import java.util.concurrent.Executors
 
+
 class CredentialAutofillService : AutofillService() {
 
     companion object {
         private const val TAG = "CredAutofill"
 
+        private const val SESSION_ID_KEY = "session_id"
         private const val CRED_HINT_PREFIX = "credential="
         private const val REQUEST_DATA_KEY = "requestData"
         private const val CANDIDATE_DATA_KEY = "candidateQueryData"
@@ -77,10 +84,27 @@
             cancellationSignal: CancellationSignal,
             callback: FillCallback
     ) {
+    }
+
+    override fun onFillCredentialRequest(
+            request: FillRequest,
+            cancellationSignal: CancellationSignal,
+            callback: FillCallback,
+            autofillCallback: IAutoFillManagerClient
+    ) {
         val context = request.fillContexts
         val structure = context[context.size - 1].structure
         val callingPackage = structure.activityComponent.packageName
-        Log.i(TAG, "onFillRequest called for $callingPackage")
+        Log.i(TAG, "onFillCredentialRequest called for $callingPackage")
+
+        var sessionId = request.clientState?.getInt(SESSION_ID_KEY)
+
+        Log.i(TAG, "Autofill sessionId: " + sessionId)
+        if (sessionId == null) {
+            Log.i(TAG, "Session Id not found")
+            callback.onFailure("Session Id not found")
+            return
+        }
 
         val getCredRequest: GetCredentialRequest? = getCredManRequest(structure)
         if (getCredRequest == null) {
@@ -95,7 +119,31 @@
                 GetCandidateCredentialsException> {
             override fun onResult(result: GetCandidateCredentialsResponse) {
                 Log.i(TAG, "getCandidateCredentials onResponse")
-                val fillResponse = convertToFillResponse(result, request)
+
+                if (result.getCredentialResponse != null) {
+                    val autofillId: AutofillId? = result.getCredentialResponse
+                            .credential.data.getParcelable(
+                                    CredentialProviderService.EXTRA_AUTOFILL_ID,
+                                    AutofillId::class.java)
+                    Log.i(TAG, "getCandidateCredentials final response, autofillId: " +
+                            autofillId)
+
+                    if (autofillId != null) {
+                        autofillCallback.autofill(
+                                sessionId,
+                                mutableListOf(autofillId),
+                                mutableListOf(
+                                        AutofillValue.forText(
+                                                convertResponseToJson(result.getCredentialResponse)
+                                        )
+                                ),
+                                false)
+                    }
+                    return
+                }
+
+                val fillResponse = convertToFillResponse(result, request,
+                        this@CredentialAutofillService)
                 if (fillResponse != null) {
                     callback.onSuccess(fillResponse)
                 } else {
@@ -115,10 +163,62 @@
                 callingPackage,
                 CancellationSignal(),
                 Executors.newSingleThreadExecutor(),
-                outcome
+                outcome,
+                autofillCallback
         )
     }
 
+    // TODO(b/318118018): Use from Jetpack
+    private fun convertResponseToJson(response: GetCredentialResponse): String? {
+        try {
+            val jsonObject = JSONObject()
+            jsonObject.put("type", "get")
+            val jsonCred = JSONObject()
+            jsonCred.put("type", response.credential.type)
+            jsonCred.put("data", credentialToJSON(
+                    response.credential))
+            jsonObject.put("credential", jsonCred)
+            return jsonObject.toString()
+        } catch (e: JSONException) {
+            Log.i(
+                    TAG, "Exception while constructing response JSON: " +
+                    e.message
+            )
+        }
+        return null
+    }
+
+    // TODO(b/318118018): Replace with calls to Jetpack
+    private fun credentialToJSON(credential: Credential): JSONObject? {
+        Log.i(TAG, "credentialToJSON")
+        try {
+            if (credential.type == "android.credentials.TYPE_PASSWORD_CREDENTIAL") {
+                Log.i(TAG, "toJSON PasswordCredential")
+
+                val json = JSONObject()
+                val id = credential.data.getString("androidx.credentials.BUNDLE_KEY_ID")
+                val pass = credential.data.getString("androidx.credentials.BUNDLE_KEY_PASSWORD")
+                json.put("androidx.credentials.BUNDLE_KEY_ID", id)
+                json.put("androidx.credentials.BUNDLE_KEY_PASSWORD", pass)
+                return json
+            } else if (credential.type == "androidx.credentials.TYPE_PUBLIC_KEY_CREDENTIAL") {
+                Log.i(TAG, "toJSON PublicKeyCredential")
+
+                val json = JSONObject()
+                val responseJson = credential
+                        .data
+                        .getString("androidx.credentials.BUNDLE_KEY_AUTHENTICATION_RESPONSE_JSON")
+                json.put("androidx.credentials.BUNDLE_KEY_AUTHENTICATION_RESPONSE_JSON",
+                        responseJson)
+                return json
+            }
+        } catch (e: JSONException) {
+            Log.i(TAG, "issue while converting credential response to JSON")
+        }
+        Log.i(TAG, "Unsupported credential type")
+        return null
+    }
+
     private fun getEntryToIconMap(
             candidateProviderDataList: MutableList<GetCredentialProviderData>
     ): Map<String, Icon> {
@@ -150,14 +250,16 @@
 
     private fun convertToFillResponse(
             getCredResponse: GetCandidateCredentialsResponse,
-            filLRequest: FillRequest
+            filLRequest: FillRequest,
+            context: Context
     ): FillResponse? {
         val providerList = GetFlowUtils.toProviderList(
                 getCredResponse.candidateProviderDataList,
-                this@CredentialAutofillService)
+                context)
         if (providerList.isEmpty()) {
             return null
         }
+
         val entryIconMap: Map<String, Icon> =
                 getEntryToIconMap(getCredResponse.candidateProviderDataList)
         val autofillIdToProvidersMap: Map<AutofillId, List<ProviderInfo>> =
@@ -166,7 +268,8 @@
         var validFillResponse = false
         autofillIdToProvidersMap.forEach { (autofillId, providers) ->
             validFillResponse = processProvidersForAutofillId(
-                    filLRequest, autofillId, providers, entryIconMap, fillResponseBuilder)
+                    filLRequest, autofillId, providers, entryIconMap, fillResponseBuilder,
+                    getCredResponse.pendingIntent)
                     .or(validFillResponse)
         }
         if (!validFillResponse) {
@@ -180,7 +283,8 @@
             autofillId: AutofillId,
             providerList: List<ProviderInfo>,
             entryIconMap: Map<String, Icon>,
-            fillResponseBuilder: FillResponse.Builder
+            fillResponseBuilder: FillResponse.Builder,
+            bottomSheetPendingIntent: PendingIntent?
     ): Boolean {
         if (providerList.isEmpty()) {
             return false
@@ -227,9 +331,9 @@
                         ?: getDefaultIcon()
             }
             // Create inline presentation
-            var inlinePresentation: InlinePresentation? = null;
+            var inlinePresentation: InlinePresentation? = null
+            var spec: InlinePresentationSpec?
             if (inlinePresentationSpecs != null) {
-                val spec: InlinePresentationSpec
                 if (i < inlinePresentationSpecsCount) {
                     spec = inlinePresentationSpecs[i]
                 } else {
@@ -265,9 +369,52 @@
                             .build())
             datasetAdded = true
         }
+        val pinnedSpec = getLastInlinePresentationSpec(inlinePresentationSpecs,
+                inlinePresentationSpecsCount)
+        if (datasetAdded && bottomSheetPendingIntent != null && pinnedSpec != null) {
+            addPinnedInlineSuggestion(bottomSheetPendingIntent, pinnedSpec, autofillId,
+                    fillResponseBuilder)
+        }
         return datasetAdded
     }
 
+    private fun getLastInlinePresentationSpec(
+            inlinePresentationSpecs: List<InlinePresentationSpec>?,
+            inlinePresentationSpecsCount: Int
+    ): InlinePresentationSpec? {
+        if (inlinePresentationSpecs != null) {
+            return inlinePresentationSpecs[inlinePresentationSpecsCount - 1]
+        }
+        return null
+    }
+
+    private fun addPinnedInlineSuggestion(
+            bottomSheetPendingIntent: PendingIntent,
+            spec: InlinePresentationSpec,
+            autofillId: AutofillId,
+            fillResponseBuilder: FillResponse.Builder
+    ) {
+        val dataSetBuilder = Dataset.Builder()
+        val sliceBuilder = InlineSuggestionUi
+                .newContentBuilder(bottomSheetPendingIntent)
+                .setStartIcon(Icon.createWithResource(this,
+                        com.android.credentialmanager.R.drawable.ic_other_sign_in_24))
+        val presentationBuilder = Presentations.Builder()
+                .setInlinePresentation(InlinePresentation(
+                        sliceBuilder.build().slice, spec, /* pinned= */ true))
+
+        fillResponseBuilder.addDataset(
+                dataSetBuilder
+                        .setField(
+                                autofillId,
+                                Field.Builder().setPresentations(
+                                        presentationBuilder.build())
+                                        .build())
+                        .setAuthentication(bottomSheetPendingIntent.intentSender)
+                        .build()
+        )
+    }
+
     /**
      *  Maps Autofill Id to provider list. For example, passing in a provider info
      *
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetModel.kt b/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetModel.kt
index 46bebc4..a291f59 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetModel.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/getflow/GetModel.kt
@@ -26,10 +26,12 @@
 import com.android.internal.util.Preconditions
 
 data class GetCredentialUiState(
+        val isRequestForAllOptions: Boolean,
     val providerInfoList: List<ProviderInfo>,
     val requestDisplayInfo: RequestDisplayInfo,
     val providerDisplayInfo: ProviderDisplayInfo = toProviderDisplayInfo(providerInfoList),
-    val currentScreenState: GetScreenState = toGetScreenState(providerDisplayInfo),
+    val currentScreenState: GetScreenState = toGetScreenState(
+            providerDisplayInfo, isRequestForAllOptions),
     val activeEntry: EntryInfo? = toActiveEntry(providerDisplayInfo),
     val isNoAccount: Boolean = false,
 )
@@ -184,7 +186,8 @@
 }
 
 private fun toGetScreenState(
-    providerDisplayInfo: ProviderDisplayInfo
+    providerDisplayInfo: ProviderDisplayInfo,
+    isRequestForAllOptions: Boolean
 ): GetScreenState {
     return if (providerDisplayInfo.sortedUserNameToCredentialEntryList.isEmpty() &&
         providerDisplayInfo.remoteEntry == null &&
@@ -194,6 +197,8 @@
         providerDisplayInfo.authenticationEntryList.isEmpty() &&
         providerDisplayInfo.remoteEntry != null)
         GetScreenState.REMOTE_ONLY
+    else if (isRequestForAllOptions)
+        GetScreenState.ALL_SIGN_IN_OPTIONS
     else GetScreenState.PRIMARY_SELECTION
 }
 
diff --git a/packages/CredentialManager/wear/src/com/android/credentialmanager/CredentialSelectorViewModel.kt b/packages/CredentialManager/wear/src/com/android/credentialmanager/CredentialSelectorViewModel.kt
index 2a7e9e1..59e6142 100644
--- a/packages/CredentialManager/wear/src/com/android/credentialmanager/CredentialSelectorViewModel.kt
+++ b/packages/CredentialManager/wear/src/com/android/credentialmanager/CredentialSelectorViewModel.kt
@@ -21,11 +21,14 @@
 import androidx.lifecycle.viewModelScope
 import com.android.credentialmanager.model.Request
 import com.android.credentialmanager.client.CredentialManagerClient
+import com.android.credentialmanager.model.get.ActionEntryInfo
+import com.android.credentialmanager.model.get.CredentialEntryInfo
 import com.android.credentialmanager.ui.mappers.toGet
 import dagger.hilt.android.lifecycle.HiltViewModel
+import kotlinx.coroutines.flow.MutableStateFlow
 import kotlinx.coroutines.flow.SharingStarted
 import kotlinx.coroutines.flow.StateFlow
-import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.combine
 import kotlinx.coroutines.flow.stateIn
 import javax.inject.Inject
 
@@ -33,15 +36,15 @@
 class CredentialSelectorViewModel @Inject constructor(
     private val credentialManagerClient: CredentialManagerClient,
 ) : ViewModel() {
-
+    private val isPrimaryScreen = MutableStateFlow(false)
     val uiState: StateFlow<CredentialSelectorUiState> = credentialManagerClient.requests
-        .map { request ->
+        .combine(isPrimaryScreen) { request, isPrimary ->
             when (request) {
                 null -> CredentialSelectorUiState.Idle
                 is Request.Cancel -> CredentialSelectorUiState.Cancel(request.appName)
                 is Request.Close -> CredentialSelectorUiState.Close
                 is Request.Create -> CredentialSelectorUiState.Create
-                is Request.Get -> request.toGet()
+                is Request.Get -> request.toGet(isPrimary)
             }
         }
         .stateIn(
@@ -57,9 +60,18 @@
 
 sealed class CredentialSelectorUiState {
     data object Idle : CredentialSelectorUiState()
-    sealed class Get : CredentialSelectorUiState() {
-        data object SingleProviderSinglePasskey : Get()
-        data object SingleProviderSinglePassword : Get()
+    sealed class Get() : CredentialSelectorUiState() {
+        data class SingleEntry(val entry: CredentialEntryInfo) : Get()
+        data class SingleEntryPerAccount(val sortedEntries: List<CredentialEntryInfo>) : Get()
+        data class MultipleEntry(
+            val accounts: List<PerUserNameEntries>,
+            val actionEntryList: List<ActionEntryInfo>,
+        ) : Get() {
+            data class PerUserNameEntries(
+                val userName: String,
+                val sortedCredentialEntryList: List<CredentialEntryInfo>,
+            )
+        }
 
         // TODO: b/301206470 add the remaining states
     }
diff --git a/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/WearApp.kt b/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/WearApp.kt
index 7e0ea30..790f5b1 100644
--- a/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/WearApp.kt
+++ b/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/WearApp.kt
@@ -26,6 +26,7 @@
 import androidx.wear.compose.navigation.rememberSwipeDismissableNavController
 import androidx.wear.compose.navigation.rememberSwipeDismissableNavHostState
 import com.android.credentialmanager.CredentialSelectorUiState
+import com.android.credentialmanager.CredentialSelectorUiState.Get.SingleEntry
 import com.android.credentialmanager.CredentialSelectorViewModel
 import com.android.credentialmanager.ui.screens.LoadingScreen
 import com.android.credentialmanager.ui.screens.single.password.SinglePasswordScreen
@@ -57,6 +58,7 @@
 
         scrollable(Screen.SinglePasswordScreen.route) {
             SinglePasswordScreen(
+                state = viewModel.uiState.value as SingleEntry,
                 columnState = it.columnState,
                 onCloseApp = onCloseApp,
             )
@@ -100,7 +102,7 @@
     onCloseApp: () -> Unit,
 ) {
     when (state) {
-        is CredentialSelectorUiState.Get.SingleProviderSinglePassword -> {
+        is SingleEntry -> {
             navController.navigateToSinglePasswordScreen()
         }
 
diff --git a/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/mappers/CredentialSelectorUiStateGetMapper.kt b/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/mappers/CredentialSelectorUiStateGetMapper.kt
index 14b992a..44a838d 100644
--- a/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/mappers/CredentialSelectorUiStateGetMapper.kt
+++ b/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/mappers/CredentialSelectorUiStateGetMapper.kt
@@ -18,18 +18,45 @@
 
 import com.android.credentialmanager.model.Request
 import com.android.credentialmanager.CredentialSelectorUiState
+import com.android.credentialmanager.CredentialSelectorUiState.Get.MultipleEntry.PerUserNameEntries
+import com.android.credentialmanager.model.CredentialType
+import com.android.credentialmanager.model.get.CredentialEntryInfo
 
-fun Request.Get.toGet(): CredentialSelectorUiState.Get {
+fun Request.Get.toGet(isPrimary: Boolean): CredentialSelectorUiState.Get {
     // TODO: b/301206470 returning a hard coded state for MVP
-    if (true) return CredentialSelectorUiState.Get.SingleProviderSinglePassword
-
-    return if (providerInfos.size == 1) {
-        if (providerInfos.first().credentialEntryList.size == 1) {
-            CredentialSelectorUiState.Get.SingleProviderSinglePassword
+    if (true) return CredentialSelectorUiState.Get.SingleEntry(
+        providerInfos
+            .flatMap { it.credentialEntryList }
+            .first { it.credentialType == CredentialType.PASSWORD }
+    )
+    val accounts = providerInfos
+        .flatMap { it.credentialEntryList }
+        .groupBy { it.userName}
+        .entries
+        .toList()
+    return if (isPrimary) {
+        if (accounts.size == 1) {
+            CredentialSelectorUiState.Get.SingleEntry(
+                accounts[0].value.minWith(comparator)
+            )
         } else {
-            TODO() // b/301206470 - Implement other get flows
+            CredentialSelectorUiState.Get.SingleEntryPerAccount(
+                accounts.map { it.value.minWith(comparator) }.sortedWith(comparator)
+            )
         }
     } else {
-        TODO() // b/301206470 - Implement other get flows
+        CredentialSelectorUiState.Get.MultipleEntry(
+            accounts = accounts.map { PerUserNameEntries(
+                it.key,
+                it.value.sortedWith(comparator)
+            )
+            },
+            actionEntryList = providerInfos.flatMap { it.actionEntryList },
+        )
     }
 }
+val comparator = compareBy<CredentialEntryInfo> { entryInfo ->
+    // Passkey type always go first
+    entryInfo.credentialType.let{ if (it == CredentialType.PASSKEY) 0 else 1 }
+}
+    .thenByDescending{ it.lastUsedTimeMillis }
diff --git a/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/single/password/SinglePasswordScreen.kt b/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/single/password/SinglePasswordScreen.kt
index b64f581..9f971ae 100644
--- a/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/single/password/SinglePasswordScreen.kt
+++ b/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/single/password/SinglePasswordScreen.kt
@@ -29,6 +29,7 @@
 import androidx.compose.ui.unit.dp
 import androidx.hilt.navigation.compose.hiltViewModel
 import androidx.lifecycle.compose.collectAsStateWithLifecycle
+import com.android.credentialmanager.CredentialSelectorUiState.Get.SingleEntry
 import com.android.credentialmanager.R
 import com.android.credentialmanager.TAG
 import com.android.credentialmanager.activity.StartBalIntentSenderForResultContract
@@ -44,12 +45,13 @@
 
 @Composable
 fun SinglePasswordScreen(
+    state: SingleEntry,
     columnState: ScalingLazyColumnState,
     onCloseApp: () -> Unit,
     modifier: Modifier = Modifier,
     viewModel: SinglePasswordScreenViewModel = hiltViewModel(),
 ) {
-    viewModel.initialize()
+    viewModel.initialize(state.entry)
 
     val uiState by viewModel.uiState.collectAsStateWithLifecycle()
 
diff --git a/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/single/password/SinglePasswordScreenViewModel.kt b/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/single/password/SinglePasswordScreenViewModel.kt
index 26bee1f..4f9fc46 100644
--- a/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/single/password/SinglePasswordScreenViewModel.kt
+++ b/packages/CredentialManager/wear/src/com/android/credentialmanager/ui/screens/single/password/SinglePasswordScreenViewModel.kt
@@ -19,12 +19,9 @@
 import android.content.Intent
 import android.credentials.ui.ProviderPendingIntentResponse
 import android.credentials.ui.UserSelectionDialogResult
-import android.util.Log
 import androidx.activity.result.IntentSenderRequest
 import androidx.annotation.MainThread
 import androidx.lifecycle.ViewModel
-import androidx.lifecycle.viewModelScope
-import com.android.credentialmanager.TAG
 import com.android.credentialmanager.ktx.getIntentSenderRequest
 import com.android.credentialmanager.model.Request
 import com.android.credentialmanager.client.CredentialManagerClient
@@ -33,7 +30,6 @@
 import dagger.hilt.android.lifecycle.HiltViewModel
 import kotlinx.coroutines.flow.MutableStateFlow
 import kotlinx.coroutines.flow.StateFlow
-import kotlinx.coroutines.launch
 import javax.inject.Inject
 
 @HiltViewModel
@@ -51,32 +47,14 @@
     val uiState: StateFlow<SinglePasswordScreenUiState> = _uiState
 
     @MainThread
-    fun initialize() {
+    fun initialize(entryInfo: CredentialEntryInfo) {
         if (initializeCalled) return
         initializeCalled = true
-
-        viewModelScope.launch {
-            val request = credentialManagerClient.requests.value
-            Log.d(TAG, "request: $request, client instance: $credentialManagerClient")
-
-            if (request !is Request.Get) {
-                _uiState.value = SinglePasswordScreenUiState.Error
-            } else {
-                requestGet = request
-
-                if (requestGet.providerInfos.all { it.credentialEntryList.isEmpty() }) {
-                    Log.d(TAG, "Empty passwordEntries")
-                    _uiState.value = SinglePasswordScreenUiState.Error
-                } else {
-                    entryInfo = requestGet.providerInfos.first().credentialEntryList.first()
-                    _uiState.value = SinglePasswordScreenUiState.Loaded(
-                        PasswordUiModel(
-                            email = entryInfo.userName,
-                        )
-                    )
-                }
-            }
-        }
+        _uiState.value = SinglePasswordScreenUiState.Loaded(
+            PasswordUiModel(
+                email = entryInfo.userName,
+            )
+        )
     }
 
     fun onCancelClick() {
diff --git a/packages/InputDevices/res/values-uk/strings.xml b/packages/InputDevices/res/values-uk/strings.xml
index 5368f2c..71b3496 100644
--- a/packages/InputDevices/res/values-uk/strings.xml
+++ b/packages/InputDevices/res/values-uk/strings.xml
@@ -3,49 +3,49 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="app_label" msgid="8016145283189546017">"Пристрої вводу"</string>
     <string name="keyboard_layouts_label" msgid="6688773268302087545">"Клавіатура Android"</string>
-    <string name="keyboard_layout_english_uk_label" msgid="6664258463319999632">"англійська (Велика Британія)"</string>
-    <string name="keyboard_layout_english_us_label" msgid="8994890249649106291">"англійська (США)"</string>
-    <string name="keyboard_layout_english_us_intl" msgid="3705168594034233583">"англійська (США), міжнародна"</string>
-    <string name="keyboard_layout_english_us_colemak_label" msgid="4194969610343455380">"англійська (США), розкладка Colemak"</string>
-    <string name="keyboard_layout_english_us_dvorak_label" msgid="793528923171145202">"англійська (США), розкладка Дворака"</string>
-    <string name="keyboard_layout_english_us_workman_label" msgid="2944541595262173111">"англійська (США), розкладка Workman"</string>
-    <string name="keyboard_layout_german_label" msgid="8451565865467909999">"німецька"</string>
-    <string name="keyboard_layout_french_label" msgid="813450119589383723">"французька"</string>
-    <string name="keyboard_layout_french_ca_label" msgid="365352601060604832">"французька (Канада)"</string>
-    <string name="keyboard_layout_russian_label" msgid="8724879775815042968">"російська"</string>
-    <string name="keyboard_layout_russian_mac_label" msgid="3795866869038264796">"російська, розкладка Mac"</string>
-    <string name="keyboard_layout_spanish_label" msgid="7091555148131908240">"іспанська"</string>
-    <string name="keyboard_layout_swiss_french_label" msgid="4659191025396371684">"французька (Швейцарія)"</string>
-    <string name="keyboard_layout_swiss_german_label" msgid="2305520941993314258">"німецька (Швейцарія)"</string>
-    <string name="keyboard_layout_belgian" msgid="2011984572838651558">"бельгійська"</string>
-    <string name="keyboard_layout_bulgarian" msgid="8951224309972028398">"болгарська"</string>
+    <string name="keyboard_layout_english_uk_label" msgid="6664258463319999632">"Англійська (Велика Британія)"</string>
+    <string name="keyboard_layout_english_us_label" msgid="8994890249649106291">"Англійська (США)"</string>
+    <string name="keyboard_layout_english_us_intl" msgid="3705168594034233583">"Англійська (США), міжнародна"</string>
+    <string name="keyboard_layout_english_us_colemak_label" msgid="4194969610343455380">"Англійська (США), розкладка Colemak"</string>
+    <string name="keyboard_layout_english_us_dvorak_label" msgid="793528923171145202">"Англійська (США), розкладка Дворака"</string>
+    <string name="keyboard_layout_english_us_workman_label" msgid="2944541595262173111">"Англійська (США), розкладка Workman"</string>
+    <string name="keyboard_layout_german_label" msgid="8451565865467909999">"Німецька"</string>
+    <string name="keyboard_layout_french_label" msgid="813450119589383723">"Французька"</string>
+    <string name="keyboard_layout_french_ca_label" msgid="365352601060604832">"Французька (Канада)"</string>
+    <string name="keyboard_layout_russian_label" msgid="8724879775815042968">"Російська"</string>
+    <string name="keyboard_layout_russian_mac_label" msgid="3795866869038264796">"Російська, розкладка Mac"</string>
+    <string name="keyboard_layout_spanish_label" msgid="7091555148131908240">"Іспанська"</string>
+    <string name="keyboard_layout_swiss_french_label" msgid="4659191025396371684">"Французька (Швейцарія)"</string>
+    <string name="keyboard_layout_swiss_german_label" msgid="2305520941993314258">"Німецька (Швейцарія)"</string>
+    <string name="keyboard_layout_belgian" msgid="2011984572838651558">"Бельгійська"</string>
+    <string name="keyboard_layout_bulgarian" msgid="8951224309972028398">"Болгарська"</string>
     <string name="keyboard_layout_bulgarian_phonetic" msgid="7568914730360106653">"Болгарська (фонетична)"</string>
-    <string name="keyboard_layout_italian" msgid="6497079660449781213">"італійська"</string>
-    <string name="keyboard_layout_danish" msgid="8036432066627127851">"данська"</string>
-    <string name="keyboard_layout_norwegian" msgid="9090097917011040937">"норвезька"</string>
-    <string name="keyboard_layout_swedish" msgid="732959109088479351">"шведська"</string>
-    <string name="keyboard_layout_finnish" msgid="5585659438924315466">"фінська"</string>
-    <string name="keyboard_layout_croatian" msgid="4172229471079281138">"хорватська"</string>
-    <string name="keyboard_layout_czech" msgid="1349256901452975343">"чеська"</string>
+    <string name="keyboard_layout_italian" msgid="6497079660449781213">"Італійська"</string>
+    <string name="keyboard_layout_danish" msgid="8036432066627127851">"Данська"</string>
+    <string name="keyboard_layout_norwegian" msgid="9090097917011040937">"Норвезька"</string>
+    <string name="keyboard_layout_swedish" msgid="732959109088479351">"Шведська"</string>
+    <string name="keyboard_layout_finnish" msgid="5585659438924315466">"Фінська"</string>
+    <string name="keyboard_layout_croatian" msgid="4172229471079281138">"Хорватська"</string>
+    <string name="keyboard_layout_czech" msgid="1349256901452975343">"Чеська"</string>
     <string name="keyboard_layout_czech_qwerty" msgid="3331402534128515501">"Чеська (QWERTY)"</string>
-    <string name="keyboard_layout_estonian" msgid="8775830985185665274">"естонська"</string>
-    <string name="keyboard_layout_hungarian" msgid="4154963661406035109">"угорська"</string>
-    <string name="keyboard_layout_icelandic" msgid="5836645650912489642">"ісландська"</string>
-    <string name="keyboard_layout_brazilian" msgid="5117896443147781939">"бразильська"</string>
-    <string name="keyboard_layout_portuguese" msgid="2888198587329660305">"португальська"</string>
-    <string name="keyboard_layout_slovak" msgid="2469379934672837296">"словацька"</string>
-    <string name="keyboard_layout_slovenian" msgid="1735933028924982368">"словенська"</string>
-    <string name="keyboard_layout_turkish" msgid="7736163250907964898">"турецька"</string>
+    <string name="keyboard_layout_estonian" msgid="8775830985185665274">"Естонська"</string>
+    <string name="keyboard_layout_hungarian" msgid="4154963661406035109">"Угорська"</string>
+    <string name="keyboard_layout_icelandic" msgid="5836645650912489642">"Ісландська"</string>
+    <string name="keyboard_layout_brazilian" msgid="5117896443147781939">"Бразильська"</string>
+    <string name="keyboard_layout_portuguese" msgid="2888198587329660305">"Португальська"</string>
+    <string name="keyboard_layout_slovak" msgid="2469379934672837296">"Словацька"</string>
+    <string name="keyboard_layout_slovenian" msgid="1735933028924982368">"Словенська"</string>
+    <string name="keyboard_layout_turkish" msgid="7736163250907964898">"Турецька"</string>
     <string name="keyboard_layout_turkish_f" msgid="9130320856010776018">"Турецька-F"</string>
-    <string name="keyboard_layout_ukrainian" msgid="8176637744389480417">"українська"</string>
-    <string name="keyboard_layout_arabic" msgid="5671970465174968712">"арабська"</string>
+    <string name="keyboard_layout_ukrainian" msgid="8176637744389480417">"Українська"</string>
+    <string name="keyboard_layout_arabic" msgid="5671970465174968712">"Арабська"</string>
     <string name="keyboard_layout_greek" msgid="7289253560162386040">"Грецька"</string>
     <string name="keyboard_layout_hebrew" msgid="7241473985890173812">"Іврит"</string>
     <string name="keyboard_layout_lithuanian" msgid="6943110873053106534">"Литовська"</string>
     <string name="keyboard_layout_spanish_latin" msgid="5690539836069535697">"Іспанська (латиниця)"</string>
     <string name="keyboard_layout_latvian" msgid="4405417142306250595">"Латвійська"</string>
     <string name="keyboard_layout_persian" msgid="3920643161015888527">"Перська"</string>
-    <string name="keyboard_layout_azerbaijani" msgid="7315895417176467567">"азербайджанська"</string>
+    <string name="keyboard_layout_azerbaijani" msgid="7315895417176467567">"Азербайджанська"</string>
     <string name="keyboard_layout_polish" msgid="1121588624094925325">"Польська"</string>
     <string name="keyboard_layout_belarusian" msgid="7619281752698687588">"Білоруська"</string>
     <string name="keyboard_layout_mongolian" msgid="7678483495823936626">"Монгольська"</string>
diff --git a/packages/PackageInstaller/res/values-ar/strings.xml b/packages/PackageInstaller/res/values-ar/strings.xml
index e4da5b7..224c6dc 100644
--- a/packages/PackageInstaller/res/values-ar/strings.xml
+++ b/packages/PackageInstaller/res/values-ar/strings.xml
@@ -44,8 +44,7 @@
     <string name="unknown_apps_user_restriction_dlg_text" msgid="151020786933988344">"يتعذر على هذا المستخدم تثبيت التطبيقات غير المعروفة"</string>
     <string name="install_apps_user_restriction_dlg_text" msgid="2154119597001074022">"غير مسموح لهذا المستخدم بتثبيت التطبيقات"</string>
     <string name="ok" msgid="7871959885003339302">"حسنًا"</string>
-    <!-- no translation found for archive (4447791830199354721) -->
-    <skip />
+    <string name="archive" msgid="4447791830199354721">"أرشفة"</string>
     <string name="update_anyway" msgid="8792432341346261969">"التحديث على أي حال"</string>
     <string name="manage_applications" msgid="5400164782453975580">"إدارة التطبيقات"</string>
     <string name="out_of_space_dlg_title" msgid="4156690013884649502">"نفدت مساحة التخزين"</string>
@@ -60,16 +59,11 @@
     <string name="uninstall_update_title" msgid="824411791011583031">"إزالة التحديث"</string>
     <string name="uninstall_activity_text" msgid="1928194674397770771">"<xliff:g id="ACTIVITY_NAME">%1$s</xliff:g> هو جزء من التطبيق التالي:"</string>
     <string name="uninstall_application_text" msgid="3816830743706143980">"هل تريد إزالة هذا التطبيق؟"</string>
-    <!-- no translation found for archive_application_text (8482325710714386348) -->
-    <skip />
-    <!-- no translation found for archive_application_text_all_users (3151229641681672580) -->
-    <skip />
-    <!-- no translation found for archive_application_text_current_user_work_profile (1450487362134779752) -->
-    <skip />
-    <!-- no translation found for archive_application_text_user (2586558895535581451) -->
-    <skip />
-    <!-- no translation found for archive_application_text_current_user_private_profile (1958423158655599132) -->
-    <skip />
+    <string name="archive_application_text" msgid="8482325710714386348">"سيتم حفظ بياناتك الشخصية."</string>
+    <string name="archive_application_text_all_users" msgid="3151229641681672580">"هل تريد أرشفة هذا التطبيق لجميع المستخدمين؟ سيتم حفظ بياناتك الشخصية."</string>
+    <string name="archive_application_text_current_user_work_profile" msgid="1450487362134779752">"هل تريد أرشفة هذا التطبيق في ملف العمل؟ سيتم حفظ بياناتك الشخصية."</string>
+    <string name="archive_application_text_user" msgid="2586558895535581451">"هل تريد أرشفة هذا التطبيق لـ \"<xliff:g id="USERNAME">%1$s</xliff:g>\"؟ سيتم حفظ بياناتك الشخصية."</string>
+    <string name="archive_application_text_current_user_private_profile" msgid="1958423158655599132">"هل تريد أرشفة هذا التطبيق المحفوظ في المساحة الخاصّة؟ سيتم حفظ بياناتك الشخصية."</string>
     <string name="uninstall_application_text_all_users" msgid="575491774380227119">"هل تريد إزالة هذا التطبيق "<b>"لكل"</b>" المستخدمين؟ ستتم إزالة التطبيق وبياناته من "<b>"كل"</b>" المستخدمين على هذا الجهاز."</string>
     <string name="uninstall_application_text_user" msgid="498072714173920526">"هل تريد إزالة هذا التطبيق للمستخدم <xliff:g id="USERNAME">%1$s</xliff:g>؟"</string>
     <string name="uninstall_application_text_current_user_work_profile" msgid="8788387739022366193">"هل تريد إزالة تثبيت هذا التطبيق من ملفك الشخصي للعمل؟"</string>
@@ -108,8 +102,7 @@
     <string name="anonymous_source_warning" product="tablet" msgid="3939101621438855516">"يعتبر الجهاز اللوحي والبيانات الشخصية أكثر عرضة لهجوم التطبيقات غير المعروفة. من خلال تثبيت هذا التطبيق، توافق على تحمل مسؤولية أي ضرر يحدث للجهاز اللوحي أو فقدان البيانات الذي قد ينتج عن استخدامه."</string>
     <string name="anonymous_source_warning" product="tv" msgid="5599483539528168566">"يعتبر جهاز التلفزيون والبيانات الشخصية أكثر عرضة لهجوم التطبيقات غير المعروفة. من خلال تثبيت هذا التطبيق، توافق على تحمل مسؤولية أي ضرر يحدث لجهاز التلفزيون أو فقدان البيانات الذي قد ينتج عن استخدامه."</string>
     <string name="cloned_app_label" msgid="7503612829833756160">"نسخة طبق الأصل من \"<xliff:g id="PACKAGE_LABEL">%1$s</xliff:g>\""</string>
-    <!-- no translation found for archiving_app_label (1127085259724124725) -->
-    <skip />
+    <string name="archiving_app_label" msgid="1127085259724124725">"هل تريد أرشفة <xliff:g id="PACKAGE_LABEL">%1$s</xliff:g>؟"</string>
     <string name="anonymous_source_continue" msgid="4375745439457209366">"متابعة"</string>
     <string name="external_sources_settings" msgid="4046964413071713807">"الإعدادات"</string>
     <string name="wear_app_channel" msgid="1960809674709107850">"‏تثبيت / إلغاء تثبيت تطبيقات Android Wear"</string>
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/v2/ui/fragments/AnonymousSourceFragment.java b/packages/PackageInstaller/src/com/android/packageinstaller/v2/ui/fragments/AnonymousSourceFragment.java
index 679f696..b29cb2a 100644
--- a/packages/PackageInstaller/src/com/android/packageinstaller/v2/ui/fragments/AnonymousSourceFragment.java
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/v2/ui/fragments/AnonymousSourceFragment.java
@@ -34,7 +34,10 @@
 public class AnonymousSourceFragment extends DialogFragment {
 
     public static String TAG = AnonymousSourceFragment.class.getSimpleName();
+    @NonNull
     private InstallActionListener mInstallActionListener;
+    @NonNull
+    private AlertDialog mDialog;
 
     @Override
     public void onAttach(@NonNull Context context) {
@@ -45,7 +48,7 @@
     @NonNull
     @Override
     public Dialog onCreateDialog(Bundle savedInstanceState) {
-        return new AlertDialog.Builder(getActivity())
+       mDialog = new AlertDialog.Builder(requireContext())
             .setMessage(R.string.anonymous_source_warning)
             .setPositiveButton(R.string.anonymous_source_continue,
                 ((dialog, which) -> mInstallActionListener.onPositiveResponse(
@@ -53,6 +56,7 @@
             .setNegativeButton(R.string.cancel,
                 ((dialog, which) -> mInstallActionListener.onNegativeResponse(
                     InstallStage.STAGE_USER_ACTION_REQUIRED))).create();
+       return mDialog;
     }
 
     @Override
@@ -60,4 +64,24 @@
         super.onCancel(dialog);
         mInstallActionListener.onNegativeResponse(InstallStage.STAGE_USER_ACTION_REQUIRED);
     }
+
+    @Override
+    public void onStart() {
+        super.onStart();
+        mDialog.getButton(DialogInterface.BUTTON_POSITIVE).setFilterTouchesWhenObscured(true);
+    }
+
+    @Override
+    public void onPause() {
+        super.onPause();
+        // This prevents tapjacking since an overlay activity started in front of Pia will
+        // cause Pia to be paused.
+        mDialog.getButton(DialogInterface.BUTTON_POSITIVE).setEnabled(false);
+    }
+
+    @Override
+    public void onResume() {
+        super.onResume();
+        mDialog.getButton(DialogInterface.BUTTON_POSITIVE).setEnabled(true);
+    }
 }
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/v2/ui/fragments/ExternalSourcesBlockedFragment.java b/packages/PackageInstaller/src/com/android/packageinstaller/v2/ui/fragments/ExternalSourcesBlockedFragment.java
index 49901de..2314d6b 100644
--- a/packages/PackageInstaller/src/com/android/packageinstaller/v2/ui/fragments/ExternalSourcesBlockedFragment.java
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/v2/ui/fragments/ExternalSourcesBlockedFragment.java
@@ -35,8 +35,12 @@
 public class ExternalSourcesBlockedFragment extends DialogFragment {
 
     private final String TAG = ExternalSourcesBlockedFragment.class.getSimpleName();
+    @NonNull
     private final InstallUserActionRequired mDialogData;
+    @NonNull
     private InstallActionListener mInstallActionListener;
+    @NonNull
+    private AlertDialog mDialog;
 
     public ExternalSourcesBlockedFragment(InstallUserActionRequired dialogData) {
         mDialogData = dialogData;
@@ -51,7 +55,7 @@
     @NonNull
     @Override
     public Dialog onCreateDialog(@Nullable Bundle savedInstanceState) {
-        return new AlertDialog.Builder(requireContext())
+        mDialog = new AlertDialog.Builder(requireContext())
             .setTitle(mDialogData.getAppLabel())
             .setIcon(mDialogData.getAppIcon())
             .setMessage(R.string.untrusted_external_source_warning)
@@ -62,6 +66,7 @@
                 (dialog, which) -> mInstallActionListener.onNegativeResponse(
                     mDialogData.getStageCode()))
             .create();
+        return mDialog;
     }
 
     @Override
@@ -69,4 +74,24 @@
         super.onCancel(dialog);
         mInstallActionListener.onNegativeResponse(mDialogData.getStageCode());
     }
+
+    @Override
+    public void onStart() {
+        super.onStart();
+        mDialog.getButton(DialogInterface.BUTTON_POSITIVE).setFilterTouchesWhenObscured(true);
+    }
+
+    @Override
+    public void onPause() {
+        super.onPause();
+        // This prevents tapjacking since an overlay activity started in front of Pia will
+        // cause Pia to be paused.
+        mDialog.getButton(DialogInterface.BUTTON_POSITIVE).setEnabled(false);
+    }
+
+    @Override
+    public void onResume() {
+        super.onResume();
+        mDialog.getButton(DialogInterface.BUTTON_POSITIVE).setEnabled(true);
+    }
 }
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/v2/ui/fragments/InstallConfirmationFragment.java b/packages/PackageInstaller/src/com/android/packageinstaller/v2/ui/fragments/InstallConfirmationFragment.java
index 25363d0..dbe32cc 100644
--- a/packages/PackageInstaller/src/com/android/packageinstaller/v2/ui/fragments/InstallConfirmationFragment.java
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/v2/ui/fragments/InstallConfirmationFragment.java
@@ -42,6 +42,8 @@
     private final InstallUserActionRequired mDialogData;
     @NonNull
     private InstallActionListener mInstallActionListener;
+    @NonNull
+    private AlertDialog mDialog;
 
     public InstallConfirmationFragment(@NonNull InstallUserActionRequired dialogData) {
         mDialogData = dialogData;
@@ -58,20 +60,29 @@
     public Dialog onCreateDialog(@Nullable Bundle savedInstanceState) {
         View dialogView = getLayoutInflater().inflate(R.layout.install_content_view, null);
 
-        AlertDialog dialog = new AlertDialog.Builder(requireContext())
+        int positiveBtnTextRes;
+        if (mDialogData.isAppUpdating()) {
+            if (mDialogData.getDialogMessage() != null) {
+                positiveBtnTextRes = R.string.update_anyway;
+            } else {
+                positiveBtnTextRes = R.string.update;
+            }
+        } else {
+            positiveBtnTextRes = R.string.install;
+        }
+
+        mDialog = new AlertDialog.Builder(requireContext())
             .setIcon(mDialogData.getAppIcon())
             .setTitle(mDialogData.getAppLabel())
             .setView(dialogView)
-            .setPositiveButton(mDialogData.isAppUpdating() ? R.string.update : R.string.install,
+            .setPositiveButton(positiveBtnTextRes,
                 (dialogInt, which) -> mInstallActionListener.onPositiveResponse(
                     InstallUserActionRequired.USER_ACTION_REASON_INSTALL_CONFIRMATION))
             .setNegativeButton(R.string.cancel,
                 (dialogInt, which) -> mInstallActionListener.onNegativeResponse(
                     mDialogData.getStageCode()))
-
             .create();
 
-        // TODO: Dynamically change positive button text to update anyway
         TextView viewToEnable;
         if (mDialogData.isAppUpdating()) {
             viewToEnable = dialogView.requireViewById(R.id.install_confirm_question_update);
@@ -84,7 +95,7 @@
         }
         viewToEnable.setVisibility(View.VISIBLE);
 
-        return dialog;
+        return mDialog;
     }
 
     @Override
@@ -92,4 +103,24 @@
         super.onCancel(dialog);
         mInstallActionListener.onNegativeResponse(mDialogData.getStageCode());
     }
+
+    @Override
+    public void onStart() {
+        super.onStart();
+        mDialog.getButton(DialogInterface.BUTTON_POSITIVE).setFilterTouchesWhenObscured(true);
+    }
+
+    @Override
+    public void onPause() {
+        super.onPause();
+        // This prevents tapjacking since an overlay activity started in front of Pia will
+        // cause Pia to be paused.
+        mDialog.getButton(DialogInterface.BUTTON_POSITIVE).setEnabled(false);
+    }
+
+    @Override
+    public void onResume() {
+        super.onResume();
+        mDialog.getButton(DialogInterface.BUTTON_POSITIVE).setEnabled(true);
+    }
 }
diff --git a/packages/SettingsLib/res/values-sw/strings.xml b/packages/SettingsLib/res/values-sw/strings.xml
index 1cdb69c..a94c313 100644
--- a/packages/SettingsLib/res/values-sw/strings.xml
+++ b/packages/SettingsLib/res/values-sw/strings.xml
@@ -140,8 +140,8 @@
     <string name="bluetooth_hid_profile_summary_use_for" msgid="4289460627406490952">"Tumia kwa kuingiza"</string>
     <string name="bluetooth_hearing_aid_profile_summary_use_for" msgid="3374057355721486932">"Tumia kwa visaidizi vya kusikia"</string>
     <string name="bluetooth_le_audio_profile_summary_use_for" msgid="2778318636027348572">"Tumia kwa ajili ya LE_AUDIO"</string>
-    <string name="bluetooth_pairing_accept" msgid="2054232610815498004">"Oanisha"</string>
-    <string name="bluetooth_pairing_accept_all_caps" msgid="2734383073450506220">"OANISHA"</string>
+    <string name="bluetooth_pairing_accept" msgid="2054232610815498004">"Unganisha"</string>
+    <string name="bluetooth_pairing_accept_all_caps" msgid="2734383073450506220">"UNGANISHA"</string>
     <string name="bluetooth_pairing_decline" msgid="6483118841204885890">"Ghairi"</string>
     <string name="bluetooth_pairing_will_share_phonebook" msgid="3064334458659165176">"Kuoanisha hutoa ruhusa ya kufikiwa kwa unaowasiliana nao na rekodi ya simu zilizopigwa unapounganishwa."</string>
     <string name="bluetooth_pairing_error_message" msgid="6626399020672335565">"Haikuwezakulinganisha na <xliff:g id="DEVICE_NAME">%1$s</xliff:g>."</string>
@@ -237,10 +237,10 @@
     <string name="adb_wireless_error" msgid="721958772149779856">"Hitilafu"</string>
     <string name="adb_wireless_settings" msgid="2295017847215680229">"Utatuzi usiotumia waya"</string>
     <string name="adb_wireless_list_empty_off" msgid="1713707973837255490">"Ili kuangalia na kutumia vifaa vinavyopatikana, washa utatuzi usiotumia waya"</string>
-    <string name="adb_pair_method_qrcode_title" msgid="6982904096137468634">"Oanisha kifaa ukitumia msimbo wa QR"</string>
-    <string name="adb_pair_method_qrcode_summary" msgid="7130694277228970888">"Oanisha vifaa vipya ukitumia kichanganuzi cha Msimbo wa QR"</string>
-    <string name="adb_pair_method_code_title" msgid="1122590300445142904">"Oanisha kifaa ukitumia msimbo wa kuoanisha"</string>
-    <string name="adb_pair_method_code_summary" msgid="6370414511333685185">"Oanisha vifaa vipya ukitumia msimbo wa tarakimu sita"</string>
+    <string name="adb_pair_method_qrcode_title" msgid="6982904096137468634">"Unganisha kifaa ukitumia msimbo wa QR"</string>
+    <string name="adb_pair_method_qrcode_summary" msgid="7130694277228970888">"Unganisha vifaa vipya ukitumia kichanganuzi cha Msimbo wa QR"</string>
+    <string name="adb_pair_method_code_title" msgid="1122590300445142904">"Unganisha kifaa ukitumia msimbo wa kuunganisha"</string>
+    <string name="adb_pair_method_code_summary" msgid="6370414511333685185">"Unganisha vifaa vipya ukitumia msimbo wa tarakimu sita"</string>
     <string name="adb_paired_devices_title" msgid="5268997341526217362">"Vifaa vilivyooanishwa"</string>
     <string name="adb_wireless_device_connected_summary" msgid="3039660790249148713">"Vilivyounganishwa kwa sasa"</string>
     <string name="adb_wireless_device_details_title" msgid="7129369670526565786">"Maelezo ya kifaa"</string>
@@ -248,16 +248,16 @@
     <string name="adb_device_fingerprint_title_format" msgid="291504822917843701">"Alama bainifu ya kifaa: <xliff:g id="FINGERPRINT_PARAM">%1$s</xliff:g>"</string>
     <string name="adb_wireless_connection_failed_title" msgid="664211177427438438">"Imeshindwa kuunganisha"</string>
     <string name="adb_wireless_connection_failed_message" msgid="9213896700171602073">"Hakikisha kuwa <xliff:g id="DEVICE_NAME">%1$s</xliff:g> kimeunganishwa kwenye mtandao sahihi"</string>
-    <string name="adb_pairing_device_dialog_title" msgid="7141739231018530210">"Oanisha na kifaa"</string>
+    <string name="adb_pairing_device_dialog_title" msgid="7141739231018530210">"Unganisha na kifaa"</string>
     <string name="adb_pairing_device_dialog_pairing_code_label" msgid="3639239786669722731">"Msimbo wa kuoanisha wa Wi-Fi"</string>
     <string name="adb_pairing_device_dialog_failed_title" msgid="3426758947882091735">"Imeshindwa kuoanisha"</string>
     <string name="adb_pairing_device_dialog_failed_msg" msgid="6611097519661997148">"Hakikisha kuwa kifaa kimeunganishwa kwenye mtandao mmoja."</string>
-    <string name="adb_wireless_qrcode_summary" msgid="8051414549011801917">"Oanisha kifaa kupitia Wi-Fi kwa kuchanganua msimbo wa QR"</string>
+    <string name="adb_wireless_qrcode_summary" msgid="8051414549011801917">"Unganisha kifaa kupitia Wi-Fi kwa kuchanganua msimbo wa QR"</string>
     <string name="adb_wireless_verifying_qrcode_text" msgid="6123192424916029207">"Inaoanisha kifaa…"</string>
     <string name="adb_qrcode_pairing_device_failed_msg" msgid="6936292092592914132">"Imeshindwa kuoanisha kifaa. Huenda msimbo wa QR haukuwa sahihi au kifaa hakijaunganishwa kwenye mtandao mmoja."</string>
     <string name="adb_wireless_ip_addr_preference_title" msgid="8335132107715311730">"Anwani ya IP na Mlango"</string>
     <string name="adb_wireless_qrcode_pairing_title" msgid="1906409667944674707">"Changanua msimbo wa QR"</string>
-    <string name="adb_wireless_qrcode_pairing_description" msgid="6014121407143607851">"Oanisha kifaa kupitia Wi-Fi kwa kuchanganua msimbo wa QR"</string>
+    <string name="adb_wireless_qrcode_pairing_description" msgid="6014121407143607851">"Unganisha kifaa kupitia Wi-Fi kwa kuchanganua msimbo wa QR"</string>
     <string name="adb_wireless_no_network_msg" msgid="2365795244718494658">"Tafadhali unganisha kwenye mtandao wa Wi-Fi"</string>
     <string name="keywords_adb_wireless" msgid="6507505581882171240">"adb, tatua, dev"</string>
     <string name="bugreport_in_power" msgid="8664089072534638709">"Njia ya mkato ya kuripoti hitilafu"</string>
diff --git a/packages/SystemUI/aconfig/Android.bp b/packages/SystemUI/aconfig/Android.bp
index 7f16ca5..03f9d74 100644
--- a/packages/SystemUI/aconfig/Android.bp
+++ b/packages/SystemUI/aconfig/Android.bp
@@ -25,6 +25,7 @@
         "//frameworks/base/packages/SystemUI:__subpackages__",
         "//frameworks/libs/systemui/tracinglib:__subpackages__",
         "//platform_testing:__subpackages__",
+        "//cts:__subpackages__",
     ],
 }
 
diff --git a/packages/SystemUI/aconfig/systemui.aconfig b/packages/SystemUI/aconfig/systemui.aconfig
index d9286b3..aa0903c 100644
--- a/packages/SystemUI/aconfig/systemui.aconfig
+++ b/packages/SystemUI/aconfig/systemui.aconfig
@@ -145,6 +145,16 @@
 }
 
 flag {
+    name: "enable_background_keyguard_ondrawn_callback"
+    namespace: "systemui"
+    description: "Calls the onDrawn keyguard in the background, without being blocked by main"
+        "thread work. This results in the screen to turn on earlier when the main thread is stuck. "
+        "Note that, even after this callback is called, we're waiting for all windows to finish "
+        " drawing."
+    bug: "295873557"
+}
+
+flag {
     name: "qs_new_pipeline"
     namespace: "systemui"
     description: "Use the new pipeline for Quick Settings. Should have no behavior changes."
@@ -284,6 +294,13 @@
 }
 
 flag {
+    name: "new_volume_panel"
+    namespace: "systemui"
+    description: "Switches to the new volume panel (without Slices)."
+    bug: "202262476"
+}
+
+flag {
     name: "screenshare_notification_hiding"
     namespace: "systemui"
     description: "Enable hiding of notifications during screenshare"
@@ -296,3 +313,10 @@
    description: "Displays the auto on toggle in the bluetooth QS tile dialog"
    bug: "316985153"
 }
+
+flag {
+   name: "smartspace_relocate_to_bottom"
+   namespace: "systemui"
+   description: "Relocate Smartspace to bottom of the Lock Screen"
+   bug: "316212788"
+}
diff --git a/packages/SystemUI/compose/core/src/com/android/compose/ui/platform/DensityAwareComposeView.kt b/packages/SystemUI/compose/core/src/com/android/compose/ui/platform/DensityAwareComposeView.kt
new file mode 100644
index 0000000..dff8753
--- /dev/null
+++ b/packages/SystemUI/compose/core/src/com/android/compose/ui/platform/DensityAwareComposeView.kt
@@ -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.compose.ui.platform
+
+import android.content.Context
+import android.content.res.Configuration
+import android.util.AttributeSet
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.ui.platform.AbstractComposeView
+
+/**
+ * A ComposeView that recreates its composition if the display size or font scale was changed.
+ *
+ * TODO(b/317317814): Remove this workaround.
+ */
+class DensityAwareComposeView(context: Context) : OpenComposeView(context) {
+    private var lastDensityDpi: Int = -1
+    private var lastFontScale: Float = -1f
+
+    override fun onAttachedToWindow() {
+        super.onAttachedToWindow()
+
+        val configuration = context.resources.configuration
+        lastDensityDpi = configuration.densityDpi
+        lastFontScale = configuration.fontScale
+    }
+
+    override fun dispatchConfigurationChanged(newConfig: Configuration) {
+        super.dispatchConfigurationChanged(newConfig)
+
+        // If the density or font scale changed, we dispose then recreate the composition. Note that
+        // we do this here after dispatching the new configuration to children (instead of doing
+        // this in onConfigurationChanged()) because the new configuration should first be
+        // dispatched to the AndroidComposeView that holds the current density before we recreate
+        // the composition.
+        val densityDpi = newConfig.densityDpi
+        val fontScale = newConfig.fontScale
+        if (densityDpi != lastDensityDpi || fontScale != lastFontScale) {
+            lastDensityDpi = densityDpi
+            lastFontScale = fontScale
+
+            disposeComposition()
+            if (isAttachedToWindow) {
+                createComposition()
+            }
+        }
+    }
+}
+
+/** A fork of [androidx.compose.ui.platform.ComposeView] that is open and can be subclassed. */
+open class OpenComposeView
+internal constructor(context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0) :
+    AbstractComposeView(context, attrs, defStyleAttr) {
+
+    private val content = mutableStateOf<(@Composable () -> Unit)?>(null)
+
+    @Suppress("RedundantVisibilityModifier")
+    protected override var shouldCreateCompositionOnAttachedToWindow: Boolean = false
+
+    @Composable
+    override fun Content() {
+        content.value?.invoke()
+    }
+
+    override fun getAccessibilityClassName(): CharSequence {
+        return javaClass.name
+    }
+
+    /**
+     * Set the Jetpack Compose UI content for this view. Initial composition will occur when the
+     * view becomes attached to a window or when [createComposition] is called, whichever comes
+     * first.
+     */
+    fun setContent(content: @Composable () -> Unit) {
+        shouldCreateCompositionOnAttachedToWindow = true
+        this.content.value = content
+        if (isAttachedToWindow) {
+            createComposition()
+        }
+    }
+}
diff --git a/packages/SystemUI/compose/facade/enabled/src/com/android/systemui/compose/ComposeFacade.kt b/packages/SystemUI/compose/facade/enabled/src/com/android/systemui/compose/ComposeFacade.kt
index 5055ee1..d31547b 100644
--- a/packages/SystemUI/compose/facade/enabled/src/com/android/systemui/compose/ComposeFacade.kt
+++ b/packages/SystemUI/compose/facade/enabled/src/com/android/systemui/compose/ComposeFacade.kt
@@ -27,6 +27,7 @@
 import androidx.compose.ui.unit.dp
 import androidx.lifecycle.LifecycleOwner
 import com.android.compose.theme.PlatformTheme
+import com.android.compose.ui.platform.DensityAwareComposeView
 import com.android.systemui.common.ui.compose.windowinsets.CutoutLocation
 import com.android.systemui.common.ui.compose.windowinsets.DisplayCutout
 import com.android.systemui.common.ui.compose.windowinsets.DisplayCutoutProvider
@@ -84,7 +85,7 @@
         viewModel: FooterActionsViewModel,
         qsVisibilityLifecycleOwner: LifecycleOwner,
     ): View {
-        return ComposeView(context).apply {
+        return DensityAwareComposeView(context).apply {
             setContent { PlatformTheme { FooterActions(viewModel, qsVisibilityLifecycleOwner) } }
         }
     }
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 55fc3a2..5a4e0a9 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
@@ -16,6 +16,7 @@
 
 package com.android.systemui.communal.ui.compose
 
+import android.appwidget.AppWidgetHostView
 import android.os.Bundle
 import android.util.SizeF
 import android.widget.FrameLayout
@@ -194,6 +195,7 @@
         items(
             count = list.size,
             key = { index -> list[index].key },
+            contentType = { index -> list[index].key },
             span = { index -> GridItemSpan(list[index].size.span) },
         ) { index ->
             val cardModifier = Modifier.width(Dimensions.CardWidth)
@@ -361,6 +363,8 @@
                     .createView(context, model.appWidgetId, model.providerInfo)
                     .apply { updateAppWidgetSize(Bundle.EMPTY, listOf(size)) }
             },
+            // For reusing composition in lazy lists.
+            onReset = {},
         )
     }
 }
@@ -373,10 +377,10 @@
     AndroidView(
         modifier = modifier,
         factory = { context ->
-            FrameLayout(context).apply { addView(model.remoteViews.apply(context, this)) }
+            AppWidgetHostView(context).apply { updateAppWidget(model.remoteViews) }
         },
         // For reusing composition in lazy lists.
-        onReset = {}
+        onReset = {},
     )
 }
 
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 67a6820..ff53ff2 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
@@ -37,9 +37,6 @@
 import kotlinx.coroutines.flow.map
 import kotlinx.coroutines.flow.stateIn
 
-/** Set this to `true` to use the LockscreenContent replacement of KeyguardRootView. */
-private val UseLockscreenContent = false
-
 /** The lock screen scene shows when the device is locked. */
 @SysUISingleton
 class LockscreenScene
@@ -48,7 +45,6 @@
     @Application private val applicationScope: CoroutineScope,
     private val viewModel: LockscreenSceneViewModel,
     private val lockscreenContent: Lazy<LockscreenContent>,
-    private val viewBasedLockscreenContent: Lazy<ViewBasedLockscreenContent>,
 ) : ComposableScene {
     override val key = SceneKey.Lockscreen
 
@@ -73,7 +69,6 @@
     ) {
         LockscreenScene(
             lockscreenContent = lockscreenContent,
-            viewBasedLockscreenContent = viewBasedLockscreenContent,
             modifier = modifier,
         )
     }
@@ -93,22 +88,13 @@
 }
 
 @Composable
-private fun SceneScope.LockscreenScene(
+private fun LockscreenScene(
     lockscreenContent: Lazy<LockscreenContent>,
-    viewBasedLockscreenContent: Lazy<ViewBasedLockscreenContent>,
     modifier: Modifier = Modifier,
 ) {
-    if (UseLockscreenContent) {
-        lockscreenContent
-            .get()
-            .Content(
-                modifier = modifier.fillMaxSize(),
-            )
-    } else {
-        with(viewBasedLockscreenContent.get()) {
-            Content(
-                modifier = modifier.fillMaxSize(),
-            )
-        }
-    }
+    lockscreenContent
+        .get()
+        .Content(
+            modifier = modifier.fillMaxSize(),
+        )
 }
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/LockscreenSceneBlueprintModule.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/LockscreenSceneBlueprintModule.kt
index 9abb50c..3677cab 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/LockscreenSceneBlueprintModule.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/LockscreenSceneBlueprintModule.kt
@@ -20,6 +20,7 @@
 import com.android.systemui.keyguard.ui.composable.blueprint.DefaultBlueprintModule
 import com.android.systemui.keyguard.ui.composable.blueprint.ShortcutsBesideUdfpsBlueprintModule
 import com.android.systemui.keyguard.ui.composable.blueprint.SplitShadeBlueprintModule
+import com.android.systemui.keyguard.ui.composable.section.OptionalSectionModule
 import dagger.Module
 
 @Module(
@@ -27,6 +28,7 @@
         [
             CommunalBlueprintModule::class,
             DefaultBlueprintModule::class,
+            OptionalSectionModule::class,
             ShortcutsBesideUdfpsBlueprintModule::class,
             SplitShadeBlueprintModule::class,
         ],
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/ViewBasedLockscreenContent.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/ViewBasedLockscreenContent.kt
deleted file mode 100644
index 976161b..0000000
--- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/ViewBasedLockscreenContent.kt
+++ /dev/null
@@ -1,111 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.keyguard.ui.composable
-
-import android.graphics.Rect
-import android.view.View
-import android.view.ViewGroup
-import androidx.compose.foundation.layout.fillMaxSize
-import androidx.compose.runtime.Composable
-import androidx.compose.runtime.collectAsState
-import androidx.compose.runtime.getValue
-import androidx.compose.ui.Modifier
-import androidx.compose.ui.graphics.toComposeRect
-import androidx.compose.ui.layout.Layout
-import androidx.compose.ui.layout.onPlaced
-import androidx.compose.ui.viewinterop.AndroidView
-import androidx.core.view.isVisible
-import com.android.compose.animation.scene.SceneScope
-import com.android.systemui.keyguard.qualifiers.KeyguardRootView
-import com.android.systemui.keyguard.ui.viewmodel.LockscreenSceneViewModel
-import com.android.systemui.notifications.ui.composable.NotificationStack
-import com.android.systemui.res.R
-import javax.inject.Inject
-
-/**
- * Renders the content of the lockscreen.
- *
- * This is different from [LockscreenContent] (which is pure compose) and uses a view-based
- * implementation of the lockscreen scene content that relies on [KeyguardRootView].
- *
- * TODO(b/316211368): remove this once [LockscreenContent] is feature complete.
- */
-class ViewBasedLockscreenContent
-@Inject
-constructor(
-    private val viewModel: LockscreenSceneViewModel,
-    @KeyguardRootView private val viewProvider: () -> @JvmSuppressWildcards View,
-) {
-    @Composable
-    fun SceneScope.Content(
-        modifier: Modifier = Modifier,
-    ) {
-        fun findSettingsMenu(): View {
-            return viewProvider().requireViewById(R.id.keyguard_settings_button)
-        }
-
-        LockscreenLongPress(
-            viewModel = viewModel.longPress,
-            modifier = modifier,
-        ) { onSettingsMenuPlaced ->
-            AndroidView(
-                factory = { _ ->
-                    val keyguardRootView = viewProvider()
-                    // Remove the KeyguardRootView from any parent it might already have in legacy
-                    // code just in case (a view can't have two parents).
-                    (keyguardRootView.parent as? ViewGroup)?.removeView(keyguardRootView)
-                    keyguardRootView
-                },
-                modifier = Modifier.fillMaxSize(),
-            )
-
-            val notificationStackPosition by
-                viewModel.keyguardRoot.notificationBounds.collectAsState()
-
-            Layout(
-                modifier =
-                    Modifier.fillMaxSize().onPlaced {
-                        val settingsMenuView = findSettingsMenu()
-                        onSettingsMenuPlaced(
-                            if (settingsMenuView.isVisible) {
-                                val bounds = Rect()
-                                settingsMenuView.getHitRect(bounds)
-                                bounds.toComposeRect()
-                            } else {
-                                null
-                            }
-                        )
-                    },
-                content = {
-                    NotificationStack(
-                        viewModel = viewModel.notifications,
-                        isScrimVisible = false,
-                    )
-                }
-            ) { measurables, constraints ->
-                check(measurables.size == 1)
-                val height = notificationStackPosition.height.toInt()
-                val childConstraints = constraints.copy(minHeight = height, maxHeight = height)
-                val placeable = measurables[0].measure(childConstraints)
-                layout(constraints.maxWidth, constraints.maxHeight) {
-                    val start = (constraints.maxWidth - placeable.measuredWidth) / 2
-                    placeable.placeRelative(x = start, y = notificationStackPosition.top.toInt())
-                }
-            }
-        }
-    }
-}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/BurnInState.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/BurnInState.kt
new file mode 100644
index 0000000..c418490
--- /dev/null
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/BurnInState.kt
@@ -0,0 +1,94 @@
+/*
+ * 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.keyguard.ui.composable.blueprint
+
+import androidx.compose.foundation.layout.WindowInsets
+import androidx.compose.foundation.layout.displayCutout
+import androidx.compose.foundation.layout.systemBars
+import androidx.compose.foundation.layout.union
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.collectAsState
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.ui.platform.LocalDensity
+import com.android.systemui.keyguard.domain.interactor.KeyguardClockInteractor
+import com.android.systemui.keyguard.ui.viewmodel.BurnInParameters
+import com.android.systemui.plugins.clocks.ClockController
+import kotlin.math.min
+import kotlin.math.roundToInt
+
+/** Produces a [BurnInState] that can be used to query the `LockscreenBurnInViewModel` flows. */
+@Composable
+fun rememberBurnIn(
+    clockInteractor: KeyguardClockInteractor,
+): BurnInState {
+    val clock by clockInteractor.currentClock.collectAsState()
+
+    val (smartspaceTop, onSmartspaceTopChanged) = remember { mutableStateOf<Float?>(null) }
+    val (smallClockTop, onSmallClockTopChanged) = remember { mutableStateOf<Float?>(null) }
+
+    val topmostTop =
+        when {
+            smartspaceTop != null && smallClockTop != null -> min(smartspaceTop, smallClockTop)
+            smartspaceTop != null -> smartspaceTop
+            smallClockTop != null -> smallClockTop
+            else -> 0f
+        }.roundToInt()
+
+    val params = rememberBurnInParameters(clock, topmostTop)
+
+    return remember(params, onSmartspaceTopChanged, onSmallClockTopChanged) {
+        BurnInState(
+            parameters = params,
+            onSmartspaceTopChanged = onSmartspaceTopChanged,
+            onSmallClockTopChanged = onSmallClockTopChanged,
+        )
+    }
+}
+
+@Composable
+private fun rememberBurnInParameters(
+    clock: ClockController?,
+    topmostTop: Int,
+): BurnInParameters {
+    val density = LocalDensity.current
+    val topInset = WindowInsets.systemBars.union(WindowInsets.displayCutout).getTop(density)
+
+    return remember(clock, topInset, topmostTop) {
+        BurnInParameters(
+            clockControllerProvider = { clock },
+            topInset = topInset,
+            statusViewTop = topmostTop,
+        )
+    }
+}
+
+data class BurnInState(
+    /** Parameters for use with the `LockscreenBurnInViewModel. */
+    val parameters: BurnInParameters,
+    /**
+     * Callback to invoke when the top coordinate of the smartspace element is updated, pass `null`
+     * when the element is not shown.
+     */
+    val onSmartspaceTopChanged: (Float?) -> Unit,
+    /**
+     * Callback to invoke when the top coordinate of the small clock element is updated, pass `null`
+     * when the element is not shown.
+     */
+    val onSmallClockTopChanged: (Float?) -> Unit,
+)
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/DefaultBlueprint.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/DefaultBlueprint.kt
index d9d98cb..84d4246 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/DefaultBlueprint.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/DefaultBlueprint.kt
@@ -24,6 +24,7 @@
 import androidx.compose.ui.layout.Layout
 import androidx.compose.ui.unit.IntRect
 import com.android.compose.animation.scene.SceneScope
+import com.android.systemui.keyguard.domain.interactor.KeyguardClockInteractor
 import com.android.systemui.keyguard.ui.composable.LockscreenLongPress
 import com.android.systemui.keyguard.ui.composable.section.AmbientIndicationSection
 import com.android.systemui.keyguard.ui.composable.section.BottomAreaSection
@@ -37,6 +38,7 @@
 import dagger.Binds
 import dagger.Module
 import dagger.multibindings.IntoSet
+import java.util.Optional
 import javax.inject.Inject
 
 /**
@@ -52,9 +54,10 @@
     private val smartSpaceSection: SmartSpaceSection,
     private val notificationSection: NotificationSection,
     private val lockSection: LockSection,
-    private val ambientIndicationSection: AmbientIndicationSection,
+    private val ambientIndicationSectionOptional: Optional<AmbientIndicationSection>,
     private val bottomAreaSection: BottomAreaSection,
     private val settingsMenuSection: SettingsMenuSection,
+    private val clockInteractor: KeyguardClockInteractor,
 ) : LockscreenSceneBlueprint {
 
     override val id: String = "default"
@@ -62,6 +65,7 @@
     @Composable
     override fun SceneScope.Content(modifier: Modifier) {
         val isUdfpsVisible = viewModel.isUdfpsVisible
+        val burnIn = rememberBurnIn(clockInteractor)
 
         LockscreenLongPress(
             viewModel = viewModel.longPress,
@@ -74,14 +78,25 @@
                         modifier = Modifier.fillMaxWidth(),
                     ) {
                         with(statusBarSection) { StatusBar(modifier = Modifier.fillMaxWidth()) }
-                        with(clockSection) { SmallClock(modifier = Modifier.fillMaxWidth()) }
-                        with(smartSpaceSection) { SmartSpace(modifier = Modifier.fillMaxWidth()) }
+                        with(clockSection) {
+                            SmallClock(
+                                onTopChanged = burnIn.onSmallClockTopChanged,
+                                modifier = Modifier.fillMaxWidth(),
+                            )
+                        }
+                        with(smartSpaceSection) {
+                            SmartSpace(
+                                burnInParams = burnIn.parameters,
+                                onTopChanged = burnIn.onSmartspaceTopChanged,
+                                modifier = Modifier.fillMaxWidth(),
+                            )
+                        }
                         with(clockSection) { LargeClock(modifier = Modifier.fillMaxWidth()) }
                         with(notificationSection) {
                             Notifications(modifier = Modifier.fillMaxWidth().weight(1f))
                         }
-                        if (!isUdfpsVisible) {
-                            with(ambientIndicationSection) {
+                        if (!isUdfpsVisible && ambientIndicationSectionOptional.isPresent) {
+                            with(ambientIndicationSectionOptional.get()) {
                                 AmbientIndication(modifier = Modifier.fillMaxWidth())
                             }
                         }
@@ -91,8 +106,8 @@
 
                     // Aligned to bottom and constrained to below the lock icon.
                     Column(modifier = Modifier.fillMaxWidth()) {
-                        if (isUdfpsVisible) {
-                            with(ambientIndicationSection) {
+                        if (isUdfpsVisible && ambientIndicationSectionOptional.isPresent) {
+                            with(ambientIndicationSectionOptional.get()) {
                                 AmbientIndication(modifier = Modifier.fillMaxWidth())
                             }
                         }
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/ShortcutsBesideUdfpsBlueprint.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/ShortcutsBesideUdfpsBlueprint.kt
index 4704f5c..4148462 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/ShortcutsBesideUdfpsBlueprint.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/blueprint/ShortcutsBesideUdfpsBlueprint.kt
@@ -24,6 +24,7 @@
 import androidx.compose.ui.layout.Layout
 import androidx.compose.ui.unit.IntRect
 import com.android.compose.animation.scene.SceneScope
+import com.android.systemui.keyguard.domain.interactor.KeyguardClockInteractor
 import com.android.systemui.keyguard.ui.composable.LockscreenLongPress
 import com.android.systemui.keyguard.ui.composable.section.AmbientIndicationSection
 import com.android.systemui.keyguard.ui.composable.section.BottomAreaSection
@@ -37,6 +38,7 @@
 import dagger.Binds
 import dagger.Module
 import dagger.multibindings.IntoSet
+import java.util.Optional
 import javax.inject.Inject
 
 /**
@@ -52,9 +54,10 @@
     private val smartSpaceSection: SmartSpaceSection,
     private val notificationSection: NotificationSection,
     private val lockSection: LockSection,
-    private val ambientIndicationSection: AmbientIndicationSection,
+    private val ambientIndicationSectionOptional: Optional<AmbientIndicationSection>,
     private val bottomAreaSection: BottomAreaSection,
     private val settingsMenuSection: SettingsMenuSection,
+    private val clockInteractor: KeyguardClockInteractor,
 ) : LockscreenSceneBlueprint {
 
     override val id: String = "shortcuts-besides-udfps"
@@ -62,6 +65,7 @@
     @Composable
     override fun SceneScope.Content(modifier: Modifier) {
         val isUdfpsVisible = viewModel.isUdfpsVisible
+        val burnIn = rememberBurnIn(clockInteractor)
 
         LockscreenLongPress(
             viewModel = viewModel.longPress,
@@ -74,14 +78,25 @@
                         modifier = Modifier.fillMaxWidth(),
                     ) {
                         with(statusBarSection) { StatusBar(modifier = Modifier.fillMaxWidth()) }
-                        with(clockSection) { SmallClock(modifier = Modifier.fillMaxWidth()) }
-                        with(smartSpaceSection) { SmartSpace(modifier = Modifier.fillMaxWidth()) }
+                        with(clockSection) {
+                            SmallClock(
+                                onTopChanged = burnIn.onSmallClockTopChanged,
+                                modifier = Modifier.fillMaxWidth(),
+                            )
+                        }
+                        with(smartSpaceSection) {
+                            SmartSpace(
+                                burnInParams = burnIn.parameters,
+                                onTopChanged = burnIn.onSmartspaceTopChanged,
+                                modifier = Modifier.fillMaxWidth(),
+                            )
+                        }
                         with(clockSection) { LargeClock(modifier = Modifier.fillMaxWidth()) }
                         with(notificationSection) {
                             Notifications(modifier = Modifier.fillMaxWidth().weight(1f))
                         }
-                        if (!isUdfpsVisible) {
-                            with(ambientIndicationSection) {
+                        if (!isUdfpsVisible && ambientIndicationSectionOptional.isPresent) {
+                            with(ambientIndicationSectionOptional.get()) {
                                 AmbientIndication(modifier = Modifier.fillMaxWidth())
                             }
                         }
@@ -97,8 +112,8 @@
 
                     // Aligned to bottom and constrained to below the lock icon.
                     Column(modifier = Modifier.fillMaxWidth()) {
-                        if (isUdfpsVisible) {
-                            with(ambientIndicationSection) {
+                        if (isUdfpsVisible && ambientIndicationSectionOptional.isPresent) {
+                            with(ambientIndicationSectionOptional.get()) {
                                 AmbientIndication(modifier = Modifier.fillMaxWidth())
                             }
                         }
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/modifier/BurnInModifiers.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/modifier/BurnInModifiers.kt
new file mode 100644
index 0000000..f9dd04b
--- /dev/null
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/modifier/BurnInModifiers.kt
@@ -0,0 +1,68 @@
+/*
+ * 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.keyguard.ui.composable.modifier
+
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.collectAsState
+import androidx.compose.runtime.getValue
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.graphicsLayer
+import androidx.compose.ui.layout.boundsInWindow
+import androidx.compose.ui.layout.onPlaced
+import com.android.systemui.keyguard.ui.viewmodel.AodBurnInViewModel
+import com.android.systemui.keyguard.ui.viewmodel.BurnInParameters
+import com.android.systemui.keyguard.ui.viewmodel.BurnInScaleViewModel
+
+/**
+ * Modifies the composable to account for anti-burn in translation, alpha, and scaling.
+ *
+ * Please override [isClock] as `true` if the composable is an element that's part of a clock.
+ */
+@Composable
+fun Modifier.burnInAware(
+    viewModel: AodBurnInViewModel,
+    params: BurnInParameters,
+    isClock: Boolean = false,
+): Modifier {
+    val translationX by viewModel.translationX(params).collectAsState(initial = 0f)
+    val translationY by viewModel.translationY(params).collectAsState(initial = 0f)
+    val alpha by viewModel.alpha.collectAsState(initial = 1f)
+    val scaleViewModel by viewModel.scale(params).collectAsState(initial = BurnInScaleViewModel())
+
+    return this.graphicsLayer {
+        val scale =
+            when {
+                scaleViewModel.scaleClockOnly && isClock -> scaleViewModel.scale
+                !scaleViewModel.scaleClockOnly -> scaleViewModel.scale
+                else -> 1f
+            }
+
+        this.translationX = translationX
+        this.translationY = translationY
+        this.alpha = alpha
+        this.scaleX = scale
+        this.scaleY = scale
+    }
+}
+
+/** Reports the "top" coordinate of the modified composable to the given [consumer]. */
+@Composable
+fun Modifier.onTopPlacementChanged(
+    consumer: (Float) -> Unit,
+): Modifier {
+    return onPlaced { coordinates -> consumer(coordinates.boundsInWindow().top) }
+}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/AmbientIndicationSection.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/AmbientIndicationSection.kt
index 0e7ac5e..af9a195 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/AmbientIndicationSection.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/AmbientIndicationSection.kt
@@ -16,36 +16,11 @@
 
 package com.android.systemui.keyguard.ui.composable.section
 
-import androidx.compose.foundation.background
-import androidx.compose.foundation.layout.Box
-import androidx.compose.foundation.layout.fillMaxWidth
-import androidx.compose.material3.Text
 import androidx.compose.runtime.Composable
-import androidx.compose.ui.Alignment
 import androidx.compose.ui.Modifier
-import androidx.compose.ui.graphics.Color
-import com.android.compose.animation.scene.ElementKey
 import com.android.compose.animation.scene.SceneScope
-import javax.inject.Inject
 
-class AmbientIndicationSection @Inject constructor() {
-    @Composable
-    fun SceneScope.AmbientIndication(modifier: Modifier = Modifier) {
-        MovableElement(
-            key = AmbientIndicationElementKey,
-            modifier = modifier,
-        ) {
-            Box(
-                modifier = Modifier.fillMaxWidth().background(Color.Green),
-            ) {
-                Text(
-                    text = "TODO(b/316211368): Ambient indication",
-                    color = Color.White,
-                    modifier = Modifier.align(Alignment.Center),
-                )
-            }
-        }
-    }
+/** Defines interface for classes that can render the ambient indication area. */
+interface AmbientIndicationSection {
+    @Composable fun SceneScope.AmbientIndication(modifier: Modifier)
 }
-
-private val AmbientIndicationElementKey = ElementKey("AmbientIndication")
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/BottomAreaSection.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/BottomAreaSection.kt
index db20f65..8bd0d45 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/BottomAreaSection.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/BottomAreaSection.kt
@@ -35,10 +35,10 @@
 import com.android.systemui.keyguard.ui.binder.KeyguardIndicationAreaBinder
 import com.android.systemui.keyguard.ui.binder.KeyguardQuickAffordanceViewBinder
 import com.android.systemui.keyguard.ui.view.KeyguardIndicationArea
+import com.android.systemui.keyguard.ui.viewmodel.AodAlphaViewModel
 import com.android.systemui.keyguard.ui.viewmodel.KeyguardIndicationAreaViewModel
 import com.android.systemui.keyguard.ui.viewmodel.KeyguardQuickAffordanceViewModel
 import com.android.systemui.keyguard.ui.viewmodel.KeyguardQuickAffordancesCombinedViewModel
-import com.android.systemui.keyguard.ui.viewmodel.KeyguardRootViewModel
 import com.android.systemui.plugins.FalsingManager
 import com.android.systemui.res.R
 import com.android.systemui.statusbar.KeyguardIndicationController
@@ -55,7 +55,7 @@
     private val vibratorHelper: VibratorHelper,
     private val indicationController: KeyguardIndicationController,
     private val indicationAreaViewModel: KeyguardIndicationAreaViewModel,
-    private val keyguardRootViewModel: KeyguardRootViewModel,
+    private val alphaViewModel: AodAlphaViewModel,
 ) {
     /**
      * Renders a single lockscreen shortcut.
@@ -74,20 +74,22 @@
             key = if (isStart) StartButtonElementKey else EndButtonElementKey,
             modifier = modifier,
         ) {
-            Shortcut(
-                viewId = if (isStart) R.id.start_button else R.id.end_button,
-                viewModel = if (isStart) viewModel.startButton else viewModel.endButton,
-                transitionAlpha = viewModel.transitionAlpha,
-                falsingManager = falsingManager,
-                vibratorHelper = vibratorHelper,
-                indicationController = indicationController,
-                modifier =
-                    if (applyPadding) {
-                        Modifier.shortcutPadding()
-                    } else {
-                        Modifier
-                    }
-            )
+            content {
+                Shortcut(
+                    viewId = if (isStart) R.id.start_button else R.id.end_button,
+                    viewModel = if (isStart) viewModel.startButton else viewModel.endButton,
+                    transitionAlpha = viewModel.transitionAlpha,
+                    falsingManager = falsingManager,
+                    vibratorHelper = vibratorHelper,
+                    indicationController = indicationController,
+                    modifier =
+                        if (applyPadding) {
+                            Modifier.shortcutPadding()
+                        } else {
+                            Modifier
+                        }
+                )
+            }
         }
     }
 
@@ -99,11 +101,13 @@
             key = IndicationAreaElementKey,
             modifier = modifier.shortcutPadding(),
         ) {
-            IndicationArea(
-                indicationAreaViewModel = indicationAreaViewModel,
-                keyguardRootViewModel = keyguardRootViewModel,
-                indicationController = indicationController,
-            )
+            content {
+                IndicationArea(
+                    indicationAreaViewModel = indicationAreaViewModel,
+                    alphaViewModel = alphaViewModel,
+                    indicationController = indicationController,
+                )
+            }
         }
     }
 
@@ -179,7 +183,7 @@
     @Composable
     private fun IndicationArea(
         indicationAreaViewModel: KeyguardIndicationAreaViewModel,
-        keyguardRootViewModel: KeyguardRootViewModel,
+        alphaViewModel: AodAlphaViewModel,
         indicationController: KeyguardIndicationController,
         modifier: Modifier = Modifier,
     ) {
@@ -192,7 +196,7 @@
                     KeyguardIndicationAreaBinder.bind(
                         view = view,
                         viewModel = indicationAreaViewModel,
-                        keyguardRootViewModel = keyguardRootViewModel,
+                        aodAlphaViewModel = alphaViewModel,
                         indicationController = indicationController,
                     )
                 )
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/ClockSection.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/ClockSection.kt
index eaf8063..f021bb6 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/ClockSection.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/ClockSection.kt
@@ -16,41 +16,73 @@
 
 package com.android.systemui.keyguard.ui.composable.section
 
-import androidx.compose.foundation.background
-import androidx.compose.foundation.layout.Box
 import androidx.compose.foundation.layout.fillMaxWidth
-import androidx.compose.material3.Text
+import androidx.compose.foundation.layout.padding
 import androidx.compose.runtime.Composable
-import androidx.compose.ui.Alignment
+import androidx.compose.runtime.DisposableEffect
+import androidx.compose.runtime.collectAsState
+import androidx.compose.runtime.getValue
 import androidx.compose.ui.Modifier
-import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.platform.LocalView
+import androidx.compose.ui.res.dimensionResource
+import androidx.compose.ui.viewinterop.AndroidView
 import com.android.compose.animation.scene.ElementKey
 import com.android.compose.animation.scene.SceneScope
+import com.android.compose.modifiers.padding
+import com.android.keyguard.KeyguardClockSwitch
+import com.android.systemui.keyguard.domain.interactor.KeyguardClockInteractor
+import com.android.systemui.keyguard.ui.composable.modifier.onTopPlacementChanged
 import com.android.systemui.keyguard.ui.viewmodel.KeyguardClockViewModel
+import com.android.systemui.res.R
 import javax.inject.Inject
 
 class ClockSection
 @Inject
 constructor(
     private val viewModel: KeyguardClockViewModel,
+    private val clockInteractor: KeyguardClockInteractor,
 ) {
+
     @Composable
-    fun SceneScope.SmallClock(modifier: Modifier = Modifier) {
-        if (viewModel.useLargeClock) {
+    fun SceneScope.SmallClock(
+        onTopChanged: (top: Float?) -> Unit,
+        modifier: Modifier = Modifier,
+    ) {
+        val clockSize by viewModel.clockSize.collectAsState()
+        val currentClock by viewModel.currentClock.collectAsState()
+        viewModel.clock = currentClock
+
+        if (clockSize != KeyguardClockSwitch.SMALL) {
+            onTopChanged(null)
             return
         }
 
+        if (currentClock?.smallClock?.view == null) {
+            return
+        }
+
+        val view = LocalView.current
+
+        DisposableEffect(view) {
+            clockInteractor.clockEventController.registerListeners(view)
+
+            onDispose { clockInteractor.clockEventController.unregisterListeners() }
+        }
+
         MovableElement(
             key = ClockElementKey,
             modifier = modifier,
         ) {
-            Box(
-                modifier = Modifier.fillMaxWidth().background(Color.Magenta),
-            ) {
-                Text(
-                    text = "TODO(b/316211368): Small clock",
-                    color = Color.White,
-                    modifier = Modifier.align(Alignment.Center),
+            content {
+                AndroidView(
+                    factory = { checkNotNull(currentClock).smallClock.view },
+                    modifier =
+                        Modifier.padding(
+                                horizontal =
+                                    dimensionResource(R.dimen.keyguard_affordance_horizontal_offset)
+                            )
+                            .padding(top = { viewModel.getSmallClockTopMargin(view.context) })
+                            .onTopPlacementChanged(onTopChanged),
                 )
             }
         }
@@ -58,21 +90,36 @@
 
     @Composable
     fun SceneScope.LargeClock(modifier: Modifier = Modifier) {
-        if (!viewModel.useLargeClock) {
+        val clockSize by viewModel.clockSize.collectAsState()
+        val currentClock by viewModel.currentClock.collectAsState()
+        viewModel.clock = currentClock
+
+        if (clockSize != KeyguardClockSwitch.LARGE) {
             return
         }
 
+        if (currentClock?.largeClock?.view == null) {
+            return
+        }
+
+        val view = LocalView.current
+
+        DisposableEffect(view) {
+            clockInteractor.clockEventController.registerListeners(view)
+
+            onDispose { clockInteractor.clockEventController.unregisterListeners() }
+        }
+
         MovableElement(
             key = ClockElementKey,
             modifier = modifier,
         ) {
-            Box(
-                modifier = Modifier.fillMaxWidth().background(Color.Blue),
-            ) {
-                Text(
-                    text = "TODO(b/316211368): Large clock",
-                    color = Color.White,
-                    modifier = Modifier.align(Alignment.Center),
+            content {
+                AndroidView(
+                    factory = { checkNotNull(currentClock).largeClock.view },
+                    modifier =
+                        Modifier.fillMaxWidth()
+                            .padding(top = { viewModel.getLargeClockTopMargin(view.context) })
                 )
             }
         }
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/NotificationSection.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/NotificationSection.kt
index c547e2b..900616f 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/NotificationSection.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/NotificationSection.kt
@@ -22,7 +22,6 @@
 import com.android.systemui.notifications.ui.composable.NotificationStack
 import com.android.systemui.statusbar.notification.stack.ui.viewmodel.NotificationsPlaceholderViewModel
 import javax.inject.Inject
-import kotlinx.coroutines.CoroutineDispatcher
 
 class NotificationSection
 @Inject
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/di/bound/CustomTileUser.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/OptionalSectionModule.kt
similarity index 61%
copy from packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/di/bound/CustomTileUser.kt
copy to packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/OptionalSectionModule.kt
index efc7431..5b7a8e6 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/di/bound/CustomTileUser.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/OptionalSectionModule.kt
@@ -14,12 +14,16 @@
  * limitations under the License.
  */
 
-package com.android.systemui.qs.tiles.impl.custom.di.bound
+package com.android.systemui.keyguard.ui.composable.section
 
-import javax.inject.Qualifier
+import dagger.BindsOptionalOf
+import dagger.Module
 
-/** User associated with current custom tile binding. */
-@Qualifier
-@MustBeDocumented
-@Retention(AnnotationRetention.RUNTIME)
-annotation class CustomTileUser
+/**
+ * Dagger module for providing placeholders for optional lockscreen scene sections that don't exist
+ * in AOSP but may be provided by OEMs.
+ */
+@Module
+interface OptionalSectionModule {
+    @BindsOptionalOf fun ambientIndicationSection(): AmbientIndicationSection
+}
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/SmartSpaceSection.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/SmartSpaceSection.kt
index 3c49cbc..9b71844 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/SmartSpaceSection.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/SmartSpaceSection.kt
@@ -36,6 +36,10 @@
 import com.android.compose.animation.scene.ElementKey
 import com.android.compose.animation.scene.SceneScope
 import com.android.systemui.keyguard.KeyguardUnlockAnimationController
+import com.android.systemui.keyguard.ui.composable.modifier.burnInAware
+import com.android.systemui.keyguard.ui.composable.modifier.onTopPlacementChanged
+import com.android.systemui.keyguard.ui.viewmodel.AodBurnInViewModel
+import com.android.systemui.keyguard.ui.viewmodel.BurnInParameters
 import com.android.systemui.keyguard.ui.viewmodel.KeyguardSmartspaceViewModel
 import com.android.systemui.res.R
 import com.android.systemui.statusbar.lockscreen.LockscreenSmartspaceController
@@ -47,11 +51,16 @@
     private val lockscreenSmartspaceController: LockscreenSmartspaceController,
     private val keyguardUnlockAnimationController: KeyguardUnlockAnimationController,
     private val keyguardSmartspaceViewModel: KeyguardSmartspaceViewModel,
+    private val aodBurnInViewModel: AodBurnInViewModel,
 ) {
     @Composable
-    fun SceneScope.SmartSpace(modifier: Modifier = Modifier) {
+    fun SceneScope.SmartSpace(
+        burnInParams: BurnInParameters,
+        onTopChanged: (top: Float?) -> Unit,
+        modifier: Modifier = Modifier,
+    ) {
         Column(
-            modifier = modifier.element(SmartSpaceElementKey),
+            modifier = modifier.element(SmartSpaceElementKey).onTopPlacementChanged(onTopChanged),
         ) {
             if (!keyguardSmartspaceViewModel.isSmartspaceEnabled) {
                 return
@@ -71,9 +80,21 @@
                                 start = paddingBelowClockStart,
                             ),
                 ) {
-                    Date()
+                    Date(
+                        modifier =
+                            Modifier.burnInAware(
+                                viewModel = aodBurnInViewModel,
+                                params = burnInParams,
+                            ),
+                    )
                     Spacer(modifier = Modifier.width(4.dp))
-                    Weather()
+                    Weather(
+                        modifier =
+                            Modifier.burnInAware(
+                                viewModel = aodBurnInViewModel,
+                                params = burnInParams,
+                            ),
+                    )
                 }
             }
 
@@ -84,6 +105,10 @@
                             start = paddingBelowClockStart,
                             end = paddingBelowClockEnd,
                         )
+                        .burnInAware(
+                            viewModel = aodBurnInViewModel,
+                            params = burnInParams,
+                        ),
             )
         }
     }
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/StatusBarSection.kt b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/StatusBarSection.kt
index 6811eb4..ddc12ff 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/StatusBarSection.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/keyguard/ui/composable/section/StatusBarSection.kt
@@ -21,9 +21,11 @@
 import android.view.View
 import android.view.ViewGroup
 import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.padding
 import androidx.compose.runtime.Composable
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.platform.LocalContext
+import androidx.compose.ui.unit.dp
 import androidx.compose.ui.viewinterop.AndroidView
 import com.android.compose.animation.scene.ElementKey
 import com.android.compose.animation.scene.SceneScope
@@ -51,37 +53,43 @@
             key = StatusBarElementKey,
             modifier = modifier,
         ) {
-            AndroidView(
-                factory = {
-                    notificationPanelView.get().findViewById<View>(R.id.keyguard_header)?.let {
-                        (it.parent as ViewGroup).removeView(it)
-                    }
-
-                    val provider =
-                        object : ShadeViewStateProvider {
-                            override val lockscreenShadeDragProgress: Float = 0f
-                            override val panelViewExpandedHeight: Float = 0f
-                            override fun shouldHeadsUpBeVisible(): Boolean {
-                                return false
-                            }
+            content {
+                AndroidView(
+                    factory = {
+                        notificationPanelView.get().findViewById<View>(R.id.keyguard_header)?.let {
+                            (it.parent as ViewGroup).removeView(it)
                         }
 
-                    @SuppressLint("InflateParams")
-                    val view =
-                        LayoutInflater.from(context)
-                            .inflate(
-                                R.layout.keyguard_status_bar,
-                                null,
-                                false,
-                            ) as KeyguardStatusBarView
-                    componentFactory.build(view, provider).keyguardStatusBarViewController.init()
-                    view
-                },
-                modifier =
-                    Modifier.fillMaxWidth().height {
-                        Utils.getStatusBarHeaderHeightKeyguard(context)
+                        val provider =
+                            object : ShadeViewStateProvider {
+                                override val lockscreenShadeDragProgress: Float = 0f
+                                override val panelViewExpandedHeight: Float = 0f
+
+                                override fun shouldHeadsUpBeVisible(): Boolean {
+                                    return false
+                                }
+                            }
+
+                        @SuppressLint("InflateParams")
+                        val view =
+                            LayoutInflater.from(context)
+                                .inflate(
+                                    R.layout.keyguard_status_bar,
+                                    null,
+                                    false,
+                                ) as KeyguardStatusBarView
+                        componentFactory
+                            .build(view, provider)
+                            .keyguardStatusBarViewController
+                            .init()
+                        view
                     },
-            )
+                    modifier =
+                        Modifier.fillMaxWidth().padding(horizontal = 16.dp).height {
+                            Utils.getStatusBarHeaderHeightKeyguard(context)
+                        },
+                )
+            }
         }
     }
 }
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/Notifications.kt b/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/Notifications.kt
index 12f1b30..0eec024 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/Notifications.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/notifications/ui/composable/Notifications.kt
@@ -44,7 +44,7 @@
 import com.android.compose.animation.scene.ElementKey
 import com.android.compose.animation.scene.SceneScope
 import com.android.compose.animation.scene.ValueKey
-import com.android.compose.animation.scene.animateSharedFloatAsState
+import com.android.compose.animation.scene.animateElementFloatAsState
 import com.android.systemui.notifications.ui.composable.Notifications.Form
 import com.android.systemui.notifications.ui.composable.Notifications.SharedValues.SharedExpansionValue
 import com.android.systemui.statusbar.notification.stack.ui.viewmodel.NotificationsPlaceholderViewModel
@@ -157,10 +157,10 @@
     modifier: Modifier = Modifier,
 ) {
     val elementKey = Notifications.Elements.NotificationPlaceholder
-    Box(
+    Element(
+        elementKey,
         modifier =
             modifier
-                .element(elementKey)
                 .debugBackground(viewModel)
                 .onSizeChanged { size: IntSize ->
                     debugLog(viewModel) { "STACK onSizeChanged: size=$size" }
@@ -182,19 +182,23 @@
                 }
     ) {
         val animatedExpansion by
-            animateSharedFloatAsState(
+            animateElementFloatAsState(
                 value = if (form == Form.HunFromTop) 0f else 1f,
-                key = SharedExpansionValue,
-                element = elementKey
+                key = SharedExpansionValue
             )
         debugLog(viewModel) { "STACK composed: expansion=$animatedExpansion" }
-        if (viewModel.isPlaceholderTextVisible) {
-            Text(
-                text = "Notifications",
-                style = MaterialTheme.typography.titleLarge,
-                color = MaterialTheme.colorScheme.onSurface,
-                modifier = Modifier.align(Alignment.Center),
-            )
+
+        content {
+            if (viewModel.isPlaceholderTextVisible) {
+                Box(Modifier.fillMaxSize()) {
+                    Text(
+                        text = "Notifications",
+                        style = MaterialTheme.typography.titleLarge,
+                        color = MaterialTheme.colorScheme.onSurface,
+                        modifier = Modifier.align(Alignment.Center),
+                    )
+                }
+            }
         }
     }
 }
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettings.kt b/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettings.kt
index f3cde53..9778e53 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettings.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/qs/ui/composable/QuickSettings.kt
@@ -40,6 +40,7 @@
 import com.android.systemui.qs.ui.adapter.QSSceneAdapter.State.Expanding
 import com.android.systemui.res.R
 import com.android.systemui.scene.ui.composable.Gone
+import com.android.systemui.scene.ui.composable.Lockscreen
 import com.android.systemui.scene.ui.composable.QuickSettings as QuickSettingsSceneKey
 import com.android.systemui.scene.ui.composable.Shade
 
@@ -77,7 +78,12 @@
                     toScene == Shade -> QSSceneAdapter.State.QQS
                     toScene == QuickSettingsSceneKey -> QSSceneAdapter.State.QS
                     toScene == Gone -> QSSceneAdapter.State.CLOSED
-                    else -> error("Bad transition for QuickSettings: $transitionState")
+                    toScene == Lockscreen -> QSSceneAdapter.State.CLOSED
+                    else ->
+                        error(
+                            "Bad transition for QuickSettings: fromScene=$fromScene," +
+                                " toScene=$toScene"
+                        )
                 }
             }
     }
@@ -98,7 +104,7 @@
         key = QuickSettings.Elements.Content,
         modifier = modifier.fillMaxWidth().defaultMinSize(minHeight = 300.dp)
     ) {
-        QuickSettingsContent(qsSceneAdapter = qsSceneAdapter, contentState)
+        content { QuickSettingsContent(qsSceneAdapter = qsSceneAdapter, contentState) }
     }
 }
 
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeHeader.kt b/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeHeader.kt
index 4bbb78b..e2beaee 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeHeader.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/shade/ui/composable/ShadeHeader.kt
@@ -50,7 +50,7 @@
 import com.android.compose.animation.scene.ElementKey
 import com.android.compose.animation.scene.SceneScope
 import com.android.compose.animation.scene.ValueKey
-import com.android.compose.animation.scene.animateSharedFloatAsState
+import com.android.compose.animation.scene.animateSceneFloatAsState
 import com.android.compose.windowsizeclass.LocalWindowSizeClass
 import com.android.settingslib.Utils
 import com.android.systemui.battery.BatteryMeterView
@@ -69,7 +69,6 @@
 
 object ShadeHeader {
     object Elements {
-        val FormatPlaceholder = ElementKey("ShadeHeaderFormatPlaceholder")
         val ExpandedContent = ElementKey("ShadeHeaderExpandedContent")
         val CollapsedContent = ElementKey("ShadeHeaderCollapsedContent")
     }
@@ -92,19 +91,19 @@
     statusBarIconController: StatusBarIconController,
     modifier: Modifier = Modifier,
 ) {
-    // TODO(b/298153892): Remove this once animateSharedFloatAsState.element can be null.
-    Spacer(Modifier.element(ShadeHeader.Elements.FormatPlaceholder))
     val formatProgress =
-        animateSharedFloatAsState(
-            0.0f,
-            ShadeHeader.Keys.transitionProgress,
-            ShadeHeader.Elements.FormatPlaceholder
-        )
+        animateSceneFloatAsState(0f, ShadeHeader.Keys.transitionProgress)
+            .unsafeCompositionState(initialValue = 0f)
 
     val cutoutWidth = LocalDisplayCutout.current.width()
     val cutoutLocation = LocalDisplayCutout.current.location
 
-    val useExpandedFormat = formatProgress.value > 0.5f || cutoutLocation != CutoutLocation.CENTER
+    val useExpandedFormat by
+        remember(formatProgress) {
+            derivedStateOf {
+                cutoutLocation != CutoutLocation.CENTER || formatProgress.value > 0.5f
+            }
+        }
 
     // This layout assumes it is globally positioned at (0, 0) and is the
     // same size as the screen.
@@ -217,14 +216,9 @@
     statusBarIconController: StatusBarIconController,
     modifier: Modifier = Modifier,
 ) {
-    // TODO(b/298153892): Remove this once animateSharedFloatAsState.element can be null.
-    Spacer(Modifier.element(ShadeHeader.Elements.FormatPlaceholder))
     val formatProgress =
-        animateSharedFloatAsState(
-            1.0f,
-            ShadeHeader.Keys.transitionProgress,
-            ShadeHeader.Elements.FormatPlaceholder
-        )
+        animateSceneFloatAsState(1f, ShadeHeader.Keys.transitionProgress)
+            .unsafeCompositionState(initialValue = 1f)
     val useExpandedFormat by
         remember(formatProgress) { derivedStateOf { formatProgress.value > 0.5f } }
 
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/AnimateSharedAsState.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/AnimateSharedAsState.kt
index 2944bd9..b26194f 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/AnimateSharedAsState.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/AnimateSharedAsState.kt
@@ -17,10 +17,15 @@
 package com.android.compose.animation.scene
 
 import androidx.compose.runtime.Composable
+import androidx.compose.runtime.DisposableEffect
+import androidx.compose.runtime.LaunchedEffect
+import androidx.compose.runtime.SideEffect
+import androidx.compose.runtime.Stable
 import androidx.compose.runtime.State
-import androidx.compose.runtime.derivedStateOf
+import androidx.compose.runtime.mutableStateOf
 import androidx.compose.runtime.remember
-import androidx.compose.runtime.snapshots.Snapshot
+import androidx.compose.runtime.snapshotFlow
+import androidx.compose.runtime.snapshots.SnapshotStateMap
 import androidx.compose.ui.graphics.Color
 import androidx.compose.ui.graphics.lerp
 import androidx.compose.ui.unit.Dp
@@ -28,180 +33,263 @@
 import com.android.compose.ui.util.lerp
 
 /**
- * Animate a shared Int value.
+ * A [State] whose [value] is animated.
  *
- * @see SceneScope.animateSharedValueAsState
+ * Important: This animated value should always be ready *after* composition, e.g. during layout,
+ * drawing or inside a LaunchedEffect. If you read [value] during composition, it will probably
+ * throw an exception, for 2 important reasons:
+ * 1. You should never read animated values during composition, because this will probably lead to
+ *    bad performance.
+ * 2. Given that this value depends on the target value in different scenes, its current value
+ *    (depending on the current transition state) can only be computed once the full tree has been
+ *    composed.
+ *
+ * If you don't have the choice and *have to* get the value during composition, for instance because
+ * a Modifier or Composable reading this value does not have a lazy/lambda-based API, then you can
+ * access [unsafeCompositionState] and use a fallback value for the first frame where this animated
+ * value can not be computed yet. Note however that doing so will be bad for performance and might
+ * lead to late-by-one-frame flickers.
+ */
+@Stable
+interface AnimatedState<T> : State<T> {
+    /**
+     * Return a [State] that can be read during composition.
+     *
+     * Important: You should avoid using this as much as possible and instead read [value] during
+     * layout/drawing, otherwise you will probably end up with a few frames that have a value that
+     * is not correctly interpolated.
+     */
+    @Composable fun unsafeCompositionState(initialValue: T): State<T>
+}
+
+/**
+ * Animate a scene Int value.
+ *
+ * @see SceneScope.animateSceneValueAsState
  */
 @Composable
-fun SceneScope.animateSharedIntAsState(
+fun SceneScope.animateSceneIntAsState(
     value: Int,
     key: ValueKey,
-    element: ElementKey?,
     canOverflow: Boolean = true,
-): State<Int> {
-    return animateSharedValueAsState(value, key, element, ::lerp, canOverflow)
+): AnimatedState<Int> {
+    return animateSceneValueAsState(value, key, ::lerp, canOverflow)
 }
 
 /**
- * Animate a shared Int value.
+ * Animate a shared element Int value.
  *
- * @see MovableElementScope.animateSharedValueAsState
+ * @see ElementScope.animateElementValueAsState
  */
 @Composable
-fun MovableElementScope.animateSharedIntAsState(
+fun ElementScope<*>.animateElementIntAsState(
     value: Int,
-    debugName: String,
+    key: ValueKey,
     canOverflow: Boolean = true,
-): State<Int> {
-    return animateSharedValueAsState(value, debugName, ::lerp, canOverflow)
+): AnimatedState<Int> {
+    return animateElementValueAsState(value, key, ::lerp, canOverflow)
 }
 
 /**
- * Animate a shared Float value.
+ * Animate a scene Float value.
  *
- * @see SceneScope.animateSharedValueAsState
+ * @see SceneScope.animateSceneValueAsState
  */
 @Composable
-fun SceneScope.animateSharedFloatAsState(
+fun SceneScope.animateSceneFloatAsState(
     value: Float,
     key: ValueKey,
-    element: ElementKey?,
     canOverflow: Boolean = true,
-): State<Float> {
-    return animateSharedValueAsState(value, key, element, ::lerp, canOverflow)
+): AnimatedState<Float> {
+    return animateSceneValueAsState(value, key, ::lerp, canOverflow)
 }
 
 /**
- * Animate a shared Float value.
+ * Animate a shared element Float value.
  *
- * @see MovableElementScope.animateSharedValueAsState
+ * @see ElementScope.animateElementValueAsState
  */
 @Composable
-fun MovableElementScope.animateSharedFloatAsState(
+fun ElementScope<*>.animateElementFloatAsState(
     value: Float,
-    debugName: String,
+    key: ValueKey,
     canOverflow: Boolean = true,
-): State<Float> {
-    return animateSharedValueAsState(value, debugName, ::lerp, canOverflow)
+): AnimatedState<Float> {
+    return animateElementValueAsState(value, key, ::lerp, canOverflow)
 }
 
 /**
- * Animate a shared Dp value.
+ * Animate a scene Dp value.
  *
- * @see SceneScope.animateSharedValueAsState
+ * @see SceneScope.animateSceneValueAsState
  */
 @Composable
-fun SceneScope.animateSharedDpAsState(
+fun SceneScope.animateSceneDpAsState(
     value: Dp,
     key: ValueKey,
-    element: ElementKey?,
     canOverflow: Boolean = true,
-): State<Dp> {
-    return animateSharedValueAsState(value, key, element, ::lerp, canOverflow)
+): AnimatedState<Dp> {
+    return animateSceneValueAsState(value, key, ::lerp, canOverflow)
 }
 
 /**
- * Animate a shared Dp value.
+ * Animate a shared element Dp value.
  *
- * @see MovableElementScope.animateSharedValueAsState
+ * @see ElementScope.animateElementValueAsState
  */
 @Composable
-fun MovableElementScope.animateSharedDpAsState(
+fun ElementScope<*>.animateElementDpAsState(
     value: Dp,
-    debugName: String,
+    key: ValueKey,
     canOverflow: Boolean = true,
-): State<Dp> {
-    return animateSharedValueAsState(value, debugName, ::lerp, canOverflow)
+): AnimatedState<Dp> {
+    return animateElementValueAsState(value, key, ::lerp, canOverflow)
 }
 
 /**
- * Animate a shared Color value.
+ * Animate a scene Color value.
  *
- * @see SceneScope.animateSharedValueAsState
+ * @see SceneScope.animateSceneValueAsState
  */
 @Composable
-fun SceneScope.animateSharedColorAsState(
+fun SceneScope.animateSceneColorAsState(
     value: Color,
     key: ValueKey,
-    element: ElementKey?,
-): State<Color> {
-    return animateSharedValueAsState(value, key, element, ::lerp, canOverflow = false)
+): AnimatedState<Color> {
+    return animateSceneValueAsState(value, key, ::lerp, canOverflow = false)
 }
 
 /**
- * Animate a shared Color value.
+ * Animate a shared element Color value.
  *
- * @see MovableElementScope.animateSharedValueAsState
+ * @see ElementScope.animateElementValueAsState
  */
 @Composable
-fun MovableElementScope.animateSharedColorAsState(
+fun ElementScope<*>.animateElementColorAsState(
     value: Color,
-    debugName: String,
-): State<Color> {
-    return animateSharedValueAsState(value, debugName, ::lerp, canOverflow = false)
+    key: ValueKey,
+): AnimatedState<Color> {
+    return animateElementValueAsState(value, key, ::lerp, canOverflow = false)
 }
 
 @Composable
 internal fun <T> animateSharedValueAsState(
     layoutImpl: SceneTransitionLayoutImpl,
-    scene: Scene,
-    element: Element?,
+    scene: SceneKey,
+    element: ElementKey?,
     key: ValueKey,
     value: T,
     lerp: (T, T, Float) -> T,
     canOverflow: Boolean,
-): State<T> {
-    val sharedValue =
-        Snapshot.withoutReadObservation {
-            val sharedValues =
-                element?.sceneValues?.getValue(scene.key)?.sharedValues ?: scene.sharedValues
-            sharedValues.getOrPut(key) { Element.SharedValue(key, value) } as Element.SharedValue<T>
-        }
+): AnimatedState<T> {
+    DisposableEffect(layoutImpl, scene, element, key) {
+        // Create the associated maps that hold the current value for each (element, scene) pair.
+        val valueMap = layoutImpl.sharedValues.getOrPut(key) { mutableMapOf() }
+        val sceneToValueMap =
+            valueMap.getOrPut(element) { SnapshotStateMap<SceneKey, Any>() }
+                as SnapshotStateMap<SceneKey, T>
+        sceneToValueMap[scene] = value
 
-    if (value != sharedValue.value) {
-        sharedValue.value = value
+        onDispose {
+            // Remove the value associated to the current scene, and eventually remove the maps if
+            // they are empty.
+            sceneToValueMap.remove(scene)
+
+            if (sceneToValueMap.isEmpty() && valueMap[element] === sceneToValueMap) {
+                valueMap.remove(element)
+
+                if (valueMap.isEmpty() && layoutImpl.sharedValues[key] === valueMap) {
+                    layoutImpl.sharedValues.remove(key)
+                }
+            }
+        }
     }
 
-    return remember(layoutImpl, element, sharedValue, lerp, canOverflow) {
-        derivedStateOf { computeValue(layoutImpl, element, sharedValue, lerp, canOverflow) }
+    // Update the current value. Note that side effects run after disposable effects, so we know
+    // that the associated maps were created at this point.
+    SideEffect { sceneToValueMap<T>(layoutImpl, key, element)[scene] = value }
+
+    return remember(layoutImpl, scene, element, lerp, canOverflow) {
+        object : AnimatedState<T> {
+            override val value: T
+                get() = value(layoutImpl, scene, element, key, lerp, canOverflow)
+
+            @Composable
+            override fun unsafeCompositionState(initialValue: T): State<T> {
+                val state = remember { mutableStateOf(initialValue) }
+
+                val animatedState = this
+                LaunchedEffect(animatedState) {
+                    snapshotFlow { animatedState.value }.collect { state.value = it }
+                }
+
+                return state
+            }
+        }
     }
 }
 
-private fun <T> computeValue(
+private fun <T> sceneToValueMap(
     layoutImpl: SceneTransitionLayoutImpl,
-    element: Element?,
-    sharedValue: Element.SharedValue<T>,
+    key: ValueKey,
+    element: ElementKey?
+): MutableMap<SceneKey, T> {
+    return layoutImpl.sharedValues[key]?.get(element)?.let { it as SnapshotStateMap<SceneKey, T> }
+        ?: error(valueReadTooEarlyMessage(key))
+}
+
+private fun valueReadTooEarlyMessage(key: ValueKey) =
+    "Animated value $key was read before its target values were set. This probably " +
+        "means that you are reading it during composition, which you should not do. See the " +
+        "documentation of AnimatedState for more information."
+
+private fun <T> value(
+    layoutImpl: SceneTransitionLayoutImpl,
+    scene: SceneKey,
+    element: ElementKey?,
+    key: ValueKey,
     lerp: (T, T, Float) -> T,
     canOverflow: Boolean,
 ): T {
-    val transition = layoutImpl.state.currentTransition
-    if (transition == null || !layoutImpl.isTransitionReady(transition)) {
-        return sharedValue.value
-    }
+    return valueOrNull(layoutImpl, scene, element, key, lerp, canOverflow)
+        ?: error(valueReadTooEarlyMessage(key))
+}
 
-    fun sceneValue(scene: SceneKey): Element.SharedValue<T>? {
-        val sharedValues =
-            if (element == null) {
-                layoutImpl.scene(scene).sharedValues
-            } else {
-                element.sceneValues[scene]?.sharedValues
-            }
-                ?: return null
-        val value = sharedValues[sharedValue.key] ?: return null
-        return value as Element.SharedValue<T>
-    }
+private fun <T> valueOrNull(
+    layoutImpl: SceneTransitionLayoutImpl,
+    scene: SceneKey,
+    element: ElementKey?,
+    key: ValueKey,
+    lerp: (T, T, Float) -> T,
+    canOverflow: Boolean,
+): T? {
+    val sceneToValueMap = sceneToValueMap<T>(layoutImpl, key, element)
+    fun sceneValue(scene: SceneKey): T? = sceneToValueMap[scene]
 
-    val fromValue = sceneValue(transition.fromScene)
-    val toValue = sceneValue(transition.toScene)
-    return if (fromValue != null && toValue != null) {
-        val progress =
-            if (canOverflow) transition.progress else transition.progress.coerceIn(0f, 1f)
-        lerp(fromValue.value, toValue.value, progress)
-    } else if (fromValue != null) {
-        fromValue.value
-    } else if (toValue != null) {
-        toValue.value
-    } else {
-        sharedValue.value
+    return when (val transition = layoutImpl.state.transitionState) {
+        is TransitionState.Idle -> sceneValue(transition.currentScene)
+        is TransitionState.Transition -> {
+            // Note: no need to check for transition ready here given that all target values are
+            // defined during composition, we should already have the correct values to interpolate
+            // between here.
+            val fromValue = sceneValue(transition.fromScene)
+            val toValue = sceneValue(transition.toScene)
+            if (fromValue != null && toValue != null) {
+                if (fromValue == toValue) {
+                    // Optimization: avoid reading progress if the values are the same, so we don't
+                    // relayout/redraw for nothing.
+                    fromValue
+                } else {
+                    val progress =
+                        if (canOverflow) transition.progress
+                        else transition.progress.coerceIn(0f, 1f)
+                    lerp(fromValue, toValue, progress)
+                }
+            } else fromValue ?: toValue
+        }
     }
+    // TODO(b/311600838): Remove this. We should not have to fallback to the current scene value,
+    // but we have to because code of removed nodes can still run if they are placed with a graphics
+    // layer.
+    ?: sceneValue(scene)
 }
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Element.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Element.kt
index a85d9bf..280fbfb 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Element.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Element.kt
@@ -16,15 +16,10 @@
 
 package com.android.compose.animation.scene
 
-import android.graphics.Picture
-import androidx.compose.runtime.Composable
 import androidx.compose.runtime.Stable
 import androidx.compose.runtime.getValue
-import androidx.compose.runtime.movableContentOf
 import androidx.compose.runtime.mutableStateOf
 import androidx.compose.runtime.setValue
-import androidx.compose.runtime.snapshots.Snapshot
-import androidx.compose.runtime.snapshots.SnapshotStateMap
 import androidx.compose.ui.ExperimentalComposeUiApi
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.geometry.Offset
@@ -52,43 +47,22 @@
 @Stable
 internal class Element(val key: ElementKey) {
     /**
-     * The last values of this element, coming from any scene. Note that this value will be unstable
+     * The last state of this element, coming from any scene. Note that this state will be unstable
      * if this element is present in multiple scenes but the shared element animation is disabled,
-     * given that multiple instances of the element with different states will write to these
-     * values. You should prefer using [TargetValues.lastValues] in the current scene if it is
-     * defined.
+     * given that multiple instances of the element with different states will write to this state.
+     * You should prefer using [SceneState.lastState] in the current scene when it is defined.
      */
-    val lastSharedValues = Values()
+    val lastSharedState = State()
 
-    /** The mapping between a scene and the values/state this element has in that scene, if any. */
-    val sceneValues = SnapshotStateMap<SceneKey, TargetValues>()
-
-    /**
-     * The movable content of this element, if this element is composed using
-     * [SceneScope.MovableElement].
-     */
-    private var _movableContent: (@Composable (@Composable () -> Unit) -> Unit)? = null
-    val movableContent: @Composable (@Composable () -> Unit) -> Unit
-        get() =
-            _movableContent
-                ?: movableContentOf { content: @Composable () -> Unit -> content() }
-                    .also { _movableContent = it }
-
-    /**
-     * The [Picture] to which we save the last drawing commands of this element, if it is movable.
-     * This is necessary because the content of this element might not be composed in the scene it
-     * should currently be drawn.
-     */
-    private var _picture: Picture? = null
-    val picture: Picture
-        get() = _picture ?: Picture().also { _picture = it }
+    /** The mapping between a scene and the state this element has in that scene, if any. */
+    val sceneStates = mutableMapOf<SceneKey, SceneState>()
 
     override fun toString(): String {
         return "Element(key=$key)"
     }
 
-    /** The current values of this element, either in a specific scene or in a shared context. */
-    class Values {
+    /** The state of this element, either in a specific scene or in a shared context. */
+    class State {
         /** The offset of the element, relative to the SceneTransitionLayout containing it. */
         var offset = Offset.Unspecified
 
@@ -102,16 +76,14 @@
         var alpha = AlphaUnspecified
     }
 
-    /** The target values of this element in a given scene. */
+    /** The last and target state of this element in a given scene. */
     @Stable
-    class TargetValues(val scene: SceneKey) {
-        val lastValues = Values()
+    class SceneState(val scene: SceneKey) {
+        val lastState = State()
 
         var targetSize by mutableStateOf(SizeUnspecified)
         var targetOffset by mutableStateOf(Offset.Unspecified)
 
-        val sharedValues = SnapshotStateMap<ValueKey, SharedValue<*>>()
-
         /**
          * The attached [ElementNode] a Modifier.element() for a given element and scene. During
          * composition, this set could have 0 to 2 elements. After composition and after all
@@ -120,12 +92,6 @@
         val nodes = mutableSetOf<ElementNode>()
     }
 
-    /** A shared value of this element. */
-    @Stable
-    class SharedValue<T>(val key: ValueKey, initialValue: T) {
-        var value by mutableStateOf(initialValue)
-    }
-
     companion object {
         val SizeUnspecified = IntSize(Int.MAX_VALUE, Int.MAX_VALUE)
         val AlphaUnspecified = Float.MIN_VALUE
@@ -147,27 +113,18 @@
     scene: Scene,
     key: ElementKey,
 ): Modifier {
-    val element: Element
-    val sceneValues: Element.TargetValues
-
-    // Get the element associated to [key] if it was already composed in another scene,
-    // otherwise create it and add it to our Map<ElementKey, Element>. This is done inside a
-    // withoutReadObservation() because there is no need to recompose when that map is mutated.
-    Snapshot.withoutReadObservation {
-        element = layoutImpl.elements[key] ?: Element(key).also { layoutImpl.elements[key] = it }
-        sceneValues =
-            element.sceneValues[scene.key]
-                ?: Element.TargetValues(scene.key).also { element.sceneValues[scene.key] = it }
-    }
-
-    return this.then(ElementModifier(layoutImpl, scene, element, sceneValues))
+    return this.then(ElementModifier(layoutImpl, scene, key))
         // TODO(b/311132415): Move this into ElementNode once we can create a delegate
         // IntermediateLayoutModifierNode.
         .intermediateLayout { measurable, constraints ->
-            val placeable =
-                measure(layoutImpl, scene, element, sceneValues, measurable, constraints)
+            // TODO(b/311132415): No need to fetch the element and sceneState from the map anymore
+            // once this is merged into ElementNode.
+            val element = layoutImpl.elements.getValue(key)
+            val sceneState = element.sceneStates.getValue(scene.key)
+
+            val placeable = measure(layoutImpl, scene, element, sceneState, measurable, constraints)
             layout(placeable.width, placeable.height) {
-                place(layoutImpl, scene, element, sceneValues, placeable, placementScope = this)
+                place(layoutImpl, scene, element, sceneState, placeable, placementScope = this)
             }
         }
         .testTag(key.testTag)
@@ -180,72 +137,89 @@
 private data class ElementModifier(
     private val layoutImpl: SceneTransitionLayoutImpl,
     private val scene: Scene,
-    private val element: Element,
-    private val sceneValues: Element.TargetValues,
+    private val key: ElementKey,
 ) : ModifierNodeElement<ElementNode>() {
-    override fun create(): ElementNode = ElementNode(layoutImpl, scene, element, sceneValues)
+    override fun create(): ElementNode = ElementNode(layoutImpl, scene, key)
 
     override fun update(node: ElementNode) {
-        node.update(layoutImpl, scene, element, sceneValues)
+        node.update(layoutImpl, scene, key)
     }
 }
 
 internal class ElementNode(
     private var layoutImpl: SceneTransitionLayoutImpl,
     private var scene: Scene,
-    private var element: Element,
-    private var sceneValues: Element.TargetValues,
+    private var key: ElementKey,
 ) : Modifier.Node(), DrawModifierNode {
+    private var _element: Element? = null
+    private val element: Element
+        get() = _element!!
+
+    private var _sceneState: Element.SceneState? = null
+    private val sceneState: Element.SceneState
+        get() = _sceneState!!
 
     override fun onAttach() {
         super.onAttach()
-        addNodeToSceneValues()
+        updateElementAndSceneValues()
+        addNodeToSceneState()
     }
 
-    private fun addNodeToSceneValues() {
-        sceneValues.nodes.add(this)
+    private fun updateElementAndSceneValues() {
+        val element =
+            layoutImpl.elements[key] ?: Element(key).also { layoutImpl.elements[key] = it }
+        _element = element
+        _sceneState =
+            element.sceneStates[scene.key]
+                ?: Element.SceneState(scene.key).also { element.sceneStates[scene.key] = it }
+    }
+
+    private fun addNodeToSceneState() {
+        sceneState.nodes.add(this)
 
         coroutineScope.launch {
             // At this point all [CodeLocationNode] have been attached or detached, which means that
-            // [sceneValues.codeLocations] should have exactly 1 element, otherwise this means that
+            // [sceneState.codeLocations] should have exactly 1 element, otherwise this means that
             // this element was composed multiple times in the same scene.
-            val nCodeLocations = sceneValues.nodes.size
-            if (nCodeLocations != 1 || !sceneValues.nodes.contains(this@ElementNode)) {
-                error("${element.key} was composed $nCodeLocations times in ${sceneValues.scene}")
+            val nCodeLocations = sceneState.nodes.size
+            if (nCodeLocations != 1 || !sceneState.nodes.contains(this@ElementNode)) {
+                error("$key was composed $nCodeLocations times in ${sceneState.scene}")
             }
         }
     }
 
     override fun onDetach() {
         super.onDetach()
-        removeNodeFromSceneValues()
-        maybePruneMaps(layoutImpl, element, sceneValues)
+        removeNodeFromSceneState()
+        maybePruneMaps(layoutImpl, element, sceneState)
+
+        _element = null
+        _sceneState = null
     }
 
-    private fun removeNodeFromSceneValues() {
-        sceneValues.nodes.remove(this)
+    private fun removeNodeFromSceneState() {
+        sceneState.nodes.remove(this)
     }
 
     fun update(
         layoutImpl: SceneTransitionLayoutImpl,
         scene: Scene,
-        element: Element,
-        sceneValues: Element.TargetValues,
+        key: ElementKey,
     ) {
         check(layoutImpl == this.layoutImpl && scene == this.scene)
-        removeNodeFromSceneValues()
+        removeNodeFromSceneState()
 
         val prevElement = this.element
-        val prevSceneValues = this.sceneValues
-        this.element = element
-        this.sceneValues = sceneValues
+        val prevSceneState = this.sceneState
+        this.key = key
+        updateElementAndSceneValues()
 
-        addNodeToSceneValues()
-        maybePruneMaps(layoutImpl, prevElement, prevSceneValues)
+        addNodeToSceneState()
+        maybePruneMaps(layoutImpl, prevElement, prevSceneState)
     }
 
     override fun ContentDrawScope.draw() {
-        val drawScale = getDrawScale(layoutImpl, element, scene, sceneValues)
+        val drawScale = getDrawScale(layoutImpl, element, scene, sceneState)
         if (drawScale == Scale.Default) {
             drawContent()
         } else {
@@ -263,18 +237,16 @@
         private fun maybePruneMaps(
             layoutImpl: SceneTransitionLayoutImpl,
             element: Element,
-            sceneValues: Element.TargetValues,
+            sceneState: Element.SceneState,
         ) {
             // If element is not composed from this scene anymore, remove the scene values. This
             // works because [onAttach] is called before [onDetach], so if an element is moved from
             // the UI tree we will first add the new code location then remove the old one.
-            if (
-                sceneValues.nodes.isEmpty() && element.sceneValues[sceneValues.scene] == sceneValues
-            ) {
-                element.sceneValues.remove(sceneValues.scene)
+            if (sceneState.nodes.isEmpty() && element.sceneStates[sceneState.scene] == sceneState) {
+                element.sceneStates.remove(sceneState.scene)
 
                 // If the element is not composed in any scene, remove it from the elements map.
-                if (element.sceneValues.isEmpty() && layoutImpl.elements[element.key] == element) {
+                if (element.sceneStates.isEmpty() && layoutImpl.elements[element.key] == element) {
                     layoutImpl.elements.remove(element.key)
                 }
             }
@@ -293,8 +265,8 @@
     if (
         transition == null ||
             !layoutImpl.isTransitionReady(transition) ||
-            transition.fromScene !in element.sceneValues ||
-            transition.toScene !in element.sceneValues
+            transition.fromScene !in element.sceneStates ||
+            transition.toScene !in element.sceneStates
     ) {
         return true
     }
@@ -310,7 +282,6 @@
         transition,
         scene.key,
         element.key,
-        sharedTransformation,
     )
 }
 
@@ -319,17 +290,14 @@
     transition: TransitionState.Transition,
     scene: SceneKey,
     element: ElementKey,
-    sharedTransformation: SharedElementTransformation?
 ): Boolean {
-    val scenePicker = sharedTransformation?.scenePicker ?: DefaultSharedElementScenePicker
+    val scenePicker = element.scenePicker
     val fromScene = transition.fromScene
     val toScene = transition.toScene
 
     return scenePicker.sceneDuringTransition(
         element = element,
-        fromScene = fromScene,
-        toScene = toScene,
-        progress = transition::progress,
+        transition = transition,
         fromSceneZIndex = layoutImpl.scenes.getValue(fromScene).zIndex,
         toSceneZIndex = layoutImpl.scenes.getValue(toScene).zIndex,
     ) == scene
@@ -374,28 +342,28 @@
     layoutImpl: SceneTransitionLayoutImpl,
     element: Element,
     scene: Scene,
-    sceneValues: Element.TargetValues,
+    sceneState: Element.SceneState,
 ): Boolean {
     val transition = layoutImpl.state.currentTransition ?: return true
 
     if (!layoutImpl.isTransitionReady(transition)) {
         val lastValue =
-            sceneValues.lastValues.alpha.takeIf { it != Element.AlphaUnspecified }
-                ?: element.lastSharedValues.alpha.takeIf { it != Element.AlphaUnspecified } ?: 1f
+            sceneState.lastState.alpha.takeIf { it != Element.AlphaUnspecified }
+                ?: element.lastSharedState.alpha.takeIf { it != Element.AlphaUnspecified } ?: 1f
 
         return lastValue == 1f
     }
 
     val fromScene = transition.fromScene
     val toScene = transition.toScene
-    val fromValues = element.sceneValues[fromScene]
-    val toValues = element.sceneValues[toScene]
+    val fromState = element.sceneStates[fromScene]
+    val toState = element.sceneStates[toScene]
 
-    if (fromValues == null && toValues == null) {
+    if (fromState == null && toState == null) {
         error("This should not happen, element $element is neither in $fromScene or $toScene")
     }
 
-    val isSharedElement = fromValues != null && toValues != null
+    val isSharedElement = fromState != null && toState != null
     if (isSharedElement && isSharedElementEnabled(layoutImpl.state, transition, element.key)) {
         return true
     }
@@ -415,7 +383,7 @@
     layoutImpl: SceneTransitionLayoutImpl,
     element: Element,
     scene: Scene,
-    sceneValues: Element.TargetValues,
+    sceneState: Element.SceneState,
 ): Float {
     return computeValue(
             layoutImpl,
@@ -426,9 +394,8 @@
             idleValue = 1f,
             currentValue = { 1f },
             lastValue = {
-                sceneValues.lastValues.alpha.takeIf { it != Element.AlphaUnspecified }
-                    ?: element.lastSharedValues.alpha.takeIf { it != Element.AlphaUnspecified }
-                        ?: 1f
+                sceneState.lastState.alpha.takeIf { it != Element.AlphaUnspecified }
+                    ?: element.lastSharedState.alpha.takeIf { it != Element.AlphaUnspecified } ?: 1f
             },
             ::lerp,
         )
@@ -440,15 +407,15 @@
     layoutImpl: SceneTransitionLayoutImpl,
     scene: Scene,
     element: Element,
-    sceneValues: Element.TargetValues,
+    sceneState: Element.SceneState,
     measurable: Measurable,
     constraints: Constraints,
 ): Placeable {
     // Update the size this element has in this scene when idle.
     val targetSizeInScene = lookaheadSize
-    if (targetSizeInScene != sceneValues.targetSize) {
+    if (targetSizeInScene != sceneState.targetSize) {
         // TODO(b/290930950): Better handle when this changes to avoid instant size jumps.
-        sceneValues.targetSize = targetSizeInScene
+        sceneState.targetSize = targetSizeInScene
     }
 
     // Some lambdas called (max once) by computeValue() will need to measure [measurable], in which
@@ -468,8 +435,8 @@
             idleValue = lookaheadSize,
             currentValue = { measurable.measure(constraints).also { maybePlaceable = it }.size() },
             lastValue = {
-                sceneValues.lastValues.size.takeIf { it != Element.SizeUnspecified }
-                    ?: element.lastSharedValues.size.takeIf { it != Element.SizeUnspecified }
+                sceneState.lastState.size.takeIf { it != Element.SizeUnspecified }
+                    ?: element.lastSharedState.size.takeIf { it != Element.SizeUnspecified }
                         ?: measurable.measure(constraints).also { maybePlaceable = it }.size()
             },
             ::lerp,
@@ -485,8 +452,8 @@
             )
 
     val size = placeable.size()
-    element.lastSharedValues.size = size
-    sceneValues.lastValues.size = size
+    element.lastSharedState.size = size
+    sceneState.lastState.size = size
     return placeable
 }
 
@@ -494,7 +461,7 @@
     layoutImpl: SceneTransitionLayoutImpl,
     element: Element,
     scene: Scene,
-    sceneValues: Element.TargetValues
+    sceneState: Element.SceneState
 ): Scale {
     return computeValue(
         layoutImpl,
@@ -505,8 +472,8 @@
         idleValue = Scale.Default,
         currentValue = { Scale.Default },
         lastValue = {
-            sceneValues.lastValues.drawScale.takeIf { it != Scale.Default }
-                ?: element.lastSharedValues.drawScale
+            sceneState.lastState.drawScale.takeIf { it != Scale.Default }
+                ?: element.lastSharedState.drawScale
         },
         ::lerp,
     )
@@ -517,7 +484,7 @@
     layoutImpl: SceneTransitionLayoutImpl,
     scene: Scene,
     element: Element,
-    sceneValues: Element.TargetValues,
+    sceneState: Element.SceneState,
     placeable: Placeable,
     placementScope: Placeable.PlacementScope,
 ) {
@@ -526,14 +493,14 @@
         // when idle.
         val coords = coordinates ?: error("Element ${element.key} does not have any coordinates")
         val targetOffsetInScene = lookaheadScopeCoordinates.localLookaheadPositionOf(coords)
-        if (targetOffsetInScene != sceneValues.targetOffset) {
+        if (targetOffsetInScene != sceneState.targetOffset) {
             // TODO(b/290930950): Better handle when this changes to avoid instant offset jumps.
-            sceneValues.targetOffset = targetOffsetInScene
+            sceneState.targetOffset = targetOffsetInScene
         }
 
         val currentOffset = lookaheadScopeCoordinates.localPositionOf(coords, Offset.Zero)
-        val lastSharedValues = element.lastSharedValues
-        val lastValues = sceneValues.lastValues
+        val lastSharedState = element.lastSharedState
+        val lastSceneState = sceneState.lastState
         val targetOffset =
             computeValue(
                 layoutImpl,
@@ -544,36 +511,36 @@
                 idleValue = targetOffsetInScene,
                 currentValue = { currentOffset },
                 lastValue = {
-                    lastValues.offset.takeIf { it.isSpecified }
-                        ?: lastSharedValues.offset.takeIf { it.isSpecified } ?: currentOffset
+                    lastSceneState.offset.takeIf { it.isSpecified }
+                        ?: lastSharedState.offset.takeIf { it.isSpecified } ?: currentOffset
                 },
                 ::lerp,
             )
 
-        lastSharedValues.offset = targetOffset
-        lastValues.offset = targetOffset
+        lastSharedState.offset = targetOffset
+        lastSceneState.offset = targetOffset
 
         // No need to place the element in this scene if we don't want to draw it anyways. Note that
-        // it's still important to compute the target offset and update lastValues, otherwise it
-        // will be out of date.
+        // it's still important to compute the target offset and update last(Shared|Scene)State,
+        // otherwise they will be out of date.
         if (!shouldDrawElement(layoutImpl, scene, element)) {
             return
         }
 
         val offset = (targetOffset - currentOffset).round()
-        if (isElementOpaque(layoutImpl, element, scene, sceneValues)) {
+        if (isElementOpaque(layoutImpl, element, scene, sceneState)) {
             // TODO(b/291071158): Call placeWithLayer() if offset != IntOffset.Zero and size is not
             // animated once b/305195729 is fixed. Test that drawing is not invalidated in that
             // case.
             placeable.place(offset)
-            lastSharedValues.alpha = 1f
-            lastValues.alpha = 1f
+            lastSharedState.alpha = 1f
+            lastSceneState.alpha = 1f
         } else {
             placeable.placeWithLayer(offset) {
-                val alpha = elementAlpha(layoutImpl, element, scene, sceneValues)
+                val alpha = elementAlpha(layoutImpl, element, scene, sceneState)
                 this.alpha = alpha
-                lastSharedValues.alpha = alpha
-                lastValues.alpha = alpha
+                lastSharedState.alpha = alpha
+                lastSceneState.alpha = alpha
             }
         }
     }
@@ -605,7 +572,7 @@
     layoutImpl: SceneTransitionLayoutImpl,
     scene: Scene,
     element: Element,
-    sceneValue: (Element.TargetValues) -> T,
+    sceneValue: (Element.SceneState) -> T,
     transformation: (ElementTransformations) -> PropertyTransformation<T>?,
     idleValue: T,
     currentValue: () -> T,
@@ -628,10 +595,10 @@
 
     val fromScene = transition.fromScene
     val toScene = transition.toScene
-    val fromValues = element.sceneValues[fromScene]
-    val toValues = element.sceneValues[toScene]
+    val fromState = element.sceneStates[fromScene]
+    val toState = element.sceneStates[toScene]
 
-    if (fromValues == null && toValues == null) {
+    if (fromState == null && toState == null) {
         // TODO(b/311600838): Throw an exception instead once layers of disposed elements are not
         // run anymore.
         return lastValue()
@@ -640,10 +607,10 @@
     // The element is shared: interpolate between the value in fromScene and the value in toScene.
     // TODO(b/290184746): Support non linear shared paths as well as a way to make sure that shared
     // elements follow the finger direction.
-    val isSharedElement = fromValues != null && toValues != null
+    val isSharedElement = fromState != null && toState != null
     if (isSharedElement && isSharedElementEnabled(layoutImpl.state, transition, element.key)) {
-        val start = sceneValue(fromValues!!)
-        val end = sceneValue(toValues!!)
+        val start = sceneValue(fromState!!)
+        val end = sceneValue(toState!!)
 
         // Make sure we don't read progress if values are the same and we don't need to interpolate,
         // so we don't invalidate the phase where this is read.
@@ -659,12 +626,12 @@
 
     // Get the transformed value, i.e. the target value at the beginning (for entering elements) or
     // end (for leaving elements) of the transition.
-    val sceneValues =
+    val sceneState =
         checkNotNull(
             when {
-                isSharedElement && scene.key == fromScene -> fromValues
-                isSharedElement -> toValues
-                else -> fromValues ?: toValues
+                isSharedElement && scene.key == fromScene -> fromState
+                isSharedElement -> toState
+                else -> fromState ?: toState
             }
         )
 
@@ -673,7 +640,7 @@
             layoutImpl,
             scene,
             element,
-            sceneValues,
+            sceneState,
             transition,
             idleValue,
         )
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Key.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Key.kt
index 84d3b86..90f46bd 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Key.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Key.kt
@@ -64,10 +64,10 @@
     identity: Any = Object(),
 
     /**
-     * Whether this element is a background and usually drawn below other elements. This should be
-     * set to true to make sure that shared backgrounds are drawn below elements of other scenes.
+     * The [ElementScenePicker] to use when deciding in which scene we should draw shared Elements
+     * or compose MovableElements.
      */
-    val isBackground: Boolean = false,
+    val scenePicker: ElementScenePicker = DefaultElementScenePicker,
 ) : Key(name, identity), ElementMatcher {
     @VisibleForTesting
     // TODO(b/240432457): Make internal once PlatformComposeSceneTransitionLayoutTestsUtils can
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/MovableElement.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/MovableElement.kt
index 49df2f6..af3c099 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/MovableElement.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/MovableElement.kt
@@ -16,27 +16,36 @@
 
 package com.android.compose.animation.scene
 
-import android.util.Log
 import androidx.compose.foundation.layout.Box
-import androidx.compose.foundation.layout.Spacer
+import androidx.compose.foundation.layout.BoxScope
 import androidx.compose.runtime.Composable
-import androidx.compose.runtime.State
 import androidx.compose.runtime.derivedStateOf
 import androidx.compose.runtime.getValue
+import androidx.compose.runtime.movableContentOf
 import androidx.compose.runtime.remember
-import androidx.compose.runtime.snapshots.Snapshot
 import androidx.compose.ui.Modifier
-import androidx.compose.ui.draw.drawBehind
-import androidx.compose.ui.draw.drawWithCache
-import androidx.compose.ui.graphics.Canvas
-import androidx.compose.ui.graphics.drawscope.draw
-import androidx.compose.ui.graphics.drawscope.drawIntoCanvas
-import androidx.compose.ui.graphics.nativeCanvas
-import androidx.compose.ui.layout.layout
-import androidx.compose.ui.unit.Constraints
+import androidx.compose.ui.layout.Layout
 import androidx.compose.ui.unit.IntSize
 
-private const val TAG = "MovableElement"
+@Composable
+internal fun Element(
+    layoutImpl: SceneTransitionLayoutImpl,
+    scene: Scene,
+    key: ElementKey,
+    modifier: Modifier,
+    content: @Composable ElementScope<ElementContentScope>.() -> Unit,
+) {
+    Box(modifier.element(layoutImpl, scene, key)) {
+        val sceneScope = scene.scope
+        val boxScope = this
+        val elementScope =
+            remember(layoutImpl, key, scene, sceneScope, boxScope) {
+                ElementScopeImpl(layoutImpl, key, scene, sceneScope, boxScope)
+            }
+
+        content(elementScope)
+    }
+}
 
 @Composable
 internal fun MovableElement(
@@ -44,72 +53,113 @@
     scene: Scene,
     key: ElementKey,
     modifier: Modifier,
-    content: @Composable MovableElementScope.() -> Unit,
+    content: @Composable ElementScope<MovableElementContentScope>.() -> Unit,
 ) {
     Box(modifier.element(layoutImpl, scene, key)) {
-        // Get the Element from the map. It will always be the same and we don't want to recompose
-        // every time an element is added/removed from SceneTransitionLayoutImpl.elements, so we
-        // disable read observation during the look-up in that map.
-        val element = Snapshot.withoutReadObservation { layoutImpl.elements.getValue(key) }
-        val movableElementScope =
-            remember(layoutImpl, element, scene) {
-                MovableElementScopeImpl(layoutImpl, element, scene)
+        val sceneScope = scene.scope
+        val boxScope = this
+        val elementScope =
+            remember(layoutImpl, key, scene, sceneScope, boxScope) {
+                MovableElementScopeImpl(layoutImpl, key, scene, sceneScope, boxScope)
             }
 
-        // The [Picture] to which we save the last drawing commands of this element. This is
-        // necessary because the content of this element might not be composed in this scene, in
-        // which case we still need to draw it.
-        val picture = element.picture
+        content(elementScope)
+    }
+}
 
+private abstract class BaseElementScope<ContentScope>(
+    private val layoutImpl: SceneTransitionLayoutImpl,
+    private val element: ElementKey,
+    private val scene: Scene,
+) : ElementScope<ContentScope> {
+    @Composable
+    override fun <T> animateElementValueAsState(
+        value: T,
+        key: ValueKey,
+        lerp: (start: T, stop: T, fraction: Float) -> T,
+        canOverflow: Boolean
+    ): AnimatedState<T> {
+        return animateSharedValueAsState(
+            layoutImpl,
+            scene.key,
+            element,
+            key,
+            value,
+            lerp,
+            canOverflow,
+        )
+    }
+}
+
+private class ElementScopeImpl(
+    layoutImpl: SceneTransitionLayoutImpl,
+    element: ElementKey,
+    scene: Scene,
+    private val sceneScope: SceneScope,
+    private val boxScope: BoxScope,
+) : BaseElementScope<ElementContentScope>(layoutImpl, element, scene) {
+    private val contentScope =
+        object : ElementContentScope, SceneScope by sceneScope, BoxScope by boxScope {}
+
+    @Composable
+    override fun content(content: @Composable ElementContentScope.() -> Unit) {
+        contentScope.content()
+    }
+}
+
+private class MovableElementScopeImpl(
+    private val layoutImpl: SceneTransitionLayoutImpl,
+    private val element: ElementKey,
+    private val scene: Scene,
+    private val sceneScope: BaseSceneScope,
+    private val boxScope: BoxScope,
+) : BaseElementScope<MovableElementContentScope>(layoutImpl, element, scene) {
+    private val contentScope =
+        object : MovableElementContentScope, BaseSceneScope by sceneScope, BoxScope by boxScope {}
+
+    @Composable
+    override fun content(content: @Composable MovableElementContentScope.() -> Unit) {
         // Whether we should compose the movable element here. The scene picker logic to know in
         // which scene we should compose/draw a movable element might depend on the current
         // transition progress, so we put this in a derivedStateOf to prevent many recompositions
         // during the transition.
+        // TODO(b/317026105): Use derivedStateOf only if the scene picker reads the progress in its
+        // logic.
         val shouldComposeMovableElement by
             remember(layoutImpl, scene.key, element) {
                 derivedStateOf { shouldComposeMovableElement(layoutImpl, scene.key, element) }
             }
 
         if (shouldComposeMovableElement) {
-            Box(
-                Modifier.drawWithCache {
-                    val width = size.width.toInt()
-                    val height = size.height.toInt()
-
-                    onDrawWithContent {
-                        // Save the draw commands into [picture] for later to draw the last content
-                        // even when this movable content is not composed.
-                        val pictureCanvas = Canvas(picture.beginRecording(width, height))
-                        draw(this, this.layoutDirection, pictureCanvas, this.size) {
-                            this@onDrawWithContent.drawContent()
+            val movableContent: MovableElementContent =
+                layoutImpl.movableContents[element]
+                    ?: movableContentOf {
+                            contentScope: MovableElementContentScope,
+                            content: @Composable MovableElementContentScope.() -> Unit ->
+                            contentScope.content()
                         }
-                        picture.endRecording()
+                        .also { layoutImpl.movableContents[element] = it }
 
-                        // Draw the content.
-                        drawIntoCanvas { canvas -> canvas.nativeCanvas.drawPicture(picture) }
-                    }
-                }
-            ) {
-                element.movableContent { movableElementScope.content() }
-            }
+            // Important: Don't introduce any parent Box or other layout here, because contentScope
+            // delegates its BoxScope implementation to the Box where this content() function is
+            // called, so it's important that this movableContent is composed directly under that
+            // Box.
+            movableContent(contentScope, content)
         } else {
-            // If we are not composed, we draw the previous drawing commands at the same size as the
-            // movable content when it was composed in this scene.
-            val sceneValues = element.sceneValues.getValue(scene.key)
-
-            Spacer(
-                Modifier.layout { measurable, _ ->
-                        val size =
-                            sceneValues.targetSize.takeIf { it != Element.SizeUnspecified }
-                                ?: IntSize.Zero
-                        val placeable =
-                            measurable.measure(Constraints.fixed(size.width, size.height))
-                        layout(size.width, size.height) { placeable.place(0, 0) }
-                    }
-                    .drawBehind {
-                        drawIntoCanvas { canvas -> canvas.nativeCanvas.drawPicture(picture) }
-                    }
-            )
+            // If we are not composed, we still need to lay out an empty space with the same *target
+            // size* as its movable content, i.e. the same *size when idle*. During transitions,
+            // this size will be used to interpolate the transition size, during the intermediate
+            // layout pass.
+            Layout { _, _ ->
+                // No need to measure or place anything.
+                val size =
+                    placeholderContentSize(
+                        layoutImpl,
+                        scene.key,
+                        layoutImpl.elements.getValue(element),
+                    )
+                layout(size.width, size.height) {}
+            }
         }
     }
 }
@@ -117,7 +167,7 @@
 private fun shouldComposeMovableElement(
     layoutImpl: SceneTransitionLayoutImpl,
     scene: SceneKey,
-    element: Element,
+    element: ElementKey,
 ): Boolean {
     val transition =
         layoutImpl.state.currentTransition
@@ -130,72 +180,55 @@
     val fromReady = layoutImpl.isSceneReady(fromScene)
     val toReady = layoutImpl.isSceneReady(toScene)
 
-    val otherScene =
-        when (scene) {
-            fromScene -> toScene
-            toScene -> fromScene
-            else ->
-                error(
-                    "shouldComposeMovableElement(scene=$scene) called with fromScene=$fromScene " +
-                        "and toScene=$toScene"
-                )
-        }
-
-    val isShared = otherScene in element.sceneValues
-
-    if (isShared && !toReady && !fromReady) {
-        // This should usually not happen given that fromScene should be ready, but let's log a
-        // warning here in case it does so it helps debugging flicker issues caused by this part of
-        // the code.
-        Log.w(
-            TAG,
-            "MovableElement $element might have to be composed for the first time in both " +
-                "fromScene=$fromScene and toScene=$toScene. This will probably lead to a flicker " +
-                "where the size of the element will jump from IntSize.Zero to its actual size " +
-                "during the transition."
-        )
-    }
-
-    // Element is not shared in this transition.
-    if (!isShared) {
-        return true
-    }
-
-    // toScene is not ready (because we are composing it for the first time), so we compose it there
-    // first. This is the most common scenario when starting a transition that has a shared movable
-    // element.
-    if (!toReady) {
+    if (!fromReady && !toReady) {
+        // Neither of the scenes will be drawn, so where we compose it doesn't really matter. Note
+        // that we could have slightly more complicated logic here to optimize for this case, but
+        // it's not worth it given that readyScenes should disappear soon (b/316901148).
         return scene == toScene
     }
 
-    // This should usually not happen, but if we are also composing for the first time in fromScene
-    // then we should compose it there only.
-    if (!fromReady) {
-        return scene == fromScene
-    }
+    // If one of the scenes is not ready, compose it in the other one to make sure it is drawn.
+    if (!fromReady) return scene == toScene
+    if (!toReady) return scene == fromScene
 
+    // Always compose movable elements in the scene picked by their scene picker.
     return shouldDrawOrComposeSharedElement(
         layoutImpl,
         transition,
         scene,
-        element.key,
-        sharedElementTransformation(layoutImpl.state, transition, element.key),
+        element,
     )
 }
 
-private class MovableElementScopeImpl(
-    private val layoutImpl: SceneTransitionLayoutImpl,
-    private val element: Element,
-    private val scene: Scene,
-) : MovableElementScope {
-    @Composable
-    override fun <T> animateSharedValueAsState(
-        value: T,
-        debugName: String,
-        lerp: (start: T, stop: T, fraction: Float) -> T,
-        canOverflow: Boolean,
-    ): State<T> {
-        val key = remember { ValueKey(debugName) }
-        return animateSharedValueAsState(layoutImpl, scene, element, key, value, lerp, canOverflow)
+/**
+ * Return the size of the placeholder/space that is composed when the movable content is not
+ * composed in a scene.
+ */
+private fun placeholderContentSize(
+    layoutImpl: SceneTransitionLayoutImpl,
+    scene: SceneKey,
+    element: Element,
+): IntSize {
+    // If the content of the movable element was already composed in this scene before, use that
+    // target size.
+    val targetValueInScene = element.sceneStates.getValue(scene).targetSize
+    if (targetValueInScene != Element.SizeUnspecified) {
+        return targetValueInScene
     }
+
+    // This code is only run during transitions (otherwise the content would be composed and the
+    // placeholder would not), so it's ok to cast the state into a Transition directly.
+    val transition = layoutImpl.state.transitionState as TransitionState.Transition
+
+    // If the content was already composed in the other scene, we use that target size assuming it
+    // doesn't change between scenes.
+    // TODO(b/317026105): Provide a way to give a hint size/content for cases where this is not
+    // true.
+    val otherScene = if (transition.fromScene == scene) transition.toScene else transition.fromScene
+    val targetValueInOtherScene = element.sceneStates[otherScene]?.targetSize
+    if (targetValueInOtherScene != null && targetValueInOtherScene != Element.SizeUnspecified) {
+        return targetValueInOtherScene
+    }
+
+    return IntSize.Zero
 }
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/PunchHole.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/PunchHole.kt
index 560e92b..454c0ec 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/PunchHole.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/PunchHole.kt
@@ -75,8 +75,8 @@
 
         if (
             bounds == null ||
-                bounds.lastSharedValues.size == Element.SizeUnspecified ||
-                bounds.lastSharedValues.offset == Offset.Unspecified
+                bounds.lastSharedState.size == Element.SizeUnspecified ||
+                bounds.lastSharedState.offset == Offset.Unspecified
         ) {
             drawContent()
             return
@@ -87,14 +87,14 @@
             canvas.withSaveLayer(size.toRect(), Paint()) {
                 drawContent()
 
-                val offset = bounds.lastSharedValues.offset - element.lastSharedValues.offset
+                val offset = bounds.lastSharedState.offset - element.lastSharedState.offset
                 translate(offset.x, offset.y) { drawHole(bounds) }
             }
         }
     }
 
     private fun DrawScope.drawHole(bounds: Element) {
-        val boundsSize = bounds.lastSharedValues.size.toSize()
+        val boundsSize = bounds.lastSharedState.size.toSize()
         if (shape == RectangleShape) {
             drawRect(Color.Black, size = boundsSize, blendMode = BlendMode.DstOut)
             return
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Scene.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Scene.kt
index 30e50a9..3537b79 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Scene.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Scene.kt
@@ -20,13 +20,10 @@
 import androidx.compose.foundation.layout.Box
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.Stable
-import androidx.compose.runtime.State
 import androidx.compose.runtime.getValue
 import androidx.compose.runtime.mutableFloatStateOf
 import androidx.compose.runtime.mutableStateOf
 import androidx.compose.runtime.setValue
-import androidx.compose.runtime.snapshots.Snapshot
-import androidx.compose.runtime.snapshots.SnapshotStateMap
 import androidx.compose.ui.ExperimentalComposeUiApi
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.graphics.Shape
@@ -45,16 +42,13 @@
     actions: Map<UserAction, SceneKey>,
     zIndex: Float,
 ) {
-    private val scope = SceneScopeImpl(layoutImpl, this)
+    internal val scope = SceneScopeImpl(layoutImpl, this)
 
     var content by mutableStateOf(content)
     var userActions by mutableStateOf(actions)
     var zIndex by mutableFloatStateOf(zIndex)
     var targetSize by mutableStateOf(IntSize.Zero)
 
-    /** The shared values in this scene that are not tied to a specific element. */
-    val sharedValues = SnapshotStateMap<ValueKey, Element.SharedValue<*>>()
-
     @Composable
     @OptIn(ExperimentalComposeUiApi::class)
     fun Content(modifier: Modifier = Modifier) {
@@ -77,7 +71,7 @@
     }
 }
 
-private class SceneScopeImpl(
+internal class SceneScopeImpl(
     private val layoutImpl: SceneTransitionLayoutImpl,
     private val scene: Scene,
 ) : SceneScope {
@@ -87,6 +81,42 @@
         return element(layoutImpl, scene, key)
     }
 
+    @Composable
+    override fun Element(
+        key: ElementKey,
+        modifier: Modifier,
+        content: @Composable (ElementScope<ElementContentScope>.() -> Unit)
+    ) {
+        Element(layoutImpl, scene, key, modifier, content)
+    }
+
+    @Composable
+    override fun MovableElement(
+        key: ElementKey,
+        modifier: Modifier,
+        content: @Composable (ElementScope<MovableElementContentScope>.() -> Unit)
+    ) {
+        MovableElement(layoutImpl, scene, key, modifier, content)
+    }
+
+    @Composable
+    override fun <T> animateSceneValueAsState(
+        value: T,
+        key: ValueKey,
+        lerp: (T, T, Float) -> T,
+        canOverflow: Boolean
+    ): AnimatedState<T> {
+        return animateSharedValueAsState(
+            layoutImpl = layoutImpl,
+            scene = scene.key,
+            element = null,
+            key = key,
+            value = value,
+            lerp = lerp,
+            canOverflow = canOverflow,
+        )
+    }
+
     override fun Modifier.horizontalNestedScrollToScene(
         leftBehavior: NestedScrollBehavior,
         rightBehavior: NestedScrollBehavior,
@@ -109,45 +139,6 @@
             bottomOrRightBehavior = bottomBehavior,
         )
 
-    @Composable
-    override fun <T> animateSharedValueAsState(
-        value: T,
-        key: ValueKey,
-        element: ElementKey?,
-        lerp: (T, T, Float) -> T,
-        canOverflow: Boolean
-    ): State<T> {
-        val element =
-            element?.let { key ->
-                Snapshot.withoutReadObservation {
-                    layoutImpl.elements[key]
-                        ?: error(
-                            "Element $key is not composed. Make sure to call " +
-                                "animateSharedXAsState *after* Modifier.element(key)."
-                        )
-                }
-            }
-
-        return animateSharedValueAsState(
-            layoutImpl,
-            scene,
-            element,
-            key,
-            value,
-            lerp,
-            canOverflow,
-        )
-    }
-
-    @Composable
-    override fun MovableElement(
-        key: ElementKey,
-        modifier: Modifier,
-        content: @Composable MovableElementScope.() -> Unit,
-    ) {
-        MovableElement(layoutImpl, scene, key, modifier, content)
-    }
-
     override fun Modifier.punchHole(
         element: ElementKey,
         bounds: ElementKey,
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayout.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayout.kt
index 5eb339e..84fade89 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayout.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayout.kt
@@ -22,9 +22,9 @@
 import androidx.compose.runtime.LaunchedEffect
 import androidx.compose.runtime.SideEffect
 import androidx.compose.runtime.Stable
-import androidx.compose.runtime.State
 import androidx.compose.runtime.remember
 import androidx.compose.runtime.rememberCoroutineScope
+import androidx.compose.ui.Alignment
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.graphics.Shape
 import androidx.compose.ui.input.nestedscroll.NestedScrollConnection
@@ -98,9 +98,9 @@
  */
 @DslMarker annotation class ElementDsl
 
-@ElementDsl
 @Stable
-interface SceneScope {
+@ElementDsl
+interface BaseSceneScope {
     /** The state of the [SceneTransitionLayout] in which this scene is contained. */
     val layoutState: SceneTransitionLayoutState
 
@@ -111,21 +111,74 @@
      * that the element can be transformed and animated when the scene transitions in or out.
      *
      * Additionally, this [key] will be used to detect elements that are shared between scenes to
-     * automatically interpolate their size, offset and [shared values][animateSharedValueAsState].
+     * automatically interpolate their size and offset. If you need to animate shared element values
+     * (i.e. values associated to this element that change depending on which scene it is composed
+     * in), use [Element] instead.
      *
      * Note that shared elements tagged using this function will be duplicated in each scene they
      * are part of, so any **internal** state (e.g. state created using `remember {
      * mutableStateOf(...) }`) will be lost. If you need to preserve internal state, you should use
      * [MovableElement] instead.
      *
+     * @see Element
      * @see MovableElement
-     *
-     * TODO(b/291566282): Migrate this to the new Modifier Node API and remove the @Composable
-     *   constraint.
      */
     fun Modifier.element(key: ElementKey): Modifier
 
     /**
+     * Create an element identified by [key].
+     *
+     * Similar to [element], this creates an element that will be automatically shared when present
+     * in multiple scenes and that can be transformed during transitions, the same way that
+     * [element] does.
+     *
+     * The only difference with [element] is that the provided [ElementScope] allows you to
+     * [animate element values][ElementScope.animateElementValueAsState] or specify its
+     * [movable content][Element.movableContent] that will be "moved" and composed only once during
+     * transitions (as opposed to [element] that duplicates shared elements) so that any internal
+     * state is preserved during and after the transition.
+     *
+     * @see element
+     * @see MovableElement
+     */
+    @Composable
+    fun Element(
+        key: ElementKey,
+        modifier: Modifier,
+
+        // TODO(b/317026105): As discussed in http://shortn/_gJVdltF8Si, remove the @Composable
+        // scope here to make sure that callers specify the content in ElementScope.content {} or
+        // ElementScope.movableContent {}.
+        content: @Composable ElementScope<ElementContentScope>.() -> Unit,
+    )
+
+    /**
+     * Create a *movable* element identified by [key].
+     *
+     * Similar to [Element], this creates an element that will be automatically shared when present
+     * in multiple scenes and that can be transformed during transitions, and you can also use the
+     * provided [ElementScope] to [animate element values][ElementScope.animateElementValueAsState].
+     *
+     * The important difference with [element] and [Element] is that this element
+     * [content][ElementScope.content] will be "moved" and composed only once during transitions, as
+     * opposed to [element] and [Element] that duplicates shared elements, so that any internal
+     * state is preserved during and after the transition.
+     *
+     * @see element
+     * @see Element
+     */
+    @Composable
+    fun MovableElement(
+        key: ElementKey,
+        modifier: Modifier,
+
+        // TODO(b/317026105): As discussed in http://shortn/_gJVdltF8Si, remove the @Composable
+        // scope here to make sure that callers specify the content in ElementScope.content {} or
+        // ElementScope.movableContent {}.
+        content: @Composable ElementScope<MovableElementContentScope>.() -> Unit,
+    )
+
+    /**
      * Adds a [NestedScrollConnection] to intercept scroll events not handled by the scrollable
      * component.
      *
@@ -150,51 +203,6 @@
     ): Modifier
 
     /**
-     * Create a *movable* element identified by [key].
-     *
-     * This creates an element that will be automatically shared when present in multiple scenes and
-     * that can be transformed during transitions, the same way that [element] does. The major
-     * difference with [element] is that elements created with [MovableElement] will be "moved" and
-     * composed only once during transitions (as opposed to [element] that duplicates shared
-     * elements) so that any internal state is preserved during and after the transition.
-     *
-     * @see element
-     */
-    @Composable
-    fun MovableElement(
-        key: ElementKey,
-        modifier: Modifier,
-        content: @Composable MovableElementScope.() -> Unit,
-    )
-
-    /**
-     * Animate some value of a shared element.
-     *
-     * @param value the value of this shared value in the current scene.
-     * @param key the key of this shared value.
-     * @param element the element associated with this value. If `null`, this value will be
-     *   associated at the scene level, which means that [key] should be used maximum once in the
-     *   same scene.
-     * @param lerp the *linear* interpolation function that should be used to interpolate between
-     *   two different values. Note that it has to be linear because the [fraction] passed to this
-     *   interpolator is already interpolated.
-     * @param canOverflow whether this value can overflow past the values it is interpolated
-     *   between, for instance because the transition is animated using a bouncy spring.
-     * @see animateSharedIntAsState
-     * @see animateSharedFloatAsState
-     * @see animateSharedDpAsState
-     * @see animateSharedColorAsState
-     */
-    @Composable
-    fun <T> animateSharedValueAsState(
-        value: T,
-        key: ValueKey,
-        element: ElementKey?,
-        lerp: (start: T, stop: T, fraction: Float) -> T,
-        canOverflow: Boolean,
-    ): State<T>
-
-    /**
      * Punch a hole in this [element] using the bounds of [bounds] in [scene] and the given [shape].
      *
      * Punching a hole in an element will "remove" any pixel drawn by that element in the hole area.
@@ -213,19 +221,96 @@
     fun Modifier.noResizeDuringTransitions(): Modifier
 }
 
-// TODO(b/291053742): Add animateSharedValueAsState(targetValue) without any ValueKey and ElementKey
-// arguments to allow sharing values inside a movable element.
+@Stable
 @ElementDsl
-interface MovableElementScope {
+interface SceneScope : BaseSceneScope {
+    /**
+     * Animate some value at the scene level.
+     *
+     * @param value the value of this shared value in the current scene.
+     * @param key the key of this shared value.
+     * @param lerp the *linear* interpolation function that should be used to interpolate between
+     *   two different values. Note that it has to be linear because the [fraction] passed to this
+     *   interpolator is already interpolated.
+     * @param canOverflow whether this value can overflow past the values it is interpolated
+     *   between, for instance because the transition is animated using a bouncy spring.
+     * @see animateSceneIntAsState
+     * @see animateSceneFloatAsState
+     * @see animateSceneDpAsState
+     * @see animateSceneColorAsState
+     */
     @Composable
-    fun <T> animateSharedValueAsState(
+    fun <T> animateSceneValueAsState(
         value: T,
-        debugName: String,
+        key: ValueKey,
         lerp: (start: T, stop: T, fraction: Float) -> T,
         canOverflow: Boolean,
-    ): State<T>
+    ): AnimatedState<T>
 }
 
+@Stable
+@ElementDsl
+interface ElementScope<ContentScope> {
+    /**
+     * Animate some value associated to this element.
+     *
+     * @param value the value of this shared value in the current scene.
+     * @param key the key of this shared value.
+     * @param lerp the *linear* interpolation function that should be used to interpolate between
+     *   two different values. Note that it has to be linear because the [fraction] passed to this
+     *   interpolator is already interpolated.
+     * @param canOverflow whether this value can overflow past the values it is interpolated
+     *   between, for instance because the transition is animated using a bouncy spring.
+     * @see animateElementIntAsState
+     * @see animateElementFloatAsState
+     * @see animateElementDpAsState
+     * @see animateElementColorAsState
+     */
+    @Composable
+    fun <T> animateElementValueAsState(
+        value: T,
+        key: ValueKey,
+        lerp: (start: T, stop: T, fraction: Float) -> T,
+        canOverflow: Boolean,
+    ): AnimatedState<T>
+
+    /**
+     * The content of this element.
+     *
+     * Important: This must be called exactly once, after all calls to [animateElementValueAsState].
+     */
+    @Composable fun content(content: @Composable ContentScope.() -> Unit)
+}
+
+/**
+ * The exact same scope as [androidx.compose.foundation.layout.BoxScope].
+ *
+ * We can't reuse BoxScope directly because of the @LayoutScopeMarker annotation on it, which would
+ * prevent us from calling Modifier.element() and other methods of [SceneScope] inside any Box {} in
+ * the [content][ElementScope.content] of a [SceneScope.Element] or a [SceneScope.MovableElement].
+ */
+@Stable
+@ElementDsl
+interface ElementBoxScope {
+    /** @see [androidx.compose.foundation.layout.BoxScope.align]. */
+    @Stable fun Modifier.align(alignment: Alignment): Modifier
+
+    /** @see [androidx.compose.foundation.layout.BoxScope.matchParentSize]. */
+    @Stable fun Modifier.matchParentSize(): Modifier
+}
+
+/** The scope for "normal" (not movable) elements. */
+@Stable @ElementDsl interface ElementContentScope : SceneScope, ElementBoxScope
+
+/**
+ * The scope for the content of movable elements.
+ *
+ * Note that it extends [BaseSceneScope] and not [SceneScope] because movable elements should not
+ * call [SceneScope.animateSceneValueAsState], given that their content is not composed in all
+ * scenes.
+ */
+@Stable @ElementDsl interface MovableElementContentScope : BaseSceneScope, ElementBoxScope
+
 /** An action performed by the user. */
 sealed interface UserAction
 
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutImpl.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutImpl.kt
index 45e1a0f..0227aba 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutImpl.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutImpl.kt
@@ -36,6 +36,16 @@
 import com.android.compose.ui.util.lerp
 import kotlinx.coroutines.CoroutineScope
 
+/**
+ * The type for the content of movable elements.
+ *
+ * TODO(b/317972419): Revert back to make this movable content have a single @Composable lambda
+ *   parameter.
+ */
+internal typealias MovableElementContent =
+    @Composable
+    (MovableElementContentScope, @Composable MovableElementContentScope.() -> Unit) -> Unit
+
 @Stable
 internal class SceneTransitionLayoutImpl(
     internal val state: SceneTransitionLayoutStateImpl,
@@ -56,16 +66,47 @@
     /**
      * The map of [Element]s.
      *
-     * Note that this map is *mutated* directly during composition, so it is a [SnapshotStateMap] to
-     * make sure that mutations are reverted if composition is cancelled.
+     * Important: [Element]s from this map should never be accessed during composition because the
+     * Elements are added when the associated Modifier.element() node is attached to the Modifier
+     * tree, i.e. after composition.
      */
-    internal val elements = SnapshotStateMap<ElementKey, Element>()
+    internal val elements = mutableMapOf<ElementKey, Element>()
+
+    /**
+     * The map of contents of movable elements.
+     *
+     * Note that given that this map is mutated directly during a composition, it has to be a
+     * [SnapshotStateMap] to make sure that mutations are reverted if composition is cancelled.
+     */
+    private var _movableContents: SnapshotStateMap<ElementKey, MovableElementContent>? = null
+    val movableContents: SnapshotStateMap<ElementKey, MovableElementContent>
+        get() =
+            _movableContents
+                ?: SnapshotStateMap<ElementKey, MovableElementContent>().also {
+                    _movableContents = it
+                }
+
+    /**
+     * The different values of a shared value keyed by a a [ValueKey] and the different elements and
+     * scenes it is associated to.
+     */
+    private var _sharedValues:
+        MutableMap<ValueKey, MutableMap<ElementKey?, SnapshotStateMap<SceneKey, *>>>? =
+        null
+    internal val sharedValues:
+        MutableMap<ValueKey, MutableMap<ElementKey?, SnapshotStateMap<SceneKey, *>>>
+        get() =
+            _sharedValues
+                ?: mutableMapOf<ValueKey, MutableMap<ElementKey?, SnapshotStateMap<SceneKey, *>>>()
+                    .also { _sharedValues = it }
 
     /**
      * The scenes that are "ready", i.e. they were composed and fully laid-out at least once.
      *
      * Note that this map is *read* during composition, so it is a [SnapshotStateMap] to make sure
      * that we recompose when modifications are made to this map.
+     *
+     * TODO(b/316901148): Remove this map.
      */
     private val readyScenes = SnapshotStateMap<SceneKey, Boolean>()
 
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutState.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutState.kt
index d1ba582..0607aa1 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutState.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutState.kt
@@ -92,6 +92,20 @@
 
         /** Whether user input is currently driving the transition. */
         abstract val isUserInputOngoing: Boolean
+
+        /**
+         * Whether we are transitioning. If [from] or [to] is empty, we will also check that they
+         * match the scenes we are animating from and/or to.
+         */
+        fun isTransitioning(from: SceneKey? = null, to: SceneKey? = null): Boolean {
+            return (from == null || fromScene == from) && (to == null || toScene == to)
+        }
+
+        /** Whether we are transitioning from [scene] to [other], or from [other] to [scene]. */
+        fun isTransitioningBetween(scene: SceneKey, other: SceneKey): Boolean {
+            return isTransitioning(from = scene, to = other) ||
+                isTransitioning(from = other, to = scene)
+        }
     }
 }
 
@@ -111,13 +125,12 @@
 
     override fun isTransitioning(from: SceneKey?, to: SceneKey?): Boolean {
         val transition = currentTransition ?: return false
-        return (from == null || transition.fromScene == from) &&
-            (to == null || transition.toScene == to)
+        return transition.isTransitioning(from, to)
     }
 
     override fun isTransitioningBetween(scene: SceneKey, other: SceneKey): Boolean {
-        return isTransitioning(from = scene, to = other) ||
-            isTransitioning(from = other, to = scene)
+        val transition = currentTransition ?: return false
+        return transition.isTransitioningBetween(scene, other)
     }
 
     /** Start a new [transition], instantly interrupting any ongoing transition if there was one. */
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 dfa2a9a..dc8505c 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
@@ -119,14 +119,8 @@
      *
      * @param enabled whether the matched element(s) should actually be shared in this transition.
      *   Defaults to true.
-     * @param scenePicker the [SharedElementScenePicker] to use when deciding in which scene we
-     *   should draw or compose this shared element.
      */
-    fun sharedElement(
-        matcher: ElementMatcher,
-        enabled: Boolean = true,
-        scenePicker: SharedElementScenePicker = DefaultSharedElementScenePicker,
-    )
+    fun sharedElement(matcher: ElementMatcher, enabled: Boolean = true)
 
     /**
      * Adds the transformations in [builder] but in reversed order. This allows you to partially
@@ -136,37 +130,65 @@
     fun reversed(builder: TransitionBuilder.() -> Unit)
 }
 
-interface SharedElementScenePicker {
+/**
+ * An interface to decide where we should draw shared Elements or compose MovableElements.
+ *
+ * @see DefaultElementScenePicker
+ * @see HighestZIndexScenePicker
+ * @see LowestZIndexScenePicker
+ * @see MovableElementScenePicker
+ */
+interface ElementScenePicker {
     /**
      * Return the scene in which [element] should be drawn (when using `Modifier.element(key)`) or
-     * composed (when using `MovableElement(key)`) during the transition from [fromScene] to
-     * [toScene].
+     * composed (when using `MovableElement(key)`) during the given [transition].
+     *
+     * Important: For [MovableElements][SceneScope.MovableElement], this scene picker will *always*
+     * be used during transitions to decide whether we should compose that element in a given scene
+     * or not. Therefore, you should make sure that the returned [SceneKey] contains the movable
+     * element, otherwise that element will not be composed in any scene during the transition.
      */
     fun sceneDuringTransition(
         element: ElementKey,
-        fromScene: SceneKey,
-        toScene: SceneKey,
-        progress: () -> Float,
+        transition: TransitionState.Transition,
         fromSceneZIndex: Float,
         toSceneZIndex: Float,
     ): SceneKey
-}
 
-object DefaultSharedElementScenePicker : SharedElementScenePicker {
-    override fun sceneDuringTransition(
+    /**
+     * Return [transition.fromScene] if it is in [scenes] and [transition.toScene] is not, or return
+     * [transition.toScene] if it is in [scenes] and [transition.fromScene] is not, otherwise throw
+     * an exception (i.e. if neither or both of fromScene and toScene are in [scenes]).
+     *
+     * This function can be useful when computing the scene in which a movable element should be
+     * composed.
+     */
+    fun pickSingleSceneIn(
+        scenes: Set<SceneKey>,
+        transition: TransitionState.Transition,
         element: ElementKey,
-        fromScene: SceneKey,
-        toScene: SceneKey,
-        progress: () -> Float,
-        fromSceneZIndex: Float,
-        toSceneZIndex: Float
     ): SceneKey {
-        // By default shared elements are drawn in the highest scene possible, unless it is a
-        // background.
-        return if (
-            (fromSceneZIndex > toSceneZIndex && !element.isBackground) ||
-                (fromSceneZIndex < toSceneZIndex && element.isBackground)
-        ) {
+        val fromScene = transition.fromScene
+        val toScene = transition.toScene
+        val fromSceneInScenes = scenes.contains(fromScene)
+        val toSceneInScenes = scenes.contains(toScene)
+        if (fromSceneInScenes && toSceneInScenes) {
+            error(
+                "Element $element can be in both $fromScene and $toScene. You should add a " +
+                    "special case for this transition before calling pickSingleSceneIn()."
+            )
+        }
+
+        if (!fromSceneInScenes && !toSceneInScenes) {
+            error(
+                "Element $element can be neither in $fromScene and $toScene. This either means " +
+                    "that you should add one of them in the scenes set passed to " +
+                    "pickSingleSceneIn(), or there is an internal error and this element was " +
+                    "composed when it shouldn't be."
+            )
+        }
+
+        return if (fromSceneInScenes) {
             fromScene
         } else {
             toScene
@@ -174,6 +196,66 @@
     }
 }
 
+/** An [ElementScenePicker] that draws/composes elements in the scene with the highest z-order. */
+object HighestZIndexScenePicker : ElementScenePicker {
+    override fun sceneDuringTransition(
+        element: ElementKey,
+        transition: TransitionState.Transition,
+        fromSceneZIndex: Float,
+        toSceneZIndex: Float
+    ): SceneKey {
+        return if (fromSceneZIndex > toSceneZIndex) {
+            transition.fromScene
+        } else {
+            transition.toScene
+        }
+    }
+}
+
+/** An [ElementScenePicker] that draws/composes elements in the scene with the lowest z-order. */
+object LowestZIndexScenePicker : ElementScenePicker {
+    override fun sceneDuringTransition(
+        element: ElementKey,
+        transition: TransitionState.Transition,
+        fromSceneZIndex: Float,
+        toSceneZIndex: Float
+    ): SceneKey {
+        return if (fromSceneZIndex < toSceneZIndex) {
+            transition.fromScene
+        } else {
+            transition.toScene
+        }
+    }
+}
+
+/**
+ * An [ElementScenePicker] that draws/composes elements in the scene we are transitioning to, iff
+ * that scene is in [scenes].
+ *
+ * This picker can be useful for movable elements whose content size depends on its content (because
+ * it wraps it) in at least one scene. That way, the target size of the MovableElement will be
+ * computed in the scene we are going to and, given that this element was probably already composed
+ * in the scene we are going from before starting the transition, the interpolated size of the
+ * movable element during the transition should be correct.
+ *
+ * The downside of this picker is that the zIndex of the element when going from scene A to scene B
+ * is not the same as when going from scene B to scene A, so it's not usable in situations where
+ * z-ordering during the transition matters.
+ */
+class MovableElementScenePicker(private val scenes: Set<SceneKey>) : ElementScenePicker {
+    override fun sceneDuringTransition(
+        element: ElementKey,
+        transition: TransitionState.Transition,
+        fromSceneZIndex: Float,
+        toSceneZIndex: Float,
+    ): SceneKey {
+        return if (scenes.contains(transition.toScene)) transition.toScene else transition.fromScene
+    }
+}
+
+/** The default [ElementScenePicker]. */
+val DefaultElementScenePicker = HighestZIndexScenePicker
+
 @TransitionDsl
 interface PropertyTransformationBuilder {
     /**
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/TransitionDslImpl.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/TransitionDslImpl.kt
index 7046866..b96f9be 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/TransitionDslImpl.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/TransitionDslImpl.kt
@@ -108,12 +108,8 @@
         range = null
     }
 
-    override fun sharedElement(
-        matcher: ElementMatcher,
-        enabled: Boolean,
-        scenePicker: SharedElementScenePicker,
-    ) {
-        transformations.add(SharedElementTransformation(matcher, enabled, scenePicker))
+    override fun sharedElement(matcher: ElementMatcher, enabled: Boolean) {
+        transformations.add(SharedElementTransformation(matcher, enabled))
     }
 
     override fun timestampRange(
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/AnchoredSize.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/AnchoredSize.kt
index 40c814e..124ec29 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/AnchoredSize.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/AnchoredSize.kt
@@ -36,12 +36,12 @@
         layoutImpl: SceneTransitionLayoutImpl,
         scene: Scene,
         element: Element,
-        sceneValues: Element.TargetValues,
+        sceneState: Element.SceneState,
         transition: TransitionState.Transition,
         value: IntSize,
     ): IntSize {
         fun anchorSizeIn(scene: SceneKey): IntSize {
-            val size = layoutImpl.elements[anchor]?.sceneValues?.get(scene)?.targetSize
+            val size = layoutImpl.elements[anchor]?.sceneStates?.get(scene)?.targetSize
             return if (size != null && size != Element.SizeUnspecified) {
                 IntSize(
                     width = if (anchorWidth) size.width else value.width,
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/AnchoredTranslate.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/AnchoredTranslate.kt
index a1d6319..7aa702b 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/AnchoredTranslate.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/AnchoredTranslate.kt
@@ -35,13 +35,13 @@
         layoutImpl: SceneTransitionLayoutImpl,
         scene: Scene,
         element: Element,
-        sceneValues: Element.TargetValues,
+        sceneState: Element.SceneState,
         transition: TransitionState.Transition,
         value: Offset,
     ): Offset {
         val anchor = layoutImpl.elements[anchor] ?: return value
         fun anchorOffsetIn(scene: SceneKey): Offset? {
-            return anchor.sceneValues[scene]?.targetOffset?.takeIf { it.isSpecified }
+            return anchor.sceneStates[scene]?.targetOffset?.takeIf { it.isSpecified }
         }
 
         // [element] will move the same amount as [anchor] does.
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/DrawScale.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/DrawScale.kt
index d1cf8ee..6704a3b 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/DrawScale.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/DrawScale.kt
@@ -39,7 +39,7 @@
         layoutImpl: SceneTransitionLayoutImpl,
         scene: Scene,
         element: Element,
-        sceneValues: Element.TargetValues,
+        sceneState: Element.SceneState,
         transition: TransitionState.Transition,
         value: Scale,
     ): Scale {
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/EdgeTranslate.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/EdgeTranslate.kt
index 70534dd..191a8fb 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/EdgeTranslate.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/EdgeTranslate.kt
@@ -34,12 +34,12 @@
         layoutImpl: SceneTransitionLayoutImpl,
         scene: Scene,
         element: Element,
-        sceneValues: Element.TargetValues,
+        sceneState: Element.SceneState,
         transition: TransitionState.Transition,
         value: Offset
     ): Offset {
         val sceneSize = scene.targetSize
-        val elementSize = sceneValues.targetSize
+        val elementSize = sceneState.targetSize
         if (elementSize == Element.SizeUnspecified) {
             return value
         }
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/Fade.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/Fade.kt
index 17032dc..41f626e 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/Fade.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/Fade.kt
@@ -30,7 +30,7 @@
         layoutImpl: SceneTransitionLayoutImpl,
         scene: Scene,
         element: Element,
-        sceneValues: Element.TargetValues,
+        sceneState: Element.SceneState,
         transition: TransitionState.Transition,
         value: Float
     ): Float {
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/ScaleSize.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/ScaleSize.kt
index 233ae59..f5207dc 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/ScaleSize.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/ScaleSize.kt
@@ -37,7 +37,7 @@
         layoutImpl: SceneTransitionLayoutImpl,
         scene: Scene,
         element: Element,
-        sceneValues: Element.TargetValues,
+        sceneState: Element.SceneState,
         transition: TransitionState.Transition,
         value: IntSize,
     ): IntSize {
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/Transformation.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/Transformation.kt
index 0cd11b9..04254fb 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/Transformation.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/Transformation.kt
@@ -20,7 +20,6 @@
 import com.android.compose.animation.scene.ElementMatcher
 import com.android.compose.animation.scene.Scene
 import com.android.compose.animation.scene.SceneTransitionLayoutImpl
-import com.android.compose.animation.scene.SharedElementScenePicker
 import com.android.compose.animation.scene.TransitionState
 
 /** A transformation applied to one or more elements during a transition. */
@@ -48,7 +47,6 @@
 internal class SharedElementTransformation(
     override val matcher: ElementMatcher,
     internal val enabled: Boolean,
-    internal val scenePicker: SharedElementScenePicker,
 ) : Transformation
 
 /** A transformation that changes the value of an element property, like its size or offset. */
@@ -62,7 +60,7 @@
         layoutImpl: SceneTransitionLayoutImpl,
         scene: Scene,
         element: Element,
-        sceneValues: Element.TargetValues,
+        sceneState: Element.SceneState,
         transition: TransitionState.Transition,
         value: T,
     ): T
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/Translate.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/Translate.kt
index 864b937..04d5914 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/Translate.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/Translate.kt
@@ -35,7 +35,7 @@
         layoutImpl: SceneTransitionLayoutImpl,
         scene: Scene,
         element: Element,
-        sceneValues: Element.TargetValues,
+        sceneState: Element.SceneState,
         transition: TransitionState.Transition,
         value: Offset,
     ): Offset {
diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/AnimatedSharedAsStateTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/AnimatedSharedAsStateTest.kt
index 5473186..a116501 100644
--- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/AnimatedSharedAsStateTest.kt
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/AnimatedSharedAsStateTest.kt
@@ -18,10 +18,11 @@
 
 import androidx.compose.animation.core.LinearEasing
 import androidx.compose.animation.core.tween
-import androidx.compose.foundation.layout.Box
 import androidx.compose.runtime.Composable
+import androidx.compose.runtime.LaunchedEffect
 import androidx.compose.runtime.SideEffect
 import androidx.compose.runtime.getValue
+import androidx.compose.runtime.snapshotFlow
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.graphics.Color
 import androidx.compose.ui.graphics.lerp
@@ -32,6 +33,7 @@
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import com.android.compose.ui.util.lerp
 import com.google.common.truth.Truth.assertThat
+import org.junit.Assert.assertThrows
 import org.junit.Rule
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -62,17 +64,17 @@
         onCurrentValueChanged: (Values) -> Unit,
     ) {
         val key = TestElements.Foo
-        Box(Modifier.element(key)) {
-            val int by animateSharedIntAsState(targetValues.int, TestValues.Value1, key)
-            val float by animateSharedFloatAsState(targetValues.float, TestValues.Value2, key)
-            val dp by animateSharedDpAsState(targetValues.dp, TestValues.Value3, key)
-            val color by
-                animateSharedColorAsState(targetValues.color, TestValues.Value4, element = null)
+        Element(key, Modifier) {
+            val int by animateElementIntAsState(targetValues.int, key = TestValues.Value1)
+            val float by animateElementFloatAsState(targetValues.float, key = TestValues.Value2)
+            val dp by animateElementDpAsState(targetValues.dp, key = TestValues.Value3)
+            val color by animateElementColorAsState(targetValues.color, key = TestValues.Value4)
 
-            // Make sure we read the values during composition, so that we recompose and call
-            // onCurrentValueChanged() with the latest values.
-            val currentValues = Values(int, float, dp, color)
-            SideEffect { onCurrentValueChanged(currentValues) }
+            content {
+                LaunchedEffect(Unit) {
+                    snapshotFlow { Values(int, float, dp, color) }.collect(onCurrentValueChanged)
+                }
+            }
         }
     }
 
@@ -83,30 +85,34 @@
     ) {
         val key = TestElements.Foo
         MovableElement(key = key, Modifier) {
-            val int by
-                animateSharedIntAsState(targetValues.int, debugName = TestValues.Value1.debugName)
-            val float by
-                animateSharedFloatAsState(
-                    targetValues.float,
-                    debugName = TestValues.Value2.debugName
-                )
-            val dp by
-                animateSharedDpAsState(targetValues.dp, debugName = TestValues.Value3.debugName)
-            val color by
-                animateSharedColorAsState(
-                    targetValues.color,
-                    debugName = TestValues.Value4.debugName
-                )
+            val int by animateElementIntAsState(targetValues.int, key = TestValues.Value1)
+            val float by animateElementFloatAsState(targetValues.float, key = TestValues.Value2)
+            val dp by animateElementDpAsState(targetValues.dp, key = TestValues.Value3)
+            val color by animateElementColorAsState(targetValues.color, key = TestValues.Value4)
 
-            // Make sure we read the values during composition, so that we recompose and call
-            // onCurrentValueChanged() with the latest values.
-            val currentValues = Values(int, float, dp, color)
-            SideEffect { onCurrentValueChanged(currentValues) }
+            LaunchedEffect(Unit) {
+                snapshotFlow { Values(int, float, dp, color) }.collect(onCurrentValueChanged)
+            }
+        }
+    }
+
+    @Composable
+    private fun SceneScope.SceneValues(
+        targetValues: Values,
+        onCurrentValueChanged: (Values) -> Unit,
+    ) {
+        val int by animateSceneIntAsState(targetValues.int, key = TestValues.Value1)
+        val float by animateSceneFloatAsState(targetValues.float, key = TestValues.Value2)
+        val dp by animateSceneDpAsState(targetValues.dp, key = TestValues.Value3)
+        val color by animateSceneColorAsState(targetValues.color, key = TestValues.Value4)
+
+        LaunchedEffect(Unit) {
+            snapshotFlow { Values(int, float, dp, color) }.collect(onCurrentValueChanged)
         }
     }
 
     @Test
-    fun animateSharedValues() {
+    fun animateElementValues() {
         val fromValues = Values(int = 0, float = 0f, dp = 0.dp, color = Color.Red)
         val toValues = Values(int = 100, float = 100f, dp = 100.dp, color = Color.Blue)
 
@@ -194,24 +200,183 @@
             }
 
             at(16) {
-                // Given that we use MovableElement here, animateSharedXAsState is composed only
-                // once, in the highest scene (in this case, in toScene).
-                assertThat(lastValueInFrom).isEqualTo(fromValues)
+                assertThat(lastValueInFrom).isEqualTo(lerp(fromValues, toValues, fraction = 0.25f))
                 assertThat(lastValueInTo).isEqualTo(lerp(fromValues, toValues, fraction = 0.25f))
             }
 
             at(32) {
-                assertThat(lastValueInFrom).isEqualTo(fromValues)
+                assertThat(lastValueInFrom).isEqualTo(lerp(fromValues, toValues, fraction = 0.5f))
                 assertThat(lastValueInTo).isEqualTo(lerp(fromValues, toValues, fraction = 0.5f))
             }
 
             at(48) {
-                assertThat(lastValueInFrom).isEqualTo(fromValues)
+                assertThat(lastValueInFrom).isEqualTo(lerp(fromValues, toValues, fraction = 0.75f))
                 assertThat(lastValueInTo).isEqualTo(lerp(fromValues, toValues, fraction = 0.75f))
             }
 
             after {
+                assertThat(lastValueInFrom).isEqualTo(toValues)
+                assertThat(lastValueInTo).isEqualTo(toValues)
+            }
+        }
+    }
+
+    @Test
+    fun animateSceneValues() {
+        val fromValues = Values(int = 0, float = 0f, dp = 0.dp, color = Color.Red)
+        val toValues = Values(int = 100, float = 100f, dp = 100.dp, color = Color.Blue)
+
+        var lastValueInFrom = fromValues
+        var lastValueInTo = toValues
+
+        rule.testTransition(
+            fromSceneContent = {
+                SceneValues(
+                    targetValues = fromValues,
+                    onCurrentValueChanged = { lastValueInFrom = it }
+                )
+            },
+            toSceneContent = {
+                SceneValues(targetValues = toValues, onCurrentValueChanged = { lastValueInTo = it })
+            },
+            transition = {
+                // The transition lasts 64ms = 4 frames.
+                spec = tween(durationMillis = 16 * 4, easing = LinearEasing)
+            },
+            fromScene = TestScenes.SceneA,
+            toScene = TestScenes.SceneB,
+        ) {
+            before {
                 assertThat(lastValueInFrom).isEqualTo(fromValues)
+
+                // to was not composed yet, so lastValueInTo was not set yet.
+                assertThat(lastValueInTo).isEqualTo(toValues)
+            }
+
+            at(16) {
+                // Given that we use scene values here, animateSceneXAsState is composed in both
+                // scenes and values should be interpolated with the transition fraction.
+                val expectedValues = lerp(fromValues, toValues, fraction = 0.25f)
+                assertThat(lastValueInFrom).isEqualTo(expectedValues)
+                assertThat(lastValueInTo).isEqualTo(expectedValues)
+            }
+
+            at(32) {
+                val expectedValues = lerp(fromValues, toValues, fraction = 0.5f)
+                assertThat(lastValueInFrom).isEqualTo(expectedValues)
+                assertThat(lastValueInTo).isEqualTo(expectedValues)
+            }
+
+            at(48) {
+                val expectedValues = lerp(fromValues, toValues, fraction = 0.75f)
+                assertThat(lastValueInFrom).isEqualTo(expectedValues)
+                assertThat(lastValueInTo).isEqualTo(expectedValues)
+            }
+
+            after {
+                assertThat(lastValueInFrom).isEqualTo(toValues)
+                assertThat(lastValueInTo).isEqualTo(toValues)
+            }
+        }
+    }
+
+    @Test
+    fun readingAnimatedStateValueDuringCompositionThrows() {
+        assertThrows(IllegalStateException::class.java) {
+            rule.testTransition(
+                fromSceneContent = { animateSceneIntAsState(0, TestValues.Value1).value },
+                toSceneContent = {},
+                transition = {},
+            ) {}
+        }
+    }
+
+    @Test
+    fun readingAnimatedStateValueDuringCompositionIsStillPossible() {
+        @Composable
+        fun SceneScope.SceneValuesDuringComposition(
+            targetValues: Values,
+            onCurrentValueChanged: (Values) -> Unit,
+        ) {
+            val int by
+                animateSceneIntAsState(targetValues.int, key = TestValues.Value1)
+                    .unsafeCompositionState(targetValues.int)
+            val float by
+                animateSceneFloatAsState(targetValues.float, key = TestValues.Value2)
+                    .unsafeCompositionState(targetValues.float)
+            val dp by
+                animateSceneDpAsState(targetValues.dp, key = TestValues.Value3)
+                    .unsafeCompositionState(targetValues.dp)
+            val color by
+                animateSceneColorAsState(targetValues.color, key = TestValues.Value4)
+                    .unsafeCompositionState(targetValues.color)
+
+            val values = Values(int, float, dp, color)
+            SideEffect { onCurrentValueChanged(values) }
+        }
+
+        val fromValues = Values(int = 0, float = 0f, dp = 0.dp, color = Color.Red)
+        val toValues = Values(int = 100, float = 100f, dp = 100.dp, color = Color.Blue)
+
+        var lastValueInFrom = fromValues
+        var lastValueInTo = toValues
+
+        rule.testTransition(
+            fromSceneContent = {
+                SceneValuesDuringComposition(
+                    targetValues = fromValues,
+                    onCurrentValueChanged = { lastValueInFrom = it },
+                )
+            },
+            toSceneContent = {
+                SceneValuesDuringComposition(
+                    targetValues = toValues,
+                    onCurrentValueChanged = { lastValueInTo = it },
+                )
+            },
+            transition = {
+                // The transition lasts 64ms = 4 frames.
+                spec = tween(durationMillis = 16 * 4, easing = LinearEasing)
+            },
+        ) {
+            before {
+                assertThat(lastValueInFrom).isEqualTo(fromValues)
+
+                // to was not composed yet, so lastValueInTo was not set yet.
+                assertThat(lastValueInTo).isEqualTo(toValues)
+            }
+
+            at(16) {
+                // Because we are using unsafeCompositionState(), values are one frame behind their
+                // expected progress so at this first frame we are at progress = 0% instead of 25%.
+                val expectedValues = lerp(fromValues, toValues, fraction = 0f)
+                assertThat(lastValueInFrom).isEqualTo(expectedValues)
+                assertThat(lastValueInTo).isEqualTo(expectedValues)
+            }
+
+            at(32) {
+                // One frame behind, so 25% instead of 50%.
+                val expectedValues = lerp(fromValues, toValues, fraction = 0.25f)
+                assertThat(lastValueInFrom).isEqualTo(expectedValues)
+                assertThat(lastValueInTo).isEqualTo(expectedValues)
+            }
+
+            at(48) {
+                // One frame behind, so 50% instead of 75%.
+                val expectedValues = lerp(fromValues, toValues, fraction = 0.5f)
+                assertThat(lastValueInFrom).isEqualTo(expectedValues)
+                assertThat(lastValueInTo).isEqualTo(expectedValues)
+            }
+
+            after {
+                // from should have been last composed at progress = 100% before it is removed from
+                // composition, but given that we are one frame behind the last values are stuck at
+                // 75%.
+                assertThat(lastValueInFrom).isEqualTo(lerp(fromValues, toValues, fraction = 0.75f))
+
+                // The after {} block resumes the clock and will run as many frames as necessary so
+                // that the application is idle, so the toScene settle to the idle state and to the
+                // final values.
                 assertThat(lastValueInTo).isEqualTo(toValues)
             }
         }
diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/ElementScenePickerTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/ElementScenePickerTest.kt
new file mode 100644
index 0000000..3b022e8
--- /dev/null
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/ElementScenePickerTest.kt
@@ -0,0 +1,88 @@
+/*
+ * 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.compose.animation.scene
+
+import androidx.compose.animation.core.LinearEasing
+import androidx.compose.animation.core.tween
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.size
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.test.assertIsDisplayed
+import androidx.compose.ui.test.assertIsNotDisplayed
+import androidx.compose.ui.test.junit4.createComposeRule
+import androidx.compose.ui.unit.dp
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidJUnit4::class)
+class ElementScenePickerTest {
+    @get:Rule val rule = createComposeRule()
+
+    @Test
+    fun highestZIndexPicker() {
+        val key = ElementKey("TestElement", scenePicker = HighestZIndexScenePicker)
+        rule.testTransition(
+            fromSceneContent = { Box(Modifier.element(key).size(10.dp)) },
+            toSceneContent = { Box(Modifier.element(key).size(10.dp)) },
+            transition = { spec = tween(4 * 16, easing = LinearEasing) },
+            fromScene = TestScenes.SceneA,
+            toScene = TestScenes.SceneB,
+        ) {
+            before {
+                onElement(key, TestScenes.SceneA).assertIsDisplayed()
+                onElement(key, TestScenes.SceneB).assertDoesNotExist()
+            }
+            at(32) {
+                // Scene B has the highest index, so the element is placed only there.
+                onElement(key, TestScenes.SceneA).assertExists().assertIsNotDisplayed()
+                onElement(key, TestScenes.SceneB).assertIsDisplayed()
+            }
+            after {
+                onElement(key, TestScenes.SceneA).assertDoesNotExist()
+                onElement(key, TestScenes.SceneB).assertIsDisplayed()
+            }
+        }
+    }
+
+    @Test
+    fun lowestZIndexPicker() {
+        val key = ElementKey("TestElement", scenePicker = LowestZIndexScenePicker)
+        rule.testTransition(
+            fromSceneContent = { Box(Modifier.element(key).size(10.dp)) },
+            toSceneContent = { Box(Modifier.element(key).size(10.dp)) },
+            transition = { spec = tween(4 * 16, easing = LinearEasing) },
+            fromScene = TestScenes.SceneA,
+            toScene = TestScenes.SceneB,
+        ) {
+            before {
+                onElement(key, TestScenes.SceneA).assertIsDisplayed()
+                onElement(key, TestScenes.SceneB).assertDoesNotExist()
+            }
+            at(32) {
+                // Scene A has the lowest index, so the element is placed only there.
+                onElement(key, TestScenes.SceneA).assertIsDisplayed()
+                onElement(key, TestScenes.SceneB).assertExists().assertIsNotDisplayed()
+            }
+            after {
+                onElement(key, TestScenes.SceneA).assertDoesNotExist()
+                onElement(key, TestScenes.SceneB).assertIsDisplayed()
+            }
+        }
+    }
+}
diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/ElementTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/ElementTest.kt
index da5a0a0..54c5de7 100644
--- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/ElementTest.kt
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/ElementTest.kt
@@ -306,7 +306,7 @@
 
         assertThat(layoutImpl.elements.keys).containsExactly(key)
         val element = layoutImpl.elements.getValue(key)
-        assertThat(element.sceneValues.keys).containsExactly(TestScenes.SceneB)
+        assertThat(element.sceneStates.keys).containsExactly(TestScenes.SceneB)
 
         // Scene C, state 0: the same element is reused.
         currentScene = TestScenes.SceneC
@@ -315,7 +315,7 @@
 
         assertThat(layoutImpl.elements.keys).containsExactly(key)
         assertThat(layoutImpl.elements.getValue(key)).isSameInstanceAs(element)
-        assertThat(element.sceneValues.keys).containsExactly(TestScenes.SceneC)
+        assertThat(element.sceneStates.keys).containsExactly(TestScenes.SceneC)
 
         // Scene C, state 1: the same element is reused.
         sceneCState = 1
@@ -323,7 +323,7 @@
 
         assertThat(layoutImpl.elements.keys).containsExactly(key)
         assertThat(layoutImpl.elements.getValue(key)).isSameInstanceAs(element)
-        assertThat(element.sceneValues.keys).containsExactly(TestScenes.SceneC)
+        assertThat(element.sceneStates.keys).containsExactly(TestScenes.SceneC)
 
         // Scene D, state 0: the same element is reused.
         currentScene = TestScenes.SceneD
@@ -332,7 +332,7 @@
 
         assertThat(layoutImpl.elements.keys).containsExactly(key)
         assertThat(layoutImpl.elements.getValue(key)).isSameInstanceAs(element)
-        assertThat(element.sceneValues.keys).containsExactly(TestScenes.SceneD)
+        assertThat(element.sceneStates.keys).containsExactly(TestScenes.SceneD)
 
         // Scene D, state 1: the same element is reused.
         sceneDState = 1
@@ -340,13 +340,13 @@
 
         assertThat(layoutImpl.elements.keys).containsExactly(key)
         assertThat(layoutImpl.elements.getValue(key)).isSameInstanceAs(element)
-        assertThat(element.sceneValues.keys).containsExactly(TestScenes.SceneD)
+        assertThat(element.sceneStates.keys).containsExactly(TestScenes.SceneD)
 
         // Scene D, state 2: the element is removed from the map.
         sceneDState = 2
         rule.waitForIdle()
 
-        assertThat(element.sceneValues).isEmpty()
+        assertThat(element.sceneStates).isEmpty()
         assertThat(layoutImpl.elements).isEmpty()
     }
 
@@ -442,7 +442,7 @@
         // There is only Foo in the elements map.
         assertThat(layoutImpl.elements.keys).containsExactly(TestElements.Foo)
         val fooElement = layoutImpl.elements.getValue(TestElements.Foo)
-        assertThat(fooElement.sceneValues.keys).containsExactly(TestScenes.SceneA)
+        assertThat(fooElement.sceneStates.keys).containsExactly(TestScenes.SceneA)
 
         key = TestElements.Bar
 
@@ -450,8 +450,8 @@
         rule.waitForIdle()
         assertThat(layoutImpl.elements.keys).containsExactly(TestElements.Bar)
         val barElement = layoutImpl.elements.getValue(TestElements.Bar)
-        assertThat(barElement.sceneValues.keys).containsExactly(TestScenes.SceneA)
-        assertThat(fooElement.sceneValues).isEmpty()
+        assertThat(barElement.sceneStates.keys).containsExactly(TestScenes.SceneA)
+        assertThat(fooElement.sceneStates).isEmpty()
     }
 
     @Test
@@ -505,7 +505,7 @@
         // There is only Foo in the elements map.
         assertThat(layoutImpl.elements.keys).containsExactly(TestElements.Foo)
         val element = layoutImpl.elements.getValue(TestElements.Foo)
-        val sceneValues = element.sceneValues
+        val sceneValues = element.sceneStates
         assertThat(sceneValues.keys).containsExactly(TestScenes.SceneA)
 
         // Get the ElementModifier node that should be reused later on when coming back to this
@@ -528,7 +528,7 @@
 
         assertThat(layoutImpl.elements.keys).containsExactly(TestElements.Foo)
         val newElement = layoutImpl.elements.getValue(TestElements.Foo)
-        val newSceneValues = newElement.sceneValues
+        val newSceneValues = newElement.sceneStates
         assertThat(newElement).isNotEqualTo(element)
         assertThat(newSceneValues).isNotEqualTo(sceneValues)
         assertThat(newSceneValues.keys).containsExactly(TestScenes.SceneA)
@@ -579,11 +579,11 @@
 
         fun foo() = layoutImpl().elements[TestElements.Foo] ?: error("Foo not in elements map")
 
-        fun Element.lastSharedOffset() = lastSharedValues.offset.toDpOffset()
+        fun Element.lastSharedOffset() = lastSharedState.offset.toDpOffset()
 
         fun Element.lastOffsetIn(scene: SceneKey) =
-            (sceneValues[scene] ?: error("$scene not in sceneValues map"))
-                .lastValues
+            (sceneStates[scene] ?: error("$scene not in sceneValues map"))
+                .lastState
                 .offset
                 .toDpOffset()
 
diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/MovableElementScenePickerTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/MovableElementScenePickerTest.kt
new file mode 100644
index 0000000..fb46a34
--- /dev/null
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/MovableElementScenePickerTest.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.compose.animation.scene
+
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidJUnit4::class)
+class MovableElementScenePickerTest {
+    @Test
+    fun toSceneInScenes() {
+        val picker = MovableElementScenePicker(scenes = setOf(TestScenes.SceneA, TestScenes.SceneB))
+        assertThat(
+                picker.sceneDuringTransition(
+                    TestElements.Foo,
+                    transition(from = TestScenes.SceneA, to = TestScenes.SceneB),
+                    fromSceneZIndex = 0f,
+                    toSceneZIndex = 1f,
+                )
+            )
+            .isEqualTo(TestScenes.SceneB)
+    }
+
+    @Test
+    fun toSceneNotInScenes() {
+        val picker = MovableElementScenePicker(scenes = emptySet())
+        assertThat(
+                picker.sceneDuringTransition(
+                    TestElements.Foo,
+                    transition(from = TestScenes.SceneA, to = TestScenes.SceneB),
+                    fromSceneZIndex = 0f,
+                    toSceneZIndex = 1f,
+                )
+            )
+            .isEqualTo(TestScenes.SceneA)
+    }
+}
diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/MovableElementTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/MovableElementTest.kt
index 3cd65cd..35cb691 100644
--- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/MovableElementTest.kt
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/MovableElementTest.kt
@@ -28,19 +28,24 @@
 import androidx.compose.runtime.mutableIntStateOf
 import androidx.compose.runtime.remember
 import androidx.compose.runtime.setValue
+import androidx.compose.ui.Alignment
 import androidx.compose.ui.Modifier
+import androidx.compose.ui.platform.testTag
 import androidx.compose.ui.test.assertIsDisplayed
 import androidx.compose.ui.test.assertIsNotDisplayed
+import androidx.compose.ui.test.assertPositionInRootIsEqualTo
 import androidx.compose.ui.test.hasParent
 import androidx.compose.ui.test.hasText
 import androidx.compose.ui.test.junit4.createComposeRule
 import androidx.compose.ui.test.onAllNodesWithText
+import androidx.compose.ui.test.onNodeWithTag
 import androidx.compose.ui.test.onNodeWithText
 import androidx.compose.ui.test.performClick
 import androidx.compose.ui.unit.dp
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import com.android.compose.test.assertSizeIsEqualTo
 import com.google.common.truth.Truth.assertThat
+import org.junit.Ignore
 import org.junit.Rule
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -58,7 +63,7 @@
 
     @Composable
     private fun SceneScope.MovableCounter(key: ElementKey, modifier: Modifier) {
-        MovableElement(key, modifier) { Counter() }
+        MovableElement(key, modifier) { content { Counter() } }
     }
 
     @Test
@@ -142,39 +147,37 @@
 
     @Test
     fun movableElementIsMovedAndComposedOnlyOnce() {
-        rule.testTransition(
-            fromSceneContent = { MovableCounter(TestElements.Foo, Modifier.size(50.dp)) },
-            toSceneContent = { MovableCounter(TestElements.Foo, Modifier.size(100.dp)) },
-            transition = {
-                spec = tween(durationMillis = 16 * 4, easing = LinearEasing)
-                sharedElement(
-                    TestElements.Foo,
-                    scenePicker =
-                        object : SharedElementScenePicker {
-                            override fun sceneDuringTransition(
-                                element: ElementKey,
-                                fromScene: SceneKey,
-                                toScene: SceneKey,
-                                progress: () -> Float,
-                                fromSceneZIndex: Float,
-                                toSceneZIndex: Float
-                            ): SceneKey {
-                                assertThat(fromScene).isEqualTo(TestScenes.SceneA)
-                                assertThat(toScene).isEqualTo(TestScenes.SceneB)
-                                assertThat(fromSceneZIndex).isEqualTo(0)
-                                assertThat(toSceneZIndex).isEqualTo(1)
+        val key =
+            ElementKey(
+                "Foo",
+                scenePicker =
+                    object : ElementScenePicker {
+                        override fun sceneDuringTransition(
+                            element: ElementKey,
+                            transition: TransitionState.Transition,
+                            fromSceneZIndex: Float,
+                            toSceneZIndex: Float
+                        ): SceneKey {
+                            assertThat(transition.fromScene).isEqualTo(TestScenes.SceneA)
+                            assertThat(transition.toScene).isEqualTo(TestScenes.SceneB)
+                            assertThat(fromSceneZIndex).isEqualTo(0)
+                            assertThat(toSceneZIndex).isEqualTo(1)
 
-                                // Compose Foo in Scene A if progress < 0.65f, otherwise compose it
-                                // in Scene B.
-                                return if (progress() < 0.65f) {
-                                    TestScenes.SceneA
-                                } else {
-                                    TestScenes.SceneB
-                                }
+                            // Compose Foo in Scene A if progress < 0.65f, otherwise compose it
+                            // in Scene B.
+                            return if (transition.progress < 0.65f) {
+                                TestScenes.SceneA
+                            } else {
+                                TestScenes.SceneB
                             }
                         }
-                )
-            },
+                    }
+            )
+
+        rule.testTransition(
+            fromSceneContent = { MovableCounter(key, Modifier.size(50.dp)) },
+            toSceneContent = { MovableCounter(key, Modifier.size(100.dp)) },
+            transition = { spec = tween(durationMillis = 16 * 4, easing = LinearEasing) },
             fromScene = TestScenes.SceneA,
             toScene = TestScenes.SceneB,
         ) {
@@ -257,4 +260,73 @@
             }
         }
     }
+
+    @Test
+    @Ignore("b/317972419#comment2")
+    fun movableElementContentIsRecomposedIfContentParametersChange() {
+        @Composable
+        fun SceneScope.MovableFoo(text: String, modifier: Modifier = Modifier) {
+            MovableElement(TestElements.Foo, modifier) { content { Text(text) } }
+        }
+
+        rule.testTransition(
+            fromSceneContent = { MovableFoo(text = "fromScene") },
+            toSceneContent = { MovableFoo(text = "toScene") },
+            transition = { spec = tween(durationMillis = 16 * 4, easing = LinearEasing) },
+            fromScene = TestScenes.SceneA,
+            toScene = TestScenes.SceneB,
+        ) {
+            // Before the transition, only fromScene is composed.
+            before {
+                rule.onNodeWithText("fromScene").assertIsDisplayed()
+                rule.onNodeWithText("toScene").assertDoesNotExist()
+            }
+
+            // During the transition, the element is composed in toScene.
+            at(32) {
+                rule.onNodeWithText("fromScene").assertDoesNotExist()
+                rule.onNodeWithText("toScene").assertIsDisplayed()
+            }
+
+            // At the end of the transition, the element is composed in toScene.
+            after {
+                rule.onNodeWithText("fromScene").assertDoesNotExist()
+                rule.onNodeWithText("toScene").assertIsDisplayed()
+            }
+        }
+    }
+
+    @Test
+    fun elementScopeExtendsBoxScope() {
+        rule.setContent {
+            TestSceneScope {
+                Element(TestElements.Foo, Modifier.size(200.dp)) {
+                    content {
+                        Box(Modifier.testTag("bottomEnd").align(Alignment.BottomEnd))
+                        Box(Modifier.testTag("matchParentSize").matchParentSize())
+                    }
+                }
+            }
+        }
+
+        rule.onNodeWithTag("bottomEnd").assertPositionInRootIsEqualTo(200.dp, 200.dp)
+        rule.onNodeWithTag("matchParentSize").assertSizeIsEqualTo(200.dp, 200.dp)
+    }
+
+    @Test
+    fun movableElementScopeExtendsBoxScope() {
+        rule.setContent {
+            TestSceneScope {
+                MovableElement(TestElements.Foo, Modifier.size(200.dp)) {
+                    content {
+                        Box(Modifier.testTag("bottomEnd").align(Alignment.BottomEnd))
+                        Box(Modifier.testTag("matchParentSize").matchParentSize())
+                    }
+                }
+            }
+        }
+
+        rule.onNodeWithTag("bottomEnd").assertPositionInRootIsEqualTo(200.dp, 200.dp)
+        rule.onNodeWithTag("matchParentSize").assertSizeIsEqualTo(200.dp, 200.dp)
+    }
 }
diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneTransitionLayoutStateTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneTransitionLayoutStateTest.kt
index c5b8d9a..75dee47 100644
--- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneTransitionLayoutStateTest.kt
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneTransitionLayoutStateTest.kt
@@ -50,13 +50,4 @@
         assertThat(state.isTransitioning(to = TestScenes.SceneA)).isFalse()
         assertThat(state.isTransitioning(from = TestScenes.SceneA, to = TestScenes.SceneB)).isTrue()
     }
-
-    private fun transition(from: SceneKey, to: SceneKey): TransitionState.Transition {
-        return object : TransitionState.Transition(from, to) {
-            override val currentScene: SceneKey = from
-            override val progress: Float = 0f
-            override val isInitiatedByUserInput: Boolean = false
-            override val isUserInputOngoing: Boolean = false
-        }
-    }
 }
diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneTransitionLayoutTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneTransitionLayoutTest.kt
index ebbd500..649e499 100644
--- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneTransitionLayoutTest.kt
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneTransitionLayoutTest.kt
@@ -113,25 +113,21 @@
 
     @Composable
     private fun SceneScope.SharedFoo(size: Dp, childOffset: Dp, modifier: Modifier = Modifier) {
-        Box(
-            modifier
-                .size(size)
-                .background(Color.Red)
-                .element(TestElements.Foo)
-                .testTag(TestElements.Foo.debugName)
-        ) {
+        Element(TestElements.Foo, modifier.size(size).background(Color.Red)) {
             // Offset the single child of Foo by some animated shared offset.
-            val offset by animateSharedDpAsState(childOffset, TestValues.Value1, TestElements.Foo)
+            val offset by animateElementDpAsState(childOffset, TestValues.Value1)
 
-            Box(
-                Modifier.offset {
-                        val pxOffset = offset.roundToPx()
-                        IntOffset(pxOffset, pxOffset)
-                    }
-                    .size(30.dp)
-                    .background(Color.Blue)
-                    .testTag(TestElements.Bar.debugName)
-            )
+            content {
+                Box(
+                    Modifier.offset {
+                            val pxOffset = offset.roundToPx()
+                            IntOffset(pxOffset, pxOffset)
+                        }
+                        .size(30.dp)
+                        .background(Color.Blue)
+                        .testTag(TestElements.Bar.debugName)
+                )
+            }
         }
     }
 
diff --git a/packages/SystemUI/compose/scene/tests/utils/src/com/android/compose/animation/scene/Transition.kt b/packages/SystemUI/compose/scene/tests/utils/src/com/android/compose/animation/scene/Transition.kt
new file mode 100644
index 0000000..238b21e1
--- /dev/null
+++ b/packages/SystemUI/compose/scene/tests/utils/src/com/android/compose/animation/scene/Transition.kt
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.compose.animation.scene
+
+/** A utility to easily create a [TransitionState.Transition] in tests. */
+fun transition(
+    from: SceneKey,
+    to: SceneKey,
+    progress: () -> Float = { 0f },
+    isInitiatedByUserInput: Boolean = false,
+    isUserInputOngoing: Boolean = false,
+): TransitionState.Transition {
+    return object : TransitionState.Transition(from, to) {
+        override val currentScene: SceneKey = from
+        override val progress: Float = progress()
+        override val isInitiatedByUserInput: Boolean = isInitiatedByUserInput
+        override val isUserInputOngoing: Boolean = isUserInputOngoing
+    }
+}
diff --git a/packages/SystemUI/docs/imgs/ribbon.png b/packages/SystemUI/docs/imgs/ribbon.png
new file mode 100644
index 0000000..9f57652
--- /dev/null
+++ b/packages/SystemUI/docs/imgs/ribbon.png
Binary files differ
diff --git a/packages/SystemUI/docs/scene.md b/packages/SystemUI/docs/scene.md
new file mode 100644
index 0000000..3e4a1b4
--- /dev/null
+++ b/packages/SystemUI/docs/scene.md
@@ -0,0 +1,297 @@
+# The Scene Framework
+
+Known internally as "Flexiglass", this framework defines a graph where each node
+is a "scene" and each edge between the scenes is a transition. The scenes are
+the main components of System UI, on phones these are: the lockscreen, bouncer,
+shade, and quick settings panels/views/screens). Each scene is a standalone
+experience.
+
+The **main goal** of the framework is to increase code health by applying
+[Separation of concerns](https://en.wikipedia.org/wiki/Separation_of_concerns)
+over several dimensions:
+
+1.  Each scene is a standalone piece of UI; their code doesn't need to concern
+    itself with either transition animations or anything in other scenes. This
+    frees the developer to be able to focus only on the content of the UI for
+    that scene.
+2.  Transition definitions (which scene leads to which other scene following
+    which user action) are pulled out and separated from the content of the UI.
+3.  Transition animations (the effects that happen alongside the gradual change
+    from one scene to another) are also pulled out and separated from the
+    content of the UI.
+
+In addition to the above, some of the **secondary goals** are: 4. Make
+**customization easier**: by separating scenes to standalone pieces, it becomes
+possible for variant owners and OEMs to exclude or replace certain scenes or to
+add brand-new scenes. 5. **Enable modularization**: by separating scenes to
+standalone pieces, it becomes possible to break down System UI into smaller
+codebases, each one of which could be built on its own. Note: this isn't part of
+the scene framework itself but is something that can be done more easily once
+the scene framework is in place.
+
+## Terminology
+
+*   **Scene** a collection of UI elements in a layout that, together, make up a
+    "screen" or "page" that is as large as the container. Scenes can be
+    navigated between / transition to/from. To learn more, please see
+    [this section](#Defining-a-scene).
+*   **Element** (or "UI element") a single unit of UI within a scene. One scene
+    can arrange multiple elements within a layout structure.
+*   **Transition** the gradual switching from one scene to another scene. There
+    are two kinds: [user-driven](Scene-navigation) and
+    [automatic](Automatic-scene-transitions) scene transitions.
+*   **Transition animation** the set of UI effects that occurs while/during a
+    transition. These can apply to the entire scene or to specific elements in
+    the scene. To learn more, please see
+    [this section](#Scene-transition-animations).
+*   **Scene container** (or just "container") the root piece of UI (typically a
+    `@Composable` function) that sets up all the scenes, their transitions, etc.
+    To learn more, please see [this section](#Scene-container).
+*   **Container configuration** (or just "configuration") the collection of
+    scenes and some added information about the desired behaviour of a
+    container. To learn more, please see
+    [this section](#Scene-container-configuration).
+
+## Enabling the framework
+
+As of the end of 2023, the scene framework is under development; as such, it is
+disabled by default. For those who are interested in a preview, please follow
+the instructions below to turn it on.
+
+NOTE: in case these instructions become stale and don't actually enable the
+framework, please make sure `SceneContainerFlag.isEnabled` in the
+[`SceneContainerFlags.kt`](https://cs.android.com/android/platform/superproject/main/+/main:frameworks/base/packages/SystemUI/src/com/android/systemui/scene/shared/flag/SceneContainerFlags.kt)
+file evalutes to `true`.
+
+1.  Set **`SCENE_CONTAINER_ENABLED`** to `true` in the
+    [`Flags.kt`](https://cs.android.com/android/platform/superproject/main/+/main:frameworks/base/packages/SystemUI/src/com/android/systemui/flags/Flags.kt)
+    file
+2.  Set the **`migrate_keyguard_status_bar_view`** classic flag to `true` by
+    running: `console $ adb shell statusbar cmd migrate_keyguard_status_bar_view
+    true`
+3.  Set a collection of **aconfig flags** to `true` by running the following
+    commands: `console $ adb shell device_config put systemui
+    com.android.systemui.scene_container true $ adb shell device_config put
+    systemui com.android.systemui.keyguard_bottom_area_refactor true $ adb shell
+    device_config put systemui
+    com.android.systemui.keyguard_shade_migration_nssl true $ adb shell
+    device_config put systemui com.android.systemui.media_in_scene_container
+    true`
+4.  **Restart** System UI by issuing the following command: `console $ adb shell
+    am crash com.android.systemui`
+5.  **Verify** that the scene framework was turned on. There are two ways to do
+    this:
+
+    *(a)* look for the sash/ribbon UI at the bottom-right corner of the display:
+    ![ribbon](imgs/ribbon.png)
+
+    NOTE: this will be removed proper to the actual release of the framework.
+
+    *(b)* Turn on logging and look for the logging statements in `logcat`:
+    ```console
+
+    # Turn on logging from the framework:
+
+    $ adb shell cmd statusbar echo -b SceneFramework:verbose
+
+# Look for the log statements from the framework:
+
+$ adb logcat -v time SceneFramework:* *:S ```
+
+To **disable** the framework, simply turn off the main aconfig flag: `console $
+adb shell device_config put systemui com.android.systemui.scene_container false`
+
+## Defining a scene
+
+Each scene is defined as an implementation of the
+[`ComposableScene`](https://cs.android.com/android/platform/superproject/main/+/main:frameworks/base/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/ComposableScene.kt)
+interface, which has three parts: 1. The `key` property returns the
+[`SceneKey`](https://cs.android.com/android/platform/superproject/main/+/main:frameworks/base/packages/SystemUI/src/com/android/systemui/scene/shared/model/SceneKey.kt)
+that uniquely identifies that scene 2. The `destinationScenes` `Flow` returns
+the (potentially ever-changing) set of navigation edges to other scenes, based
+on user-actions, which is how the navigation graph is defined (see
+[the Scene navigation](#Scene-navigation) section for more) 3. The `Content`
+function which uses
+[Jetpack Compose](https://developer.android.com/jetpack/compose) to declare of
+the UI itself. This is the UI "at rest", e.g. once there is no transition
+between any two scenes. The Scene Framework has other ways to define how the
+content of your UI changes with and throughout a transition to learn more please
+see the [Scene transition animations](#Scene-transition-animations) section
+
+For example: ```kotlin @SysUISingleton class YourScene @Inject constructor( //
+your dependencies here ) : ComposableScene { override val key =
+SceneKey.YourScene
+
+```
+override val destinationScenes: StateFlow<Map<UserAction, SceneModel>> =
+    MutableStateFlow<Map<UserAction, SceneModel>>(
+        mapOf(
+            // This is where scene navigation is defined, more on that below.
+        )
+    ).asStateFlow()
+
+@Composable
+override fun SceneScope.Content(
+    modifier: Modifier,
+) {
+    // This is where the UI is defined using Jetpack Compose.
+}
+```
+
+} ```
+
+### Injecting scenes
+
+Scenes are injected into the Dagger dependency graph from the
+[`SceneModule`](https://cs.android.com/android/platform/superproject/main/+/main:frameworks/base/packages/SystemUI/compose/facade/enabled/src/com/android/systemui/scene/ui/composable/SceneModule.kt;l=35-50;drc=564f233d5b597aedf06961c76e582464eebe8ba6).
+
+## Scene navigation
+
+As seen above, each scene is responsible for providing an observable `Flow` of a
+`Map` that connects `UserAction` (for example: swipe down, swipe up, back
+button/gesture, etc.) keys to `SceneModel` destinations. This is how the scene
+navigation graph is defined.
+
+NOTE: this controls *only* user-input based navigation. To learn about the other
+type of scene navigation, please see the
+[Automatic scene transitions](#Automatic-scene-transitions) section.
+
+Because this is a `Flow`, scene implemetations should feel free to emit new
+values over time. For example, the `Lockscreen` scene ties the "swipe up" user
+action to go to the `Bouncer` scene if the device is still locked or to go to
+the `Gone` scene if the device is unlocked, allowing the user to dismiss the
+lockscreen UI when not locked.
+
+## Scene transition animations
+
+The Scene Framework separates transition animations from content UI declaration
+by placing the definition of the former in a different location. This way,
+there's no longer a need to contaminate the content UI declaration with
+animation logic, a practice that becomes unscalable over time.
+
+Under the hood, the Scene Framework uses
+[`SceneTransitionLayout`](https://cs.android.com/android/platform/superproject/main/+/main:frameworks/base/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/SceneTransitionLayout.kt),
+a `@Composable` function designed with scene graph and transitions in mind. In
+fact, the Scene Framework is merely a shallow wrapper around
+`SceneTransitionLayout`.
+
+The `SceneTransitionLayout` API requires the transitions to be passed-in
+separately from the scenes themselves. In System UI, the transitions can be
+found in
+[`SceneContainerTransitions`](https://cs.android.com/android/platform/superproject/main/+/main:frameworks/base/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneContainerTransitions.kt).
+As you can see, each possible scene-to-scene transition has its own builder,
+here's one example:
+
+```kotlin
+fun TransitionBuilder.lockscreenToShadeTransition() {
+    spec = tween(durationMillis = 500)
+
+    punchHole(Shade.Elements.QuickSettings, bounds = Shade.Elements.Scrim, Shade.Shapes.Scrim)
+    translate(Shade.Elements.Scrim, Edge.Top, startsOutsideLayoutBounds = false)
+    fractionRange(end = 0.5f) {
+        fade(Shade.Elements.ScrimBackground)
+        translate(
+            QuickSettings.Elements.CollapsedGrid,
+            Edge.Top,
+            startsOutsideLayoutBounds = false,
+        )
+    }
+    fractionRange(start = 0.5f) { fade(Notifications.Elements.Notifications) }
+}
+```
+
+Going through the example code: * The `spec` is the animation that should be
+invoked, in the example above, we use a `tween` animation with a duration of 500
+milliseconds * Then there's a series of function calls: `punchHole` applies a
+clip mask to the `Scrim` element in the destination scene (in this case it's the
+`Shade` scene) which has the position and size determined by the `bounds`
+parameter and the shape passed into the `shape` parameter. This lets the
+`Lockscreen` scene render "through" the `Shade` scene * The `translate` call
+shifts the `Scrim` element to/from the `Top` edge of the scene container * The
+first `fractionRange` wrapper tells the system to apply its contained functions
+only during the first half of the transition. Inside of it, we see a `fade` of
+the `ScrimBackground` element and a `translate` o the `CollpasedGrid` element
+to/from the `Top` edge * The second `fractionRange` only starts at the second
+half of the transition (e.g. when the previous one ends) and applies a `fade` on
+the `Notifications` element
+
+You can find the actual documentation for this API
+[here](https://cs.android.com/android/platform/superproject/main/+/main:frameworks/base/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/TransitionDsl.kt).
+
+### Tagging elements
+
+As demonstrated above, elements within a scene can be addressed from transition
+defintions. In order to "tag" an element with a specific `ElementKey`, the
+[`element` modifier](https://cs.android.com/android/platform/superproject/main/+/main:frameworks/base/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/SceneTransitionLayout.kt)
+must be used on the composable that declared that element's UI:
+
+```kotlin
+Text(
+    text = "Some text",
+    modifier = Modifier.element(MyElements.SomeText),
+)
+```
+
+In addition to the ability to refer to a tagged element in transition
+definitions, if the same `ElementKey` is used for one element in the current
+scene and another element in the destination scene, the element is considered to
+be a **shared element**. As such, the framework automatically translates and
+scales the bounds of the shared element from its current bounds in the source
+scene to its final bounds in the destination scene.
+
+## Scene container
+
+To set up a scene framework instance, a scene container must be declared. This
+is the root of an entire scene graph that puts together the scenes, their
+transitions, and the configuration. The container is then added to a parent
+`@Composable` or `View` so it can be displayed.
+
+The default scene container in System UI is defined in the
+[`SceneContainer.kt` file](https://cs.android.com/android/platform/superproject/main/+/main:frameworks/base/packages/SystemUI/compose/features/src/com/android/systemui/scene/ui/composable/SceneContainer.kt).
+
+### Scene container configuration
+
+The `SceneContainer` function is passed a few parameters including a view-model
+and a set of scenes. The exact details of what gets passed in depends on the
+[`SceneContainerConfig` object](https://cs.android.com/android/platform/superproject/main/+/main:frameworks/base/packages/SystemUI/src/com/android/systemui/scene/shared/model/SceneContainerConfig.kt)
+which is injected into the Dagger dependency graph
+[here](https://cs.android.com/android/platform/superproject/main/+/main:frameworks/base/packages/SystemUI/src/com/android/systemui/scene/shared/model/SceneContainerConfigModule.kt).
+
+## Automatic scene transitions
+
+The scene framework supports the ability for scenes to change automatically
+based on device state or events other than direct user input. For example: when
+the device is locked, there's an automatic scene transition to the `Lockscreen`
+scene.
+
+This logic is contained within the
+[`SceneContainerStartable`](https://cs.android.com/android/platform/superproject/main/+/main:frameworks/base/packages/SystemUI/src/com/android/systemui/scene/domain/startable/SceneContainerStartable.kt)
+class.
+
+## Side-effects
+
+Similarly to [the above](#Automatic-scene-transitions), the
+`SceneContainerStartable` also handles side-effects by updating other parts of
+the System UI codebase whenever internal scene framework state changes. As an
+example: the visibility of the `View` that contains our
+[scene container](#Scene-container) is updated every time there's a transition
+to or from the `Gone` scene.
+
+## Observing scene transition state
+
+There are a couple of ways to observe the transition state:
+
+1.  [Easiest] using the `SceneScope` of the scene container, simply use the
+    `animateSharedXAsState` API, the full list is
+    [here](https://cs.android.com/android/platform/superproject/main/+/main:frameworks/base/packages/SystemUI/compose/core/src/com/android/compose/animation/scene/AnimateSharedAsState.kt).
+2.  [Harder] if outside the `SceneScope` of the scene container, observe
+    [`SceneInteractor.transitionState`](https://cs.android.com/android/platform/superproject/main/+/main:frameworks/base/packages/SystemUI/src/com/android/systemui/scene/domain/interactor/SceneInteractor.kt;l=88;drc=af57d5e49431c6728e7cf192bada88e0541ebf0c).
+
+## Dependency Injection
+
+The entire framework is provided into the Dagger dependency graph from the
+top-level Dagger module at
+[`SceneContainerFrameworkModule`](https://cs.android.com/android/platform/superproject/main/+/main:frameworks/base/packages/SystemUI/src/com/android/systemui/scene/SceneContainerFrameworkModule.kt)
+this puts together the scenes from `SceneModule`, the configuration from
+`SceneContainerConfigModule`, and the startable from
+`SceneContainerStartableModule`.
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/SysuiTestCaseSelfTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/SysuiTestCaseSelfTest.kt
new file mode 100644
index 0000000..be6bb9c
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/SysuiTestCaseSelfTest.kt
@@ -0,0 +1,35 @@
+/*
+ * 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
+
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.google.common.truth.Truth
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class SysuiTestCaseSelfTest : SysuiTestCase() {
+    private val contextBeforeSetup = context
+
+    // cf b/311612168
+    @Test
+    fun captureCorrectContextBeforeSetupRuns() {
+        Truth.assertThat(contextBeforeSetup).isEqualTo(context)
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/SideFpsSensorInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/domain/interactor/SideFpsSensorInteractorTest.kt
similarity index 84%
rename from packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/SideFpsSensorInteractorTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/domain/interactor/SideFpsSensorInteractorTest.kt
index 640807b..8d6d052 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/SideFpsSensorInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/domain/interactor/SideFpsSensorInteractorTest.kt
@@ -24,6 +24,7 @@
 import android.view.WindowInsets
 import android.view.WindowManager
 import android.view.WindowMetrics
+import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.biometrics.FingerprintInteractiveToAuthProvider
@@ -36,15 +37,24 @@
 import com.android.systemui.biometrics.shared.model.FingerprintSensorType
 import com.android.systemui.biometrics.shared.model.SensorStrength
 import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
+import com.android.systemui.keyguard.domain.interactor.keyguardTransitionInteractor
+import com.android.systemui.keyguard.shared.model.KeyguardState
+import com.android.systemui.keyguard.shared.model.KeyguardState.DOZING
+import com.android.systemui.keyguard.shared.model.KeyguardState.LOCKSCREEN
+import com.android.systemui.keyguard.shared.model.KeyguardState.OFF
+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.log.SideFpsLogger
 import com.android.systemui.log.logcatLogBuffer
 import com.android.systemui.res.R
+import com.android.systemui.testKosmos
 import com.android.systemui.util.mockito.whenever
 import com.google.common.truth.Truth.assertThat
 import java.util.Optional
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.flow.MutableStateFlow
-import kotlinx.coroutines.test.StandardTestDispatcher
 import kotlinx.coroutines.test.TestScope
 import kotlinx.coroutines.test.runCurrent
 import kotlinx.coroutines.test.runTest
@@ -52,7 +62,6 @@
 import org.junit.Rule
 import org.junit.Test
 import org.junit.runner.RunWith
-import org.junit.runners.JUnit4
 import org.mockito.Mock
 import org.mockito.Mockito.mock
 import org.mockito.Mockito.spy
@@ -60,11 +69,12 @@
 
 @OptIn(ExperimentalCoroutinesApi::class)
 @SmallTest
-@RunWith(JUnit4::class)
+@RunWith(AndroidJUnit4::class)
 class SideFpsSensorInteractorTest : SysuiTestCase() {
+    private val kosmos = testKosmos()
 
     @JvmField @Rule var mockitoRule = MockitoJUnit.rule()
-    private val testScope = TestScope(StandardTestDispatcher())
+    private val testScope = kosmos.testScope
 
     private val fingerprintRepository = FakeFingerprintPropertyRepository()
 
@@ -94,6 +104,7 @@
         contextDisplayInfo.uniqueId = "current-display"
         whenever(fingerprintInteractiveToAuthProvider.enabledForCurrentUser)
             .thenReturn(isRestToUnlockEnabled)
+        overrideResource(R.bool.config_restToUnlockSupported, true)
         underTest =
             SideFpsSensorInteractor(
                 mContext,
@@ -101,6 +112,7 @@
                 windowManager,
                 displayStateInteractor,
                 Optional.of(fingerprintInteractiveToAuthProvider),
+                kosmos.keyguardTransitionInteractor,
                 SideFpsLogger(logcatLogBuffer("SfpsLogger"))
             )
     }
@@ -129,11 +141,62 @@
             assertThat(isAvailable).isFalse()
         }
 
+    private suspend fun sendTransition(from: KeyguardState, to: KeyguardState) {
+        kosmos.fakeKeyguardTransitionRepository.sendTransitionSteps(
+            listOf(
+                TransitionStep(
+                    from = from,
+                    to = to,
+                    transitionState = TransitionState.STARTED,
+                ),
+                TransitionStep(
+                    from = from,
+                    to = to,
+                    transitionState = TransitionState.FINISHED,
+                    value = 1.0f
+                )
+            ),
+            testScope
+        )
+    }
+
     @Test
-    fun authenticationDurationIsAvailableWhenSFPSSensorIsAvailable() =
+    fun authenticationDurationIsLongerIfScreenIsOff() =
         testScope.runTest {
-            assertThat(underTest.authenticationDuration)
-                .isEqualTo(context.resources.getInteger(R.integer.config_restToUnlockDuration))
+            val authenticationDuration by collectLastValue(underTest.authenticationDuration)
+            val longDuration =
+                context.resources.getInteger(R.integer.config_restToUnlockDurationScreenOff)
+            sendTransition(LOCKSCREEN, OFF)
+
+            runCurrent()
+            assertThat(authenticationDuration).isEqualTo(longDuration)
+        }
+
+    @Test
+    fun authenticationDurationIsLongerIfScreenIsDozing() =
+        testScope.runTest {
+            val authenticationDuration by collectLastValue(underTest.authenticationDuration)
+            val longDuration =
+                context.resources.getInteger(R.integer.config_restToUnlockDurationScreenOff)
+            sendTransition(LOCKSCREEN, DOZING)
+            runCurrent()
+            assertThat(authenticationDuration).isEqualTo(longDuration)
+        }
+
+    @Test
+    fun authenticationDurationIsShorterIfScreenIsNotDozingOrOff() =
+        testScope.runTest {
+            val authenticationDuration by collectLastValue(underTest.authenticationDuration)
+            val shortDuration =
+                context.resources.getInteger(R.integer.config_restToUnlockDurationDefault)
+            val allOtherKeyguardStates = KeyguardState.entries.filter { it != OFF && it != DOZING }
+
+            allOtherKeyguardStates.forEach { destinationState ->
+                sendTransition(OFF, destinationState)
+
+                runCurrent()
+                assertThat(authenticationDuration).isEqualTo(shortDuration)
+            }
         }
 
     @Test
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/common/data/repository/PackageChangeRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/common/data/repository/PackageChangeRepositoryTest.kt
new file mode 100644
index 0000000..6380ace
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/common/data/repository/PackageChangeRepositoryTest.kt
@@ -0,0 +1,209 @@
+/*
+ * 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.common.data.repository
+
+import android.content.Context
+import android.content.pm.PackageManager
+import android.os.Handler
+import android.os.UserHandle
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.common.data.shared.model.PackageChangeModel
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.coroutines.collectValues
+import com.android.systemui.kosmos.applicationCoroutineScope
+import com.android.systemui.kosmos.testDispatcher
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.log.logcatLogBuffer
+import com.android.systemui.testKosmos
+import com.android.systemui.util.mockito.whenever
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.MockitoAnnotations
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class PackageChangeRepositoryTest : SysuiTestCase() {
+
+    private val kosmos = testKosmos()
+
+    @Mock private lateinit var context: Context
+    @Mock private lateinit var packageManager: PackageManager
+    @Mock private lateinit var handler: Handler
+
+    private lateinit var repository: PackageChangeRepository
+    private lateinit var updateMonitor: PackageUpdateMonitor
+
+    @Before
+    fun setUp() =
+        with(kosmos) {
+            MockitoAnnotations.initMocks(this@PackageChangeRepositoryTest)
+            whenever(context.packageManager).thenReturn(packageManager)
+
+            repository = PackageChangeRepositoryImpl { user ->
+                updateMonitor =
+                    PackageUpdateMonitor(
+                        user = user,
+                        bgDispatcher = testDispatcher,
+                        scope = applicationCoroutineScope,
+                        context = context,
+                        bgHandler = handler,
+                        logger = PackageUpdateLogger(logcatLogBuffer())
+                    )
+                updateMonitor
+            }
+        }
+
+    @Test
+    fun packageUninstalled() =
+        with(kosmos) {
+            testScope.runTest {
+                val packageChange by collectLastValue(repository.packageChanged(USER_100))
+                assertThat(packageChange).isNull()
+
+                updateMonitor.onPackageRemoved(
+                    packageName = TEST_PACKAGE,
+                    uid = UserHandle.getUid(/* userId = */ 100, /* appId = */ 10)
+                )
+
+                assertThat(packageChange).isInstanceOf(PackageChangeModel.Uninstalled::class.java)
+                assertThat(packageChange?.packageName).isEqualTo(TEST_PACKAGE)
+            }
+        }
+
+    @Test
+    fun packageUpdateStarted() =
+        with(kosmos) {
+            testScope.runTest {
+                val packageChange by collectLastValue(repository.packageChanged(USER_100))
+                assertThat(packageChange).isNull()
+
+                updateMonitor.onPackageUpdateStarted(
+                    packageName = TEST_PACKAGE,
+                    uid = UserHandle.getUid(/* userId = */ 100, /* appId = */ 10)
+                )
+
+                assertThat(packageChange).isInstanceOf(PackageChangeModel.UpdateStarted::class.java)
+                assertThat(packageChange?.packageName).isEqualTo(TEST_PACKAGE)
+            }
+        }
+
+    @Test
+    fun packageUpdateFinished() =
+        with(kosmos) {
+            testScope.runTest {
+                val packageChange by collectLastValue(repository.packageChanged(USER_100))
+                assertThat(packageChange).isNull()
+
+                updateMonitor.onPackageUpdateFinished(
+                    packageName = TEST_PACKAGE,
+                    uid = UserHandle.getUid(/* userId = */ 100, /* appId = */ 10)
+                )
+
+                assertThat(packageChange)
+                    .isInstanceOf(PackageChangeModel.UpdateFinished::class.java)
+                assertThat(packageChange?.packageName).isEqualTo(TEST_PACKAGE)
+            }
+        }
+
+    @Test
+    fun packageInstalled() =
+        with(kosmos) {
+            testScope.runTest {
+                val packageChange by collectLastValue(repository.packageChanged(UserHandle.ALL))
+                assertThat(packageChange).isNull()
+
+                updateMonitor.onPackageAdded(
+                    packageName = TEST_PACKAGE,
+                    uid = UserHandle.getUid(/* userId = */ 100, /* appId = */ 10)
+                )
+
+                assertThat(packageChange).isInstanceOf(PackageChangeModel.Installed::class.java)
+                assertThat(packageChange?.packageName).isEqualTo(TEST_PACKAGE)
+            }
+        }
+
+    @Test
+    fun packageIsChanged() =
+        with(kosmos) {
+            testScope.runTest {
+                val packageChange by collectLastValue(repository.packageChanged(USER_100))
+                assertThat(packageChange).isNull()
+
+                updateMonitor.onPackageChanged(
+                    packageName = TEST_PACKAGE,
+                    uid = UserHandle.getUid(/* userId = */ 100, /* appId = */ 10),
+                    components = emptyArray()
+                )
+
+                assertThat(packageChange).isInstanceOf(PackageChangeModel.Changed::class.java)
+                assertThat(packageChange?.packageName).isEqualTo(TEST_PACKAGE)
+            }
+        }
+
+    @Test
+    fun filtersOutUpdatesFromOtherUsers() =
+        with(kosmos) {
+            testScope.runTest {
+                val packageChange by collectLastValue(repository.packageChanged(USER_100))
+                assertThat(packageChange).isNull()
+
+                updateMonitor.onPackageUpdateFinished(
+                    packageName = TEST_PACKAGE,
+                    uid = UserHandle.getUid(/* userId = */ 101, /* appId = */ 10)
+                )
+
+                updateMonitor.onPackageAdded(
+                    packageName = TEST_PACKAGE,
+                    uid = UserHandle.getUid(/* userId = */ 99, /* appId = */ 10)
+                )
+
+                assertThat(packageChange).isNull()
+            }
+        }
+
+    @Test
+    fun listenToUpdatesFromAllUsers() =
+        with(kosmos) {
+            testScope.runTest {
+                val packageChanges by collectValues(repository.packageChanged(UserHandle.ALL))
+                assertThat(packageChanges).isEmpty()
+
+                updateMonitor.onPackageUpdateFinished(
+                    packageName = TEST_PACKAGE,
+                    uid = UserHandle.getUid(/* userId = */ 101, /* appId = */ 10)
+                )
+
+                updateMonitor.onPackageAdded(
+                    packageName = TEST_PACKAGE,
+                    uid = UserHandle.getUid(/* userId = */ 99, /* appId = */ 10)
+                )
+
+                assertThat(packageChanges).hasSize(2)
+            }
+        }
+
+    private companion object {
+        val USER_100 = UserHandle.of(100)
+        const val TEST_PACKAGE = "pkg.test"
+    }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/common/data/repository/PackageUpdateMonitorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/common/data/repository/PackageUpdateMonitorTest.kt
new file mode 100644
index 0000000..d610925
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/common/data/repository/PackageUpdateMonitorTest.kt
@@ -0,0 +1,210 @@
+/*
+ * 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.common.data.repository
+
+import android.content.Context
+import android.content.pm.PackageManager
+import android.os.Handler
+import android.os.UserHandle
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.common.data.shared.model.PackageChangeModel
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.coroutines.collectValues
+import com.android.systemui.kosmos.applicationCoroutineScope
+import com.android.systemui.kosmos.testDispatcher
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.log.logcatLogBuffer
+import com.android.systemui.testKosmos
+import com.android.systemui.util.mockito.whenever
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.MutableSharedFlow
+import kotlinx.coroutines.flow.first
+import kotlinx.coroutines.flow.launchIn
+import kotlinx.coroutines.flow.onEach
+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.Mock
+import org.mockito.MockitoAnnotations
+
+@ExperimentalCoroutinesApi
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class PackageUpdateMonitorTest : SysuiTestCase() {
+    private val kosmos = testKosmos()
+
+    @Mock private lateinit var context: Context
+    @Mock private lateinit var packageManager: PackageManager
+    @Mock private lateinit var handler: Handler
+
+    private lateinit var monitor: PackageUpdateMonitor
+
+    @Before
+    fun setUp() =
+        with(kosmos) {
+            MockitoAnnotations.initMocks(this@PackageUpdateMonitorTest)
+            whenever(context.packageManager).thenReturn(packageManager)
+
+            monitor =
+                PackageUpdateMonitor(
+                    user = USER_100,
+                    bgDispatcher = testDispatcher,
+                    bgHandler = handler,
+                    context = context,
+                    scope = applicationCoroutineScope,
+                    logger = PackageUpdateLogger(logcatLogBuffer())
+                )
+        }
+
+    @Test
+    fun becomesActiveWhenFlowCollected() =
+        with(kosmos) {
+            testScope.runTest {
+                assertThat(monitor.isActive).isFalse()
+                val job = monitor.packageChanged.launchIn(this)
+                runCurrent()
+                assertThat(monitor.isActive).isTrue()
+                job.cancel()
+                runCurrent()
+                assertThat(monitor.isActive).isFalse()
+            }
+        }
+
+    @Test
+    fun packageAdded() =
+        with(kosmos) {
+            testScope.runTest {
+                val packageChange by collectLastValue(monitor.packageChanged)
+                assertThat(packageChange).isNull()
+
+                monitor.onPackageAdded(TEST_PACKAGE, 123)
+
+                assertThat(packageChange)
+                    .isEqualTo(
+                        PackageChangeModel.Installed(packageName = TEST_PACKAGE, packageUid = 123)
+                    )
+            }
+        }
+
+    @Test
+    fun packageRemoved() =
+        with(kosmos) {
+            testScope.runTest {
+                val packageChange by collectLastValue(monitor.packageChanged)
+                assertThat(packageChange).isNull()
+
+                monitor.onPackageRemoved(TEST_PACKAGE, 123)
+
+                assertThat(packageChange)
+                    .isEqualTo(
+                        PackageChangeModel.Uninstalled(packageName = TEST_PACKAGE, packageUid = 123)
+                    )
+            }
+        }
+
+    @Test
+    fun packageChanged() =
+        with(kosmos) {
+            testScope.runTest {
+                val packageChange by collectLastValue(monitor.packageChanged)
+                assertThat(packageChange).isNull()
+
+                monitor.onPackageChanged(TEST_PACKAGE, 123, emptyArray())
+
+                assertThat(packageChange)
+                    .isEqualTo(
+                        PackageChangeModel.Changed(packageName = TEST_PACKAGE, packageUid = 123)
+                    )
+            }
+        }
+
+    @Test
+    fun packageUpdateStarted() =
+        with(kosmos) {
+            testScope.runTest {
+                val packageChange by collectLastValue(monitor.packageChanged)
+                assertThat(packageChange).isNull()
+
+                monitor.onPackageUpdateStarted(TEST_PACKAGE, 123)
+
+                assertThat(packageChange)
+                    .isEqualTo(
+                        PackageChangeModel.UpdateStarted(
+                            packageName = TEST_PACKAGE,
+                            packageUid = 123
+                        )
+                    )
+            }
+        }
+
+    @Test
+    fun packageUpdateFinished() =
+        with(kosmos) {
+            testScope.runTest {
+                val packageChange by collectLastValue(monitor.packageChanged)
+                assertThat(packageChange).isNull()
+
+                monitor.onPackageUpdateFinished(TEST_PACKAGE, 123)
+
+                assertThat(packageChange)
+                    .isEqualTo(
+                        PackageChangeModel.UpdateFinished(
+                            packageName = TEST_PACKAGE,
+                            packageUid = 123
+                        )
+                    )
+            }
+        }
+
+    @Test
+    fun handlesBackflow() =
+        with(kosmos) {
+            testScope.runTest {
+                val latch = MutableSharedFlow<Unit>()
+                val packageChanges by collectValues(monitor.packageChanged.onEach { latch.first() })
+                assertThat(packageChanges).isEmpty()
+
+                monitor.onPackageAdded(TEST_PACKAGE, 123)
+                monitor.onPackageUpdateStarted(TEST_PACKAGE, 123)
+                monitor.onPackageUpdateFinished(TEST_PACKAGE, 123)
+
+                assertThat(packageChanges).isEmpty()
+                latch.emit(Unit)
+                assertThat(packageChanges).hasSize(1)
+                latch.emit(Unit)
+                assertThat(packageChanges).hasSize(2)
+                latch.emit(Unit)
+                assertThat(packageChanges)
+                    .containsExactly(
+                        PackageChangeModel.Installed(TEST_PACKAGE, 123),
+                        PackageChangeModel.UpdateStarted(TEST_PACKAGE, 123),
+                        PackageChangeModel.UpdateFinished(TEST_PACKAGE, 123),
+                    )
+                    .inOrder()
+            }
+        }
+
+    companion object {
+        private val USER_100 = UserHandle.of(100)
+        private const val TEST_PACKAGE = "pkg.test"
+    }
+}
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 1f8e29a..62084aa 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
@@ -28,6 +28,7 @@
 import com.android.systemui.communal.data.repository.FakeCommunalTutorialRepository
 import com.android.systemui.communal.data.repository.FakeCommunalWidgetRepository
 import com.android.systemui.communal.domain.model.CommunalContentModel
+import com.android.systemui.communal.shared.model.CommunalContentSize
 import com.android.systemui.communal.shared.model.CommunalSceneKey
 import com.android.systemui.communal.shared.model.CommunalWidgetContentModel
 import com.android.systemui.communal.widgets.EditWidgetsActivityStarter
@@ -169,6 +170,109 @@
         }
 
     @Test
+    fun smartspaceDynamicSizing_oneCard_fullSize() =
+        testSmartspaceDynamicSizing(
+            totalTargets = 1,
+            expectedSizes =
+                listOf(
+                    CommunalContentSize.FULL,
+                )
+        )
+
+    @Test
+    fun smartspace_dynamicSizing_twoCards_halfSize() =
+        testSmartspaceDynamicSizing(
+            totalTargets = 2,
+            expectedSizes =
+                listOf(
+                    CommunalContentSize.HALF,
+                    CommunalContentSize.HALF,
+                )
+        )
+
+    @Test
+    fun smartspace_dynamicSizing_threeCards_thirdSize() =
+        testSmartspaceDynamicSizing(
+            totalTargets = 3,
+            expectedSizes =
+                listOf(
+                    CommunalContentSize.THIRD,
+                    CommunalContentSize.THIRD,
+                    CommunalContentSize.THIRD,
+                )
+        )
+
+    @Test
+    fun smartspace_dynamicSizing_fourCards_oneFullAndThreeThirdSize() =
+        testSmartspaceDynamicSizing(
+            totalTargets = 4,
+            expectedSizes =
+                listOf(
+                    CommunalContentSize.FULL,
+                    CommunalContentSize.THIRD,
+                    CommunalContentSize.THIRD,
+                    CommunalContentSize.THIRD,
+                )
+        )
+
+    @Test
+    fun smartspace_dynamicSizing_fiveCards_twoHalfAndThreeThirdSize() =
+        testSmartspaceDynamicSizing(
+            totalTargets = 5,
+            expectedSizes =
+                listOf(
+                    CommunalContentSize.HALF,
+                    CommunalContentSize.HALF,
+                    CommunalContentSize.THIRD,
+                    CommunalContentSize.THIRD,
+                    CommunalContentSize.THIRD,
+                )
+        )
+
+    @Test
+    fun smartspace_dynamicSizing_sixCards_allThirdSize() =
+        testSmartspaceDynamicSizing(
+            totalTargets = 6,
+            expectedSizes =
+                listOf(
+                    CommunalContentSize.THIRD,
+                    CommunalContentSize.THIRD,
+                    CommunalContentSize.THIRD,
+                    CommunalContentSize.THIRD,
+                    CommunalContentSize.THIRD,
+                    CommunalContentSize.THIRD,
+                )
+        )
+
+    private fun testSmartspaceDynamicSizing(
+        totalTargets: Int,
+        expectedSizes: List<CommunalContentSize>,
+    ) =
+        testScope.runTest {
+            // Keyguard showing, and tutorial completed.
+            keyguardRepository.setKeyguardShowing(true)
+            keyguardRepository.setKeyguardOccluded(false)
+            tutorialRepository.setTutorialSettingState(HUB_MODE_TUTORIAL_COMPLETED)
+
+            val targets = mutableListOf<SmartspaceTarget>()
+            for (index in 0 until totalTargets) {
+                val target = mock(SmartspaceTarget::class.java)
+                whenever(target.smartspaceTargetId).thenReturn("target$index")
+                whenever(target.featureType).thenReturn(SmartspaceTarget.FEATURE_TIMER)
+                whenever(target.remoteViews).thenReturn(mock(RemoteViews::class.java))
+                targets.add(target)
+            }
+
+            smartspaceRepository.setCommunalSmartspaceTargets(targets)
+
+            val smartspaceContent by collectLastValue(underTest.smartspaceContent)
+            assertThat(smartspaceContent?.size).isEqualTo(totalTargets)
+            for (index in 0 until totalTargets) {
+                assertThat(smartspaceContent?.get(index)?.size).isEqualTo(expectedSizes[index])
+            }
+        }
+
+    @Test
     fun umo_mediaPlaying_showsUmo() =
         testScope.runTest {
             // Tutorial completed.
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/HomeControlsKeyguardQuickAffordanceConfigParameterizedStateTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/quickaffordance/HomeControlsKeyguardQuickAffordanceConfigParameterizedStateTest.kt
similarity index 96%
rename from packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/HomeControlsKeyguardQuickAffordanceConfigParameterizedStateTest.kt
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/quickaffordance/HomeControlsKeyguardQuickAffordanceConfigParameterizedStateTest.kt
index 477f455..0329794 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/quickaffordance/HomeControlsKeyguardQuickAffordanceConfigParameterizedStateTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/quickaffordance/HomeControlsKeyguardQuickAffordanceConfigParameterizedStateTest.kt
@@ -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.
@@ -12,20 +12,19 @@
  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  * See the License for the specific language governing permissions and
  * limitations under the License.
- *
  */
 
 package com.android.systemui.keyguard.data.quickaffordance
 
 import android.app.Activity
 import androidx.test.filters.SmallTest
-import com.android.systemui.res.R
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.controls.ControlsServiceInfo
 import com.android.systemui.controls.controller.ControlsController
 import com.android.systemui.controls.dagger.ControlsComponent
 import com.android.systemui.controls.management.ControlsListingController
 import com.android.systemui.controls.ui.ControlsUiController
+import com.android.systemui.res.R
 import com.android.systemui.util.mockito.mock
 import com.android.systemui.util.mockito.whenever
 import com.google.common.truth.Truth.assertThat
@@ -37,17 +36,17 @@
 import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
-import org.junit.runners.Parameterized
-import org.junit.runners.Parameterized.Parameter
-import org.junit.runners.Parameterized.Parameters
 import org.mockito.ArgumentCaptor
 import org.mockito.Captor
 import org.mockito.Mock
 import org.mockito.Mockito.verify
 import org.mockito.MockitoAnnotations
+import platform.test.runner.parameterized.Parameter
+import platform.test.runner.parameterized.ParameterizedAndroidJunit4
+import platform.test.runner.parameterized.Parameters
 
 @SmallTest
-@RunWith(Parameterized::class)
+@RunWith(ParameterizedAndroidJunit4::class)
 class HomeControlsKeyguardQuickAffordanceConfigParameterizedStateTest : SysuiTestCase() {
 
     companion object {
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerToAodTransitionViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerToAodTransitionViewModelTest.kt
index 9daf186..e7037a6 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerToAodTransitionViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/AlternateBouncerToAodTransitionViewModelTest.kt
@@ -94,7 +94,7 @@
                 testScope,
             )
 
-            assertThat(values.size).isEqualTo(6)
+            assertThat(values.size).isEqualTo(5)
             values.forEach { assertThat(it).isIn(Range.closed(0f, 1f)) }
         }
 
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/AodAlphaViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/AodAlphaViewModelTest.kt
new file mode 100644
index 0000000..83782e2
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/AodAlphaViewModelTest.kt
@@ -0,0 +1,108 @@
+/*
+ * 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.
+ */
+
+@file:OptIn(ExperimentalCoroutinesApi::class)
+
+package com.android.systemui.keyguard.ui.viewmodel
+
+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.keyguard.data.repository.fakeKeyguardRepository
+import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
+import com.android.systemui.keyguard.shared.model.KeyguardState
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.testKosmos
+import com.android.systemui.util.mockito.whenever
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.MockitoAnnotations
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class AodAlphaViewModelTest : SysuiTestCase() {
+
+    @Mock
+    private lateinit var occludedToLockscreenTransitionViewModel:
+        OccludedToLockscreenTransitionViewModel
+
+    private val kosmos = testKosmos()
+    private val testScope = kosmos.testScope
+    private val keyguardRepository = kosmos.fakeKeyguardRepository
+    private val keyguardTransitionRepository = kosmos.fakeKeyguardTransitionRepository
+    private val occludedToLockscreenAlpha = MutableStateFlow(0f)
+
+    private lateinit var underTest: AodAlphaViewModel
+
+    @Before
+    fun setUp() {
+        MockitoAnnotations.initMocks(this)
+        whenever(occludedToLockscreenTransitionViewModel.lockscreenAlpha)
+            .thenReturn(occludedToLockscreenAlpha)
+        kosmos.occludedToLockscreenTransitionViewModel = occludedToLockscreenTransitionViewModel
+
+        underTest = kosmos.aodAlphaViewModel
+    }
+
+    @Test
+    fun alpha() =
+        testScope.runTest {
+            val alpha by collectLastValue(underTest.alpha)
+
+            keyguardTransitionRepository.sendTransitionSteps(
+                from = KeyguardState.OFF,
+                to = KeyguardState.LOCKSCREEN,
+                testScope = testScope,
+            )
+
+            keyguardRepository.setKeyguardAlpha(0.1f)
+            assertThat(alpha).isEqualTo(0.1f)
+            keyguardRepository.setKeyguardAlpha(0.5f)
+            assertThat(alpha).isEqualTo(0.5f)
+            keyguardRepository.setKeyguardAlpha(0.2f)
+            assertThat(alpha).isEqualTo(0.2f)
+            keyguardRepository.setKeyguardAlpha(0f)
+            assertThat(alpha).isEqualTo(0f)
+            occludedToLockscreenAlpha.value = 0.8f
+            assertThat(alpha).isEqualTo(0.8f)
+        }
+
+    @Test
+    fun alpha_whenGone_equalsZero() =
+        testScope.runTest {
+            val alpha by collectLastValue(underTest.alpha)
+
+            keyguardTransitionRepository.sendTransitionSteps(
+                from = KeyguardState.LOCKSCREEN,
+                to = KeyguardState.GONE,
+                testScope = testScope,
+            )
+
+            keyguardRepository.setKeyguardAlpha(0.1f)
+            assertThat(alpha).isEqualTo(0f)
+            keyguardRepository.setKeyguardAlpha(0.5f)
+            assertThat(alpha).isEqualTo(0f)
+            keyguardRepository.setKeyguardAlpha(1f)
+            assertThat(alpha).isEqualTo(0f)
+        }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/AodBurnInViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/AodBurnInViewModelTest.kt
new file mode 100644
index 0000000..0543bc2
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/AodBurnInViewModelTest.kt
@@ -0,0 +1,290 @@
+/*
+ * 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.
+ */
+
+@file:OptIn(ExperimentalCoroutinesApi::class)
+
+package com.android.systemui.keyguard.ui.viewmodel
+
+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.keyguard.data.repository.fakeKeyguardTransitionRepository
+import com.android.systemui.keyguard.domain.interactor.BurnInInteractor
+import com.android.systemui.keyguard.domain.interactor.burnInInteractor
+import com.android.systemui.keyguard.shared.model.BurnInModel
+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.clocks.ClockController
+import com.android.systemui.testKosmos
+import com.android.systemui.util.mockito.whenever
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.emptyFlow
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Answers
+import org.mockito.ArgumentMatchers.anyInt
+import org.mockito.Mock
+import org.mockito.MockitoAnnotations
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class AodBurnInViewModelTest : SysuiTestCase() {
+
+    @Mock private lateinit var burnInInteractor: BurnInInteractor
+    @Mock private lateinit var goneToAodTransitionViewModel: GoneToAodTransitionViewModel
+    @Mock(answer = Answers.RETURNS_DEEP_STUBS) private lateinit var clockController: ClockController
+
+    private val kosmos = testKosmos()
+    private val testScope = kosmos.testScope
+    private val keyguardTransitionRepository = kosmos.fakeKeyguardTransitionRepository
+    private lateinit var underTest: AodBurnInViewModel
+
+    private var burnInParameters =
+        BurnInParameters(
+            clockControllerProvider = { clockController },
+        )
+    private val burnInFlow = MutableStateFlow(BurnInModel())
+    private val enterFromTopAnimationAlpha = MutableStateFlow(0f)
+
+    @Before
+    fun setUp() {
+        MockitoAnnotations.initMocks(this)
+        whenever(burnInInteractor.keyguardBurnIn).thenReturn(burnInFlow)
+        kosmos.burnInInteractor = burnInInteractor
+        whenever(goneToAodTransitionViewModel.enterFromTopAnimationAlpha)
+            .thenReturn(enterFromTopAnimationAlpha)
+        whenever(goneToAodTransitionViewModel.enterFromTopTranslationY(anyInt()))
+            .thenReturn(emptyFlow())
+        kosmos.goneToAodTransitionViewModel = goneToAodTransitionViewModel
+
+        underTest = kosmos.aodBurnInViewModel
+    }
+
+    @Test
+    fun translationY_initializedToZero() =
+        testScope.runTest {
+            val translationY by collectLastValue(underTest.translationY(burnInParameters))
+            assertThat(translationY).isEqualTo(0)
+        }
+
+    @Test
+    fun translationAndScale_whenNotDozing() =
+        testScope.runTest {
+            val translationX by collectLastValue(underTest.translationX(burnInParameters))
+            val translationY by collectLastValue(underTest.translationY(burnInParameters))
+            val scale by collectLastValue(underTest.scale(burnInParameters))
+
+            // Set to not dozing (on lockscreen)
+            keyguardTransitionRepository.sendTransitionStep(
+                TransitionStep(
+                    from = KeyguardState.AOD,
+                    to = KeyguardState.LOCKSCREEN,
+                    value = 1f,
+                    transitionState = TransitionState.FINISHED
+                ),
+                validateStep = false,
+            )
+
+            // Trigger a change to the burn-in model
+            burnInFlow.value =
+                BurnInModel(
+                    translationX = 20,
+                    translationY = 30,
+                    scale = 0.5f,
+                )
+
+            assertThat(translationX).isEqualTo(0)
+            assertThat(translationY).isEqualTo(0)
+            assertThat(scale)
+                .isEqualTo(
+                    BurnInScaleViewModel(
+                        scale = 1f,
+                        scaleClockOnly = true,
+                    )
+                )
+        }
+
+    @Test
+    fun translationAndScale_whenFullyDozing() =
+        testScope.runTest {
+            burnInParameters = burnInParameters.copy(statusViewTop = 100)
+            val translationX by collectLastValue(underTest.translationX(burnInParameters))
+            val translationY by collectLastValue(underTest.translationY(burnInParameters))
+            val scale by collectLastValue(underTest.scale(burnInParameters))
+
+            // Set to dozing (on AOD)
+            keyguardTransitionRepository.sendTransitionStep(
+                TransitionStep(
+                    from = KeyguardState.GONE,
+                    to = KeyguardState.AOD,
+                    value = 1f,
+                    transitionState = TransitionState.FINISHED
+                ),
+                validateStep = false,
+            )
+            // Trigger a change to the burn-in model
+            burnInFlow.value =
+                BurnInModel(
+                    translationX = 20,
+                    translationY = 30,
+                    scale = 0.5f,
+                )
+
+            assertThat(translationX).isEqualTo(20)
+            assertThat(translationY).isEqualTo(30)
+            assertThat(scale)
+                .isEqualTo(
+                    BurnInScaleViewModel(
+                        scale = 0.5f,
+                        scaleClockOnly = true,
+                    )
+                )
+
+            // Set to the beginning of GONE->AOD transition
+            keyguardTransitionRepository.sendTransitionStep(
+                TransitionStep(
+                    from = KeyguardState.GONE,
+                    to = KeyguardState.AOD,
+                    value = 0f,
+                    transitionState = TransitionState.STARTED
+                ),
+                validateStep = false,
+            )
+            assertThat(translationX).isEqualTo(0)
+            assertThat(translationY).isEqualTo(0)
+            assertThat(scale)
+                .isEqualTo(
+                    BurnInScaleViewModel(
+                        scale = 1f,
+                        scaleClockOnly = true,
+                    )
+                )
+        }
+
+    @Test
+    fun translationAndScale_whenFullyDozing_staysOutOfTopInset() =
+        testScope.runTest {
+            burnInParameters =
+                burnInParameters.copy(
+                    statusViewTop = 100,
+                    topInset = 80,
+                )
+            val translationX by collectLastValue(underTest.translationX(burnInParameters))
+            val translationY by collectLastValue(underTest.translationY(burnInParameters))
+            val scale by collectLastValue(underTest.scale(burnInParameters))
+
+            // Set to dozing (on AOD)
+            keyguardTransitionRepository.sendTransitionStep(
+                TransitionStep(
+                    from = KeyguardState.GONE,
+                    to = KeyguardState.AOD,
+                    value = 1f,
+                    transitionState = TransitionState.FINISHED
+                ),
+                validateStep = false,
+            )
+
+            // Trigger a change to the burn-in model
+            burnInFlow.value =
+                BurnInModel(
+                    translationX = 20,
+                    translationY = -30,
+                    scale = 0.5f,
+                )
+            assertThat(translationX).isEqualTo(20)
+            // -20 instead of -30, due to inset of 80
+            assertThat(translationY).isEqualTo(-20)
+            assertThat(scale)
+                .isEqualTo(
+                    BurnInScaleViewModel(
+                        scale = 0.5f,
+                        scaleClockOnly = true,
+                    )
+                )
+
+            // Set to the beginning of GONE->AOD transition
+            keyguardTransitionRepository.sendTransitionStep(
+                TransitionStep(
+                    from = KeyguardState.GONE,
+                    to = KeyguardState.AOD,
+                    value = 0f,
+                    transitionState = TransitionState.STARTED
+                ),
+                validateStep = false,
+            )
+            assertThat(translationX).isEqualTo(0)
+            assertThat(translationY).isEqualTo(0)
+            assertThat(scale)
+                .isEqualTo(
+                    BurnInScaleViewModel(
+                        scale = 1f,
+                        scaleClockOnly = true,
+                    )
+                )
+        }
+
+    @Test
+    fun translationAndScale_useScaleOnly() =
+        testScope.runTest {
+            whenever(clockController.config.useAlternateSmartspaceAODTransition).thenReturn(true)
+
+            val translationX by collectLastValue(underTest.translationX(burnInParameters))
+            val translationY by collectLastValue(underTest.translationY(burnInParameters))
+            val scale by collectLastValue(underTest.scale(burnInParameters))
+
+            // Set to dozing (on AOD)
+            keyguardTransitionRepository.sendTransitionStep(
+                TransitionStep(
+                    from = KeyguardState.GONE,
+                    to = KeyguardState.AOD,
+                    value = 1f,
+                    transitionState = TransitionState.FINISHED
+                ),
+                validateStep = false,
+            )
+
+            // Trigger a change to the burn-in model
+            burnInFlow.value =
+                BurnInModel(
+                    translationX = 20,
+                    translationY = 30,
+                    scale = 0.5f,
+                )
+
+            assertThat(translationX).isEqualTo(0)
+            assertThat(translationY).isEqualTo(0)
+            assertThat(scale).isEqualTo(BurnInScaleViewModel(scale = 0.5f, scaleClockOnly = false))
+        }
+
+    @Test
+    fun alpha() =
+        testScope.runTest {
+            val alpha by collectLastValue(underTest.alpha)
+
+            enterFromTopAnimationAlpha.value = 0.2f
+            assertThat(alpha).isEqualTo(0.2f)
+
+            enterFromTopAnimationAlpha.value = 1f
+            assertThat(alpha).isEqualTo(1f)
+        }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToLockscreenTransitionViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToLockscreenTransitionViewModelTest.kt
index 53bca48..e141c2b 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToLockscreenTransitionViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/DreamingToLockscreenTransitionViewModelTest.kt
@@ -55,6 +55,28 @@
     private val underTest = kosmos.dreamingToLockscreenTransitionViewModel
 
     @Test
+    fun shortcutsAlpha_bothShortcutsReceiveLastValue() =
+        testScope.runTest {
+            val valuesLeft by collectValues(underTest.shortcutsAlpha)
+            val valuesRight by collectValues(underTest.shortcutsAlpha)
+
+            keyguardTransitionRepository.sendTransitionSteps(
+                listOf(
+                    step(0f, TransitionState.STARTED),
+                    step(0.3f),
+                    step(0.5f),
+                    step(0.6f),
+                    step(0.8f),
+                    step(1f),
+                ),
+                testScope,
+            )
+
+            assertThat(valuesLeft.last()).isEqualTo(1f)
+            assertThat(valuesRight.last()).isEqualTo(1f)
+        }
+
+    @Test
     fun dreamOverlayTranslationY() =
         testScope.runTest {
             val pixels = 100
@@ -73,7 +95,7 @@
                 testScope,
             )
 
-            assertThat(values.size).isEqualTo(7)
+            assertThat(values.size).isEqualTo(6)
             values.forEach { assertThat(it).isIn(Range.closed(0f, 100f)) }
         }
 
@@ -95,7 +117,7 @@
                 testScope,
             )
 
-            assertThat(values.size).isEqualTo(4)
+            assertThat(values.size).isEqualTo(3)
             values.forEach { assertThat(it).isIn(Range.closed(0f, 1f)) }
         }
 
@@ -210,7 +232,7 @@
                 testScope,
             )
 
-            assertThat(values.size).isEqualTo(5)
+            assertThat(values.size).isEqualTo(4)
             values.forEach { assertThat(it).isIn(Range.closed(-100f, 0f)) }
         }
 
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/GoneToDreamingTransitionViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/GoneToDreamingTransitionViewModelTest.kt
index 3c07034..897ce6d 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/GoneToDreamingTransitionViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/GoneToDreamingTransitionViewModelTest.kt
@@ -61,7 +61,7 @@
 
             // Only three values should be present, since the dream overlay runs for a small
             // fraction of the overall animation time
-            assertThat(values.size).isEqualTo(5)
+            assertThat(values.size).isEqualTo(4)
             values.forEach { assertThat(it).isIn(Range.closed(0f, 1f)) }
         }
 
@@ -84,7 +84,7 @@
                 testScope,
             )
 
-            assertThat(values.size).isEqualTo(5)
+            assertThat(values.size).isEqualTo(4)
             values.forEach { assertThat(it).isIn(Range.closed(0f, 100f)) }
         }
 
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelTest.kt
new file mode 100644
index 0000000..7c3dc97
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelTest.kt
@@ -0,0 +1,207 @@
+/*
+ * 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.
+ *
+ */
+
+@file:OptIn(ExperimentalCoroutinesApi::class)
+
+package com.android.systemui.keyguard.ui.viewmodel
+
+import android.view.View
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.Flags as AConfigFlags
+import com.android.systemui.Flags.FLAG_NEW_AOD_TRANSITION
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.deviceentry.data.repository.fakeDeviceEntryRepository
+import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
+import com.android.systemui.keyguard.shared.model.KeyguardState
+import com.android.systemui.keyguard.shared.model.TransitionState
+import com.android.systemui.keyguard.shared.model.TransitionStep
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.statusbar.notification.data.repository.fakeNotificationsKeyguardViewStateRepository
+import com.android.systemui.statusbar.phone.dozeParameters
+import com.android.systemui.statusbar.phone.screenOffAnimationController
+import com.android.systemui.testKosmos
+import com.android.systemui.util.mockito.whenever
+import com.android.systemui.util.ui.isAnimating
+import com.android.systemui.util.ui.stopAnimating
+import com.android.systemui.util.ui.value
+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
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class KeyguardRootViewModelTest : SysuiTestCase() {
+    private val kosmos = testKosmos()
+    private val testScope = kosmos.testScope
+    private val keyguardTransitionRepository = kosmos.fakeKeyguardTransitionRepository
+    private val screenOffAnimationController = kosmos.screenOffAnimationController
+    private val deviceEntryRepository = kosmos.fakeDeviceEntryRepository
+    private val fakeNotificationsKeyguardViewStateRepository =
+        kosmos.fakeNotificationsKeyguardViewStateRepository
+    private val dozeParameters = kosmos.dozeParameters
+    private val underTest = kosmos.keyguardRootViewModel
+
+    @Before
+    fun setUp() {
+        mSetFlagsRule.enableFlags(AConfigFlags.FLAG_KEYGUARD_BOTTOM_AREA_REFACTOR)
+        mSetFlagsRule.enableFlags(FLAG_NEW_AOD_TRANSITION)
+        mSetFlagsRule.disableFlags(AConfigFlags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT)
+    }
+
+    @Test
+    fun burnInLayerVisibility() =
+        testScope.runTest {
+            val burnInLayerVisibility by collectLastValue(underTest.burnInLayerVisibility)
+
+            keyguardTransitionRepository.sendTransitionStep(
+                TransitionStep(
+                    from = KeyguardState.LOCKSCREEN,
+                    to = KeyguardState.AOD,
+                    value = 0f,
+                    transitionState = TransitionState.STARTED
+                ),
+                validateStep = false,
+            )
+            assertThat(burnInLayerVisibility).isEqualTo(View.VISIBLE)
+        }
+
+    @Test
+    fun iconContainer_isNotVisible_notOnKeyguard_dontShowAodIconsWhenShade() =
+        testScope.runTest {
+            val isVisible by collectLastValue(underTest.isNotifIconContainerVisible)
+            runCurrent()
+            keyguardTransitionRepository.sendTransitionSteps(
+                from = KeyguardState.OFF,
+                to = KeyguardState.GONE,
+                testScope,
+            )
+            whenever(screenOffAnimationController.shouldShowAodIconsWhenShade()).thenReturn(false)
+            runCurrent()
+
+            assertThat(isVisible?.value).isFalse()
+            assertThat(isVisible?.isAnimating).isFalse()
+        }
+
+    @Test
+    fun iconContainer_isVisible_bypassEnabled() =
+        testScope.runTest {
+            val isVisible by collectLastValue(underTest.isNotifIconContainerVisible)
+            runCurrent()
+            deviceEntryRepository.setBypassEnabled(true)
+            runCurrent()
+
+            assertThat(isVisible?.value).isTrue()
+        }
+
+    @Test
+    fun iconContainer_isNotVisible_pulseExpanding_notBypassing() =
+        testScope.runTest {
+            val isVisible by collectLastValue(underTest.isNotifIconContainerVisible)
+            runCurrent()
+            fakeNotificationsKeyguardViewStateRepository.setPulseExpanding(true)
+            deviceEntryRepository.setBypassEnabled(false)
+            runCurrent()
+
+            assertThat(isVisible?.value).isEqualTo(false)
+        }
+
+    @Test
+    fun iconContainer_isVisible_notifsFullyHidden_bypassEnabled() =
+        testScope.runTest {
+            val isVisible by collectLastValue(underTest.isNotifIconContainerVisible)
+            runCurrent()
+            fakeNotificationsKeyguardViewStateRepository.setPulseExpanding(false)
+            deviceEntryRepository.setBypassEnabled(true)
+            fakeNotificationsKeyguardViewStateRepository.setNotificationsFullyHidden(true)
+            runCurrent()
+
+            assertThat(isVisible?.value).isTrue()
+            assertThat(isVisible?.isAnimating).isTrue()
+        }
+
+    @Test
+    fun iconContainer_isVisible_notifsFullyHidden_bypassDisabled_aodDisabled() =
+        testScope.runTest {
+            val isVisible by collectLastValue(underTest.isNotifIconContainerVisible)
+            runCurrent()
+            fakeNotificationsKeyguardViewStateRepository.setPulseExpanding(false)
+            deviceEntryRepository.setBypassEnabled(false)
+            whenever(dozeParameters.alwaysOn).thenReturn(false)
+            fakeNotificationsKeyguardViewStateRepository.setNotificationsFullyHidden(true)
+            runCurrent()
+
+            assertThat(isVisible?.value).isTrue()
+            assertThat(isVisible?.isAnimating).isFalse()
+        }
+
+    @Test
+    fun iconContainer_isVisible_notifsFullyHidden_bypassDisabled_displayNeedsBlanking() =
+        testScope.runTest {
+            val isVisible by collectLastValue(underTest.isNotifIconContainerVisible)
+            runCurrent()
+            fakeNotificationsKeyguardViewStateRepository.setPulseExpanding(false)
+            deviceEntryRepository.setBypassEnabled(false)
+            whenever(dozeParameters.alwaysOn).thenReturn(true)
+            whenever(dozeParameters.displayNeedsBlanking).thenReturn(true)
+            fakeNotificationsKeyguardViewStateRepository.setNotificationsFullyHidden(true)
+            runCurrent()
+
+            assertThat(isVisible?.value).isTrue()
+            assertThat(isVisible?.isAnimating).isFalse()
+        }
+
+    @Test
+    fun iconContainer_isVisible_notifsFullyHidden_bypassDisabled() =
+        testScope.runTest {
+            val isVisible by collectLastValue(underTest.isNotifIconContainerVisible)
+            runCurrent()
+            fakeNotificationsKeyguardViewStateRepository.setPulseExpanding(false)
+            deviceEntryRepository.setBypassEnabled(false)
+            whenever(dozeParameters.alwaysOn).thenReturn(true)
+            whenever(dozeParameters.displayNeedsBlanking).thenReturn(false)
+            fakeNotificationsKeyguardViewStateRepository.setNotificationsFullyHidden(true)
+            runCurrent()
+
+            assertThat(isVisible?.value).isTrue()
+            assertThat(isVisible?.isAnimating).isTrue()
+        }
+
+    @Test
+    fun isIconContainerVisible_stopAnimation() =
+        testScope.runTest {
+            val isVisible by collectLastValue(underTest.isNotifIconContainerVisible)
+            runCurrent()
+            fakeNotificationsKeyguardViewStateRepository.setPulseExpanding(false)
+            deviceEntryRepository.setBypassEnabled(false)
+            whenever(dozeParameters.alwaysOn).thenReturn(true)
+            whenever(dozeParameters.displayNeedsBlanking).thenReturn(false)
+            fakeNotificationsKeyguardViewStateRepository.setNotificationsFullyHidden(true)
+            runCurrent()
+
+            assertThat(isVisible?.isAnimating).isEqualTo(true)
+            isVisible?.stopAnimating()
+            runCurrent()
+
+            assertThat(isVisible?.isAnimating).isEqualTo(false)
+        }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModelTest.kt
index d07836d..74d309c 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModelTest.kt
@@ -14,6 +14,8 @@
  * limitations under the License.
  */
 
+@file:OptIn(ExperimentalCoroutinesApi::class)
+
 package com.android.systemui.keyguard.ui.viewmodel
 
 import androidx.test.ext.junit.runners.AndroidJUnit4
@@ -26,6 +28,7 @@
 import com.android.systemui.scene.shared.model.SceneModel
 import com.android.systemui.util.mockito.mock
 import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.test.runTest
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -94,7 +97,6 @@
                 KeyguardLongPressViewModel(
                     interactor = mock(),
                 ),
-            keyguardRoot = utils.keyguardRootViewModel(),
             notifications = utils.notificationsPlaceholderViewModel(),
         )
     }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToDreamingTransitionViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToDreamingTransitionViewModelTest.kt
index a346e8b..4843f8b 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToDreamingTransitionViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToDreamingTransitionViewModelTest.kt
@@ -75,7 +75,7 @@
 
             // Only three values should be present, since the dream overlay runs for a small
             // fraction of the overall animation time
-            assertThat(values.size).isEqualTo(5)
+            assertThat(values.size).isEqualTo(4)
             values.forEach { assertThat(it).isIn(Range.closed(0f, 1f)) }
         }
 
@@ -98,10 +98,10 @@
                 testScope = testScope,
             )
 
-            assertThat(values.size).isEqualTo(6)
+            assertThat(values.size).isEqualTo(5)
             values.forEach { assertThat(it).isIn(Range.closed(0f, 100f)) }
             // Validate finished value
-            assertThat(values[5]).isEqualTo(0f)
+            assertThat(values[4]).isEqualTo(0f)
         }
 
     @Test
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToOccludedTransitionViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToOccludedTransitionViewModelTest.kt
index 274bde1..a1b8aab 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToOccludedTransitionViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenToOccludedTransitionViewModelTest.kt
@@ -76,7 +76,7 @@
             )
             // Only 3 values should be present, since the dream overlay runs for a small fraction
             // of the overall animation time
-            assertThat(values.size).isEqualTo(5)
+            assertThat(values.size).isEqualTo(4)
             values.forEach { assertThat(it).isIn(Range.closed(0f, 1f)) }
         }
 
@@ -99,7 +99,7 @@
                     ),
                 testScope = testScope,
             )
-            assertThat(values.size).isEqualTo(5)
+            assertThat(values.size).isEqualTo(4)
             values.forEach { assertThat(it).isIn(Range.closed(0f, 100f)) }
         }
 
@@ -121,11 +121,11 @@
                     ),
                 testScope = testScope,
             )
-            assertThat(values.size).isEqualTo(4)
+            assertThat(values.size).isEqualTo(3)
             values.forEach { assertThat(it).isIn(Range.closed(0f, 100f)) }
 
             // Cancel will reset the translation
-            assertThat(values[3]).isEqualTo(0)
+            assertThat(values[2]).isEqualTo(0)
         }
 
     @Test
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToLockscreenTransitionViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToLockscreenTransitionViewModelTest.kt
index d419d4a..2111ad5 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToLockscreenTransitionViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToLockscreenTransitionViewModelTest.kt
@@ -95,7 +95,7 @@
                 testScope,
             )
 
-            assertThat(values.size).isEqualTo(5)
+            assertThat(values.size).isEqualTo(4)
             values.forEach { assertThat(it).isIn(Range.closed(-100f, 0f)) }
         }
 
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToGoneTransitionViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToGoneTransitionViewModelTest.kt
index f027bc8..90b8362 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToGoneTransitionViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToGoneTransitionViewModelTest.kt
@@ -95,7 +95,7 @@
                 testScope,
             )
 
-            assertThat(values.size).isEqualTo(3)
+            assertThat(values.size).isEqualTo(1)
             values.forEach { assertThat(it).isEqualTo(0f) }
         }
 
@@ -107,7 +107,7 @@
             keyguardTransitionRepository.sendTransitionStep(step(0f, TransitionState.STARTED))
             keyguardTransitionRepository.sendTransitionStep(step(1f))
 
-            assertThat(values.size).isEqualTo(2)
+            assertThat(values.size).isEqualTo(1)
             values.forEach { assertThat(it).isEqualTo(0f) }
         }
 
@@ -121,7 +121,7 @@
             keyguardTransitionRepository.sendTransitionStep(step(0f, TransitionState.STARTED))
             keyguardTransitionRepository.sendTransitionStep(step(1f))
 
-            assertThat(values.size).isEqualTo(2)
+            assertThat(values.size).isEqualTo(1)
             values.forEach { assertThat(it).isEqualTo(1f) }
         }
 
@@ -135,7 +135,7 @@
             keyguardTransitionRepository.sendTransitionStep(step(0f, TransitionState.STARTED))
             keyguardTransitionRepository.sendTransitionStep(step(1f))
 
-            assertThat(values.size).isEqualTo(2)
+            assertThat(values.size).isEqualTo(1)
             values.forEach { assertThat(it).isEqualTo(1f) }
         }
 
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/custom/domain/interactor/CustomTileInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/custom/domain/interactor/CustomTileInteractorTest.kt
index 90779cb..20653ca 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/custom/domain/interactor/CustomTileInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/custom/domain/interactor/CustomTileInteractorTest.kt
@@ -55,6 +55,7 @@
     private val underTest: CustomTileInteractor =
         with(kosmos) {
             CustomTileInteractor(
+                tileSpec,
                 customTileDefaultsRepository,
                 customTileRepository,
                 testScope.backgroundScope,
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 224903f..efd4f9b 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/scene/SceneFrameworkIntegrationTest.kt
@@ -153,7 +153,6 @@
                 KeyguardLongPressViewModel(
                     interactor = mock(),
                 ),
-            keyguardRoot = utils.keyguardRootViewModel(),
             notifications = utils.notificationsPlaceholderViewModel(),
         )
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/DozeServiceHostTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/DozeServiceHostTest.java
similarity index 97%
rename from packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/DozeServiceHostTest.java
rename to packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/DozeServiceHostTest.java
index 53cb8a7..7a78b36 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/DozeServiceHostTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/statusbar/phone/DozeServiceHostTest.java
@@ -25,15 +25,14 @@
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.reset;
 import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.verifyZeroInteractions;
 import static org.mockito.Mockito.when;
 
 import android.graphics.Point;
 import android.os.PowerManager;
-import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper.RunWithLooper;
 import android.view.View;
 
+import androidx.test.ext.junit.runners.AndroidJUnit4;
 import androidx.test.filters.SmallTest;
 
 import com.android.keyguard.KeyguardUpdateMonitor;
@@ -68,7 +67,7 @@
 import java.util.HashSet;
 
 @SmallTest
-@RunWith(AndroidTestingRunner.class)
+@RunWith(AndroidJUnit4.class)
 @RunWithLooper(setAsMainLooper = true)
 public class DozeServiceHostTest extends SysuiTestCase {
 
@@ -181,6 +180,7 @@
                         DozeLog.PULSE_REASON_DOCKING,
                         DozeLog.REASON_SENSOR_WAKE_UP_PRESENCE,
                         DozeLog.REASON_SENSOR_QUICK_PICKUP,
+                        DozeLog.PULSE_REASON_FINGERPRINT_ACTIVATED,
                         DozeLog.REASON_SENSOR_TAP));
         HashSet<Integer> reasonsThatDontPulse = new HashSet<>(
                 Arrays.asList(DozeLog.REASON_SENSOR_PICKUP,
@@ -232,7 +232,7 @@
     public void onSlpiTap_doesnt_pass_negative_values() {
         mDozeServiceHost.onSlpiTap(-1, 200);
         mDozeServiceHost.onSlpiTap(100, -2);
-        verifyZeroInteractions(mDozeInteractor);
+        verify(mDozeInteractor, never()).setLastTapToWakePosition(any());
     }
     @Test
     public void dozeTimeTickSentToDozeInteractor() {
diff --git a/packages/SystemUI/res/values-am/strings.xml b/packages/SystemUI/res/values-am/strings.xml
index 74e92ba..40fddc8 100644
--- a/packages/SystemUI/res/values-am/strings.xml
+++ b/packages/SystemUI/res/values-am/strings.xml
@@ -1208,8 +1208,7 @@
     <string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"በቅንብሮች ውስጥ ነባሪ የማስታወሻዎች መተግበሪያን ያቀናብሩ"</string>
     <string name="install_app" msgid="5066668100199613936">"መተግበሪያን ጫን"</string>
     <string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"ወደ ውጫዊ ማሳያ ይንጸባረቅ?"</string>
-    <!-- no translation found for connected_display_dialog_dual_display_stop_warning (4174707498892447947) -->
-    <skip />
+    <string name="connected_display_dialog_dual_display_stop_warning" msgid="4174707498892447947">"የውስጥ ማሳያዎ ይንጸባረቃል። የፊት ማሳያዎ ይጠፋል።"</string>
     <string name="mirror_display" msgid="2515262008898122928">"ማሳያን አንጸባርቅ"</string>
     <string name="dismiss_dialog" msgid="2195508495854675882">"አሰናብት"</string>
     <string name="connected_display_icon_desc" msgid="6373560639989971997">"ማሳያ ተገናኝቷል"</string>
diff --git a/packages/SystemUI/res/values-ar/strings.xml b/packages/SystemUI/res/values-ar/strings.xml
index 8aeeaf7..d19c77b 100644
--- a/packages/SystemUI/res/values-ar/strings.xml
+++ b/packages/SystemUI/res/values-ar/strings.xml
@@ -330,12 +330,9 @@
     <string name="quick_settings_screen_record_label" msgid="8650355346742003694">"تسجيل الشاشة"</string>
     <string name="quick_settings_screen_record_start" msgid="1574725369331638985">"بدء"</string>
     <string name="quick_settings_screen_record_stop" msgid="8087348522976412119">"إيقاف"</string>
-    <!-- no translation found for qs_record_issue_label (8166290137285529059) -->
-    <skip />
-    <!-- no translation found for qs_record_issue_start (2979831312582567056) -->
-    <skip />
-    <!-- no translation found for qs_record_issue_stop (3531747965741982657) -->
-    <skip />
+    <string name="qs_record_issue_label" msgid="8166290137285529059">"تسجيل المشكلة"</string>
+    <string name="qs_record_issue_start" msgid="2979831312582567056">"بدء"</string>
+    <string name="qs_record_issue_stop" msgid="3531747965741982657">"إيقاف"</string>
     <string name="qs_record_issue_dropdown_header" msgid="5995983175678658329">"ما هو الجانب الذي تأثّر في تجربة استخدام الجهاز؟"</string>
     <string name="qs_record_issue_dropdown_prompt" msgid="2526949919167046219">"اختيار نوع المشكلة"</string>
     <string name="qs_record_issue_dropdown_screenrecord" msgid="6396141928484257626">"تسجيل الشاشة"</string>
@@ -416,12 +413,9 @@
     <string name="keyguard_indication_charging_time_dock" msgid="3149328898931741271">"<xliff:g id="PERCENTAGE">%2$s</xliff:g> • جارٍ الشحن • ستمتلئ البطارية خلال <xliff:g id="CHARGING_TIME_LEFT">%1$s</xliff:g>"</string>
     <string name="communal_tutorial_indicator_text" msgid="4503010353591430123">"مرِّر سريعًا لليمين لبدء الدليل التوجيهي العام."</string>
     <string name="button_to_open_widget_editor" msgid="5599945944349057600">"فتح محرِّر التطبيقات المصغّرة"</string>
-    <!-- no translation found for button_to_remove_widget (3948204829181214098) -->
-    <skip />
-    <!-- no translation found for hub_mode_add_widget_button_text (4831464661209971729) -->
-    <skip />
-    <!-- no translation found for hub_mode_editing_exit_button_text (3704686734192264771) -->
-    <skip />
+    <string name="button_to_remove_widget" msgid="3948204829181214098">"إزالة"</string>
+    <string name="hub_mode_add_widget_button_text" msgid="4831464661209971729">"إضافة تطبيق مصغّر"</string>
+    <string name="hub_mode_editing_exit_button_text" msgid="3704686734192264771">"تم"</string>
     <string name="accessibility_multi_user_switch_switcher" msgid="5330448341251092660">"تبديل المستخدم"</string>
     <string name="accessibility_multi_user_list_switcher" msgid="8574105376229857407">"القائمة المنسدلة"</string>
     <string name="guest_exit_guest_dialog_message" msgid="8183450985628495709">"سيتم حذف كل التطبيقات والبيانات في هذه الجلسة."</string>
@@ -1214,8 +1208,7 @@
     <string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"يمكنك ضبط تطبيق تدوين الملاحظات التلقائي في \"الإعدادات\"."</string>
     <string name="install_app" msgid="5066668100199613936">"تثبيت التطبيق"</string>
     <string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"هل تريد بث محتوى جهازك على الشاشة الخارجية؟"</string>
-    <!-- no translation found for connected_display_dialog_dual_display_stop_warning (4174707498892447947) -->
-    <skip />
+    <string name="connected_display_dialog_dual_display_stop_warning" msgid="4174707498892447947">"سيتم النسخ المطابق لمحتوى الشاشة الداخلية، وإيقاف الشاشة الأمامية."</string>
     <string name="mirror_display" msgid="2515262008898122928">"بث المحتوى على الشاشة"</string>
     <string name="dismiss_dialog" msgid="2195508495854675882">"إغلاق"</string>
     <string name="connected_display_icon_desc" msgid="6373560639989971997">"تم توصيل الشاشة"</string>
diff --git a/packages/SystemUI/res/values-as/strings.xml b/packages/SystemUI/res/values-as/strings.xml
index a1b5389..4e78f55 100644
--- a/packages/SystemUI/res/values-as/strings.xml
+++ b/packages/SystemUI/res/values-as/strings.xml
@@ -1208,8 +1208,7 @@
     <string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"ছেটিঙত টোকাৰ ডিফ’ল্ট এপ্ ছেট কৰক"</string>
     <string name="install_app" msgid="5066668100199613936">"এপ্‌টো ইনষ্টল কৰক"</string>
     <string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"বাহ্যিক ডিছপ্লে’লৈ মিৰ’ৰ কৰিবনে?"</string>
-    <!-- no translation found for connected_display_dialog_dual_display_stop_warning (4174707498892447947) -->
-    <skip />
+    <string name="connected_display_dialog_dual_display_stop_warning" msgid="4174707498892447947">"আপোনাৰ ইনাৰ ডিছপ্লে’ প্ৰতিবিম্বিত কৰা হ’ব। আপোনাৰ ফ্ৰণ্ট ডিছপ্লে’ অফ কৰা হ’ব।"</string>
     <string name="mirror_display" msgid="2515262008898122928">"ডিছপ্লে’ মিৰ’ৰ কৰক"</string>
     <string name="dismiss_dialog" msgid="2195508495854675882">"অগ্ৰাহ্য কৰক"</string>
     <string name="connected_display_icon_desc" msgid="6373560639989971997">"ডিছপ্লে’ সংযোগ কৰা হৈছে"</string>
diff --git a/packages/SystemUI/res/values-az/strings.xml b/packages/SystemUI/res/values-az/strings.xml
index 2a9a9cf..bf32c5e 100644
--- a/packages/SystemUI/res/values-az/strings.xml
+++ b/packages/SystemUI/res/values-az/strings.xml
@@ -1208,8 +1208,7 @@
     <string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"Ayarlarda defolt qeydlər tətbiqi ayarlayın"</string>
     <string name="install_app" msgid="5066668100199613936">"Tətbiqi quraşdırın"</string>
     <string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"Xarici displeyə əks etdirilsin?"</string>
-    <!-- no translation found for connected_display_dialog_dual_display_stop_warning (4174707498892447947) -->
-    <skip />
+    <string name="connected_display_dialog_dual_display_stop_warning" msgid="4174707498892447947">"İç displey əks etdiriləcək. Ön ekran deaktiv ediləcək."</string>
     <string name="mirror_display" msgid="2515262008898122928">"Displeyi əks etdirin"</string>
     <string name="dismiss_dialog" msgid="2195508495854675882">"İmtina edin"</string>
     <string name="connected_display_icon_desc" msgid="6373560639989971997">"Displey qoşulub"</string>
diff --git a/packages/SystemUI/res/values-b+sr+Latn/strings.xml b/packages/SystemUI/res/values-b+sr+Latn/strings.xml
index 47468ff..56c3e45 100644
--- a/packages/SystemUI/res/values-b+sr+Latn/strings.xml
+++ b/packages/SystemUI/res/values-b+sr+Latn/strings.xml
@@ -1208,8 +1208,7 @@
     <string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"Podesite podrazumevanu aplikaciju za beleške u Podešavanjima"</string>
     <string name="install_app" msgid="5066668100199613936">"Instaliraj aplikaciju"</string>
     <string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"Želite li da preslikate na spoljnji ekran?"</string>
-    <!-- no translation found for connected_display_dialog_dual_display_stop_warning (4174707498892447947) -->
-    <skip />
+    <string name="connected_display_dialog_dual_display_stop_warning" msgid="4174707498892447947">"Unutrašnji ekran će se preslikati. Prednji ekran će se isključiti."</string>
     <string name="mirror_display" msgid="2515262008898122928">"Preslikaj ekran"</string>
     <string name="dismiss_dialog" msgid="2195508495854675882">"Odbaci"</string>
     <string name="connected_display_icon_desc" msgid="6373560639989971997">"Ekran je povezan"</string>
diff --git a/packages/SystemUI/res/values-bg/strings.xml b/packages/SystemUI/res/values-bg/strings.xml
index 1fe96a6..22d167e 100644
--- a/packages/SystemUI/res/values-bg/strings.xml
+++ b/packages/SystemUI/res/values-bg/strings.xml
@@ -1208,8 +1208,7 @@
     <string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"Задайте стандартно приложение за бележки от настройките"</string>
     <string name="install_app" msgid="5066668100199613936">"Инсталиране на приложението"</string>
     <string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"Да се дублира ли на външния екран?"</string>
-    <!-- no translation found for connected_display_dialog_dual_display_stop_warning (4174707498892447947) -->
-    <skip />
+    <string name="connected_display_dialog_dual_display_stop_warning" msgid="4174707498892447947">"Съдържанието на вътрешния ви дисплей ще бъде дублирано. Предният ви дисплей ще бъде изключен."</string>
     <string name="mirror_display" msgid="2515262008898122928">"Дублиране на дисплея"</string>
     <string name="dismiss_dialog" msgid="2195508495854675882">"Отхвърляне"</string>
     <string name="connected_display_icon_desc" msgid="6373560639989971997">"Свързан е екран"</string>
diff --git a/packages/SystemUI/res/values-bn/strings.xml b/packages/SystemUI/res/values-bn/strings.xml
index fa30131..045af93 100644
--- a/packages/SystemUI/res/values-bn/strings.xml
+++ b/packages/SystemUI/res/values-bn/strings.xml
@@ -1208,8 +1208,7 @@
     <string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"\'সেটিংস\' থেকে ডিফল্ট নোট নেওয়ার অ্যাপ সেট করুন"</string>
     <string name="install_app" msgid="5066668100199613936">"অ্যাপ ইনস্টল করুন"</string>
     <string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"এক্সটার্নাল ডিসপ্লেতে মিরর করবেন?"</string>
-    <!-- no translation found for connected_display_dialog_dual_display_stop_warning (4174707498892447947) -->
-    <skip />
+    <string name="connected_display_dialog_dual_display_stop_warning" msgid="4174707498892447947">"আপনার ইনার ডিসপ্লে মিরর করা হবে। আপনার ফ্রন্ট ডিসপ্লে বন্ধ করা হবে।"</string>
     <string name="mirror_display" msgid="2515262008898122928">"ডিসপ্লে দেখান"</string>
     <string name="dismiss_dialog" msgid="2195508495854675882">"বাতিল করুন"</string>
     <string name="connected_display_icon_desc" msgid="6373560639989971997">"ডিসপ্লে কানেক্ট করা আছে"</string>
diff --git a/packages/SystemUI/res/values-bs/strings.xml b/packages/SystemUI/res/values-bs/strings.xml
index db5987fb..ae962ce 100644
--- a/packages/SystemUI/res/values-bs/strings.xml
+++ b/packages/SystemUI/res/values-bs/strings.xml
@@ -1208,8 +1208,7 @@
     <string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"Postavite zadanu aplikaciju za bilješke u Postavkama"</string>
     <string name="install_app" msgid="5066668100199613936">"Instaliraj aplikaciju"</string>
     <string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"Preslikati na vanjski ekran?"</string>
-    <!-- no translation found for connected_display_dialog_dual_display_stop_warning (4174707498892447947) -->
-    <skip />
+    <string name="connected_display_dialog_dual_display_stop_warning" msgid="4174707498892447947">"Unutrašnji ekran će se preslikavati. Prednji ekran će se isključiti."</string>
     <string name="mirror_display" msgid="2515262008898122928">"Preslikaj ekran"</string>
     <string name="dismiss_dialog" msgid="2195508495854675882">"Odbaci"</string>
     <string name="connected_display_icon_desc" msgid="6373560639989971997">"Ekran je povezan"</string>
diff --git a/packages/SystemUI/res/values-ca/strings.xml b/packages/SystemUI/res/values-ca/strings.xml
index af4492b..8cf8828 100644
--- a/packages/SystemUI/res/values-ca/strings.xml
+++ b/packages/SystemUI/res/values-ca/strings.xml
@@ -1207,10 +1207,9 @@
     <string name="assistant_attention_content_description" msgid="4166330881435263596">"S\'ha detectat la presència d\'usuaris"</string>
     <string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"Defineix l\'aplicació de notes predeterminada a Configuració"</string>
     <string name="install_app" msgid="5066668100199613936">"Instal·la l\'aplicació"</string>
-    <string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"Replicar a la pantalla externa?"</string>
-    <!-- no translation found for connected_display_dialog_dual_display_stop_warning (4174707498892447947) -->
-    <skip />
-    <string name="mirror_display" msgid="2515262008898122928">"Replica la pantalla"</string>
+    <string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"Duplicar a la pantalla externa?"</string>
+    <string name="connected_display_dialog_dual_display_stop_warning" msgid="4174707498892447947">"La pantalla interior es duplicarà. La pantalla frontal es desactivarà."</string>
+    <string name="mirror_display" msgid="2515262008898122928">"Duplica la pantalla"</string>
     <string name="dismiss_dialog" msgid="2195508495854675882">"Ignora"</string>
     <string name="connected_display_icon_desc" msgid="6373560639989971997">"Pantalla connectada"</string>
     <string name="privacy_dialog_title" msgid="7839968133469098311">"Micròfon i càmera"</string>
diff --git a/packages/SystemUI/res/values-cs/strings.xml b/packages/SystemUI/res/values-cs/strings.xml
index 25fcf99..17084dc 100644
--- a/packages/SystemUI/res/values-cs/strings.xml
+++ b/packages/SystemUI/res/values-cs/strings.xml
@@ -1208,8 +1208,7 @@
     <string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"Výchozí aplikaci pro poznámky nastavíte v Nastavení"</string>
     <string name="install_app" msgid="5066668100199613936">"Nainstalovat aplikaci"</string>
     <string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"Zrcadlit na externí displej?"</string>
-    <!-- no translation found for connected_display_dialog_dual_display_stop_warning (4174707498892447947) -->
-    <skip />
+    <string name="connected_display_dialog_dual_display_stop_warning" msgid="4174707498892447947">"Vnitřní displej bude zrcadlen. Přední displej bude vypnutý."</string>
     <string name="mirror_display" msgid="2515262008898122928">"Zrcadlit displej"</string>
     <string name="dismiss_dialog" msgid="2195508495854675882">"Zavřít"</string>
     <string name="connected_display_icon_desc" msgid="6373560639989971997">"Displej připojen"</string>
diff --git a/packages/SystemUI/res/values-da/strings.xml b/packages/SystemUI/res/values-da/strings.xml
index 91c223a..41abea3 100644
--- a/packages/SystemUI/res/values-da/strings.xml
+++ b/packages/SystemUI/res/values-da/strings.xml
@@ -1208,8 +1208,7 @@
     <string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"Angiv standardapp til noter i Indstillinger"</string>
     <string name="install_app" msgid="5066668100199613936">"Installer app"</string>
     <string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"Vil du spejle til ekstern skærm?"</string>
-    <!-- no translation found for connected_display_dialog_dual_display_stop_warning (4174707498892447947) -->
-    <skip />
+    <string name="connected_display_dialog_dual_display_stop_warning" msgid="4174707498892447947">"Din indre skærm spejles. Din skærm på forsiden slukkes."</string>
     <string name="mirror_display" msgid="2515262008898122928">"Spejl skærm"</string>
     <string name="dismiss_dialog" msgid="2195508495854675882">"Luk"</string>
     <string name="connected_display_icon_desc" msgid="6373560639989971997">"Skærmen er tilsluttet"</string>
diff --git a/packages/SystemUI/res/values-de/strings.xml b/packages/SystemUI/res/values-de/strings.xml
index 29b7018..d95e229 100644
--- a/packages/SystemUI/res/values-de/strings.xml
+++ b/packages/SystemUI/res/values-de/strings.xml
@@ -1208,8 +1208,7 @@
     <string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"Standard-Notizen-App in den Einstellungen einrichten"</string>
     <string name="install_app" msgid="5066668100199613936">"App installieren"</string>
     <string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"Auf externen Bildschirm spiegeln?"</string>
-    <!-- no translation found for connected_display_dialog_dual_display_stop_warning (4174707498892447947) -->
-    <skip />
+    <string name="connected_display_dialog_dual_display_stop_warning" msgid="4174707498892447947">"Dein inneres Display wird gespiegelt. Das Frontdisplay wird ausgeschaltet."</string>
     <string name="mirror_display" msgid="2515262008898122928">"Bildschirm spiegeln"</string>
     <string name="dismiss_dialog" msgid="2195508495854675882">"Schließen"</string>
     <string name="connected_display_icon_desc" msgid="6373560639989971997">"Bildschirm verbunden"</string>
diff --git a/packages/SystemUI/res/values-el/strings.xml b/packages/SystemUI/res/values-el/strings.xml
index dd24ca6..5848e4f 100644
--- a/packages/SystemUI/res/values-el/strings.xml
+++ b/packages/SystemUI/res/values-el/strings.xml
@@ -1208,8 +1208,7 @@
     <string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"Ορίστε την προεπιλεγμένη εφαρμογή σημειώσεων στις Ρυθμίσεις"</string>
     <string name="install_app" msgid="5066668100199613936">"Εγκατάσταση εφαρμογής"</string>
     <string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"Κατοπτρισμός σε εξωτερική οθόνη;"</string>
-    <!-- no translation found for connected_display_dialog_dual_display_stop_warning (4174707498892447947) -->
-    <skip />
+    <string name="connected_display_dialog_dual_display_stop_warning" msgid="4174707498892447947">"Θα γίνει κατοπτρισμός της εσωτερικής προβολής. Η μπροστινή οθόνη θα απενεργοποιηθεί."</string>
     <string name="mirror_display" msgid="2515262008898122928">"Κατοπτρισμός οθόνης"</string>
     <string name="dismiss_dialog" msgid="2195508495854675882">"Παράβλεψη"</string>
     <string name="connected_display_icon_desc" msgid="6373560639989971997">"Η οθόνη είναι συνδεδεμένη"</string>
diff --git a/packages/SystemUI/res/values-en-rAU/strings.xml b/packages/SystemUI/res/values-en-rAU/strings.xml
index 2d05291..870e4dd 100644
--- a/packages/SystemUI/res/values-en-rAU/strings.xml
+++ b/packages/SystemUI/res/values-en-rAU/strings.xml
@@ -1208,8 +1208,7 @@
     <string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"Set default notes app in Settings"</string>
     <string name="install_app" msgid="5066668100199613936">"Install app"</string>
     <string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"Mirror to external display?"</string>
-    <!-- no translation found for connected_display_dialog_dual_display_stop_warning (4174707498892447947) -->
-    <skip />
+    <string name="connected_display_dialog_dual_display_stop_warning" msgid="4174707498892447947">"Your inner display will be mirrored. Your front display will be turned off."</string>
     <string name="mirror_display" msgid="2515262008898122928">"Mirror display"</string>
     <string name="dismiss_dialog" msgid="2195508495854675882">"Dismiss"</string>
     <string name="connected_display_icon_desc" msgid="6373560639989971997">"Display connected"</string>
diff --git a/packages/SystemUI/res/values-en-rCA/strings.xml b/packages/SystemUI/res/values-en-rCA/strings.xml
index c043b2c..f25baf2 100644
--- a/packages/SystemUI/res/values-en-rCA/strings.xml
+++ b/packages/SystemUI/res/values-en-rCA/strings.xml
@@ -1208,8 +1208,7 @@
     <string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"Set default notes app in Settings"</string>
     <string name="install_app" msgid="5066668100199613936">"Install app"</string>
     <string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"Mirror to external display?"</string>
-    <!-- no translation found for connected_display_dialog_dual_display_stop_warning (4174707498892447947) -->
-    <skip />
+    <string name="connected_display_dialog_dual_display_stop_warning" msgid="4174707498892447947">"Your inner display will be mirrored. Your front display will be turned off."</string>
     <string name="mirror_display" msgid="2515262008898122928">"Mirror display"</string>
     <string name="dismiss_dialog" msgid="2195508495854675882">"Dismiss"</string>
     <string name="connected_display_icon_desc" msgid="6373560639989971997">"Display connected"</string>
diff --git a/packages/SystemUI/res/values-en-rGB/strings.xml b/packages/SystemUI/res/values-en-rGB/strings.xml
index 2d05291..870e4dd 100644
--- a/packages/SystemUI/res/values-en-rGB/strings.xml
+++ b/packages/SystemUI/res/values-en-rGB/strings.xml
@@ -1208,8 +1208,7 @@
     <string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"Set default notes app in Settings"</string>
     <string name="install_app" msgid="5066668100199613936">"Install app"</string>
     <string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"Mirror to external display?"</string>
-    <!-- no translation found for connected_display_dialog_dual_display_stop_warning (4174707498892447947) -->
-    <skip />
+    <string name="connected_display_dialog_dual_display_stop_warning" msgid="4174707498892447947">"Your inner display will be mirrored. Your front display will be turned off."</string>
     <string name="mirror_display" msgid="2515262008898122928">"Mirror display"</string>
     <string name="dismiss_dialog" msgid="2195508495854675882">"Dismiss"</string>
     <string name="connected_display_icon_desc" msgid="6373560639989971997">"Display connected"</string>
diff --git a/packages/SystemUI/res/values-en-rIN/strings.xml b/packages/SystemUI/res/values-en-rIN/strings.xml
index 2d05291..870e4dd 100644
--- a/packages/SystemUI/res/values-en-rIN/strings.xml
+++ b/packages/SystemUI/res/values-en-rIN/strings.xml
@@ -1208,8 +1208,7 @@
     <string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"Set default notes app in Settings"</string>
     <string name="install_app" msgid="5066668100199613936">"Install app"</string>
     <string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"Mirror to external display?"</string>
-    <!-- no translation found for connected_display_dialog_dual_display_stop_warning (4174707498892447947) -->
-    <skip />
+    <string name="connected_display_dialog_dual_display_stop_warning" msgid="4174707498892447947">"Your inner display will be mirrored. Your front display will be turned off."</string>
     <string name="mirror_display" msgid="2515262008898122928">"Mirror display"</string>
     <string name="dismiss_dialog" msgid="2195508495854675882">"Dismiss"</string>
     <string name="connected_display_icon_desc" msgid="6373560639989971997">"Display connected"</string>
diff --git a/packages/SystemUI/res/values-en-rXC/strings.xml b/packages/SystemUI/res/values-en-rXC/strings.xml
index 3691b2c..b3ed714 100644
--- a/packages/SystemUI/res/values-en-rXC/strings.xml
+++ b/packages/SystemUI/res/values-en-rXC/strings.xml
@@ -1208,8 +1208,7 @@
     <string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‎‎‏‏‏‎‎‎‎‎‏‏‏‏‎‎‎‏‏‏‏‏‎‎‎‏‏‎‏‎‏‎‏‏‏‏‎‏‏‏‏‎‏‏‏‏‎‎‏‏‏‎‏‎‏‎‎‎‎‎‏‎Set default notes app in Settings‎‏‎‎‏‎"</string>
     <string name="install_app" msgid="5066668100199613936">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‎‎‏‏‎‎‏‎‏‎‎‎‎‎‏‏‎‏‎‏‏‏‏‎‎‏‎‏‎‏‏‏‏‎‎‏‏‎‎‏‏‎‎‏‏‏‎‎‎‎‏‎‏‏‏‏‏‎‎‎‎‎Install app‎‏‎‎‏‎"</string>
     <string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‏‎‏‏‎‏‎‎‏‎‎‎‏‎‏‏‏‎‏‎‏‎‎‏‎‏‎‏‏‏‏‎‏‎‎‎‎‎‎‏‎‎‎‏‏‎‎‏‎‎‎‏‏‏‏‎‎‎‏‏‎‎Mirror to external display?‎‏‎‎‏‎"</string>
-    <!-- no translation found for connected_display_dialog_dual_display_stop_warning (4174707498892447947) -->
-    <skip />
+    <string name="connected_display_dialog_dual_display_stop_warning" msgid="4174707498892447947">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‏‏‎‎‏‏‏‏‎‏‏‏‏‏‎‎‎‏‎‏‎‎‏‎‏‏‎‏‎‎‎‎‎‏‎‎‎‏‎‏‎‎‏‎‎‏‏‎‏‎‎‎‎‏‏‎‎‏‎‏‏‎Your inner display will be mirrored. Your front display will be turned off.‎‏‎‎‏‎"</string>
     <string name="mirror_display" msgid="2515262008898122928">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‎‎‎‏‎‏‏‏‎‏‎‎‎‎‎‎‎‎‎‎‏‎‏‏‏‏‎‎‎‎‏‏‏‏‏‎‏‎‏‏‎‏‎‎‎‎‏‏‏‎‏‎‎‏‎‏‏‎‎‎‎‎Mirror display‎‏‎‎‏‎"</string>
     <string name="dismiss_dialog" msgid="2195508495854675882">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‏‏‏‏‏‎‎‏‏‏‏‎‎‎‎‎‎‎‎‎‏‏‎‏‎‏‏‎‎‎‎‎‏‏‏‏‎‎‏‏‎‎‎‏‎‏‎‎‏‎‎‏‏‏‏‎‏‎‏‎‏‎‎Dismiss‎‏‎‎‏‎"</string>
     <string name="connected_display_icon_desc" msgid="6373560639989971997">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‏‏‎‎‎‎‏‏‏‎‎‏‏‎‏‏‎‏‏‏‏‏‎‏‎‎‎‏‎‎‎‏‎‏‏‎‏‎‎‎‎‎‎‏‏‎‏‏‏‏‏‎‎‎‎‎‏‏‏‎‏‎Display connected‎‏‎‎‏‎"</string>
diff --git a/packages/SystemUI/res/values-es-rUS/strings.xml b/packages/SystemUI/res/values-es-rUS/strings.xml
index 6a4818e..c535560 100644
--- a/packages/SystemUI/res/values-es-rUS/strings.xml
+++ b/packages/SystemUI/res/values-es-rUS/strings.xml
@@ -1208,8 +1208,7 @@
     <string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"Configura la app de notas predeterminada en Configuración"</string>
     <string name="install_app" msgid="5066668100199613936">"Instalar app"</string>
     <string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"¿Quieres duplicar en la pantalla externa?"</string>
-    <!-- no translation found for connected_display_dialog_dual_display_stop_warning (4174707498892447947) -->
-    <skip />
+    <string name="connected_display_dialog_dual_display_stop_warning" msgid="4174707498892447947">"Se duplicará la pantalla interior. Se apagará la pantalla frontal."</string>
     <string name="mirror_display" msgid="2515262008898122928">"Duplicar pantalla"</string>
     <string name="dismiss_dialog" msgid="2195508495854675882">"Descartar"</string>
     <string name="connected_display_icon_desc" msgid="6373560639989971997">"Pantalla conectada"</string>
diff --git a/packages/SystemUI/res/values-es/strings.xml b/packages/SystemUI/res/values-es/strings.xml
index 4e41db3..1157ff1 100644
--- a/packages/SystemUI/res/values-es/strings.xml
+++ b/packages/SystemUI/res/values-es/strings.xml
@@ -1208,8 +1208,7 @@
     <string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"Configura la aplicación de notas predeterminada en Ajustes"</string>
     <string name="install_app" msgid="5066668100199613936">"Instalar aplicación"</string>
     <string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"¿Proyectar a pantalla externa?"</string>
-    <!-- no translation found for connected_display_dialog_dual_display_stop_warning (4174707498892447947) -->
-    <skip />
+    <string name="connected_display_dialog_dual_display_stop_warning" msgid="4174707498892447947">"Se proyectará tu pantalla interior. Se apagará tu pantalla frontal."</string>
     <string name="mirror_display" msgid="2515262008898122928">"Proyectar pantalla"</string>
     <string name="dismiss_dialog" msgid="2195508495854675882">"Cerrar"</string>
     <string name="connected_display_icon_desc" msgid="6373560639989971997">"Pantalla conectada"</string>
diff --git a/packages/SystemUI/res/values-et/strings.xml b/packages/SystemUI/res/values-et/strings.xml
index 231ddeb..36b3f1e 100644
--- a/packages/SystemUI/res/values-et/strings.xml
+++ b/packages/SystemUI/res/values-et/strings.xml
@@ -1208,8 +1208,7 @@
     <string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"Määrake seadetes märkmete vaikerakendus."</string>
     <string name="install_app" msgid="5066668100199613936">"Installi rakendus"</string>
     <string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"Kas peegeldada välisekraanile?"</string>
-    <!-- no translation found for connected_display_dialog_dual_display_stop_warning (4174707498892447947) -->
-    <skip />
+    <string name="connected_display_dialog_dual_display_stop_warning" msgid="4174707498892447947">"Teie siseekraani peegeldatakse. Teie esiekraan lülitatakse välja."</string>
     <string name="mirror_display" msgid="2515262008898122928">"Peegelda ekraani"</string>
     <string name="dismiss_dialog" msgid="2195508495854675882">"Loobu"</string>
     <string name="connected_display_icon_desc" msgid="6373560639989971997">"Kuvar on ühendatud"</string>
diff --git a/packages/SystemUI/res/values-eu/strings.xml b/packages/SystemUI/res/values-eu/strings.xml
index 20bf71b..5ea02d1 100644
--- a/packages/SystemUI/res/values-eu/strings.xml
+++ b/packages/SystemUI/res/values-eu/strings.xml
@@ -1208,8 +1208,7 @@
     <string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"Ezarri oharren aplikazio lehenetsia ezarpenetan"</string>
     <string name="install_app" msgid="5066668100199613936">"Instalatu aplikazioa"</string>
     <string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"Kanpoko pantailan islatu nahi duzu?"</string>
-    <!-- no translation found for connected_display_dialog_dual_display_stop_warning (4174707498892447947) -->
-    <skip />
+    <string name="connected_display_dialog_dual_display_stop_warning" msgid="4174707498892447947">"Barneko pantaila islatuko da. Aurreko pantaila desaktibatu egingo da."</string>
     <string name="mirror_display" msgid="2515262008898122928">"Islatu pantaila"</string>
     <string name="dismiss_dialog" msgid="2195508495854675882">"Baztertu"</string>
     <string name="connected_display_icon_desc" msgid="6373560639989971997">"Konektatutako pantaila"</string>
diff --git a/packages/SystemUI/res/values-fa/strings.xml b/packages/SystemUI/res/values-fa/strings.xml
index e161d7a..aa77b4b 100644
--- a/packages/SystemUI/res/values-fa/strings.xml
+++ b/packages/SystemUI/res/values-fa/strings.xml
@@ -1208,8 +1208,7 @@
     <string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"برنامه پیش‌فرض یادداشت را در «تنظیمات» تنظیم کنید"</string>
     <string name="install_app" msgid="5066668100199613936">"نصب برنامه"</string>
     <string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"روی نمایشگر خارجی قرینه‌سازی شود؟"</string>
-    <!-- no translation found for connected_display_dialog_dual_display_stop_warning (4174707498892447947) -->
-    <skip />
+    <string name="connected_display_dialog_dual_display_stop_warning" msgid="4174707498892447947">"نمایشگر داخلی شما قرینه‌سازی می‌شود. نمایشگر جلو خاموش می‌شود."</string>
     <string name="mirror_display" msgid="2515262008898122928">"قرینه‌سازی نمایشگر"</string>
     <string name="dismiss_dialog" msgid="2195508495854675882">"بستن"</string>
     <string name="connected_display_icon_desc" msgid="6373560639989971997">"نمایشگر متصل شد"</string>
diff --git a/packages/SystemUI/res/values-fi/strings.xml b/packages/SystemUI/res/values-fi/strings.xml
index 240607b..882c42c 100644
--- a/packages/SystemUI/res/values-fi/strings.xml
+++ b/packages/SystemUI/res/values-fi/strings.xml
@@ -1208,8 +1208,7 @@
     <string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"Aseta oletusmuistiinpanosovellus Asetuksista"</string>
     <string name="install_app" msgid="5066668100199613936">"Asenna sovellus"</string>
     <string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"Peilataanko ulkoiselle näytölle?"</string>
-    <!-- no translation found for connected_display_dialog_dual_display_stop_warning (4174707498892447947) -->
-    <skip />
+    <string name="connected_display_dialog_dual_display_stop_warning" msgid="4174707498892447947">"Sisänäyttö peilataan. Etunäyttö laitetaan pois päältä."</string>
     <string name="mirror_display" msgid="2515262008898122928">"Peilaa näyttö"</string>
     <string name="dismiss_dialog" msgid="2195508495854675882">"Ohita"</string>
     <string name="connected_display_icon_desc" msgid="6373560639989971997">"Näyttö yhdistetty"</string>
diff --git a/packages/SystemUI/res/values-fr-rCA/strings.xml b/packages/SystemUI/res/values-fr-rCA/strings.xml
index 82a56f5..8370b01 100644
--- a/packages/SystemUI/res/values-fr-rCA/strings.xml
+++ b/packages/SystemUI/res/values-fr-rCA/strings.xml
@@ -1208,8 +1208,7 @@
     <string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"Définir l\'application de prise de notes par défaut dans les Paramètres"</string>
     <string name="install_app" msgid="5066668100199613936">"Installer l\'application"</string>
     <string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"Dupliquer l\'écran sur un moniteur externe?"</string>
-    <!-- no translation found for connected_display_dialog_dual_display_stop_warning (4174707498892447947) -->
-    <skip />
+    <string name="connected_display_dialog_dual_display_stop_warning" msgid="4174707498892447947">"Votre écran intérieur sera dupliqué. Votre écran frontal sera désactivé."</string>
     <string name="mirror_display" msgid="2515262008898122928">"Dupliquer l\'écran"</string>
     <string name="dismiss_dialog" msgid="2195508495854675882">"Fermer"</string>
     <string name="connected_display_icon_desc" msgid="6373560639989971997">"Écran connecté"</string>
diff --git a/packages/SystemUI/res/values-fr/strings.xml b/packages/SystemUI/res/values-fr/strings.xml
index c35f9ee..af81a31 100644
--- a/packages/SystemUI/res/values-fr/strings.xml
+++ b/packages/SystemUI/res/values-fr/strings.xml
@@ -1208,8 +1208,7 @@
     <string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"Définir une appli de notes par défaut dans les paramètres"</string>
     <string name="install_app" msgid="5066668100199613936">"Installer l\'appli"</string>
     <string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"Dupliquer sur l\'écran externe ?"</string>
-    <!-- no translation found for connected_display_dialog_dual_display_stop_warning (4174707498892447947) -->
-    <skip />
+    <string name="connected_display_dialog_dual_display_stop_warning" msgid="4174707498892447947">"Votre écran intérieur sera dupliqué. Votre écran frontal sera éteint."</string>
     <string name="mirror_display" msgid="2515262008898122928">"Dupliquer l\'écran"</string>
     <string name="dismiss_dialog" msgid="2195508495854675882">"Fermer"</string>
     <string name="connected_display_icon_desc" msgid="6373560639989971997">"Écran connecté"</string>
diff --git a/packages/SystemUI/res/values-gl/strings.xml b/packages/SystemUI/res/values-gl/strings.xml
index 9cf968e..ef0751b 100644
--- a/packages/SystemUI/res/values-gl/strings.xml
+++ b/packages/SystemUI/res/values-gl/strings.xml
@@ -1208,8 +1208,7 @@
     <string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"Establece a aplicación de notas predeterminada en Configuración"</string>
     <string name="install_app" msgid="5066668100199613936">"Instalar aplicación"</string>
     <string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"Queres proxectar contido nunha pantalla externa?"</string>
-    <!-- no translation found for connected_display_dialog_dual_display_stop_warning (4174707498892447947) -->
-    <skip />
+    <string name="connected_display_dialog_dual_display_stop_warning" msgid="4174707498892447947">"Proxectarase a pantalla interior. Desactivarase a pantalla frontal."</string>
     <string name="mirror_display" msgid="2515262008898122928">"Proxectar pantalla"</string>
     <string name="dismiss_dialog" msgid="2195508495854675882">"Pechar"</string>
     <string name="connected_display_icon_desc" msgid="6373560639989971997">"Pantalla conectada"</string>
diff --git a/packages/SystemUI/res/values-gu/strings.xml b/packages/SystemUI/res/values-gu/strings.xml
index df7b203..d92231c 100644
--- a/packages/SystemUI/res/values-gu/strings.xml
+++ b/packages/SystemUI/res/values-gu/strings.xml
@@ -1208,8 +1208,7 @@
     <string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"સેટિંગમાં નોંધની ડિફૉલ્ટ ઍપ સેટ કરો"</string>
     <string name="install_app" msgid="5066668100199613936">"ઍપ ઇન્સ્ટૉલ કરો"</string>
     <string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"શું બાહ્ય ડિસ્પ્લે પર મિરર કરીએ?"</string>
-    <!-- no translation found for connected_display_dialog_dual_display_stop_warning (4174707498892447947) -->
-    <skip />
+    <string name="connected_display_dialog_dual_display_stop_warning" msgid="4174707498892447947">"તમારું ઇનર ડિસ્પ્લે મિરર કરવામાં આવશે. તમારું ફ્રન્ટ ડિસ્પ્લે બંધ કરવામાં આવશે."</string>
     <string name="mirror_display" msgid="2515262008898122928">"મિરર ડિસ્પ્લે"</string>
     <string name="dismiss_dialog" msgid="2195508495854675882">"છોડી દો"</string>
     <string name="connected_display_icon_desc" msgid="6373560639989971997">"Display કનેક્ટેડ છે"</string>
diff --git a/packages/SystemUI/res/values-hi/strings.xml b/packages/SystemUI/res/values-hi/strings.xml
index eec3078..f8c78a9 100644
--- a/packages/SystemUI/res/values-hi/strings.xml
+++ b/packages/SystemUI/res/values-hi/strings.xml
@@ -1208,8 +1208,7 @@
     <string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"सेटिंग में जाकर, नोट लेने की सुविधा देने वाले ऐप्लिकेशन को डिफ़ॉल्ट के तौर पर सेट करें"</string>
     <string name="install_app" msgid="5066668100199613936">"ऐप्लिकेशन इंस्टॉल करें"</string>
     <string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"क्या आपको किसी बाहरी डिवाइस पर डिसप्ले करना है?"</string>
-    <!-- no translation found for connected_display_dialog_dual_display_stop_warning (4174707498892447947) -->
-    <skip />
+    <string name="connected_display_dialog_dual_display_stop_warning" msgid="4174707498892447947">"आपके फ़ोन के इनर डिसप्ले की स्क्रीन शेयर की जाएगी. फ़्रंट डिसप्ले को बंद कर दिया जाएगा."</string>
     <string name="mirror_display" msgid="2515262008898122928">"डिसप्ले करें"</string>
     <string name="dismiss_dialog" msgid="2195508495854675882">"खारिज करें"</string>
     <string name="connected_display_icon_desc" msgid="6373560639989971997">"डिसप्ले कनेक्ट किया गया"</string>
diff --git a/packages/SystemUI/res/values-hr/strings.xml b/packages/SystemUI/res/values-hr/strings.xml
index f0ed400..0803aeb 100644
--- a/packages/SystemUI/res/values-hr/strings.xml
+++ b/packages/SystemUI/res/values-hr/strings.xml
@@ -1208,8 +1208,7 @@
     <string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"Postavite zadanu aplikaciju za bilješke u postavkama"</string>
     <string name="install_app" msgid="5066668100199613936">"Instalacija"</string>
     <string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"Želite li zrcaliti na vanjski zaslon?"</string>
-    <!-- no translation found for connected_display_dialog_dual_display_stop_warning (4174707498892447947) -->
-    <skip />
+    <string name="connected_display_dialog_dual_display_stop_warning" msgid="4174707498892447947">"Unutarnji zaslon bit će zrcaljen. Prednji zaslon bit će isključen."</string>
     <string name="mirror_display" msgid="2515262008898122928">"Zrcaljenje zaslona"</string>
     <string name="dismiss_dialog" msgid="2195508495854675882">"Odbaci"</string>
     <string name="connected_display_icon_desc" msgid="6373560639989971997">"Zaslon je povezan"</string>
diff --git a/packages/SystemUI/res/values-hu/strings.xml b/packages/SystemUI/res/values-hu/strings.xml
index 30d2961..fd9d832 100644
--- a/packages/SystemUI/res/values-hu/strings.xml
+++ b/packages/SystemUI/res/values-hu/strings.xml
@@ -1208,8 +1208,7 @@
     <string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"Állítson be alapértelmezett jegyzetkészítő alkalmazást a Beállításokban"</string>
     <string name="install_app" msgid="5066668100199613936">"Alkalmazás telepítése"</string>
     <string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"Tükrözi a kijelzőt a külső képernyőre?"</string>
-    <!-- no translation found for connected_display_dialog_dual_display_stop_warning (4174707498892447947) -->
-    <skip />
+    <string name="connected_display_dialog_dual_display_stop_warning" msgid="4174707498892447947">"A belső kijelző tükrözve lesz. Az elülső kijelző ki lesz kapcsolva."</string>
     <string name="mirror_display" msgid="2515262008898122928">"Kijelző tükrözése"</string>
     <string name="dismiss_dialog" msgid="2195508495854675882">"Elvetés"</string>
     <string name="connected_display_icon_desc" msgid="6373560639989971997">"Kijelző csatlakoztatva"</string>
diff --git a/packages/SystemUI/res/values-hy/strings.xml b/packages/SystemUI/res/values-hy/strings.xml
index bbdeb16..a9252e2 100644
--- a/packages/SystemUI/res/values-hy/strings.xml
+++ b/packages/SystemUI/res/values-hy/strings.xml
@@ -1208,8 +1208,7 @@
     <string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"Կարգավորեք նշումների կանխադրված հավելված Կարգավորումներում"</string>
     <string name="install_app" msgid="5066668100199613936">"Տեղադրել հավելվածը"</string>
     <string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"Հայելապատճենե՞լ արտաքին էկրանին"</string>
-    <!-- no translation found for connected_display_dialog_dual_display_stop_warning (4174707498892447947) -->
-    <skip />
+    <string name="connected_display_dialog_dual_display_stop_warning" msgid="4174707498892447947">"Ներքին էկրանը կհայելապատճենվի։ Առջևի էկրանը կանջատվի։"</string>
     <string name="mirror_display" msgid="2515262008898122928">"Հայելապատճենել էկրանը"</string>
     <string name="dismiss_dialog" msgid="2195508495854675882">"Փակել"</string>
     <string name="connected_display_icon_desc" msgid="6373560639989971997">"Էկրանը միացած է"</string>
diff --git a/packages/SystemUI/res/values-in/strings.xml b/packages/SystemUI/res/values-in/strings.xml
index f2ed0e7..8ef809a 100644
--- a/packages/SystemUI/res/values-in/strings.xml
+++ b/packages/SystemUI/res/values-in/strings.xml
@@ -1208,8 +1208,7 @@
     <string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"Setel aplikasi catatan default di Setelan"</string>
     <string name="install_app" msgid="5066668100199613936">"Instal aplikasi"</string>
     <string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"Cerminkan ke layar eksternal?"</string>
-    <!-- no translation found for connected_display_dialog_dual_display_stop_warning (4174707498892447947) -->
-    <skip />
+    <string name="connected_display_dialog_dual_display_stop_warning" msgid="4174707498892447947">"Layar dalam akan dicerminkan. Layar depan akan dinonaktifkan."</string>
     <string name="mirror_display" msgid="2515262008898122928">"Cerminkan layar"</string>
     <string name="dismiss_dialog" msgid="2195508495854675882">"Tutup"</string>
     <string name="connected_display_icon_desc" msgid="6373560639989971997">"Layar terhubung"</string>
diff --git a/packages/SystemUI/res/values-is/strings.xml b/packages/SystemUI/res/values-is/strings.xml
index f1f1b0f..cef3285 100644
--- a/packages/SystemUI/res/values-is/strings.xml
+++ b/packages/SystemUI/res/values-is/strings.xml
@@ -1208,8 +1208,7 @@
     <string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"Stilltu sjálfgefið glósuforrit í stillingunum"</string>
     <string name="install_app" msgid="5066668100199613936">"Setja upp forrit"</string>
     <string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"Spegla yfir á ytri skjá?"</string>
-    <!-- no translation found for connected_display_dialog_dual_display_stop_warning (4174707498892447947) -->
-    <skip />
+    <string name="connected_display_dialog_dual_display_stop_warning" msgid="4174707498892447947">"Innri skjárinn þinn verður speglaður. Slökkt verður á framskjánum þínum."</string>
     <string name="mirror_display" msgid="2515262008898122928">"Spegla skjá"</string>
     <string name="dismiss_dialog" msgid="2195508495854675882">"Hunsa"</string>
     <string name="connected_display_icon_desc" msgid="6373560639989971997">"Skjár tengdur"</string>
diff --git a/packages/SystemUI/res/values-it/strings.xml b/packages/SystemUI/res/values-it/strings.xml
index 223e39f..c5079fe 100644
--- a/packages/SystemUI/res/values-it/strings.xml
+++ b/packages/SystemUI/res/values-it/strings.xml
@@ -1208,8 +1208,7 @@
     <string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"Imposta l\'app per le note predefinita nelle Impostazioni"</string>
     <string name="install_app" msgid="5066668100199613936">"Installa app"</string>
     <string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"Vuoi eseguire il mirroring al display esterno?"</string>
-    <!-- no translation found for connected_display_dialog_dual_display_stop_warning (4174707498892447947) -->
-    <skip />
+    <string name="connected_display_dialog_dual_display_stop_warning" msgid="4174707498892447947">"Verrà eseguito il mirroring del tuo display interno. Il tuo display frontale verrà spento."</string>
     <string name="mirror_display" msgid="2515262008898122928">"Esegui il mirroring del display"</string>
     <string name="dismiss_dialog" msgid="2195508495854675882">"Chiudi"</string>
     <string name="connected_display_icon_desc" msgid="6373560639989971997">"Display collegato"</string>
diff --git a/packages/SystemUI/res/values-iw/strings.xml b/packages/SystemUI/res/values-iw/strings.xml
index 44fd608..486b22f 100644
--- a/packages/SystemUI/res/values-iw/strings.xml
+++ b/packages/SystemUI/res/values-iw/strings.xml
@@ -1208,8 +1208,7 @@
     <string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"צריך להגדיר את אפליקציית ברירת המחדל לפתקים ב\'הגדרות\'"</string>
     <string name="install_app" msgid="5066668100199613936">"התקנת האפליקציה"</string>
     <string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"לשקף למסך חיצוני?"</string>
-    <!-- no translation found for connected_display_dialog_dual_display_stop_warning (4174707498892447947) -->
-    <skip />
+    <string name="connected_display_dialog_dual_display_stop_warning" msgid="4174707498892447947">"המסך הפנימי שלך ישוכפל. המסך החיצוני שלך יכובה."</string>
     <string name="mirror_display" msgid="2515262008898122928">"תצוגת מראה"</string>
     <string name="dismiss_dialog" msgid="2195508495854675882">"סגירה"</string>
     <string name="connected_display_icon_desc" msgid="6373560639989971997">"המסך מחובר"</string>
diff --git a/packages/SystemUI/res/values-ja/strings.xml b/packages/SystemUI/res/values-ja/strings.xml
index 38f2dc8..c742f93 100644
--- a/packages/SystemUI/res/values-ja/strings.xml
+++ b/packages/SystemUI/res/values-ja/strings.xml
@@ -1208,8 +1208,7 @@
     <string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"[設定] でデフォルトのメモアプリを設定してください"</string>
     <string name="install_app" msgid="5066668100199613936">"アプリをインストール"</string>
     <string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"外部ディスプレイにミラーリングしますか?"</string>
-    <!-- no translation found for connected_display_dialog_dual_display_stop_warning (4174707498892447947) -->
-    <skip />
+    <string name="connected_display_dialog_dual_display_stop_warning" msgid="4174707498892447947">"インナー ディスプレイがミラーリングされます。フロント ディスプレイは OFF になります。"</string>
     <string name="mirror_display" msgid="2515262008898122928">"ディスプレイをミラーリングする"</string>
     <string name="dismiss_dialog" msgid="2195508495854675882">"閉じる"</string>
     <string name="connected_display_icon_desc" msgid="6373560639989971997">"ディスプレイに接続しました"</string>
diff --git a/packages/SystemUI/res/values-ka/strings.xml b/packages/SystemUI/res/values-ka/strings.xml
index bb21f3f..9d386ef 100644
--- a/packages/SystemUI/res/values-ka/strings.xml
+++ b/packages/SystemUI/res/values-ka/strings.xml
@@ -1208,8 +1208,7 @@
     <string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"დააყენეთ ნაგულისხმევი შენიშვნების აპი პარამეტრებში"</string>
     <string name="install_app" msgid="5066668100199613936">"აპის ინსტალაცია"</string>
     <string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"აირეკლოს გარე ეკრანზე?"</string>
-    <!-- no translation found for connected_display_dialog_dual_display_stop_warning (4174707498892447947) -->
-    <skip />
+    <string name="connected_display_dialog_dual_display_stop_warning" msgid="4174707498892447947">"თქვენი შიდა ეკრანი აირეკლება. თქვენი წინა ეკრანი გამოირთვება."</string>
     <string name="mirror_display" msgid="2515262008898122928">"ეკრანის არეკვლა"</string>
     <string name="dismiss_dialog" msgid="2195508495854675882">"დახურვა"</string>
     <string name="connected_display_icon_desc" msgid="6373560639989971997">"ეკრანი დაკავშირებულია"</string>
diff --git a/packages/SystemUI/res/values-kk/strings.xml b/packages/SystemUI/res/values-kk/strings.xml
index b39d5a5..d2c60f1 100644
--- a/packages/SystemUI/res/values-kk/strings.xml
+++ b/packages/SystemUI/res/values-kk/strings.xml
@@ -1208,8 +1208,7 @@
     <string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"Параметрлерден әдепкі жазба қолданбасын орнатыңыз."</string>
     <string name="install_app" msgid="5066668100199613936">"Қолданбаны орнату"</string>
     <string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"Сыртқы экран арқылы да көрсету керек пе?"</string>
-    <!-- no translation found for connected_display_dialog_dual_display_stop_warning (4174707498892447947) -->
-    <skip />
+    <string name="connected_display_dialog_dual_display_stop_warning" msgid="4174707498892447947">"Ішкі экран көшірмесі көрсетіледі. Алдыңғы экран өшіріледі."</string>
     <string name="mirror_display" msgid="2515262008898122928">"Көрсету"</string>
     <string name="dismiss_dialog" msgid="2195508495854675882">"Жабу"</string>
     <string name="connected_display_icon_desc" msgid="6373560639989971997">"Дисплей қосылды"</string>
diff --git a/packages/SystemUI/res/values-km/strings.xml b/packages/SystemUI/res/values-km/strings.xml
index e989a9d..ec81523 100644
--- a/packages/SystemUI/res/values-km/strings.xml
+++ b/packages/SystemUI/res/values-km/strings.xml
@@ -1208,8 +1208,7 @@
     <string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"កំណត់កម្មវិធីកំណត់ចំណាំលំនាំដើមនៅក្នុងការកំណត់"</string>
     <string name="install_app" msgid="5066668100199613936">"ដំឡើង​កម្មវិធី"</string>
     <string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"បញ្ចាំងទៅ​ផ្ទាំងអេក្រង់​ខាងក្រៅឬ?"</string>
-    <!-- no translation found for connected_display_dialog_dual_display_stop_warning (4174707498892447947) -->
-    <skip />
+    <string name="connected_display_dialog_dual_display_stop_warning" msgid="4174707498892447947">"អេក្រង់ខាងក្នុងរបស់អ្នកនឹងត្រូវបានធ្វើ​សមកាលកម្មទៅវិញទៅមក។ អេក្រង់មុខរបស់អ្នកនឹងត្រូវបានបិទ។"</string>
     <string name="mirror_display" msgid="2515262008898122928">"បញ្ចាំងទៅផ្ទាំងអេក្រង់"</string>
     <string name="dismiss_dialog" msgid="2195508495854675882">"ច្រានចោល"</string>
     <string name="connected_display_icon_desc" msgid="6373560639989971997">"ផ្ទាំងអេក្រង់ត្រូវបានភ្ជាប់"</string>
diff --git a/packages/SystemUI/res/values-kn/strings.xml b/packages/SystemUI/res/values-kn/strings.xml
index 39b1468..33f4528 100644
--- a/packages/SystemUI/res/values-kn/strings.xml
+++ b/packages/SystemUI/res/values-kn/strings.xml
@@ -93,7 +93,7 @@
     <string name="screenshot_bottom_boundary_pct" msgid="3880821519814946478">"ಕೆಳಗಿನ ಬೌಂಡರಿ ಶೇಕಡಾ <xliff:g id="PERCENT">%1$d</xliff:g>"</string>
     <string name="screenshot_left_boundary_pct" msgid="8502323556112287469">"ಎಡಭಾಗದ ಬೌಂಡರಿ ಶೇಕಡಾ <xliff:g id="PERCENT">%1$d</xliff:g>"</string>
     <string name="screenshot_right_boundary_pct" msgid="1201150713021779321">"ಬಲಭಾಗದ ಬೌಂಡರಿ ಶೇಕಡಾ <xliff:g id="PERCENT">%1$d</xliff:g>"</string>
-    <string name="screenshot_work_profile_notification" msgid="203041724052970693">"ಕೆಲಸದ ಪ್ರೊಫೈಲ್‌ನಲ್ಲಿನ <xliff:g id="APP">%1$s</xliff:g> ನಲ್ಲಿ ಉಳಿಸಲಾಗಿದೆ"</string>
+    <string name="screenshot_work_profile_notification" msgid="203041724052970693">"ಕೆಲಸದ ಪ್ರೊಫೈಲ್‌ನಲ್ಲಿನ <xliff:g id="APP">%1$s</xliff:g> ನಲ್ಲಿ ಸೇವ್ ಮಾಡಲಾಗಿದೆ"</string>
     <string name="screenshot_default_files_app_name" msgid="8721579578575161912">"ಫೈಲ್‌ಗಳು"</string>
     <string name="screenshot_detected_template" msgid="7940376642921719915">"ಈ ಸ್ಕ್ರೀನ್‌ಶಾಟ್ ಅನ್ನು <xliff:g id="APPNAME">%1$s</xliff:g> ಪತ್ತೆಹಚ್ಚಿದೆ."</string>
     <string name="screenshot_detected_multiple_template" msgid="7644827792093819241">"<xliff:g id="APPNAME">%1$s</xliff:g> ಹಾಗೂ ತೆರೆದಿರುವ ಇತರ ಆ್ಯಪ್‌ಗಳು ಈ ಸ್ಕ್ರೀನ್‌ಶಾಟ್ ಅನ್ನು ಪತ್ತೆಹಚ್ಚಿವೆ."</string>
@@ -1208,8 +1208,7 @@
     <string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"ಸೆಟ್ಟಿಂಗ್‌ಗಳಲ್ಲಿ ಡೀಫಾಲ್ಟ್ ಟಿಪ್ಪಣಿಗಳ ಆ್ಯಪ್ ಅನ್ನು ಸೆಟ್ ಮಾಡಿ"</string>
     <string name="install_app" msgid="5066668100199613936">"ಆ್ಯಪ್ ಇನ್‌ಸ್ಟಾಲ್ ಮಾಡಿ"</string>
     <string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"ಬಾಹ್ಯ ಡಿಸ್‌ಪ್ಲೇಗೆ ಪ್ರತಿಬಿಂಬಿಸಬೇಕೆ?"</string>
-    <!-- no translation found for connected_display_dialog_dual_display_stop_warning (4174707498892447947) -->
-    <skip />
+    <string name="connected_display_dialog_dual_display_stop_warning" msgid="4174707498892447947">"ನಿಮ್ಮ ಒಳಗಿನ ಡಿಸ್‌ಪ್ಲೇ ಅನ್ನು ಪ್ರತಿಬಿಂಬಿಸಲಾಗುತ್ತದೆ. ನಿಮ್ಮ ಫ್ರಂಟ್ ಡಿಸ್‌ಪ್ಲೇ ಅನ್ನು ಆಫ್ ಮಾಡಲಾಗುತ್ತದೆ."</string>
     <string name="mirror_display" msgid="2515262008898122928">"ಮಿರರ್ ಡಿಸ್‌ಪ್ಲೇ"</string>
     <string name="dismiss_dialog" msgid="2195508495854675882">"ವಜಾಗೊಳಿಸಿ"</string>
     <string name="connected_display_icon_desc" msgid="6373560639989971997">"ಡಿಸ್‌ಪ್ಲೇ ಕನೆಕ್ಟ್ ಆಗಿದೆ"</string>
diff --git a/packages/SystemUI/res/values-kn/tiles_states_strings.xml b/packages/SystemUI/res/values-kn/tiles_states_strings.xml
index 16e82ea..876562d 100644
--- a/packages/SystemUI/res/values-kn/tiles_states_strings.xml
+++ b/packages/SystemUI/res/values-kn/tiles_states_strings.xml
@@ -33,147 +33,147 @@
     <!-- no translation found for tile_states_default:2 (9192445505551219506) -->
   <string-array name="tile_states_internet">
     <item msgid="5499482407653291407">"ಲಭ್ಯವಿಲ್ಲ"</item>
-    <item msgid="3048856902433862868">"ಆಫ್ ಮಾಡಿ"</item>
+    <item msgid="3048856902433862868">"ಆಫ್"</item>
     <item msgid="6877982264300789870">"ಆನ್ ಮಾಡಿ"</item>
   </string-array>
   <string-array name="tile_states_wifi">
     <item msgid="8054147400538405410">"ಲಭ್ಯವಿಲ್ಲ"</item>
-    <item msgid="4293012229142257455">"ಆಫ್ ಮಾಡಿ"</item>
+    <item msgid="4293012229142257455">"ಆಫ್"</item>
     <item msgid="6221288736127914861">"ಆನ್ ಮಾಡಿ"</item>
   </string-array>
   <string-array name="tile_states_cell">
     <item msgid="1235899788959500719">"ಲಭ್ಯವಿಲ್ಲ"</item>
-    <item msgid="2074416252859094119">"ಆಫ್ ಮಾಡಿ"</item>
+    <item msgid="2074416252859094119">"ಆಫ್"</item>
     <item msgid="287997784730044767">"ಆನ್ ಮಾಡಿ"</item>
   </string-array>
   <string-array name="tile_states_battery">
     <item msgid="6311253873330062961">"ಲಭ್ಯವಿಲ್ಲ"</item>
-    <item msgid="7838121007534579872">"ಆಫ್ ಮಾಡಿ"</item>
+    <item msgid="7838121007534579872">"ಆಫ್"</item>
     <item msgid="1578872232501319194">"ಆನ್ ಮಾಡಿ"</item>
   </string-array>
   <string-array name="tile_states_dnd">
     <item msgid="467587075903158357">"ಲಭ್ಯವಿಲ್ಲ"</item>
-    <item msgid="5376619709702103243">"ಆಫ್ ಮಾಡಿ"</item>
+    <item msgid="5376619709702103243">"ಆಫ್"</item>
     <item msgid="4875147066469902392">"ಆನ್ ಮಾಡಿ"</item>
   </string-array>
   <string-array name="tile_states_flashlight">
     <item msgid="3465257127433353857">"ಲಭ್ಯವಿಲ್ಲ"</item>
-    <item msgid="5044688398303285224">"ಆಫ್ ಮಾಡಿ"</item>
+    <item msgid="5044688398303285224">"ಆಫ್"</item>
     <item msgid="8527389108867454098">"ಆನ್ ಮಾಡಿ"</item>
   </string-array>
   <string-array name="tile_states_rotation">
     <item msgid="4578491772376121579">"ಲಭ್ಯವಿಲ್ಲ"</item>
-    <item msgid="5776427577477729185">"ಆಫ್ ಮಾಡಿ"</item>
+    <item msgid="5776427577477729185">"ಆಫ್"</item>
     <item msgid="7105052717007227415">"ಆನ್ ಮಾಡಿ"</item>
   </string-array>
   <string-array name="tile_states_bt">
     <item msgid="5330252067413512277">"ಲಭ್ಯವಿಲ್ಲ"</item>
-    <item msgid="5315121904534729843">"ಆಫ್ ಮಾಡಿ"</item>
+    <item msgid="5315121904534729843">"ಆಫ್"</item>
     <item msgid="503679232285959074">"ಆನ್ ಮಾಡಿ"</item>
   </string-array>
   <string-array name="tile_states_airplane">
     <item msgid="1985366811411407764">"ಲಭ್ಯವಿಲ್ಲ"</item>
-    <item msgid="4801037224991420996">"ಆಫ್ ಮಾಡಿ"</item>
+    <item msgid="4801037224991420996">"ಆಫ್"</item>
     <item msgid="1982293347302546665">"ಆನ್ ಮಾಡಿ"</item>
   </string-array>
   <string-array name="tile_states_location">
     <item msgid="3316542218706374405">"ಲಭ್ಯವಿಲ್ಲ"</item>
-    <item msgid="4813655083852587017">"ಆಫ್ ಮಾಡಿ"</item>
+    <item msgid="4813655083852587017">"ಆಫ್"</item>
     <item msgid="6744077414775180687">"ಆನ್ ಮಾಡಿ"</item>
   </string-array>
   <string-array name="tile_states_hotspot">
     <item msgid="3145597331197351214">"ಲಭ್ಯವಿಲ್ಲ"</item>
-    <item msgid="5715725170633593906">"ಆಫ್ ಮಾಡಿ"</item>
+    <item msgid="5715725170633593906">"ಆಫ್"</item>
     <item msgid="2075645297847971154">"ಆನ್ ಮಾಡಿ"</item>
   </string-array>
   <string-array name="tile_states_color_correction">
     <item msgid="2840507878437297682">"ಲಭ್ಯವಿಲ್ಲ"</item>
-    <item msgid="1909756493418256167">"ಆಫ್ ಮಾಡಿ"</item>
+    <item msgid="1909756493418256167">"ಆಫ್"</item>
     <item msgid="4531508423703413340">"ಆನ್ ಮಾಡಿ"</item>
   </string-array>
   <string-array name="tile_states_inversion">
     <item msgid="3638187931191394628">"ಲಭ್ಯವಿಲ್ಲ"</item>
-    <item msgid="9103697205127645916">"ಆಫ್ ಮಾಡಿ"</item>
+    <item msgid="9103697205127645916">"ಆಫ್"</item>
     <item msgid="8067744885820618230">"ಆನ್ ಮಾಡಿ"</item>
   </string-array>
   <string-array name="tile_states_saver">
     <item msgid="39714521631367660">"ಲಭ್ಯವಿಲ್ಲ"</item>
-    <item msgid="6983679487661600728">"ಆಫ್ ಮಾಡಿ"</item>
+    <item msgid="6983679487661600728">"ಆಫ್"</item>
     <item msgid="7520663805910678476">"ಆನ್ ಮಾಡಿ"</item>
   </string-array>
   <string-array name="tile_states_dark">
     <item msgid="2762596907080603047">"ಲಭ್ಯವಿಲ್ಲ"</item>
-    <item msgid="400477985171353">"ಆಫ್ ಮಾಡಿ"</item>
+    <item msgid="400477985171353">"ಆಫ್"</item>
     <item msgid="630890598801118771">"ಆನ್ ಮಾಡಿ"</item>
   </string-array>
   <string-array name="tile_states_work">
     <item msgid="389523503690414094">"ಲಭ್ಯವಿಲ್ಲ"</item>
-    <item msgid="8045580926543311193">"ಆಫ್ ಮಾಡಿ"</item>
+    <item msgid="8045580926543311193">"ಆಫ್"</item>
     <item msgid="4913460972266982499">"ಆನ್ ಮಾಡಿ"</item>
   </string-array>
   <string-array name="tile_states_cast">
     <item msgid="6032026038702435350">"ಲಭ್ಯವಿಲ್ಲ"</item>
-    <item msgid="1488620600954313499">"ಆಫ್ ಮಾಡಿ"</item>
+    <item msgid="1488620600954313499">"ಆಫ್"</item>
     <item msgid="588467578853244035">"ಆನ್ ಮಾಡಿ"</item>
   </string-array>
   <string-array name="tile_states_night">
     <item msgid="7857498964264855466">"ಲಭ್ಯವಿಲ್ಲ"</item>
-    <item msgid="2744885441164350155">"ಆಫ್ ಮಾಡಿ"</item>
+    <item msgid="2744885441164350155">"ಆಫ್"</item>
     <item msgid="151121227514952197">"ಆನ್ ಮಾಡಿ"</item>
   </string-array>
   <string-array name="tile_states_screenrecord">
     <item msgid="1085836626613341403">"ಲಭ್ಯವಿಲ್ಲ"</item>
-    <item msgid="8259411607272330225">"ಆಫ್ ಮಾಡಿ"</item>
+    <item msgid="8259411607272330225">"ಆಫ್"</item>
     <item msgid="578444932039713369">"ಆನ್ ಮಾಡಿ"</item>
   </string-array>
   <string-array name="tile_states_reverse">
     <item msgid="3574611556622963971">"ಲಭ್ಯವಿಲ್ಲ"</item>
-    <item msgid="8707481475312432575">"ಆಫ್ ಮಾಡಿ"</item>
+    <item msgid="8707481475312432575">"ಆಫ್"</item>
     <item msgid="8031106212477483874">"ಆನ್ ಮಾಡಿ"</item>
   </string-array>
   <string-array name="tile_states_reduce_brightness">
     <item msgid="1839836132729571766">"ಲಭ್ಯವಿಲ್ಲ"</item>
-    <item msgid="4572245614982283078">"ಆಫ್ ಮಾಡಿ"</item>
+    <item msgid="4572245614982283078">"ಆಫ್"</item>
     <item msgid="6536448410252185664">"ಆನ್ ಮಾಡಿ"</item>
   </string-array>
   <string-array name="tile_states_cameratoggle">
     <item msgid="6680671247180519913">"ಲಭ್ಯವಿಲ್ಲ"</item>
-    <item msgid="4765607635752003190">"ಆಫ್ ಮಾಡಿ"</item>
+    <item msgid="4765607635752003190">"ಆಫ್"</item>
     <item msgid="1697460731949649844">"ಆನ್ ಮಾಡಿ"</item>
   </string-array>
   <string-array name="tile_states_mictoggle">
     <item msgid="6895831614067195493">"ಲಭ್ಯವಿಲ್ಲ"</item>
-    <item msgid="3296179158646568218">"ಆಫ್ ಮಾಡಿ"</item>
+    <item msgid="3296179158646568218">"ಆಫ್"</item>
     <item msgid="8998632451221157987">"ಆನ್ ಮಾಡಿ"</item>
   </string-array>
   <string-array name="tile_states_controls">
     <item msgid="8199009425335668294">"ಲಭ್ಯವಿಲ್ಲ"</item>
-    <item msgid="4544919905196727508">"ಆಫ್ ಮಾಡಿ"</item>
+    <item msgid="4544919905196727508">"ಆಫ್"</item>
     <item msgid="3422023746567004609">"ಆನ್ ಮಾಡಿ"</item>
   </string-array>
   <string-array name="tile_states_wallet">
     <item msgid="4177615438710836341">"ಲಭ್ಯವಿಲ್ಲ"</item>
-    <item msgid="7571394439974244289">"ಆಫ್ ಮಾಡಿ"</item>
+    <item msgid="7571394439974244289">"ಆಫ್"</item>
     <item msgid="6866424167599381915">"ಆನ್ ಮಾಡಿ"</item>
   </string-array>
   <string-array name="tile_states_qr_code_scanner">
     <item msgid="7435143266149257618">"ಲಭ್ಯವಿಲ್ಲ"</item>
-    <item msgid="3301403109049256043">"ಆಫ್ ಮಾಡಿ"</item>
+    <item msgid="3301403109049256043">"ಆಫ್"</item>
     <item msgid="8878684975184010135">"ಆನ್ ಮಾಡಿ"</item>
   </string-array>
   <string-array name="tile_states_alarm">
     <item msgid="4936533380177298776">"ಲಭ್ಯವಿಲ್ಲ"</item>
-    <item msgid="2710157085538036590">"ಆಫ್ ಮಾಡಿ"</item>
+    <item msgid="2710157085538036590">"ಆಫ್"</item>
     <item msgid="7809470840976856149">"ಆನ್ ಮಾಡಿ"</item>
   </string-array>
   <string-array name="tile_states_onehanded">
     <item msgid="8189342855739930015">"ಲಭ್ಯವಿಲ್ಲ"</item>
-    <item msgid="146088982397753810">"ಆಫ್ ಮಾಡಿ"</item>
+    <item msgid="146088982397753810">"ಆಫ್"</item>
     <item msgid="460891964396502657">"ಆನ್ ಮಾಡಿ"</item>
   </string-array>
   <string-array name="tile_states_dream">
     <item msgid="6184819793571079513">"ಲಭ್ಯವಿಲ್ಲ"</item>
-    <item msgid="8014986104355098744">"ಆಫ್ ಮಾಡಿ"</item>
+    <item msgid="8014986104355098744">"ಆಫ್"</item>
     <item msgid="5966994759929723339">"ಆನ್ ಮಾಡಿ"</item>
   </string-array>
   <string-array name="tile_states_font_scaling">
diff --git a/packages/SystemUI/res/values-ko/strings.xml b/packages/SystemUI/res/values-ko/strings.xml
index 7bd6e6f..897b266 100644
--- a/packages/SystemUI/res/values-ko/strings.xml
+++ b/packages/SystemUI/res/values-ko/strings.xml
@@ -1208,8 +1208,7 @@
     <string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"설정에서 기본 메모 앱 설정"</string>
     <string name="install_app" msgid="5066668100199613936">"앱 설치"</string>
     <string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"외부 디스플레이로 미러링하시겠습니까?"</string>
-    <!-- no translation found for connected_display_dialog_dual_display_stop_warning (4174707498892447947) -->
-    <skip />
+    <string name="connected_display_dialog_dual_display_stop_warning" msgid="4174707498892447947">"내부 디스플레이가 미러링됩니다. 전면 디스플레이는 꺼집니다."</string>
     <string name="mirror_display" msgid="2515262008898122928">"디스플레이 미러링"</string>
     <string name="dismiss_dialog" msgid="2195508495854675882">"닫기"</string>
     <string name="connected_display_icon_desc" msgid="6373560639989971997">"디스플레이 연결됨"</string>
diff --git a/packages/SystemUI/res/values-ky/strings.xml b/packages/SystemUI/res/values-ky/strings.xml
index d4974e8..2f091ec 100644
--- a/packages/SystemUI/res/values-ky/strings.xml
+++ b/packages/SystemUI/res/values-ky/strings.xml
@@ -1208,8 +1208,7 @@
     <string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"Параметрлерден демейки кыска жазуулар колдонмосун тууралаңыз"</string>
     <string name="install_app" msgid="5066668100199613936">"Колдонмону орнотуу"</string>
     <string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"Тышкы экранга чыгарасызбы?"</string>
-    <!-- no translation found for connected_display_dialog_dual_display_stop_warning (4174707498892447947) -->
-    <skip />
+    <string name="connected_display_dialog_dual_display_stop_warning" msgid="4174707498892447947">"Ички экраныңыз башка экранга чыгарылат. Алдыңкы экраныңыз өчүрүлөт."</string>
     <string name="mirror_display" msgid="2515262008898122928">"Тышкы экран"</string>
     <string name="dismiss_dialog" msgid="2195508495854675882">"Жабуу"</string>
     <string name="connected_display_icon_desc" msgid="6373560639989971997">"Экран туташтырылды"</string>
diff --git a/packages/SystemUI/res/values-lo/strings.xml b/packages/SystemUI/res/values-lo/strings.xml
index 431b77b..c0c5d44 100644
--- a/packages/SystemUI/res/values-lo/strings.xml
+++ b/packages/SystemUI/res/values-lo/strings.xml
@@ -1208,8 +1208,7 @@
     <string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"ຕັ້ງຄ່າແອັບຈົດບັນທຶກເລີ່ມຕົ້ນໃນການຕັ້ງຄ່າ"</string>
     <string name="install_app" msgid="5066668100199613936">"ຕິດຕັ້ງແອັບ"</string>
     <string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"ສາຍໃສ່ຈໍສະແດງຜົນພາຍນອກບໍ?"</string>
-    <!-- no translation found for connected_display_dialog_dual_display_stop_warning (4174707498892447947) -->
-    <skip />
+    <string name="connected_display_dialog_dual_display_stop_warning" msgid="4174707498892447947">"ການສະແດງຜົນທາງໃນຂອງທ່ານຈະຖືກສະທ້ອນ. ການສະແດງຜົນທາງໜ້າຂອງທ່ານຈະຖືກປິດໄວ້."</string>
     <string name="mirror_display" msgid="2515262008898122928">"ຈໍສະແດງຜົນແບບສະທ້ອນ"</string>
     <string name="dismiss_dialog" msgid="2195508495854675882">"ປິດໄວ້"</string>
     <string name="connected_display_icon_desc" msgid="6373560639989971997">"ເຊື່ອມຕໍ່ຈໍແລ້ວ"</string>
diff --git a/packages/SystemUI/res/values-lt/strings.xml b/packages/SystemUI/res/values-lt/strings.xml
index a0eaea2..83d2724 100644
--- a/packages/SystemUI/res/values-lt/strings.xml
+++ b/packages/SystemUI/res/values-lt/strings.xml
@@ -1208,8 +1208,7 @@
     <string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"Nustatykite numatytąją užrašų programą Nustatymuose"</string>
     <string name="install_app" msgid="5066668100199613936">"Įdiegti programą"</string>
     <string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"Bendrinti ekrano vaizdą išoriniame ekrane?"</string>
-    <!-- no translation found for connected_display_dialog_dual_display_stop_warning (4174707498892447947) -->
-    <skip />
+    <string name="connected_display_dialog_dual_display_stop_warning" msgid="4174707498892447947">"Bus bendrinamas vidinio rodinio ekrano vaizdas. Priekinis rodinys bus išjungtas."</string>
     <string name="mirror_display" msgid="2515262008898122928">"Bendrinti ekrano vaizdą"</string>
     <string name="dismiss_dialog" msgid="2195508495854675882">"Atsisakyti"</string>
     <string name="connected_display_icon_desc" msgid="6373560639989971997">"Ekranas prijungtas"</string>
diff --git a/packages/SystemUI/res/values-lv/strings.xml b/packages/SystemUI/res/values-lv/strings.xml
index a419f87..4bc71c3 100644
--- a/packages/SystemUI/res/values-lv/strings.xml
+++ b/packages/SystemUI/res/values-lv/strings.xml
@@ -1208,8 +1208,7 @@
     <string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"Iestatījumos iestatiet noklusējuma piezīmju lietotni."</string>
     <string name="install_app" msgid="5066668100199613936">"Instalēt lietotni"</string>
     <string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"Vai spoguļot ārējā displejā?"</string>
-    <!-- no translation found for connected_display_dialog_dual_display_stop_warning (4174707498892447947) -->
-    <skip />
+    <string name="connected_display_dialog_dual_display_stop_warning" msgid="4174707498892447947">"Jūsu iekšējais displejs tiks spoguļots. Jūsu priekšējais displejs tiks izslēgts."</string>
     <string name="mirror_display" msgid="2515262008898122928">"Spoguļot displeju"</string>
     <string name="dismiss_dialog" msgid="2195508495854675882">"Nerādīt"</string>
     <string name="connected_display_icon_desc" msgid="6373560639989971997">"Pievienots displejs"</string>
diff --git a/packages/SystemUI/res/values-mk/strings.xml b/packages/SystemUI/res/values-mk/strings.xml
index ca9fe9e..28a2a02 100644
--- a/packages/SystemUI/res/values-mk/strings.xml
+++ b/packages/SystemUI/res/values-mk/strings.xml
@@ -1208,8 +1208,7 @@
     <string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"Поставете стандардна апликација за белешки во „Поставки“"</string>
     <string name="install_app" msgid="5066668100199613936">"Инсталирајте ја апликацијата"</string>
     <string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"Да се преслика на надворешниот екран?"</string>
-    <!-- no translation found for connected_display_dialog_dual_display_stop_warning (4174707498892447947) -->
-    <skip />
+    <string name="connected_display_dialog_dual_display_stop_warning" msgid="4174707498892447947">"Вашиот внатрешен екран ќе се отслика. Вашиот преден екран ќе се исклучи."</string>
     <string name="mirror_display" msgid="2515262008898122928">"Пресликај екран"</string>
     <string name="dismiss_dialog" msgid="2195508495854675882">"Отфрли"</string>
     <string name="connected_display_icon_desc" msgid="6373560639989971997">"Екранот е поврзан"</string>
diff --git a/packages/SystemUI/res/values-ml/strings.xml b/packages/SystemUI/res/values-ml/strings.xml
index 5ff4656..057f0b0 100644
--- a/packages/SystemUI/res/values-ml/strings.xml
+++ b/packages/SystemUI/res/values-ml/strings.xml
@@ -1208,8 +1208,7 @@
     <string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"ക്രമീകരണത്തിൽ കുറിപ്പുകൾക്കുള്ള ഡിഫോൾട്ട് ആപ്പ് സജ്ജീകരിക്കുക"</string>
     <string name="install_app" msgid="5066668100199613936">"ആപ്പ് ഇൻസ്റ്റാൾ ചെയ്യൂ"</string>
     <string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"ബാഹ്യ ഡിസ്‌പ്ലേയിലേക്ക് മിറർ ചെയ്യണോ?"</string>
-    <!-- no translation found for connected_display_dialog_dual_display_stop_warning (4174707498892447947) -->
-    <skip />
+    <string name="connected_display_dialog_dual_display_stop_warning" msgid="4174707498892447947">"നിങ്ങളുടെ ഇന്നർ ഡിസ്പ്ലേ മിറർ ചെയ്യും. നിങ്ങളുടെ ഫ്രണ്ട് ഡിസ്പ്ലേ ഓഫാകും."</string>
     <string name="mirror_display" msgid="2515262008898122928">"മിറർ ഡിസ്‌പ്ലേ"</string>
     <string name="dismiss_dialog" msgid="2195508495854675882">"ഡിസ്‌മിസ് ചെയ്യുക"</string>
     <string name="connected_display_icon_desc" msgid="6373560639989971997">"ഡിസ്പ്ലേ കണക്റ്റ് ചെയ്തിരിക്കുന്നു"</string>
diff --git a/packages/SystemUI/res/values-mn/strings.xml b/packages/SystemUI/res/values-mn/strings.xml
index a71aca2..933652b 100644
--- a/packages/SystemUI/res/values-mn/strings.xml
+++ b/packages/SystemUI/res/values-mn/strings.xml
@@ -1208,8 +1208,7 @@
     <string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"Тохиргоонд тэмдэглэлийн өгөгдмөл апп тохируулна уу"</string>
     <string name="install_app" msgid="5066668100199613936">"Аппыг суулгах"</string>
     <string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"Гадны дэлгэцэд тусгал үүсгэх үү?"</string>
-    <!-- no translation found for connected_display_dialog_dual_display_stop_warning (4174707498892447947) -->
-    <skip />
+    <string name="connected_display_dialog_dual_display_stop_warning" msgid="4174707498892447947">"Таны дотоод дэлгэцийн тусгалыг үүсгэнэ. Таны урд талын дэлгэцийг унтраана."</string>
     <string name="mirror_display" msgid="2515262008898122928">"Дэлгэцийн тусгал үүсгэх"</string>
     <string name="dismiss_dialog" msgid="2195508495854675882">"Хаах"</string>
     <string name="connected_display_icon_desc" msgid="6373560639989971997">"Дэлгэц холбогдсон"</string>
diff --git a/packages/SystemUI/res/values-mr/strings.xml b/packages/SystemUI/res/values-mr/strings.xml
index 9dfaa00..aad269b 100644
--- a/packages/SystemUI/res/values-mr/strings.xml
+++ b/packages/SystemUI/res/values-mr/strings.xml
@@ -1208,8 +1208,7 @@
     <string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"सेटिंग्ज मध्ये डीफॉल्ट टिपा अ‍ॅप सेट करा"</string>
     <string name="install_app" msgid="5066668100199613936">"अ‍ॅप इंस्टॉल करा"</string>
     <string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"बाह्य डिस्प्लेवर मिरर करायचे आहे का?"</string>
-    <!-- no translation found for connected_display_dialog_dual_display_stop_warning (4174707498892447947) -->
-    <skip />
+    <string name="connected_display_dialog_dual_display_stop_warning" msgid="4174707498892447947">"तुमचा अंतर्गत डिस्प्ले मिरर केला जाईल. तुमचा पुढील डिस्प्ले बंद केला जाईल."</string>
     <string name="mirror_display" msgid="2515262008898122928">"डिस्प्ले मिरर करा"</string>
     <string name="dismiss_dialog" msgid="2195508495854675882">"डिसमिस करा"</string>
     <string name="connected_display_icon_desc" msgid="6373560639989971997">"डिस्प्ले कनेक्ट केला आहे"</string>
diff --git a/packages/SystemUI/res/values-ms/strings.xml b/packages/SystemUI/res/values-ms/strings.xml
index 553d221..8287d2b 100644
--- a/packages/SystemUI/res/values-ms/strings.xml
+++ b/packages/SystemUI/res/values-ms/strings.xml
@@ -1208,8 +1208,7 @@
     <string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"Tetapkan apl nota lalai dalam Tetapan"</string>
     <string name="install_app" msgid="5066668100199613936">"Pasang apl"</string>
     <string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"Paparkan pada paparan luaran?"</string>
-    <!-- no translation found for connected_display_dialog_dual_display_stop_warning (4174707498892447947) -->
-    <skip />
+    <string name="connected_display_dialog_dual_display_stop_warning" msgid="4174707498892447947">"Paparan dalaman anda akan dicerminkan. Paparan depan anda akan dimatikan."</string>
     <string name="mirror_display" msgid="2515262008898122928">"Segerakkan paparan"</string>
     <string name="dismiss_dialog" msgid="2195508495854675882">"Ketepikan"</string>
     <string name="connected_display_icon_desc" msgid="6373560639989971997">"Paparan disambungkan"</string>
diff --git a/packages/SystemUI/res/values-my/strings.xml b/packages/SystemUI/res/values-my/strings.xml
index d3f1bad..25afad4 100644
--- a/packages/SystemUI/res/values-my/strings.xml
+++ b/packages/SystemUI/res/values-my/strings.xml
@@ -1208,8 +1208,7 @@
     <string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"ဆက်တင်များတွင် မူရင်းမှတ်စုများအက်ပ် သတ်မှတ်ပါ"</string>
     <string name="install_app" msgid="5066668100199613936">"အက်ပ် ထည့်သွင်းရန်"</string>
     <string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"ပြင်ပဖန်သားပြင်သို့ စကရင်ပွားမလား။"</string>
-    <!-- no translation found for connected_display_dialog_dual_display_stop_warning (4174707498892447947) -->
-    <skip />
+    <string name="connected_display_dialog_dual_display_stop_warning" msgid="4174707498892447947">"အတွင်းပြကွက်ကို စကရင်ပွားပါမည်။ ရှေ့မျက်နှာပြင်ပြကွက်ကို ပိတ်မည်။"</string>
     <string name="mirror_display" msgid="2515262008898122928">"ဖန်သားပြင်ကို စကရင်ပွားရန်"</string>
     <string name="dismiss_dialog" msgid="2195508495854675882">"ပယ်ရန်"</string>
     <string name="connected_display_icon_desc" msgid="6373560639989971997">"ဖန်သားပြင်ကို ချိတ်ဆက်လိုက်ပါပြီ"</string>
diff --git a/packages/SystemUI/res/values-nb/strings.xml b/packages/SystemUI/res/values-nb/strings.xml
index 234e725..d915743 100644
--- a/packages/SystemUI/res/values-nb/strings.xml
+++ b/packages/SystemUI/res/values-nb/strings.xml
@@ -1208,8 +1208,7 @@
     <string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"Du kan velge en standardapp for notater i Innstillinger"</string>
     <string name="install_app" msgid="5066668100199613936">"Installer appen"</string>
     <string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"Vil du speile til en ekstern skjerm?"</string>
-    <!-- no translation found for connected_display_dialog_dual_display_stop_warning (4174707498892447947) -->
-    <skip />
+    <string name="connected_display_dialog_dual_display_stop_warning" msgid="4174707498892447947">"Den indre skjermen speiles. Den ytre skjermen slås av."</string>
     <string name="mirror_display" msgid="2515262008898122928">"Speil skjermen"</string>
     <string name="dismiss_dialog" msgid="2195508495854675882">"Lukk"</string>
     <string name="connected_display_icon_desc" msgid="6373560639989971997">"En skjerm er koblet til"</string>
diff --git a/packages/SystemUI/res/values-ne/strings.xml b/packages/SystemUI/res/values-ne/strings.xml
index 51839db..4abb4a4 100644
--- a/packages/SystemUI/res/values-ne/strings.xml
+++ b/packages/SystemUI/res/values-ne/strings.xml
@@ -1208,8 +1208,7 @@
     <string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"सेटिङमा गई नोट बनाउने डिफल्ट एप तोक्नुहोस्"</string>
     <string name="install_app" msgid="5066668100199613936">"एप इन्स्टल गर्नुहोस्"</string>
     <string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"बाह्य डिस्प्लेमा मिरर गर्ने हो?"</string>
-    <!-- no translation found for connected_display_dialog_dual_display_stop_warning (4174707498892447947) -->
-    <skip />
+    <string name="connected_display_dialog_dual_display_stop_warning" msgid="4174707498892447947">"तपाईंको भित्री डिस्प्ले मिरर गरिने छ। तपाईंको अगाडिको डिस्प्ले अफ गरिने छ।"</string>
     <string name="mirror_display" msgid="2515262008898122928">"डिस्प्ले मिरर गर्नुहोस्"</string>
     <string name="dismiss_dialog" msgid="2195508495854675882">"खारेज गर्नुहोस्"</string>
     <string name="connected_display_icon_desc" msgid="6373560639989971997">"डिस्प्ले कनेक्ट गरिएको छ"</string>
diff --git a/packages/SystemUI/res/values-nl/strings.xml b/packages/SystemUI/res/values-nl/strings.xml
index f66b4e1..0576730 100644
--- a/packages/SystemUI/res/values-nl/strings.xml
+++ b/packages/SystemUI/res/values-nl/strings.xml
@@ -1208,8 +1208,7 @@
     <string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"Standaard notitie-app instellen in Instellingen"</string>
     <string name="install_app" msgid="5066668100199613936">"App installeren"</string>
     <string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"Spiegelen naar extern scherm?"</string>
-    <!-- no translation found for connected_display_dialog_dual_display_stop_warning (4174707498892447947) -->
-    <skip />
+    <string name="connected_display_dialog_dual_display_stop_warning" msgid="4174707498892447947">"Het scherm aan de binnenkant wordt gemirrord. Het scherm aan de voorkant wordt uitgezet."</string>
     <string name="mirror_display" msgid="2515262008898122928">"Scherm spiegelen"</string>
     <string name="dismiss_dialog" msgid="2195508495854675882">"Sluiten"</string>
     <string name="connected_display_icon_desc" msgid="6373560639989971997">"Scherm verbonden"</string>
diff --git a/packages/SystemUI/res/values-or/strings.xml b/packages/SystemUI/res/values-or/strings.xml
index 0a45769..cb58e64 100644
--- a/packages/SystemUI/res/values-or/strings.xml
+++ b/packages/SystemUI/res/values-or/strings.xml
@@ -1208,8 +1208,7 @@
     <string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"ସେଟିଂସରେ ଡିଫଲ୍ଟ ନୋଟ୍ସ ଆପ ସେଟ କରନ୍ତୁ"</string>
     <string name="install_app" msgid="5066668100199613936">"ଆପ ଇନଷ୍ଟଲ କରନ୍ତୁ"</string>
     <string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"ଏକ୍ସଟର୍ନଲ ଡିସପ୍ଲେକୁ ମିରର କରିବେ?"</string>
-    <!-- no translation found for connected_display_dialog_dual_display_stop_warning (4174707498892447947) -->
-    <skip />
+    <string name="connected_display_dialog_dual_display_stop_warning" msgid="4174707498892447947">"ଆପଣଙ୍କ ଇନର ଡିସପ୍ଲେକୁ ମିରର କରାଯିବ। ଆପଣଙ୍କ ଫ୍ରଣ୍ଟ ଡିସପ୍ଲେକୁ ବନ୍ଦ କରାଯିବ।"</string>
     <string name="mirror_display" msgid="2515262008898122928">"ଡିସପ୍ଲେ ମିରର କରନ୍ତୁ"</string>
     <string name="dismiss_dialog" msgid="2195508495854675882">"ଖାରଜ କରନ୍ତୁ"</string>
     <string name="connected_display_icon_desc" msgid="6373560639989971997">"ଡିସପ୍ଲେ କନେକ୍ଟ କରାଯାଇଛି"</string>
diff --git a/packages/SystemUI/res/values-pa/strings.xml b/packages/SystemUI/res/values-pa/strings.xml
index c83659f..0340bd1 100644
--- a/packages/SystemUI/res/values-pa/strings.xml
+++ b/packages/SystemUI/res/values-pa/strings.xml
@@ -1208,8 +1208,7 @@
     <string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"ਸੈਟਿੰਗਾਂ ਵਿੱਚ ਜਾ ਕੇ ਪੂਰਵ-ਨਿਰਧਾਰਿਤ ਨੋਟ ਐਪ ਨੂੰ ਸੈੱਟ ਕਰੋ"</string>
     <string name="install_app" msgid="5066668100199613936">"ਐਪ ਸਥਾਪਤ ਕਰੋ"</string>
     <string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"ਕੀ ਬਾਹਰੀ ਡਿਸਪਲੇ \'ਤੇ ਪ੍ਰਤਿਬਿੰਬਿਤ ਕਰਨਾ ਹੈ?"</string>
-    <!-- no translation found for connected_display_dialog_dual_display_stop_warning (4174707498892447947) -->
-    <skip />
+    <string name="connected_display_dialog_dual_display_stop_warning" msgid="4174707498892447947">"ਤੁਹਾਡੀ ਅੰਦਰੂਨੀ ਡਿਸਪਲੇ ਪ੍ਰਤੀਬਿੰਬਤ ਕੀਤੀ ਜਾਵੇਗੀ। ਤੁਹਾਡੀ ਅਗਲੀ ਡਿਸਪਲੇ ਬੰਦ ਕਰ ਦਿੱਤੀ ਜਾਵੇਗੀ।"</string>
     <string name="mirror_display" msgid="2515262008898122928">"ਡਿਸਪਲੇ ਨੂੰ ਪ੍ਰਤਿਬਿੰਬਿਤ ਕਰੋ"</string>
     <string name="dismiss_dialog" msgid="2195508495854675882">"ਖਾਰਜ ਕਰੋ"</string>
     <string name="connected_display_icon_desc" msgid="6373560639989971997">"ਡਿਸਪਲੇ ਨੂੰ ਕਨੈਕਟ ਕੀਤਾ ਗਿਆ"</string>
diff --git a/packages/SystemUI/res/values-pl/strings.xml b/packages/SystemUI/res/values-pl/strings.xml
index 91f2c78..aa24818 100644
--- a/packages/SystemUI/res/values-pl/strings.xml
+++ b/packages/SystemUI/res/values-pl/strings.xml
@@ -1208,8 +1208,7 @@
     <string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"Ustaw domyślną aplikację do obsługi notatek w Ustawieniach"</string>
     <string name="install_app" msgid="5066668100199613936">"Zainstaluj aplikację"</string>
     <string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"Powielić na wyświetlaczu zewnętrznym?"</string>
-    <!-- no translation found for connected_display_dialog_dual_display_stop_warning (4174707498892447947) -->
-    <skip />
+    <string name="connected_display_dialog_dual_display_stop_warning" msgid="4174707498892447947">"Powstanie odbicie lustrzane Twojego wewnętrznego ekranu. Przedni ekran zostanie wyłączony."</string>
     <string name="mirror_display" msgid="2515262008898122928">"Powielaj obraz"</string>
     <string name="dismiss_dialog" msgid="2195508495854675882">"Zamknij"</string>
     <string name="connected_display_icon_desc" msgid="6373560639989971997">"Wyświetlacz podłączony"</string>
diff --git a/packages/SystemUI/res/values-pt-rBR/strings.xml b/packages/SystemUI/res/values-pt-rBR/strings.xml
index 9fe372b..c65c56e 100644
--- a/packages/SystemUI/res/values-pt-rBR/strings.xml
+++ b/packages/SystemUI/res/values-pt-rBR/strings.xml
@@ -1208,8 +1208,7 @@
     <string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"Defina o app de notas padrão nas Configurações"</string>
     <string name="install_app" msgid="5066668100199613936">"Instalar o app"</string>
     <string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"Espelhar para a tela externa?"</string>
-    <!-- no translation found for connected_display_dialog_dual_display_stop_warning (4174707498892447947) -->
-    <skip />
+    <string name="connected_display_dialog_dual_display_stop_warning" msgid="4174707498892447947">"Seu display interno será espelhado. O display frontal será desligado."</string>
     <string name="mirror_display" msgid="2515262008898122928">"Espelhar tela"</string>
     <string name="dismiss_dialog" msgid="2195508495854675882">"Dispensar"</string>
     <string name="connected_display_icon_desc" msgid="6373560639989971997">"Tela conectada"</string>
diff --git a/packages/SystemUI/res/values-pt-rPT/strings.xml b/packages/SystemUI/res/values-pt-rPT/strings.xml
index 3bf9c7b..0b9580d 100644
--- a/packages/SystemUI/res/values-pt-rPT/strings.xml
+++ b/packages/SystemUI/res/values-pt-rPT/strings.xml
@@ -1208,8 +1208,7 @@
     <string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"Predefina a app de notas nas Definições"</string>
     <string name="install_app" msgid="5066668100199613936">"Instalar app"</string>
     <string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"Espelhar para o ecrã externo?"</string>
-    <!-- no translation found for connected_display_dialog_dual_display_stop_warning (4174707498892447947) -->
-    <skip />
+    <string name="connected_display_dialog_dual_display_stop_warning" msgid="4174707498892447947">"O seu ecrã interior vai ser espelhado. O seu ecrã frontal vai ser desativado."</string>
     <string name="mirror_display" msgid="2515262008898122928">"Espelhar ecrã"</string>
     <string name="dismiss_dialog" msgid="2195508495854675882">"Ignorar"</string>
     <string name="connected_display_icon_desc" msgid="6373560639989971997">"Ecrã ligado"</string>
diff --git a/packages/SystemUI/res/values-pt/strings.xml b/packages/SystemUI/res/values-pt/strings.xml
index 9fe372b..c65c56e 100644
--- a/packages/SystemUI/res/values-pt/strings.xml
+++ b/packages/SystemUI/res/values-pt/strings.xml
@@ -1208,8 +1208,7 @@
     <string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"Defina o app de notas padrão nas Configurações"</string>
     <string name="install_app" msgid="5066668100199613936">"Instalar o app"</string>
     <string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"Espelhar para a tela externa?"</string>
-    <!-- no translation found for connected_display_dialog_dual_display_stop_warning (4174707498892447947) -->
-    <skip />
+    <string name="connected_display_dialog_dual_display_stop_warning" msgid="4174707498892447947">"Seu display interno será espelhado. O display frontal será desligado."</string>
     <string name="mirror_display" msgid="2515262008898122928">"Espelhar tela"</string>
     <string name="dismiss_dialog" msgid="2195508495854675882">"Dispensar"</string>
     <string name="connected_display_icon_desc" msgid="6373560639989971997">"Tela conectada"</string>
diff --git a/packages/SystemUI/res/values-ro/strings.xml b/packages/SystemUI/res/values-ro/strings.xml
index 18aa668..b2fdef3 100644
--- a/packages/SystemUI/res/values-ro/strings.xml
+++ b/packages/SystemUI/res/values-ro/strings.xml
@@ -1208,8 +1208,7 @@
     <string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"Setează aplicația prestabilită de note din Setări"</string>
     <string name="install_app" msgid="5066668100199613936">"Instalează aplicația"</string>
     <string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"Oglindești pe ecranul extern?"</string>
-    <!-- no translation found for connected_display_dialog_dual_display_stop_warning (4174707498892447947) -->
-    <skip />
+    <string name="connected_display_dialog_dual_display_stop_warning" msgid="4174707498892447947">"Ecranul interior va fi oglindit. Ecranul frontal va fi dezactivat."</string>
     <string name="mirror_display" msgid="2515262008898122928">"Afișare în oglindă"</string>
     <string name="dismiss_dialog" msgid="2195508495854675882">"Închide"</string>
     <string name="connected_display_icon_desc" msgid="6373560639989971997">"Ecran conectat"</string>
diff --git a/packages/SystemUI/res/values-ru/strings.xml b/packages/SystemUI/res/values-ru/strings.xml
index 51192f8..ff4bd45 100644
--- a/packages/SystemUI/res/values-ru/strings.xml
+++ b/packages/SystemUI/res/values-ru/strings.xml
@@ -1208,8 +1208,7 @@
     <string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"Задайте стандартное приложение для заметок в настройках."</string>
     <string name="install_app" msgid="5066668100199613936">"Установить приложение"</string>
     <string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"Дублировать на внешний дисплей?"</string>
-    <!-- no translation found for connected_display_dialog_dual_display_stop_warning (4174707498892447947) -->
-    <skip />
+    <string name="connected_display_dialog_dual_display_stop_warning" msgid="4174707498892447947">"Для внутреннего экрана включится дублирование. Передний экран будет отключен."</string>
     <string name="mirror_display" msgid="2515262008898122928">"Дублировать"</string>
     <string name="dismiss_dialog" msgid="2195508495854675882">"Закрыть"</string>
     <string name="connected_display_icon_desc" msgid="6373560639989971997">"Экран подключен"</string>
diff --git a/packages/SystemUI/res/values-si/strings.xml b/packages/SystemUI/res/values-si/strings.xml
index 7ade110..4b4d08b 100644
--- a/packages/SystemUI/res/values-si/strings.xml
+++ b/packages/SystemUI/res/values-si/strings.xml
@@ -1208,8 +1208,7 @@
     <string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"සැකසීම් තුළ පෙරනිමි සටහන් යෙදුම සකසන්න"</string>
     <string name="install_app" msgid="5066668100199613936">"යෙදුම ස්ථාපනය කරන්න"</string>
     <string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"බාහිර සංදර්ශකයට දර්පණය කරන්න ද?"</string>
-    <!-- no translation found for connected_display_dialog_dual_display_stop_warning (4174707498892447947) -->
-    <skip />
+    <string name="connected_display_dialog_dual_display_stop_warning" msgid="4174707498892447947">"ඔබේ අභ්‍යන්තර සංදර්ශකය පිළිබිඹු වනු ඇත. ඔබේ ඉදිරිපස සංදර්ශකය ක්‍රියාවිරහිත වනු ඇත."</string>
     <string name="mirror_display" msgid="2515262008898122928">"සංදර්ශකය දර්පණය කරන්න"</string>
     <string name="dismiss_dialog" msgid="2195508495854675882">"අස් කරන්න"</string>
     <string name="connected_display_icon_desc" msgid="6373560639989971997">"සංදර්ශකය සම්බන්ධ කර ඇත"</string>
diff --git a/packages/SystemUI/res/values-sk/strings.xml b/packages/SystemUI/res/values-sk/strings.xml
index 8d760d5..9e9507e 100644
--- a/packages/SystemUI/res/values-sk/strings.xml
+++ b/packages/SystemUI/res/values-sk/strings.xml
@@ -1208,8 +1208,7 @@
     <string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"Nastavte predvolenú aplikáciu na poznámky v Nastaveniach"</string>
     <string name="install_app" msgid="5066668100199613936">"Inštalovať aplikáciu"</string>
     <string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"Chcete zrkadliť na externú obrazovku?"</string>
-    <!-- no translation found for connected_display_dialog_dual_display_stop_warning (4174707498892447947) -->
-    <skip />
+    <string name="connected_display_dialog_dual_display_stop_warning" msgid="4174707498892447947">"Vnútorná obrazovka bude zrkadlená. Predná obrazovka bude vypnutá."</string>
     <string name="mirror_display" msgid="2515262008898122928">"Zrkadliť obrazovku"</string>
     <string name="dismiss_dialog" msgid="2195508495854675882">"Zavrieť"</string>
     <string name="connected_display_icon_desc" msgid="6373560639989971997">"Obrazovka je pripojená"</string>
diff --git a/packages/SystemUI/res/values-sl/strings.xml b/packages/SystemUI/res/values-sl/strings.xml
index 4ae80ef..cba5416 100644
--- a/packages/SystemUI/res/values-sl/strings.xml
+++ b/packages/SystemUI/res/values-sl/strings.xml
@@ -1208,8 +1208,7 @@
     <string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"Nastavite privzeto aplikacijo za zapiske v nastavitvah."</string>
     <string name="install_app" msgid="5066668100199613936">"Namesti aplikacijo"</string>
     <string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"Želite zrcaliti na zunanji zaslon?"</string>
-    <!-- no translation found for connected_display_dialog_dual_display_stop_warning (4174707498892447947) -->
-    <skip />
+    <string name="connected_display_dialog_dual_display_stop_warning" msgid="4174707498892447947">"Notranji zaslon bo zrcaljen. Sprednji zaslon bo izklopljen."</string>
     <string name="mirror_display" msgid="2515262008898122928">"Zrcali zaslon"</string>
     <string name="dismiss_dialog" msgid="2195508495854675882">"Opusti"</string>
     <string name="connected_display_icon_desc" msgid="6373560639989971997">"Zaslon je povezan"</string>
diff --git a/packages/SystemUI/res/values-sq/strings.xml b/packages/SystemUI/res/values-sq/strings.xml
index 8372703..b35668f 100644
--- a/packages/SystemUI/res/values-sq/strings.xml
+++ b/packages/SystemUI/res/values-sq/strings.xml
@@ -1208,8 +1208,7 @@
     <string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"Cakto aplikacionin e parazgjedhur të shënimeve te \"Cilësimet\""</string>
     <string name="install_app" msgid="5066668100199613936">"Instalo aplikacionin"</string>
     <string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"Të pasqyrohet në ekranin e jashtëm?"</string>
-    <!-- no translation found for connected_display_dialog_dual_display_stop_warning (4174707498892447947) -->
-    <skip />
+    <string name="connected_display_dialog_dual_display_stop_warning" msgid="4174707498892447947">"Ekrani i brendshëm do të pasqyrohet. Ekrani i parmë do të çaktivizohet."</string>
     <string name="mirror_display" msgid="2515262008898122928">"Pasqyro ekranin"</string>
     <string name="dismiss_dialog" msgid="2195508495854675882">"Hiq"</string>
     <string name="connected_display_icon_desc" msgid="6373560639989971997">"Ekrani është lidhur"</string>
diff --git a/packages/SystemUI/res/values-sr/strings.xml b/packages/SystemUI/res/values-sr/strings.xml
index 1bda1db..1d45fbd 100644
--- a/packages/SystemUI/res/values-sr/strings.xml
+++ b/packages/SystemUI/res/values-sr/strings.xml
@@ -1208,8 +1208,7 @@
     <string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"Подесите подразумевану апликацију за белешке у Подешавањима"</string>
     <string name="install_app" msgid="5066668100199613936">"Инсталирај апликацију"</string>
     <string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"Желите ли да пресликате на спољњи екран?"</string>
-    <!-- no translation found for connected_display_dialog_dual_display_stop_warning (4174707498892447947) -->
-    <skip />
+    <string name="connected_display_dialog_dual_display_stop_warning" msgid="4174707498892447947">"Унутрашњи екран ће се пресликати. Предњи екран ће се искључити."</string>
     <string name="mirror_display" msgid="2515262008898122928">"Пресликај екран"</string>
     <string name="dismiss_dialog" msgid="2195508495854675882">"Одбаци"</string>
     <string name="connected_display_icon_desc" msgid="6373560639989971997">"Екран је повезан"</string>
diff --git a/packages/SystemUI/res/values-sv/strings.xml b/packages/SystemUI/res/values-sv/strings.xml
index 8cfafcf..0d6272f 100644
--- a/packages/SystemUI/res/values-sv/strings.xml
+++ b/packages/SystemUI/res/values-sv/strings.xml
@@ -1208,8 +1208,7 @@
     <string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"Ställ in en standardapp för anteckningar i inställningarna"</string>
     <string name="install_app" msgid="5066668100199613936">"Installera appen"</string>
     <string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"Vill du spegla till extern skärm?"</string>
-    <!-- no translation found for connected_display_dialog_dual_display_stop_warning (4174707498892447947) -->
-    <skip />
+    <string name="connected_display_dialog_dual_display_stop_warning" msgid="4174707498892447947">"Den inre skärmen speglas. Den främre skärmen stängs av."</string>
     <string name="mirror_display" msgid="2515262008898122928">"Spegla skärm"</string>
     <string name="dismiss_dialog" msgid="2195508495854675882">"Ignorera"</string>
     <string name="connected_display_icon_desc" msgid="6373560639989971997">"Skärm har anslutits"</string>
diff --git a/packages/SystemUI/res/values-sw/strings.xml b/packages/SystemUI/res/values-sw/strings.xml
index 286e65a..1bd2ed7 100644
--- a/packages/SystemUI/res/values-sw/strings.xml
+++ b/packages/SystemUI/res/values-sw/strings.xml
@@ -252,7 +252,7 @@
     <string name="quick_settings_bluetooth_label" msgid="7018763367142041481">"Bluetooth"</string>
     <string name="quick_settings_bluetooth_detail_empty_text" msgid="5760239584390514322">"Hakuna vifaa vilivyooanishwa vinavyopatikana"</string>
     <string name="quick_settings_bluetooth_tile_subtitle" msgid="212752719010829550">"Gusa ili uunganishe au utenganishe kifaa"</string>
-    <string name="pair_new_bluetooth_devices" msgid="4601767620843349645">"Oanisha kifaa kipya"</string>
+    <string name="pair_new_bluetooth_devices" msgid="4601767620843349645">"Unganisha kifaa kipya"</string>
     <string name="see_all_bluetooth_devices" msgid="1761596816620200433">"Angalia vyote"</string>
     <string name="turn_on_bluetooth" msgid="5681370462180289071">"Tumia Bluetooth"</string>
     <string name="quick_settings_bluetooth_device_connected" msgid="7884777006729260996">"Imeunganishwa"</string>
@@ -1208,8 +1208,7 @@
     <string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"Teua programu chaguomsingi ya madokezo katika Mipangilio"</string>
     <string name="install_app" msgid="5066668100199613936">"Sakinisha programu"</string>
     <string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"Ungependa kuonyesha kwenye skrini ya nje?"</string>
-    <!-- no translation found for connected_display_dialog_dual_display_stop_warning (4174707498892447947) -->
-    <skip />
+    <string name="connected_display_dialog_dual_display_stop_warning" msgid="4174707498892447947">"Mwonekano wa ndani wa kifaa chako utaakisiwa. Mwonekano wa mbele wa kifaa chako utazimwa."</string>
     <string name="mirror_display" msgid="2515262008898122928">"Akisi skrini"</string>
     <string name="dismiss_dialog" msgid="2195508495854675882">"Ondoa"</string>
     <string name="connected_display_icon_desc" msgid="6373560639989971997">"Skrini imeunganishwa"</string>
diff --git a/packages/SystemUI/res/values-ta/strings.xml b/packages/SystemUI/res/values-ta/strings.xml
index aab713f..0cd076f 100644
--- a/packages/SystemUI/res/values-ta/strings.xml
+++ b/packages/SystemUI/res/values-ta/strings.xml
@@ -1208,8 +1208,7 @@
     <string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"குறிப்பு எடுப்பதற்கான இயல்புநிலை ஆப்ஸை அமைப்புகளில் அமையுங்கள்"</string>
     <string name="install_app" msgid="5066668100199613936">"ஆப்ஸை நிறுவுங்கள்"</string>
     <string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"வெளிப்புறக் காட்சிக்கு மிரர் செய்யவா?"</string>
-    <!-- no translation found for connected_display_dialog_dual_display_stop_warning (4174707498892447947) -->
-    <skip />
+    <string name="connected_display_dialog_dual_display_stop_warning" msgid="4174707498892447947">"உங்கள் உட்புற டிஸ்பிளே பிரதிபலிக்கப்படும். உங்கள் முன்புற டிஸ்பிளே முடக்கப்படும்."</string>
     <string name="mirror_display" msgid="2515262008898122928">"டிஸ்பிளேயை மிரர் செய்"</string>
     <string name="dismiss_dialog" msgid="2195508495854675882">"வேண்டாம்"</string>
     <string name="connected_display_icon_desc" msgid="6373560639989971997">"டிஸ்ப்ளே இணைக்கப்பட்டது"</string>
diff --git a/packages/SystemUI/res/values-te/strings.xml b/packages/SystemUI/res/values-te/strings.xml
index 2536a96..6a59812 100644
--- a/packages/SystemUI/res/values-te/strings.xml
+++ b/packages/SystemUI/res/values-te/strings.xml
@@ -1208,8 +1208,7 @@
     <string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"సెట్టింగ్‌లలో ఆటోమేటిక్‌గా ఉండేలా ఒక నోట్స్ యాప్‌ను సెట్ చేసుకోండి"</string>
     <string name="install_app" msgid="5066668100199613936">"యాప్‌ను ఇన్‌స్టాల్ చేయండి"</string>
     <string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"ఎక్స్‌టర్నల్ డిస్‌ప్లే‌కి మిర్రర్‌ చేయాలా?"</string>
-    <!-- no translation found for connected_display_dialog_dual_display_stop_warning (4174707498892447947) -->
-    <skip />
+    <string name="connected_display_dialog_dual_display_stop_warning" msgid="4174707498892447947">"మీ లోపలి డిస్‌ప్లే మిర్రర్ చేయబడుతుంది. మీ ముందు వైపు డిస్‌ప్లే ఆఫ్ చేయబడుతుంది."</string>
     <string name="mirror_display" msgid="2515262008898122928">"మిర్రర్ డిస్‌ప్లే"</string>
     <string name="dismiss_dialog" msgid="2195508495854675882">"విస్మరించండి"</string>
     <string name="connected_display_icon_desc" msgid="6373560639989971997">"డిస్‌ప్లే కనెక్ట్ చేయబడింది"</string>
diff --git a/packages/SystemUI/res/values-th/strings.xml b/packages/SystemUI/res/values-th/strings.xml
index 3c96e30..dc4c6cf 100644
--- a/packages/SystemUI/res/values-th/strings.xml
+++ b/packages/SystemUI/res/values-th/strings.xml
@@ -1208,8 +1208,7 @@
     <string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"กำหนดแอปการจดบันทึกเริ่มต้นในการตั้งค่า"</string>
     <string name="install_app" msgid="5066668100199613936">"ติดตั้งแอป"</string>
     <string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"มิเรอร์ไปยังจอแสดงผลภายนอกไหม"</string>
-    <!-- no translation found for connected_display_dialog_dual_display_stop_warning (4174707498892447947) -->
-    <skip />
+    <string name="connected_display_dialog_dual_display_stop_warning" msgid="4174707498892447947">"ระบบจะมิเรอร์หน้าจอด้านใน และจะปิดหน้าจอด้านหน้า"</string>
     <string name="mirror_display" msgid="2515262008898122928">"มิเรอร์จอแสดงผล"</string>
     <string name="dismiss_dialog" msgid="2195508495854675882">"ปิด"</string>
     <string name="connected_display_icon_desc" msgid="6373560639989971997">"เชื่อมต่อจอแสดงผลแล้ว"</string>
diff --git a/packages/SystemUI/res/values-tl/strings.xml b/packages/SystemUI/res/values-tl/strings.xml
index 8fc5b6b..c09ac97 100644
--- a/packages/SystemUI/res/values-tl/strings.xml
+++ b/packages/SystemUI/res/values-tl/strings.xml
@@ -1208,8 +1208,7 @@
     <string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"Magtakda ng default na app sa pagtatala sa Mga Setting"</string>
     <string name="install_app" msgid="5066668100199613936">"I-install ang app"</string>
     <string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"I-mirror sa external na display?"</string>
-    <!-- no translation found for connected_display_dialog_dual_display_stop_warning (4174707498892447947) -->
-    <skip />
+    <string name="connected_display_dialog_dual_display_stop_warning" msgid="4174707498892447947">"Imi-mirror ang inner display mo. Io-off ang iyong front display."</string>
     <string name="mirror_display" msgid="2515262008898122928">"I-mirror ang display"</string>
     <string name="dismiss_dialog" msgid="2195508495854675882">"I-dismiss"</string>
     <string name="connected_display_icon_desc" msgid="6373560639989971997">"Naikonekta ang display"</string>
diff --git a/packages/SystemUI/res/values-tr/strings.xml b/packages/SystemUI/res/values-tr/strings.xml
index a4083ef..ee1909b 100644
--- a/packages/SystemUI/res/values-tr/strings.xml
+++ b/packages/SystemUI/res/values-tr/strings.xml
@@ -1208,8 +1208,7 @@
     <string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"Ayarlar\'ı kullanarak varsayılan notlar ayarlayın"</string>
     <string name="install_app" msgid="5066668100199613936">"Uygulamayı yükle"</string>
     <string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"Harici ekrana yansıtılsın mı?"</string>
-    <!-- no translation found for connected_display_dialog_dual_display_stop_warning (4174707498892447947) -->
-    <skip />
+    <string name="connected_display_dialog_dual_display_stop_warning" msgid="4174707498892447947">"İç ekranınız yansıtılacak. Ön ekranınız kapatılacak."</string>
     <string name="mirror_display" msgid="2515262008898122928">"Ekranı yansıt"</string>
     <string name="dismiss_dialog" msgid="2195508495854675882">"Kapat"</string>
     <string name="connected_display_icon_desc" msgid="6373560639989971997">"Ekran bağlandı"</string>
diff --git a/packages/SystemUI/res/values-uk/strings.xml b/packages/SystemUI/res/values-uk/strings.xml
index 5e0f2c2..04a97bc 100644
--- a/packages/SystemUI/res/values-uk/strings.xml
+++ b/packages/SystemUI/res/values-uk/strings.xml
@@ -1208,8 +1208,7 @@
     <string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"Призначте стандартний додаток для нотаток у налаштуваннях"</string>
     <string name="install_app" msgid="5066668100199613936">"Установити додаток"</string>
     <string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"Дублювати на зовнішньому екрані?"</string>
-    <!-- no translation found for connected_display_dialog_dual_display_stop_warning (4174707498892447947) -->
-    <skip />
+    <string name="connected_display_dialog_dual_display_stop_warning" msgid="4174707498892447947">"Ваш внутрішній екран буде продубльовано. Передній екран буде вимкнено."</string>
     <string name="mirror_display" msgid="2515262008898122928">"Дублювати екран"</string>
     <string name="dismiss_dialog" msgid="2195508495854675882">"Закрити"</string>
     <string name="connected_display_icon_desc" msgid="6373560639989971997">"Дисплей під’єднано"</string>
diff --git a/packages/SystemUI/res/values-ur/strings.xml b/packages/SystemUI/res/values-ur/strings.xml
index c28e064..fd984b9 100644
--- a/packages/SystemUI/res/values-ur/strings.xml
+++ b/packages/SystemUI/res/values-ur/strings.xml
@@ -1208,8 +1208,7 @@
     <string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"ترتیبات میں ڈیفالٹ نوٹس ایپ سیٹ کریں"</string>
     <string name="install_app" msgid="5066668100199613936">"ایپ انسٹال کریں"</string>
     <string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"بیرونی ڈسپلے پر مرر کریں؟"</string>
-    <!-- no translation found for connected_display_dialog_dual_display_stop_warning (4174707498892447947) -->
-    <skip />
+    <string name="connected_display_dialog_dual_display_stop_warning" msgid="4174707498892447947">"آپ کے اندرونی ڈسپلے کو دو طرفہ مطابقت پذیر بنایا جائے گا۔ آپ کا فرنٹ ڈسپلے آف ہو جائے گا۔"</string>
     <string name="mirror_display" msgid="2515262008898122928">"ڈسپلے کو مرر کریں"</string>
     <string name="dismiss_dialog" msgid="2195508495854675882">"برخاست کریں"</string>
     <string name="connected_display_icon_desc" msgid="6373560639989971997">"ڈسپلے منسلک ہے"</string>
diff --git a/packages/SystemUI/res/values-uz/strings.xml b/packages/SystemUI/res/values-uz/strings.xml
index e032c76..b9a9832 100644
--- a/packages/SystemUI/res/values-uz/strings.xml
+++ b/packages/SystemUI/res/values-uz/strings.xml
@@ -1208,8 +1208,7 @@
     <string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"Standart qaydlar ilovasini Sozlamalar orqali tanlang"</string>
     <string name="install_app" msgid="5066668100199613936">"Ilovani oʻrnatish"</string>
     <string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"Tashqi displeyda aks ettirilsinmi?"</string>
-    <!-- no translation found for connected_display_dialog_dual_display_stop_warning (4174707498892447947) -->
-    <skip />
+    <string name="connected_display_dialog_dual_display_stop_warning" msgid="4174707498892447947">"Ichki ekran uchun aks ettirish yoqiladi. Old ekran oʻchiriladi."</string>
     <string name="mirror_display" msgid="2515262008898122928">"Displeyni aks ettirish"</string>
     <string name="dismiss_dialog" msgid="2195508495854675882">"Yopish"</string>
     <string name="connected_display_icon_desc" msgid="6373560639989971997">"Displey ulandi"</string>
diff --git a/packages/SystemUI/res/values-vi/strings.xml b/packages/SystemUI/res/values-vi/strings.xml
index b87619e..b1ff9a8 100644
--- a/packages/SystemUI/res/values-vi/strings.xml
+++ b/packages/SystemUI/res/values-vi/strings.xml
@@ -1208,8 +1208,7 @@
     <string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"Đặt ứng dụng ghi chú mặc định trong phần Cài đặt"</string>
     <string name="install_app" msgid="5066668100199613936">"Cài đặt ứng dụng"</string>
     <string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"Phản chiếu sang màn hình ngoài?"</string>
-    <!-- no translation found for connected_display_dialog_dual_display_stop_warning (4174707498892447947) -->
-    <skip />
+    <string name="connected_display_dialog_dual_display_stop_warning" msgid="4174707498892447947">"Màn hình trong của bạn sẽ được phản chiếu. Màn hình ngoài của bạn sẽ tắt."</string>
     <string name="mirror_display" msgid="2515262008898122928">"Phản chiếu màn hình"</string>
     <string name="dismiss_dialog" msgid="2195508495854675882">"Đóng"</string>
     <string name="connected_display_icon_desc" msgid="6373560639989971997">"Đã kết nối màn hình"</string>
diff --git a/packages/SystemUI/res/values-zh-rCN/strings.xml b/packages/SystemUI/res/values-zh-rCN/strings.xml
index 43dfbe8..237fd57 100644
--- a/packages/SystemUI/res/values-zh-rCN/strings.xml
+++ b/packages/SystemUI/res/values-zh-rCN/strings.xml
@@ -1208,8 +1208,7 @@
     <string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"在设置中设置默认记事应用"</string>
     <string name="install_app" msgid="5066668100199613936">"安装应用"</string>
     <string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"镜像到外接显示屏?"</string>
-    <!-- no translation found for connected_display_dialog_dual_display_stop_warning (4174707498892447947) -->
-    <skip />
+    <string name="connected_display_dialog_dual_display_stop_warning" msgid="4174707498892447947">"系统将镜像您的内屏,而关闭外屏。"</string>
     <string name="mirror_display" msgid="2515262008898122928">"镜像到显示屏"</string>
     <string name="dismiss_dialog" msgid="2195508495854675882">"关闭"</string>
     <string name="connected_display_icon_desc" msgid="6373560639989971997">"显示屏已连接"</string>
diff --git a/packages/SystemUI/res/values-zh-rHK/strings.xml b/packages/SystemUI/res/values-zh-rHK/strings.xml
index 784f667..313af30 100644
--- a/packages/SystemUI/res/values-zh-rHK/strings.xml
+++ b/packages/SystemUI/res/values-zh-rHK/strings.xml
@@ -1208,8 +1208,7 @@
     <string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"在「設定」中指定預設筆記應用程式"</string>
     <string name="install_app" msgid="5066668100199613936">"安裝應用程式"</string>
     <string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"要鏡像投射至外部顯示屏嗎?"</string>
-    <!-- no translation found for connected_display_dialog_dual_display_stop_warning (4174707498892447947) -->
-    <skip />
+    <string name="connected_display_dialog_dual_display_stop_warning" msgid="4174707498892447947">"鏡像畫面將顯示在內部螢幕,前方螢幕則會關閉。"</string>
     <string name="mirror_display" msgid="2515262008898122928">"鏡像顯示"</string>
     <string name="dismiss_dialog" msgid="2195508495854675882">"關閉"</string>
     <string name="connected_display_icon_desc" msgid="6373560639989971997">"已連接顯示屏"</string>
diff --git a/packages/SystemUI/res/values-zh-rTW/strings.xml b/packages/SystemUI/res/values-zh-rTW/strings.xml
index 15da239..6a13d3d 100644
--- a/packages/SystemUI/res/values-zh-rTW/strings.xml
+++ b/packages/SystemUI/res/values-zh-rTW/strings.xml
@@ -1208,8 +1208,7 @@
     <string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"在「設定」中指定預設記事應用程式"</string>
     <string name="install_app" msgid="5066668100199613936">"安裝應用程式"</string>
     <string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"要以鏡像方式投放至外部螢幕嗎?"</string>
-    <!-- no translation found for connected_display_dialog_dual_display_stop_warning (4174707498892447947) -->
-    <skip />
+    <string name="connected_display_dialog_dual_display_stop_warning" msgid="4174707498892447947">"鏡像畫面將顯示在內螢幕,封面螢幕則會關閉。"</string>
     <string name="mirror_display" msgid="2515262008898122928">"鏡像顯示"</string>
     <string name="dismiss_dialog" msgid="2195508495854675882">"關閉"</string>
     <string name="connected_display_icon_desc" msgid="6373560639989971997">"螢幕已連結"</string>
diff --git a/packages/SystemUI/res/values-zu/strings.xml b/packages/SystemUI/res/values-zu/strings.xml
index 31cdca9..23862a7 100644
--- a/packages/SystemUI/res/values-zu/strings.xml
+++ b/packages/SystemUI/res/values-zu/strings.xml
@@ -1208,8 +1208,7 @@
     <string name="set_default_notes_app_toast_content" msgid="2812374329662610753">"Setha i-app yamanothi azenzakalelayo Kumsethingi"</string>
     <string name="install_app" msgid="5066668100199613936">"Faka i-app"</string>
     <string name="connected_display_dialog_start_mirroring" msgid="6237895789920854982">"Fanisa nesibonisi sangaphandle?"</string>
-    <!-- no translation found for connected_display_dialog_dual_display_stop_warning (4174707498892447947) -->
-    <skip />
+    <string name="connected_display_dialog_dual_display_stop_warning" msgid="4174707498892447947">"Isibonisi sakho sangaphakathi sizoboniswa. Isibonisi sakho sangaphambili sizovalwa."</string>
     <string name="mirror_display" msgid="2515262008898122928">"Isibonisi sokufanisa"</string>
     <string name="dismiss_dialog" msgid="2195508495854675882">"Chitha"</string>
     <string name="connected_display_icon_desc" msgid="6373560639989971997">"Isibonisi sixhunyiwe"</string>
diff --git a/packages/SystemUI/res/values/config.xml b/packages/SystemUI/res/values/config.xml
index e01a2aa..10f7c4d 100644
--- a/packages/SystemUI/res/values/config.xml
+++ b/packages/SystemUI/res/values/config.xml
@@ -962,11 +962,20 @@
     <!-- Whether to show bottom sheets edge to edge -->
     <bool name="config_edgeToEdgeBottomSheetDialog">true</bool>
 
+    <!-- Device specific config that controls whether rest to unlock feature is supported.  -->
+    <bool name="config_restToUnlockSupported">false</bool>
+
+    <!--
+    Time in milliseconds the user has to touch the side FPS sensor to successfully authenticate when
+    the screen is turned off with AOD not enabled.
+    TODO(b/302332976) Get this value from the HAL if they can provide an API for it.
+    -->
+    <integer name="config_restToUnlockDurationScreenOff">500</integer>
     <!--
     Time in milliseconds the user has to touch the side FPS sensor to successfully authenticate
     TODO(b/302332976) Get this value from the HAL if they can provide an API for it.
     -->
-    <integer name="config_restToUnlockDuration">300</integer>
+    <integer name="config_restToUnlockDurationDefault">300</integer>
 
     <!--
     Width in pixels of the Side FPS sensor.
diff --git a/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt b/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt
index bcc2044..82410fd 100644
--- a/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt
+++ b/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt
@@ -33,6 +33,7 @@
 import androidx.annotation.VisibleForTesting
 import androidx.lifecycle.Lifecycle
 import androidx.lifecycle.repeatOnLifecycle
+import com.android.systemui.Flags.migrateClocksToBlueprint
 import com.android.systemui.broadcast.BroadcastDispatcher
 import com.android.systemui.customization.R
 import com.android.systemui.dagger.qualifiers.Background
@@ -325,6 +326,10 @@
                     }
                 }
 
+                if (visible) {
+                    refreshTime()
+                }
+
                 smallTimeListener?.update(shouldTimeListenerRun)
                 largeTimeListener?.update(shouldTimeListenerRun)
             }
@@ -346,6 +351,19 @@
                 weatherData = data
                 clock?.run { events.onWeatherDataChanged(data) }
             }
+
+            override fun onTimeChanged() {
+                refreshTime()
+            }
+
+            private fun refreshTime() {
+                if (!migrateClocksToBlueprint()) {
+                    return
+                }
+
+                clock?.smallClock?.events?.onTimeTick()
+                clock?.largeClock?.events?.onTimeTick()
+            }
         }
 
     private val zenModeCallback = object : ZenModeController.Callback {
@@ -558,7 +576,8 @@
             isRunning = true
             when (clockFace.config.tickRate) {
                 ClockTickRate.PER_MINUTE -> {
-                    /* Handled by KeyguardClockSwitchController */
+                    // Handled by KeyguardClockSwitchController and
+                    // by KeyguardUpdateMonitorCallback#onTimeChanged.
                 }
                 ClockTickRate.PER_SECOND -> executor.execute(secondsRunnable)
                 ClockTickRate.PER_FRAME -> {
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
index f3cd01f..4d84d0b 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
@@ -217,7 +217,8 @@
     private static final int MSG_BIOMETRIC_ENROLLMENT_STATE_CHANGED = 348;
 
     /** Biometric authentication state: Not listening. */
-    private static final int BIOMETRIC_STATE_STOPPED = 0;
+    @VisibleForTesting
+    protected static final int BIOMETRIC_STATE_STOPPED = 0;
 
     /** Biometric authentication state: Listening. */
     private static final int BIOMETRIC_STATE_RUNNING = 1;
@@ -1803,6 +1804,7 @@
                 public void onFingerprintDetected(int sensorId, int userId,
                         boolean isStrongBiometric) {
                     handleBiometricDetected(userId, FINGERPRINT, isStrongBiometric);
+                    setFingerprintRunningState(BIOMETRIC_STATE_STOPPED);
                 }
             };
 
diff --git a/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java b/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java
index 175fcdb..d5dc85c 100644
--- a/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java
@@ -26,7 +26,6 @@
 import static com.android.systemui.doze.util.BurnInHelperKt.getBurnInOffset;
 import static com.android.systemui.flags.Flags.DOZING_MIGRATION_1;
 import static com.android.systemui.flags.Flags.LOCKSCREEN_WALLPAPER_DREAM_ENABLED;
-import static com.android.systemui.Flags.newAodTransition;
 import static com.android.systemui.util.kotlin.JavaAdapterKt.collectFlow;
 
 import android.annotation.SuppressLint;
@@ -395,16 +394,6 @@
             mView.updateIcon(ICON_LOCK, true);
             mView.setContentDescription(mLockedLabel);
             mView.setVisibility(View.VISIBLE);
-        } else if (mIsDozing && newAodTransition()) {
-            mView.animate()
-                    .alpha(0f)
-                    .setDuration(FADE_OUT_DURATION_MS)
-                    .withEndAction(() -> {
-                        mView.clearIcon();
-                        mView.setVisibility(View.INVISIBLE);
-                        mView.setContentDescription(null);
-                    })
-                    .start();
         } else {
             mView.clearIcon();
             mView.setVisibility(View.INVISIBLE);
diff --git a/packages/SystemUI/src/com/android/keyguard/mediator/ScreenOnCoordinator.kt b/packages/SystemUI/src/com/android/keyguard/mediator/ScreenOnCoordinator.kt
index 603471b..7a560e8 100644
--- a/packages/SystemUI/src/com/android/keyguard/mediator/ScreenOnCoordinator.kt
+++ b/packages/SystemUI/src/com/android/keyguard/mediator/ScreenOnCoordinator.kt
@@ -19,6 +19,7 @@
 import android.annotation.BinderThread
 import android.os.Handler
 import android.os.Trace
+import com.android.systemui.Flags
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Main
 import com.android.systemui.unfold.SysUIUnfoldComponent
@@ -59,8 +60,11 @@
         foldAodAnimationController?.onScreenTurningOn(pendingTasks.registerTask("fold-to-aod"))
 
         pendingTasks.onTasksComplete {
-            mainHandler.post {
+            if (Flags.enableBackgroundKeyguardOndrawnCallback()) {
+                // called by whatever thread completes the last task registered.
                 onDrawn.run()
+            } else {
+                mainHandler.post { onDrawn.run() }
             }
         }
         Trace.endSection()
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java
index 8a1a2da..a4f90eb 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java
@@ -106,6 +106,7 @@
 import javax.inject.Provider;
 
 import kotlinx.coroutines.CoroutineScope;
+import kotlinx.coroutines.Job;
 
 /**
  * Receives messages sent from {@link com.android.server.biometrics.BiometricService} and shows the
@@ -136,6 +137,7 @@
     private final Provider<UdfpsController> mUdfpsControllerFactory;
     private final Provider<SideFpsController> mSidefpsControllerFactory;
     private final CoroutineScope mApplicationCoroutineScope;
+    private Job mBiometricContextListenerJob = null;
 
     // TODO: these should be migrated out once ready
     @NonNull private final Provider<PromptCredentialInteractor> mPromptCredentialInteractor;
@@ -914,7 +916,11 @@
 
     @Override
     public void setBiometricContextListener(IBiometricContextListener listener) {
-        mLogContextInteractor.get().addBiometricContextListener(listener);
+        if (mBiometricContextListenerJob != null) {
+            mBiometricContextListenerJob.cancel(null);
+        }
+        mBiometricContextListenerJob =
+                mLogContextInteractor.get().addBiometricContextListener(listener);
     }
 
     /**
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/SideFpsSensorInteractor.kt b/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/SideFpsSensorInteractor.kt
index f4231ac..348b54e 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/SideFpsSensorInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/SideFpsSensorInteractor.kt
@@ -26,6 +26,8 @@
 import com.android.systemui.biometrics.shared.model.FingerprintSensorType
 import com.android.systemui.biometrics.shared.model.isDefaultOrientation
 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.SideFpsLogger
 import com.android.systemui.res.R
 import java.util.Optional
@@ -47,9 +49,13 @@
     windowManager: WindowManager,
     displayStateInteractor: DisplayStateInteractor,
     fingerprintInteractiveToAuthProvider: Optional<FingerprintInteractiveToAuthProvider>,
+    keyguardTransitionInteractor: KeyguardTransitionInteractor,
     private val logger: SideFpsLogger,
 ) {
 
+    private val isProlongedTouchEnabledForDevice =
+        context.resources.getBoolean(R.bool.config_restToUnlockSupported)
+
     private val sensorLocationForCurrentDisplay =
         combine(
                 displayStateInteractor.displayChanges,
@@ -62,11 +68,24 @@
     val isAvailable: Flow<Boolean> =
         fingerprintPropertyRepository.sensorType.map { it == FingerprintSensorType.POWER_BUTTON }
 
-    val authenticationDuration: Long =
-        context.resources?.getInteger(R.integer.config_restToUnlockDuration)?.toLong() ?: 0L
+    val authenticationDuration: Flow<Long> =
+        keyguardTransitionInteractor
+            .isFinishedInStateWhere { it == KeyguardState.OFF || it == KeyguardState.DOZING }
+            .map {
+                if (it)
+                    context.resources
+                        ?.getInteger(R.integer.config_restToUnlockDurationScreenOff)
+                        ?.toLong()
+                else
+                    context.resources
+                        ?.getInteger(R.integer.config_restToUnlockDurationDefault)
+                        ?.toLong()
+            }
+            .map { it ?: 0L }
+            .onEach { logger.authDurationChanged(it) }
 
     val isProlongedTouchRequiredForAuthentication: Flow<Boolean> =
-        if (fingerprintInteractiveToAuthProvider.isEmpty) {
+        if (fingerprintInteractiveToAuthProvider.isEmpty || !isProlongedTouchEnabledForDevice) {
             flowOf(false)
         } else {
             combine(
diff --git a/packages/SystemUI/src/com/android/systemui/common/data/CommonDataLayerModule.kt b/packages/SystemUI/src/com/android/systemui/common/data/CommonDataLayerModule.kt
index 27c9b3f..d1c728c 100644
--- a/packages/SystemUI/src/com/android/systemui/common/data/CommonDataLayerModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/common/data/CommonDataLayerModule.kt
@@ -16,6 +16,8 @@
 
 package com.android.systemui.common.data
 
+import com.android.systemui.common.data.repository.PackageChangeRepository
+import com.android.systemui.common.data.repository.PackageChangeRepositoryImpl
 import com.android.systemui.common.ui.data.repository.ConfigurationRepository
 import com.android.systemui.common.ui.data.repository.ConfigurationRepositoryImpl
 import dagger.Binds
@@ -23,5 +25,13 @@
 
 @Module
 abstract class CommonDataLayerModule {
-    @Binds abstract fun bindRepository(impl: ConfigurationRepositoryImpl): ConfigurationRepository
+    @Binds
+    abstract fun bindConfigurationRepository(
+        impl: ConfigurationRepositoryImpl
+    ): ConfigurationRepository
+
+    @Binds
+    abstract fun bindPackageChangeRepository(
+        impl: PackageChangeRepositoryImpl
+    ): PackageChangeRepository
 }
diff --git a/packages/SystemUI/src/com/android/systemui/common/data/repository/PackageChangeRepository.kt b/packages/SystemUI/src/com/android/systemui/common/data/repository/PackageChangeRepository.kt
new file mode 100644
index 0000000..7c7b3db
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/common/data/repository/PackageChangeRepository.kt
@@ -0,0 +1,31 @@
+/*
+ * 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.common.data.repository
+
+import android.os.UserHandle
+import com.android.systemui.common.data.shared.model.PackageChangeModel
+import kotlinx.coroutines.flow.Flow
+
+interface PackageChangeRepository {
+    /**
+     * Emits values when packages for the specified user are changed. See supported modifications in
+     * [PackageChangeModel]
+     *
+     * [UserHandle.USER_ALL] may be used to listen to all users.
+     */
+    fun packageChanged(user: UserHandle): Flow<PackageChangeModel>
+}
diff --git a/packages/SystemUI/src/com/android/systemui/common/data/repository/PackageChangeRepositoryImpl.kt b/packages/SystemUI/src/com/android/systemui/common/data/repository/PackageChangeRepositoryImpl.kt
new file mode 100644
index 0000000..b1b348c
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/common/data/repository/PackageChangeRepositoryImpl.kt
@@ -0,0 +1,42 @@
+/*
+ * 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.common.data.repository
+
+import android.os.UserHandle
+import com.android.systemui.common.data.shared.model.PackageChangeModel
+import com.android.systemui.dagger.SysUISingleton
+import javax.inject.Inject
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.filter
+
+@SysUISingleton
+class PackageChangeRepositoryImpl
+@Inject
+constructor(
+    private val monitorFactory: PackageUpdateMonitor.Factory,
+) : PackageChangeRepository {
+    /**
+     * A [PackageUpdateMonitor] which monitors package updates for all users. The per-user filtering
+     * is done by [packageChanged].
+     */
+    private val monitor by lazy { monitorFactory.create(UserHandle.ALL) }
+
+    override fun packageChanged(user: UserHandle): Flow<PackageChangeModel> =
+        monitor.packageChanged.filter {
+            user == UserHandle.ALL || user == UserHandle.getUserHandleForUid(it.packageUid)
+        }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/common/data/repository/PackageUpdateLogger.kt b/packages/SystemUI/src/com/android/systemui/common/data/repository/PackageUpdateLogger.kt
new file mode 100644
index 0000000..adbb37c
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/common/data/repository/PackageUpdateLogger.kt
@@ -0,0 +1,57 @@
+/*
+ * 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.common.data.repository
+
+import android.os.UserHandle
+import com.android.systemui.common.data.shared.model.PackageChangeModel
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.log.LogBuffer
+import com.android.systemui.log.core.LogLevel
+import com.android.systemui.log.dagger.PackageChangeRepoLog
+import javax.inject.Inject
+
+private fun getChangeString(model: PackageChangeModel) =
+    when (model) {
+        is PackageChangeModel.Installed -> "installed"
+        is PackageChangeModel.Uninstalled -> "uninstalled"
+        is PackageChangeModel.UpdateStarted -> "started updating"
+        is PackageChangeModel.UpdateFinished -> "finished updating"
+        is PackageChangeModel.Changed -> "changed"
+    }
+
+/** A debug logger for [PackageChangeRepository]. */
+@SysUISingleton
+class PackageUpdateLogger @Inject constructor(@PackageChangeRepoLog private val buffer: LogBuffer) {
+
+    fun logChange(model: PackageChangeModel) {
+        buffer.log(
+            TAG,
+            LogLevel.DEBUG,
+            {
+                str1 = model.packageName
+                str2 = getChangeString(model)
+                int1 = model.packageUid
+            },
+            {
+                val user = UserHandle.getUserHandleForUid(int1)
+                "Package $str1 ($int1) $str2 on user $user"
+            }
+        )
+    }
+}
+
+private const val TAG = "PackageChangeRepoLog"
diff --git a/packages/SystemUI/src/com/android/systemui/common/data/repository/PackageUpdateMonitor.kt b/packages/SystemUI/src/com/android/systemui/common/data/repository/PackageUpdateMonitor.kt
new file mode 100644
index 0000000..f7cc344
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/common/data/repository/PackageUpdateMonitor.kt
@@ -0,0 +1,127 @@
+/*
+ * 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.common.data.repository
+
+import android.content.Context
+import android.os.Handler
+import android.os.UserHandle
+import com.android.internal.content.PackageMonitor
+import com.android.systemui.common.data.shared.model.PackageChangeModel
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.dagger.qualifiers.Background
+import dagger.assisted.Assisted
+import dagger.assisted.AssistedFactory
+import dagger.assisted.AssistedInject
+import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableSharedFlow
+import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.flowOn
+import kotlinx.coroutines.flow.launchIn
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.onEach
+
+/**
+ * A wrapper around [PackageMonitor] which exposes package updates as a flow.
+ *
+ * External clients should use [PackageChangeRepository] instead to ensure only a single callback is
+ * registered for all of SystemUI.
+ */
+class PackageUpdateMonitor
+@AssistedInject
+constructor(
+    @Assisted private val user: UserHandle,
+    @Background private val bgDispatcher: CoroutineDispatcher,
+    @Background private val bgHandler: Handler,
+    @Application private val context: Context,
+    @Application private val scope: CoroutineScope,
+    private val logger: PackageUpdateLogger,
+) : PackageMonitor() {
+
+    @AssistedFactory
+    fun interface Factory {
+        fun create(user: UserHandle): PackageUpdateMonitor
+    }
+
+    var isActive = false
+        private set
+
+    private val _packageChanged =
+        MutableSharedFlow<PackageChangeModel>(replay = 0, extraBufferCapacity = BUFFER_CAPACITY)
+            .apply {
+                // Automatically register/unregister as needed, depending on whether
+                // there are subscribers to this flow.
+                subscriptionCount
+                    .map { it > 0 }
+                    .distinctUntilChanged()
+                    .onEach { active ->
+                        if (active) {
+                            register(context, user, bgHandler)
+                        } else if (isActive) {
+                            // Avoid calling unregister if we were not previously active, as this
+                            // will cause an IllegalStateException.
+                            unregister()
+                        }
+                        isActive = active
+                    }
+                    .flowOn(bgDispatcher)
+                    .launchIn(scope)
+            }
+
+    val packageChanged: Flow<PackageChangeModel>
+        get() = _packageChanged.onEach(logger::logChange)
+
+    override fun onPackageAdded(packageName: String, uid: Int) {
+        super.onPackageAdded(packageName, uid)
+        _packageChanged.tryEmit(PackageChangeModel.Installed(packageName, uid))
+    }
+
+    override fun onPackageRemoved(packageName: String, uid: Int) {
+        super.onPackageRemoved(packageName, uid)
+        _packageChanged.tryEmit(PackageChangeModel.Uninstalled(packageName, uid))
+    }
+
+    override fun onPackageChanged(
+        packageName: String,
+        uid: Int,
+        components: Array<out String>
+    ): Boolean {
+        super.onPackageChanged(packageName, uid, components)
+        _packageChanged.tryEmit(PackageChangeModel.Changed(packageName, uid))
+        return false
+    }
+
+    override fun onPackageUpdateStarted(packageName: String, uid: Int) {
+        super.onPackageUpdateStarted(packageName, uid)
+        _packageChanged.tryEmit(PackageChangeModel.UpdateStarted(packageName, uid))
+    }
+
+    override fun onPackageUpdateFinished(packageName: String, uid: Int) {
+        super.onPackageUpdateFinished(packageName, uid)
+        _packageChanged.tryEmit(PackageChangeModel.UpdateFinished(packageName, uid))
+    }
+
+    private companion object {
+        // This capacity is the number of package changes that we will keep buffered in the shared
+        // flow. It is unlikely that at any given time there would be this many changes being
+        // processed by consumers, but this is done just in case that many packages are changed at
+        // the same time and there is backflow due to consumers processing the changes more slowly
+        // than they are being emitted.
+        const val BUFFER_CAPACITY = 100
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/common/data/shared/model/PackageChangeModel.kt b/packages/SystemUI/src/com/android/systemui/common/data/shared/model/PackageChangeModel.kt
new file mode 100644
index 0000000..853eff7
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/common/data/shared/model/PackageChangeModel.kt
@@ -0,0 +1,71 @@
+/*
+ * 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.common.data.shared.model
+
+import android.content.Intent
+
+/** Represents changes to an installed package. */
+sealed interface PackageChangeModel {
+    val packageName: String
+    val packageUid: Int
+
+    /**
+     * An existing application package was uninstalled.
+     *
+     * Equivalent to receiving the [Intent.ACTION_PACKAGE_REMOVED] broadcast with
+     * [Intent.EXTRA_REPLACING] set to false.
+     */
+    data class Uninstalled(override val packageName: String, override val packageUid: Int) :
+        PackageChangeModel
+
+    /**
+     * A new version of an existing application is going to be installed.
+     *
+     * Equivalent to receiving the [Intent.ACTION_PACKAGE_REMOVED] broadcast with
+     * [Intent.EXTRA_REPLACING] set to true.
+     */
+    data class UpdateStarted(override val packageName: String, override val packageUid: Int) :
+        PackageChangeModel
+
+    /**
+     * A new version of an existing application package has been installed, replacing the old
+     * version.
+     *
+     * Equivalent to receiving the [Intent.ACTION_PACKAGE_ADDED] broadcast with
+     * [Intent.EXTRA_REPLACING] set to true.
+     */
+    data class UpdateFinished(override val packageName: String, override val packageUid: Int) :
+        PackageChangeModel
+
+    /**
+     * A new application package has been installed.
+     *
+     * Equivalent to receiving the [Intent.ACTION_PACKAGE_ADDED] broadcast with
+     * [Intent.EXTRA_REPLACING] set to false.
+     */
+    data class Installed(override val packageName: String, override val packageUid: Int) :
+        PackageChangeModel
+
+    /**
+     * An existing application package has been changed (for example, a component has been enabled
+     * or disabled).
+     *
+     * Equivalent to receiving the [Intent.ACTION_PACKAGE_CHANGED] broadcast.
+     */
+    data class Changed(override val packageName: String, override val packageUid: Int) :
+        PackageChangeModel
+}
diff --git a/packages/SystemUI/src/com/android/systemui/communal/dagger/CommunalModule.kt b/packages/SystemUI/src/com/android/systemui/communal/dagger/CommunalModule.kt
index 847b98e..10768ea 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/dagger/CommunalModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/dagger/CommunalModule.kt
@@ -16,7 +16,6 @@
 
 package com.android.systemui.communal.dagger
 
-import android.content.Context
 import com.android.systemui.communal.data.db.CommunalDatabaseModule
 import com.android.systemui.communal.data.repository.CommunalMediaRepositoryModule
 import com.android.systemui.communal.data.repository.CommunalRepositoryModule
@@ -24,9 +23,8 @@
 import com.android.systemui.communal.data.repository.CommunalWidgetRepositoryModule
 import com.android.systemui.communal.widgets.EditWidgetsActivityStarter
 import com.android.systemui.communal.widgets.EditWidgetsActivityStarterImpl
-import com.android.systemui.dagger.qualifiers.Application
+import dagger.Binds
 import dagger.Module
-import dagger.Provides
 
 @Module(
     includes =
@@ -38,11 +36,9 @@
             CommunalDatabaseModule::class,
         ]
 )
-class CommunalModule {
-    @Provides
-    fun provideEditWidgetsActivityStarter(
-        @Application context: Context
-    ): EditWidgetsActivityStarter {
-        return EditWidgetsActivityStarterImpl(context)
-    }
+interface CommunalModule {
+    @Binds
+    fun bindEditWidgetsActivityStarter(
+        starter: EditWidgetsActivityStarterImpl
+    ): EditWidgetsActivityStarter
 }
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 0f4e583..18fb895 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
@@ -24,6 +24,9 @@
 import com.android.systemui.communal.data.repository.CommunalWidgetRepository
 import com.android.systemui.communal.domain.model.CommunalContentModel
 import com.android.systemui.communal.shared.model.CommunalContentSize
+import com.android.systemui.communal.shared.model.CommunalContentSize.FULL
+import com.android.systemui.communal.shared.model.CommunalContentSize.HALF
+import com.android.systemui.communal.shared.model.CommunalContentSize.THIRD
 import com.android.systemui.communal.shared.model.CommunalSceneKey
 import com.android.systemui.communal.shared.model.ObservableCommunalTransitionState
 import com.android.systemui.communal.widgets.EditWidgetsActivityStarter
@@ -133,12 +136,11 @@
                         target.featureType == SmartspaceTarget.FEATURE_TIMER &&
                             target.remoteViews != null
                     }
-                    .map Target@{ target ->
+                    .mapIndexed Target@{ index, target ->
                         return@Target CommunalContentModel.Smartspace(
                             smartspaceTargetId = target.smartspaceTargetId,
                             remoteViews = target.remoteViews!!,
-                            // Smartspace always as HALF for now.
-                            size = CommunalContentSize.HALF,
+                            size = dynamicContentSize(targets.size, index),
                         )
                     }
             }
@@ -147,23 +149,50 @@
     /** A list of tutorial content to be displayed in the communal hub in tutorial mode. */
     val tutorialContent: List<CommunalContentModel.Tutorial> =
         listOf(
-            CommunalContentModel.Tutorial(id = 0, CommunalContentSize.FULL),
-            CommunalContentModel.Tutorial(id = 1, CommunalContentSize.THIRD),
-            CommunalContentModel.Tutorial(id = 2, CommunalContentSize.THIRD),
-            CommunalContentModel.Tutorial(id = 3, CommunalContentSize.THIRD),
-            CommunalContentModel.Tutorial(id = 4, CommunalContentSize.HALF),
-            CommunalContentModel.Tutorial(id = 5, CommunalContentSize.HALF),
-            CommunalContentModel.Tutorial(id = 6, CommunalContentSize.HALF),
-            CommunalContentModel.Tutorial(id = 7, CommunalContentSize.HALF),
+            CommunalContentModel.Tutorial(id = 0, FULL),
+            CommunalContentModel.Tutorial(id = 1, THIRD),
+            CommunalContentModel.Tutorial(id = 2, THIRD),
+            CommunalContentModel.Tutorial(id = 3, THIRD),
+            CommunalContentModel.Tutorial(id = 4, HALF),
+            CommunalContentModel.Tutorial(id = 5, HALF),
+            CommunalContentModel.Tutorial(id = 6, HALF),
+            CommunalContentModel.Tutorial(id = 7, HALF),
         )
 
     val umoContent: Flow<List<CommunalContentModel.Umo>> =
         mediaRepository.mediaPlaying.flatMapLatest { mediaPlaying ->
             if (mediaPlaying) {
                 // TODO(b/310254801): support HALF and FULL layouts
-                flowOf(listOf(CommunalContentModel.Umo(CommunalContentSize.THIRD)))
+                flowOf(listOf(CommunalContentModel.Umo(THIRD)))
             } else {
                 flowOf(emptyList())
             }
         }
+
+    companion object {
+        /**
+         * Calculates the content size dynamically based on the total number of contents of that
+         * type.
+         *
+         * Contents with the same type are expected to fill each column evenly. Currently there are
+         * three possible sizes. When the total number is 1, size for that content is [FULL], when
+         * the total number is 2, size for each is [HALF], and 3, size for each is [THIRD].
+         *
+         * When dynamic contents fill in multiple columns, the first column follows the algorithm
+         * above, and the remaining contents are packed in [THIRD]s. For example, when the total
+         * number if 4, the first one is [FULL], filling the column, and the remaining 3 are
+         * [THIRD].
+         *
+         * @param size The total number of contents of this type.
+         * @param index The index of the current content of this type.
+         */
+        private fun dynamicContentSize(size: Int, index: Int): CommunalContentSize {
+            val remainder = size % CommunalContentSize.entries.size
+            return CommunalContentSize.toSize(
+                span =
+                    FULL.span /
+                        if (index > remainder - 1) CommunalContentSize.entries.size else remainder
+            )
+        }
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/communal/shared/model/CommunalContentSize.kt b/packages/SystemUI/src/com/android/systemui/communal/shared/model/CommunalContentSize.kt
index c903709..572794d 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/shared/model/CommunalContentSize.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/shared/model/CommunalContentSize.kt
@@ -30,5 +30,13 @@
     HALF(3),
 
     /** Content takes a third of the height of the column. */
-    THIRD(2),
+    THIRD(2);
+
+    companion object {
+        /** Converts from span to communal content size. */
+        fun toSize(span: Int): CommunalContentSize {
+            return entries.find { it.span == span }
+                ?: throw Exception("Invalid span for communal content size")
+        }
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/communal/widgets/EditWidgetsActivityStarter.kt b/packages/SystemUI/src/com/android/systemui/communal/widgets/EditWidgetsActivityStarter.kt
index 846e300..55acad0 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/widgets/EditWidgetsActivityStarter.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/widgets/EditWidgetsActivityStarter.kt
@@ -19,17 +19,26 @@
 import android.content.Context
 import android.content.Intent
 import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.plugins.ActivityStarter
+import javax.inject.Inject
 
 interface EditWidgetsActivityStarter {
     fun startActivity()
 }
 
-class EditWidgetsActivityStarterImpl(@Application private val applicationContext: Context) :
-    EditWidgetsActivityStarter {
+class EditWidgetsActivityStarterImpl
+@Inject
+constructor(
+    @Application private val applicationContext: Context,
+    private val activityStarter: ActivityStarter,
+) : EditWidgetsActivityStarter {
+
     override fun startActivity() {
-        applicationContext.startActivity(
+        activityStarter.startActivityDismissingKeyguard(
             Intent(applicationContext, EditWidgetsActivity::class.java)
-                .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK)
+                .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK),
+            /* onlyProvisioned = */ true,
+            /* dismissShade = */ true,
         )
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/controls/management/PanelConfirmationDialogFactory.kt b/packages/SystemUI/src/com/android/systemui/controls/management/PanelConfirmationDialogFactory.kt
index 5df26b3..a6b4320 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/management/PanelConfirmationDialogFactory.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/management/PanelConfirmationDialogFactory.kt
@@ -28,27 +28,26 @@
 /**
  * Factory to create dialogs for consenting to show app panels for specific apps.
  *
- * [internalDialogFactory] is for facilitating testing.
+ * [dialogFactory] is for facilitating testing.
  */
-class PanelConfirmationDialogFactory(
-    private val internalDialogFactory: (Context) -> SystemUIDialog
+class PanelConfirmationDialogFactory @Inject constructor(
+        private val dialogFactory: SystemUIDialog.Factory
 ) {
-    @Inject constructor() : this({ SystemUIDialog(it) })
 
     /**
      * Creates a dialog to show to the user. [response] will be true if an only if the user responds
      * affirmatively.
      */
     fun createConfirmationDialog(
-        context: Context,
-        appName: CharSequence,
-        response: Consumer<Boolean>
+            context: Context,
+            appName: CharSequence,
+            response: Consumer<Boolean>
     ): Dialog {
         val listener =
             DialogInterface.OnClickListener { _, which ->
                 response.accept(which == DialogInterface.BUTTON_POSITIVE)
             }
-        return internalDialogFactory(context).apply {
+        return dialogFactory.create(context).apply {
             setTitle(this.context.getString(R.string.controls_panel_authorization_title, appName))
             setMessage(this.context.getString(R.string.controls_panel_authorization, appName))
             setCanceledOnTouchOutside(true)
diff --git a/packages/SystemUI/src/com/android/systemui/controls/start/ControlsStartable.kt b/packages/SystemUI/src/com/android/systemui/controls/start/ControlsStartable.kt
index 0218f45..20bfbc9 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/start/ControlsStartable.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/start/ControlsStartable.kt
@@ -26,6 +26,8 @@
 import androidx.annotation.WorkerThread
 import com.android.systemui.CoreStartable
 import com.android.systemui.broadcast.BroadcastDispatcher
+import com.android.systemui.common.data.shared.model.PackageChangeModel
+import com.android.systemui.common.data.repository.PackageChangeRepository
 import com.android.systemui.controls.controller.ControlsController
 import com.android.systemui.controls.dagger.ControlsComponent
 import com.android.systemui.controls.management.ControlsListingController
@@ -33,8 +35,16 @@
 import com.android.systemui.controls.panels.SelectedComponentRepository
 import com.android.systemui.controls.ui.SelectedItem
 import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.dagger.qualifiers.Background
 import com.android.systemui.settings.UserTracker
+import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Job
+import kotlinx.coroutines.flow.filter
+import kotlinx.coroutines.flow.flowOn
+import kotlinx.coroutines.flow.launchIn
+import kotlinx.coroutines.flow.onEach
 import java.util.concurrent.Executor
 import javax.inject.Inject
 
@@ -53,13 +63,16 @@
 class ControlsStartable
 @Inject
 constructor(
-        @Background private val executor: Executor,
-        private val controlsComponent: ControlsComponent,
-        private val userTracker: UserTracker,
-        private val authorizedPanelsRepository: AuthorizedPanelsRepository,
-        private val selectedComponentRepository: SelectedComponentRepository,
-        private val userManager: UserManager,
-        private val broadcastDispatcher: BroadcastDispatcher,
+    @Application private val scope: CoroutineScope,
+    @Background private val bgDispatcher: CoroutineDispatcher,
+    @Background private val executor: Executor,
+    private val controlsComponent: ControlsComponent,
+    private val userTracker: UserTracker,
+    private val authorizedPanelsRepository: AuthorizedPanelsRepository,
+    private val selectedComponentRepository: SelectedComponentRepository,
+    private val packageChangeRepository: PackageChangeRepository,
+    private val userManager: UserManager,
+    private val broadcastDispatcher: BroadcastDispatcher,
 ) : CoreStartable {
 
     // These two controllers can only be accessed after `start` method once we've checked if the
@@ -78,6 +91,8 @@
             }
         }
 
+    private var packageJob: Job? = null
+
     override fun start() {}
 
     override fun onBootCompleted() {
@@ -94,6 +109,21 @@
         controlsListingController.forceReload()
         selectDefaultPanelIfNecessary()
         bindToPanel()
+        monitorPackageUninstall()
+    }
+
+    private fun monitorPackageUninstall() {
+        packageJob?.cancel()
+        packageJob = packageChangeRepository.packageChanged(userTracker.userHandle)
+            .filter {
+                val selectedPackage =
+                    selectedComponentRepository.getSelectedComponent()?.componentName?.packageName
+                // Selected package was uninstalled
+                (it is PackageChangeModel.Uninstalled) && (it.packageName == selectedPackage)
+            }
+            .onEach { selectedComponentRepository.removeSelectedComponent() }
+            .flowOn(bgDispatcher)
+            .launchIn(scope)
     }
 
     private fun selectDefaultPanelIfNecessary() {
diff --git a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsDialogsFactory.kt b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsDialogsFactory.kt
index 2ad6014..e42a4a6 100644
--- a/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsDialogsFactory.kt
+++ b/packages/SystemUI/src/com/android/systemui/controls/ui/ControlsDialogsFactory.kt
@@ -25,20 +25,21 @@
 import java.util.function.Consumer
 import javax.inject.Inject
 
-class ControlsDialogsFactory(private val internalDialogFactory: (Context) -> SystemUIDialog) {
+class ControlsDialogsFactory @Inject constructor(
+        private val dialogFactory: SystemUIDialog.Factory
+) {
 
-    @Inject constructor() : this({ SystemUIDialog(it) })
 
     fun createRemoveAppDialog(
-        context: Context,
-        appName: CharSequence,
-        response: Consumer<Boolean>
+            context: Context,
+            appName: CharSequence,
+            response: Consumer<Boolean>
     ): Dialog {
         val listener =
             DialogInterface.OnClickListener { _, which ->
                 response.accept(which == DialogInterface.BUTTON_POSITIVE)
             }
-        return internalDialogFactory(context).apply {
+        return dialogFactory.create(context).apply {
             setTitle(context.getString(R.string.controls_panel_remove_app_authorization, appName))
             setCanceledOnTouchOutside(true)
             setOnCancelListener { response.accept(false) }
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt b/packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt
index ac71664..87a736d 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt
@@ -54,6 +54,7 @@
 import com.android.systemui.stylus.StylusUsiPowerStartable
 import com.android.systemui.temporarydisplay.chipbar.ChipbarCoordinator
 import com.android.systemui.theme.ThemeOverlayController
+import com.android.systemui.unfold.DisplaySwitchLatencyTracker
 import com.android.systemui.usb.StorageNotification
 import com.android.systemui.util.NotificationChannels
 import com.android.systemui.util.StartBinderLoggerModule
@@ -141,6 +142,12 @@
     @ClassKey(LatencyTester::class)
     abstract fun bindLatencyTester(sysui: LatencyTester): CoreStartable
 
+    /** Inject into DisplaySwitchLatencyTracker.  */
+    @Binds
+    @IntoMap
+    @ClassKey(DisplaySwitchLatencyTracker::class)
+    abstract fun bindDisplaySwitchLatencyTracker(sysui: DisplaySwitchLatencyTracker): CoreStartable
+
     /** Inject into NotificationChannels.  */
     @Binds
     @IntoMap
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
index a25c788..92300ef 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemUIModule.java
@@ -112,6 +112,7 @@
 import com.android.systemui.statusbar.notification.row.dagger.ExpandableNotificationRowComponent;
 import com.android.systemui.statusbar.notification.row.dagger.NotificationRowComponent;
 import com.android.systemui.statusbar.phone.CentralSurfaces;
+import com.android.systemui.statusbar.phone.ConfigurationControllerModule;
 import com.android.systemui.statusbar.phone.LetterboxModule;
 import com.android.systemui.statusbar.phone.NotificationIconAreaControllerModule;
 import com.android.systemui.statusbar.pipeline.dagger.StatusBarPipelineModule;
@@ -178,6 +179,7 @@
         ClockRegistryModule.class,
         CommunalModule.class,
         CommonDataLayerModule.class,
+        ConfigurationControllerModule.class,
         ConnectivityModule.class,
         ControlsModule.class,
         CoroutinesModule.class,
diff --git a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryFingerprintAuthInteractor.kt b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryFingerprintAuthInteractor.kt
index efa1c0a..684627b 100644
--- a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryFingerprintAuthInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryFingerprintAuthInteractor.kt
@@ -19,6 +19,7 @@
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.keyguard.data.repository.DeviceEntryFingerprintAuthRepository
 import com.android.systemui.keyguard.shared.model.FailFingerprintAuthenticationStatus
+import com.android.systemui.keyguard.shared.model.FingerprintAuthenticationStatus
 import javax.inject.Inject
 import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.filterIsInstance
@@ -31,4 +32,11 @@
 ) {
     val fingerprintFailure: Flow<FailFingerprintAuthenticationStatus> =
         repository.authenticationStatus.filterIsInstance<FailFingerprintAuthenticationStatus>()
+
+    /** Whether fingerprint authentication is currently running or not */
+    val isRunning: Flow<Boolean> = repository.isRunning
+
+    /** Provide the current status of fingerprint authentication. */
+    val authenticationStatus: Flow<FingerprintAuthenticationStatus> =
+        repository.authenticationStatus
 }
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeHost.java b/packages/SystemUI/src/com/android/systemui/doze/DozeHost.java
index 4c4aa5c..8776ec5 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeHost.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeHost.java
@@ -118,6 +118,11 @@
          * Called when the dozing state may have been updated.
          */
         default void onDozingChanged(boolean isDozing) {}
+
+        /**
+         * Called when fingerprint acquisition has started and screen state might need updating.
+         */
+        default void onSideFingerprintAcquisitionStarted() {}
     }
 
     interface PulseCallback {
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeLog.java b/packages/SystemUI/src/com/android/systemui/doze/DozeLog.java
index 5b90ef2..424bd0a 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeLog.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeLog.java
@@ -514,6 +514,7 @@
             case REASON_SENSOR_TAP: return "tap";
             case REASON_SENSOR_UDFPS_LONG_PRESS: return "udfps";
             case REASON_SENSOR_QUICK_PICKUP: return "quickPickup";
+            case PULSE_REASON_FINGERPRINT_ACTIVATED: return "fingerprint-triggered";
             default: throw new IllegalArgumentException("invalid reason: " + pulseReason);
         }
     }
@@ -542,7 +543,9 @@
             PULSE_REASON_SENSOR_SIGMOTION, REASON_SENSOR_PICKUP, REASON_SENSOR_DOUBLE_TAP,
             PULSE_REASON_SENSOR_LONG_PRESS, PULSE_REASON_DOCKING, REASON_SENSOR_WAKE_UP_PRESENCE,
             PULSE_REASON_SENSOR_WAKE_REACH, REASON_SENSOR_TAP,
-            REASON_SENSOR_UDFPS_LONG_PRESS, REASON_SENSOR_QUICK_PICKUP})
+            REASON_SENSOR_UDFPS_LONG_PRESS, REASON_SENSOR_QUICK_PICKUP,
+            PULSE_REASON_FINGERPRINT_ACTIVATED
+    })
     public @interface Reason {}
     public static final int PULSE_REASON_NONE = -1;
     public static final int PULSE_REASON_INTENT = 0;
@@ -557,6 +560,7 @@
     public static final int REASON_SENSOR_TAP = 9;
     public static final int REASON_SENSOR_UDFPS_LONG_PRESS = 10;
     public static final int REASON_SENSOR_QUICK_PICKUP = 11;
+    public static final int PULSE_REASON_FINGERPRINT_ACTIVATED = 12;
 
-    public static final int TOTAL_REASONS = 12;
+    public static final int TOTAL_REASONS = 13;
 }
diff --git a/packages/SystemUI/src/com/android/systemui/doze/DozeTriggers.java b/packages/SystemUI/src/com/android/systemui/doze/DozeTriggers.java
index 795c3d4..9311187 100644
--- a/packages/SystemUI/src/com/android/systemui/doze/DozeTriggers.java
+++ b/packages/SystemUI/src/com/android/systemui/doze/DozeTriggers.java
@@ -265,6 +265,10 @@
         mDozeLog.traceNotificationPulse();
     }
 
+    private void onSideFingerprintAcquisitionStarted() {
+        requestPulse(DozeLog.PULSE_REASON_FINGERPRINT_ACTIVATED, false, null);
+    }
+
     private static void runIfNotNull(Runnable runnable) {
         if (runnable != null) {
             runnable.run();
@@ -690,5 +694,10 @@
         public void onNotificationAlerted(Runnable onPulseSuppressedListener) {
             onNotification(onPulseSuppressedListener);
         }
+
+        @Override
+        public void onSideFingerprintAcquisitionStarted() {
+            DozeTriggers.this.onSideFingerprintAcquisitionStarted();
+        }
     };
 }
diff --git a/packages/SystemUI/src/com/android/systemui/haptics/slider/SliderHapticFeedbackConfig.kt b/packages/SystemUI/src/com/android/systemui/haptics/slider/SliderHapticFeedbackConfig.kt
index 6cb68ba..89bfd96 100644
--- a/packages/SystemUI/src/com/android/systemui/haptics/slider/SliderHapticFeedbackConfig.kt
+++ b/packages/SystemUI/src/com/android/systemui/haptics/slider/SliderHapticFeedbackConfig.kt
@@ -16,6 +16,7 @@
 
 package com.android.systemui.haptics.slider
 
+import android.view.MotionEvent
 import androidx.annotation.FloatRange
 
 /** Configuration parameters of a [SliderHapticFeedbackProvider] */
@@ -38,6 +39,8 @@
     val numberOfLowTicks: Int = 5,
     /** Maximum velocity allowed for vibration scaling. This is not expected to change. */
     val maxVelocityToScale: Float = 2000f, /* In pixels/sec */
+    /** Axis to use when computing velocity. Must be the same as the slider's axis of movement */
+    val velocityAxis: Int = MotionEvent.AXIS_X,
     /** Vibration scale at the upper bookend of the slider */
     @FloatRange(from = 0.0, to = 1.0) val upperBookendScale: Float = 1f,
     /** Vibration scale at the lower bookend of the slider */
diff --git a/packages/SystemUI/src/com/android/systemui/haptics/slider/SliderHapticFeedbackProvider.kt b/packages/SystemUI/src/com/android/systemui/haptics/slider/SliderHapticFeedbackProvider.kt
index 9e6245a..6f28ab7 100644
--- a/packages/SystemUI/src/com/android/systemui/haptics/slider/SliderHapticFeedbackProvider.kt
+++ b/packages/SystemUI/src/com/android/systemui/haptics/slider/SliderHapticFeedbackProvider.kt
@@ -162,27 +162,33 @@
 
     override fun onLowerBookend() {
         if (!hasVibratedAtLowerBookend) {
-            velocityTracker.computeCurrentVelocity(UNITS_SECOND, config.maxVelocityToScale)
-            vibrateOnEdgeCollision(abs(velocityTracker.xVelocity))
+            vibrateOnEdgeCollision(abs(getTrackedVelocity()))
             hasVibratedAtLowerBookend = true
         }
     }
 
     override fun onUpperBookend() {
         if (!hasVibratedAtUpperBookend) {
-            velocityTracker.computeCurrentVelocity(UNITS_SECOND, config.maxVelocityToScale)
-            vibrateOnEdgeCollision(abs(velocityTracker.xVelocity))
+            vibrateOnEdgeCollision(abs(getTrackedVelocity()))
             hasVibratedAtUpperBookend = true
         }
     }
 
     override fun onProgress(@FloatRange(from = 0.0, to = 1.0) progress: Float) {
-        velocityTracker.computeCurrentVelocity(UNITS_SECOND, config.maxVelocityToScale)
-        vibrateDragTexture(abs(velocityTracker.xVelocity), progress)
+        vibrateDragTexture(abs(getTrackedVelocity()), progress)
         hasVibratedAtUpperBookend = false
         hasVibratedAtLowerBookend = false
     }
 
+    private fun getTrackedVelocity(): Float {
+        velocityTracker.computeCurrentVelocity(UNITS_SECOND, config.maxVelocityToScale)
+        return if (velocityTracker.isAxisSupported(config.velocityAxis)) {
+            velocityTracker.getAxisVelocity(config.velocityAxis)
+        } else {
+            0f
+        }
+    }
+
     override fun onProgressJump(@FloatRange(from = 0.0, to = 1.0) progress: Float) {}
 
     override fun onSelectAndArrow(@FloatRange(from = 0.0, to = 1.0) progress: Float) {}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewConfigurator.kt b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewConfigurator.kt
index af5d48d..afef875 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewConfigurator.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewConfigurator.kt
@@ -39,12 +39,14 @@
 import com.android.systemui.keyguard.ui.view.KeyguardIndicationArea
 import com.android.systemui.keyguard.ui.view.KeyguardRootView
 import com.android.systemui.keyguard.ui.view.layout.KeyguardBlueprintCommandListener
+import com.android.systemui.keyguard.ui.viewmodel.AodAlphaViewModel
 import com.android.systemui.keyguard.ui.viewmodel.KeyguardBlueprintViewModel
 import com.android.systemui.keyguard.ui.viewmodel.KeyguardIndicationAreaViewModel
 import com.android.systemui.keyguard.ui.viewmodel.KeyguardRootViewModel
 import com.android.systemui.keyguard.ui.viewmodel.OccludingAppDeviceEntryMessageViewModel
 import com.android.systemui.plugins.FalsingManager
 import com.android.systemui.res.R
+import com.android.systemui.scene.shared.flag.SceneContainerFlag
 import com.android.systemui.shade.NotificationShadeWindowView
 import com.android.systemui.shade.domain.interactor.ShadeInteractor
 import com.android.systemui.statusbar.KeyguardIndicationController
@@ -83,6 +85,7 @@
     private val deviceEntryHapticsInteractor: DeviceEntryHapticsInteractor,
     private val vibratorHelper: VibratorHelper,
     private val falsingManager: FalsingManager,
+    private val aodAlphaViewModel: AodAlphaViewModel,
 ) : CoreStartable {
 
     private var rootViewHandle: DisposableHandle? = null
@@ -109,7 +112,9 @@
         bindKeyguardRootView()
         initializeViews()
 
-        KeyguardBlueprintViewBinder.bind(keyguardRootView, keyguardBlueprintViewModel)
+        if (!SceneContainerFlag.isEnabled) {
+            KeyguardBlueprintViewBinder.bind(keyguardRootView, keyguardBlueprintViewModel)
+        }
         keyguardBlueprintCommandListener.start()
     }
 
@@ -126,7 +131,7 @@
             KeyguardIndicationAreaBinder.bind(
                 notificationShadeWindowView.requireViewById(R.id.keyguard_indication_area),
                 keyguardIndicationAreaViewModel,
-                keyguardRootViewModel,
+                aodAlphaViewModel,
                 indicationController,
             )
     }
@@ -142,13 +147,16 @@
     }
 
     private fun bindKeyguardRootView() {
+        if (SceneContainerFlag.isEnabled) {
+            return
+        }
+
         rootViewHandle?.dispose()
         rootViewHandle =
             KeyguardRootViewBinder.bind(
                 keyguardRootView,
                 keyguardRootViewModel,
                 configuration,
-                featureFlags,
                 occludingAppDeviceEntryMessageViewModel,
                 chipbarCoordinator,
                 screenOffAnimationController,
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 31ef100..2f937bc 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
@@ -110,7 +110,7 @@
     val isKeyguardGoingAway: Flow<Boolean>
 
     /** Is the always-on display available to be used? */
-    val isAodAvailable: Flow<Boolean>
+    val isAodAvailable: StateFlow<Boolean>
 
     fun setAodAvailable(value: Boolean)
 
@@ -338,7 +338,7 @@
             .distinctUntilChanged()
 
     private val _isAodAvailable = MutableStateFlow(false)
-    override val isAodAvailable: Flow<Boolean> = _isAodAvailable.asStateFlow()
+    override val isAodAvailable: StateFlow<Boolean> = _isAodAvailable.asStateFlow()
 
     override fun setAodAvailable(value: Boolean) {
         _isAodAvailable.value = value
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
new file mode 100644
index 0000000..70c2e6d
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/FromGlanceableHubTransitionInteractor.kt
@@ -0,0 +1,52 @@
+/*
+ * 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.keyguard.domain.interactor
+
+import android.animation.ValueAnimator
+import com.android.app.animation.Interpolators
+import com.android.systemui.Flags
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.keyguard.data.repository.KeyguardTransitionRepository
+import com.android.systemui.keyguard.shared.model.KeyguardState
+import javax.inject.Inject
+import kotlin.time.Duration.Companion.milliseconds
+
+@SysUISingleton
+class FromGlanceableHubTransitionInteractor
+@Inject
+constructor(
+    override val transitionRepository: KeyguardTransitionRepository,
+    override val transitionInteractor: KeyguardTransitionInteractor,
+) : TransitionInteractor(fromState = KeyguardState.GLANCEABLE_HUB) {
+    override fun start() {
+        if (!Flags.communalHub()) {
+            return
+        }
+    }
+
+    override fun getDefaultAnimatorForTransitionsToState(toState: KeyguardState): ValueAnimator {
+        return ValueAnimator().apply {
+            interpolator = Interpolators.LINEAR
+            duration = DEFAULT_DURATION.inWholeMilliseconds
+        }
+    }
+
+    companion object {
+        const val TAG = "FromGlanceableHubTransitionInteractor"
+        val DEFAULT_DURATION = 500.milliseconds
+    }
+}
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 21651ba2..6eb3b64 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
@@ -104,7 +104,7 @@
     val dozeTimeTick: Flow<Long> = repository.dozeTimeTick
 
     /** Whether Always-on Display mode is available. */
-    val isAodAvailable: Flow<Boolean> = repository.isAodAvailable
+    val isAodAvailable: StateFlow<Boolean> = repository.isAodAvailable
 
     /** Doze transition information. */
     val dozeTransitionModel: Flow<DozeTransitionModel> = repository.dozeTransitionModel
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionCoreStartable.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionCoreStartable.kt
index ba7b987..91f8420 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionCoreStartable.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardTransitionCoreStartable.kt
@@ -42,6 +42,7 @@
                     is FromGoneTransitionInteractor -> Log.d(TAG, "Started $it")
                     is FromLockscreenTransitionInteractor -> Log.d(TAG, "Started $it")
                     is FromDreamingTransitionInteractor -> Log.d(TAG, "Started $it")
+                    is FromGlanceableHubTransitionInteractor -> Log.d(TAG, "Started $it")
                     is FromOccludedTransitionInteractor -> Log.d(TAG, "Started $it")
                     is FromDozingTransitionInteractor -> Log.d(TAG, "Started $it")
                     is FromAlternateBouncerTransitionInteractor -> Log.d(TAG, "Started $it")
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/LightRevealScrimInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/LightRevealScrimInteractor.kt
index 2d43897..fbf6936 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/LightRevealScrimInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/LightRevealScrimInteractor.kt
@@ -96,6 +96,7 @@
                 KeyguardState.AOD -> false
                 KeyguardState.DREAMING -> true
                 KeyguardState.DREAMING_LOCKSCREEN_HOSTED -> true
+                KeyguardState.GLANCEABLE_HUB -> true
                 KeyguardState.ALTERNATE_BOUNCER -> true
                 KeyguardState.PRIMARY_BOUNCER -> true
                 KeyguardState.LOCKSCREEN -> true
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/StartKeyguardTransitionModule.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/StartKeyguardTransitionModule.kt
index 56f5529..d95c38e 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/StartKeyguardTransitionModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/StartKeyguardTransitionModule.kt
@@ -67,4 +67,10 @@
     abstract fun fromAlternateBouncer(
         impl: FromAlternateBouncerTransitionInteractor
     ): TransitionInteractor
+
+    @Binds
+    @IntoSet
+    abstract fun fromGlanceableHub(
+        impl: FromGlanceableHubTransitionInteractor
+    ): TransitionInteractor
 }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/KeyguardState.kt b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/KeyguardState.kt
index f5bcab9..92612b8 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/KeyguardState.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/KeyguardState.kt
@@ -62,6 +62,12 @@
      * unlocked if SWIPE security method is used, or if face lockscreen bypass is false.
      */
     LOCKSCREEN,
+    /**
+     * Device is locked or on dream and user has swiped from the right edge to enter the glanceable
+     * hub UI. From this state, the user can swipe from the left edge to go back to the lock screen
+     * or dream, as well as swipe down for the notifications and up for the bouncer.
+     */
+    GLANCEABLE_HUB,
     /*
      * Keyguard is no longer visible. In most cases the user has just authenticated and keyguard
      * is being removed, but there are other cases where the user is swiping away keyguard, such as
@@ -95,6 +101,7 @@
                 DOZING -> false
                 DREAMING -> false
                 DREAMING_LOCKSCREEN_HOSTED -> false
+                GLANCEABLE_HUB -> true
                 AOD -> false
                 ALTERNATE_BOUNCER -> true
                 PRIMARY_BOUNCER -> true
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/KeyguardTransitionAnimationFlow.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/KeyguardTransitionAnimationFlow.kt
index 1277585..cf1d247 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/KeyguardTransitionAnimationFlow.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/KeyguardTransitionAnimationFlow.kt
@@ -32,6 +32,7 @@
 import kotlin.time.Duration.Companion.milliseconds
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.distinctUntilChanged
 import kotlinx.coroutines.flow.filterNotNull
 import kotlinx.coroutines.flow.map
 
@@ -89,7 +90,6 @@
             val start = (startTime / transitionDuration).toFloat()
             val chunks = (transitionDuration / duration).toFloat()
             logger.logCreate(name, start)
-            var isComplete = true
 
             fun stepToValue(step: TransitionStep): Float? {
                 val value = (step.value - start) * chunks
@@ -98,17 +98,13 @@
                     // middle, it is possible this animation is being skipped but we need to inform
                     // the ViewModels of the last update
                     STARTED -> {
-                        isComplete = false
                         onStart?.invoke()
                         max(0f, min(1f, value))
                     }
                     // Always send a final value of 1. Because of rounding, [value] may never be
                     // exactly 1.
                     RUNNING ->
-                        if (isComplete) {
-                            null
-                        } else if (value >= 1f) {
-                            isComplete = true
+                        if (value >= 1f) {
                             1f
                         } else if (value >= 0f) {
                             value
@@ -132,6 +128,7 @@
                     value
                 }
                 .filterNotNull()
+                .distinctUntilChanged()
         }
 
         /**
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardIndicationAreaBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardIndicationAreaBinder.kt
index 4c33d90..7c1368a 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardIndicationAreaBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardIndicationAreaBinder.kt
@@ -23,8 +23,8 @@
 import androidx.lifecycle.Lifecycle
 import androidx.lifecycle.repeatOnLifecycle
 import com.android.systemui.Flags.keyguardBottomAreaRefactor
+import com.android.systemui.keyguard.ui.viewmodel.AodAlphaViewModel
 import com.android.systemui.keyguard.ui.viewmodel.KeyguardIndicationAreaViewModel
-import com.android.systemui.keyguard.ui.viewmodel.KeyguardRootViewModel
 import com.android.systemui.lifecycle.repeatWhenAttached
 import com.android.systemui.res.R
 import com.android.systemui.statusbar.KeyguardIndicationController
@@ -51,7 +51,7 @@
     fun bind(
         view: ViewGroup,
         viewModel: KeyguardIndicationAreaViewModel,
-        keyguardRootViewModel: KeyguardRootViewModel,
+        aodAlphaViewModel: AodAlphaViewModel,
         indicationController: KeyguardIndicationController,
     ): DisposableHandle {
         indicationController.setIndicationArea(view)
@@ -69,7 +69,7 @@
                 repeatOnLifecycle(Lifecycle.State.STARTED) {
                     launch {
                         if (keyguardBottomAreaRefactor()) {
-                            keyguardRootViewModel.alpha.collect { alpha ->
+                            aodAlphaViewModel.alpha.collect { alpha ->
                                 view.apply {
                                     this.importantForAccessibility =
                                         if (alpha == 0f) {
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 fad0370..2aebd99 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
@@ -42,9 +42,9 @@
 import com.android.systemui.common.shared.model.TintedIcon
 import com.android.systemui.common.ui.ConfigurationState
 import com.android.systemui.deviceentry.domain.interactor.DeviceEntryHapticsInteractor
-import com.android.systemui.flags.FeatureFlagsClassic
 import com.android.systemui.keyguard.shared.KeyguardShadeMigrationNssl
 import com.android.systemui.keyguard.shared.model.TransitionState
+import com.android.systemui.keyguard.ui.viewmodel.BurnInParameters
 import com.android.systemui.keyguard.ui.viewmodel.KeyguardRootViewModel
 import com.android.systemui.keyguard.ui.viewmodel.OccludingAppDeviceEntryMessageViewModel
 import com.android.systemui.lifecycle.repeatWhenAttached
@@ -68,7 +68,10 @@
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.coroutineScope
 import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.flatMapLatest
 import kotlinx.coroutines.flow.stateIn
+import kotlinx.coroutines.flow.update
 import kotlinx.coroutines.launch
 
 /** Bind occludingAppDeviceEntryMessageViewModel to run whenever the keyguard view is attached. */
@@ -81,7 +84,6 @@
         view: ViewGroup,
         viewModel: KeyguardRootViewModel,
         configuration: ConfigurationState,
-        featureFlags: FeatureFlagsClassic,
         occludingAppDeviceEntryMessageViewModel: OccludingAppDeviceEntryMessageViewModel,
         chipbarCoordinator: ChipbarCoordinator,
         screenOffAnimationController: ScreenOffAnimationController,
@@ -108,6 +110,8 @@
             }
         }
 
+        val burnInParams = MutableStateFlow(BurnInParameters())
+
         val disposableHandle =
             view.repeatWhenAttached {
                 repeatOnLifecycle(Lifecycle.State.CREATED) {
@@ -164,35 +168,41 @@
                             // 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
-                            }
-                        }
-
-                        launch {
-                            viewModel.translationX.collect { x ->
-                                childViews[burnInLayerId]?.translationX = x
-                                childViews[largeClockId]?.translationX = x
-                            }
-                        }
-
-                        launch {
-                            viewModel.scale.collect { (scale, scaleClockOnly) ->
-                                if (scaleClockOnly) {
-                                    // For clocks except weather clock, we have scale transition
-                                    // besides translate
-                                    childViews[largeClockId]?.let {
-                                        it.scaleX = scale
-                                        it.scaleY = scale
-                                    }
-                                } else {
-                                    // For weather clock, large clock should have only scale
-                                    // transition with other parts in burnInLayer
-                                    childViews[burnInLayerId]?.scaleX = scale
-                                    childViews[burnInLayerId]?.scaleY = scale
+                            burnInParams
+                                .flatMapLatest { params -> viewModel.translationY(params) }
+                                .collect { y ->
+                                    childViews[burnInLayerId]?.translationY = y
+                                    childViews[largeClockId]?.translationY = y
                                 }
-                            }
+                        }
+
+                        launch {
+                            burnInParams
+                                .flatMapLatest { params -> viewModel.translationX(params) }
+                                .collect { x ->
+                                    childViews[burnInLayerId]?.translationX = x
+                                    childViews[largeClockId]?.translationX = x
+                                }
+                        }
+
+                        launch {
+                            burnInParams
+                                .flatMapLatest { params -> viewModel.scale(params) }
+                                .collect { scaleViewModel ->
+                                    if (scaleViewModel.scaleClockOnly) {
+                                        // For clocks except weather clock, we have scale transition
+                                        // besides translate
+                                        childViews[largeClockId]?.let {
+                                            it.scaleX = scaleViewModel.scale
+                                            it.scaleY = scaleViewModel.scale
+                                        }
+                                    } else {
+                                        // For weather clock, large clock should have only scale
+                                        // transition with other parts in burnInLayer
+                                        childViews[burnInLayerId]?.scaleX = scaleViewModel.scale
+                                        childViews[burnInLayerId]?.scaleY = scaleViewModel.scale
+                                    }
+                                }
                         }
 
                         if (NotificationIconContainerRefactor.isEnabled) {
@@ -274,10 +284,12 @@
             }
 
         if (!migrateClocksToBlueprint()) {
-            viewModel.clockControllerProvider = clockControllerProvider
+            burnInParams.update { current ->
+                current.copy(clockControllerProvider = clockControllerProvider)
+            }
         }
 
-        onLayoutChangeListener = OnLayoutChange(viewModel)
+        onLayoutChangeListener = OnLayoutChange(viewModel, burnInParams)
         view.addOnLayoutChangeListener(onLayoutChangeListener)
 
         // Views will be added or removed after the call to bind(). This is needed to avoid many
@@ -296,7 +308,9 @@
 
         view.setOnApplyWindowInsetsListener { v: View, insets: WindowInsets ->
             val insetTypes = WindowInsets.Type.systemBars() or WindowInsets.Type.displayCutout()
-            viewModel.topInset = insets.getInsetsIgnoringVisibility(insetTypes).top
+            burnInParams.update { current ->
+                current.copy(topInset = insets.getInsetsIgnoringVisibility(insetTypes).top)
+            }
             insets
         }
 
@@ -333,8 +347,10 @@
         )
     }
 
-    private class OnLayoutChange(private val viewModel: KeyguardRootViewModel) :
-        OnLayoutChangeListener {
+    private class OnLayoutChange(
+        private val viewModel: KeyguardRootViewModel,
+        private val burnInParams: MutableStateFlow<BurnInParameters>,
+    ) : OnLayoutChangeListener {
         override fun onLayoutChange(
             view: View,
             left: Int,
@@ -355,7 +371,7 @@
             }
 
             view.findViewById<View>(R.id.keyguard_status_view)?.let { statusView ->
-                viewModel.statusViewTop = statusView.top
+                burnInParams.update { current -> current.copy(statusViewTop = statusView.top) }
             }
         }
     }
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 03e45fd..eb3afb7 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
@@ -367,7 +367,6 @@
                     keyguardRootView,
                     keyguardRootViewModel,
                     configuration,
-                    featureFlags,
                     occludingAppDeviceEntryMessageViewModel,
                     chipbarCoordinator,
                     screenOffAnimationController,
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultIndicationAreaSection.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultIndicationAreaSection.kt
index 66c137f..ea05c1d 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultIndicationAreaSection.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultIndicationAreaSection.kt
@@ -25,8 +25,8 @@
 import com.android.systemui.keyguard.shared.model.KeyguardSection
 import com.android.systemui.keyguard.ui.binder.KeyguardIndicationAreaBinder
 import com.android.systemui.keyguard.ui.view.KeyguardIndicationArea
+import com.android.systemui.keyguard.ui.viewmodel.AodAlphaViewModel
 import com.android.systemui.keyguard.ui.viewmodel.KeyguardIndicationAreaViewModel
-import com.android.systemui.keyguard.ui.viewmodel.KeyguardRootViewModel
 import com.android.systemui.res.R
 import com.android.systemui.statusbar.KeyguardIndicationController
 import javax.inject.Inject
@@ -37,7 +37,7 @@
 constructor(
     private val context: Context,
     private val keyguardIndicationAreaViewModel: KeyguardIndicationAreaViewModel,
-    private val keyguardRootViewModel: KeyguardRootViewModel,
+    private val aodAlphaViewModel: AodAlphaViewModel,
     private val indicationController: KeyguardIndicationController,
 ) : KeyguardSection() {
     private val indicationAreaViewId = R.id.keyguard_indication_area
@@ -56,7 +56,7 @@
                 KeyguardIndicationAreaBinder.bind(
                     constraintLayout.requireViewById(R.id.keyguard_indication_area),
                     keyguardIndicationAreaViewModel,
-                    keyguardRootViewModel,
+                    aodAlphaViewModel,
                     indicationController,
                 )
         }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AodAlphaViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AodAlphaViewModel.kt
new file mode 100644
index 0000000..d4ea728
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AodAlphaViewModel.kt
@@ -0,0 +1,62 @@
+/*
+ * 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.
+ */
+
+@file:OptIn(ExperimentalCoroutinesApi::class)
+
+package com.android.systemui.keyguard.ui.viewmodel
+
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
+import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
+import com.android.systemui.keyguard.shared.model.KeyguardState
+import javax.inject.Inject
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.merge
+import kotlinx.coroutines.flow.onStart
+
+/** Models UI state for the alpha of the AOD (always-on display). */
+@SysUISingleton
+class AodAlphaViewModel
+@Inject
+constructor(
+    keyguardInteractor: KeyguardInteractor,
+    keyguardTransitionInteractor: KeyguardTransitionInteractor,
+    occludedToLockscreenTransitionViewModel: OccludedToLockscreenTransitionViewModel,
+) {
+
+    /** The alpha level for the entire lockscreen while in AOD. */
+    val alpha: Flow<Float> =
+        combine(
+                keyguardTransitionInteractor.transitionValue(KeyguardState.GONE).onStart {
+                    emit(0f)
+                },
+                merge(
+                    keyguardInteractor.keyguardAlpha,
+                    occludedToLockscreenTransitionViewModel.lockscreenAlpha,
+                )
+            ) { transitionToGone, alpha ->
+                if (transitionToGone == 1f) {
+                    // Ensures content is not visible when in GONE state
+                    0f
+                } else {
+                    alpha
+                }
+            }
+            .distinctUntilChanged()
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AodBurnInViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AodBurnInViewModel.kt
new file mode 100644
index 0000000..780e323
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/AodBurnInViewModel.kt
@@ -0,0 +1,192 @@
+/*
+ * 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.
+ */
+
+@file:OptIn(ExperimentalCoroutinesApi::class)
+
+package com.android.systemui.keyguard.ui.viewmodel
+
+import android.util.MathUtils
+import com.android.app.animation.Interpolators
+import com.android.keyguard.KeyguardClockSwitch
+import com.android.systemui.Flags
+import com.android.systemui.common.ui.domain.interactor.ConfigurationInteractor
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.keyguard.domain.interactor.BurnInInteractor
+import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
+import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
+import com.android.systemui.keyguard.shared.model.BurnInModel
+import com.android.systemui.plugins.clocks.ClockController
+import com.android.systemui.res.R
+import javax.inject.Inject
+import javax.inject.Provider
+import kotlin.math.max
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.flatMapLatest
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.merge
+import kotlinx.coroutines.flow.onStart
+
+/**
+ * Models UI state for elements that need to apply anti-burn-in tactics when showing in AOD
+ * (always-on display).
+ */
+@SysUISingleton
+class AodBurnInViewModel
+@Inject
+constructor(
+    private val burnInInteractor: BurnInInteractor,
+    private val configurationInteractor: ConfigurationInteractor,
+    private val keyguardInteractor: KeyguardInteractor,
+    private val keyguardTransitionInteractor: KeyguardTransitionInteractor,
+    private val goneToAodTransitionViewModel: GoneToAodTransitionViewModel,
+    private val occludedToLockscreenTransitionViewModel: OccludedToLockscreenTransitionViewModel,
+    private val keyguardClockViewModel: KeyguardClockViewModel,
+) {
+    /** Alpha for elements that appear and move during the animation -> AOD */
+    val alpha: Flow<Float> = goneToAodTransitionViewModel.enterFromTopAnimationAlpha
+
+    /** Horizontal translation for elements that need to apply anti-burn-in tactics. */
+    fun translationX(
+        params: BurnInParameters,
+    ): Flow<Float> {
+        return burnIn(params).map { it.translationX.toFloat() }
+    }
+
+    /** Vertical translation for elements that need to apply anti-burn-in tactics. */
+    fun translationY(
+        params: BurnInParameters,
+    ): Flow<Float> {
+        return configurationInteractor
+            .dimensionPixelSize(R.dimen.keyguard_enter_from_top_translation_y)
+            .flatMapLatest { enterFromTopAmount ->
+                combine(
+                    keyguardInteractor.keyguardTranslationY.onStart { emit(0f) },
+                    burnIn(params).map { it.translationY.toFloat() }.onStart { emit(0f) },
+                    goneToAodTransitionViewModel
+                        .enterFromTopTranslationY(enterFromTopAmount)
+                        .onStart { emit(0f) },
+                    occludedToLockscreenTransitionViewModel.lockscreenTranslationY.onStart {
+                        emit(0f)
+                    },
+                ) {
+                    keyguardTransitionY,
+                    burnInTranslationY,
+                    goneToAodTransitionTranslationY,
+                    occludedToLockscreenTransitionTranslationY ->
+
+                    // All values need to be combined for a smooth translation
+                    keyguardTransitionY +
+                        burnInTranslationY +
+                        goneToAodTransitionTranslationY +
+                        occludedToLockscreenTransitionTranslationY
+                }
+            }
+            .distinctUntilChanged()
+    }
+
+    /** Scale for elements that need to apply anti-burn-in tactics. */
+    fun scale(
+        params: BurnInParameters,
+    ): Flow<BurnInScaleViewModel> {
+        return burnIn(params).map {
+            BurnInScaleViewModel(
+                scale = it.scale,
+                scaleClockOnly = it.scaleClockOnly,
+            )
+        }
+    }
+
+    private fun burnIn(
+        params: BurnInParameters,
+    ): Flow<BurnInModel> {
+        return combine(
+            merge(
+                    keyguardTransitionInteractor.goneToAodTransition.map { it.value },
+                    keyguardTransitionInteractor.dozeAmountTransition.map { it.value },
+                )
+                .map { dozeAmount -> Interpolators.FAST_OUT_SLOW_IN.getInterpolation(dozeAmount) },
+            burnInInteractor.keyguardBurnIn,
+        ) { interpolated, burnIn ->
+            val useScaleOnly =
+                (clockController(params.clockControllerProvider)
+                    ?.get()
+                    ?.config
+                    ?.useAlternateSmartspaceAODTransition
+                    ?: false) && keyguardClockViewModel.clockSize.value == KeyguardClockSwitch.LARGE
+
+            if (useScaleOnly) {
+                BurnInModel(
+                    translationX = 0,
+                    translationY = 0,
+                    scale = MathUtils.lerp(burnIn.scale, 1f, 1f - interpolated),
+                )
+            } else {
+                // Ensure the desired translation doesn't encroach on the top inset
+                val burnInY = MathUtils.lerp(0, burnIn.translationY, interpolated).toInt()
+                val translationY =
+                    if (Flags.migrateClocksToBlueprint()) {
+                        burnInY
+                    } else {
+                        max(params.topInset, params.statusViewTop + burnInY) - params.statusViewTop
+                    }
+
+                BurnInModel(
+                    translationX = MathUtils.lerp(0, burnIn.translationX, interpolated).toInt(),
+                    translationY = translationY,
+                    scale =
+                        MathUtils.lerp(
+                            /* start= */ burnIn.scale,
+                            /* stop= */ 1f,
+                            /* amount= */ 1f - interpolated,
+                        ),
+                    scaleClockOnly = true,
+                )
+            }
+        }
+    }
+
+    private fun clockController(
+        provider: Provider<ClockController>?,
+    ): Provider<ClockController>? {
+        return if (Flags.migrateClocksToBlueprint()) {
+            Provider { keyguardClockViewModel.clock }
+        } else {
+            provider
+        }
+    }
+}
+
+/** UI-sourced parameters to pass into the various methods of [AodBurnInViewModel]. */
+data class BurnInParameters(
+    val clockControllerProvider: Provider<ClockController>? = null,
+    /** System insets that keyguard needs to stay out of */
+    val topInset: Int = 0,
+    /** Status view top, without translation added in */
+    val statusViewTop: Int = 0,
+)
+
+/**
+ * Models UI state of the scaling to apply to elements that need to be scaled for anti-burn-in
+ * purposes.
+ */
+data class BurnInScaleViewModel(
+    val scale: Float = 1f,
+    /** Whether the scale only applies to clock UI elements. */
+    val scaleClockOnly: Boolean = false,
+)
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardClockViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardClockViewModel.kt
index 528a2ee..5bb2782 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardClockViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardClockViewModel.kt
@@ -16,6 +16,7 @@
 
 package com.android.systemui.keyguard.ui.viewmodel
 
+import android.content.Context
 import androidx.constraintlayout.helper.widget.Layer
 import com.android.keyguard.KeyguardClockSwitch.LARGE
 import com.android.keyguard.KeyguardClockSwitch.SMALL
@@ -25,6 +26,9 @@
 import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
 import com.android.systemui.keyguard.shared.model.SettingsClockSize
 import com.android.systemui.plugins.clocks.ClockController
+import com.android.systemui.res.R
+import com.android.systemui.statusbar.policy.SplitShadeStateController
+import com.android.systemui.util.Utils
 import javax.inject.Inject
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.flow.SharingStarted
@@ -36,9 +40,10 @@
 class KeyguardClockViewModel
 @Inject
 constructor(
-    val keyguardInteractor: KeyguardInteractor,
-    val keyguardClockInteractor: KeyguardClockInteractor,
+    keyguardInteractor: KeyguardInteractor,
+    private val keyguardClockInteractor: KeyguardClockInteractor,
     @Application private val applicationScope: CoroutineScope,
+    private val splitShadeStateController: SplitShadeStateController,
 ) {
     var burnInLayer: Layer? = null
     val useLargeClock: Boolean
@@ -85,4 +90,43 @@
             started = SharingStarted.WhileSubscribed(),
             initialValue = false
         )
+
+    // Needs to use a non application context to get display cutout.
+    fun getSmallClockTopMargin(context: Context) =
+        if (splitShadeStateController.shouldUseSplitNotificationShade(context.resources)) {
+            context.resources.getDimensionPixelSize(R.dimen.keyguard_split_shade_top_margin)
+        } else {
+            context.resources.getDimensionPixelSize(R.dimen.keyguard_clock_top_margin) +
+                Utils.getStatusBarHeaderHeightKeyguard(context)
+        }
+
+    fun getLargeClockTopMargin(context: Context): Int {
+        var largeClockTopMargin =
+            context.resources.getDimensionPixelSize(R.dimen.status_bar_height) +
+                context.resources.getDimensionPixelSize(
+                    com.android.systemui.customization.R.dimen.small_clock_padding_top
+                ) +
+                context.resources.getDimensionPixelSize(R.dimen.keyguard_smartspace_top_offset)
+        largeClockTopMargin += getDimen(context, DATE_WEATHER_VIEW_HEIGHT)
+        largeClockTopMargin += getDimen(context, ENHANCED_SMARTSPACE_HEIGHT)
+        if (!useLargeClock) {
+            largeClockTopMargin -=
+                context.resources.getDimensionPixelSize(
+                    com.android.systemui.customization.R.dimen.small_clock_height
+                )
+        }
+
+        return largeClockTopMargin
+    }
+
+    private fun getDimen(context: Context, name: String): Int {
+        val res = context.packageManager.getResourcesForApplication(context.packageName)
+        val id = res.getIdentifier(name, "dimen", context.packageName)
+        return res.getDimensionPixelSize(id)
+    }
+
+    companion object {
+        private const val DATE_WEATHER_VIEW_HEIGHT = "date_weather_view_height"
+        private const val ENHANCED_SMARTSPACE_HEIGHT = "enhanced_smartspace_height"
+    }
 }
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 26dace0..5059e6b 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
@@ -18,27 +18,17 @@
 package com.android.systemui.keyguard.ui.viewmodel
 
 import android.graphics.Point
-import android.util.MathUtils
 import android.view.View.VISIBLE
-import com.android.app.animation.Interpolators
-import com.android.keyguard.KeyguardClockSwitch.LARGE
-import com.android.systemui.Flags.migrateClocksToBlueprint
 import com.android.systemui.Flags.newAodTransition
 import com.android.systemui.common.shared.model.NotificationContainerBounds
-import com.android.systemui.common.ui.domain.interactor.ConfigurationInteractor
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.deviceentry.domain.interactor.DeviceEntryInteractor
-import com.android.systemui.flags.FeatureFlagsClassic
-import com.android.systemui.keyguard.domain.interactor.BurnInInteractor
 import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
 import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
-import com.android.systemui.keyguard.shared.model.BurnInModel
 import com.android.systemui.keyguard.shared.model.KeyguardState
 import com.android.systemui.keyguard.shared.model.KeyguardState.AOD
 import com.android.systemui.keyguard.shared.model.KeyguardState.GONE
 import com.android.systemui.keyguard.shared.model.KeyguardState.LOCKSCREEN
-import com.android.systemui.plugins.clocks.ClockController
-import com.android.systemui.res.R
 import com.android.systemui.statusbar.notification.domain.interactor.NotificationsKeyguardInteractor
 import com.android.systemui.statusbar.phone.DozeParameters
 import com.android.systemui.statusbar.phone.ScreenOffAnimationController
@@ -49,51 +39,29 @@
 import com.android.systemui.util.ui.toAnimatedValueFlow
 import com.android.systemui.util.ui.zip
 import javax.inject.Inject
-import javax.inject.Provider
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.StateFlow
 import kotlinx.coroutines.flow.combine
 import kotlinx.coroutines.flow.distinctUntilChanged
 import kotlinx.coroutines.flow.filter
-import kotlinx.coroutines.flow.flatMapLatest
 import kotlinx.coroutines.flow.map
-import kotlinx.coroutines.flow.merge
-import kotlinx.coroutines.flow.onStart
 
 @OptIn(ExperimentalCoroutinesApi::class)
 @SysUISingleton
 class KeyguardRootViewModel
 @Inject
 constructor(
-    configurationInteractor: ConfigurationInteractor,
     private val deviceEntryInteractor: DeviceEntryInteractor,
     private val dozeParameters: DozeParameters,
     private val keyguardInteractor: KeyguardInteractor,
-    private val keyguardTransitionInteractor: KeyguardTransitionInteractor,
+    keyguardTransitionInteractor: KeyguardTransitionInteractor,
     private val notificationsKeyguardInteractor: NotificationsKeyguardInteractor,
-    private val burnInInteractor: BurnInInteractor,
-    private val keyguardClockViewModel: KeyguardClockViewModel,
-    private val goneToAodTransitionViewModel: GoneToAodTransitionViewModel,
-    private val aodToLockscreenTransitionViewModel: AodToLockscreenTransitionViewModel,
-    private val occludedToLockscreenTransitionViewModel: OccludedToLockscreenTransitionViewModel,
+    aodToLockscreenTransitionViewModel: AodToLockscreenTransitionViewModel,
     screenOffAnimationController: ScreenOffAnimationController,
-    // TODO(b/310989341): remove after changing migrate_clocks_to_blueprint to aconfig
-    private val featureFlags: FeatureFlagsClassic,
+    private val aodBurnInViewModel: AodBurnInViewModel,
+    aodAlphaViewModel: AodAlphaViewModel,
 ) {
-    var clockControllerProvider: Provider<ClockController>? = null
-        get() {
-            if (migrateClocksToBlueprint()) {
-                return Provider { keyguardClockViewModel.clock }
-            } else {
-                return field
-            }
-        }
-
-    /** System insets that keyguard needs to stay out of */
-    var topInset: Int = 0
-    /** Status view top, without translation added in */
-    var statusViewTop: Int = 0
 
     val burnInLayerVisibility: Flow<Int> =
         keyguardTransitionInteractor.startedKeyguardState
@@ -110,96 +78,25 @@
         keyguardInteractor.notificationContainerBounds
 
     /** An observable for the alpha level for the entire keyguard root view. */
-    val alpha: Flow<Float> =
-        combine(
-                keyguardTransitionInteractor.transitionValue(GONE).onStart { emit(0f) },
-                merge(
-                    keyguardInteractor.keyguardAlpha,
-                    occludedToLockscreenTransitionViewModel.lockscreenAlpha,
-                )
-            ) { transitionToGone, alpha ->
-                if (transitionToGone == 1f) {
-                    // Ensures content is not visible when in GONE state
-                    0f
-                } else {
-                    alpha
-                }
-            }
-            .distinctUntilChanged()
-
-    private fun burnIn(): Flow<BurnInModel> {
-        val dozingAmount: Flow<Float> =
-            merge(
-                keyguardTransitionInteractor.goneToAodTransition.map { it.value },
-                keyguardTransitionInteractor.dozeAmountTransition.map { it.value },
-            )
-
-        return combine(dozingAmount, burnInInteractor.keyguardBurnIn) { dozeAmount, burnIn ->
-            val interpolation = Interpolators.FAST_OUT_SLOW_IN.getInterpolation(dozeAmount)
-            val useScaleOnly =
-                (clockControllerProvider?.get()?.config?.useAlternateSmartspaceAODTransition
-                    ?: false) && keyguardClockViewModel.clockSize.value == LARGE
-            if (useScaleOnly) {
-                BurnInModel(
-                    translationX = 0,
-                    translationY = 0,
-                    scale = MathUtils.lerp(burnIn.scale, 1f, 1f - interpolation),
-                )
-            } else {
-                // Ensure the desired translation doesn't encroach on the top inset
-                val burnInY = MathUtils.lerp(0, burnIn.translationY, interpolation).toInt()
-                val translationY =
-                    if (migrateClocksToBlueprint()) {
-                        burnInY
-                    } else {
-                        -(statusViewTop - Math.max(topInset, statusViewTop + burnInY))
-                    }
-                BurnInModel(
-                    translationX = MathUtils.lerp(0, burnIn.translationX, interpolation).toInt(),
-                    translationY = translationY,
-                    scale = MathUtils.lerp(burnIn.scale, 1f, 1f - interpolation),
-                    scaleClockOnly = true,
-                )
-            }
-        }
-    }
+    val alpha: Flow<Float> = aodAlphaViewModel.alpha
 
     /** Specific alpha value for elements visible during [KeyguardState.LOCKSCREEN] */
     val lockscreenStateAlpha: Flow<Float> = aodToLockscreenTransitionViewModel.lockscreenAlpha
 
     /** For elements that appear and move during the animation -> AOD */
-    val burnInLayerAlpha: Flow<Float> = goneToAodTransitionViewModel.enterFromTopAnimationAlpha
+    val burnInLayerAlpha: Flow<Float> = aodBurnInViewModel.alpha
 
-    val translationY: Flow<Float> =
-        configurationInteractor
-            .dimensionPixelSize(R.dimen.keyguard_enter_from_top_translation_y)
-            .flatMapLatest { enterFromTopAmount ->
-                combine(
-                    keyguardInteractor.keyguardTranslationY.onStart { emit(0f) },
-                    burnIn().map { it.translationY.toFloat() }.onStart { emit(0f) },
-                    goneToAodTransitionViewModel
-                        .enterFromTopTranslationY(enterFromTopAmount)
-                        .onStart { emit(0f) },
-                    occludedToLockscreenTransitionViewModel.lockscreenTranslationY.onStart {
-                        emit(0f)
-                    },
-                ) {
-                    keyguardTransitionY,
-                    burnInTranslationY,
-                    goneToAodTransitionTranslationY,
-                    occludedToLockscreenTransitionTranslationY ->
-                    // All values need to be combined for a smooth translation
-                    keyguardTransitionY +
-                        burnInTranslationY +
-                        goneToAodTransitionTranslationY +
-                        occludedToLockscreenTransitionTranslationY
-                }
-            }
-            .distinctUntilChanged()
+    fun translationY(params: BurnInParameters): Flow<Float> {
+        return aodBurnInViewModel.translationY(params)
+    }
 
-    val translationX: Flow<Float> = burnIn().map { it.translationX.toFloat() }
+    fun translationX(params: BurnInParameters): Flow<Float> {
+        return aodBurnInViewModel.translationX(params)
+    }
 
-    val scale: Flow<Pair<Float, Boolean>> = burnIn().map { Pair(it.scale, it.scaleClockOnly) }
+    fun scale(params: BurnInParameters): Flow<BurnInScaleViewModel> {
+        return aodBurnInViewModel.scale(params)
+    }
 
     /** Is the notification icon container visible? */
     val isNotifIconContainerVisible: Flow<AnimatedValue<Boolean>> =
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModel.kt
index 539db7f..2b28a71 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/LockscreenSceneViewModel.kt
@@ -38,7 +38,6 @@
     deviceEntryInteractor: DeviceEntryInteractor,
     communalInteractor: CommunalInteractor,
     val longPress: KeyguardLongPressViewModel,
-    val keyguardRoot: KeyguardRootViewModel,
     val notifications: NotificationsPlaceholderViewModel,
 ) {
     /** The key of the scene we should switch to when swiping up. */
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/SideFpsProgressBarViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/SideFpsProgressBarViewModel.kt
index 1dbf1f1..693e3b7 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/SideFpsProgressBarViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/SideFpsProgressBarViewModel.kt
@@ -28,13 +28,16 @@
 import com.android.systemui.biometrics.shared.model.isDefaultOrientation
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Application
-import com.android.systemui.keyguard.data.repository.DeviceEntryFingerprintAuthRepository
+import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.deviceentry.domain.interactor.DeviceEntryFingerprintAuthInteractor
 import com.android.systemui.keyguard.shared.model.AcquiredFingerprintAuthenticationStatus
 import com.android.systemui.keyguard.shared.model.ErrorFingerprintAuthenticationStatus
 import com.android.systemui.keyguard.shared.model.FailFingerprintAuthenticationStatus
 import com.android.systemui.keyguard.shared.model.SuccessFingerprintAuthenticationStatus
 import com.android.systemui.res.R
+import com.android.systemui.statusbar.phone.DozeServiceHost
 import javax.inject.Inject
+import kotlinx.coroutines.CoroutineDispatcher
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.Job
 import kotlinx.coroutines.flow.Flow
@@ -43,6 +46,7 @@
 import kotlinx.coroutines.flow.collectLatest
 import kotlinx.coroutines.flow.combine
 import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.flowOn
 import kotlinx.coroutines.flow.launchIn
 import kotlinx.coroutines.flow.map
 import kotlinx.coroutines.flow.onCompletion
@@ -54,9 +58,13 @@
 @Inject
 constructor(
     private val context: Context,
-    private val fpAuthRepository: DeviceEntryFingerprintAuthRepository,
+    private val fpAuthRepository: DeviceEntryFingerprintAuthInteractor,
     private val sfpsSensorInteractor: SideFpsSensorInteractor,
+    // todo (b/317432075) Injecting DozeServiceHost directly instead of using it through
+    //  DozeInteractor as DozeServiceHost already depends on DozeInteractor.
+    private val dozeServiceHost: DozeServiceHost,
     displayStateInteractor: DisplayStateInteractor,
+    @Main private val mainDispatcher: CoroutineDispatcher,
     @Application private val applicationScope: CoroutineScope,
 ) {
     private val _progress = MutableStateFlow(0.0f)
@@ -168,18 +176,21 @@
                     return@collectLatest
                 }
                 animatorJob =
-                    fpAuthRepository.authenticationStatus
-                        .onEach { authStatus ->
+                    combine(
+                            sfpsSensorInteractor.authenticationDuration,
+                            fpAuthRepository.authenticationStatus,
+                            ::Pair
+                        )
+                        .onEach { (authDuration, authStatus) ->
                             when (authStatus) {
                                 is AcquiredFingerprintAuthenticationStatus -> {
                                     if (authStatus.fingerprintCaptureStarted) {
                                         _visible.value = true
+                                        dozeServiceHost.fireSideFpsAcquisitionStarted()
                                         _animator?.cancel()
                                         _animator =
                                             ValueAnimator.ofFloat(0.0f, 1.0f)
-                                                .setDuration(
-                                                    sfpsSensorInteractor.authenticationDuration
-                                                )
+                                                .setDuration(authDuration)
                                                 .apply {
                                                     addUpdateListener {
                                                         _progress.value = it.animatedValue as Float
@@ -209,6 +220,7 @@
                                 else -> Unit
                             }
                         }
+                        .flowOn(mainDispatcher)
                         .onCompletion { _animator?.cancel() }
                         .launchIn(applicationScope)
             }
diff --git a/packages/SystemUI/src/com/android/systemui/log/SideFpsLogger.kt b/packages/SystemUI/src/com/android/systemui/log/SideFpsLogger.kt
index 919072a..171656a 100644
--- a/packages/SystemUI/src/com/android/systemui/log/SideFpsLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/log/SideFpsLogger.kt
@@ -108,4 +108,13 @@
             }
         )
     }
+
+    fun authDurationChanged(duration: Long) {
+        buffer.log(
+            TAG,
+            LogLevel.DEBUG,
+            { long1 = duration },
+            { "SideFpsSensor auth duration changed: $long1" }
+        )
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java b/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java
index 0d5ba64..1e67771 100644
--- a/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java
+++ b/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java
@@ -18,6 +18,7 @@
 
 import android.os.Build;
 
+import com.android.systemui.common.data.repository.PackageChangeRepository;
 import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.log.LogBuffer;
 import com.android.systemui.log.LogBufferFactory;
@@ -600,4 +601,12 @@
     public static LogBuffer provideQBluetoothTileDialogLogBuffer(LogBufferFactory factory) {
         return factory.create("BluetoothTileDialogLog", 50);
     }
+
+    /** Provides a {@link LogBuffer} for {@link PackageChangeRepository} */
+    @Provides
+    @SysUISingleton
+    @PackageChangeRepoLog
+    public static LogBuffer providePackageChangeRepoLogBuffer(LogBufferFactory factory) {
+        return factory.create("PackageChangeRepo", 50);
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/di/bound/CustomTileUser.kt b/packages/SystemUI/src/com/android/systemui/log/dagger/PackageChangeRepoLog.kt
similarity index 73%
rename from packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/di/bound/CustomTileUser.kt
rename to packages/SystemUI/src/com/android/systemui/log/dagger/PackageChangeRepoLog.kt
index efc7431..93b776c 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/di/bound/CustomTileUser.kt
+++ b/packages/SystemUI/src/com/android/systemui/log/dagger/PackageChangeRepoLog.kt
@@ -14,12 +14,14 @@
  * limitations under the License.
  */
 
-package com.android.systemui.qs.tiles.impl.custom.di.bound
+package com.android.systemui.log.dagger
 
+import com.android.systemui.common.data.repository.PackageChangeRepository
+import com.android.systemui.log.LogBuffer
 import javax.inject.Qualifier
 
-/** User associated with current custom tile binding. */
+/** A [LogBuffer] for [PackageChangeRepository]. */
 @Qualifier
 @MustBeDocumented
 @Retention(AnnotationRetention.RUNTIME)
-annotation class CustomTileUser
+annotation class PackageChangeRepoLog
diff --git a/packages/SystemUI/src/com/android/systemui/power/PowerNotificationWarnings.java b/packages/SystemUI/src/com/android/systemui/power/PowerNotificationWarnings.java
index a6c6233..7e06f5a 100644
--- a/packages/SystemUI/src/com/android/systemui/power/PowerNotificationWarnings.java
+++ b/packages/SystemUI/src/com/android/systemui/power/PowerNotificationWarnings.java
@@ -87,6 +87,7 @@
 import java.util.Objects;
 
 import javax.inject.Inject;
+import javax.inject.Provider;
 
 /**
  */
@@ -149,6 +150,7 @@
     public static final String EXTRA_CONFIRM_ONLY = "extra_confirm_only";
 
     private final Context mContext;
+    private final SystemUIDialog.Factory mSystemUIDialogFactory;
     private final NotificationManager mNoMan;
     private final PowerManager mPowerMan;
     private final KeyguardManager mKeyguard;
@@ -186,11 +188,17 @@
     /**
      */
     @Inject
-    public PowerNotificationWarnings(Context context, ActivityStarter activityStarter,
-            BroadcastSender broadcastSender, Lazy<BatteryController> batteryControllerLazy,
-            DialogLaunchAnimator dialogLaunchAnimator, UiEventLogger uiEventLogger,
-            GlobalSettings globalSettings, UserTracker userTracker) {
+    public PowerNotificationWarnings(
+            Context context,
+            ActivityStarter activityStarter,
+            BroadcastSender broadcastSender,
+            Lazy<BatteryController> batteryControllerLazy,
+            DialogLaunchAnimator dialogLaunchAnimator,
+            UiEventLogger uiEventLogger,
+            UserTracker userTracker,
+            SystemUIDialog.Factory systemUIDialogFactory) {
         mContext = context;
+        mSystemUIDialogFactory = systemUIDialogFactory;
         mNoMan = mContext.getSystemService(NotificationManager.class);
         mPowerMan = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
         mKeyguard = mContext.getSystemService(KeyguardManager.class);
@@ -444,7 +452,7 @@
 
     private void showHighTemperatureDialog() {
         if (mHighTempDialog != null) return;
-        final SystemUIDialog d = new SystemUIDialog(mContext);
+        final SystemUIDialog d = mSystemUIDialogFactory.create();
         d.setIconAttribute(android.R.attr.alertDialogIcon);
         d.setTitle(R.string.high_temp_title);
         d.setMessage(R.string.high_temp_dialog_message);
@@ -479,7 +487,7 @@
 
     private void showThermalShutdownDialog() {
         if (mThermalShutdownDialog != null) return;
-        final SystemUIDialog d = new SystemUIDialog(mContext);
+        final SystemUIDialog d = mSystemUIDialogFactory.create();
         d.setIconAttribute(android.R.attr.alertDialogIcon);
         d.setTitle(R.string.thermal_shutdown_title);
         d.setMessage(R.string.thermal_shutdown_dialog_message);
@@ -643,7 +651,7 @@
 
     private void showStartSaverConfirmation(Bundle extras) {
         if (mSaverConfirmation != null || mUseExtraSaverConfirmation) return;
-        final SystemUIDialog d = new SystemUIDialog(mContext);
+        final SystemUIDialog d = mSystemUIDialogFactory.create();
         final boolean confirmOnly = extras.getBoolean(BatterySaverUtils.EXTRA_CONFIRM_TEXT_ONLY);
         final int batterySaverTriggerMode =
                 extras.getInt(BatterySaverUtils.EXTRA_POWER_SAVE_MODE_TRIGGER,
diff --git a/packages/SystemUI/src/com/android/systemui/power/shared/model/WakeSleepReason.kt b/packages/SystemUI/src/com/android/systemui/power/shared/model/WakeSleepReason.kt
index faf9fbe..75055668 100644
--- a/packages/SystemUI/src/com/android/systemui/power/shared/model/WakeSleepReason.kt
+++ b/packages/SystemUI/src/com/android/systemui/power/shared/model/WakeSleepReason.kt
@@ -51,7 +51,10 @@
     BIOMETRIC(isTouch = false, PowerManager.WAKE_REASON_BIOMETRIC),
 
     /** Something else happened to wake up or sleep the device. */
-    OTHER(isTouch = false, PowerManager.WAKE_REASON_UNKNOWN);
+    OTHER(isTouch = false, PowerManager.WAKE_REASON_UNKNOWN),
+
+    /** Device goes to sleep due to folding of a foldable device. */
+    FOLD(isTouch = false, PowerManager.GO_TO_SLEEP_REASON_DEVICE_FOLD);
 
     companion object {
         fun fromPowerManagerWakeReason(reason: Int): WakeSleepReason {
@@ -72,6 +75,7 @@
         fun fromPowerManagerSleepReason(reason: Int): WakeSleepReason {
             return when (reason) {
                 PowerManager.GO_TO_SLEEP_REASON_POWER_BUTTON -> POWER_BUTTON
+                PowerManager.GO_TO_SLEEP_REASON_DEVICE_FOLD -> FOLD
                 else -> OTHER
             }
         }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/FgsManagerController.kt b/packages/SystemUI/src/com/android/systemui/qs/FgsManagerController.kt
index 6f35cfb..b5def41 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/FgsManagerController.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/FgsManagerController.kt
@@ -148,7 +148,8 @@
     private val deviceConfigProxy: DeviceConfigProxy,
     private val dialogLaunchAnimator: DialogLaunchAnimator,
     private val broadcastDispatcher: BroadcastDispatcher,
-    private val dumpManager: DumpManager
+    private val dumpManager: DumpManager,
+    private val systemUIDialogFactory: SystemUIDialog.Factory,
 ) : Dumpable, FgsManagerController {
 
     companion object {
@@ -375,7 +376,7 @@
     override fun showDialog(expandable: Expandable?) {
         synchronized(lock) {
             if (dialog == null) {
-                val dialog = SystemUIDialog(context)
+                val dialog = systemUIDialogFactory.create()
                 dialog.setTitle(R.string.fgs_manager_dialog_title)
                 dialog.setMessage(R.string.fgs_manager_dialog_message)
 
diff --git a/packages/SystemUI/src/com/android/systemui/qs/customize/TileQueryHelper.java b/packages/SystemUI/src/com/android/systemui/qs/customize/TileQueryHelper.java
index 47b0624..a45d6f6 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/customize/TileQueryHelper.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/customize/TileQueryHelper.java
@@ -259,7 +259,11 @@
     private State getState(Collection<QSTile> tiles, String spec) {
         for (QSTile tile : tiles) {
             if (spec.equals(tile.getTileSpec())) {
-                return tile.getState().copy();
+                if (tile.isTileReady()) {
+                    return tile.getState().copy();
+                } else {
+                    return null;
+                }
             }
         }
         return 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 5d28c8c..957cb1e 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
@@ -319,7 +319,7 @@
 
     override fun dumpProto(systemUIProtoDump: SystemUIProtoDump, args: Array<String>) {
         val data =
-            currentTiles.value.map { it.tile.state }.mapNotNull { it.toProto() }.toTypedArray()
+            currentTiles.value.map { it.tile.state }.mapNotNull { it?.toProto() }.toTypedArray()
         systemUIProtoDump.tiles = data
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/DataSaverTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/DataSaverTile.java
index ccf7afb..c9b0022 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/DataSaverTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/DataSaverTile.java
@@ -55,6 +55,7 @@
 
     private final DataSaverController mDataSaverController;
     private final DialogLaunchAnimator mDialogLaunchAnimator;
+    private final SystemUIDialog.Factory mSystemUIDialogFactory;
 
     @Inject
     public DataSaverTile(
@@ -68,12 +69,14 @@
             ActivityStarter activityStarter,
             QSLogger qsLogger,
             DataSaverController dataSaverController,
-            DialogLaunchAnimator dialogLaunchAnimator
+            DialogLaunchAnimator dialogLaunchAnimator,
+            SystemUIDialog.Factory systemUIDialogFactory
     ) {
         super(host, uiEventLogger, backgroundLooper, mainHandler, falsingManager, metricsLogger,
                 statusBarStateController, activityStarter, qsLogger);
         mDataSaverController = dataSaverController;
         mDialogLaunchAnimator = dialogLaunchAnimator;
+        mSystemUIDialogFactory = systemUIDialogFactory;
         mDataSaverController.observe(getLifecycle(), this);
     }
 
@@ -98,7 +101,7 @@
         // Show a dialog to confirm first. Dialogs shown by the DialogLaunchAnimator must be created
         // and shown on the main thread, so we post it to the UI handler.
         mUiHandler.post(() -> {
-            SystemUIDialog dialog = new SystemUIDialog(mContext);
+            SystemUIDialog dialog = mSystemUIDialogFactory.create();
             dialog.setTitle(com.android.internal.R.string.data_saver_enable_title);
             dialog.setMessage(com.android.internal.R.string.data_saver_description);
             dialog.setPositiveButton(com.android.internal.R.string.data_saver_enable_button,
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/base/actions/QSTileIntentUserInputHandler.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/base/actions/QSTileIntentUserInputHandler.kt
index 840db26..fc06090 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/base/actions/QSTileIntentUserInputHandler.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/base/actions/QSTileIntentUserInputHandler.kt
@@ -63,6 +63,10 @@
                     InteractionJankMonitor.CUJ_SHADE_APP_LAUNCH_FROM_QS_TILE,
                 )
             }
-        activityStarter.postStartActivityDismissingKeyguard(pendingIntent, animationController)
+        activityStarter.startPendingIntentMaybeDismissingKeyguard(
+            pendingIntent,
+            null,
+            animationController
+        )
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/base/logging/QSTileLogger.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/base/logging/QSTileLogger.kt
index 0a9a6d3..bc016bd 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/base/logging/QSTileLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/base/logging/QSTileLogger.kt
@@ -158,6 +158,33 @@
             )
     }
 
+    fun logError(
+        tileSpec: TileSpec,
+        message: String,
+        error: Throwable,
+    ) {
+        tileSpec
+            .getLogBuffer()
+            .log(
+                tileSpec.getLogTag(),
+                LogLevel.ERROR,
+                {},
+                { message },
+                error,
+            )
+    }
+
+    fun logCustomTileUserActionDelivered(tileSpec: TileSpec) {
+        tileSpec
+            .getLogBuffer()
+            .log(
+                tileSpec.getLogTag(),
+                LogLevel.DEBUG,
+                {},
+                { "user action delivered to the service" },
+            )
+    }
+
     private fun TileSpec.getLogTag(): String = "${TAG_FORMAT_PREFIX}_${this.spec}"
 
     private fun TileSpec.getLogBuffer(): LogBuffer =
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/di/NewQSTileFactory.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/di/NewQSTileFactory.kt
index 382cfe2..6c9a8a4 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/di/NewQSTileFactory.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/di/NewQSTileFactory.kt
@@ -56,7 +56,7 @@
     override fun createTile(tileSpec: String): QSTile? {
         val viewModel: QSTileViewModel =
             when (val spec = TileSpec.create(tileSpec)) {
-                is TileSpec.CustomTileSpec -> null
+                is TileSpec.CustomTileSpec -> createCustomTileViewModel(spec)
                 is TileSpec.PlatformTileSpec -> tileMap[tileSpec]?.get()
                 is TileSpec.Invalid -> null
             }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/CustomTileInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/CustomTileInteractor.kt
deleted file mode 100644
index 14bf25d..0000000
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/CustomTileInteractor.kt
+++ /dev/null
@@ -1,40 +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.qs.tiles.impl.custom
-
-import android.os.UserHandle
-import com.android.systemui.qs.tiles.base.interactor.DataUpdateTrigger
-import com.android.systemui.qs.tiles.base.interactor.QSTileDataInteractor
-import com.android.systemui.qs.tiles.impl.custom.domain.entity.CustomTileDataModel
-import com.android.systemui.qs.tiles.impl.di.QSTileScope
-import javax.inject.Inject
-import kotlinx.coroutines.flow.Flow
-
-@QSTileScope
-class CustomTileInteractor @Inject constructor() : QSTileDataInteractor<CustomTileDataModel> {
-
-    override fun tileData(
-        user: UserHandle,
-        triggers: Flow<DataUpdateTrigger>
-    ): Flow<CustomTileDataModel> {
-        TODO("Not yet implemented")
-    }
-
-    override fun availability(user: UserHandle): Flow<Boolean> {
-        TODO("Not yet implemented")
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/CustomTileMapper.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/CustomTileMapper.kt
deleted file mode 100644
index e23a5c2..0000000
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/CustomTileMapper.kt
+++ /dev/null
@@ -1,32 +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.qs.tiles.impl.custom
-
-import com.android.systemui.qs.tiles.base.interactor.QSTileDataToStateMapper
-import com.android.systemui.qs.tiles.impl.custom.domain.entity.CustomTileDataModel
-import com.android.systemui.qs.tiles.impl.di.QSTileScope
-import com.android.systemui.qs.tiles.viewmodel.QSTileConfig
-import com.android.systemui.qs.tiles.viewmodel.QSTileState
-import javax.inject.Inject
-
-@QSTileScope
-class CustomTileMapper @Inject constructor() : QSTileDataToStateMapper<CustomTileDataModel> {
-
-    override fun map(config: QSTileConfig, data: CustomTileDataModel): QSTileState {
-        TODO("Not yet implemented")
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/CustomTileUserActionInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/CustomTileUserActionInteractor.kt
deleted file mode 100644
index f34704b..0000000
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/CustomTileUserActionInteractor.kt
+++ /dev/null
@@ -1,32 +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.qs.tiles.impl.custom
-
-import com.android.systemui.qs.tiles.base.interactor.QSTileInput
-import com.android.systemui.qs.tiles.base.interactor.QSTileUserActionInteractor
-import com.android.systemui.qs.tiles.impl.custom.domain.entity.CustomTileDataModel
-import com.android.systemui.qs.tiles.impl.di.QSTileScope
-import javax.inject.Inject
-
-@QSTileScope
-class CustomTileUserActionInteractor @Inject constructor() :
-    QSTileUserActionInteractor<CustomTileDataModel> {
-
-    override suspend fun handleInput(input: QSTileInput<CustomTileDataModel>) {
-        TODO("Not yet implemented")
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/di/CustomTileComponent.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/di/CustomTileComponent.kt
index 88bc8fa..7b099c2 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/di/CustomTileComponent.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/di/CustomTileComponent.kt
@@ -16,7 +16,10 @@
 
 package com.android.systemui.qs.tiles.impl.custom.di
 
+import com.android.systemui.qs.tiles.impl.custom.data.repository.CustomTilePackageUpdatesRepository
 import com.android.systemui.qs.tiles.impl.custom.domain.entity.CustomTileDataModel
+import com.android.systemui.qs.tiles.impl.custom.domain.interactor.CustomTileInteractor
+import com.android.systemui.qs.tiles.impl.custom.domain.interactor.CustomTileServiceInteractor
 import com.android.systemui.qs.tiles.impl.di.QSTileComponent
 import com.android.systemui.qs.tiles.impl.di.QSTileScope
 import dagger.Subcomponent
@@ -25,6 +28,12 @@
 @Subcomponent(modules = [QSTileConfigModule::class, CustomTileModule::class])
 interface CustomTileComponent : QSTileComponent<CustomTileDataModel> {
 
+    fun customTileInterfaceInteractor(): CustomTileServiceInteractor
+
+    fun customTileInteractor(): CustomTileInteractor
+
+    fun customTilePackageUpdatesRepository(): CustomTilePackageUpdatesRepository
+
     @Subcomponent.Builder
     interface Builder {
 
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/di/CustomTileModule.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/di/CustomTileModule.kt
index ba8b23a..196fa12 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/di/CustomTileModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/di/CustomTileModule.kt
@@ -20,14 +20,16 @@
 import com.android.systemui.qs.tiles.base.interactor.QSTileDataToStateMapper
 import com.android.systemui.qs.tiles.base.interactor.QSTileUserActionInteractor
 import com.android.systemui.qs.tiles.base.viewmodel.QSTileCoroutineScopeFactory
-import com.android.systemui.qs.tiles.impl.custom.CustomTileInteractor
-import com.android.systemui.qs.tiles.impl.custom.CustomTileMapper
-import com.android.systemui.qs.tiles.impl.custom.CustomTileUserActionInteractor
 import com.android.systemui.qs.tiles.impl.custom.data.repository.CustomTileDefaultsRepository
 import com.android.systemui.qs.tiles.impl.custom.data.repository.CustomTileDefaultsRepositoryImpl
+import com.android.systemui.qs.tiles.impl.custom.data.repository.CustomTilePackageUpdatesRepository
+import com.android.systemui.qs.tiles.impl.custom.data.repository.CustomTilePackageUpdatesRepositoryImpl
 import com.android.systemui.qs.tiles.impl.custom.data.repository.CustomTileRepository
 import com.android.systemui.qs.tiles.impl.custom.data.repository.CustomTileRepositoryImpl
+import com.android.systemui.qs.tiles.impl.custom.domain.CustomTileMapper
 import com.android.systemui.qs.tiles.impl.custom.domain.entity.CustomTileDataModel
+import com.android.systemui.qs.tiles.impl.custom.domain.interactor.CustomTileDataInteractor
+import com.android.systemui.qs.tiles.impl.custom.domain.interactor.CustomTileUserActionInteractor
 import com.android.systemui.qs.tiles.impl.di.QSTileScope
 import dagger.Binds
 import dagger.Module
@@ -40,7 +42,7 @@
 
     @Binds
     fun bindDataInteractor(
-        dataInteractor: CustomTileInteractor
+        dataInteractor: CustomTileDataInteractor
     ): QSTileDataInteractor<CustomTileDataModel>
 
     @Binds
@@ -58,6 +60,11 @@
 
     @Binds fun bindCustomTileRepository(impl: CustomTileRepositoryImpl): CustomTileRepository
 
+    @Binds
+    abstract fun bindCustomTilePackageUpdatesRepository(
+        impl: CustomTilePackageUpdatesRepositoryImpl
+    ): CustomTilePackageUpdatesRepository
+
     companion object {
 
         @Provides
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/di/bound/CustomTileBoundComponent.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/di/bound/CustomTileBoundComponent.kt
deleted file mode 100644
index d382d20..0000000
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/di/bound/CustomTileBoundComponent.kt
+++ /dev/null
@@ -1,36 +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.qs.tiles.impl.custom.di.bound
-
-import android.os.UserHandle
-import dagger.BindsInstance
-import dagger.Subcomponent
-import kotlinx.coroutines.CoroutineScope
-
-/** @see CustomTileBoundScope */
-@CustomTileBoundScope
-@Subcomponent(modules = [CustomTileBoundModule::class])
-interface CustomTileBoundComponent {
-
-    @Subcomponent.Builder
-    interface Builder {
-        @BindsInstance fun user(@CustomTileUser user: UserHandle): Builder
-        @BindsInstance fun coroutineScope(@CustomTileBoundScope scope: CoroutineScope): Builder
-
-        fun build(): CustomTileBoundComponent
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/di/bound/CustomTileBoundModule.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/di/bound/CustomTileBoundModule.kt
deleted file mode 100644
index 889424a..0000000
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/di/bound/CustomTileBoundModule.kt
+++ /dev/null
@@ -1,31 +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.qs.tiles.impl.custom.di.bound
-
-import com.android.systemui.qs.tiles.impl.custom.data.repository.CustomTilePackageUpdatesRepository
-import com.android.systemui.qs.tiles.impl.custom.data.repository.CustomTilePackageUpdatesRepositoryImpl
-import dagger.Binds
-import dagger.Module
-
-@Module
-interface CustomTileBoundModule {
-
-    @Binds
-    fun bindCustomTilePackageUpdatesRepository(
-        impl: CustomTilePackageUpdatesRepositoryImpl
-    ): CustomTilePackageUpdatesRepository
-}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/di/bound/CustomTileBoundScope.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/di/bound/CustomTileBoundScope.kt
deleted file mode 100644
index 4a4ba2b..0000000
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/di/bound/CustomTileBoundScope.kt
+++ /dev/null
@@ -1,29 +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.qs.tiles.impl.custom.di.bound
-
-import javax.inject.Scope
-
-/**
- * Scope annotation for bound custom tile scope. This scope lives when a particular
- * [com.android.systemui.qs.external.CustomTile] is listening and bound to the
- * [android.service.quicksettings.TileService].
- */
-@MustBeDocumented
-@Retention(AnnotationRetention.RUNTIME)
-@Scope
-annotation class CustomTileBoundScope
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/domain/CustomTileMapper.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/domain/CustomTileMapper.kt
new file mode 100644
index 0000000..875079c
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/domain/CustomTileMapper.kt
@@ -0,0 +1,130 @@
+/*
+ * 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.qs.tiles.impl.custom.domain
+
+import android.annotation.SuppressLint
+import android.app.IUriGrantsManager
+import android.content.Context
+import android.graphics.drawable.Drawable
+import android.os.UserHandle
+import android.service.quicksettings.Tile
+import android.widget.Button
+import android.widget.Switch
+import com.android.systemui.common.shared.model.Icon
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.qs.tiles.base.interactor.QSTileDataToStateMapper
+import com.android.systemui.qs.tiles.impl.custom.domain.entity.CustomTileDataModel
+import com.android.systemui.qs.tiles.viewmodel.QSTileConfig
+import com.android.systemui.qs.tiles.viewmodel.QSTileState
+import javax.inject.Inject
+
+@SysUISingleton
+class CustomTileMapper
+@Inject
+constructor(
+    private val context: Context,
+    private val uriGrantsManager: IUriGrantsManager,
+) : QSTileDataToStateMapper<CustomTileDataModel> {
+
+    override fun map(config: QSTileConfig, data: CustomTileDataModel): QSTileState {
+        val userContext = context.createContextAsUser(UserHandle(data.user.identifier), 0)
+
+        val iconResult =
+            getIconProvider(
+                userContext = userContext,
+                icon = data.tile.icon,
+                callingAppUid = data.callingAppUid,
+                packageName = data.componentName.packageName,
+                defaultIcon = data.defaultTileIcon,
+            )
+
+        return QSTileState.build(iconResult.iconProvider, data.tile.label) {
+            var tileState: Int = data.tile.state
+            if (data.hasPendingBind) {
+                tileState = Tile.STATE_UNAVAILABLE
+            }
+
+            icon = iconResult.iconProvider
+            activationState =
+                if (iconResult.failedToLoad) {
+                    QSTileState.ActivationState.INACTIVE
+                } else {
+                    QSTileState.ActivationState.valueOf(tileState)
+                }
+
+            if (!data.tile.subtitle.isNullOrEmpty()) {
+                secondaryLabel = data.tile.subtitle
+            }
+
+            contentDescription = data.tile.contentDescription
+            stateDescription = data.tile.stateDescription
+
+            if (!data.isToggleable) {
+                sideViewIcon = QSTileState.SideViewIcon.Chevron
+            }
+
+            supportedActions =
+                if (tileState == Tile.STATE_UNAVAILABLE) {
+                    setOf(QSTileState.UserAction.LONG_CLICK)
+                } else {
+                    setOf(QSTileState.UserAction.CLICK, QSTileState.UserAction.LONG_CLICK)
+                }
+            expandedAccessibilityClass =
+                if (data.isToggleable) {
+                    Switch::class
+                } else {
+                    Button::class
+                }
+        }
+    }
+
+    @SuppressLint("MissingPermission") // android.permission.INTERACT_ACROSS_USERS_FULL
+    private fun getIconProvider(
+        userContext: Context,
+        icon: android.graphics.drawable.Icon?,
+        callingAppUid: Int,
+        packageName: String,
+        defaultIcon: android.graphics.drawable.Icon?,
+    ): IconResult {
+        var failedToLoad = false
+        val drawable: Drawable? =
+            try {
+                icon?.loadDrawableCheckingUriGrant(
+                    userContext,
+                    uriGrantsManager,
+                    callingAppUid,
+                    packageName,
+                )
+            } catch (e: Exception) {
+                failedToLoad = true
+                null
+            } ?: defaultIcon?.loadDrawable(userContext)
+        return IconResult(
+            {
+                drawable?.constantState?.newDrawable()?.let {
+                    Icon.Loaded(it, contentDescription = null)
+                }
+            },
+            failedToLoad,
+        )
+    }
+
+    class IconResult(
+        val iconProvider: () -> Icon?,
+        val failedToLoad: Boolean,
+    )
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/domain/entity/CustomTileDataModel.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/domain/entity/CustomTileDataModel.kt
index f095c01..5b6ff1e 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/domain/entity/CustomTileDataModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/domain/entity/CustomTileDataModel.kt
@@ -20,16 +20,14 @@
 import android.graphics.drawable.Icon
 import android.os.UserHandle
 import android.service.quicksettings.Tile
-import com.android.systemui.qs.tiles.impl.custom.di.bound.CustomTileBoundComponent
 
 data class CustomTileDataModel(
     val user: UserHandle,
     val componentName: ComponentName,
     val tile: Tile,
+    val isToggleable: Boolean,
     val callingAppUid: Int,
     val hasPendingBind: Boolean,
-    val shouldShowChevron: Boolean,
-    val defaultTileLabel: CharSequence?,
-    val defaultTileIcon: Icon?,
-    val component: CustomTileBoundComponent,
+    val defaultTileLabel: CharSequence,
+    val defaultTileIcon: Icon,
 )
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/domain/interactor/CustomTileDataInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/domain/interactor/CustomTileDataInteractor.kt
new file mode 100644
index 0000000..cff95d8
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/domain/interactor/CustomTileDataInteractor.kt
@@ -0,0 +1,133 @@
+/*
+ * 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.qs.tiles.impl.custom.domain.interactor
+
+import android.os.UserHandle
+import android.service.quicksettings.Tile
+import com.android.systemui.common.coroutine.ConflatedCallbackFlow
+import com.android.systemui.qs.pipeline.shared.TileSpec
+import com.android.systemui.qs.tiles.base.interactor.DataUpdateTrigger
+import com.android.systemui.qs.tiles.base.interactor.QSTileDataInteractor
+import com.android.systemui.qs.tiles.impl.custom.data.entity.CustomTileDefaults
+import com.android.systemui.qs.tiles.impl.custom.data.repository.CustomTileDefaultsRepository
+import com.android.systemui.qs.tiles.impl.custom.data.repository.CustomTilePackageUpdatesRepository
+import com.android.systemui.qs.tiles.impl.custom.domain.entity.CustomTileDataModel
+import com.android.systemui.qs.tiles.impl.di.QSTileScope
+import com.android.systemui.user.data.repository.UserRepository
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.channels.awaitClose
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.first
+import kotlinx.coroutines.flow.flatMapLatest
+import kotlinx.coroutines.flow.launchIn
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.mapNotNull
+import kotlinx.coroutines.flow.onEach
+import kotlinx.coroutines.flow.onStart
+import kotlinx.coroutines.flow.shareIn
+import kotlinx.coroutines.launch
+
+@QSTileScope
+@OptIn(ExperimentalCoroutinesApi::class)
+class CustomTileDataInteractor
+@Inject
+constructor(
+    private val tileSpec: TileSpec.CustomTileSpec,
+    private val defaultsRepository: CustomTileDefaultsRepository,
+    private val serviceInteractor: CustomTileServiceInteractor,
+    private val customTileInteractor: CustomTileInteractor,
+    private val packageUpdatesRepository: CustomTilePackageUpdatesRepository,
+    userRepository: UserRepository,
+    @QSTileScope private val tileScope: CoroutineScope,
+) : QSTileDataInteractor<CustomTileDataModel> {
+
+    private val mutableUserFlow = MutableStateFlow(userRepository.getSelectedUserInfo().userHandle)
+    private val bindingFlow =
+        mutableUserFlow
+            .flatMapLatest { user ->
+                ConflatedCallbackFlow.conflatedCallbackFlow {
+                    serviceInteractor.setUser(user)
+
+                    // Wait for the CustomTileInteractor to become initialized first, because
+                    // binding
+                    // the service might access it
+                    customTileInteractor.initForUser(user)
+                    // Bind the TileService for not active tile
+                    serviceInteractor.bindOnStart()
+
+                    packageUpdatesRepository
+                        .getPackageChangesForUser(user)
+                        .onEach {
+                            defaultsRepository.requestNewDefaults(
+                                user,
+                                tileSpec.componentName,
+                                true
+                            )
+                        }
+                        .launchIn(this)
+
+                    send(Unit)
+                    awaitClose { serviceInteractor.unbind() }
+                }
+            }
+            .shareIn(tileScope, SharingStarted.WhileSubscribed())
+
+    init {
+        // Initialize binding once to flush all the pending messages inside
+        // CustomTileServiceInteractor and then unbind if the tile data isn't observed. This ensures
+        // that all the interactors are loaded and warmed up before binding.
+        tileScope.launch { bindingFlow.first() }
+    }
+
+    override fun tileData(
+        user: UserHandle,
+        triggers: Flow<DataUpdateTrigger>
+    ): Flow<CustomTileDataModel> {
+        tileScope.launch { mutableUserFlow.emit(user) }
+        return bindingFlow.combine(triggers) { _, _ -> }.flatMapLatest { dataFlow(user) }
+    }
+
+    private fun dataFlow(user: UserHandle): Flow<CustomTileDataModel> =
+        combine(
+            serviceInteractor.refreshEvents.onStart { emit(Unit) },
+            serviceInteractor.callingAppIds,
+            customTileInteractor.getTiles(user),
+            defaultsRepository.defaults(user).mapNotNull { it as? CustomTileDefaults.Result },
+        ) { _: Unit, callingAppId: Int, tile: Tile, defaults: CustomTileDefaults.Result ->
+            CustomTileDataModel(
+                user = user,
+                componentName = tileSpec.componentName,
+                tile = tile,
+                callingAppUid = callingAppId,
+                hasPendingBind = serviceInteractor.hasPendingBind(),
+                defaultTileLabel = defaults.label,
+                defaultTileIcon = defaults.icon,
+                isToggleable = customTileInteractor.isTileToggleable(),
+            )
+        }
+
+    override fun availability(user: UserHandle): Flow<Boolean> =
+        with(defaultsRepository) {
+            requestNewDefaults(user, tileSpec.componentName)
+            return defaults(user).map { it is CustomTileDefaults.Result }
+        }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/domain/interactor/CustomTileInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/domain/interactor/CustomTileInteractor.kt
index 10b012d..fd96fc5 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/domain/interactor/CustomTileInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/domain/interactor/CustomTileInteractor.kt
@@ -19,12 +19,14 @@
 import android.os.UserHandle
 import android.service.quicksettings.Tile
 import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.qs.pipeline.shared.TileSpec
 import com.android.systemui.qs.tiles.impl.custom.data.repository.CustomTileDefaultsRepository
 import com.android.systemui.qs.tiles.impl.custom.data.repository.CustomTileRepository
 import com.android.systemui.qs.tiles.impl.di.QSTileScope
 import javax.inject.Inject
 import kotlin.coroutines.CoroutineContext
 import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Job
 import kotlinx.coroutines.channels.BufferOverflow
 import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.MutableSharedFlow
@@ -32,21 +34,29 @@
 import kotlinx.coroutines.flow.flowOn
 import kotlinx.coroutines.flow.launchIn
 import kotlinx.coroutines.flow.onEach
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.sync.Mutex
+import kotlinx.coroutines.sync.withLock
 
 /** Manages updates of the [Tile] assigned for the current custom tile. */
 @QSTileScope
 class CustomTileInteractor
 @Inject
 constructor(
+    private val tileSpec: TileSpec.CustomTileSpec,
     private val defaultsRepository: CustomTileDefaultsRepository,
     private val customTileRepository: CustomTileRepository,
     @QSTileScope private val tileScope: CoroutineScope,
     @Background private val backgroundContext: CoroutineContext,
 ) {
 
+    private val userMutex = Mutex()
     private val tileUpdates =
         MutableSharedFlow<Tile>(replay = 1, onBufferOverflow = BufferOverflow.DROP_OLDEST)
 
+    private var currentUser: UserHandle? = null
+    private var updatesJob: Job? = null
+
     /** [Tile] updates. [updateTile] to emit a new one. */
     fun getTiles(user: UserHandle): Flow<Tile> = customTileRepository.getTiles(user)
 
@@ -55,7 +65,7 @@
      *
      * @throws IllegalStateException when the repository stores a tile for another user. This means
      *   the tile hasn't been updated for the current user. Can happen when this is accessed before
-     *   [init] returns.
+     *   [initForUser] returns.
      */
     fun getTile(user: UserHandle): Tile =
         customTileRepository.getTile(user)
@@ -67,45 +77,60 @@
     suspend fun isTileToggleable(): Boolean = customTileRepository.isTileToggleable()
 
     /**
-     * Initializes the repository for the current user. Suspends until it's safe to call [tile]
+     * Initializes the repository for the current user. Suspends until it's safe to call [getTile]
      * which needs at least one of the following:
      * - defaults are loaded;
      * - receive tile update in [updateTile];
      * - restoration happened for a persisted tile.
      */
     suspend fun initForUser(user: UserHandle) {
-        launchUpdates(user)
-        customTileRepository.restoreForTheUserIfNeeded(user, customTileRepository.isTileActive())
-        // Suspend to make sure it gets the tile from one of the sources: restoration, defaults, or
-        // tile update.
-        customTileRepository.getTiles(user).firstOrNull()
+        userMutex.withLock {
+            if (currentUser == user) {
+                return
+            }
+            updatesJob?.cancel()
+            defaultsRepository.requestNewDefaults(user, tileSpec.componentName)
+            launchUpdates(user)
+            customTileRepository.restoreForTheUserIfNeeded(
+                user,
+                customTileRepository.isTileActive()
+            )
+            // Suspend to make sure it gets the tile from one of the sources: restoration, defaults,
+            // or
+            // tile update.
+            customTileRepository.getTiles(user).firstOrNull()
+            currentUser = user
+        }
     }
 
     private fun launchUpdates(user: UserHandle) {
-        tileUpdates
-            .onEach {
-                customTileRepository.updateWithTile(
-                    user,
-                    it,
-                    customTileRepository.isTileActive(),
-                )
+        updatesJob =
+            tileScope.launch {
+                tileUpdates
+                    .onEach {
+                        customTileRepository.updateWithTile(
+                            user,
+                            it,
+                            customTileRepository.isTileActive(),
+                        )
+                    }
+                    .flowOn(backgroundContext)
+                    .launchIn(this)
+                defaultsRepository
+                    .defaults(user)
+                    .onEach {
+                        customTileRepository.updateWithDefaults(
+                            user,
+                            it,
+                            customTileRepository.isTileActive(),
+                        )
+                    }
+                    .flowOn(backgroundContext)
+                    .launchIn(this)
             }
-            .flowOn(backgroundContext)
-            .launchIn(tileScope)
-        defaultsRepository
-            .defaults(user)
-            .onEach {
-                customTileRepository.updateWithDefaults(
-                    user,
-                    it,
-                    customTileRepository.isTileActive(),
-                )
-            }
-            .flowOn(backgroundContext)
-            .launchIn(tileScope)
     }
 
-    /** Updates current [Tile]. Emits a new event in [tiles]. */
+    /** Updates current [Tile]. Emits a new event in [getTiles]. */
     fun updateTile(newTile: Tile) {
         tileUpdates.tryEmit(newTile)
     }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/domain/interactor/CustomTileServiceInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/domain/interactor/CustomTileServiceInteractor.kt
new file mode 100644
index 0000000..acff40f
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/domain/interactor/CustomTileServiceInteractor.kt
@@ -0,0 +1,216 @@
+/*
+ * 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.qs.tiles.impl.custom.domain.interactor
+
+import android.app.PendingIntent
+import android.content.ComponentName
+import android.os.IBinder
+import android.os.Process
+import android.os.RemoteException
+import android.os.UserHandle
+import android.service.quicksettings.IQSTileService
+import android.service.quicksettings.Tile
+import android.service.quicksettings.TileService
+import com.android.systemui.plugins.ActivityStarter
+import com.android.systemui.qs.external.CustomTileInterface
+import com.android.systemui.qs.external.TileServiceManager
+import com.android.systemui.qs.external.TileServices
+import com.android.systemui.qs.pipeline.shared.TileSpec
+import com.android.systemui.qs.tiles.base.logging.QSTileLogger
+import com.android.systemui.qs.tiles.impl.di.QSTileScope
+import com.android.systemui.user.data.repository.UserRepository
+import dagger.Lazy
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.Job
+import kotlinx.coroutines.channels.awaitClose
+import kotlinx.coroutines.channels.produce
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableSharedFlow
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.launch
+
+/**
+ * Communicates with [TileService] via [TileServiceManager] and [IQSTileService]. This interactor is
+ * also responsible for the binding to the [TileService].
+ */
+@OptIn(ExperimentalCoroutinesApi::class)
+@QSTileScope
+class CustomTileServiceInteractor
+@Inject
+constructor(
+    private val tileSpec: TileSpec.CustomTileSpec,
+    private val activityStarter: ActivityStarter,
+    private val userActionInteractor: Lazy<CustomTileUserActionInteractor>,
+    private val customTileInteractor: CustomTileInteractor,
+    private val userRepository: UserRepository,
+    private val qsTileLogger: QSTileLogger,
+    private val tileServices: TileServices,
+    @QSTileScope private val tileScope: CoroutineScope,
+) {
+
+    private val tileReceivingInterface = ReceivingInterface()
+    private var tileServiceManager: TileServiceManager? = null
+    private val tileServiceInterface: IQSTileService
+        get() = getTileServiceManager().tileService
+
+    private var currentUser: UserHandle = userRepository.getSelectedUserInfo().userHandle
+    private var destructionJob: Job? = null
+
+    val callingAppIds: Flow<Int>
+        get() = tileReceivingInterface.mutableCallingAppIds
+    val refreshEvents: Flow<Unit>
+        get() = tileReceivingInterface.mutableRefreshEvents
+
+    /** Clears all pending binding for an active tile and binds not active one. */
+    fun bindOnStart() {
+        try {
+            with(getTileServiceManager()) {
+                if (isActiveTile) {
+                    clearPendingBind()
+                } else {
+                    setBindRequested(true)
+                    tileServiceInterface.onStartListening()
+                }
+            }
+        } catch (e: RemoteException) {
+            qsTileLogger.logError(tileSpec, "Binding to the service failed", e)
+        }
+    }
+
+    /** Binds active tile WITHOUT CLEARING pending binds. */
+    fun bindOnClick() {
+        try {
+            with(getTileServiceManager()) {
+                if (isActiveTile) {
+                    setBindRequested(true)
+                    tileServiceInterface.onStartListening()
+                }
+            }
+        } catch (e: RemoteException) {
+            qsTileLogger.logError(tileSpec, "Binding to the service on click failed", e)
+        }
+    }
+
+    /** Releases resources held by the binding and prepares the interactor to be collected */
+    fun unbind() {
+        try {
+            with(userActionInteractor.get()) {
+                clearLastClickedView()
+                tileServiceInterface.onStopListening()
+                revokeToken(false)
+                setShowingDialog(false)
+            }
+            getTileServiceManager().setBindRequested(false)
+        } catch (e: RemoteException) {
+            qsTileLogger.logError(tileSpec, "Unbinding failed", e)
+        }
+    }
+
+    /**
+     * Checks if [TileServiceManager] has a pending [android.service.quicksettings.TileService]
+     * bind.
+     */
+    fun hasPendingBind(): Boolean = getTileServiceManager().hasPendingBind()
+
+    /** Sets a [user] for the custom tile to use. User change triggers service rebinding. */
+    fun setUser(user: UserHandle) {
+        if (user == currentUser) {
+            return
+        }
+        currentUser = user
+        destructionJob?.cancel()
+
+        tileServiceManager = null
+    }
+
+    /** Sends click event to [TileService] using [IQSTileService.onClick]. */
+    fun onClick(token: IBinder) {
+        tileServiceInterface.onClick(token)
+    }
+
+    private fun getTileServiceManager(): TileServiceManager =
+        synchronized(tileServices) {
+            if (tileServiceManager == null) {
+                tileServices
+                    .getTileWrapper(tileReceivingInterface)
+                    .also { destructionJob = createDestructionJob() }
+                    .also { tileServiceManager = it }
+            } else {
+                tileServiceManager!!
+            }
+        }
+
+    /**
+     * This job used to free the resources when the [QSTileScope] coroutine scope gets cancelled by
+     * the View Model.
+     */
+    private fun createDestructionJob(): Job =
+        tileScope.launch {
+            produce<Unit> {
+                awaitClose {
+                    userActionInteractor.get().revokeToken(true)
+                    tileServices.freeService(tileReceivingInterface, getTileServiceManager())
+                    destructionJob = null
+                }
+            }
+        }
+
+    private inner class ReceivingInterface : CustomTileInterface {
+
+        override val user: Int
+            get() = currentUser.identifier
+        override val qsTile: Tile
+            get() = customTileInteractor.getTile(currentUser)
+        override val component: ComponentName = tileSpec.componentName
+
+        val mutableCallingAppIds = MutableStateFlow(Process.INVALID_UID)
+        val mutableRefreshEvents = MutableSharedFlow<Unit>()
+
+        override fun getTileSpec(): String = tileSpec.spec
+
+        override fun refreshState() {
+            tileScope.launch { mutableRefreshEvents.emit(Unit) }
+        }
+
+        override fun updateTileState(tile: Tile, uid: Int) {
+            customTileInteractor.updateTile(tile)
+            mutableCallingAppIds.tryEmit(uid)
+        }
+
+        override fun onDialogShown() {
+            userActionInteractor.get().setShowingDialog(true)
+        }
+
+        override fun onDialogHidden() =
+            with(userActionInteractor.get()) {
+                setShowingDialog(false)
+                revokeToken(true)
+            }
+
+        override fun startActivityAndCollapse(pendingIntent: PendingIntent) {
+            userActionInteractor.get().startActivityAndCollapse(pendingIntent)
+        }
+
+        override fun startUnlockAndRun() {
+            activityStarter.postQSRunnableDismissingKeyguard {
+                tileServiceInterface.onUnlockComplete()
+            }
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/domain/interactor/CustomTileUserActionInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/domain/interactor/CustomTileUserActionInteractor.kt
new file mode 100644
index 0000000..c3e1fea
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/domain/interactor/CustomTileUserActionInteractor.kt
@@ -0,0 +1,193 @@
+/*
+ * 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.qs.tiles.impl.custom.domain.interactor
+
+import android.app.PendingIntent
+import android.content.ComponentName
+import android.content.Context
+import android.content.Intent
+import android.content.IntentFilter
+import android.net.Uri
+import android.os.Binder
+import android.os.IBinder
+import android.os.RemoteException
+import android.os.UserHandle
+import android.provider.Settings
+import android.service.quicksettings.TileService
+import android.view.IWindowManager
+import android.view.View
+import android.view.WindowManager
+import androidx.annotation.GuardedBy
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.qs.pipeline.shared.TileSpec
+import com.android.systemui.qs.tiles.base.actions.QSTileIntentUserInputHandler
+import com.android.systemui.qs.tiles.base.interactor.QSTileInput
+import com.android.systemui.qs.tiles.base.interactor.QSTileUserActionInteractor
+import com.android.systemui.qs.tiles.base.logging.QSTileLogger
+import com.android.systemui.qs.tiles.impl.custom.domain.entity.CustomTileDataModel
+import com.android.systemui.qs.tiles.impl.di.QSTileScope
+import com.android.systemui.qs.tiles.viewmodel.QSTileUserAction
+import com.android.systemui.settings.DisplayTracker
+import java.util.concurrent.atomic.AtomicReference
+import javax.inject.Inject
+import kotlin.coroutines.CoroutineContext
+import kotlinx.coroutines.withContext
+
+@QSTileScope
+class CustomTileUserActionInteractor
+@Inject
+constructor(
+    private val context: Context,
+    private val tileSpec: TileSpec,
+    private val qsTileLogger: QSTileLogger,
+    private val windowManager: IWindowManager,
+    private val displayTracker: DisplayTracker,
+    private val qsTileIntentUserInputHandler: QSTileIntentUserInputHandler,
+    @Background private val backgroundContext: CoroutineContext,
+    private val serviceInteractor: CustomTileServiceInteractor,
+) : QSTileUserActionInteractor<CustomTileDataModel> {
+
+    private val token: IBinder = Binder()
+
+    @GuardedBy("token") private var isTokenGranted: Boolean = false
+    @GuardedBy("token") private var isShowingDialog: Boolean = false
+    private val lastClickedView: AtomicReference<View> = AtomicReference<View>()
+
+    override suspend fun handleInput(input: QSTileInput<CustomTileDataModel>) =
+        with(input) {
+            when (action) {
+                is QSTileUserAction.Click -> click(action.view, data.tile.activityLaunchForClick)
+                is QSTileUserAction.LongClick ->
+                    longClick(user, action.view, data.componentName, data.tile.state)
+            }
+            qsTileLogger.logCustomTileUserActionDelivered(tileSpec)
+        }
+
+    private fun click(
+        view: View?,
+        activityLaunchForClick: PendingIntent?,
+    ) {
+        grantToken()
+        try {
+            // Bind active tile to deliver user action
+            serviceInteractor.bindOnClick()
+            if (activityLaunchForClick == null) {
+                lastClickedView.set(view)
+                serviceInteractor.onClick(token)
+            } else {
+                qsTileIntentUserInputHandler.handle(view, activityLaunchForClick)
+            }
+        } catch (e: RemoteException) {
+            qsTileLogger.logError(tileSpec, "Failed to deliver click", e)
+        }
+    }
+
+    fun revokeToken(ignoreShownDialog: Boolean) {
+        synchronized(token) {
+            if (isTokenGranted && (ignoreShownDialog || !isShowingDialog)) {
+                try {
+                    windowManager.removeWindowToken(token, displayTracker.defaultDisplayId)
+                } catch (e: RemoteException) {
+                    qsTileLogger.logError(tileSpec, "Failed to remove a window token", e)
+                }
+                isTokenGranted = false
+            }
+        }
+    }
+
+    fun setShowingDialog(isShowingDialog: Boolean) {
+        synchronized(token) { this.isShowingDialog = isShowingDialog }
+    }
+
+    fun startActivityAndCollapse(pendingIntent: PendingIntent) {
+        if (!pendingIntent.isActivity) {
+            return
+        }
+        if (!isTokenGranted) {
+            return
+        }
+        qsTileIntentUserInputHandler.handle(lastClickedView.getAndSet(null), pendingIntent)
+    }
+
+    fun clearLastClickedView() = lastClickedView.set(null)
+
+    private fun grantToken() {
+        synchronized(token) {
+            if (!isTokenGranted) {
+                try {
+                    windowManager.addWindowToken(
+                        token,
+                        WindowManager.LayoutParams.TYPE_QS_DIALOG,
+                        displayTracker.defaultDisplayId,
+                        null /* options */
+                    )
+                } catch (e: RemoteException) {
+                    qsTileLogger.logError(tileSpec, "Failed to grant a window token", e)
+                }
+                isTokenGranted = true
+            }
+        }
+    }
+
+    private suspend fun longClick(
+        user: UserHandle,
+        view: View?,
+        componentName: ComponentName,
+        state: Int
+    ) {
+        val resolvedIntent: Intent? =
+            resolveIntent(
+                    Intent(TileService.ACTION_QS_TILE_PREFERENCES).apply {
+                        setPackage(componentName.packageName)
+                    },
+                    user,
+                )
+                ?.apply {
+                    putExtra(Intent.EXTRA_COMPONENT_NAME, componentName)
+                    putExtra(TileService.EXTRA_STATE, state)
+                }
+        if (resolvedIntent == null) {
+            qsTileIntentUserInputHandler.handle(
+                view,
+                Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS)
+                    .setData(
+                        Uri.fromParts(IntentFilter.SCHEME_PACKAGE, componentName.packageName, null)
+                    )
+            )
+        } else {
+            qsTileIntentUserInputHandler.handle(view, resolvedIntent)
+        }
+    }
+
+    /**
+     * Returns an intent resolved by [android.content.pm.PackageManager.resolveActivityAsUser] or
+     * null.
+     */
+    private suspend fun resolveIntent(intent: Intent, user: UserHandle): Intent? =
+        withContext(backgroundContext) {
+            val activityInfo =
+                context.packageManager
+                    .resolveActivityAsUser(intent, 0, user.identifier)
+                    ?.activityInfo
+            activityInfo ?: return@withContext null
+            with(activityInfo) {
+                Intent(TileService.ACTION_QS_TILE_PREFERENCES).apply {
+                    setClassName(packageName, name)
+                }
+            }
+        }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileState.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileState.kt
index be1b740..b927e41 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileState.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileState.kt
@@ -32,7 +32,7 @@
  * // TODO(b/http://b/299909989): Clean up legacy mappings after the transition
  */
 data class QSTileState(
-    val icon: () -> Icon,
+    val icon: () -> Icon?,
     val label: CharSequence,
     val activationState: ActivationState,
     val secondaryLabel: CharSequence?,
@@ -60,7 +60,7 @@
             )
         }
 
-        fun build(icon: () -> Icon, label: CharSequence, build: Builder.() -> Unit): QSTileState =
+        fun build(icon: () -> Icon?, label: CharSequence, build: Builder.() -> Unit): QSTileState =
             Builder(icon, label).apply(build).build()
     }
 
@@ -108,7 +108,7 @@
     }
 
     class Builder(
-        var icon: () -> Icon,
+        var icon: () -> Icon?,
         var label: CharSequence,
     ) {
         var activationState: ActivationState = ActivationState.INACTIVE
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileViewModel.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileViewModel.kt
index ef3df48..226e2fa0 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileViewModel.kt
@@ -45,7 +45,10 @@
      */
     fun onUserChanged(user: UserHandle)
 
-    /** Triggers the emission of the new [QSTileState] in a [state]. */
+    /**
+     * Triggers the emission of the new [QSTileState] in a [state]. The new value can still be
+     * skipped if there is no change.
+     */
     fun forceUpdate()
 
     /** Notifies underlying logic about user input. */
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileViewModelAdapter.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileViewModelAdapter.kt
index 977df81..4780a2e 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileViewModelAdapter.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileViewModelAdapter.kt
@@ -37,6 +37,7 @@
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.Job
 import kotlinx.coroutines.flow.collectIndexed
+import kotlinx.coroutines.flow.first
 import kotlinx.coroutines.flow.launchIn
 import kotlinx.coroutines.flow.map
 import kotlinx.coroutines.flow.onEach
@@ -60,27 +61,34 @@
     private val listeningClients: MutableCollection<Any> = mutableSetOf()
 
     // Cancels the jobs when the adapter is no longer alive
-    private var availabilityJob: Job? = null
+    private var tileAdapterJob: Job? = null
     // Cancels the jobs when clients stop listening
     private var stateJob: Job? = null
 
     init {
-        availabilityJob =
+        tileAdapterJob =
             applicationScope.launch {
-                qsTileViewModel.isAvailable.collectIndexed { index, isAvailable ->
-                    if (!isAvailable) {
-                        qsHost.removeTile(tileSpec)
-                    }
-                    // qsTileViewModel.isAvailable flow often starts with isAvailable == true.
-                    // That's
-                    // why we only allow isAvailable == true once and throw an exception afterwards.
-                    if (index > 0 && isAvailable) {
-                        // See com.android.systemui.qs.pipeline.domain.model.AutoAddable for
-                        // additional
-                        // guidance on how to auto add your tile
-                        throw UnsupportedOperationException("Turning on tile is not supported now")
+                launch {
+                    qsTileViewModel.isAvailable.collectIndexed { index, isAvailable ->
+                        if (!isAvailable) {
+                            qsHost.removeTile(tileSpec)
+                        }
+                        // qsTileViewModel.isAvailable flow often starts with isAvailable == true.
+                        // That's
+                        // why we only allow isAvailable == true once and throw an exception
+                        // afterwards.
+                        if (index > 0 && isAvailable) {
+                            // See com.android.systemui.qs.pipeline.domain.model.AutoAddable for
+                            // additional
+                            // guidance on how to auto add your tile
+                            throw UnsupportedOperationException(
+                                "Turning on tile is not supported now"
+                            )
+                        }
                     }
                 }
+                // Warm up tile with some initial state
+                launch { qsTileViewModel.state.first() }
             }
 
         // QSTileHost doesn't call this when userId is initialized
@@ -185,7 +193,7 @@
 
     override fun destroy() {
         stateJob?.cancel()
-        availabilityJob?.cancel()
+        tileAdapterJob?.cancel()
         qsTileViewModel.destroy()
     }
 
@@ -222,8 +230,9 @@
             QSTile.BooleanState().apply {
                 spec = config.tileSpec.spec
                 label = viewModelState.label
-                // This value is synthetic and doesn't have any meaning
-                value = false
+                // This value is synthetic and doesn't have any meaning. It's only needed to satisfy
+                // CTS tests.
+                value = viewModelState.activationState == QSTileState.ActivationState.ACTIVE
 
                 secondaryLabel = viewModelState.secondaryLabel
                 handlesLongClick =
@@ -233,6 +242,7 @@
                     when (val stateIcon = viewModelState.icon()) {
                         is Icon.Loaded -> DrawableIcon(stateIcon.drawable)
                         is Icon.Resource -> ResourceIcon.get(stateIcon.res)
+                        null -> null
                     }
                 }
                 state = viewModelState.activationState.legacyState
diff --git a/packages/SystemUI/src/com/android/systemui/qs/user/UserSwitchDialogController.kt b/packages/SystemUI/src/com/android/systemui/qs/user/UserSwitchDialogController.kt
index acd7510..41cd221 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/user/UserSwitchDialogController.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/user/UserSwitchDialogController.kt
@@ -23,7 +23,6 @@
 import android.content.Intent
 import android.provider.Settings
 import android.view.LayoutInflater
-import androidx.annotation.VisibleForTesting
 import com.android.internal.jank.InteractionJankMonitor
 import com.android.internal.logging.UiEventLogger
 import com.android.systemui.res.R
@@ -44,31 +43,15 @@
  * Controller for [UserDialog].
  */
 @SysUISingleton
-class UserSwitchDialogController @VisibleForTesting constructor(
-    private val userDetailViewAdapterProvider: Provider<UserDetailView.Adapter>,
-    private val activityStarter: ActivityStarter,
-    private val falsingManager: FalsingManager,
-    private val dialogLaunchAnimator: DialogLaunchAnimator,
-    private val uiEventLogger: UiEventLogger,
-    private val dialogFactory: (Context) -> SystemUIDialog
+class UserSwitchDialogController @Inject constructor(
+        private val userDetailViewAdapterProvider: Provider<UserDetailView.Adapter>,
+        private val activityStarter: ActivityStarter,
+        private val falsingManager: FalsingManager,
+        private val dialogLaunchAnimator: DialogLaunchAnimator,
+        private val uiEventLogger: UiEventLogger,
+        private val dialogFactory: SystemUIDialog.Factory
 ) {
 
-    @Inject
-    constructor(
-        userDetailViewAdapterProvider: Provider<UserDetailView.Adapter>,
-        activityStarter: ActivityStarter,
-        falsingManager: FalsingManager,
-        dialogLaunchAnimator: DialogLaunchAnimator,
-        uiEventLogger: UiEventLogger
-    ) : this(
-        userDetailViewAdapterProvider,
-        activityStarter,
-        falsingManager,
-        dialogLaunchAnimator,
-        uiEventLogger,
-        { SystemUIDialog(it) }
-    )
-
     companion object {
         private const val INTERACTION_JANK_TAG = "switch_user"
         private val USER_SETTINGS_INTENT = Intent(Settings.ACTION_USER_SETTINGS)
@@ -81,7 +64,7 @@
      * [userDetailViewAdapterProvider] and show it as launched from [expandable].
      */
     fun showDialog(context: Context, expandable: Expandable) {
-        with(dialogFactory(context)) {
+        with(dialogFactory.create()) {
             setShowForAllUsers(true)
             setCanceledOnTouchOutside(true)
 
diff --git a/packages/SystemUI/src/com/android/systemui/reardisplay/RearDisplayDialogController.java b/packages/SystemUI/src/com/android/systemui/reardisplay/RearDisplayDialogController.java
index f071623..9076182 100644
--- a/packages/SystemUI/src/com/android/systemui/reardisplay/RearDisplayDialogController.java
+++ b/packages/SystemUI/src/com/android/systemui/reardisplay/RearDisplayDialogController.java
@@ -21,8 +21,10 @@
 import android.annotation.TestApi;
 import android.content.Context;
 import android.content.res.Configuration;
+import android.content.res.Resources;
 import android.hardware.devicestate.DeviceStateManager;
 import android.hardware.devicestate.DeviceStateManagerGlobal;
+import android.view.LayoutInflater;
 import android.view.View;
 import android.view.ViewGroup.LayoutParams;
 import android.widget.LinearLayout;
@@ -72,20 +74,27 @@
     private DeviceStateManager.DeviceStateCallback mDeviceStateManagerCallback =
             new DeviceStateManagerCallback();
 
-    private final Context mContext;
     private final CommandQueue mCommandQueue;
     private final Executor mExecutor;
+    private final Resources mResources;
+    private final LayoutInflater mLayoutInflater;
+    private final SystemUIDialog.Factory mSystemUIDialogFactory;
 
-    @VisibleForTesting
-    SystemUIDialog mRearDisplayEducationDialog;
+    private SystemUIDialog mRearDisplayEducationDialog;
     @Nullable LinearLayout mDialogViewContainer;
 
     @Inject
-    public RearDisplayDialogController(Context context, CommandQueue commandQueue,
-            @Main Executor executor) {
-        mContext = context;
+    public RearDisplayDialogController(
+            CommandQueue commandQueue,
+            @Main Executor executor,
+            @Main Resources resources,
+            LayoutInflater layoutInflater,
+            SystemUIDialog.Factory systemUIDialogFactory) {
         mCommandQueue = commandQueue;
         mExecutor = executor;
+        mResources = resources;
+        mLayoutInflater = layoutInflater;
+        mSystemUIDialogFactory = systemUIDialogFactory;
     }
 
     @Override
@@ -104,8 +113,7 @@
         if (mRearDisplayEducationDialog != null && mRearDisplayEducationDialog.isShowing()
                 && mDialogViewContainer != null) {
             // Refresh the dialog view when configuration is changed.
-            Context dialogContext = mRearDisplayEducationDialog.getContext();
-            View dialogView = createDialogView(dialogContext);
+            View dialogView = createDialogView(mRearDisplayEducationDialog.getContext());
             mDialogViewContainer.removeAllViews();
             mDialogViewContainer.addView(dialogView);
         }
@@ -114,9 +122,7 @@
     private void createAndShowDialog() {
         mServiceNotified = false;
         Context dialogContext = mRearDisplayEducationDialog.getContext();
-
         View dialogView = createDialogView(dialogContext);
-
         mDialogViewContainer = new LinearLayout(dialogContext);
         mDialogViewContainer.setLayoutParams(
                 new LinearLayout.LayoutParams(
@@ -133,11 +139,11 @@
 
     private View createDialogView(Context context) {
         View dialogView;
+        LayoutInflater inflater = mLayoutInflater.cloneInContext(context);
         if (mStartedFolded) {
-            dialogView = View.inflate(context,
-                    R.layout.activity_rear_display_education, null);
+            dialogView = inflater.inflate(R.layout.activity_rear_display_education, null);
         } else {
-            dialogView = View.inflate(context,
+            dialogView = inflater.inflate(
                     R.layout.activity_rear_display_education_opened, null);
         }
         LottieAnimationView animationView = dialogView.findViewById(
@@ -172,9 +178,9 @@
      * Ensures we're not using old values from when the dialog may have been shown previously.
      */
     private void initializeValues(int startingBaseState) {
-        mRearDisplayEducationDialog = new SystemUIDialog(mContext);
+        mRearDisplayEducationDialog = mSystemUIDialogFactory.create();
         if (mFoldedStates == null) {
-            mFoldedStates = mContext.getResources().getIntArray(
+            mFoldedStates = mResources.getIntArray(
                     com.android.internal.R.array.config_foldedDeviceStates);
         }
         mStartedFolded = isFoldedState(startingBaseState);
diff --git a/packages/SystemUI/src/com/android/systemui/shade/GlanceableHubContainerController.kt b/packages/SystemUI/src/com/android/systemui/shade/GlanceableHubContainerController.kt
index ab69acb..3be60b7 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/GlanceableHubContainerController.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/GlanceableHubContainerController.kt
@@ -157,7 +157,10 @@
         // If the hub is fully visible, send all touch events to it.
         val communalVisible = hubShowing && !hubOccluded
         if (communalVisible) {
-            return communalContainerView.dispatchTouchEvent(ev)
+            communalContainerView.dispatchTouchEvent(ev)
+            // Return true regardless of dispatch result as some touches at the start of a gesture
+            // may return false from dispatchTouchEvent.
+            return true
         }
 
         if (edgeSwipeRegionWidth == 0) {
@@ -172,13 +175,19 @@
                 x >= communalContainerView.width - edgeSwipeRegionWidth
             if (inOpeningSwipeRegion && !hubOccluded) {
                 isTrackingOpenGesture = true
-                return communalContainerView.dispatchTouchEvent(ev)
+                communalContainerView.dispatchTouchEvent(ev)
+                // Return true regardless of dispatch result as some touches at the start of a
+                // gesture may return false from dispatchTouchEvent.
+                return true
             }
         } else if (isTrackingOpenGesture) {
             if (isUp || isCancel) {
                 isTrackingOpenGesture = false
             }
-            return communalContainerView.dispatchTouchEvent(ev)
+            communalContainerView.dispatchTouchEvent(ev)
+            // Return true regardless of dispatch result as some touches at the start of a gesture
+            // may return false from dispatchTouchEvent.
+            return true
         }
 
         return false
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
index 286037e..fb6bc38 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
@@ -2478,6 +2478,13 @@
             return 0;
         }
         if (!mKeyguardBypassController.getBypassEnabled()) {
+            if (migrateClocksToBlueprint()) {
+                View nsslPlaceholder = mView.getRootView().findViewById(R.id.nssl_placeholder);
+                if (!mSplitShadeEnabled && nsslPlaceholder != null) {
+                    return nsslPlaceholder.getTop();
+                }
+            }
+
             return mClockPositionResult.stackScrollerPadding;
         }
         int collapsedPosition = mHeadsUpInset;
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java
index c9df317..9b8dd0b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerImpl.java
@@ -275,7 +275,12 @@
 
                     updateLockscreenNotificationSetting();
                     updatePublicMode();
-                    mPresenter.onUserSwitched(mCurrentUserId);
+                    if (mPresenter != null) {
+                        mPresenter.onUserSwitched(mCurrentUserId);
+                    } else {
+                        Log.w(TAG, "user switch before setup with presenter",
+                                new Exception());
+                    }
 
                     for (UserChangedListener listener : mListeners) {
                         listener.onUserChanged(mCurrentUserId);
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 0f640c9..805b44c 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
@@ -4455,9 +4455,13 @@
 
         mSectionsManager.setHeaderForegroundColors(onSurface, onSurfaceVariant);
 
-        mFooterView.updateColors();
+        if (mFooterView != null) {
+            mFooterView.updateColors();
+        }
 
-        mEmptyShadeView.setTextColors(onSurface, onSurfaceVariant);
+        if (mEmptyShadeView != null) {
+            mEmptyShadeView.setTextColors(onSurface, onSurfaceVariant);
+        }
     }
 
     void goToFullShade(long delay) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/ConfigurationControllerModule.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ConfigurationControllerModule.kt
new file mode 100644
index 0000000..fc3456a
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/ConfigurationControllerModule.kt
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2024 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.statusbar.phone
+
+import com.android.systemui.CoreStartable
+import dagger.Binds
+import dagger.Module
+import dagger.multibindings.ClassKey
+import dagger.multibindings.IntoMap
+
+@Module
+interface ConfigurationControllerModule {
+
+    /** Starts [ConfigurationControllerStartable] */
+    @Binds
+    @IntoMap
+    @ClassKey(ConfigurationControllerStartable::class)
+    fun bindConfigControllerStartable(impl: ConfigurationControllerStartable): CoreStartable
+}
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 600d4af..45005cb 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeServiceHost.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/DozeServiceHost.java
@@ -55,11 +55,12 @@
 import com.android.systemui.statusbar.policy.OnHeadsUpChangedListener;
 import com.android.systemui.util.Assert;
 
+import dagger.Lazy;
+
 import java.util.ArrayList;
 
 import javax.inject.Inject;
 
-import dagger.Lazy;
 import kotlinx.coroutines.ExperimentalCoroutinesApi;
 
 /**
@@ -175,6 +176,16 @@
         }
     }
 
+    /**
+     * Notify the registered callback about SPFS fingerprint acquisition started event.
+     */
+    public void fireSideFpsAcquisitionStarted() {
+        Assert.isMainThread();
+        for (int i = 0; i < mCallbacks.size(); i++) {
+            mCallbacks.get(i).onSideFingerprintAcquisitionStarted();
+        }
+    }
+
     void fireNotificationPulse(NotificationEntry entry) {
         Runnable pulseSuppressedListener = () -> {
             if (NotificationIconContainerRefactor.isEnabled()) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemUIDialog.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemUIDialog.java
index af6da3f..3394eac 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemUIDialog.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/SystemUIDialog.java
@@ -149,6 +149,14 @@
             return create(new DialogDelegate<>(){}, mContext);
         }
 
+        /** Creates a new instance of {@link SystemUIDialog} with no customized behavior.
+         *
+         * When you just need a dialog created with a specific {@link Context}, call this.
+         */
+        public SystemUIDialog create(Context context) {
+            return create(new DialogDelegate<>(){}, context);
+        }
+
         /**
          * Creates a new instance of {@link SystemUIDialog} with {@code delegate} as the {@link
          * Delegate}.
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarPhoneModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarPhoneModule.java
index b048da492..942d186 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarPhoneModule.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/dagger/StatusBarPhoneModule.java
@@ -16,16 +16,12 @@
 
 package com.android.systemui.statusbar.phone.dagger;
 
-import com.android.systemui.CoreStartable;
 import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.statusbar.phone.CentralSurfaces;
 import com.android.systemui.statusbar.phone.CentralSurfacesImpl;
-import com.android.systemui.statusbar.phone.ConfigurationControllerStartable;
 
 import dagger.Binds;
 import dagger.Module;
-import dagger.multibindings.ClassKey;
-import dagger.multibindings.IntoMap;
 
 /**
  * Dagger Module providing {@link CentralSurfacesImpl}.
@@ -38,12 +34,4 @@
     @Binds
     @SysUISingleton
     CentralSurfaces bindsCentralSurfaces(CentralSurfacesImpl impl);
-
-    /**
-     * Starts {@link ConfigurationControllerStartable}
-     */
-    @Binds
-    @IntoMap
-    @ClassKey(ConfigurationControllerStartable.class)
-    CoreStartable bindConfigControllerStartable(ConfigurationControllerStartable impl);
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardStateControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardStateControllerImpl.java
index 0c5472f..16334d3 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardStateControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardStateControllerImpl.java
@@ -41,15 +41,14 @@
 import com.android.systemui.dump.DumpManager;
 import com.android.systemui.flags.FeatureFlags;
 import com.android.systemui.keyguard.KeyguardUnlockAnimationController;
-import com.android.systemui.log.core.LogLevel;
 import com.android.systemui.res.R;
 import com.android.systemui.user.domain.interactor.SelectedUserInteractor;
 
 import dagger.Lazy;
 
 import java.io.PrintWriter;
-import java.util.ArrayList;
 import java.util.Objects;
+import java.util.concurrent.ConcurrentHashMap;
 import java.util.function.Consumer;
 
 import javax.inject.Inject;
@@ -63,7 +62,8 @@
     private static final boolean DEBUG_AUTH_WITH_ADB = false;
     private static final String AUTH_BROADCAST_KEY = "debug_trigger_auth";
 
-    private final ArrayList<Callback> mCallbacks = new ArrayList<>();
+    private final ConcurrentHashMap.KeySetView<Callback, Boolean> mCallbacks =
+            ConcurrentHashMap.<Callback>newKeySet();
     private final Context mContext;
     private final KeyguardUpdateMonitor mKeyguardUpdateMonitor;
     private final LockPatternUtils mLockPatternUtils;
@@ -157,9 +157,7 @@
     @Override
     public void addCallback(@NonNull Callback callback) {
         Objects.requireNonNull(callback, "Callback must not be null. b/128895449");
-        if (!mCallbacks.contains(callback)) {
-            mCallbacks.add(callback);
-        }
+        mCallbacks.add(callback);
     }
 
     @Override
@@ -221,18 +219,7 @@
     }
 
     private void invokeForEachCallback(Consumer<Callback> consumer) {
-        // Copy the list to allow removal during callback.
-        ArrayList<Callback> copyOfCallbacks = new ArrayList<>(mCallbacks);
-        for (int i = 0; i < copyOfCallbacks.size(); i++) {
-            Callback callback = copyOfCallbacks.get(i);
-            // Temporary fix for b/315731775, callback is null even though only non-null callbacks
-            // are added to the list by addCallback
-            if (callback != null) {
-                consumer.accept(callback);
-            } else {
-                mLogger.log("KeyguardStateController callback is null", LogLevel.DEBUG);
-            }
-        }
+        mCallbacks.forEach(consumer);
     }
 
     private void notifyUnlockedChanged() {
diff --git a/packages/SystemUI/src/com/android/systemui/tuner/TunerServiceImpl.java b/packages/SystemUI/src/com/android/systemui/tuner/TunerServiceImpl.java
index 8087a87..550a65c 100644
--- a/packages/SystemUI/src/com/android/systemui/tuner/TunerServiceImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/tuner/TunerServiceImpl.java
@@ -48,6 +48,8 @@
 import com.android.systemui.statusbar.phone.SystemUIDialog;
 import com.android.systemui.util.leak.LeakDetector;
 
+import dagger.Lazy;
+
 import java.util.HashSet;
 import java.util.Set;
 import java.util.concurrent.ConcurrentHashMap;
@@ -87,6 +89,7 @@
     // Set of all tunables, used for leak detection.
     private final HashSet<Tunable> mTunables = LeakDetector.ENABLED ? new HashSet<>() : null;
     private final Context mContext;
+    private final Lazy<SystemUIDialog.Factory> mSystemUIDialogFactoryLazy;
     private final LeakDetector mLeakDetector;
     private final DemoModeController mDemoModeController;
 
@@ -104,9 +107,11 @@
             @Main Handler mainHandler,
             LeakDetector leakDetector,
             DemoModeController demoModeController,
-            UserTracker userTracker) {
+            UserTracker userTracker,
+            Lazy<SystemUIDialog.Factory> systemUIDialogFactoryLazy) {
         super(context);
         mContext = context;
+        mSystemUIDialogFactoryLazy = systemUIDialogFactoryLazy;
         mContentResolver = mContext.getContentResolver();
         mLeakDetector = leakDetector;
         mDemoModeController = demoModeController;
@@ -301,7 +306,7 @@
 
     @Override
     public void showResetRequest(Runnable onDisabled) {
-        SystemUIDialog dialog = new SystemUIDialog(mContext);
+        SystemUIDialog dialog = mSystemUIDialogFactoryLazy.get().create();
         dialog.setShowForAllUsers(true);
         dialog.setMessage(R.string.remove_from_settings_prompt);
         dialog.setButton(DialogInterface.BUTTON_NEGATIVE, mContext.getString(R.string.cancel),
diff --git a/packages/SystemUI/src/com/android/systemui/unfold/DisplaySwitchLatencyLogger.kt b/packages/SystemUI/src/com/android/systemui/unfold/DisplaySwitchLatencyLogger.kt
new file mode 100644
index 0000000..76f7609
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/unfold/DisplaySwitchLatencyLogger.kt
@@ -0,0 +1,55 @@
+/*
+ * 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.unfold
+
+import com.android.systemui.shared.system.SysUiStatsLog
+
+class DisplaySwitchLatencyLogger {
+
+    /**
+     * Based on data present in [displaySwitchLatencyEvent], logs metrics for atom
+     * [DisplaySwitchLatencyTracked]
+     */
+    fun log(displaySwitchLatencyEvent: DisplaySwitchLatencyTracker.DisplaySwitchLatencyEvent) {
+        with(displaySwitchLatencyEvent) {
+            SysUiStatsLog.write(
+                SysUiStatsLog.DISPLAY_SWITCH_LATENCY_TRACKED,
+                latencyMs,
+                fromFoldableDeviceState,
+                fromState,
+                fromFocusedAppUid,
+                fromPipAppUid,
+                fromVisibleAppsUid.toIntArray(),
+                fromDensityDpi,
+                toState,
+                toFoldableDeviceState,
+                toFocusedAppUid,
+                toPipAppUid,
+                toVisibleAppsUid.toIntArray(),
+                toDensityDpi,
+                notificationCount,
+                externalDisplayCount,
+                throttlingLevel,
+                vskinTemperatureC,
+                hallSensorToFirstHingeAngleChangeMs,
+                hallSensorToDeviceStateChangeMs,
+                onScreenTurningOnToOnDrawnMs,
+                onDrawnToOnScreenTurnedOnMs,
+            )
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/unfold/DisplaySwitchLatencyTracker.kt b/packages/SystemUI/src/com/android/systemui/unfold/DisplaySwitchLatencyTracker.kt
new file mode 100644
index 0000000..92a64a6
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/unfold/DisplaySwitchLatencyTracker.kt
@@ -0,0 +1,248 @@
+/*
+ * 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.unfold
+
+import android.content.Context
+import android.util.Log
+import com.android.app.tracing.TraceUtils.instantForTrack
+import com.android.app.tracing.TraceUtils.traceAsync
+import com.android.systemui.CoreStartable
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.display.data.repository.DeviceStateRepository
+import com.android.systemui.display.data.repository.DeviceStateRepository.DeviceState
+import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
+import com.android.systemui.power.domain.interactor.PowerInteractor
+import com.android.systemui.power.shared.model.ScreenPowerState
+import com.android.systemui.power.shared.model.WakeSleepReason
+import com.android.systemui.shared.system.SysUiStatsLog
+import com.android.systemui.unfold.dagger.UnfoldSingleThreadBg
+import com.android.systemui.unfold.domain.interactor.UnfoldTransitionInteractor
+import com.android.systemui.util.Compile
+import com.android.systemui.util.Utils.isDeviceFoldable
+import com.android.systemui.util.animation.data.repository.AnimationStatusRepository
+import com.android.systemui.util.kotlin.pairwise
+import com.android.systemui.util.time.SystemClock
+import com.android.systemui.util.time.measureTimeMillis
+import java.util.concurrent.Executor
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.asCoroutineDispatcher
+import kotlinx.coroutines.flow.filter
+import kotlinx.coroutines.flow.first
+import kotlinx.coroutines.flow.flatMapLatest
+import kotlinx.coroutines.flow.flow
+import kotlinx.coroutines.launch
+
+/**
+ * [DisplaySwitchLatencyTracker] tracks latency and related fields for display switch of a foldable
+ * device. This class populates [DisplaySwitchLatencyEvent] while an ongoing display switch event
+ */
+@SysUISingleton
+class DisplaySwitchLatencyTracker
+@Inject
+constructor(
+    private val context: Context,
+    private val deviceStateRepository: DeviceStateRepository,
+    private val powerInteractor: PowerInteractor,
+    private val unfoldTransitionInteractor: UnfoldTransitionInteractor,
+    private val animationStatusRepository: AnimationStatusRepository,
+    private val keyguardInteractor: KeyguardInteractor,
+    @UnfoldSingleThreadBg private val singleThreadBgExecutor: Executor,
+    @Application private val applicationScope: CoroutineScope,
+    private val displaySwitchLatencyLogger: DisplaySwitchLatencyLogger,
+    private val systemClock: SystemClock
+) : CoreStartable {
+
+    private val backgroundDispatcher = singleThreadBgExecutor.asCoroutineDispatcher()
+
+    @OptIn(ExperimentalCoroutinesApi::class)
+    override fun start() {
+        if (!isDeviceFoldable(context)) {
+            return
+        }
+        applicationScope.launch(backgroundDispatcher) {
+            deviceStateRepository.state
+                .pairwise()
+                .filter {
+                    // Start tracking only when the foldable device is
+                    //folding(UNFOLDED/HALF_FOLDED -> FOLDED) or
+                    //unfolding(FOLDED -> HALF_FOLD/UNFOLDED)
+                    foldableDeviceState ->
+                    foldableDeviceState.previousValue == DeviceState.FOLDED ||
+                        foldableDeviceState.newValue == DeviceState.FOLDED
+                }
+                .flatMapLatest { foldableDeviceState ->
+                    flow {
+                        var displaySwitchLatencyEvent = DisplaySwitchLatencyEvent()
+                        val toFoldableDeviceState = foldableDeviceState.newValue.toStatsInt()
+                        displaySwitchLatencyEvent =
+                            displaySwitchLatencyEvent.withBeforeFields(
+                                foldableDeviceState.previousValue.toStatsInt()
+                            )
+
+                        val displaySwitchTimeMs =
+                            measureTimeMillis(systemClock) {
+                                traceAsync(TAG, "displaySwitch") {
+                                    waitForDisplaySwitch(toFoldableDeviceState)
+                                }
+                            }
+
+                        displaySwitchLatencyEvent =
+                            displaySwitchLatencyEvent.withAfterFields(
+                                toFoldableDeviceState,
+                                displaySwitchTimeMs.toInt(),
+                                getCurrentState()
+                            )
+                        emit(displaySwitchLatencyEvent)
+                    }
+                }
+                .collect { displaySwitchLatencyLogger.log(it) }
+        }
+    }
+
+    private fun DeviceState.toStatsInt(): Int =
+        when (this) {
+            DeviceState.FOLDED -> FOLDABLE_DEVICE_STATE_CLOSED
+            DeviceState.HALF_FOLDED -> FOLDABLE_DEVICE_STATE_HALF_OPEN
+            DeviceState.UNFOLDED -> FOLDABLE_DEVICE_STATE_OPEN
+            DeviceState.CONCURRENT_DISPLAY -> FOLDABLE_DEVICE_STATE_FLIPPED
+            else -> FOLDABLE_DEVICE_STATE_UNKNOWN
+        }
+
+    private suspend fun waitForDisplaySwitch(toFoldableDeviceState: Int) {
+        val isTransitionEnabled =
+            unfoldTransitionInteractor.isAvailable &&
+                animationStatusRepository.areAnimationsEnabled().first()
+        if (shouldWaitForScreenOn(toFoldableDeviceState, isTransitionEnabled)) {
+            waitForScreenTurnedOn()
+        } else {
+            traceAsync(TAG, "waitForTransitionStart()") {
+                unfoldTransitionInteractor.waitForTransitionStart()
+            }
+        }
+    }
+
+    private fun shouldWaitForScreenOn(
+        toFoldableDeviceState: Int,
+        isTransitionEnabled: Boolean
+    ): Boolean = (toFoldableDeviceState == FOLDABLE_DEVICE_STATE_CLOSED || !isTransitionEnabled)
+
+    private suspend fun waitForScreenTurnedOn() {
+        traceAsync(TAG, "waitForScreenTurnedOn()") {
+            powerInteractor.screenPowerState.filter { it == ScreenPowerState.SCREEN_ON }.first()
+        }
+    }
+
+    private fun getCurrentState(): Int =
+        when {
+            isStateAod() -> SysUiStatsLog.DISPLAY_SWITCH_LATENCY_TRACKED__TO_STATE__AOD
+            else -> SysUiStatsLog.DISPLAY_SWITCH_LATENCY_TRACKED__TO_STATE__UNKNOWN
+        }
+
+    private fun isStateAod(): Boolean {
+        val lastWakefulnessEvent = powerInteractor.detailedWakefulness.value
+        val isAodEnabled = keyguardInteractor.isAodAvailable.value
+
+        return (lastWakefulnessEvent.isAsleep() &&
+            (lastWakefulnessEvent.lastSleepReason == WakeSleepReason.FOLD) &&
+            isAodEnabled)
+    }
+
+    private inline fun log(msg: () -> String) {
+        if (DEBUG) Log.d(TAG, msg())
+    }
+
+    private fun DisplaySwitchLatencyEvent.withBeforeFields(
+        fromFoldableDeviceState: Int
+    ): DisplaySwitchLatencyEvent {
+        log { "fromFoldableDeviceState=$fromFoldableDeviceState" }
+        instantForTrack(TAG, "fromFoldableDeviceState=$fromFoldableDeviceState")
+
+        return copy(fromFoldableDeviceState = fromFoldableDeviceState)
+    }
+
+    private fun DisplaySwitchLatencyEvent.withAfterFields(
+        toFoldableDeviceState: Int,
+        displaySwitchTimeMs: Int,
+        toState: Int
+    ): DisplaySwitchLatencyEvent {
+        log {
+            "toFoldableDeviceState=$toFoldableDeviceState, " +
+                "toState=$toState, " +
+                "latencyMs=$displaySwitchTimeMs"
+        }
+        instantForTrack(TAG, "toFoldableDeviceState=$toFoldableDeviceState, toState=$toState")
+
+        return copy(
+            toFoldableDeviceState = toFoldableDeviceState,
+            latencyMs = displaySwitchTimeMs,
+            toState = toState
+        )
+    }
+
+    /**
+     * Stores values corresponding to all respective [DisplaySwitchLatencyTrackedField] in a single
+     * event of display switch for foldable devices.
+     *
+     * Once the data is captured in this data class and appropriate to log, it is logged through
+     * [DisplaySwitchLatencyLogger]
+     */
+    data class DisplaySwitchLatencyEvent(
+        val latencyMs: Int = VALUE_UNKNOWN,
+        val fromFoldableDeviceState: Int = FOLDABLE_DEVICE_STATE_UNKNOWN,
+        val fromState: Int = SysUiStatsLog.DISPLAY_SWITCH_LATENCY_TRACKED__FROM_STATE__UNKNOWN,
+        val fromFocusedAppUid: Int = VALUE_UNKNOWN,
+        val fromPipAppUid: Int = VALUE_UNKNOWN,
+        val fromVisibleAppsUid: Set<Int> = setOf(),
+        val fromDensityDpi: Int = VALUE_UNKNOWN,
+        val toFoldableDeviceState: Int = FOLDABLE_DEVICE_STATE_UNKNOWN,
+        val toState: Int = SysUiStatsLog.DISPLAY_SWITCH_LATENCY_TRACKED__FROM_STATE__UNKNOWN,
+        val toFocusedAppUid: Int = VALUE_UNKNOWN,
+        val toPipAppUid: Int = VALUE_UNKNOWN,
+        val toVisibleAppsUid: Set<Int> = setOf(),
+        val toDensityDpi: Int = VALUE_UNKNOWN,
+        val notificationCount: Int = VALUE_UNKNOWN,
+        val externalDisplayCount: Int = VALUE_UNKNOWN,
+        val throttlingLevel: Int =
+            SysUiStatsLog.DISPLAY_SWITCH_LATENCY_TRACKED__THROTTLING_LEVEL__NONE,
+        val vskinTemperatureC: Int = VALUE_UNKNOWN,
+        val hallSensorToFirstHingeAngleChangeMs: Int = VALUE_UNKNOWN,
+        val hallSensorToDeviceStateChangeMs: Int = VALUE_UNKNOWN,
+        val onScreenTurningOnToOnDrawnMs: Int = VALUE_UNKNOWN,
+        val onDrawnToOnScreenTurnedOnMs: Int = VALUE_UNKNOWN
+    )
+
+    companion object {
+        private const val VALUE_UNKNOWN = -1
+        private const val TAG = "DisplaySwitchLatency"
+        private val DEBUG = Compile.IS_DEBUG && Log.isLoggable(TAG, Log.VERBOSE)
+
+        private const val FOLDABLE_DEVICE_STATE_UNKNOWN =
+            SysUiStatsLog.DISPLAY_SWITCH_LATENCY_TRACKED__FROM_FOLDABLE_DEVICE_STATE__STATE_UNKNOWN
+        const val FOLDABLE_DEVICE_STATE_CLOSED =
+            SysUiStatsLog.DISPLAY_SWITCH_LATENCY_TRACKED__FROM_FOLDABLE_DEVICE_STATE__STATE_CLOSED
+        const val FOLDABLE_DEVICE_STATE_HALF_OPEN =
+            SysUiStatsLog
+                .DISPLAY_SWITCH_LATENCY_TRACKED__FROM_FOLDABLE_DEVICE_STATE__STATE_HALF_OPENED
+        private const val FOLDABLE_DEVICE_STATE_OPEN =
+            SysUiStatsLog.DISPLAY_SWITCH_LATENCY_TRACKED__FROM_FOLDABLE_DEVICE_STATE__STATE_OPENED
+        private const val FOLDABLE_DEVICE_STATE_FLIPPED =
+            SysUiStatsLog.DISPLAY_SWITCH_LATENCY_TRACKED__FROM_FOLDABLE_DEVICE_STATE__STATE_FLIPPED
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/unfold/UnfoldTraceLogger.kt b/packages/SystemUI/src/com/android/systemui/unfold/UnfoldTraceLogger.kt
index 94912bf8..adf50a1 100644
--- a/packages/SystemUI/src/com/android/systemui/unfold/UnfoldTraceLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/unfold/UnfoldTraceLogger.kt
@@ -22,8 +22,8 @@
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.unfold.data.repository.FoldStateRepository
 import com.android.systemui.unfold.system.DeviceStateRepository
-import com.android.systemui.unfold.updates.FoldStateRepository
 import javax.inject.Inject
 import kotlin.coroutines.CoroutineContext
 import kotlinx.coroutines.CoroutineScope
diff --git a/packages/SystemUI/src/com/android/systemui/unfold/UnfoldTransitionModule.kt b/packages/SystemUI/src/com/android/systemui/unfold/UnfoldTransitionModule.kt
index 50515da..8bef53c 100644
--- a/packages/SystemUI/src/com/android/systemui/unfold/UnfoldTransitionModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/unfold/UnfoldTransitionModule.kt
@@ -27,6 +27,8 @@
 import com.android.systemui.unfold.config.UnfoldTransitionConfig
 import com.android.systemui.unfold.dagger.UnfoldBgProgressFlag
 import com.android.systemui.unfold.dagger.UnfoldMain
+import com.android.systemui.unfold.data.repository.FoldStateRepository
+import com.android.systemui.unfold.data.repository.FoldStateRepositoryImpl
 import com.android.systemui.unfold.data.repository.UnfoldTransitionRepository
 import com.android.systemui.unfold.data.repository.UnfoldTransitionRepositoryImpl
 import com.android.systemui.unfold.domain.interactor.UnfoldTransitionInteractor
@@ -168,6 +170,11 @@
     @Provides
     fun screenStatusProvider(impl: LifecycleScreenStatusProvider): ScreenStatusProvider = impl
 
+    @Provides
+    @Singleton
+    fun provideDisplaySwitchLatencyLogger(): DisplaySwitchLatencyLogger =
+        DisplaySwitchLatencyLogger()
+
     @Module
     interface Bindings {
         @Binds
@@ -178,6 +185,8 @@
         @Binds fun bindRepository(impl: UnfoldTransitionRepositoryImpl): UnfoldTransitionRepository
 
         @Binds fun bindInteractor(impl: UnfoldTransitionInteractorImpl): UnfoldTransitionInteractor
+
+        @Binds fun bindFoldStateRepository(impl: FoldStateRepositoryImpl): FoldStateRepository
     }
 }
 
diff --git a/packages/SystemUI/unfold/src/com/android/systemui/unfold/updates/FoldStateRepository.kt b/packages/SystemUI/src/com/android/systemui/unfold/data/repository/FoldStateRepository.kt
similarity index 84%
rename from packages/SystemUI/unfold/src/com/android/systemui/unfold/updates/FoldStateRepository.kt
rename to packages/SystemUI/src/com/android/systemui/unfold/data/repository/FoldStateRepository.kt
index 61b0b40..04b00ca 100644
--- a/packages/SystemUI/unfold/src/com/android/systemui/unfold/updates/FoldStateRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/unfold/data/repository/FoldStateRepository.kt
@@ -13,9 +13,15 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package com.android.systemui.unfold.updates
+package com.android.systemui.unfold.data.repository
 
-import com.android.systemui.unfold.updates.FoldStateRepository.FoldUpdate
+import com.android.systemui.unfold.data.repository.FoldStateRepository.FoldUpdate
+import com.android.systemui.unfold.updates.FOLD_UPDATE_FINISH_CLOSED
+import com.android.systemui.unfold.updates.FOLD_UPDATE_FINISH_FULL_OPEN
+import com.android.systemui.unfold.updates.FOLD_UPDATE_FINISH_HALF_OPEN
+import com.android.systemui.unfold.updates.FOLD_UPDATE_START_CLOSING
+import com.android.systemui.unfold.updates.FOLD_UPDATE_START_OPENING
+import com.android.systemui.unfold.updates.FoldStateProvider
 import javax.inject.Inject
 import kotlinx.coroutines.channels.Channel
 import kotlinx.coroutines.channels.awaitClose
@@ -50,7 +56,7 @@
                     FOLD_UPDATE_FINISH_HALF_OPEN -> FINISH_HALF_OPEN
                     FOLD_UPDATE_FINISH_FULL_OPEN -> FINISH_FULL_OPEN
                     FOLD_UPDATE_FINISH_CLOSED -> FINISH_CLOSED
-                    else -> error("FoldUpdateNotFound")
+                    else -> error("Fold update with id $oldId is not supported")
                 }
             }
         }
diff --git a/packages/SystemUI/src/com/android/systemui/unfold/domain/interactor/UnfoldTransitionInteractor.kt b/packages/SystemUI/src/com/android/systemui/unfold/domain/interactor/UnfoldTransitionInteractor.kt
index a2e77af..3e2e564 100644
--- a/packages/SystemUI/src/com/android/systemui/unfold/domain/interactor/UnfoldTransitionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/unfold/domain/interactor/UnfoldTransitionInteractor.kt
@@ -15,16 +15,26 @@
  */
 package com.android.systemui.unfold.domain.interactor
 
-import com.android.systemui.unfold.data.repository.UnfoldTransitionStatus.TransitionFinished
 import com.android.systemui.unfold.data.repository.UnfoldTransitionRepository
+import com.android.systemui.unfold.data.repository.UnfoldTransitionStatus.TransitionFinished
+import com.android.systemui.unfold.data.repository.UnfoldTransitionStatus.TransitionStarted
 import javax.inject.Inject
 import kotlinx.coroutines.flow.filter
 import kotlinx.coroutines.flow.first
 
+/**
+ * Contains business-logic related to fold-unfold transitions while interacting with
+ * [UnfoldTransitionRepository]
+ */
 interface UnfoldTransitionInteractor {
+    /** Returns availability of fold/unfold transitions on the device */
     val isAvailable: Boolean
 
+    /** Suspends and waits for a fold/unfold transition to finish */
     suspend fun waitForTransitionFinish()
+
+    /** Suspends and waits for a fold/unfold transition to start */
+    suspend fun waitForTransitionStart()
 }
 
 class UnfoldTransitionInteractorImpl
@@ -37,4 +47,8 @@
     override suspend fun waitForTransitionFinish() {
         repository.transitionStatus.filter { it is TransitionFinished }.first()
     }
+
+    override suspend fun waitForTransitionStart() {
+        repository.transitionStatus.filter { it is TransitionStarted }.first()
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/util/Utils.java b/packages/SystemUI/src/com/android/systemui/util/Utils.java
index fa6d055..7861ded 100644
--- a/packages/SystemUI/src/com/android/systemui/util/Utils.java
+++ b/packages/SystemUI/src/com/android/systemui/util/Utils.java
@@ -82,6 +82,14 @@
     }
 
     /**
+     * Returns {@code true} if the device is a foldable device
+     */
+    public static boolean isDeviceFoldable(Context context) {
+        return context.getResources()
+                .getIntArray(com.android.internal.R.array.config_foldedDeviceStates).length != 0;
+    }
+
+    /**
      * Allow the media player to be shown in the QS area, controlled by 2 flags.
      * On by default, but can be disabled by setting either flag to 0/false.
      */
diff --git a/packages/SystemUI/src/com/android/systemui/util/time/MeasureTimeUtil.kt b/packages/SystemUI/src/com/android/systemui/util/time/MeasureTimeUtil.kt
new file mode 100644
index 0000000..f131968
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/util/time/MeasureTimeUtil.kt
@@ -0,0 +1,32 @@
+/*
+ * 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.util.time
+
+import kotlin.contracts.ExperimentalContracts
+import kotlin.contracts.InvocationKind
+import kotlin.contracts.contract
+
+/**
+ * Executes the given [block] and returns elapsed time using provided [systemClock] in milliseconds.
+ */
+@OptIn(ExperimentalContracts::class)
+inline fun measureTimeMillis(systemClock: SystemClock, block: () -> Unit): Long {
+    contract { callsInPlace(block, InvocationKind.EXACTLY_ONCE) }
+    val start = systemClock.currentTimeMillis()
+    block()
+    return systemClock.currentTimeMillis() - start
+}
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
index d03a898..c5ce856 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
@@ -30,6 +30,7 @@
 import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.SOME_AUTH_REQUIRED_AFTER_USER_REQUEST;
 import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN;
 import static com.android.keyguard.KeyguardUpdateMonitor.BIOMETRIC_STATE_CANCELLING_RESTARTING;
+import static com.android.keyguard.KeyguardUpdateMonitor.BIOMETRIC_STATE_STOPPED;
 import static com.android.keyguard.KeyguardUpdateMonitor.DEFAULT_CANCEL_SIGNAL_TIMEOUT;
 import static com.android.keyguard.KeyguardUpdateMonitor.HAL_POWER_PRESS_TIMEOUT;
 import static com.android.systemui.statusbar.policy.DevicePostureController.DEVICE_POSTURE_OPENED;
@@ -1973,6 +1974,24 @@
     }
 
     @Test
+    public void detectFingerprint_onSuccess_biometricStateStopped() {
+        // GIVEN FP detection is running
+        givenDetectFingerprintWithClearingFingerprintManagerInvocations();
+
+        // WHEN detection is successful
+        ArgumentCaptor<FingerprintManager.FingerprintDetectionCallback> fpDetectCallbackCaptor =
+                ArgumentCaptor.forClass(FingerprintManager.FingerprintDetectionCallback.class);
+        verify(mFingerprintManager).detectFingerprint(
+                any(), fpDetectCallbackCaptor.capture(), any());
+        fpDetectCallbackCaptor.getValue().onFingerprintDetected(0, 0, true);
+        mTestableLooper.processAllMessages();
+
+        // THEN fingerprint detect state should immediately update to STOPPED
+        assertThat(mKeyguardUpdateMonitor.mFingerprintRunningState)
+                .isEqualTo(BIOMETRIC_STATE_STOPPED);
+    }
+
+    @Test
     public void testFingerprintSensorProperties() throws RemoteException {
         mFingerprintAuthenticatorsRegisteredCallback.onAllAuthenticatorsRegistered(
                 new ArrayList<>());
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/mediator/ScreenOnCoordinatorTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/mediator/ScreenOnCoordinatorTest.kt
index 9fe32f1..b45c894 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/mediator/ScreenOnCoordinatorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/keyguard/mediator/ScreenOnCoordinatorTest.kt
@@ -16,16 +16,21 @@
 
 package com.android.keyguard.mediator
 
-import android.os.Handler
 import android.os.Looper
+import android.platform.test.flag.junit.SetFlagsRule
+import android.platform.test.flag.junit.SetFlagsRule.DefaultInitValueType.DEVICE_DEFAULT
 import android.testing.AndroidTestingRunner
 import androidx.test.filters.SmallTest
+import com.android.systemui.Flags
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.unfold.FoldAodAnimationController
 import com.android.systemui.unfold.SysUIUnfoldComponent
 import com.android.systemui.unfold.UnfoldLightRevealOverlayAnimation
 import com.android.systemui.util.mockito.capture
+import com.android.systemui.utils.os.FakeHandler
+import com.android.systemui.utils.os.FakeHandler.Mode.QUEUEING
 import org.junit.Before
+import org.junit.Rule
 import org.junit.Test
 import org.junit.runner.RunWith
 import org.mockito.ArgumentCaptor
@@ -52,10 +57,13 @@
     @Captor
     private lateinit var readyCaptor: ArgumentCaptor<Runnable>
 
-    private val testHandler = Handler(Looper.getMainLooper())
+    private val testHandler = FakeHandler(Looper.getMainLooper()).apply { setMode(QUEUEING) }
 
     private lateinit var screenOnCoordinator: ScreenOnCoordinator
 
+    @get:Rule
+    val setFlagsRule = SetFlagsRule(DEVICE_DEFAULT)
+
     @Before
     fun setUp() {
         MockitoAnnotations.initMocks(this)
@@ -77,7 +85,7 @@
 
         onUnfoldOverlayReady()
         onFoldAodReady()
-        waitHandlerIdle(testHandler)
+        waitHandlerIdle()
 
         // Should be called when both unfold overlay and keyguard drawn ready
         verify(runnable).run()
@@ -90,7 +98,7 @@
 
         onUnfoldOverlayReady()
         onFoldAodReady()
-        waitHandlerIdle(testHandler)
+        waitHandlerIdle()
 
         // Should be called when both unfold overlay and keyguard drawn ready
         verify(runnable).run()
@@ -104,7 +112,8 @@
 
         onUnfoldOverlayReady()
         onFoldAodReady()
-        waitHandlerIdle(testHandler)
+        waitHandlerIdle()
+
 
         // Should not be called because this screen turning on call is not valid anymore
         verify(runnable, never()).run()
@@ -112,13 +121,43 @@
 
     @Test
     fun testUnfoldTransitionDisabledDrawnTasksReady_onScreenTurningOn_callsDrawnCallback() {
+        setFlagsRule.disableFlags(Flags.FLAG_ENABLE_BACKGROUND_KEYGUARD_ONDRAWN_CALLBACK)
         // Recreate with empty unfoldComponent
         screenOnCoordinator = ScreenOnCoordinator(
             Optional.empty(),
             testHandler
         )
         screenOnCoordinator.onScreenTurningOn(runnable)
-        waitHandlerIdle(testHandler)
+        waitHandlerIdle()
+
+        // Should be called when only keyguard drawn
+        verify(runnable).run()
+    }
+    @Test
+    fun testUnfoldTransitionDisabledDrawnTasksReady_onScreenTurningOn_usesMainHandler() {
+        setFlagsRule.disableFlags(Flags.FLAG_ENABLE_BACKGROUND_KEYGUARD_ONDRAWN_CALLBACK)
+        // Recreate with empty unfoldComponent
+        screenOnCoordinator = ScreenOnCoordinator(
+                Optional.empty(),
+                testHandler
+        )
+        screenOnCoordinator.onScreenTurningOn(runnable)
+
+        // Never called as the main handler didn't schedule it yet.
+        verify(runnable, never()).run()
+    }
+
+    @Test
+    fun unfoldTransitionDisabledDrawnTasksReady_onScreenTurningOn_bgCallback_callsDrawnCallback() {
+        setFlagsRule.enableFlags(Flags.FLAG_ENABLE_BACKGROUND_KEYGUARD_ONDRAWN_CALLBACK)
+        // Recreate with empty unfoldComponent
+        screenOnCoordinator = ScreenOnCoordinator(
+                Optional.empty(),
+                testHandler
+        )
+        screenOnCoordinator.onScreenTurningOn(runnable)
+        // No need to wait for the handler to be idle, as it shouldn't be used
+        // waitHandlerIdle()
 
         // Should be called when only keyguard drawn
         verify(runnable).run()
@@ -134,7 +173,7 @@
         readyCaptor.value.run()
     }
 
-    private fun waitHandlerIdle(handler: Handler) {
-        handler.runWithScissors({},  /* timeout= */ 0)
+    private fun waitHandlerIdle() {
+        testHandler.dispatchQueuedMessages()
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/fontscaling/FontScalingDialogDelegateTest.kt b/packages/SystemUI/tests/src/com/android/systemui/accessibility/fontscaling/FontScalingDialogDelegateTest.kt
index bfb5485..c525711 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/fontscaling/FontScalingDialogDelegateTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/fontscaling/FontScalingDialogDelegateTest.kt
@@ -120,7 +120,7 @@
             fontScalingDialogDelegate
         )
 
-        whenever(dialogFactory.create(any())).thenReturn(dialog)
+        whenever(dialogFactory.create(any(), any())).thenReturn(dialog)
     }
 
     @Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/binder/SideFpsOverlayViewBinderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/binder/SideFpsOverlayViewBinderTest.kt
index cb26178..755fa02 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/binder/SideFpsOverlayViewBinderTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/binder/SideFpsOverlayViewBinderTest.kt
@@ -75,6 +75,7 @@
 import com.android.systemui.user.domain.interactor.SelectedUserInteractor
 import com.android.systemui.util.concurrency.FakeExecutor
 import com.android.systemui.util.mockito.eq
+import com.android.systemui.util.mockito.mock
 import com.android.systemui.util.mockito.whenever
 import com.android.systemui.util.time.FakeSystemClock
 import java.util.Optional
@@ -82,6 +83,7 @@
 import kotlinx.coroutines.flow.MutableStateFlow
 import kotlinx.coroutines.test.StandardTestDispatcher
 import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.UnconfinedTestDispatcher
 import kotlinx.coroutines.test.runCurrent
 import kotlinx.coroutines.test.runTest
 import org.junit.Before
@@ -235,15 +237,18 @@
                 windowManager,
                 displayStateInteractor,
                 Optional.of(fingerprintInteractiveToAuthProvider),
+                mock(),
                 SideFpsLogger(logcatLogBuffer("SfpsLogger"))
             )
 
         sideFpsProgressBarViewModel =
             SideFpsProgressBarViewModel(
                 mContext,
-                deviceEntryFingerprintAuthRepository,
+                mock(),
                 sfpsSensorInteractor,
+                mock(),
                 displayStateInteractor,
+                UnconfinedTestDispatcher(),
                 testScope.backgroundScope,
             )
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/SideFpsOverlayViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/SideFpsOverlayViewModelTest.kt
index 823b952..bdca948 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/SideFpsOverlayViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/SideFpsOverlayViewModelTest.kt
@@ -72,6 +72,7 @@
 import com.android.systemui.unfold.compat.ScreenSizeFoldProvider
 import com.android.systemui.user.domain.interactor.SelectedUserInteractor
 import com.android.systemui.util.concurrency.FakeExecutor
+import com.android.systemui.util.mockito.mock
 import com.android.systemui.util.mockito.whenever
 import com.android.systemui.util.time.FakeSystemClock
 import com.google.common.truth.Truth.assertThat
@@ -238,15 +239,18 @@
                 windowManager,
                 displayStateInteractor,
                 Optional.of(fingerprintInteractiveToAuthProvider),
+                mock(),
                 SideFpsLogger(logcatLogBuffer("SfpsLogger"))
             )
 
         sideFpsProgressBarViewModel =
             SideFpsProgressBarViewModel(
                 mContext,
-                deviceEntryFingerprintAuthRepository,
+                mock(),
                 sfpsSensorInteractor,
+                mock(),
                 displayStateInteractor,
+                StandardTestDispatcher(),
                 testScope.backgroundScope,
             )
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bluetooth/BroadcastDialogDelegateTest.java b/packages/SystemUI/tests/src/com/android/systemui/bluetooth/BroadcastDialogDelegateTest.java
index 4022d43..3ff43c6 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/bluetooth/BroadcastDialogDelegateTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/bluetooth/BroadcastDialogDelegateTest.java
@@ -28,8 +28,6 @@
 
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper;
-import android.view.LayoutInflater;
-import android.view.View;
 import android.widget.Button;
 import android.widget.TextView;
 
@@ -95,7 +93,7 @@
 
         mFeatureFlags.set(Flags.WM_ENABLE_PREDICTIVE_BACK_QS_DIALOG_ANIM, true);
         when(mSysUiState.setFlag(anyInt(), anyBoolean())).thenReturn(mSysUiState);
-        when(mSystemUIDialogFactory.create(any())).thenReturn(mDialog);
+        when(mSystemUIDialogFactory.create(any(), any())).thenReturn(mDialog);
 
         mBroadcastDialogDelegate = new BroadcastDialogDelegate(
                 mContext,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/contrast/ContrastDialogDelegateTest.kt b/packages/SystemUI/tests/src/com/android/systemui/contrast/ContrastDialogDelegateTest.kt
index 65f68f9..35ac2ae 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/contrast/ContrastDialogDelegateTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/contrast/ContrastDialogDelegateTest.kt
@@ -31,6 +31,7 @@
 import com.android.systemui.flags.FeatureFlags
 import com.android.systemui.model.SysUiState
 import com.android.systemui.settings.UserTracker
+import com.android.systemui.statusbar.phone.DialogDelegate
 import com.android.systemui.statusbar.phone.SystemUIDialog
 import com.android.systemui.util.concurrency.FakeExecutor
 import com.android.systemui.util.mockito.any
@@ -69,7 +70,8 @@
         mDependency.injectTestDependency(SysUiState::class.java, sysuiState)
         mDependency.injectMockDependency(DialogLaunchAnimator::class.java)
         whenever(sysuiState.setFlag(any(), any())).thenReturn(sysuiState)
-        whenever(sysuiDialogFactory.create(any())).thenReturn(sysuiDialog)
+        whenever(sysuiDialogFactory.create(any(SystemUIDialog.Delegate::class.java)))
+            .thenReturn(sysuiDialog)
         whenever(sysuiDialog.layoutInflater).thenReturn(LayoutInflater.from(mContext))
 
         whenever(mockUserTracker.userId).thenReturn(context.userId)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/management/PanelConfirmationDialogFactoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/controls/management/PanelConfirmationDialogFactoryTest.kt
index 4e8f866..7f0ea9a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/controls/management/PanelConfirmationDialogFactoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/controls/management/PanelConfirmationDialogFactoryTest.kt
@@ -17,34 +17,48 @@
 
 package com.android.systemui.controls.management
 
+import android.content.Context
 import android.content.DialogInterface
 import android.testing.AndroidTestingRunner
 import androidx.test.filters.SmallTest
-import com.android.systemui.res.R
 import com.android.systemui.SysuiTestCase
+import com.android.systemui.res.R
 import com.android.systemui.statusbar.phone.SystemUIDialog
+import com.android.systemui.util.mockito.any
 import com.android.systemui.util.mockito.argumentCaptor
 import com.android.systemui.util.mockito.capture
-import com.android.systemui.util.mockito.eq
-import com.android.systemui.util.mockito.mock
 import com.google.common.truth.Truth.assertThat
+import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
 import org.mockito.ArgumentCaptor
+import org.mockito.ArgumentMatchers.eq
+import org.mockito.Mock
 import org.mockito.Mockito.verify
-import org.mockito.Mockito.`when`
+import org.mockito.MockitoAnnotations
+import org.mockito.Mockito.`when` as whenever
 
 @SmallTest
 @RunWith(AndroidTestingRunner::class)
 class PanelConfirmationDialogFactoryTest : SysuiTestCase() {
 
+    @Mock private lateinit var mockDialog : SystemUIDialog
+    @Mock private lateinit var mockDialogFactory : SystemUIDialog.Factory
+    private lateinit var factory : PanelConfirmationDialogFactory
+    @Before
+    fun setup() {
+        MockitoAnnotations.initMocks(this)
+
+        whenever(mockDialogFactory.create(any(Context::class.java))).thenReturn(mockDialog)
+        whenever(mockDialog.context).thenReturn(mContext)
+        factory = PanelConfirmationDialogFactory(mockDialogFactory)
+    }
+
     @Test
     fun testDialogHasCorrectInfo() {
-        val mockDialog: SystemUIDialog = mock() { `when`(context).thenReturn(mContext) }
-        val factory = PanelConfirmationDialogFactory { mockDialog }
         val appName = "appName"
 
-        factory.createConfirmationDialog(context, appName) {}
+        factory.createConfirmationDialog(mContext, appName) {}
 
         verify(mockDialog).setCanceledOnTouchOutside(true)
         verify(mockDialog)
@@ -55,12 +69,9 @@
 
     @Test
     fun testDialogPositiveButton() {
-        val mockDialog: SystemUIDialog = mock() { `when`(context).thenReturn(mContext) }
-        val factory = PanelConfirmationDialogFactory { mockDialog }
-
         var response: Boolean? = null
 
-        factory.createConfirmationDialog(context, "") { response = it }
+        factory.createConfirmationDialog(mContext,"") { response = it }
 
         val captor: ArgumentCaptor<DialogInterface.OnClickListener> = argumentCaptor()
         verify(mockDialog).setPositiveButton(eq(R.string.controls_dialog_ok), capture(captor))
@@ -72,12 +83,9 @@
 
     @Test
     fun testDialogNeutralButton() {
-        val mockDialog: SystemUIDialog = mock() { `when`(context).thenReturn(mContext) }
-        val factory = PanelConfirmationDialogFactory { mockDialog }
-
         var response: Boolean? = null
 
-        factory.createConfirmationDialog(context, "") { response = it }
+        factory.createConfirmationDialog(mContext, "") { response = it }
 
         val captor: ArgumentCaptor<DialogInterface.OnClickListener> = argumentCaptor()
         verify(mockDialog).setNeutralButton(eq(R.string.cancel), capture(captor))
@@ -89,12 +97,9 @@
 
     @Test
     fun testDialogCancel() {
-        val mockDialog: SystemUIDialog = mock() { `when`(context).thenReturn(mContext) }
-        val factory = PanelConfirmationDialogFactory { mockDialog }
-
         var response: Boolean? = null
 
-        factory.createConfirmationDialog(context, "") { response = it }
+        factory.createConfirmationDialog(mContext, "") { response = it }
 
         val captor: ArgumentCaptor<DialogInterface.OnCancelListener> = argumentCaptor()
         verify(mockDialog).setOnCancelListener(capture(captor))
diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/start/ControlsStartableTest.kt b/packages/SystemUI/tests/src/com/android/systemui/controls/start/ControlsStartableTest.kt
index 8f65fc8..bcef67e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/controls/start/ControlsStartableTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/controls/start/ControlsStartableTest.kt
@@ -24,19 +24,28 @@
 import android.content.IntentFilter
 import android.content.pm.ApplicationInfo
 import android.content.pm.ServiceInfo
+import android.os.UserHandle
 import android.os.UserManager
 import android.testing.AndroidTestingRunner
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.broadcast.BroadcastDispatcher
+import com.android.systemui.common.data.repository.fakePackageChangeRepository
+import com.android.systemui.common.data.repository.packageChangeRepository
+import com.android.systemui.common.data.shared.model.PackageChangeModel
 import com.android.systemui.controls.ControlsServiceInfo
 import com.android.systemui.controls.controller.ControlsController
 import com.android.systemui.controls.dagger.ControlsComponent
 import com.android.systemui.controls.management.ControlsListingController
 import com.android.systemui.controls.panels.AuthorizedPanelsRepository
 import com.android.systemui.controls.panels.FakeSelectedComponentRepository
+import com.android.systemui.controls.panels.SelectedComponentRepository
 import com.android.systemui.controls.ui.SelectedItem
+import com.android.systemui.kosmos.applicationCoroutineScope
+import com.android.systemui.kosmos.testDispatcher
+import com.android.systemui.kosmos.testScope
 import com.android.systemui.settings.UserTracker
+import com.android.systemui.testKosmos
 import com.android.systemui.util.concurrency.FakeExecutor
 import com.android.systemui.util.mockito.any
 import com.android.systemui.util.mockito.argumentCaptor
@@ -48,6 +57,9 @@
 import com.android.systemui.util.time.FakeSystemClock
 import com.google.common.truth.Truth.assertThat
 import java.util.Optional
+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
@@ -61,10 +73,13 @@
 import org.mockito.Mockito.`when`
 import org.mockito.MockitoAnnotations
 
+@OptIn(ExperimentalCoroutinesApi::class)
 @SmallTest
 @RunWith(AndroidTestingRunner::class)
 class ControlsStartableTest : SysuiTestCase() {
 
+    private val kosmos = testKosmos()
+
     @Mock private lateinit var controlsController: ControlsController
     @Mock private lateinit var controlsListingController: ControlsListingController
     @Mock private lateinit var userTracker: UserTracker
@@ -72,7 +87,7 @@
     @Mock private lateinit var userManager: UserManager
     @Mock private lateinit var broadcastDispatcher: BroadcastDispatcher
 
-    private val preferredPanelsRepository = FakeSelectedComponentRepository()
+    private lateinit var preferredPanelsRepository: FakeSelectedComponentRepository
 
     private lateinit var fakeExecutor: FakeExecutor
 
@@ -81,8 +96,10 @@
         MockitoAnnotations.initMocks(this)
         whenever(authorizedPanelsRepository.getPreferredPackages()).thenReturn(setOf())
         whenever(userManager.isUserUnlocked(anyInt())).thenReturn(true)
+        whenever(userTracker.userHandle).thenReturn(UserHandle.of(1))
 
         fakeExecutor = FakeExecutor(FakeSystemClock())
+        preferredPanelsRepository = FakeSelectedComponentRepository()
     }
 
     @Test
@@ -306,6 +323,100 @@
         verify(controlsController, never()).setPreferredSelection(any())
     }
 
+    @Test
+    fun testSelectedComponentIsUninstalled() =
+        with(kosmos) {
+            testScope.runTest {
+                val selectedComponent =
+                    SelectedComponentRepository.SelectedComponent(
+                        "panel",
+                        TEST_COMPONENT_PANEL,
+                        isPanel = true
+                    )
+                preferredPanelsRepository.setSelectedComponent(selectedComponent)
+                val activeUser = UserHandle.of(100)
+                whenever(userTracker.userHandle).thenReturn(activeUser)
+
+                createStartable(enabled = true).onBootCompleted()
+                fakeExecutor.runAllReady()
+                runCurrent()
+
+                assertThat(preferredPanelsRepository.getSelectedComponent())
+                    .isEqualTo(selectedComponent)
+                fakePackageChangeRepository.notifyChange(
+                    PackageChangeModel.Uninstalled(
+                        packageName = TEST_PACKAGE_PANEL,
+                        packageUid = UserHandle.getUid(100, 1)
+                    )
+                )
+                runCurrent()
+
+                assertThat(preferredPanelsRepository.getSelectedComponent()).isNull()
+            }
+        }
+
+    @Test
+    fun testSelectedComponentIsChanged() =
+        with(kosmos) {
+            testScope.runTest {
+                val selectedComponent =
+                    SelectedComponentRepository.SelectedComponent(
+                        "panel",
+                        TEST_COMPONENT_PANEL,
+                        isPanel = true
+                    )
+                preferredPanelsRepository.setSelectedComponent(selectedComponent)
+                val activeUser = UserHandle.of(100)
+                whenever(userTracker.userHandle).thenReturn(activeUser)
+
+                createStartable(enabled = true).onBootCompleted()
+                fakeExecutor.runAllReady()
+                runCurrent()
+
+                fakePackageChangeRepository.notifyChange(
+                    PackageChangeModel.Changed(
+                        packageName = TEST_PACKAGE_PANEL,
+                        packageUid = UserHandle.getUid(100, 1)
+                    )
+                )
+                runCurrent()
+
+                assertThat(preferredPanelsRepository.getSelectedComponent())
+                    .isEqualTo(selectedComponent)
+            }
+        }
+
+    @Test
+    fun testOtherPackageIsUninstalled() =
+        with(kosmos) {
+            testScope.runTest {
+                val selectedComponent =
+                    SelectedComponentRepository.SelectedComponent(
+                        "panel",
+                        TEST_COMPONENT_PANEL,
+                        isPanel = true
+                    )
+                preferredPanelsRepository.setSelectedComponent(selectedComponent)
+                val activeUser = UserHandle.of(100)
+                whenever(userTracker.userHandle).thenReturn(activeUser)
+
+                createStartable(enabled = true).onBootCompleted()
+                fakeExecutor.runAllReady()
+                runCurrent()
+
+                fakePackageChangeRepository.notifyChange(
+                    PackageChangeModel.Uninstalled(
+                        packageName = TEST_PACKAGE,
+                        packageUid = UserHandle.getUid(100, 1)
+                    )
+                )
+                runCurrent()
+
+                assertThat(preferredPanelsRepository.getSelectedComponent())
+                    .isEqualTo(selectedComponent)
+            }
+        }
+
     private fun setUpControlsListingControls(listings: List<ControlsServiceInfo>) {
         doAnswer { doReturn(listings).`when`(controlsListingController).getCurrentServices() }
             .`when`(controlsListingController)
@@ -326,11 +437,14 @@
                 }
             }
         return ControlsStartable(
+            kosmos.applicationCoroutineScope,
+            kosmos.testDispatcher,
             fakeExecutor,
             component,
             userTracker,
             authorizedPanelsRepository,
             preferredPanelsRepository,
+            kosmos.packageChangeRepository,
             userManager,
             broadcastDispatcher,
         )
diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/ui/ControlsDialogsFactoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/controls/ui/ControlsDialogsFactoryTest.kt
index 8eebcee..38c6a0e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/controls/ui/ControlsDialogsFactoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/controls/ui/ControlsDialogsFactoryTest.kt
@@ -17,17 +17,23 @@
 
 package com.android.systemui.controls.ui
 
+import android.content.Context
 import android.testing.AndroidTestingRunner
 import androidx.test.filters.SmallTest
 import com.android.systemui.res.R
 import com.android.systemui.SysuiTestCase
+import com.android.systemui.statusbar.phone.SystemUIDialog
 import com.android.systemui.util.FakeSystemUIDialogController
+import com.android.systemui.util.mockito.whenever
 import com.google.common.truth.Truth.assertThat
 import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.Mockito.any
 import org.mockito.Mockito.eq
 import org.mockito.Mockito.verify
+import org.mockito.MockitoAnnotations
 
 @SmallTest
 @RunWith(AndroidTestingRunner::class)
@@ -37,18 +43,24 @@
         const val APP_NAME = "Test App"
     }
 
-    private val fakeDialogController = FakeSystemUIDialogController()
+    @Mock
+    private lateinit var mockDialogFactory : SystemUIDialog.Factory
+
+    private val fakeDialogController = FakeSystemUIDialogController(mContext)
 
     private lateinit var underTest: ControlsDialogsFactory
 
     @Before
     fun setup() {
-        underTest = ControlsDialogsFactory { fakeDialogController.dialog }
+        MockitoAnnotations.initMocks(this)
+        whenever(mockDialogFactory.create(any(Context::class.java)))
+            .thenReturn(fakeDialogController.dialog)
+        underTest = ControlsDialogsFactory(mockDialogFactory)
     }
 
     @Test
     fun testCreatesRemoveAppDialog() {
-        val dialog = underTest.createRemoveAppDialog(context, APP_NAME) {}
+        val dialog = underTest.createRemoveAppDialog(mContext, APP_NAME) {}
 
         verify(dialog)
             .setTitle(
@@ -60,7 +72,7 @@
     @Test
     fun testPositiveClickRemoveAppDialogWorks() {
         var dialogResult: Boolean? = null
-        underTest.createRemoveAppDialog(context, APP_NAME) { dialogResult = it }
+        underTest.createRemoveAppDialog(mContext, APP_NAME) { dialogResult = it }
 
         fakeDialogController.clickPositive()
 
@@ -70,7 +82,7 @@
     @Test
     fun testNeutralClickRemoveAppDialogWorks() {
         var dialogResult: Boolean? = null
-        underTest.createRemoveAppDialog(context, APP_NAME) { dialogResult = it }
+        underTest.createRemoveAppDialog(mContext, APP_NAME) { dialogResult = it }
 
         fakeDialogController.clickNeutral()
 
@@ -80,7 +92,7 @@
     @Test
     fun testCancelRemoveAppDialogWorks() {
         var dialogResult: Boolean? = null
-        underTest.createRemoveAppDialog(context, APP_NAME) { dialogResult = it }
+        underTest.createRemoveAppDialog(mContext, APP_NAME) { dialogResult = it }
 
         fakeDialogController.cancel()
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/controls/ui/ControlsUiControllerImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/controls/ui/ControlsUiControllerImplTest.kt
index 11bd9cb..36ae0c7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/controls/ui/ControlsUiControllerImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/controls/ui/ControlsUiControllerImplTest.kt
@@ -51,6 +51,7 @@
 import com.android.systemui.plugins.ActivityStarter
 import com.android.systemui.res.R
 import com.android.systemui.settings.UserTracker
+import com.android.systemui.statusbar.phone.SystemUIDialog
 import com.android.systemui.statusbar.policy.KeyguardStateController
 import com.android.systemui.util.FakeSystemUIDialogController
 import com.android.systemui.util.concurrency.FakeExecutor
@@ -97,9 +98,10 @@
     @Mock lateinit var authorizedPanelsRepository: AuthorizedPanelsRepository
     @Mock lateinit var featureFlags: FeatureFlags
     @Mock lateinit var packageManager: PackageManager
+    @Mock lateinit var systemUIDialogFactory: SystemUIDialog.Factory
 
     private val preferredPanelRepository = FakeSelectedComponentRepository()
-    private val fakeDialogController = FakeSystemUIDialogController()
+    private lateinit var fakeDialogController: FakeSystemUIDialogController
     private val uiExecutor = FakeExecutor(FakeSystemClock())
     private val bgExecutor = FakeExecutor(FakeSystemClock())
 
@@ -114,6 +116,9 @@
     fun setup() {
         MockitoAnnotations.initMocks(this)
 
+        fakeDialogController = FakeSystemUIDialogController(mContext)
+        whenever(systemUIDialogFactory.create(any(Context::class.java)))
+            .thenReturn(fakeDialogController.dialog)
         controlsSettingsRepository = FakeControlsSettingsRepository()
 
         // This way, it won't be cloned every time `LayoutInflater.fromContext` is called, but we
@@ -146,10 +151,7 @@
                 authorizedPanelsRepository,
                 preferredPanelRepository,
                 featureFlags,
-                ControlsDialogsFactory {
-                    isRemoveAppDialogCreated = true
-                    fakeDialogController.dialog
-                },
+                ControlsDialogsFactory(systemUIDialogFactory),
                 dumpManager,
             )
         `when`(userTracker.userId).thenReturn(0)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/haptics/slider/SliderHapticFeedbackProviderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/haptics/slider/SliderHapticFeedbackProviderTest.kt
index ab6bc2c..66fdf53 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/haptics/slider/SliderHapticFeedbackProviderTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/haptics/slider/SliderHapticFeedbackProviderTest.kt
@@ -19,7 +19,6 @@
 import android.os.VibrationAttributes
 import android.os.VibrationEffect
 import android.view.VelocityTracker
-import android.view.animation.AccelerateInterpolator
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
@@ -51,8 +50,6 @@
     private val lowTickDuration = 12 // Mocked duration of a low tick
     private val dragTextureThresholdMillis =
         lowTickDuration * config.numberOfLowTicks + config.deltaMillisForDragInterval
-    private val progressInterpolator = AccelerateInterpolator(config.progressInterpolatorFactor)
-    private val velocityInterpolator = AccelerateInterpolator(config.velocityInterpolatorFactor)
     private lateinit var sliderHapticFeedbackProvider: SliderHapticFeedbackProvider
 
     @Before
@@ -60,7 +57,9 @@
         MockitoAnnotations.initMocks(this)
         whenever(vibratorHelper.getPrimitiveDurations(any()))
             .thenReturn(intArrayOf(lowTickDuration))
-        whenever(velocityTracker.xVelocity).thenReturn(config.maxVelocityToScale)
+        whenever(velocityTracker.isAxisSupported(config.velocityAxis)).thenReturn(true)
+        whenever(velocityTracker.getAxisVelocity(config.velocityAxis))
+            .thenReturn(config.maxVelocityToScale)
         sliderHapticFeedbackProvider =
             SliderHapticFeedbackProvider(vibratorHelper, velocityTracker, config, clock)
     }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultIndicationAreaSectionTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultIndicationAreaSectionTest.kt
index 8dd33d5..1205dce 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultIndicationAreaSectionTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultIndicationAreaSectionTest.kt
@@ -21,11 +21,11 @@
 import androidx.constraintlayout.widget.ConstraintLayout
 import androidx.constraintlayout.widget.ConstraintSet
 import androidx.test.filters.SmallTest
-import com.android.systemui.res.R
-import com.android.systemui.SysuiTestCase
 import com.android.systemui.Flags as AConfigFlags
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.keyguard.ui.viewmodel.AodAlphaViewModel
 import com.android.systemui.keyguard.ui.viewmodel.KeyguardIndicationAreaViewModel
-import com.android.systemui.keyguard.ui.viewmodel.KeyguardRootViewModel
+import com.android.systemui.res.R
 import com.android.systemui.statusbar.KeyguardIndicationController
 import com.google.common.truth.Truth.assertThat
 import org.junit.Before
@@ -38,8 +38,9 @@
 @RunWith(JUnit4::class)
 @SmallTest
 class DefaultIndicationAreaSectionTest : SysuiTestCase() {
+
     @Mock private lateinit var keyguardIndicationAreaViewModel: KeyguardIndicationAreaViewModel
-    @Mock private lateinit var keyguardRootViewModel: KeyguardRootViewModel
+    @Mock private lateinit var aodAlphaViewModel: AodAlphaViewModel
     @Mock private lateinit var indicationController: KeyguardIndicationController
 
     private lateinit var underTest: DefaultIndicationAreaSection
@@ -51,7 +52,7 @@
             DefaultIndicationAreaSection(
                 context,
                 keyguardIndicationAreaViewModel,
-                keyguardRootViewModel,
+                aodAlphaViewModel,
                 indicationController,
             )
     }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardClockViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardClockViewModelTest.kt
index d421004..1b4573d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardClockViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardClockViewModelTest.kt
@@ -34,6 +34,7 @@
 import com.android.systemui.plugins.clocks.ClockFaceConfig
 import com.android.systemui.plugins.clocks.ClockFaceController
 import com.android.systemui.shared.clocks.ClockRegistry
+import com.android.systemui.statusbar.policy.SplitShadeStateController
 import com.android.systemui.util.mockito.whenever
 import com.android.systemui.util.settings.FakeSettings
 import com.google.common.truth.Truth.assertThat
@@ -66,6 +67,8 @@
     @Mock private lateinit var largeClock: ClockFaceController
     @Mock private lateinit var clockFaceConfig: ClockFaceConfig
     @Mock private lateinit var eventController: ClockEventController
+    @Mock private lateinit var splitShadeStateController: SplitShadeStateController
+
     @Before
     fun setup() {
         MockitoAnnotations.initMocks(this)
@@ -92,6 +95,7 @@
                 keyguardInteractor,
                 keyguardClockInteractor,
                 scope.backgroundScope,
+                splitShadeStateController,
             )
     }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelTest.kt
deleted file mode 100644
index ee1be10..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelTest.kt
+++ /dev/null
@@ -1,498 +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.
- *
- */
-
-@file:OptIn(ExperimentalCoroutinesApi::class)
-
-package com.android.systemui.keyguard.ui.viewmodel
-
-import android.view.View
-import androidx.test.filters.SmallTest
-import com.android.systemui.Flags as AConfigFlags
-import com.android.systemui.Flags.FLAG_NEW_AOD_TRANSITION
-import com.android.systemui.SysuiTestCase
-import com.android.systemui.common.ui.data.repository.fakeConfigurationRepository
-import com.android.systemui.common.ui.domain.interactor.configurationInteractor
-import com.android.systemui.coroutines.collectLastValue
-import com.android.systemui.deviceentry.data.repository.fakeDeviceEntryRepository
-import com.android.systemui.deviceentry.domain.interactor.deviceEntryInteractor
-import com.android.systemui.flags.featureFlagsClassic
-import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository
-import com.android.systemui.keyguard.data.repository.fakeKeyguardTransitionRepository
-import com.android.systemui.keyguard.domain.interactor.BurnInInteractor
-import com.android.systemui.keyguard.domain.interactor.keyguardInteractor
-import com.android.systemui.keyguard.domain.interactor.keyguardTransitionInteractor
-import com.android.systemui.keyguard.shared.model.BurnInModel
-import com.android.systemui.keyguard.shared.model.KeyguardState
-import com.android.systemui.keyguard.shared.model.TransitionState
-import com.android.systemui.keyguard.shared.model.TransitionStep
-import com.android.systemui.kosmos.testScope
-import com.android.systemui.plugins.clocks.ClockController
-import com.android.systemui.statusbar.notification.data.repository.fakeNotificationsKeyguardViewStateRepository
-import com.android.systemui.statusbar.notification.stack.domain.interactor.notificationsKeyguardInteractor
-import com.android.systemui.statusbar.phone.dozeParameters
-import com.android.systemui.statusbar.phone.screenOffAnimationController
-import com.android.systemui.testKosmos
-import com.android.systemui.util.mockito.whenever
-import com.android.systemui.util.ui.isAnimating
-import com.android.systemui.util.ui.stopAnimating
-import com.android.systemui.util.ui.value
-import com.google.common.truth.Truth.assertThat
-import javax.inject.Provider
-import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.flow.MutableStateFlow
-import kotlinx.coroutines.flow.emptyFlow
-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.junit.runners.JUnit4
-import org.mockito.Answers
-import org.mockito.Mock
-import org.mockito.Mockito.RETURNS_DEEP_STUBS
-import org.mockito.Mockito.anyInt
-import org.mockito.MockitoAnnotations
-
-@SmallTest
-@RunWith(JUnit4::class)
-class KeyguardRootViewModelTest : SysuiTestCase() {
-    private val kosmos = testKosmos()
-    private val testScope = kosmos.testScope
-    private val repository = kosmos.fakeKeyguardRepository
-    private val configurationRepository = kosmos.fakeConfigurationRepository
-    private val keyguardTransitionRepository = kosmos.fakeKeyguardTransitionRepository
-    private val screenOffAnimationController = kosmos.screenOffAnimationController
-    private val deviceEntryRepository = kosmos.fakeDeviceEntryRepository
-    private val notificationsKeyguardInteractor = kosmos.notificationsKeyguardInteractor
-    private val fakeNotificationsKeyguardViewStateRepository =
-        kosmos.fakeNotificationsKeyguardViewStateRepository
-    private val dozeParameters = kosmos.dozeParameters
-    private lateinit var underTest: KeyguardRootViewModel
-
-    @Mock private lateinit var burnInInteractor: BurnInInteractor
-    private val burnInFlow = MutableStateFlow(BurnInModel())
-
-    @Mock private lateinit var goneToAodTransitionViewModel: GoneToAodTransitionViewModel
-    private val enterFromTopAnimationAlpha = MutableStateFlow(0f)
-
-    @Mock
-    private lateinit var aodToLockscreenTransitionViewModel: AodToLockscreenTransitionViewModel
-    @Mock
-    private lateinit var occludedToLockscreenTransitionViewModel:
-        OccludedToLockscreenTransitionViewModel
-    private val occludedToLockscreenTranslationY = MutableStateFlow(0f)
-    private val occludedToLockscreenAlpha = MutableStateFlow(0f)
-
-    @Mock(answer = Answers.RETURNS_DEEP_STUBS) private lateinit var clockController: ClockController
-
-    @Before
-    fun setUp() {
-        MockitoAnnotations.initMocks(this)
-
-        mSetFlagsRule.enableFlags(AConfigFlags.FLAG_KEYGUARD_BOTTOM_AREA_REFACTOR)
-        mSetFlagsRule.enableFlags(FLAG_NEW_AOD_TRANSITION)
-        mSetFlagsRule.disableFlags(AConfigFlags.FLAG_MIGRATE_CLOCKS_TO_BLUEPRINT)
-
-        whenever(goneToAodTransitionViewModel.enterFromTopTranslationY(anyInt()))
-            .thenReturn(emptyFlow<Float>())
-        whenever(goneToAodTransitionViewModel.enterFromTopAnimationAlpha)
-            .thenReturn(enterFromTopAnimationAlpha)
-
-        whenever(burnInInteractor.keyguardBurnIn).thenReturn(burnInFlow)
-
-        whenever(occludedToLockscreenTransitionViewModel.lockscreenTranslationY)
-            .thenReturn(occludedToLockscreenTranslationY)
-        whenever(occludedToLockscreenTransitionViewModel.lockscreenAlpha)
-            .thenReturn(occludedToLockscreenAlpha)
-
-        underTest =
-            KeyguardRootViewModel(
-                configurationInteractor = kosmos.configurationInteractor,
-                deviceEntryInteractor = kosmos.deviceEntryInteractor,
-                dozeParameters = kosmos.dozeParameters,
-                keyguardInteractor = kosmos.keyguardInteractor,
-                keyguardTransitionInteractor = kosmos.keyguardTransitionInteractor,
-                notificationsKeyguardInteractor = kosmos.notificationsKeyguardInteractor,
-                burnInInteractor = burnInInteractor,
-                keyguardClockViewModel = kosmos.keyguardClockViewModel,
-                goneToAodTransitionViewModel = goneToAodTransitionViewModel,
-                aodToLockscreenTransitionViewModel = aodToLockscreenTransitionViewModel,
-                occludedToLockscreenTransitionViewModel = occludedToLockscreenTransitionViewModel,
-                screenOffAnimationController = screenOffAnimationController,
-                // TODO(b/310989341): remove after change to aconfig
-                featureFlags = kosmos.featureFlagsClassic
-            )
-
-        underTest.clockControllerProvider = Provider { clockController }
-    }
-
-    @Test
-    fun alpha() =
-        testScope.runTest {
-            val alpha by collectLastValue(underTest.alpha)
-
-            keyguardTransitionRepository.sendTransitionSteps(
-                from = KeyguardState.OFF,
-                to = KeyguardState.LOCKSCREEN,
-                testScope = testScope,
-            )
-
-            repository.setKeyguardAlpha(0.1f)
-            assertThat(alpha).isEqualTo(0.1f)
-            repository.setKeyguardAlpha(0.5f)
-            assertThat(alpha).isEqualTo(0.5f)
-            repository.setKeyguardAlpha(0.2f)
-            assertThat(alpha).isEqualTo(0.2f)
-            repository.setKeyguardAlpha(0f)
-            assertThat(alpha).isEqualTo(0f)
-            occludedToLockscreenAlpha.value = 0.8f
-            assertThat(alpha).isEqualTo(0.8f)
-        }
-
-    @Test
-    fun alphaWhenGoneEqualsZero() =
-        testScope.runTest {
-            val alpha by collectLastValue(underTest.alpha)
-
-            keyguardTransitionRepository.sendTransitionSteps(
-                from = KeyguardState.LOCKSCREEN,
-                to = KeyguardState.GONE,
-                testScope = testScope,
-            )
-
-            repository.setKeyguardAlpha(0.1f)
-            assertThat(alpha).isEqualTo(0f)
-            repository.setKeyguardAlpha(0.5f)
-            assertThat(alpha).isEqualTo(0f)
-            repository.setKeyguardAlpha(1f)
-            assertThat(alpha).isEqualTo(0f)
-        }
-
-    @Test
-    fun translationYInitialValueIsZero() =
-        testScope.runTest {
-            val translationY by collectLastValue(underTest.translationY)
-            assertThat(translationY).isEqualTo(0)
-        }
-
-    @Test
-    fun translationAndScaleFromBurnInNotDozing() =
-        testScope.runTest {
-            val translationX by collectLastValue(underTest.translationX)
-            val translationY by collectLastValue(underTest.translationY)
-            val scale by collectLastValue(underTest.scale)
-
-            // Set to not dozing (on lockscreen)
-            keyguardTransitionRepository.sendTransitionStep(
-                TransitionStep(
-                    from = KeyguardState.AOD,
-                    to = KeyguardState.LOCKSCREEN,
-                    value = 1f,
-                    transitionState = TransitionState.FINISHED
-                ),
-                validateStep = false,
-            )
-
-            // Trigger a change to the burn-in model
-            burnInFlow.value =
-                BurnInModel(
-                    translationX = 20,
-                    translationY = 30,
-                    scale = 0.5f,
-                )
-
-            assertThat(translationX).isEqualTo(0)
-            assertThat(translationY).isEqualTo(0)
-            assertThat(scale).isEqualTo(Pair(1f, true /* scaleClockOnly */))
-        }
-
-    @Test
-    fun translationAndScaleFromBurnFullyDozing() =
-        testScope.runTest {
-            val translationX by collectLastValue(underTest.translationX)
-            val translationY by collectLastValue(underTest.translationY)
-            val scale by collectLastValue(underTest.scale)
-
-            underTest.statusViewTop = 100
-
-            // Set to dozing (on AOD)
-            keyguardTransitionRepository.sendTransitionStep(
-                TransitionStep(
-                    from = KeyguardState.GONE,
-                    to = KeyguardState.AOD,
-                    value = 1f,
-                    transitionState = TransitionState.FINISHED
-                ),
-                validateStep = false,
-            )
-            // Trigger a change to the burn-in model
-            burnInFlow.value =
-                BurnInModel(
-                    translationX = 20,
-                    translationY = 30,
-                    scale = 0.5f,
-                )
-
-            assertThat(translationX).isEqualTo(20)
-            assertThat(translationY).isEqualTo(30)
-            assertThat(scale).isEqualTo(Pair(0.5f, true /* scaleClockOnly */))
-
-            // Set to the beginning of GONE->AOD transition
-            keyguardTransitionRepository.sendTransitionStep(
-                TransitionStep(
-                    from = KeyguardState.GONE,
-                    to = KeyguardState.AOD,
-                    value = 0f,
-                    transitionState = TransitionState.STARTED
-                ),
-                validateStep = false,
-            )
-            assertThat(translationX).isEqualTo(0)
-            assertThat(translationY).isEqualTo(0)
-            assertThat(scale).isEqualTo(Pair(1f, true /* scaleClockOnly */))
-        }
-
-    @Test
-    fun translationAndScaleFromBurnFullyDozingStaysOutOfTopInset() =
-        testScope.runTest {
-            val translationX by collectLastValue(underTest.translationX)
-            val translationY by collectLastValue(underTest.translationY)
-            val scale by collectLastValue(underTest.scale)
-
-            underTest.statusViewTop = 100
-            underTest.topInset = 80
-
-            // Set to dozing (on AOD)
-            keyguardTransitionRepository.sendTransitionStep(
-                TransitionStep(
-                    from = KeyguardState.GONE,
-                    to = KeyguardState.AOD,
-                    value = 1f,
-                    transitionState = TransitionState.FINISHED
-                ),
-                validateStep = false,
-            )
-
-            // Trigger a change to the burn-in model
-            burnInFlow.value =
-                BurnInModel(
-                    translationX = 20,
-                    translationY = -30,
-                    scale = 0.5f,
-                )
-            assertThat(translationX).isEqualTo(20)
-            // -20 instead of -30, due to inset of 80
-            assertThat(translationY).isEqualTo(-20)
-            assertThat(scale).isEqualTo(Pair(0.5f, true /* scaleClockOnly */))
-
-            // Set to the beginning of GONE->AOD transition
-            keyguardTransitionRepository.sendTransitionStep(
-                TransitionStep(
-                    from = KeyguardState.GONE,
-                    to = KeyguardState.AOD,
-                    value = 0f,
-                    transitionState = TransitionState.STARTED
-                ),
-                validateStep = false,
-            )
-            assertThat(translationX).isEqualTo(0)
-            assertThat(translationY).isEqualTo(0)
-            assertThat(scale).isEqualTo(Pair(1f, true /* scaleClockOnly */))
-        }
-
-    @Test
-    fun translationAndScaleFromBurnInUseScaleOnly() =
-        testScope.runTest {
-            whenever(clockController.config.useAlternateSmartspaceAODTransition).thenReturn(true)
-
-            val translationX by collectLastValue(underTest.translationX)
-            val translationY by collectLastValue(underTest.translationY)
-            val scale by collectLastValue(underTest.scale)
-
-            // Set to dozing (on AOD)
-            keyguardTransitionRepository.sendTransitionStep(
-                TransitionStep(
-                    from = KeyguardState.GONE,
-                    to = KeyguardState.AOD,
-                    value = 1f,
-                    transitionState = TransitionState.FINISHED
-                ),
-                validateStep = false,
-            )
-
-            // Trigger a change to the burn-in model
-            burnInFlow.value =
-                BurnInModel(
-                    translationX = 20,
-                    translationY = 30,
-                    scale = 0.5f,
-                )
-
-            assertThat(translationX).isEqualTo(0)
-            assertThat(translationY).isEqualTo(0)
-            assertThat(scale).isEqualTo(Pair(0.5f, false /* scaleClockOnly */))
-        }
-
-    @Test
-    fun burnInLayerVisibility() =
-        testScope.runTest {
-            val burnInLayerVisibility by collectLastValue(underTest.burnInLayerVisibility)
-
-            keyguardTransitionRepository.sendTransitionStep(
-                TransitionStep(
-                    from = KeyguardState.LOCKSCREEN,
-                    to = KeyguardState.AOD,
-                    value = 0f,
-                    transitionState = TransitionState.STARTED
-                ),
-                validateStep = false,
-            )
-            assertThat(burnInLayerVisibility).isEqualTo(View.VISIBLE)
-        }
-
-    @Test
-    fun burnInLayerAlpha() =
-        testScope.runTest {
-            val burnInLayerAlpha by collectLastValue(underTest.burnInLayerAlpha)
-
-            enterFromTopAnimationAlpha.value = 0.2f
-            assertThat(burnInLayerAlpha).isEqualTo(0.2f)
-
-            enterFromTopAnimationAlpha.value = 1f
-            assertThat(burnInLayerAlpha).isEqualTo(1f)
-        }
-
-    @Test
-    fun iconContainer_isNotVisible_notOnKeyguard_dontShowAodIconsWhenShade() =
-        testScope.runTest {
-            val isVisible by collectLastValue(underTest.isNotifIconContainerVisible)
-            runCurrent()
-            keyguardTransitionRepository.sendTransitionSteps(
-                from = KeyguardState.OFF,
-                to = KeyguardState.GONE,
-                testScope,
-            )
-            whenever(screenOffAnimationController.shouldShowAodIconsWhenShade()).thenReturn(false)
-            runCurrent()
-
-            assertThat(isVisible?.value).isFalse()
-            assertThat(isVisible?.isAnimating).isFalse()
-        }
-
-    @Test
-    fun iconContainer_isVisible_bypassEnabled() =
-        testScope.runTest {
-            val isVisible by collectLastValue(underTest.isNotifIconContainerVisible)
-            runCurrent()
-            deviceEntryRepository.setBypassEnabled(true)
-            runCurrent()
-
-            assertThat(isVisible?.value).isTrue()
-        }
-
-    @Test
-    fun iconContainer_isNotVisible_pulseExpanding_notBypassing() =
-        testScope.runTest {
-            val isVisible by collectLastValue(underTest.isNotifIconContainerVisible)
-            runCurrent()
-            fakeNotificationsKeyguardViewStateRepository.setPulseExpanding(true)
-            deviceEntryRepository.setBypassEnabled(false)
-            runCurrent()
-
-            assertThat(isVisible?.value).isEqualTo(false)
-        }
-
-    @Test
-    fun iconContainer_isVisible_notifsFullyHidden_bypassEnabled() =
-        testScope.runTest {
-            val isVisible by collectLastValue(underTest.isNotifIconContainerVisible)
-            runCurrent()
-            fakeNotificationsKeyguardViewStateRepository.setPulseExpanding(false)
-            deviceEntryRepository.setBypassEnabled(true)
-            fakeNotificationsKeyguardViewStateRepository.setNotificationsFullyHidden(true)
-            runCurrent()
-
-            assertThat(isVisible?.value).isTrue()
-            assertThat(isVisible?.isAnimating).isTrue()
-        }
-
-    @Test
-    fun iconContainer_isVisible_notifsFullyHidden_bypassDisabled_aodDisabled() =
-        testScope.runTest {
-            val isVisible by collectLastValue(underTest.isNotifIconContainerVisible)
-            runCurrent()
-            fakeNotificationsKeyguardViewStateRepository.setPulseExpanding(false)
-            deviceEntryRepository.setBypassEnabled(false)
-            whenever(dozeParameters.alwaysOn).thenReturn(false)
-            fakeNotificationsKeyguardViewStateRepository.setNotificationsFullyHidden(true)
-            runCurrent()
-
-            assertThat(isVisible?.value).isTrue()
-            assertThat(isVisible?.isAnimating).isFalse()
-        }
-
-    @Test
-    fun iconContainer_isVisible_notifsFullyHidden_bypassDisabled_displayNeedsBlanking() =
-        testScope.runTest {
-            val isVisible by collectLastValue(underTest.isNotifIconContainerVisible)
-            runCurrent()
-            fakeNotificationsKeyguardViewStateRepository.setPulseExpanding(false)
-            deviceEntryRepository.setBypassEnabled(false)
-            whenever(dozeParameters.alwaysOn).thenReturn(true)
-            whenever(dozeParameters.displayNeedsBlanking).thenReturn(true)
-            fakeNotificationsKeyguardViewStateRepository.setNotificationsFullyHidden(true)
-            runCurrent()
-
-            assertThat(isVisible?.value).isTrue()
-            assertThat(isVisible?.isAnimating).isFalse()
-        }
-
-    @Test
-    fun iconContainer_isVisible_notifsFullyHidden_bypassDisabled() =
-        testScope.runTest {
-            val isVisible by collectLastValue(underTest.isNotifIconContainerVisible)
-            runCurrent()
-            fakeNotificationsKeyguardViewStateRepository.setPulseExpanding(false)
-            deviceEntryRepository.setBypassEnabled(false)
-            whenever(dozeParameters.alwaysOn).thenReturn(true)
-            whenever(dozeParameters.displayNeedsBlanking).thenReturn(false)
-            fakeNotificationsKeyguardViewStateRepository.setNotificationsFullyHidden(true)
-            runCurrent()
-
-            assertThat(isVisible?.value).isTrue()
-            assertThat(isVisible?.isAnimating).isTrue()
-        }
-
-    @Test
-    fun isIconContainerVisible_stopAnimation() =
-        testScope.runTest {
-            val isVisible by collectLastValue(underTest.isNotifIconContainerVisible)
-            runCurrent()
-            fakeNotificationsKeyguardViewStateRepository.setPulseExpanding(false)
-            deviceEntryRepository.setBypassEnabled(false)
-            whenever(dozeParameters.alwaysOn).thenReturn(true)
-            whenever(dozeParameters.displayNeedsBlanking).thenReturn(false)
-            fakeNotificationsKeyguardViewStateRepository.setNotificationsFullyHidden(true)
-            runCurrent()
-
-            assertThat(isVisible?.isAnimating).isEqualTo(true)
-            isVisible?.stopAnimating()
-            runCurrent()
-
-            assertThat(isVisible?.isAnimating).isEqualTo(false)
-        }
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/power/PowerNotificationWarningsTest.java b/packages/SystemUI/tests/src/com/android/systemui/power/PowerNotificationWarningsTest.java
index 6248bb1..1a303b0 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/power/PowerNotificationWarningsTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/power/PowerNotificationWarningsTest.java
@@ -55,6 +55,7 @@
 import com.android.systemui.broadcast.BroadcastSender;
 import com.android.systemui.plugins.ActivityStarter;
 import com.android.systemui.settings.UserTracker;
+import com.android.systemui.statusbar.phone.SystemUIDialog;
 import com.android.systemui.statusbar.policy.BatteryController;
 import com.android.systemui.util.NotificationChannels;
 import com.android.systemui.util.settings.FakeGlobalSettings;
@@ -77,7 +78,6 @@
     public static final String FORMATTED_45M = "0h 45m";
     public static final String FORMATTED_HOUR = "1h 0m";
     private final NotificationManager mMockNotificationManager = mock(NotificationManager.class);
-    private final GlobalSettings mGlobalSettings = new FakeGlobalSettings();
     private PowerNotificationWarnings mPowerNotificationWarnings;
 
     @Mock
@@ -90,6 +90,10 @@
     private UserTracker mUserTracker;
     @Mock
     private View mView;
+    @Mock
+    private SystemUIDialog.Factory mSystemUIDialogFactory;
+    @Mock
+    private SystemUIDialog mSystemUIDialog;
 
     private BroadcastReceiver mReceiver;
 
@@ -113,9 +117,16 @@
         when(mUserTracker.getUserId()).thenReturn(ActivityManager.getCurrentUser());
         when(mUserTracker.getUserHandle()).thenReturn(
                 UserHandle.of(ActivityManager.getCurrentUser()));
-        mPowerNotificationWarnings = new PowerNotificationWarnings(wrapper, starter,
-                broadcastSender, () -> mBatteryController, mDialogLaunchAnimator, mUiEventLogger,
-                mGlobalSettings, mUserTracker);
+        when(mSystemUIDialogFactory.create()).thenReturn(mSystemUIDialog);
+        mPowerNotificationWarnings = new PowerNotificationWarnings(
+                wrapper,
+                starter,
+                broadcastSender,
+                () -> mBatteryController,
+                mDialogLaunchAnimator,
+                mUiEventLogger,
+                mUserTracker,
+                mSystemUIDialogFactory);
         BatteryStateSnapshot snapshot = new BatteryStateSnapshot(100, false, false, 1,
                 BatteryManager.BATTERY_HEALTH_GOOD, 5, 15);
         mPowerNotificationWarnings.updateSnapshot(snapshot);
@@ -251,7 +262,7 @@
 
         verify(mDialogLaunchAnimator, never()).showFromView(any(), any());
 
-        assertThat(mPowerNotificationWarnings.getSaverConfirmationDialog().isShowing()).isTrue();
+        verify(mPowerNotificationWarnings.getSaverConfirmationDialog()).show();
         mPowerNotificationWarnings.getSaverConfirmationDialog().dismiss();
     }
 
@@ -266,7 +277,7 @@
 
         verify(mDialogLaunchAnimator, never()).showFromView(any(), any());
 
-        assertThat(mPowerNotificationWarnings.getSaverConfirmationDialog().isShowing()).isTrue();
+        verify(mPowerNotificationWarnings.getSaverConfirmationDialog()).show();
         mPowerNotificationWarnings.getSaverConfirmationDialog().dismiss();
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/FgsManagerControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/qs/FgsManagerControllerTest.java
index f5a3bec..698868d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/FgsManagerControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/FgsManagerControllerTest.java
@@ -27,6 +27,7 @@
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
 
 import android.app.IActivityManager;
 import android.app.IForegroundServiceObserver;
@@ -53,6 +54,7 @@
 import com.android.systemui.broadcast.BroadcastDispatcher;
 import com.android.systemui.dump.DumpManager;
 import com.android.systemui.settings.UserTracker;
+import com.android.systemui.statusbar.phone.SystemUIDialog;
 import com.android.systemui.util.DeviceConfigProxyFake;
 import com.android.systemui.util.concurrency.FakeExecutor;
 import com.android.systemui.util.time.FakeSystemClock;
@@ -95,6 +97,10 @@
     BroadcastDispatcher mBroadcastDispatcher;
     @Mock
     DumpManager mDumpManager;
+    @Mock
+    SystemUIDialog.Factory mSystemUIDialogFactory;
+    @Mock
+    SystemUIDialog mSystemUIDialog;
 
     private FgsManagerController mFmc;
 
@@ -114,6 +120,7 @@
         mSystemClock = new FakeSystemClock();
         mMainExecutor = new FakeExecutor(mSystemClock);
         mBackgroundExecutor = new FakeExecutor(mSystemClock);
+        when(mSystemUIDialogFactory.create()).thenReturn(mSystemUIDialog);
 
         mUserProfiles = new ArrayList<>();
         Mockito.doReturn(mUserProfiles).when(mUserTracker).getUserProfiles();
@@ -325,7 +332,8 @@
                 mDeviceConfigProxyFake,
                 mDialogLaunchAnimator,
                 mBroadcastDispatcher,
-                mDumpManager
+                mDumpManager,
+                mSystemUIDialogFactory
         );
         fmc.init();
         Assert.assertTrue(fmc.getIncludesUserVisibleJobs());
@@ -351,7 +359,8 @@
                 mDeviceConfigProxyFake,
                 mDialogLaunchAnimator,
                 mBroadcastDispatcher,
-                mDumpManager
+                mDumpManager,
+                mSystemUIDialogFactory
         );
         fmc.init();
         Assert.assertFalse(fmc.getIncludesUserVisibleJobs());
@@ -457,7 +466,8 @@
                 mDeviceConfigProxyFake,
                 mDialogLaunchAnimator,
                 mBroadcastDispatcher,
-                mDumpManager
+                mDumpManager,
+                mSystemUIDialogFactory
         );
         result.init();
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/DataSaverTileTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/DataSaverTileTest.kt
index 51e95be..c109a1e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/DataSaverTileTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/DataSaverTileTest.kt
@@ -32,7 +32,9 @@
 import com.android.systemui.qs.QsEventLogger
 import com.android.systemui.qs.logging.QSLogger
 import com.android.systemui.qs.tileimpl.QSTileImpl
+import com.android.systemui.statusbar.phone.SystemUIDialog
 import com.android.systemui.statusbar.policy.DataSaverController
+import com.android.systemui.util.mockito.whenever
 import com.google.common.truth.Truth.assertThat
 import org.junit.After
 import org.junit.Before
@@ -49,8 +51,6 @@
 
     @Mock private lateinit var mHost: QSHost
     @Mock private lateinit var mMetricsLogger: MetricsLogger
-    @Mock private lateinit var mStatusBarStateController: StatusBarStateController
-    @Mock private lateinit var mActivityStarter: ActivityStarter
     @Mock private lateinit var mQsLogger: QSLogger
     private val falsingManager = FalsingManagerFake()
     @Mock private lateinit var statusBarStateController: StatusBarStateController
@@ -58,6 +58,8 @@
     @Mock private lateinit var dataSaverController: DataSaverController
     @Mock private lateinit var dialogLaunchAnimator: DialogLaunchAnimator
     @Mock private lateinit var uiEventLogger: QsEventLogger
+    @Mock private lateinit var systemUIDialogFactory: SystemUIDialog.Factory
+    @Mock private lateinit var systemUIDialog: SystemUIDialog
 
     private lateinit var testableLooper: TestableLooper
     private lateinit var tile: DataSaverTile
@@ -67,7 +69,8 @@
         MockitoAnnotations.initMocks(this)
         testableLooper = TestableLooper.get(this)
 
-        Mockito.`when`(mHost.context).thenReturn(mContext)
+        whenever(mHost.context).thenReturn(mContext)
+        whenever(systemUIDialogFactory.create()).thenReturn(systemUIDialog)
 
         tile =
             DataSaverTile(
@@ -81,7 +84,8 @@
                 activityStarter,
                 mQsLogger,
                 dataSaverController,
-                dialogLaunchAnimator
+                dialogLaunchAnimator,
+                systemUIDialogFactory
             )
     }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/base/viewmodel/QSTileViewModelImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/base/viewmodel/QSTileViewModelImplTest.kt
index 02d40da..ea2b22c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/base/viewmodel/QSTileViewModelImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/base/viewmodel/QSTileViewModelImplTest.kt
@@ -115,7 +115,7 @@
                 .isEqualTo(
                     "test_spec:\n" +
                         "    QSTileState(" +
-                        "icon=() -> com.android.systemui.common.shared.model.Icon, " +
+                        "icon=() -> com.android.systemui.common.shared.model.Icon?, " +
                         "label=test_data, " +
                         "activationState=INACTIVE, " +
                         "secondaryLabel=null, " +
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/user/UserSwitchDialogControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/user/UserSwitchDialogControllerTest.kt
index 0a34810..945490f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/user/UserSwitchDialogControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/user/UserSwitchDialogControllerTest.kt
@@ -36,6 +36,7 @@
 import com.android.systemui.util.mockito.capture
 import com.android.systemui.util.mockito.eq
 import com.android.systemui.util.mockito.mock
+import com.android.systemui.util.mockito.whenever
 import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -56,6 +57,8 @@
 class UserSwitchDialogControllerTest : SysuiTestCase() {
 
     @Mock
+    private lateinit var dialogFactory: SystemUIDialog.Factory
+    @Mock
     private lateinit var dialog: SystemUIDialog
     @Mock
     private lateinit var falsingManager: FalsingManager
@@ -80,7 +83,8 @@
     fun setUp() {
         MockitoAnnotations.initMocks(this)
 
-        `when`(dialog.context).thenReturn(mContext)
+        whenever(dialog.context).thenReturn(mContext)
+        whenever(dialogFactory.create()).thenReturn(dialog)
 
         controller = UserSwitchDialogController(
             { userDetailViewAdapter },
@@ -88,7 +92,7 @@
             falsingManager,
             dialogLaunchAnimator,
             uiEventLogger,
-            { dialog }
+            dialogFactory
         )
     }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/reardisplay/RearDisplayDialogControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/reardisplay/RearDisplayDialogControllerTest.java
index 273ce85..35bf775 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/reardisplay/RearDisplayDialogControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/reardisplay/RearDisplayDialogControllerTest.java
@@ -18,25 +18,42 @@
 
 import static junit.framework.Assert.assertEquals;
 import static junit.framework.Assert.assertNotSame;
-import static junit.framework.Assert.assertTrue;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.Mockito.reset;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
 
 import android.content.res.Configuration;
+import android.content.res.Resources;
 import android.hardware.devicestate.DeviceStateManager;
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper;
+import android.view.LayoutInflater;
+import android.view.View;
 import android.widget.TextView;
 
 import androidx.test.filters.SmallTest;
 
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.res.R;
+import com.android.systemui.flags.FakeFeatureFlags;
+import com.android.systemui.flags.Flags;
+import com.android.systemui.model.SysUiState;
+import com.android.systemui.res.R;
 import com.android.systemui.statusbar.CommandQueue;
+import com.android.systemui.statusbar.phone.SystemUIDialog;
 import com.android.systemui.util.concurrency.FakeExecutor;
 import com.android.systemui.util.time.FakeSystemClock;
 
+import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
 import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
 
 @SmallTest
 @RunWith(AndroidTestingRunner.class)
@@ -45,24 +62,49 @@
 
     @Mock
     private CommandQueue mCommandQueue;
+    @Mock
+    private SystemUIDialog.Factory mSystemUIDialogFactory;
+    @Mock
+    private SystemUIDialog mSystemUIDialog;
+    private final FakeFeatureFlags mFeatureFlags = new FakeFeatureFlags();
+    @Mock
+    private SysUiState mSysUiState;
+    @Mock
+    private Resources mResources;
 
-    private FakeExecutor mFakeExecutor = new FakeExecutor(new FakeSystemClock());
+    LayoutInflater mLayoutInflater = LayoutInflater.from(mContext);
 
+    private final FakeExecutor mFakeExecutor = new FakeExecutor(new FakeSystemClock());
 
     private static final int CLOSED_BASE_STATE = 0;
     private static final int OPEN_BASE_STATE = 1;
 
+    @Before
+    public void setup() {
+        MockitoAnnotations.initMocks(this);
+
+        mFeatureFlags.set(Flags.WM_ENABLE_PREDICTIVE_BACK_QS_DIALOG_ANIM, true);
+        when(mSysUiState.setFlag(anyInt(), anyBoolean())).thenReturn(mSysUiState);
+        when(mSystemUIDialogFactory.create()).thenReturn(mSystemUIDialog);
+        when(mSystemUIDialog.getContext()).thenReturn(mContext);
+    }
     @Test
     public void testClosedDialogIsShown() {
-        RearDisplayDialogController controller = new RearDisplayDialogController(mContext,
-                mCommandQueue, mFakeExecutor);
+        RearDisplayDialogController controller = new RearDisplayDialogController(
+                mCommandQueue,
+                mFakeExecutor,
+                mResources,
+                mLayoutInflater,
+                mSystemUIDialogFactory);
         controller.setDeviceStateManagerCallback(new TestDeviceStateManagerCallback());
         controller.setFoldedStates(new int[]{0});
         controller.setAnimationRepeatCount(0);
 
         controller.showRearDisplayDialog(CLOSED_BASE_STATE);
-        assertTrue(controller.mRearDisplayEducationDialog.isShowing());
-        TextView deviceClosedTitleTextView = controller.mRearDisplayEducationDialog.findViewById(
+        verify(mSystemUIDialog).show();
+
+        View container = getDialogViewContainer();
+        TextView deviceClosedTitleTextView = container.findViewById(
                 R.id.rear_display_title_text_view);
         assertEquals(deviceClosedTitleTextView.getText().toString(),
                 getContext().getResources().getString(
@@ -71,20 +113,28 @@
 
     @Test
     public void testClosedDialogIsRefreshedOnConfigurationChange() {
-        RearDisplayDialogController controller = new RearDisplayDialogController(mContext,
-                mCommandQueue, mFakeExecutor);
+        RearDisplayDialogController controller = new RearDisplayDialogController(
+                mCommandQueue,
+                mFakeExecutor,
+                mResources,
+                mLayoutInflater,
+                mSystemUIDialogFactory);
         controller.setDeviceStateManagerCallback(new TestDeviceStateManagerCallback());
         controller.setFoldedStates(new int[]{0});
         controller.setAnimationRepeatCount(0);
 
         controller.showRearDisplayDialog(CLOSED_BASE_STATE);
-        assertTrue(controller.mRearDisplayEducationDialog.isShowing());
-        TextView deviceClosedTitleTextView = controller.mRearDisplayEducationDialog.findViewById(
+        verify(mSystemUIDialog).show();
+        View container = getDialogViewContainer();
+        TextView deviceClosedTitleTextView = container.findViewById(
                 R.id.rear_display_title_text_view);
 
+        reset(mSystemUIDialog);
+        when(mSystemUIDialog.isShowing()).thenReturn(true);
+        when(mSystemUIDialog.getContext()).thenReturn(mContext);
+
         controller.onConfigChanged(new Configuration());
-        assertTrue(controller.mRearDisplayEducationDialog.isShowing());
-        TextView deviceClosedTitleTextView2 = controller.mRearDisplayEducationDialog.findViewById(
+        TextView deviceClosedTitleTextView2 = container.findViewById(
                 R.id.rear_display_title_text_view);
 
         assertNotSame(deviceClosedTitleTextView, deviceClosedTitleTextView2);
@@ -92,22 +142,33 @@
 
     @Test
     public void testOpenDialogIsShown() {
-        RearDisplayDialogController controller = new RearDisplayDialogController(mContext,
-                mCommandQueue, mFakeExecutor);
+        RearDisplayDialogController controller = new RearDisplayDialogController(
+                mCommandQueue,
+                mFakeExecutor,
+                mResources,
+                mLayoutInflater,
+                mSystemUIDialogFactory);
         controller.setDeviceStateManagerCallback(new TestDeviceStateManagerCallback());
         controller.setFoldedStates(new int[]{0});
         controller.setAnimationRepeatCount(0);
 
         controller.showRearDisplayDialog(OPEN_BASE_STATE);
 
-        assertTrue(controller.mRearDisplayEducationDialog.isShowing());
-        TextView deviceClosedTitleTextView = controller.mRearDisplayEducationDialog.findViewById(
+        verify(mSystemUIDialog).show();
+        View container = getDialogViewContainer();
+        TextView deviceClosedTitleTextView = container.findViewById(
                 R.id.rear_display_title_text_view);
         assertEquals(deviceClosedTitleTextView.getText().toString(),
                 getContext().getResources().getString(
                         R.string.rear_display_unfolded_bottom_sheet_title));
     }
 
+    private View getDialogViewContainer() {
+        ArgumentCaptor<View> viewCaptor = ArgumentCaptor.forClass(View.class);
+        verify(mSystemUIDialog).setView(viewCaptor.capture());
+
+        return viewCaptor.getValue();
+    }
     /**
      * Empty device state manager callbacks, so we can verify that the correct
      * dialogs are being created regardless of device state of the test device.
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java
index 48baeb3..2220756 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java
@@ -341,7 +341,7 @@
     @Mock private JavaAdapter mJavaAdapter;
     @Mock private CastController mCastController;
     @Mock private SharedNotificationContainerInteractor mSharedNotificationContainerInteractor;
-    @Mock private ActiveNotificationsInteractor mActiveNotificationsInteractor;
+    @Mock protected ActiveNotificationsInteractor mActiveNotificationsInteractor;
     @Mock private KeyguardClockPositionAlgorithm mKeyguardClockPositionAlgorithm;
     @Mock private NaturalScrollingSettingObserver mNaturalScrollingSettingObserver;
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java
index 28fe8e4..3cbb9bb 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerTest.java
@@ -456,11 +456,13 @@
         enableSplitShade(/* enabled= */ true);
 
         when(mNotificationStackScrollLayoutController.getVisibleNotificationCount()).thenReturn(2);
+        when(mActiveNotificationsInteractor.getAreAnyNotificationsPresentValue()).thenReturn(true);
         mNotificationPanelViewController.updateResources();
         assertThat(getConstraintSetLayout(R.id.keyguard_status_view).endToEnd)
                 .isEqualTo(R.id.qs_edge_guideline);
 
         when(mNotificationStackScrollLayoutController.getVisibleNotificationCount()).thenReturn(0);
+        when(mActiveNotificationsInteractor.getAreAnyNotificationsPresentValue()).thenReturn(false);
         mNotificationPanelViewController.updateResources();
         assertThat(getConstraintSetLayout(R.id.keyguard_status_view).endToEnd)
                 .isEqualTo(ConstraintSet.PARENT_ID);
@@ -469,6 +471,7 @@
     @Test
     public void keyguardStatusView_splitShade_dozing_alwaysDozingOn_isCentered() {
         when(mNotificationStackScrollLayoutController.getVisibleNotificationCount()).thenReturn(2);
+        when(mActiveNotificationsInteractor.getAreAnyNotificationsPresentValue()).thenReturn(true);
         mStatusBarStateController.setState(KEYGUARD);
         enableSplitShade(/* enabled= */ true);
 
@@ -480,6 +483,7 @@
     @Test
     public void keyguardStatusView_splitShade_dozing_alwaysDozingOff_isNotCentered() {
         when(mNotificationStackScrollLayoutController.getVisibleNotificationCount()).thenReturn(2);
+        when(mActiveNotificationsInteractor.getAreAnyNotificationsPresentValue()).thenReturn(true);
         mStatusBarStateController.setState(KEYGUARD);
         enableSplitShade(/* enabled= */ true);
 
@@ -491,6 +495,7 @@
     @Test
     public void keyguardStatusView_splitShade_notDozing_alwaysDozingOn_isNotCentered() {
         when(mNotificationStackScrollLayoutController.getVisibleNotificationCount()).thenReturn(2);
+        when(mActiveNotificationsInteractor.getAreAnyNotificationsPresentValue()).thenReturn(true);
         mStatusBarStateController.setState(KEYGUARD);
         enableSplitShade(/* enabled= */ true);
 
@@ -502,6 +507,7 @@
     @Test
     public void keyguardStatusView_splitShade_pulsing_isNotCentered() {
         when(mNotificationStackScrollLayoutController.getVisibleNotificationCount()).thenReturn(2);
+        when(mActiveNotificationsInteractor.getAreAnyNotificationsPresentValue()).thenReturn(true);
         when(mNotificationListContainer.hasPulsingNotifications()).thenReturn(true);
         mStatusBarStateController.setState(KEYGUARD);
         enableSplitShade(/* enabled= */ true);
@@ -514,6 +520,7 @@
     @Test
     public void keyguardStatusView_splitShade_notPulsing_isNotCentered() {
         when(mNotificationStackScrollLayoutController.getVisibleNotificationCount()).thenReturn(2);
+        when(mActiveNotificationsInteractor.getAreAnyNotificationsPresentValue()).thenReturn(true);
         when(mNotificationListContainer.hasPulsingNotifications()).thenReturn(false);
         mStatusBarStateController.setState(KEYGUARD);
         enableSplitShade(/* enabled= */ true);
@@ -529,6 +536,7 @@
         // The conditions below would make the clock NOT be centered on split shade.
         // On single shade it should always be centered though.
         when(mNotificationStackScrollLayoutController.getVisibleNotificationCount()).thenReturn(2);
+        when(mActiveNotificationsInteractor.getAreAnyNotificationsPresentValue()).thenReturn(true);
         when(mNotificationListContainer.hasPulsingNotifications()).thenReturn(false);
         mStatusBarStateController.setState(KEYGUARD);
         setDozing(/* dozing= */ false, /* dozingAlwaysOn= */ false);
@@ -539,6 +547,7 @@
     @Test
     public void keyguardStatusView_willPlayDelayedDoze_isCentered_thenNot() {
         when(mNotificationStackScrollLayoutController.getVisibleNotificationCount()).thenReturn(2);
+        when(mActiveNotificationsInteractor.getAreAnyNotificationsPresentValue()).thenReturn(true);
         mStatusBarStateController.setState(KEYGUARD);
         enableSplitShade(/* enabled= */ true);
 
@@ -553,6 +562,7 @@
     @Test
     public void keyguardStatusView_willPlayDelayedDoze_notifiesKeyguardMediaController() {
         when(mNotificationStackScrollLayoutController.getVisibleNotificationCount()).thenReturn(2);
+        when(mActiveNotificationsInteractor.getAreAnyNotificationsPresentValue()).thenReturn(true);
         mStatusBarStateController.setState(KEYGUARD);
         enableSplitShade(/* enabled= */ true);
 
@@ -564,6 +574,7 @@
     @Test
     public void keyguardStatusView_willPlayDelayedDoze_isCentered_thenStillCenteredIfNoNotifs() {
         when(mNotificationStackScrollLayoutController.getVisibleNotificationCount()).thenReturn(0);
+        when(mActiveNotificationsInteractor.getAreAnyNotificationsPresentValue()).thenReturn(false);
         mStatusBarStateController.setState(KEYGUARD);
         enableSplitShade(/* enabled= */ true);
 
@@ -700,10 +711,12 @@
         mStatusBarStateController.setState(KEYGUARD);
 
         when(mNotificationStackScrollLayoutController.getVisibleNotificationCount()).thenReturn(0);
+        when(mActiveNotificationsInteractor.getAreAnyNotificationsPresentValue()).thenReturn(false);
         triggerPositionClockAndNotifications();
         verify(mKeyguardStatusViewController).displayClock(LARGE, /* animate */ true);
 
         when(mNotificationStackScrollLayoutController.getVisibleNotificationCount()).thenReturn(1);
+        when(mActiveNotificationsInteractor.getAreAnyNotificationsPresentValue()).thenReturn(true);
         triggerPositionClockAndNotifications();
         verify(mKeyguardStatusViewController).displayClock(SMALL, /* animate */ true);
     }
@@ -715,10 +728,12 @@
         clearInvocations(mKeyguardStatusViewController);
 
         when(mNotificationStackScrollLayoutController.getVisibleNotificationCount()).thenReturn(0);
+        when(mActiveNotificationsInteractor.getAreAnyNotificationsPresentValue()).thenReturn(false);
         triggerPositionClockAndNotifications();
         verify(mKeyguardStatusViewController).displayClock(LARGE, /* animate */ true);
 
         when(mNotificationStackScrollLayoutController.getVisibleNotificationCount()).thenReturn(1);
+        when(mActiveNotificationsInteractor.getAreAnyNotificationsPresentValue()).thenReturn(true);
         triggerPositionClockAndNotifications();
         verify(mKeyguardStatusViewController, times(2))
                 .displayClock(LARGE, /* animate */ true);
@@ -730,6 +745,7 @@
     public void testHasNotifications_switchesToLargeClockWhenEnteringSplitShade() {
         mStatusBarStateController.setState(KEYGUARD);
         when(mNotificationStackScrollLayoutController.getVisibleNotificationCount()).thenReturn(1);
+        when(mActiveNotificationsInteractor.getAreAnyNotificationsPresentValue()).thenReturn(true);
 
         enableSplitShade(/* enabled= */ true);
 
@@ -740,6 +756,7 @@
     public void testNoNotifications_switchesToLargeClockWhenEnteringSplitShade() {
         mStatusBarStateController.setState(KEYGUARD);
         when(mNotificationStackScrollLayoutController.getVisibleNotificationCount()).thenReturn(0);
+        when(mActiveNotificationsInteractor.getAreAnyNotificationsPresentValue()).thenReturn(false);
 
         enableSplitShade(/* enabled= */ true);
 
@@ -752,6 +769,7 @@
         enableSplitShade(/* enabled= */ true);
         clearInvocations(mKeyguardStatusViewController);
         when(mNotificationStackScrollLayoutController.getVisibleNotificationCount()).thenReturn(1);
+        when(mActiveNotificationsInteractor.getAreAnyNotificationsPresentValue()).thenReturn(true);
 
         enableSplitShade(/* enabled= */ false);
 
@@ -764,6 +782,7 @@
         enableSplitShade(/* enabled= */ true);
         clearInvocations(mKeyguardStatusViewController);
         when(mNotificationStackScrollLayoutController.getVisibleNotificationCount()).thenReturn(0);
+        when(mActiveNotificationsInteractor.getAreAnyNotificationsPresentValue()).thenReturn(false);
 
         enableSplitShade(/* enabled= */ false);
 
@@ -777,6 +796,7 @@
         enableSplitShade(/* enabled= */ true);
         when(mMediaDataManager.hasActiveMediaOrRecommendation()).thenReturn(true);
         when(mNotificationStackScrollLayoutController.getVisibleNotificationCount()).thenReturn(2);
+        when(mActiveNotificationsInteractor.getAreAnyNotificationsPresentValue()).thenReturn(true);
         clearInvocations(mKeyguardStatusViewController);
 
         mNotificationPanelViewController.setDozing(/* dozing= */ true, /* animate= */ false);
@@ -791,6 +811,7 @@
         enableSplitShade(/* enabled= */ true);
         when(mMediaDataManager.hasActiveMediaOrRecommendation()).thenReturn(true);
         when(mNotificationStackScrollLayoutController.getVisibleNotificationCount()).thenReturn(2);
+        when(mActiveNotificationsInteractor.getAreAnyNotificationsPresentValue()).thenReturn(true);
         clearInvocations(mKeyguardStatusViewController);
 
         mNotificationPanelViewController.setDozing(/* dozing= */ true, /* animate= */ false);
@@ -847,6 +868,7 @@
         clearInvocations(mKeyguardStatusViewController);
         when(mMediaDataManager.hasActiveMedia()).thenReturn(true);
         when(mNotificationStackScrollLayoutController.getVisibleNotificationCount()).thenReturn(2);
+        when(mActiveNotificationsInteractor.getAreAnyNotificationsPresentValue()).thenReturn(true);
 
         mNotificationPanelViewController.setDozing(true, false);
 
@@ -863,6 +885,7 @@
         when(mResources.getBoolean(R.bool.force_small_clock_on_lockscreen)).thenReturn(true);
         when(mMediaDataManager.hasActiveMedia()).thenReturn(false);
         when(mNotificationStackScrollLayoutController.getVisibleNotificationCount()).thenReturn(0);
+        when(mActiveNotificationsInteractor.getAreAnyNotificationsPresentValue()).thenReturn(false);
         clearInvocations(mKeyguardStatusViewController);
 
         enableSplitShade(/* enabled= */ true);
@@ -881,6 +904,7 @@
         when(mResources.getBoolean(R.bool.force_small_clock_on_lockscreen)).thenReturn(true);
         when(mMediaDataManager.hasActiveMedia()).thenReturn(false);
         when(mNotificationStackScrollLayoutController.getVisibleNotificationCount()).thenReturn(0);
+        when(mActiveNotificationsInteractor.getAreAnyNotificationsPresentValue()).thenReturn(false);
         clearInvocations(mKeyguardStatusViewController);
 
         enableSplitShade(/* enabled= */ true);
@@ -898,11 +922,13 @@
 
         // one notification + media player visible
         when(mNotificationStackScrollLayoutController.getVisibleNotificationCount()).thenReturn(1);
+        when(mActiveNotificationsInteractor.getAreAnyNotificationsPresentValue()).thenReturn(true);
         triggerPositionClockAndNotifications();
         verify(mKeyguardStatusViewController).displayClock(SMALL, /* animate */ true);
 
         // only media player visible
         when(mNotificationStackScrollLayoutController.getVisibleNotificationCount()).thenReturn(0);
+        when(mActiveNotificationsInteractor.getAreAnyNotificationsPresentValue()).thenReturn(false);
         triggerPositionClockAndNotifications();
         verify(mKeyguardStatusViewController, times(2)).displayClock(SMALL, true);
         verify(mKeyguardStatusViewController, never()).displayClock(LARGE, /* animate */ true);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerTest.java
index 0c6f456..757f16c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationLockscreenUserManagerTest.java
@@ -598,6 +598,17 @@
 
     @Test
     @EnableFlags(FLAG_KEYGUARD_PRIVATE_NOTIFICATIONS)
+    public void testEarlyUserSwitch() {
+        mLockscreenUserManager =
+                new TestNotificationLockscreenUserManager(mContext);
+        mBackgroundExecutor.runAllReady();
+        mLockscreenUserManager.mUserChangedCallback.onUserChanging(
+                mCurrentUser.id, mContext);
+        // no crash!
+    }
+
+    @Test
+    @EnableFlags(FLAG_KEYGUARD_PRIVATE_NOTIFICATIONS)
     public void testKeyguardManager_noPrivateNotifications() {
         Mockito.clearInvocations(mDevicePolicyManager);
         // User allows notifications
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/StackCoordinatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/StackCoordinatorTest.kt
index 255cf6f..9b4a100 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/StackCoordinatorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/coordinator/StackCoordinatorTest.kt
@@ -31,6 +31,7 @@
 import com.android.systemui.statusbar.notification.collection.render.NotifStats
 import com.android.systemui.statusbar.notification.domain.interactor.ActiveNotificationsInteractor
 import com.android.systemui.statusbar.notification.domain.interactor.RenderNotificationListInteractor
+import com.android.systemui.statusbar.notification.footer.shared.FooterViewRefactor
 import com.android.systemui.statusbar.notification.shared.NotificationIconContainerRefactor
 import com.android.systemui.statusbar.notification.stack.BUCKET_ALERTING
 import com.android.systemui.statusbar.notification.stack.BUCKET_SILENT
@@ -80,7 +81,7 @@
     }
 
     @Test
-    @DisableFlags(NotificationIconContainerRefactor.FLAG_NAME)
+    @DisableFlags(NotificationIconContainerRefactor.FLAG_NAME, FooterViewRefactor.FLAG_NAME)
     fun testUpdateNotificationIcons() {
         afterRenderListListener.onAfterRenderList(listOf(entry), stackController)
         verify(notificationIconAreaController).updateNotificationIcons(eq(listOf(entry)))
diff --git a/packages/SystemUI/tests/src/com/android/systemui/unfold/DisplaySwitchLatencyTrackerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/unfold/DisplaySwitchLatencyTrackerTest.kt
new file mode 100644
index 0000000..ee2e5ad
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/unfold/DisplaySwitchLatencyTrackerTest.kt
@@ -0,0 +1,324 @@
+/*
+ * 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.unfold
+
+import android.content.Context
+import android.content.res.Resources
+import android.testing.AndroidTestingRunner
+import androidx.test.filters.SmallTest
+import com.android.internal.R
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.display.data.repository.DeviceStateRepository
+import com.android.systemui.display.data.repository.DeviceStateRepository.DeviceState
+import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
+import com.android.systemui.power.domain.interactor.PowerInteractor
+import com.android.systemui.power.shared.model.ScreenPowerState
+import com.android.systemui.power.shared.model.WakeSleepReason
+import com.android.systemui.power.shared.model.WakefulnessModel
+import com.android.systemui.power.shared.model.WakefulnessState
+import com.android.systemui.shared.system.SysUiStatsLog
+import com.android.systemui.unfold.DisplaySwitchLatencyTracker.Companion.FOLDABLE_DEVICE_STATE_CLOSED
+import com.android.systemui.unfold.DisplaySwitchLatencyTracker.Companion.FOLDABLE_DEVICE_STATE_HALF_OPEN
+import com.android.systemui.unfold.DisplaySwitchLatencyTracker.DisplaySwitchLatencyEvent
+import com.android.systemui.unfold.data.repository.UnfoldTransitionRepositoryImpl
+import com.android.systemui.unfold.domain.interactor.UnfoldTransitionInteractorImpl
+import com.android.systemui.util.animation.data.repository.AnimationStatusRepository
+import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.capture
+import com.android.systemui.util.mockito.mock
+import com.android.systemui.util.time.FakeSystemClock
+import com.google.common.truth.Truth.assertThat
+import java.util.Optional
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.asExecutor
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.test.StandardTestDispatcher
+import kotlinx.coroutines.test.TestDispatcher
+import kotlinx.coroutines.test.TestScope
+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.ArgumentCaptor
+import org.mockito.Captor
+import org.mockito.Mockito.never
+import org.mockito.Mockito.verify
+import org.mockito.Mockito.`when` as whenever
+import org.mockito.MockitoAnnotations
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@RunWith(AndroidTestingRunner::class)
+@SmallTest
+class DisplaySwitchLatencyTrackerTest : SysuiTestCase() {
+    private lateinit var displaySwitchLatencyTracker: DisplaySwitchLatencyTracker
+    @Captor private lateinit var loggerArgumentCaptor: ArgumentCaptor<DisplaySwitchLatencyEvent>
+
+    private val mockContext = mock<Context>()
+    private val resources = mock<Resources>()
+    private val foldStateRepository = mock<DeviceStateRepository>()
+    private val powerInteractor = mock<PowerInteractor>()
+    private val animationStatusRepository = mock<AnimationStatusRepository>()
+    private val keyguardInteractor = mock<KeyguardInteractor>()
+    private val displaySwitchLatencyLogger = mock<DisplaySwitchLatencyLogger>()
+
+    private val nonEmptyClosedDeviceStatesArray: IntArray = IntArray(2) { 0 }
+    private val testDispatcher: TestDispatcher = StandardTestDispatcher()
+    private val testScope: TestScope = TestScope(testDispatcher)
+    private val isAsleep = MutableStateFlow(false)
+    private val isAodAvailable = MutableStateFlow(false)
+    private val deviceState = MutableStateFlow(DeviceState.UNFOLDED)
+    private val screenPowerState = MutableStateFlow(ScreenPowerState.SCREEN_ON)
+    private val areAnimationEnabled = MutableStateFlow(true)
+    private val lastWakefulnessEvent = MutableStateFlow(WakefulnessModel())
+    private val systemClock = FakeSystemClock()
+    private val unfoldTransitionProgressProvider = TestUnfoldTransitionProvider()
+    private val unfoldTransitionRepository =
+        UnfoldTransitionRepositoryImpl(Optional.of(unfoldTransitionProgressProvider))
+    private val unfoldTransitionInteractor =
+        UnfoldTransitionInteractorImpl(unfoldTransitionRepository)
+
+    @Before
+    fun setup() {
+        MockitoAnnotations.initMocks(this)
+        whenever(mockContext.resources).thenReturn(resources)
+        whenever(resources.getIntArray(R.array.config_foldedDeviceStates))
+            .thenReturn(nonEmptyClosedDeviceStatesArray)
+        whenever(foldStateRepository.state).thenReturn(deviceState)
+        whenever(powerInteractor.isAsleep).thenReturn(isAsleep)
+        whenever(animationStatusRepository.areAnimationsEnabled()).thenReturn(areAnimationEnabled)
+        whenever(powerInteractor.screenPowerState).thenReturn(screenPowerState)
+        whenever(keyguardInteractor.isAodAvailable).thenReturn(isAodAvailable)
+        whenever(powerInteractor.detailedWakefulness).thenReturn(lastWakefulnessEvent)
+
+        displaySwitchLatencyTracker =
+            DisplaySwitchLatencyTracker(
+                mockContext,
+                foldStateRepository,
+                powerInteractor,
+                unfoldTransitionInteractor,
+                animationStatusRepository,
+                keyguardInteractor,
+                testDispatcher.asExecutor(),
+                testScope.backgroundScope,
+                displaySwitchLatencyLogger,
+                systemClock
+            )
+    }
+
+    @Test
+    fun unfold_logsLatencyTillTransitionStarted() {
+        testScope.runTest {
+            areAnimationEnabled.emit(true)
+
+            displaySwitchLatencyTracker.start()
+            deviceState.emit(DeviceState.FOLDED)
+            screenPowerState.emit(ScreenPowerState.SCREEN_OFF)
+            systemClock.advanceTime(50)
+            runCurrent()
+            deviceState.emit(DeviceState.HALF_FOLDED)
+            runCurrent()
+            systemClock.advanceTime(50)
+            screenPowerState.emit(ScreenPowerState.SCREEN_ON)
+            systemClock.advanceTime(200)
+            unfoldTransitionProgressProvider.onTransitionStarted()
+            runCurrent()
+            deviceState.emit(DeviceState.UNFOLDED)
+
+            verify(displaySwitchLatencyLogger).log(capture(loggerArgumentCaptor))
+            val loggedEvent = loggerArgumentCaptor.value
+            val expectedLoggedEvent =
+                DisplaySwitchLatencyEvent(
+                    latencyMs = 250,
+                    fromFoldableDeviceState = FOLDABLE_DEVICE_STATE_CLOSED,
+                    toFoldableDeviceState = FOLDABLE_DEVICE_STATE_HALF_OPEN
+                )
+            assertThat(loggedEvent).isEqualTo(expectedLoggedEvent)
+        }
+    }
+
+    @Test
+    fun unfold_progressUnavailable_logsLatencyTillScreenTurnedOn() {
+        testScope.runTest {
+            val unfoldTransitionInteractorWithEmptyProgressProvider =
+                UnfoldTransitionInteractorImpl(UnfoldTransitionRepositoryImpl(Optional.empty()))
+            displaySwitchLatencyTracker =
+                DisplaySwitchLatencyTracker(
+                    mockContext,
+                    foldStateRepository,
+                    powerInteractor,
+                    unfoldTransitionInteractorWithEmptyProgressProvider,
+                    animationStatusRepository,
+                    keyguardInteractor,
+                    testDispatcher.asExecutor(),
+                    testScope.backgroundScope,
+                    displaySwitchLatencyLogger,
+                    systemClock
+                )
+            areAnimationEnabled.emit(true)
+
+            displaySwitchLatencyTracker.start()
+            deviceState.emit(DeviceState.FOLDED)
+            screenPowerState.emit(ScreenPowerState.SCREEN_OFF)
+            systemClock.advanceTime(50)
+            runCurrent()
+            deviceState.emit(DeviceState.HALF_FOLDED)
+            systemClock.advanceTime(50)
+            runCurrent()
+            screenPowerState.emit(ScreenPowerState.SCREEN_ON)
+            systemClock.advanceTime(50)
+            runCurrent()
+            systemClock.advanceTime(200)
+            unfoldTransitionProgressProvider.onTransitionStarted()
+            runCurrent()
+            deviceState.emit(DeviceState.UNFOLDED)
+
+            verify(displaySwitchLatencyLogger).log(capture(loggerArgumentCaptor))
+            val loggedEvent = loggerArgumentCaptor.value
+            val expectedLoggedEvent =
+                DisplaySwitchLatencyEvent(
+                    latencyMs = 50,
+                    fromFoldableDeviceState = FOLDABLE_DEVICE_STATE_CLOSED,
+                    toFoldableDeviceState = FOLDABLE_DEVICE_STATE_HALF_OPEN
+                )
+            assertThat(loggedEvent).isEqualTo(expectedLoggedEvent)
+        }
+    }
+
+    @Test
+    fun unfold_animationDisabled_logsLatencyTillScreenTurnedOn() {
+        testScope.runTest {
+            areAnimationEnabled.emit(false)
+
+            displaySwitchLatencyTracker.start()
+            deviceState.emit(DeviceState.FOLDED)
+            screenPowerState.emit(ScreenPowerState.SCREEN_OFF)
+            systemClock.advanceTime(50)
+            runCurrent()
+            deviceState.emit(DeviceState.HALF_FOLDED)
+            systemClock.advanceTime(50)
+            runCurrent()
+            screenPowerState.emit(ScreenPowerState.SCREEN_ON)
+            systemClock.advanceTime(50)
+            runCurrent()
+            unfoldTransitionProgressProvider.onTransitionStarted()
+            systemClock.advanceTime(200)
+            runCurrent()
+            deviceState.emit(DeviceState.UNFOLDED)
+
+            verify(displaySwitchLatencyLogger).log(capture(loggerArgumentCaptor))
+            val loggedEvent = loggerArgumentCaptor.value
+            val expectedLoggedEvent =
+                DisplaySwitchLatencyEvent(
+                    latencyMs = 50,
+                    fromFoldableDeviceState = FOLDABLE_DEVICE_STATE_CLOSED,
+                    toFoldableDeviceState = FOLDABLE_DEVICE_STATE_HALF_OPEN
+                )
+            assertThat(loggedEvent).isEqualTo(expectedLoggedEvent)
+        }
+    }
+
+    @Test
+    fun foldWhileStayingAwake_logsLatency() {
+        testScope.runTest {
+            areAnimationEnabled.emit(true)
+            deviceState.emit(DeviceState.UNFOLDED)
+            screenPowerState.emit(ScreenPowerState.SCREEN_ON)
+
+            displaySwitchLatencyTracker.start()
+            deviceState.emit(DeviceState.HALF_FOLDED)
+            systemClock.advanceTime(50)
+            runCurrent()
+            deviceState.emit(DeviceState.FOLDED)
+            screenPowerState.emit(ScreenPowerState.SCREEN_OFF)
+            runCurrent()
+            systemClock.advanceTime(200)
+            screenPowerState.emit(ScreenPowerState.SCREEN_ON)
+            runCurrent()
+
+            verify(displaySwitchLatencyLogger).log(capture(loggerArgumentCaptor))
+            val loggedEvent = loggerArgumentCaptor.value
+            val expectedLoggedEvent =
+                DisplaySwitchLatencyEvent(
+                    latencyMs = 200,
+                    fromFoldableDeviceState = FOLDABLE_DEVICE_STATE_HALF_OPEN,
+                    toFoldableDeviceState = FOLDABLE_DEVICE_STATE_CLOSED
+                )
+            assertThat(loggedEvent).isEqualTo(expectedLoggedEvent)
+        }
+    }
+
+    @Test
+    fun foldToAod_capturesToStateAsAod() {
+        testScope.runTest {
+            areAnimationEnabled.emit(true)
+            deviceState.emit(DeviceState.UNFOLDED)
+            isAodAvailable.emit(true)
+
+            displaySwitchLatencyTracker.start()
+            deviceState.emit(DeviceState.HALF_FOLDED)
+            systemClock.advanceTime(50)
+            runCurrent()
+            deviceState.emit(DeviceState.FOLDED)
+            lastWakefulnessEvent.emit(
+                WakefulnessModel(
+                    internalWakefulnessState = WakefulnessState.ASLEEP,
+                    lastSleepReason = WakeSleepReason.FOLD
+                )
+            )
+            screenPowerState.emit(ScreenPowerState.SCREEN_OFF)
+            runCurrent()
+            systemClock.advanceTime(200)
+            screenPowerState.emit(ScreenPowerState.SCREEN_ON)
+            runCurrent()
+
+            verify(displaySwitchLatencyLogger).log(capture(loggerArgumentCaptor))
+            val loggedEvent = loggerArgumentCaptor.value
+            val expectedLoggedEvent =
+                DisplaySwitchLatencyEvent(
+                    latencyMs = 200,
+                    fromFoldableDeviceState = FOLDABLE_DEVICE_STATE_HALF_OPEN,
+                    toFoldableDeviceState = FOLDABLE_DEVICE_STATE_CLOSED,
+                    toState = SysUiStatsLog.DISPLAY_SWITCH_LATENCY_TRACKED__TO_STATE__AOD
+                )
+            assertThat(loggedEvent).isEqualTo(expectedLoggedEvent)
+        }
+    }
+
+    @Test
+    fun fold_notAFoldable_shouldNotLogLatency() {
+        testScope.runTest {
+            areAnimationEnabled.emit(true)
+            deviceState.emit(DeviceState.UNFOLDED)
+            whenever(resources.getIntArray(R.array.config_foldedDeviceStates))
+                .thenReturn(IntArray(0))
+
+            displaySwitchLatencyTracker.start()
+            deviceState.emit(DeviceState.HALF_FOLDED)
+            systemClock.advanceTime(50)
+            runCurrent()
+            deviceState.emit(DeviceState.FOLDED)
+            screenPowerState.emit(ScreenPowerState.SCREEN_OFF)
+            runCurrent()
+            systemClock.advanceTime(200)
+            screenPowerState.emit(ScreenPowerState.SCREEN_ON)
+            runCurrent()
+
+            verify(displaySwitchLatencyLogger, never()).log(any())
+        }
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/unfold/updates/FoldStateRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/unfold/FoldStateRepositoryTest.kt
similarity index 82%
rename from packages/SystemUI/tests/src/com/android/systemui/unfold/updates/FoldStateRepositoryTest.kt
rename to packages/SystemUI/tests/src/com/android/systemui/unfold/FoldStateRepositoryTest.kt
index 0651323..ab779a7 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/unfold/updates/FoldStateRepositoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/unfold/FoldStateRepositoryTest.kt
@@ -13,13 +13,20 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package com.android.systemui.unfold.updates
+package com.android.systemui.unfold
 
 import android.testing.AndroidTestingRunner
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.coroutines.collectLastValue
-import com.android.systemui.unfold.updates.FoldStateRepository.FoldUpdate
+import com.android.systemui.unfold.data.repository.FoldStateRepository.FoldUpdate
+import com.android.systemui.unfold.data.repository.FoldStateRepositoryImpl
+import com.android.systemui.unfold.updates.FOLD_UPDATE_FINISH_CLOSED
+import com.android.systemui.unfold.updates.FOLD_UPDATE_FINISH_FULL_OPEN
+import com.android.systemui.unfold.updates.FOLD_UPDATE_FINISH_HALF_OPEN
+import com.android.systemui.unfold.updates.FOLD_UPDATE_START_CLOSING
+import com.android.systemui.unfold.updates.FOLD_UPDATE_START_OPENING
+import com.android.systemui.unfold.updates.FoldStateProvider
 import com.android.systemui.util.mockito.argumentCaptor
 import com.android.systemui.util.mockito.capture
 import com.android.systemui.util.mockito.mock
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/SysuiTestCase.java b/packages/SystemUI/tests/utils/src/com/android/systemui/SysuiTestCase.java
index 29e737e..d23dae9 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/SysuiTestCase.java
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/SysuiTestCase.java
@@ -33,6 +33,7 @@
 import android.testing.TestableLooper;
 import android.util.Log;
 
+import androidx.annotation.NonNull;
 import androidx.core.animation.AndroidXAnimatorIsolationRule;
 import androidx.test.InstrumentationRegistry;
 import androidx.test.uiautomator.UiDevice;
@@ -68,8 +69,20 @@
     public final SetFlagsRule mSetFlagsRule = new SetFlagsRule(DEVICE_DEFAULT);
 
     @Rule
-    public SysuiTestableContext mContext = new SysuiTestableContext(
-            InstrumentationRegistry.getContext(), getLeakCheck());
+    public SysuiTestableContext mContext = createTestableContext();
+
+    @NonNull
+    private SysuiTestableContext createTestableContext() {
+        SysuiTestableContext context = new SysuiTestableContext(
+                InstrumentationRegistry.getContext(), getLeakCheck());
+        if (isRobolectricTest()) {
+            // Manually associate a Display to context for Robolectric test. Similar to b/214297409
+            return context.createDefaultDisplayContext();
+        } else {
+            return context;
+        }
+    }
+
     @Rule
     public final DexmakerShareClassLoaderRule mDexmakerShareClassLoaderRule =
             new DexmakerShareClassLoaderRule();
@@ -84,10 +97,6 @@
 
     @Before
     public void SysuiSetup() throws Exception {
-        // Manually associate a Display to context for Robolectric test. Similar to b/214297409
-        if (isRobolectricTest()) {
-            mContext = mContext.createDefaultDisplayContext();
-        }
         mSysuiDependency = new SysuiTestDependency(mContext, shouldFailOnLeakedReceiver());
         mDependency = mSysuiDependency.install();
         mRealInstrumentation = InstrumentationRegistry.getInstrumentation();
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/common/data/repository/FakePackageChangeRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/common/data/repository/FakePackageChangeRepository.kt
new file mode 100644
index 0000000..60f0448
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/common/data/repository/FakePackageChangeRepository.kt
@@ -0,0 +1,36 @@
+/*
+ * 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.common.data.repository
+
+import android.os.UserHandle
+import com.android.systemui.common.data.shared.model.PackageChangeModel
+import kotlinx.coroutines.flow.MutableSharedFlow
+import kotlinx.coroutines.flow.filter
+
+class FakePackageChangeRepository : PackageChangeRepository {
+
+    private var _packageChanged = MutableSharedFlow<PackageChangeModel>()
+
+    override fun packageChanged(user: UserHandle) =
+        _packageChanged.filter {
+            user == UserHandle.ALL || user == UserHandle.getUserHandleForUid(it.packageUid)
+        }
+
+    suspend fun notifyChange(model: PackageChangeModel) {
+        _packageChanged.emit(model)
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/di/bound/CustomTileUser.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/common/data/repository/PackageChangeRepositoryKosmos.kt
similarity index 66%
copy from packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/di/bound/CustomTileUser.kt
copy to packages/SystemUI/tests/utils/src/com/android/systemui/common/data/repository/PackageChangeRepositoryKosmos.kt
index efc7431..adc05e0 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/di/bound/CustomTileUser.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/common/data/repository/PackageChangeRepositoryKosmos.kt
@@ -14,12 +14,10 @@
  * limitations under the License.
  */
 
-package com.android.systemui.qs.tiles.impl.custom.di.bound
+package com.android.systemui.common.data.repository
 
-import javax.inject.Qualifier
+import com.android.systemui.kosmos.Kosmos
 
-/** User associated with current custom tile binding. */
-@Qualifier
-@MustBeDocumented
-@Retention(AnnotationRetention.RUNTIME)
-annotation class CustomTileUser
+var Kosmos.packageChangeRepository: PackageChangeRepository by
+    Kosmos.Fixture { fakePackageChangeRepository }
+val Kosmos.fakePackageChangeRepository by Kosmos.Fixture { FakePackageChangeRepository() }
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 4200f05..975db3b 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
@@ -80,7 +80,7 @@
     override val lastDozeTapToWakePosition = _lastDozeTapToWakePosition.asStateFlow()
 
     private val _isAodAvailable = MutableStateFlow(false)
-    override val isAodAvailable: Flow<Boolean> = _isAodAvailable
+    override val isAodAvailable: StateFlow<Boolean> = _isAodAvailable
 
     private val _isDreaming = MutableStateFlow(false)
     override val isDreaming: Flow<Boolean> = _isDreaming
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/BurnInInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/BurnInInteractorKosmos.kt
index b0d941d..a9d89a3 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/BurnInInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/BurnInInteractorKosmos.kt
@@ -26,7 +26,7 @@
 import com.android.systemui.kosmos.applicationCoroutineScope
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 
-val Kosmos.burnInInteractor by Fixture {
+var Kosmos.burnInInteractor by Fixture {
     BurnInInteractor(
         context = applicationContext,
         burnInHelperWrapper = burnInHelperWrapper,
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/di/bound/CustomTileUser.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardBottomAreaInteractorKosmos.kt
similarity index 63%
copy from packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/di/bound/CustomTileUser.kt
copy to packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardBottomAreaInteractorKosmos.kt
index efc7431..a3955f7 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/custom/di/bound/CustomTileUser.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardBottomAreaInteractorKosmos.kt
@@ -14,12 +14,14 @@
  * limitations under the License.
  */
 
-package com.android.systemui.qs.tiles.impl.custom.di.bound
+package com.android.systemui.keyguard.domain.interactor
 
-import javax.inject.Qualifier
+import com.android.systemui.keyguard.data.repository.keyguardRepository
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.Kosmos.Fixture
 
-/** User associated with current custom tile binding. */
-@Qualifier
-@MustBeDocumented
-@Retention(AnnotationRetention.RUNTIME)
-annotation class CustomTileUser
+val Kosmos.keyguardBottomAreaInteractor by Fixture {
+    KeyguardBottomAreaInteractor(
+        repository = keyguardRepository,
+    )
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/AodAlphaViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/AodAlphaViewModelKosmos.kt
new file mode 100644
index 0000000..6b89e0f
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/AodAlphaViewModelKosmos.kt
@@ -0,0 +1,33 @@
+/*
+ * 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.
+ */
+
+@file:OptIn(ExperimentalCoroutinesApi::class)
+
+package com.android.systemui.keyguard.ui.viewmodel
+
+import com.android.systemui.keyguard.domain.interactor.keyguardInteractor
+import com.android.systemui.keyguard.domain.interactor.keyguardTransitionInteractor
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.Kosmos.Fixture
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+
+val Kosmos.aodAlphaViewModel by Fixture {
+    AodAlphaViewModel(
+        keyguardInteractor = keyguardInteractor,
+        keyguardTransitionInteractor = keyguardTransitionInteractor,
+        occludedToLockscreenTransitionViewModel = occludedToLockscreenTransitionViewModel,
+    )
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/AodBurnInViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/AodBurnInViewModelKosmos.kt
new file mode 100644
index 0000000..35cfa89
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/AodBurnInViewModelKosmos.kt
@@ -0,0 +1,39 @@
+/*
+ * 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.
+ */
+
+@file:OptIn(ExperimentalCoroutinesApi::class)
+
+package com.android.systemui.keyguard.ui.viewmodel
+
+import com.android.systemui.common.ui.domain.interactor.configurationInteractor
+import com.android.systemui.keyguard.domain.interactor.burnInInteractor
+import com.android.systemui.keyguard.domain.interactor.keyguardInteractor
+import com.android.systemui.keyguard.domain.interactor.keyguardTransitionInteractor
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.Kosmos.Fixture
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+
+val Kosmos.aodBurnInViewModel by Fixture {
+    AodBurnInViewModel(
+        burnInInteractor = burnInInteractor,
+        configurationInteractor = configurationInteractor,
+        keyguardInteractor = keyguardInteractor,
+        keyguardTransitionInteractor = keyguardTransitionInteractor,
+        goneToAodTransitionViewModel = goneToAodTransitionViewModel,
+        occludedToLockscreenTransitionViewModel = occludedToLockscreenTransitionViewModel,
+        keyguardClockViewModel = keyguardClockViewModel,
+    )
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/GoneToAodTransitionViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/GoneToAodTransitionViewModelKosmos.kt
index 14e2cff..00ece14 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/GoneToAodTransitionViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/GoneToAodTransitionViewModelKosmos.kt
@@ -25,7 +25,7 @@
 import com.android.systemui.kosmos.Kosmos.Fixture
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 
-val Kosmos.goneToAodTransitionViewModel by Fixture {
+var Kosmos.goneToAodTransitionViewModel by Fixture {
     GoneToAodTransitionViewModel(
         interactor = keyguardTransitionInteractor,
         deviceEntryUdfpsInteractor = deviceEntryUdfpsInteractor,
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardClockViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardClockViewModelKosmos.kt
index d878683..5ca0439 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardClockViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardClockViewModelKosmos.kt
@@ -20,6 +20,7 @@
 import com.android.systemui.keyguard.domain.interactor.keyguardInteractor
 import com.android.systemui.kosmos.Kosmos
 import com.android.systemui.kosmos.applicationCoroutineScope
+import com.android.systemui.statusbar.policy.splitShadeStateController
 
 val Kosmos.keyguardClockViewModel by
     Kosmos.Fixture {
@@ -27,5 +28,6 @@
             keyguardInteractor = keyguardInteractor,
             keyguardClockInteractor = keyguardClockInteractor,
             applicationScope = applicationCoroutineScope,
+            splitShadeStateController = splitShadeStateController,
         )
     }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelKosmos.kt
index 13ee747..933f50c 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelKosmos.kt
@@ -18,10 +18,7 @@
 
 package com.android.systemui.keyguard.ui.viewmodel
 
-import com.android.systemui.common.ui.domain.interactor.configurationInteractor
 import com.android.systemui.deviceentry.domain.interactor.deviceEntryInteractor
-import com.android.systemui.flags.FakeFeatureFlagsClassic
-import com.android.systemui.keyguard.domain.interactor.burnInInteractor
 import com.android.systemui.keyguard.domain.interactor.keyguardInteractor
 import com.android.systemui.keyguard.domain.interactor.keyguardTransitionInteractor
 import com.android.systemui.kosmos.Kosmos
@@ -33,18 +30,14 @@
 
 val Kosmos.keyguardRootViewModel by Fixture {
     KeyguardRootViewModel(
-        configurationInteractor = configurationInteractor,
         deviceEntryInteractor = deviceEntryInteractor,
         dozeParameters = dozeParameters,
         keyguardInteractor = keyguardInteractor,
         keyguardTransitionInteractor = keyguardTransitionInteractor,
         notificationsKeyguardInteractor = notificationsKeyguardInteractor,
-        burnInInteractor = burnInInteractor,
-        goneToAodTransitionViewModel = goneToAodTransitionViewModel,
         aodToLockscreenTransitionViewModel = aodToLockscreenTransitionViewModel,
-        occludedToLockscreenTransitionViewModel = occludedToLockscreenTransitionViewModel,
         screenOffAnimationController = screenOffAnimationController,
-        keyguardClockViewModel = keyguardClockViewModel,
-        featureFlags = FakeFeatureFlagsClassic(),
+        aodBurnInViewModel = aodBurnInViewModel,
+        aodAlphaViewModel = aodAlphaViewModel,
     )
 }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToLockscreenTransitionViewModelKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToLockscreenTransitionViewModelKosmos.kt
index 5bbde2b..93ecb79 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToLockscreenTransitionViewModelKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/ui/viewmodel/OccludedToLockscreenTransitionViewModelKosmos.kt
@@ -26,7 +26,7 @@
 import com.android.systemui.kosmos.Kosmos.Fixture
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 
-val Kosmos.occludedToLockscreenTransitionViewModel by Fixture {
+var Kosmos.occludedToLockscreenTransitionViewModel by Fixture {
     OccludedToLockscreenTransitionViewModel(
         interactor = keyguardTransitionInteractor,
         deviceEntryUdfpsInteractor = deviceEntryUdfpsInteractor,
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/impl/custom/CustomTileKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/impl/custom/CustomTileKosmos.kt
index d705248..14f28fe 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/impl/custom/CustomTileKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/impl/custom/CustomTileKosmos.kt
@@ -33,6 +33,7 @@
 val Kosmos.customTileRepository: FakeCustomTileRepository by
     Kosmos.Fixture {
         FakeCustomTileRepository(
+            tileSpec,
             customTileStatePersister,
             packageManagerAdapterFacade,
             testScope.testScheduler,
@@ -46,4 +47,4 @@
     Kosmos.Fixture { FakeCustomTilePackageUpdatesRepository() }
 
 val Kosmos.packageManagerAdapterFacade: FakePackageManagerAdapterFacade by
-    Kosmos.Fixture { FakePackageManagerAdapterFacade(tileSpec) }
+    Kosmos.Fixture { FakePackageManagerAdapterFacade(tileSpec.componentName) }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/impl/custom/data/repository/FakeCustomTileRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/impl/custom/data/repository/FakeCustomTileRepository.kt
index ba803d8..c110da0 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/impl/custom/data/repository/FakeCustomTileRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/impl/custom/data/repository/FakeCustomTileRepository.kt
@@ -19,11 +19,13 @@
 import android.os.UserHandle
 import android.service.quicksettings.Tile
 import com.android.systemui.qs.external.FakeCustomTileStatePersister
+import com.android.systemui.qs.pipeline.shared.TileSpec
 import com.android.systemui.qs.tiles.impl.custom.data.entity.CustomTileDefaults
 import kotlin.coroutines.CoroutineContext
 import kotlinx.coroutines.flow.Flow
 
 class FakeCustomTileRepository(
+    tileSpec: TileSpec.CustomTileSpec,
     customTileStatePersister: FakeCustomTileStatePersister,
     private val packageManagerAdapterFacade: FakePackageManagerAdapterFacade,
     testBackgroundContext: CoroutineContext,
@@ -31,12 +33,16 @@
 
     private val realDelegate: CustomTileRepository =
         CustomTileRepositoryImpl(
-            packageManagerAdapterFacade.tileSpec,
+            tileSpec,
             customTileStatePersister,
             packageManagerAdapterFacade.packageManagerAdapter,
             testBackgroundContext,
         )
 
+    init {
+        require(tileSpec.componentName == packageManagerAdapterFacade.componentName)
+    }
+
     override suspend fun restoreForTheUserIfNeeded(user: UserHandle, isPersistable: Boolean) =
         realDelegate.restoreForTheUserIfNeeded(user, isPersistable)
 
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 c9a7655..634d121 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
@@ -16,17 +16,27 @@
 
 package com.android.systemui.qs.tiles.impl.custom.data.repository
 
+import android.content.ComponentName
 import android.content.pm.ServiceInfo
 import android.os.Bundle
 import com.android.systemui.qs.external.PackageManagerAdapter
-import com.android.systemui.qs.pipeline.shared.TileSpec
 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
 
+/**
+ * Facade for [PackageManagerAdapter] to provide a fake-like behaviour. You can create this class
+ * and then get [packageManagerAdapter] to use in your test code.
+ *
+ * This allows to mock [PackageManagerAdapter] to provide a custom behaviour for
+ * [CustomTileRepository.isTileActive], [CustomTileRepository.isTileToggleable],
+ * [com.android.systemui.qs.external.TileServiceManager.isToggleableTile] or
+ * [com.android.systemui.qs.external.TileServiceManager.isActiveTile] when the real objects are
+ * used.
+ */
 class FakePackageManagerAdapterFacade(
-    val tileSpec: TileSpec.CustomTileSpec,
+    val componentName: ComponentName,
     val packageManagerAdapter: PackageManagerAdapter = mock {},
 ) {
 
@@ -34,22 +44,21 @@
     private var isActive: Boolean = false
 
     init {
-        whenever(packageManagerAdapter.getServiceInfo(eq(tileSpec.componentName), any()))
-            .thenAnswer {
-                ServiceInfo().apply {
-                    metaData =
-                        Bundle().apply {
-                            putBoolean(
-                                android.service.quicksettings.TileService.META_DATA_TOGGLEABLE_TILE,
-                                isToggleable
-                            )
-                            putBoolean(
-                                android.service.quicksettings.TileService.META_DATA_ACTIVE_TILE,
-                                isActive
-                            )
-                        }
-                }
+        whenever(packageManagerAdapter.getServiceInfo(eq(componentName), any())).thenAnswer {
+            ServiceInfo().apply {
+                metaData =
+                    Bundle().apply {
+                        putBoolean(
+                            android.service.quicksettings.TileService.META_DATA_TOGGLEABLE_TILE,
+                            isToggleable
+                        )
+                        putBoolean(
+                            android.service.quicksettings.TileService.META_DATA_ACTIVE_TILE,
+                            isActive
+                        )
+                    }
             }
+        }
     }
 
     fun setIsActive(isActive: Boolean) {
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/util/FakeSystemUIDialogController.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/util/FakeSystemUIDialogController.kt
index 0c9ce0f..697b508 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/util/FakeSystemUIDialogController.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/util/FakeSystemUIDialogController.kt
@@ -17,6 +17,7 @@
 
 package com.android.systemui.util
 
+import android.content.Context
 import android.content.DialogInterface
 import com.android.systemui.statusbar.phone.SystemUIDialog
 import com.android.systemui.util.mockito.any
@@ -27,13 +28,15 @@
 import org.mockito.Mockito.verify
 import org.mockito.stubbing.Stubber
 
-class FakeSystemUIDialogController {
+class FakeSystemUIDialogController(context: Context) {
 
     val dialog: SystemUIDialog = mock()
 
+
     private val clickListeners: MutableMap<Int, DialogInterface.OnClickListener> = mutableMapOf()
 
     init {
+        whenever(dialog.context).thenReturn(context)
         saveListener(DialogInterface.BUTTON_POSITIVE)
             .whenever(dialog)
             .setPositiveButton(any(), any())
diff --git a/packages/SystemUI/unfold/src/com/android/systemui/unfold/UnfoldSharedModule.kt b/packages/SystemUI/unfold/src/com/android/systemui/unfold/UnfoldSharedModule.kt
index f7fb014..1b7e71a 100644
--- a/packages/SystemUI/unfold/src/com/android/systemui/unfold/UnfoldSharedModule.kt
+++ b/packages/SystemUI/unfold/src/com/android/systemui/unfold/UnfoldSharedModule.kt
@@ -27,8 +27,6 @@
 import com.android.systemui.unfold.progress.UnfoldTransitionProgressForwarder
 import com.android.systemui.unfold.updates.DeviceFoldStateProvider
 import com.android.systemui.unfold.updates.FoldStateProvider
-import com.android.systemui.unfold.updates.FoldStateRepository
-import com.android.systemui.unfold.updates.FoldStateRepositoryImpl
 import com.android.systemui.unfold.updates.RotationChangeProvider
 import com.android.systemui.unfold.updates.hinge.EmptyHingeAngleProvider
 import com.android.systemui.unfold.updates.hinge.HingeAngleProvider
@@ -68,10 +66,6 @@
     fun unfoldKeyguardVisibilityManager(
         impl: UnfoldKeyguardVisibilityManagerImpl
     ): UnfoldKeyguardVisibilityManager = impl
-
-    @Provides
-    @Singleton
-    fun foldStateRepository(impl: FoldStateRepositoryImpl): FoldStateRepository = impl
 }
 
 @Module
diff --git a/packages/SystemUI/unfold/src/com/android/systemui/unfold/progress/UnfoldRemoteFilter.kt b/packages/SystemUI/unfold/src/com/android/systemui/unfold/progress/UnfoldRemoteFilter.kt
index 843cc3b..54d8054 100644
--- a/packages/SystemUI/unfold/src/com/android/systemui/unfold/progress/UnfoldRemoteFilter.kt
+++ b/packages/SystemUI/unfold/src/com/android/systemui/unfold/progress/UnfoldRemoteFilter.kt
@@ -41,8 +41,6 @@
             if (inProgress) {
                 logCounter({ "$TAG#filtered_progress" }, newProgress)
                 listener.onTransitionProgress(newProgress)
-            } else {
-                Log.e(TAG, "Filtered progress received received while animation not in progress.")
             }
             field = newProgress
         }
diff --git a/packages/overlays/Android.bp b/packages/overlays/Android.bp
new file mode 100644
index 0000000..5e001fb
--- /dev/null
+++ b/packages/overlays/Android.bp
@@ -0,0 +1,44 @@
+// Copyright (C) 2019 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//      http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package {
+    // See: http://go/android-license-faq
+    default_applicable_licenses: [
+        "frameworks_base_license",
+    ],
+}
+
+phony {
+    name: "frameworks-base-overlays",
+    required: [
+        "DisplayCutoutEmulationCornerOverlay",
+        "DisplayCutoutEmulationDoubleOverlay",
+        "DisplayCutoutEmulationHoleOverlay",
+        "DisplayCutoutEmulationTallOverlay",
+        "DisplayCutoutEmulationWaterfallOverlay",
+        "FontNotoSerifSourceOverlay",
+        "NavigationBarMode3ButtonOverlay",
+        "NavigationBarModeGesturalOverlay",
+        "NavigationBarModeGesturalOverlayNarrowBack",
+        "NavigationBarModeGesturalOverlayWideBack",
+        "NavigationBarModeGesturalOverlayExtraWideBack",
+        "TransparentNavigationBarOverlay",
+        "NotesRoleEnabledOverlay",
+        "preinstalled-packages-platform-overlays.xml",
+    ],
+}
+
+phony {
+    name: "frameworks-base-overlays-debug",
+}
diff --git a/packages/overlays/Android.mk b/packages/overlays/Android.mk
deleted file mode 100644
index a41d0e5..0000000
--- a/packages/overlays/Android.mk
+++ /dev/null
@@ -1,47 +0,0 @@
-# Copyright (C) 2019 The Android Open Source Project
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-#      http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-LOCAL_PATH:= $(call my-dir)
-include $(CLEAR_VARS)
-
-LOCAL_MODULE := frameworks-base-overlays
-LOCAL_LICENSE_KINDS := SPDX-license-identifier-Apache-2.0
-LOCAL_LICENSE_CONDITIONS := notice
-LOCAL_NOTICE_FILE := $(LOCAL_PATH)/../../NOTICE
-LOCAL_REQUIRED_MODULES := \
-	DisplayCutoutEmulationCornerOverlay \
-	DisplayCutoutEmulationDoubleOverlay \
-    DisplayCutoutEmulationHoleOverlay \
-	DisplayCutoutEmulationTallOverlay \
-	DisplayCutoutEmulationWaterfallOverlay \
-	FontNotoSerifSourceOverlay \
-	NavigationBarMode3ButtonOverlay \
-	NavigationBarModeGesturalOverlay \
-	NavigationBarModeGesturalOverlayNarrowBack \
-	NavigationBarModeGesturalOverlayWideBack \
-	NavigationBarModeGesturalOverlayExtraWideBack \
-	TransparentNavigationBarOverlay \
-	NotesRoleEnabledOverlay \
-	preinstalled-packages-platform-overlays.xml
-
-include $(BUILD_PHONY_PACKAGE)
-include $(CLEAR_VARS)
-
-LOCAL_MODULE := frameworks-base-overlays-debug
-LOCAL_LICENSE_KINDS := SPDX-license-identifier-Apache-2.0
-LOCAL_LICENSE_CONDITIONS := notice
-LOCAL_NOTICE_FILE := $(LOCAL_PATH)/../../NOTICE
-
-include $(BUILD_PHONY_PACKAGE)
-include $(call first-makefiles-under,$(LOCAL_PATH))
diff --git a/services/Android.bp b/services/Android.bp
index 5cb8ec6..0b484f4 100644
--- a/services/Android.bp
+++ b/services/Android.bp
@@ -148,6 +148,9 @@
 java_library {
     name: "Slogf",
     srcs: ["core/java/com/android/server/utils/Slogf.java"],
+    lint: {
+        baseline_filename: "lint-baseline.xml",
+    },
 }
 
 // merge all required services into one jar
@@ -220,6 +223,9 @@
     required: [
         "libukey2_jni_shared",
     ],
+    lint: {
+        baseline_filename: "lint-baseline.xml",
+    },
 
     // Uncomment to enable output of certain warnings (deprecated, unchecked)
     //javacflags: ["-Xlint"],
diff --git a/services/accessibility/Android.bp b/services/accessibility/Android.bp
index e2488a5..a354671 100644
--- a/services/accessibility/Android.bp
+++ b/services/accessibility/Android.bp
@@ -21,6 +21,7 @@
     ],
     lint: {
         error_checks: ["MissingPermissionAnnotation"],
+        baseline_filename: "lint-baseline.xml",
     },
     srcs: [
         ":services.accessibility-sources",
@@ -49,6 +50,9 @@
     libs: [
         "androidx.annotation_annotation",
     ],
+    lint: {
+        baseline_filename: "lint-baseline.xml",
+    },
 }
 
 aconfig_declarations {
diff --git a/services/accessibility/accessibility.aconfig b/services/accessibility/accessibility.aconfig
index a19920f..993b254 100644
--- a/services/accessibility/accessibility.aconfig
+++ b/services/accessibility/accessibility.aconfig
@@ -59,13 +59,6 @@
 }
 
 flag {
-    name: "reduce_touch_exploration_sensitivity"
-    namespace: "accessibility"
-    description: "Reduces touch exploration sensitivity by only sending a hover event when the ifnger has moved the amount of pixels defined by the system's touch slop."
-    bug: "303677860"
-}
-
-flag {
     name: "scan_packages_without_lock"
     namespace: "accessibility"
     description: "Scans packages for accessibility service/activity info without holding the A11yMS lock"
diff --git a/services/accessibility/java/com/android/server/accessibility/gestures/TouchExplorer.java b/services/accessibility/java/com/android/server/accessibility/gestures/TouchExplorer.java
index fc8d4f8..c418485 100644
--- a/services/accessibility/java/com/android/server/accessibility/gestures/TouchExplorer.java
+++ b/services/accessibility/java/com/android/server/accessibility/gestures/TouchExplorer.java
@@ -882,22 +882,10 @@
         final int pointerIndex = event.findPointerIndex(pointerId);
         switch (event.getPointerCount()) {
             case 1:
-                // Touch exploration.
+            // Touch exploration.
                 sendTouchExplorationGestureStartAndHoverEnterIfNeeded(policyFlags);
-                if (Flags.reduceTouchExplorationSensitivity()
-                        && mState.getLastInjectedHoverEvent() != null) {
-                    final MotionEvent lastEvent = mState.getLastInjectedHoverEvent();
-                    final float deltaX = lastEvent.getX() - rawEvent.getX();
-                    final float deltaY = lastEvent.getY() - rawEvent.getY();
-                    final double moveDelta = Math.hypot(deltaX, deltaY);
-                    if (moveDelta > mTouchSlop) {
-                        mDispatcher.sendMotionEvent(
-                                event, ACTION_HOVER_MOVE, rawEvent, pointerIdBits, policyFlags);
-                    }
-                } else {
-                    mDispatcher.sendMotionEvent(
-                            event, ACTION_HOVER_MOVE, rawEvent, pointerIdBits, policyFlags);
-                }
+                mDispatcher.sendMotionEvent(
+                        event, ACTION_HOVER_MOVE, rawEvent, pointerIdBits, policyFlags);
                 break;
             case 2:
                 if (mGestureDetector.isMultiFingerGesturesEnabled()
diff --git a/services/autofill/java/com/android/server/autofill/RemoteFillService.java b/services/autofill/java/com/android/server/autofill/RemoteFillService.java
index 4688658..6d0915b 100644
--- a/services/autofill/java/com/android/server/autofill/RemoteFillService.java
+++ b/services/autofill/java/com/android/server/autofill/RemoteFillService.java
@@ -38,6 +38,7 @@
 import android.service.autofill.SaveRequest;
 import android.text.format.DateUtils;
 import android.util.Slog;
+import android.view.autofill.IAutoFillManagerClient;
 
 import com.android.internal.infra.AbstractRemoteService;
 import com.android.internal.infra.ServiceConnector;
@@ -56,12 +57,22 @@
     private static final long TIMEOUT_IDLE_BIND_MILLIS = 5 * DateUtils.SECOND_IN_MILLIS;
     private static final long TIMEOUT_REMOTE_REQUEST_MILLIS = 5 * DateUtils.SECOND_IN_MILLIS;
 
+    private static final ComponentName CREDMAN_SERVICE_COMPONENT_NAME =
+            new ComponentName("com.android.credentialmanager",
+                    "com.android.credentialmanager.autofill.CredentialAutofillService");
+
     private final FillServiceCallbacks mCallbacks;
     private final Object mLock = new Object();
     private CompletableFuture<FillResponse> mPendingFillRequest;
     private int mPendingFillRequestId = INVALID_REQUEST_ID;
     private final ComponentName mComponentName;
 
+    private final boolean mIsCredentialAutofillService;
+
+    public boolean isCredentialAutofillService() {
+        return mIsCredentialAutofillService;
+    }
+
     public interface FillServiceCallbacks
             extends AbstractRemoteService.VultureCallback<RemoteFillService> {
         void onFillRequestSuccess(int requestId, @Nullable FillResponse response,
@@ -83,6 +94,7 @@
                 userId, IAutoFillService.Stub::asInterface);
         mCallbacks = callbacks;
         mComponentName = componentName;
+        mIsCredentialAutofillService = mComponentName.equals(CREDMAN_SERVICE_COMPONENT_NAME);
     }
 
     @Override // from ServiceConnector.Impl
@@ -117,6 +129,10 @@
         super.addLast(iAutoFillServiceJob);
     }
 
+    public ComponentName getComponentName() {
+        return mComponentName;
+    }
+
     /**
      * Cancel the currently pending request.
      *
@@ -134,6 +150,78 @@
         }
     }
 
+    public void onFillCredentialRequest(@NonNull FillRequest request,
+            IAutoFillManagerClient autofillCallback) {
+        if (sVerbose) {
+            Slog.v(TAG, "onFillRequest:" + request);
+        }
+        AtomicReference<ICancellationSignal> cancellationSink = new AtomicReference<>();
+        AtomicReference<CompletableFuture<FillResponse>> futureRef = new AtomicReference<>();
+
+        CompletableFuture<FillResponse> connectThenFillRequest = postAsync(remoteService -> {
+            if (sVerbose) {
+                Slog.v(TAG, "calling onFillRequest() for id=" + request.getId());
+            }
+
+            CompletableFuture<FillResponse> fillRequest = new CompletableFuture<>();
+            remoteService.onFillCredentialRequest(request, new IFillCallback.Stub() {
+                @Override
+                public void onCancellable(ICancellationSignal cancellation) {
+                    CompletableFuture<FillResponse> future = futureRef.get();
+                    if (future != null && future.isCancelled()) {
+                        dispatchCancellationSignal(cancellation);
+                    } else {
+                        cancellationSink.set(cancellation);
+                    }
+                }
+
+                @Override
+                public void onSuccess(FillResponse response) {
+                    fillRequest.complete(response);
+                }
+
+                @Override
+                public void onFailure(int requestId, CharSequence message) {
+                    String errorMessage = message == null ? "" : String.valueOf(message);
+                    fillRequest.completeExceptionally(
+                            new RuntimeException(errorMessage));
+                }
+            }, autofillCallback);
+            return fillRequest;
+        }).orTimeout(TIMEOUT_REMOTE_REQUEST_MILLIS, TimeUnit.MILLISECONDS);
+        futureRef.set(connectThenFillRequest);
+
+        synchronized (mLock) {
+            mPendingFillRequest = connectThenFillRequest;
+            mPendingFillRequestId = request.getId();
+        }
+
+        connectThenFillRequest.whenComplete((res, err) -> Handler.getMain().post(() -> {
+            synchronized (mLock) {
+                mPendingFillRequest = null;
+                mPendingFillRequestId = INVALID_REQUEST_ID;
+            }
+            if (mCallbacks == null) {
+                Slog.w(TAG, "Error calling RemoteFillService - service already unbound");
+                return;
+            }
+            if (err == null) {
+                mCallbacks.onFillRequestSuccess(request.getId(), res,
+                        mComponentName.getPackageName(), request.getFlags());
+            } else {
+                Slog.e(TAG, "Error calling on fill request", err);
+                if (err instanceof TimeoutException) {
+                    dispatchCancellationSignal(cancellationSink.get());
+                    mCallbacks.onFillRequestTimeout(request.getId());
+                } else if (err instanceof CancellationException) {
+                    dispatchCancellationSignal(cancellationSink.get());
+                } else {
+                    mCallbacks.onFillRequestFailure(request.getId(), err.getMessage());
+                }
+            }
+        }));
+    }
+
     public void onFillRequest(@NonNull FillRequest request) {
         if (sVerbose) {
             Slog.v(TAG, "onFillRequest:" + request);
diff --git a/services/autofill/java/com/android/server/autofill/SecondaryProviderHandler.java b/services/autofill/java/com/android/server/autofill/SecondaryProviderHandler.java
index 4a6d5c9b..553ba12 100644
--- a/services/autofill/java/com/android/server/autofill/SecondaryProviderHandler.java
+++ b/services/autofill/java/com/android/server/autofill/SecondaryProviderHandler.java
@@ -16,14 +16,19 @@
 
 package com.android.server.autofill;
 
+import static com.android.server.autofill.Session.SESSION_ID_KEY;
+
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.IntentSender;
+import android.os.Bundle;
 import android.service.autofill.FillRequest;
 import android.service.autofill.FillResponse;
 import android.util.Slog;
+import android.view.autofill.IAutoFillManagerClient;
+import android.view.inputmethod.InlineSuggestionsRequest;
 
 /**
  * Requests autofill response from a Remote Autofill Service. This autofill service can be
@@ -95,10 +100,33 @@
     /**
      * Requests a new fill response.
      */
-    public void onFillRequest(FillRequest pendingFillRequest, int flag) {
+    public void onFillRequest(FillRequest pendingFillRequest,
+            InlineSuggestionsRequest pendingInlineSuggestionsRequest, int flag, int id,
+            IAutoFillManagerClient client) {
         Slog.v(TAG, "Requesting fill response to secondary provider.");
         mLastFlag = flag;
-        mRemoteFillService.onFillRequest(pendingFillRequest);
+        if (mRemoteFillService != null && mRemoteFillService.isCredentialAutofillService()) {
+            Slog.v(TAG, "About to call CredAutofill service as secondary provider");
+            addSessionIdToClientState(pendingFillRequest, pendingInlineSuggestionsRequest, id);
+            mRemoteFillService.onFillCredentialRequest(pendingFillRequest, client);
+        } else {
+            mRemoteFillService.onFillRequest(pendingFillRequest);
+        }
+    }
+
+    private FillRequest addSessionIdToClientState(FillRequest pendingFillRequest,
+            InlineSuggestionsRequest pendingInlineSuggestionsRequest, int id) {
+        if (pendingFillRequest.getClientState() == null) {
+            pendingFillRequest = new FillRequest(pendingFillRequest.getId(),
+                    pendingFillRequest.getFillContexts(),
+                    pendingFillRequest.getHints(),
+                    new Bundle(),
+                    pendingFillRequest.getFlags(),
+                    pendingInlineSuggestionsRequest,
+                    pendingFillRequest.getDelayedFillIntentSender());
+        }
+        pendingFillRequest.getClientState().putInt(SESSION_ID_KEY, id);
+        return pendingFillRequest;
     }
 
     public void destroy() {
diff --git a/services/autofill/java/com/android/server/autofill/Session.java b/services/autofill/java/com/android/server/autofill/Session.java
index d71258a..a49f9db 100644
--- a/services/autofill/java/com/android/server/autofill/Session.java
+++ b/services/autofill/java/com/android/server/autofill/Session.java
@@ -234,6 +234,8 @@
             new ComponentName("com.android.credentialmanager",
                     "com.android.credentialmanager.autofill.CredentialAutofillService");
 
+    static final String SESSION_ID_KEY = "session_id";
+
     final Object mLock;
 
     private final AutofillManagerServiceImpl mService;
@@ -383,7 +385,7 @@
      */
     private boolean mHasCallback;
 
-    /** Whether the session has credential provider as the primary provider. */
+    /** Whether the session has credential manager provider as the primary provider. */
     private boolean mIsPrimaryCredential;
 
     @GuardedBy("mLock")
@@ -723,9 +725,16 @@
                     && mSecondaryProviderHandler != null) {
                 Slog.v(TAG, "Requesting fill response to secondary provider.");
                 mSecondaryProviderHandler.onFillRequest(mPendingFillRequest,
-                        mPendingFillRequest.getFlags());
+                        mPendingInlineSuggestionsRequest,
+                        mPendingFillRequest.getFlags(), id, mClient);
             } else if (mRemoteFillService != null) {
-                mRemoteFillService.onFillRequest(mPendingFillRequest);
+                if (mIsPrimaryCredential) {
+                    mPendingFillRequest = addSessionIdToClientState(mPendingFillRequest,
+                            mPendingInlineSuggestionsRequest, id);
+                    mRemoteFillService.onFillCredentialRequest(mPendingFillRequest, mClient);
+                } else {
+                    mRemoteFillService.onFillRequest(mPendingFillRequest);
+                }
             }
             mPendingInlineSuggestionsRequest = null;
             mWaitForInlineRequest = false;
@@ -868,6 +877,21 @@
         }
     }
 
+    private FillRequest addSessionIdToClientState(FillRequest pendingFillRequest,
+            InlineSuggestionsRequest pendingInlineSuggestionsRequest, int id) {
+        if (pendingFillRequest.getClientState() == null) {
+            pendingFillRequest = new FillRequest(pendingFillRequest.getId(),
+                    pendingFillRequest.getFillContexts(),
+                    pendingFillRequest.getHints(),
+                    new Bundle(),
+                    pendingFillRequest.getFlags(),
+                    pendingInlineSuggestionsRequest,
+                    pendingFillRequest.getDelayedFillIntentSender());
+        }
+        pendingFillRequest.getClientState().putInt(SESSION_ID_KEY, id);
+        return pendingFillRequest;
+    }
+
     /**
      * Get the list of valid autofill hint types from Device flags
      * Returns empty list if PCC is off or no types available
diff --git a/services/companion/java/com/android/server/companion/AssociationRequestsProcessor.java b/services/companion/java/com/android/server/companion/AssociationRequestsProcessor.java
index 6964763..a6ed846 100644
--- a/services/companion/java/com/android/server/companion/AssociationRequestsProcessor.java
+++ b/services/companion/java/com/android/server/companion/AssociationRequestsProcessor.java
@@ -37,6 +37,7 @@
 import android.annotation.Nullable;
 import android.annotation.SuppressLint;
 import android.annotation.UserIdInt;
+import android.app.ActivityOptions;
 import android.app.PendingIntent;
 import android.companion.AssociatedDevice;
 import android.companion.AssociationInfo;
@@ -285,32 +286,33 @@
                 selfManaged, /* notifyOnDeviceNearby */ false, /* revoked */ false,
                 timestamp, Long.MAX_VALUE, /* systemDataSyncFlags */ 0);
 
-        if (deviceProfile != null) {
-            // If the "Device Profile" is specified, make the companion application a holder of the
-            // corresponding role.
-            addRoleHolderForAssociation(mService.getContext(), association, success -> {
-                if (success) {
-                    addAssociationToStore(association, deviceProfile);
-
-                    sendCallbackAndFinish(association, callback, resultReceiver);
-                } else {
-                    Slog.e(TAG, "Failed to add u" + userId + "\\" + packageName
-                            + " to the list of " + deviceProfile + " holders.");
-
-                    sendCallbackAndFinish(null, callback, resultReceiver);
-                }
-            });
-        } else {
-            addAssociationToStore(association, null);
-
-            sendCallbackAndFinish(association, callback, resultReceiver);
-        }
+        // Add role holder for association (if specified) and add new association to store.
+        maybeGrantRoleAndStoreAssociation(association, callback, resultReceiver);
 
         // Don't need to update the mRevokedAssociationsPendingRoleHolderRemoval since
         // maybeRemoveRoleHolderForAssociation in PackageInactivityListener will handle the case
         // that there are other devices with the same profile, so the role holder won't be removed.
     }
 
+    public void maybeGrantRoleAndStoreAssociation(@NonNull AssociationInfo association,
+            @Nullable IAssociationRequestCallback callback,
+            @Nullable ResultReceiver resultReceiver) {
+        // If the "Device Profile" is specified, make the companion application a holder of the
+        // corresponding role.
+        // If it is null, then the operation will succeed without granting any role.
+        addRoleHolderForAssociation(mService.getContext(), association, success -> {
+            if (success) {
+                addAssociationToStore(association);
+                sendCallbackAndFinish(association, callback, resultReceiver);
+            } else {
+                Slog.e(TAG, "Failed to add u" + association.getUserId()
+                        + "\\" + association.getPackageName()
+                        + " to the list of " + association.getDeviceProfile() + " holders.");
+                sendCallbackAndFinish(null, callback, resultReceiver);
+            }
+        });
+    }
+
     public void enableSystemDataSync(int associationId, int flags) {
         AssociationInfo association = mAssociationStore.getAssociationById(associationId);
         AssociationInfo updated = (new AssociationInfo.Builder(association))
@@ -325,15 +327,14 @@
         mAssociationStore.updateAssociation(updated);
     }
 
-    private void addAssociationToStore(@NonNull AssociationInfo association,
-            @Nullable String deviceProfile) {
+    private void addAssociationToStore(@NonNull AssociationInfo association) {
         Slog.i(TAG, "New CDM association created=" + association);
 
         mAssociationStore.addAssociation(association);
 
         mService.updateSpecialAccessPermissionForAssociatedPackage(association);
 
-        logCreateAssociation(deviceProfile);
+        logCreateAssociation(association.getDeviceProfile());
     }
 
     private void sendCallbackAndFinish(@Nullable AssociationInfo association,
@@ -398,7 +399,11 @@
             pendingIntent = PendingIntent.getActivityAsUser(
                     mContext, /*requestCode */ packageUid, intent,
                     FLAG_ONE_SHOT | FLAG_CANCEL_CURRENT | FLAG_IMMUTABLE,
-                    /* options= */ null, UserHandle.CURRENT);
+                    ActivityOptions.makeBasic()
+                            .setPendingIntentCreatorBackgroundActivityStartMode(
+                                    ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED)
+                            .toBundle(),
+                    UserHandle.CURRENT);
         } finally {
             Binder.restoreCallingIdentity(token);
         }
diff --git a/services/companion/java/com/android/server/companion/RolesUtils.java b/services/companion/java/com/android/server/companion/RolesUtils.java
index 163f614..af9d2d7 100644
--- a/services/companion/java/com/android/server/companion/RolesUtils.java
+++ b/services/companion/java/com/android/server/companion/RolesUtils.java
@@ -47,6 +47,17 @@
         return roleHolders.contains(packageName);
     }
 
+    /**
+     * Attempt to add the association's companion app as the role holder for the device profile
+     * specified in the association. If the association does not have any device profile specified,
+     * then the operation will always be successful as a no-op.
+     *
+     * @param context
+     * @param associationInfo the association for which the role should be granted to the app
+     * @param roleGrantResult the result callback for adding role holder. True if successful, and
+     *                        false if failed. If the association does not have any device profile
+     *                        specified, then the operation will always be successful as a no-op.
+     */
     static void addRoleHolderForAssociation(
             @NonNull Context context, @NonNull AssociationInfo associationInfo,
             @NonNull Consumer<Boolean> roleGrantResult) {
@@ -55,7 +66,11 @@
         }
 
         final String deviceProfile = associationInfo.getDeviceProfile();
-        if (deviceProfile == null) return;
+        if (deviceProfile == null) {
+            // If no device profile is specified, then no-op and resolve callback with success.
+            roleGrantResult.accept(true);
+            return;
+        }
 
         final RoleManager roleManager = context.getSystemService(RoleManager.class);
 
diff --git a/services/companion/java/com/android/server/companion/datatransfer/SystemDataTransferProcessor.java b/services/companion/java/com/android/server/companion/datatransfer/SystemDataTransferProcessor.java
index bd646fa..4e471f5 100644
--- a/services/companion/java/com/android/server/companion/datatransfer/SystemDataTransferProcessor.java
+++ b/services/companion/java/com/android/server/companion/datatransfer/SystemDataTransferProcessor.java
@@ -27,6 +27,7 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.UserIdInt;
+import android.app.ActivityOptions;
 import android.app.PendingIntent;
 import android.companion.AssociationInfo;
 import android.companion.DeviceNotAssociatedException;
@@ -186,7 +187,11 @@
         final long token = Binder.clearCallingIdentity();
         try {
             return PendingIntent.getActivityAsUser(mContext, /*requestCode */ associationId, intent,
-                    FLAG_ONE_SHOT | FLAG_CANCEL_CURRENT | FLAG_IMMUTABLE, /* options= */ null,
+                    FLAG_ONE_SHOT | FLAG_CANCEL_CURRENT | FLAG_IMMUTABLE,
+                    ActivityOptions.makeBasic()
+                            .setPendingIntentCreatorBackgroundActivityStartMode(
+                                    ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED)
+                            .toBundle(),
                     UserHandle.CURRENT);
         } finally {
             Binder.restoreCallingIdentity(token);
diff --git a/services/core/java/com/android/server/am/ActivityManagerShellCommand.java b/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
index 848a2b0..57c52c2 100644
--- a/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
+++ b/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
@@ -402,7 +402,7 @@
                 case "get-bg-restriction-level":
                     return runGetBgRestrictionLevel(pw);
                 case "observe-foreground-process":
-                    return runGetCurrentForegroundProcess(pw, mInternal, mTaskInterface);
+                    return runGetCurrentForegroundProcess(pw, mInternal);
                 case "reset-dropbox-rate-limiter":
                     return runResetDropboxRateLimiter();
                 case "list-displays-for-starting-users":
@@ -3690,11 +3690,10 @@
         return -1;
     }
 
-    private int runGetCurrentForegroundProcess(PrintWriter pw,
-            IActivityManager iam, IActivityTaskManager iatm)
+    private int runGetCurrentForegroundProcess(PrintWriter pw, IActivityManager iam)
             throws RemoteException {
 
-        ProcessObserver observer = new ProcessObserver(pw, iam, iatm, mInternal);
+        ProcessObserver observer = new ProcessObserver(pw, iam);
         iam.registerProcessObserver(observer);
 
         final InputStream mInput = getRawInputStream();
@@ -3729,15 +3728,10 @@
 
         private PrintWriter mPw;
         private IActivityManager mIam;
-        private IActivityTaskManager mIatm;
-        private ActivityManagerService mInternal;
 
-        ProcessObserver(PrintWriter mPw, IActivityManager mIam,
-                IActivityTaskManager mIatm, ActivityManagerService ams) {
+        ProcessObserver(PrintWriter mPw, IActivityManager mIam) {
             this.mPw = mPw;
             this.mIam = mIam;
-            this.mIatm = mIatm;
-            this.mInternal = ams;
         }
 
         @Override
diff --git a/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java b/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java
index d0d647c..b7feca5 100644
--- a/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java
+++ b/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java
@@ -163,6 +163,7 @@
         "pixel_audio_android",
         "pixel_bluetooth",
         "pixel_system_sw_touch",
+        "pixel_system_sw_video",
         "pixel_watch",
         "platform_security",
         "power",
@@ -181,6 +182,7 @@
         "tv_system_ui",
         "usb",
         "vibrator",
+        "virtualization",
         "virtual_devices",
         "wallet_integration",
         "wear_calling_messaging",
diff --git a/services/core/java/com/android/server/am/TEST_MAPPING b/services/core/java/com/android/server/am/TEST_MAPPING
index 575db01..e90910a 100644
--- a/services/core/java/com/android/server/am/TEST_MAPPING
+++ b/services/core/java/com/android/server/am/TEST_MAPPING
@@ -146,6 +146,15 @@
         { "include-filter": "android.app.cts.ServiceTest" },
         { "include-filter": "android.app.cts.ActivityManagerFgsBgStartTest" }
       ]
+    },
+    {
+      "name": "CtsStatsdAtomHostTestCases",
+      "options": [
+        { "include-filter": "android.cts.statsdatom.appexit.AppExitHostTest" },
+        { "exclude-annotation": "androidx.test.filters.LargeTest" },
+        { "exclude-annotation": "androidx.test.filters.FlakyTest" },
+        { "exclude-annotation": "org.junit.Ignore" }
+      ]
     }
   ]
 }
diff --git a/services/core/java/com/android/server/appop/AppOpsService.java b/services/core/java/com/android/server/appop/AppOpsService.java
index d80638a..203ac2c 100644
--- a/services/core/java/com/android/server/appop/AppOpsService.java
+++ b/services/core/java/com/android/server/appop/AppOpsService.java
@@ -33,6 +33,7 @@
 import static android.app.AppOpsManager.MODE_ERRORED;
 import static android.app.AppOpsManager.MODE_FOREGROUND;
 import static android.app.AppOpsManager.MODE_IGNORED;
+import static android.app.AppOpsManager.OP_BLUETOOTH_CONNECT;
 import static android.app.AppOpsManager.OP_CAMERA;
 import static android.app.AppOpsManager.OP_CAMERA_SANDBOXED;
 import static android.app.AppOpsManager.OP_FLAGS_ALL;
@@ -2849,6 +2850,11 @@
         verifyIncomingUid(uid);
         verifyIncomingOp(code);
         if (!isIncomingPackageValid(packageName, UserHandle.getUserId(uid))) {
+            // TODO(b/302609140): Remove extra logging after this issue is diagnosed.
+            if (code == OP_BLUETOOTH_CONNECT) {
+                Slog.e(TAG, "noting OP_BLUETOOTH_CONNECT returned MODE_ERRORED as incoming "
+                        + "package: " + packageName + " and uid: " + uid + " is invalid");
+            }
             return new SyncNotedAppOp(AppOpsManager.MODE_ERRORED, code, attributionTag,
                     packageName);
         }
@@ -2877,6 +2883,13 @@
             }
         } catch (SecurityException e) {
             logVerifyAndGetBypassFailure(uid, e, "noteOperation");
+            // TODO(b/302609140): Remove extra logging after this issue is diagnosed.
+            if (code == OP_BLUETOOTH_CONNECT) {
+                Slog.e(TAG, "noting OP_BLUETOOTH_CONNECT returned MODE_ERRORED as"
+                        + " verifyAndGetBypass returned a SecurityException for package: "
+                        + packageName + " and uid: " + uid + " and attributionTag: "
+                        + attributionTag, e);
+            }
             return new SyncNotedAppOp(AppOpsManager.MODE_ERRORED, code, attributionTag,
                     packageName);
         }
@@ -2890,6 +2903,11 @@
                 if (DEBUG) Slog.d(TAG, "noteOperation: no op for code " + code + " uid " + uid
                         + " package " + packageName + "flags: " +
                         AppOpsManager.flagsToString(flags));
+                // TODO(b/302609140): Remove extra logging after this issue is diagnosed.
+                if (code == OP_BLUETOOTH_CONNECT) {
+                    Slog.e(TAG, "noting OP_BLUETOOTH_CONNECT returned MODE_ERRORED as"
+                            + " #getOpsLocked returned null");
+                }
                 return new SyncNotedAppOp(AppOpsManager.MODE_ERRORED, code, attributionTag,
                         packageName);
             }
@@ -2930,6 +2948,11 @@
                     attributedOp.rejected(uidState.getState(), flags);
                     scheduleOpNotedIfNeededLocked(code, uid, packageName, attributionTag, flags,
                             uidMode);
+                    // TODO(b/302609140): Remove extra logging after this issue is diagnosed.
+                    if (code == OP_BLUETOOTH_CONNECT && uidMode == MODE_ERRORED) {
+                        Slog.e(TAG, "noting OP_BLUETOOTH_CONNECT returned MODE_ERRORED as"
+                                + " uid mode is MODE_ERRORED");
+                    }
                     return new SyncNotedAppOp(uidMode, code, attributionTag, packageName);
                 }
             } else {
@@ -2949,6 +2972,11 @@
                     attributedOp.rejected(uidState.getState(), flags);
                     scheduleOpNotedIfNeededLocked(code, uid, packageName, attributionTag, flags,
                             mode);
+                    // TODO(b/302609140): Remove extra logging after this issue is diagnosed.
+                    if (code == OP_BLUETOOTH_CONNECT && mode == MODE_ERRORED) {
+                        Slog.e(TAG, "noting OP_BLUETOOTH_CONNECT returned MODE_ERRORED as"
+                                + " package mode is MODE_ERRORED");
+                    }
                     return new SyncNotedAppOp(mode, code, attributionTag, packageName);
                 }
             }
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/HidlToAidlSensorAdapter.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/HidlToAidlSensorAdapter.java
index 0bb61415..90da74c 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/HidlToAidlSensorAdapter.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/HidlToAidlSensorAdapter.java
@@ -147,7 +147,7 @@
                 gestureAvailabilityDispatcher, () -> mCurrentUserId, getUserSwitchCallback()));
         mLockoutTracker = new LockoutFrameworkImpl(getContext(),
                 userId -> mLockoutResetDispatcher.notifyLockoutResetCallbacks(
-                        getSensorProperties().sensorId));
+                        getSensorProperties().sensorId), getHandler());
     }
 
     @Override
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/LockoutFrameworkImpl.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/LockoutFrameworkImpl.java
index 2f77275..0e05a79 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/LockoutFrameworkImpl.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/LockoutFrameworkImpl.java
@@ -19,6 +19,7 @@
 import static android.Manifest.permission.RESET_FINGERPRINT_LOCKOUT;
 
 import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.app.AlarmManager;
 import android.app.PendingIntent;
 import android.content.BroadcastReceiver;
@@ -81,19 +82,30 @@
             @NonNull LockoutResetCallback lockoutResetCallback) {
         this(context, lockoutResetCallback, (userId) -> PendingIntent.getBroadcast(context, userId,
                 new Intent(ACTION_LOCKOUT_RESET).putExtra(KEY_LOCKOUT_RESET_USER, userId),
-                PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE));
+                PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE),
+                null /* handler */);
+    }
+
+    public LockoutFrameworkImpl(@NonNull Context context,
+            @NonNull LockoutResetCallback lockoutResetCallback,
+            @NonNull Handler handler) {
+        this(context, lockoutResetCallback, (userId) -> PendingIntent.getBroadcast(context, userId,
+                new Intent(ACTION_LOCKOUT_RESET).putExtra(KEY_LOCKOUT_RESET_USER, userId),
+                PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE),
+                handler);
     }
 
     @VisibleForTesting
     LockoutFrameworkImpl(@NonNull Context context,
             @NonNull LockoutResetCallback lockoutResetCallback,
-            @NonNull Function<Integer, PendingIntent> lockoutResetIntent) {
+            @NonNull Function<Integer, PendingIntent> lockoutResetIntent,
+            @Nullable Handler handler) {
         mLockoutResetCallback = lockoutResetCallback;
         mTimedLockoutCleared = new SparseBooleanArray();
         mFailedAttempts = new SparseIntArray();
         mAlarmManager = context.getSystemService(AlarmManager.class);
         mLockoutReceiver = new LockoutReceiver();
-        mHandler = new Handler(Looper.getMainLooper());
+        mHandler = handler == null ? new Handler(Looper.getMainLooper()) : handler;
         mLockoutResetIntent = lockoutResetIntent;
 
         context.registerReceiver(mLockoutReceiver, new IntentFilter(ACTION_LOCKOUT_RESET),
diff --git a/services/core/java/com/android/server/clipboard/ClipboardService.java b/services/core/java/com/android/server/clipboard/ClipboardService.java
index 56a94ec0..49f6070 100644
--- a/services/core/java/com/android/server/clipboard/ClipboardService.java
+++ b/services/core/java/com/android/server/clipboard/ClipboardService.java
@@ -1424,7 +1424,11 @@
         String defaultIme = Settings.Secure.getStringForUser(getContext().getContentResolver(),
                 Settings.Secure.DEFAULT_INPUT_METHOD, userId);
         if (!TextUtils.isEmpty(defaultIme)) {
-            final String imePkg = ComponentName.unflattenFromString(defaultIme).getPackageName();
+            final ComponentName imeComponent = ComponentName.unflattenFromString(defaultIme);
+            if (imeComponent == null) {
+                return false;
+            }
+            final String imePkg = imeComponent.getPackageName();
             return imePkg.equals(packageName);
         }
         return false;
diff --git a/services/core/java/com/android/server/display/config/DisplayBrightnessMappingConfig.java b/services/core/java/com/android/server/display/config/DisplayBrightnessMappingConfig.java
index 6978686..544f490 100644
--- a/services/core/java/com/android/server/display/config/DisplayBrightnessMappingConfig.java
+++ b/services/core/java/com/android/server/display/config/DisplayBrightnessMappingConfig.java
@@ -118,8 +118,13 @@
                             "The first lux value in the display brightness mapping must be 0");
                 }
 
-                String key = (mapping.getMode() == null ? "default" : mapping.getMode()) + "_"
-                        + (mapping.getSetting() == null ? "normal" : mapping.getSetting());
+                String key = (mapping.getMode() == null
+                        ? AutoBrightnessModeName._default.getRawName()
+                        : mapping.getMode().getRawName())
+                        + "_"
+                        + (mapping.getSetting() == null
+                        ? AutoBrightnessSettingName.normal.getRawName()
+                        : mapping.getSetting().getRawName());
                 if (mBrightnessLevelsMap.containsKey(key)
                         || mBrightnessLevelsLuxMap.containsKey(key)) {
                     throw new IllegalArgumentException(
diff --git a/services/core/java/com/android/server/input/InputManagerService.java b/services/core/java/com/android/server/input/InputManagerService.java
index 087c525..36dac83 100644
--- a/services/core/java/com/android/server/input/InputManagerService.java
+++ b/services/core/java/com/android/server/input/InputManagerService.java
@@ -48,6 +48,7 @@
 import android.hardware.input.IInputManager;
 import android.hardware.input.IInputSensorEventListener;
 import android.hardware.input.IKeyboardBacklightListener;
+import android.hardware.input.IStickyModifierStateListener;
 import android.hardware.input.ITabletModeChangedListener;
 import android.hardware.input.InputDeviceIdentifier;
 import android.hardware.input.InputManager;
@@ -319,6 +320,9 @@
     // Manages Keyboard backlight
     private final KeyboardBacklightControllerInterface mKeyboardBacklightController;
 
+    // Manages Sticky modifier state
+    private final StickyModifierStateController mStickyModifierStateController;
+
     // Manages Keyboard modifier keys remapping
     private final KeyRemapper mKeyRemapper;
 
@@ -464,6 +468,7 @@
                 ? new KeyboardBacklightController(mContext, mNative, mDataStore,
                         injector.getLooper(), injector.getUEventManager())
                 : new KeyboardBacklightControllerInterface() {};
+        mStickyModifierStateController = new StickyModifierStateController();
         mKeyRemapper = new KeyRemapper(mContext, mNative, mDataStore, injector.getLooper());
 
         mUseDevInputEventForAudioJack =
@@ -2827,6 +2832,33 @@
                         yPosition)).sendToTarget();
     }
 
+    @Override
+    @EnforcePermission(Manifest.permission.MONITOR_STICKY_MODIFIER_STATE)
+    public void registerStickyModifierStateListener(
+            @NonNull IStickyModifierStateListener listener) {
+        super.registerStickyModifierStateListener_enforcePermission();
+        Objects.requireNonNull(listener);
+        mStickyModifierStateController.registerStickyModifierStateListener(listener,
+                Binder.getCallingPid());
+    }
+
+    @Override
+    @EnforcePermission(Manifest.permission.MONITOR_STICKY_MODIFIER_STATE)
+    public void unregisterStickyModifierStateListener(
+            @NonNull IStickyModifierStateListener listener) {
+        super.unregisterStickyModifierStateListener_enforcePermission();
+        Objects.requireNonNull(listener);
+        mStickyModifierStateController.unregisterStickyModifierStateListener(listener,
+                Binder.getCallingPid());
+    }
+
+    // Native callback
+    @SuppressWarnings("unused")
+    void notifyStickyModifierStateChanged(int modifierState, int lockedModifierState) {
+        mStickyModifierStateController.notifyStickyModifierStateChanged(modifierState,
+                lockedModifierState);
+    }
+
     // Native callback.
     @SuppressWarnings("unused")
     boolean isInputMethodConnectionActive() {
diff --git a/services/core/java/com/android/server/input/StickyModifierStateController.java b/services/core/java/com/android/server/input/StickyModifierStateController.java
new file mode 100644
index 0000000..5a22c10
--- /dev/null
+++ b/services/core/java/com/android/server/input/StickyModifierStateController.java
@@ -0,0 +1,133 @@
+/*
+ * 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.IStickyModifierStateListener;
+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 the sticky
+ * modifier state for A11y Sticky keys feature.
+ */
+final class StickyModifierStateController {
+
+    private static final String TAG = "ModifierStateController";
+
+    // To enable these logs, run:
+    // 'adb shell setprop log.tag.ModifierStateController DEBUG' (requires restart)
+    private static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
+
+    // List of currently registered sticky modifier state listeners
+    @GuardedBy("mStickyModifierStateListenerRecords")
+    private final SparseArray<StickyModifierStateListenerRecord>
+            mStickyModifierStateListenerRecords = new SparseArray<>();
+
+    public void notifyStickyModifierStateChanged(int modifierState, int lockedModifierState) {
+        if (DEBUG) {
+            Slog.d(TAG, "Sticky modifier state changed, modifierState = " + modifierState
+                    + ", lockedModifierState = " + lockedModifierState);
+        }
+
+        synchronized (mStickyModifierStateListenerRecords) {
+            for (int i = 0; i < mStickyModifierStateListenerRecords.size(); i++) {
+                mStickyModifierStateListenerRecords.valueAt(i).notifyStickyModifierStateChanged(
+                        modifierState, lockedModifierState);
+            }
+        }
+    }
+
+    /** Register the sticky modifier state listener for a process. */
+    @BinderThread
+    public void registerStickyModifierStateListener(IStickyModifierStateListener listener,
+            int pid) {
+        synchronized (mStickyModifierStateListenerRecords) {
+            if (mStickyModifierStateListenerRecords.get(pid) != null) {
+                throw new IllegalStateException("The calling process has already registered "
+                        + "a StickyModifierStateListener.");
+            }
+            StickyModifierStateListenerRecord record = new StickyModifierStateListenerRecord(pid,
+                    listener);
+            try {
+                listener.asBinder().linkToDeath(record, 0);
+            } catch (RemoteException ex) {
+                throw new RuntimeException(ex);
+            }
+            mStickyModifierStateListenerRecords.put(pid, record);
+        }
+    }
+
+    /** Unregister the sticky modifier state listener for a process. */
+    @BinderThread
+    public void unregisterStickyModifierStateListener(IStickyModifierStateListener listener,
+            int pid) {
+        synchronized (mStickyModifierStateListenerRecords) {
+            StickyModifierStateListenerRecord record = mStickyModifierStateListenerRecords.get(pid);
+            if (record == null) {
+                throw new IllegalStateException("The calling process has no registered "
+                        + "StickyModifierStateListener.");
+            }
+            if (record.mListener.asBinder() != listener.asBinder()) {
+                throw new IllegalStateException("The calling process has a different registered "
+                        + "StickyModifierStateListener.");
+            }
+            record.mListener.asBinder().unlinkToDeath(record, 0);
+            mStickyModifierStateListenerRecords.remove(pid);
+        }
+    }
+
+    private void onStickyModifierStateListenerDied(int pid) {
+        synchronized (mStickyModifierStateListenerRecords) {
+            mStickyModifierStateListenerRecords.remove(pid);
+        }
+    }
+
+    // A record of a registered sticky modifier state listener from one process.
+    private class StickyModifierStateListenerRecord implements IBinder.DeathRecipient {
+        public final int mPid;
+        public final IStickyModifierStateListener mListener;
+
+        StickyModifierStateListenerRecord(int pid, IStickyModifierStateListener listener) {
+            mPid = pid;
+            mListener = listener;
+        }
+
+        @Override
+        public void binderDied() {
+            if (DEBUG) {
+                Slog.d(TAG, "Sticky modifier state listener for pid " + mPid + " died.");
+            }
+            onStickyModifierStateListenerDied(mPid);
+        }
+
+        public void notifyStickyModifierStateChanged(int modifierState, int lockedModifierState) {
+            try {
+                mListener.onStickyModifierStateChanged(modifierState, lockedModifierState);
+            } catch (RemoteException ex) {
+                Slog.w(TAG, "Failed to notify process " + mPid
+                        + " that sticky modifier state changed, assuming it died.", ex);
+                binderDied();
+            }
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/inputmethod/HandwritingModeController.java b/services/core/java/com/android/server/inputmethod/HandwritingModeController.java
index 66807ae..f96bb8fb 100644
--- a/services/core/java/com/android/server/inputmethod/HandwritingModeController.java
+++ b/services/core/java/com/android/server/inputmethod/HandwritingModeController.java
@@ -52,6 +52,7 @@
 import java.util.List;
 import java.util.Objects;
 import java.util.OptionalInt;
+import java.util.function.IntConsumer;
 
 // TODO(b/210039666): See if we can make this class thread-safe.
 final class HandwritingModeController {
@@ -84,14 +85,14 @@
     private boolean mDelegatorFromDefaultHomePackage;
     private Runnable mDelegationIdleTimeoutRunnable;
     private Handler mDelegationIdleTimeoutHandler;
-
+    private IntConsumer mPointerToolTypeConsumer;
     private HandwritingEventReceiverSurface mHandwritingSurface;
 
     private int mCurrentRequestId;
 
     @AnyThread
     HandwritingModeController(Context context, Looper uiThreadLooper,
-            Runnable inkWindowInitRunnable) {
+            Runnable inkWindowInitRunnable, IntConsumer toolTypeConsumer) {
         mContext = context;
         mLooper = uiThreadLooper;
         mCurrentDisplayId = Display.INVALID_DISPLAY;
@@ -100,6 +101,7 @@
         mPackageManagerInternal = LocalServices.getService(PackageManagerInternal.class);
         mCurrentRequestId = 0;
         mInkWindowInitRunnable = inkWindowInitRunnable;
+        mPointerToolTypeConsumer = toolTypeConsumer;
     }
 
     /**
@@ -355,6 +357,11 @@
             return false;
         }
         final MotionEvent event = (MotionEvent) ev;
+        if (mPointerToolTypeConsumer != null && event.getAction() == MotionEvent.ACTION_DOWN) {
+            int toolType = event.getToolType(event.getActionIndex());
+            // notify IME of change in tool type.
+            mPointerToolTypeConsumer.accept(toolType);
+        }
         if (!event.isStylusPointer()) {
             return false;
         }
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
index c4d94ee..24bcb4e 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
@@ -124,6 +124,7 @@
 import android.view.WindowManager.LayoutParams;
 import android.view.WindowManager.LayoutParams.SoftInputModeFlags;
 import android.view.inputmethod.EditorInfo;
+import android.view.inputmethod.Flags;
 import android.view.inputmethod.ImeTracker;
 import android.view.inputmethod.InputBinding;
 import android.view.inputmethod.InputConnection;
@@ -206,6 +207,7 @@
 import java.util.concurrent.CopyOnWriteArrayList;
 import java.util.concurrent.Future;
 import java.util.concurrent.atomic.AtomicInteger;
+import java.util.function.IntConsumer;
 
 /**
  * This class provides a system service that manages input methods.
@@ -276,7 +278,7 @@
     final Context mContext;
     final Resources mRes;
     private final Handler mHandler;
-    final InputMethodSettings mSettings;
+    private final InputMethodSettings mSettings;
     final SettingsObserver mSettingsObserver;
     private final SparseBooleanArray mLoggedDeniedGetInputMethodWindowVisibleHeightForUid =
             new SparseBooleanArray(0);
@@ -316,7 +318,7 @@
     // Mapping from deviceId to the device-specific imeId for that device.
     private final SparseArray<String> mVirtualDeviceMethodMap = new SparseArray<>();
 
-    final InputMethodSubtypeSwitchingController mSwitchingController;
+    private final InputMethodSubtypeSwitchingController mSwitchingController;
     final HardwareKeyboardShortcutController mHardwareKeyboardShortcutController =
             new HardwareKeyboardShortcutController();
 
@@ -1713,8 +1715,11 @@
                 com.android.internal.R.bool.config_preventImeStartupUnlessTextEditor);
         mNonPreemptibleInputMethods = mRes.getStringArray(
                 com.android.internal.R.array.config_nonPreemptibleInputMethods);
+        IntConsumer toolTypeConsumer =
+                Flags.useHandwritingListenerForTooltype()
+                        ? toolType -> onUpdateEditorToolType(toolType) : null;
         mHwController = new HandwritingModeController(mContext, thread.getLooper(),
-                new InkWindowInitializer());
+                new InkWindowInitializer(), toolTypeConsumer);
         registerDeviceListenerAndCheckStylusSupport();
     }
 
@@ -1735,6 +1740,15 @@
         }
     }
 
+    private void onUpdateEditorToolType(int toolType) {
+        synchronized (ImfLock.class) {
+            IInputMethodInvoker curMethod = getCurMethodLocked();
+            if (curMethod != null) {
+                curMethod.updateEditorToolType(toolType);
+            }
+        }
+    }
+
     @GuardedBy("ImfLock.class")
     private void resetDefaultImeLocked(Context context) {
         // Do not reset the default (current) IME when it is a 3rd-party IME
@@ -3525,7 +3539,8 @@
             ImeTracker.forLogging().onProgress(statsToken, ImeTracker.PHASE_SERVER_HAS_IME);
             mCurStatsToken = null;
 
-            if (lastClickToolType != MotionEvent.TOOL_TYPE_UNKNOWN) {
+            if (!Flags.useHandwritingListenerForTooltype()
+                    && lastClickToolType != MotionEvent.TOOL_TYPE_UNKNOWN) {
                 curMethod.updateEditorToolType(lastClickToolType);
             }
             mVisibilityApplier.performShowIme(windowToken, statsToken,
@@ -4812,7 +4827,20 @@
                         Slog.e(TAG, "Unknown subtype picker mode = " + msg.arg1);
                         return false;
                 }
-                mMenuController.showInputMethodMenu(showAuxSubtypes, displayId);
+                synchronized (ImfLock.class) {
+                    final boolean isScreenLocked = mWindowManagerInternal.isKeyguardLocked()
+                            && mWindowManagerInternal.isKeyguardSecure(
+                                    mSettings.getCurrentUserId());
+                    final String lastInputMethodId = mSettings.getSelectedInputMethod();
+                    int lastInputMethodSubtypeId =
+                            mSettings.getSelectedInputMethodSubtypeId(lastInputMethodId);
+
+                    final List<ImeSubtypeListItem> imList = mSwitchingController
+                            .getSortedInputMethodAndSubtypeListForImeMenuLocked(
+                                    showAuxSubtypes, isScreenLocked);
+                    mMenuController.showInputMethodMenuLocked(showAuxSubtypes, displayId,
+                            lastInputMethodId, lastInputMethodSubtypeId, imList);
+                }
                 return true;
 
             // ---------------------------------------------------------
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodMenuController.java b/services/core/java/com/android/server/inputmethod/InputMethodMenuController.java
index efa1e0d..6ed4848 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodMenuController.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodMenuController.java
@@ -19,12 +19,14 @@
 import static com.android.server.inputmethod.InputMethodManagerService.DEBUG;
 import static com.android.server.inputmethod.InputMethodUtils.NOT_A_SUBTYPE_ID;
 
+import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.app.AlertDialog;
 import android.content.Context;
 import android.content.DialogInterface;
 import android.content.res.TypedArray;
 import android.graphics.drawable.Drawable;
+import android.provider.Settings;
 import android.text.TextUtils;
 import android.util.Slog;
 import android.view.LayoutInflater;
@@ -51,8 +53,6 @@
     private static final String TAG = InputMethodMenuController.class.getSimpleName();
 
     private final InputMethodManagerService mService;
-    private final InputMethodUtils.InputMethodSettings mSettings;
-    private final InputMethodSubtypeSwitchingController mSwitchingController;
     private final WindowManagerInternal mWindowManagerInternal;
 
     private AlertDialog.Builder mDialogBuilder;
@@ -69,145 +69,141 @@
 
     InputMethodMenuController(InputMethodManagerService service) {
         mService = service;
-        mSettings = mService.mSettings;
-        mSwitchingController = mService.mSwitchingController;
         mWindowManagerInternal = LocalServices.getService(WindowManagerInternal.class);
     }
 
-    void showInputMethodMenu(boolean showAuxSubtypes, int displayId) {
+    @GuardedBy("ImfLock.class")
+    void showInputMethodMenuLocked(boolean showAuxSubtypes, int displayId,
+            String preferredInputMethodId, int preferredInputMethodSubtypeId,
+            @NonNull List<ImeSubtypeListItem> imList) {
         if (DEBUG) Slog.v(TAG, "Show switching menu. showAuxSubtypes=" + showAuxSubtypes);
 
-        synchronized (ImfLock.class) {
-            final boolean isScreenLocked = mWindowManagerInternal.isKeyguardLocked()
-                    && mWindowManagerInternal.isKeyguardSecure(
-                            mService.getCurrentImeUserIdLocked());
-            final String lastInputMethodId = mSettings.getSelectedInputMethod();
-            int lastInputMethodSubtypeId =
-                    mSettings.getSelectedInputMethodSubtypeId(lastInputMethodId);
-            if (DEBUG) Slog.v(TAG, "Current IME: " + lastInputMethodId);
+        final int userId = mService.getCurrentImeUserIdLocked();
 
-            final List<ImeSubtypeListItem> imList = mSwitchingController
-                    .getSortedInputMethodAndSubtypeListForImeMenuLocked(
-                            showAuxSubtypes, isScreenLocked);
-            if (imList.isEmpty()) {
-                return;
-            }
-
-            hideInputMethodMenuLocked();
-
-            if (lastInputMethodSubtypeId == NOT_A_SUBTYPE_ID) {
-                final InputMethodSubtype currentSubtype =
-                        mService.getCurrentInputMethodSubtypeLocked();
-                if (currentSubtype != null) {
-                    final String curMethodId = mService.getSelectedMethodIdLocked();
-                    final InputMethodInfo currentImi =
-                            mService.queryInputMethodForCurrentUserLocked(curMethodId);
-                    lastInputMethodSubtypeId = SubtypeUtils.getSubtypeIdFromHashCode(
-                            currentImi, currentSubtype.hashCode());
-                }
-            }
-
-            final int size = imList.size();
-            mIms = new InputMethodInfo[size];
-            mSubtypeIds = new int[size];
-            int checkedItem = 0;
-            for (int i = 0; i < size; ++i) {
-                final ImeSubtypeListItem item = imList.get(i);
-                mIms[i] = item.mImi;
-                mSubtypeIds[i] = item.mSubtypeId;
-                if (mIms[i].getId().equals(lastInputMethodId)) {
-                    int subtypeId = mSubtypeIds[i];
-                    if ((subtypeId == NOT_A_SUBTYPE_ID)
-                            || (lastInputMethodSubtypeId == NOT_A_SUBTYPE_ID && subtypeId == 0)
-                            || (subtypeId == lastInputMethodSubtypeId)) {
-                        checkedItem = i;
-                    }
-                }
-            }
-
-            if (mDialogWindowContext == null) {
-                mDialogWindowContext = new InputMethodDialogWindowContext();
-            }
-            final Context dialogWindowContext = mDialogWindowContext.get(displayId);
-            mDialogBuilder = new AlertDialog.Builder(dialogWindowContext);
-            mDialogBuilder.setOnCancelListener(dialog -> hideInputMethodMenu());
-
-            final Context dialogContext = mDialogBuilder.getContext();
-            final TypedArray a = dialogContext.obtainStyledAttributes(null,
-                    com.android.internal.R.styleable.DialogPreference,
-                    com.android.internal.R.attr.alertDialogStyle, 0);
-            final Drawable dialogIcon = a.getDrawable(
-                    com.android.internal.R.styleable.DialogPreference_dialogIcon);
-            a.recycle();
-
-            mDialogBuilder.setIcon(dialogIcon);
-
-            final LayoutInflater inflater = dialogContext.getSystemService(LayoutInflater.class);
-            final View tv = inflater.inflate(
-                    com.android.internal.R.layout.input_method_switch_dialog_title, null);
-            mDialogBuilder.setCustomTitle(tv);
-
-            // Setup layout for a toggle switch of the hardware keyboard
-            mSwitchingDialogTitleView = tv;
-            mSwitchingDialogTitleView
-                    .findViewById(com.android.internal.R.id.hard_keyboard_section)
-                    .setVisibility(mWindowManagerInternal.isHardKeyboardAvailable()
-                            ? View.VISIBLE : View.GONE);
-            final Switch hardKeySwitch = mSwitchingDialogTitleView.findViewById(
-                    com.android.internal.R.id.hard_keyboard_switch);
-            hardKeySwitch.setChecked(mShowImeWithHardKeyboard);
-            hardKeySwitch.setOnCheckedChangeListener((buttonView, isChecked) -> {
-                mSettings.setShowImeWithHardKeyboard(isChecked);
-                // Ensure that the input method dialog is dismissed when changing
-                // the hardware keyboard state.
-                hideInputMethodMenu();
-            });
-
-            final ImeSubtypeListAdapter adapter = new ImeSubtypeListAdapter(dialogContext,
-                    com.android.internal.R.layout.input_method_switch_item, imList, checkedItem);
-            final DialogInterface.OnClickListener choiceListener = (dialog, which) -> {
-                synchronized (ImfLock.class) {
-                    if (mIms == null || mIms.length <= which || mSubtypeIds == null
-                            || mSubtypeIds.length <= which) {
-                        return;
-                    }
-                    final InputMethodInfo im = mIms[which];
-                    int subtypeId = mSubtypeIds[which];
-                    adapter.mCheckedItem = which;
-                    adapter.notifyDataSetChanged();
-                    if (im != null) {
-                        if (subtypeId < 0 || subtypeId >= im.getSubtypeCount()) {
-                            subtypeId = NOT_A_SUBTYPE_ID;
-                        }
-                        mService.setInputMethodLocked(im.getId(), subtypeId);
-                    }
-                    hideInputMethodMenuLocked();
-                }
-            };
-            mDialogBuilder.setSingleChoiceItems(adapter, checkedItem, choiceListener);
-
-            mSwitchingDialog = mDialogBuilder.create();
-            mSwitchingDialog.setCanceledOnTouchOutside(true);
-            final Window w = mSwitchingDialog.getWindow();
-            final WindowManager.LayoutParams attrs = w.getAttributes();
-            w.setType(WindowManager.LayoutParams.TYPE_INPUT_METHOD_DIALOG);
-            w.setHideOverlayWindows(true);
-            // Use an alternate token for the dialog for that window manager can group the token
-            // with other IME windows based on type vs. grouping based on whichever token happens
-            // to get selected by the system later on.
-            attrs.token = dialogWindowContext.getWindowContextToken();
-            attrs.privateFlags |= WindowManager.LayoutParams.SYSTEM_FLAG_SHOW_FOR_ALL_USERS;
-            attrs.setTitle("Select input method");
-            w.setAttributes(attrs);
-            mService.updateSystemUiLocked();
-            mService.sendOnNavButtonFlagsChangedLocked();
-            mSwitchingDialog.show();
-
+        if (imList.isEmpty()) {
+            return;
         }
+
+        hideInputMethodMenuLocked();
+
+        if (preferredInputMethodSubtypeId == NOT_A_SUBTYPE_ID) {
+            final InputMethodSubtype currentSubtype =
+                    mService.getCurrentInputMethodSubtypeLocked();
+            if (currentSubtype != null) {
+                final String curMethodId = mService.getSelectedMethodIdLocked();
+                final InputMethodInfo currentImi =
+                        mService.queryInputMethodForCurrentUserLocked(curMethodId);
+                preferredInputMethodSubtypeId = SubtypeUtils.getSubtypeIdFromHashCode(
+                        currentImi, currentSubtype.hashCode());
+            }
+        }
+
+        // Find out which item should be checked by default.
+        final int size = imList.size();
+        mIms = new InputMethodInfo[size];
+        mSubtypeIds = new int[size];
+        int checkedItem = 0;
+        for (int i = 0; i < size; ++i) {
+            final ImeSubtypeListItem item = imList.get(i);
+            mIms[i] = item.mImi;
+            mSubtypeIds[i] = item.mSubtypeId;
+            if (mIms[i].getId().equals(preferredInputMethodId)) {
+                int subtypeId = mSubtypeIds[i];
+                if ((subtypeId == NOT_A_SUBTYPE_ID)
+                        || (preferredInputMethodSubtypeId == NOT_A_SUBTYPE_ID && subtypeId == 0)
+                        || (subtypeId == preferredInputMethodSubtypeId)) {
+                    checkedItem = i;
+                }
+            }
+        }
+
+        if (mDialogWindowContext == null) {
+            mDialogWindowContext = new InputMethodDialogWindowContext();
+        }
+        final Context dialogWindowContext = mDialogWindowContext.get(displayId);
+        mDialogBuilder = new AlertDialog.Builder(dialogWindowContext);
+        mDialogBuilder.setOnCancelListener(dialog -> hideInputMethodMenu());
+
+        final Context dialogContext = mDialogBuilder.getContext();
+        final TypedArray a = dialogContext.obtainStyledAttributes(null,
+                com.android.internal.R.styleable.DialogPreference,
+                com.android.internal.R.attr.alertDialogStyle, 0);
+        final Drawable dialogIcon = a.getDrawable(
+                com.android.internal.R.styleable.DialogPreference_dialogIcon);
+        a.recycle();
+
+        mDialogBuilder.setIcon(dialogIcon);
+
+        final LayoutInflater inflater = dialogContext.getSystemService(LayoutInflater.class);
+        final View tv = inflater.inflate(
+                com.android.internal.R.layout.input_method_switch_dialog_title, null);
+        mDialogBuilder.setCustomTitle(tv);
+
+        // Setup layout for a toggle switch of the hardware keyboard
+        mSwitchingDialogTitleView = tv;
+        mSwitchingDialogTitleView
+                .findViewById(com.android.internal.R.id.hard_keyboard_section)
+                .setVisibility(mWindowManagerInternal.isHardKeyboardAvailable()
+                        ? View.VISIBLE : View.GONE);
+        final Switch hardKeySwitch = mSwitchingDialogTitleView.findViewById(
+                com.android.internal.R.id.hard_keyboard_switch);
+        hardKeySwitch.setChecked(mShowImeWithHardKeyboard);
+        hardKeySwitch.setOnCheckedChangeListener((buttonView, isChecked) -> {
+            SecureSettingsWrapper.putBoolean(Settings.Secure.SHOW_IME_WITH_HARD_KEYBOARD,
+                    isChecked, userId);
+            // Ensure that the input method dialog is dismissed when changing
+            // the hardware keyboard state.
+            hideInputMethodMenu();
+        });
+
+        // Fill the list items with onClick listener, which takes care of IME (and subtype)
+        // switching when clicked.
+        final ImeSubtypeListAdapter adapter = new ImeSubtypeListAdapter(dialogContext,
+                com.android.internal.R.layout.input_method_switch_item, imList, checkedItem);
+        final DialogInterface.OnClickListener choiceListener = (dialog, which) -> {
+            synchronized (ImfLock.class) {
+                if (mIms == null || mIms.length <= which || mSubtypeIds == null
+                        || mSubtypeIds.length <= which) {
+                    return;
+                }
+                final InputMethodInfo im = mIms[which];
+                int subtypeId = mSubtypeIds[which];
+                adapter.mCheckedItem = which;
+                adapter.notifyDataSetChanged();
+                if (im != null) {
+                    if (subtypeId < 0 || subtypeId >= im.getSubtypeCount()) {
+                        subtypeId = NOT_A_SUBTYPE_ID;
+                    }
+                    mService.setInputMethodLocked(im.getId(), subtypeId);
+                }
+                hideInputMethodMenuLocked();
+            }
+        };
+        mDialogBuilder.setSingleChoiceItems(adapter, checkedItem, choiceListener);
+
+        // Final steps to instantiate a dialog to show it up.
+        mSwitchingDialog = mDialogBuilder.create();
+        mSwitchingDialog.setCanceledOnTouchOutside(true);
+        final Window w = mSwitchingDialog.getWindow();
+        final WindowManager.LayoutParams attrs = w.getAttributes();
+        w.setType(WindowManager.LayoutParams.TYPE_INPUT_METHOD_DIALOG);
+        w.setHideOverlayWindows(true);
+        // Use an alternate token for the dialog for that window manager can group the token
+        // with other IME windows based on type vs. grouping based on whichever token happens
+        // to get selected by the system later on.
+        attrs.token = dialogWindowContext.getWindowContextToken();
+        attrs.privateFlags |= WindowManager.LayoutParams.SYSTEM_FLAG_SHOW_FOR_ALL_USERS;
+        attrs.setTitle("Select input method");
+        w.setAttributes(attrs);
+        mService.updateSystemUiLocked();
+        mService.sendOnNavButtonFlagsChangedLocked();
+        mSwitchingDialog.show();
     }
 
     void updateKeyboardFromSettingsLocked() {
-        mShowImeWithHardKeyboard = mSettings.isShowImeWithHardKeyboardEnabled();
+        mShowImeWithHardKeyboard =
+                SecureSettingsWrapper.getBoolean(Settings.Secure.SHOW_IME_WITH_HARD_KEYBOARD,
+                        false, mService.getCurrentImeUserIdLocked());
         if (mSwitchingDialog != null && mSwitchingDialogTitleView != null
                 && mSwitchingDialog.isShowing()) {
             final Switch hardKeySwitch = mSwitchingDialogTitleView.findViewById(
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodUtils.java b/services/core/java/com/android/server/inputmethod/InputMethodUtils.java
index 547fd2f..a0b55ed 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodUtils.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodUtils.java
@@ -660,14 +660,6 @@
             return getInt(Settings.Secure.SELECTED_INPUT_METHOD_SUBTYPE, NOT_A_SUBTYPE_ID);
         }
 
-        boolean isShowImeWithHardKeyboardEnabled() {
-            return getBoolean(Settings.Secure.SHOW_IME_WITH_HARD_KEYBOARD, false);
-        }
-
-        void setShowImeWithHardKeyboard(boolean show) {
-            putBoolean(Settings.Secure.SHOW_IME_WITH_HARD_KEYBOARD, show);
-        }
-
         @UserIdInt
         public int getCurrentUserId() {
             return mCurrentUserId;
diff --git a/services/core/java/com/android/server/location/gnss/GnssConfiguration.java b/services/core/java/com/android/server/location/gnss/GnssConfiguration.java
index 5ef89ad..a5939e9 100644
--- a/services/core/java/com/android/server/location/gnss/GnssConfiguration.java
+++ b/services/core/java/com/android/server/location/gnss/GnssConfiguration.java
@@ -17,6 +17,7 @@
 package com.android.server.location.gnss;
 
 import android.content.Context;
+import android.location.flags.Flags;
 import android.os.PersistableBundle;
 import android.os.SystemProperties;
 import android.telephony.CarrierConfigManager;
@@ -36,6 +37,7 @@
 import java.util.Collections;
 import java.util.HashMap;
 import java.util.List;
+import java.util.Locale;
 import java.util.Map;
 import java.util.Map.Entry;
 import java.util.Properties;
@@ -275,6 +277,11 @@
         }
         loadPropertiesFromCarrierConfig(inEmergency, activeSubId);
 
+        if (Flags.gnssConfigurationFromResource()) {
+            // Overlay carrier properties from resources.
+            loadPropertiesFromResource(mContext, mProperties);
+        }
+
         if (isSimAbsent(mContext)) {
             // Use the default SIM's LPP profile when SIM is absent.
             String lpp_prof = SystemProperties.get(LPP_PROFILE);
@@ -382,7 +389,7 @@
             if (configKey.startsWith(CarrierConfigManager.Gps.KEY_PREFIX)) {
                 String key = configKey
                         .substring(CarrierConfigManager.Gps.KEY_PREFIX.length())
-                        .toUpperCase();
+                        .toUpperCase(Locale.ROOT);
                 Object value = configs.get(configKey);
                 if (DEBUG) Log.d(TAG, "Gps config: " + key + " = " + value);
                 if (value instanceof String) {
@@ -410,6 +417,24 @@
         }
     }
 
+    private void loadPropertiesFromResource(Context context,
+            Properties properties) {
+        String[] configValues = context.getResources().getStringArray(
+                com.android.internal.R.array.config_gnssParameters);
+        for (String item : configValues) {
+            if (DEBUG) Log.d(TAG, "GnssParamsResource: " + item);
+            // We need to support "KEY =", but not "=VALUE".
+            int index = item.indexOf("=");
+            if (index > 0 && index + 1 < item.length()) {
+                String key = item.substring(0, index);
+                String value = item.substring(index + 1);
+                properties.setProperty(key.trim().toUpperCase(Locale.ROOT), value);
+            } else {
+                Log.w(TAG, "malformed contents: " + item);
+            }
+        }
+    }
+
     private int getRangeCheckedConfigEsExtensionSec() {
         int emergencyExtensionSeconds = getIntConfig(CONFIG_ES_EXTENSION_SEC, 0);
         if (emergencyExtensionSeconds > MAX_EMERGENCY_MODE_EXTENSION_SECONDS) {
diff --git a/services/core/java/com/android/server/media/MediaRoute2ProviderServiceProxy.java b/services/core/java/com/android/server/media/MediaRoute2ProviderServiceProxy.java
index ae889d8..21e7bef 100644
--- a/services/core/java/com/android/server/media/MediaRoute2ProviderServiceProxy.java
+++ b/services/core/java/com/android/server/media/MediaRoute2ProviderServiceProxy.java
@@ -231,18 +231,21 @@
     }
 
     private boolean shouldBind() {
-        if (mRunning) {
-            boolean shouldBind =
-                    mLastDiscoveryPreference != null
-                            && !mLastDiscoveryPreference.getPreferredFeatures().isEmpty();
-            if (mIsSelfScanOnlyProvider) {
-                shouldBind &= mLastDiscoveryPreferenceIncludesThisPackage;
-            }
-            shouldBind |= mIsManagerScanning;
-            shouldBind |= !getSessionInfos().isEmpty();
-            return shouldBind;
+        if (!mRunning) {
+            return false;
         }
-        return false;
+        if (!getSessionInfos().isEmpty() || mIsManagerScanning) {
+            // We bind if any manager is scanning (regardless of whether an app is scanning) to give
+            // the opportunity for providers to publish routing sessions that were established
+            // directly between the app and the provider (typically via AndroidX MediaRouter). See
+            // b/176774510#comment20 for more information.
+            return true;
+        }
+        boolean anAppIsScanning =
+                mLastDiscoveryPreference != null
+                        && !mLastDiscoveryPreference.getPreferredFeatures().isEmpty();
+        return anAppIsScanning
+                && (mLastDiscoveryPreferenceIncludesThisPackage || !mIsSelfScanOnlyProvider);
     }
 
     private void bind() {
diff --git a/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java b/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java
index 000b6d2..06a8d98 100644
--- a/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java
+++ b/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java
@@ -2051,6 +2051,7 @@
             String indent = prefix + "  ";
 
             pw.println(indent + "mOwnerPackageName=" + mOwnerPackageName);
+            pw.println(indent + "mTargetPackageName=" + mTargetPackageName);
             pw.println(indent + "mManagerId=" + mManagerId);
             pw.println(indent + "mOwnerUid=" + mOwnerUid);
             pw.println(indent + "mOwnerPid=" + mOwnerPid);
diff --git a/services/core/java/com/android/server/notification/ZenAdapters.java b/services/core/java/com/android/server/notification/ZenAdapters.java
index 2a65aff..91df04c 100644
--- a/services/core/java/com/android/server/notification/ZenAdapters.java
+++ b/services/core/java/com/android/server/notification/ZenAdapters.java
@@ -16,6 +16,7 @@
 
 package com.android.server.notification;
 
+import android.app.Flags;
 import android.app.NotificationManager.Policy;
 import android.service.notification.ZenModeConfig;
 import android.service.notification.ZenPolicy;
@@ -57,6 +58,12 @@
                     .showStatusBarIcons(policy.showStatusBarIcons());
         }
 
+        if (Flags.modesApi()) {
+            zenPolicyBuilder.allowChannels(
+                    policy.allowPriorityChannels()
+                            ? ZenPolicy.CHANNEL_TYPE_PRIORITY : ZenPolicy.CHANNEL_TYPE_NONE);
+        }
+
         return zenPolicyBuilder.build();
     }
 
diff --git a/services/core/java/com/android/server/pdb/TEST_MAPPING b/services/core/java/com/android/server/pdb/TEST_MAPPING
index 1aa8601..9e98023 100644
--- a/services/core/java/com/android/server/pdb/TEST_MAPPING
+++ b/services/core/java/com/android/server/pdb/TEST_MAPPING
@@ -1,5 +1,5 @@
 {
-    "postsubmit": [
+    "presubmit": [
         {
             "name": "FrameworksServicesTests",
             "options": [
diff --git a/services/core/java/com/android/server/pm/ComputerEngine.java b/services/core/java/com/android/server/pm/ComputerEngine.java
index 3cb2420..0555d90 100644
--- a/services/core/java/com/android/server/pm/ComputerEngine.java
+++ b/services/core/java/com/android/server/pm/ComputerEngine.java
@@ -78,6 +78,7 @@
 import android.content.pm.ApplicationInfo;
 import android.content.pm.AuxiliaryResolveInfo;
 import android.content.pm.ComponentInfo;
+import android.content.pm.Flags;
 import android.content.pm.InstallSourceInfo;
 import android.content.pm.InstantAppRequest;
 import android.content.pm.InstantAppResolveInfo;
@@ -1511,6 +1512,13 @@
             packageInfo.packageName = packageInfo.applicationInfo.packageName =
                     resolveExternalPackageName(p);
 
+            if (Flags.provideInfoOfApkInApex()) {
+                final String apexModuleName =  ps.getApexModuleName();
+                if (apexModuleName != null) {
+                    packageInfo.setApexPackageName(
+                            mApexManager.getActivePackageNameForApexModuleName(apexModuleName));
+                }
+            }
             return packageInfo;
         } else if ((flags & (MATCH_UNINSTALLED_PACKAGES | MATCH_ARCHIVED_PACKAGES)) != 0
                 && PackageUserStateUtils.isAvailable(state, flags)) {
diff --git a/services/core/java/com/android/server/pm/InstallPackageHelper.java b/services/core/java/com/android/server/pm/InstallPackageHelper.java
index b638d30..1b5e789 100644
--- a/services/core/java/com/android/server/pm/InstallPackageHelper.java
+++ b/services/core/java/com/android/server/pm/InstallPackageHelper.java
@@ -3264,9 +3264,9 @@
                 Slog.e(TAG, "updateAllSharedLibrariesLPw failed: " + e.getMessage());
             }
         }
-        mAppDataHelper.prepareAppDataAfterInstallLIF(pkg);
-
         setPackageInstalledForSystemPackage(pkg, allUserHandles, origUserHandles, writeSettings);
+
+        mAppDataHelper.prepareAppDataAfterInstallLIF(pkg);
     }
 
     private void setPackageInstalledForSystemPackage(@NonNull AndroidPackage pkg,
diff --git a/services/core/java/com/android/server/pm/ModuleInfoProvider.java b/services/core/java/com/android/server/pm/ModuleInfoProvider.java
index 230f555..6561d46 100644
--- a/services/core/java/com/android/server/pm/ModuleInfoProvider.java
+++ b/services/core/java/com/android/server/pm/ModuleInfoProvider.java
@@ -18,6 +18,7 @@
 
 import android.annotation.NonNull;
 import android.content.Context;
+import android.content.pm.Flags;
 import android.content.pm.IPackageManager;
 import android.content.pm.ModuleInfo;
 import android.content.pm.PackageInfo;
@@ -165,6 +166,10 @@
                 mi.setApexModuleName(
                         mApexManager.getApexModuleNameForPackageName(modulePackageName));
 
+                if (Flags.provideInfoOfApkInApex()) {
+                    mi.setApkInApexPackageNames(mApexManager.getApksInApex(modulePackageName));
+                }
+
                 mModuleInfo.put(modulePackageName, mi);
             }
         } catch (XmlPullParserException | IOException e) {
diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java
index 2305d6c..75b4531 100644
--- a/services/core/java/com/android/server/pm/UserManagerService.java
+++ b/services/core/java/com/android/server/pm/UserManagerService.java
@@ -2285,6 +2285,11 @@
             throw new SecurityException("You need MANAGE_USERS permission to query if u=" + userId
                     + " is a demo user");
         }
+
+        if (SystemProperties.getBoolean("ro.boot.arc_demo_mode", false)) {
+            return true;
+        }
+
         synchronized (mUsersLock) {
             UserInfo userInfo = getUserInfoLU(userId);
             return userInfo != null && userInfo.isDemo();
diff --git a/services/core/java/com/android/server/pm/permission/PermissionManagerService.java b/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
index 10e6edc..d683855 100644
--- a/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
+++ b/services/core/java/com/android/server/pm/permission/PermissionManagerService.java
@@ -1229,11 +1229,6 @@
                         sPlatformPermissions.put(permission, permissionInfo);
                     }
                 } catch (PackageManager.NameNotFoundException ignored) {
-                    // TODO(b/302609140): Remove extra logging after this issue is diagnosed.
-                    if (permission.equals(Manifest.permission.BLUETOOTH_CONNECT)) {
-                        Slog.e(LOG_TAG, "BLUETOOTH_CONNECT permission hard denied as package"
-                                + " not found when retrieving permission info");
-                    }
                     return PermissionChecker.PERMISSION_HARD_DENIED;
                 }
             }
@@ -1353,34 +1348,17 @@
                 // way we can avoid the datasource creating an attribution context for every call.
                 if (!(fromDatasource && current.equals(attributionSource))
                         && next != null && !current.isTrusted(context)) {
-                    // TODO(b/302609140): Remove extra logging after this issue is diagnosed.
-                    if (permission.equals(Manifest.permission.BLUETOOTH_CONNECT)) {
-                        Slog.e(LOG_TAG, "BLUETOOTH_CONNECT permission hard denied as "
-                                + current + " attribution source isn't a data source and "
-                                + current + " isn't trusted");
-                    }
                     return PermissionChecker.PERMISSION_HARD_DENIED;
                 }
 
                 // If we already checked the permission for this one, skip the work
                 if (!skipCurrentChecks && !checkPermission(context, permissionManagerServiceInt,
                         permission, current)) {
-                    // TODO(b/302609140): Remove extra logging after this issue is diagnosed.
-                    if (permission.equals(Manifest.permission.BLUETOOTH_CONNECT)) {
-                        Slog.e(LOG_TAG, "BLUETOOTH_CONNECT permission hard denied as we"
-                                + " aren't skipping permission checks and permission check returns"
-                                + " false for " + current);
-                    }
                     return PermissionChecker.PERMISSION_HARD_DENIED;
                 }
 
                 if (next != null && !checkPermission(context, permissionManagerServiceInt,
                         permission, next)) {
-                    // TODO(b/302609140): Remove extra logging after this issue is diagnosed.
-                    if (permission.equals(Manifest.permission.BLUETOOTH_CONNECT)) {
-                        Slog.e(LOG_TAG, "BLUETOOTH_CONNECT permission hard denied as"
-                                + " permission check returns false for next source " + next);
-                    }
                     return PermissionChecker.PERMISSION_HARD_DENIED;
                 }
 
@@ -1697,12 +1675,6 @@
                 final AttributionSource resolvedAttributionSource = resolveAttributionSource(
                         context, accessorSource);
                 if (resolvedAttributionSource.getPackageName() == null) {
-                    // TODO(b/302609140): Remove extra logging after this issue is diagnosed.
-                    if (op == OP_BLUETOOTH_CONNECT) {
-                        Slog.e(LOG_TAG, "BLUETOOTH_CONNECT permission hard denied as resolved"
-                                + "package name for " + resolvedAttributionSource + " returned"
-                                + " null");
-                    }
                     return AppOpsManager.MODE_ERRORED;
                 }
                 int notedOp = op;
@@ -1716,13 +1688,6 @@
                 if (attributedOp != AppOpsManager.OP_NONE && attributedOp != op) {
                     checkedOpResult = appOpsManager.checkOpNoThrow(op, resolvedAttributionSource);
                     if (checkedOpResult == MODE_ERRORED) {
-                        // TODO(b/302609140): Remove extra logging after this issue is diagnosed.
-                        if (op == OP_BLUETOOTH_CONNECT) {
-                            Slog.e(LOG_TAG, "BLUETOOTH_CONNECT permission hard denied as"
-                                    + " checkOp for resolvedAttributionSource "
-                                    + resolvedAttributionSource + " and op " + op
-                                    + " returned MODE_ERRORED");
-                        }
                         return checkedOpResult;
                     }
                     notedOp = attributedOp;
diff --git a/services/core/java/com/android/server/utils/AnrTimer.java b/services/core/java/com/android/server/utils/AnrTimer.java
index 7b5192c..e3aba0f 100644
--- a/services/core/java/com/android/server/utils/AnrTimer.java
+++ b/services/core/java/com/android/server/utils/AnrTimer.java
@@ -16,21 +16,30 @@
 
 package com.android.server.utils;
 
+import static android.text.TextUtils.formatSimple;
+
 import android.annotation.NonNull;
 import android.os.Handler;
 import android.os.Message;
 import android.os.SystemClock;
 import android.os.Trace;
+import android.text.TextUtils;
 import android.text.format.TimeMigrationUtils;
+import android.util.ArrayMap;
 import android.util.IndentingPrintWriter;
 import android.util.Log;
+import android.util.LongSparseArray;
+import android.util.SparseArray;
 
 import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.Keep;
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.util.RingBuffer;
 
+import java.lang.ref.WeakReference;
 import java.io.PrintWriter;
 import java.util.Arrays;
+import java.util.ArrayList;
 import java.util.Objects;
 
 /**
@@ -60,9 +69,14 @@
  * is restarted with the extension timeout.  If extensions are disabled or if the extension is zero,
  * the client process is notified of the expiration.
  *
+ * <p>Instances use native resources but not system resources when the feature is enabled.
+ * Instances should be explicitly closed unless they are being closed as part of process
+ * exit. (So, instances in system server generally need not be explicitly closed since they are
+ * created during process start and will last until process exit.)
+ *
  * @hide
  */
-public class AnrTimer<V> {
+public class AnrTimer<V> implements AutoCloseable {
 
     /**
      * The log tag.
@@ -87,6 +101,12 @@
     private static final long TRACE_TAG = Trace.TRACE_TAG_ACTIVITY_MANAGER;
 
     /**
+     * Enable tracing from the time a timer expires until it is accepted or discarded.  This is
+     * used to diagnose long latencies in the client.
+     */
+    private static final boolean ENABLE_TRACING = false;
+
+    /**
      * Return true if the feature is enabled.  By default, the value is take from the Flags class
      * but it can be changed for local testing.
      */
@@ -103,6 +123,9 @@
         }
     }
 
+    /** The default injector. */
+    private static final Injector sDefaultInjector = new Injector();
+
     /**
      * An error is defined by its issue, the operation that detected the error, the tag of the
      * affected service, a short stack of the bad call, and the stringified arg associated with
@@ -160,41 +183,46 @@
     /** A lock for the AnrTimer instance. */
     private final Object mLock = new Object();
 
-    /**
-     * The total number of timers started.
-     */
+    /** The map from client argument to the associated timer ID. */
+    @GuardedBy("mLock")
+    private final ArrayMap<V, Integer> mTimerIdMap = new ArrayMap<>();
+
+    /** Reverse map from timer ID to client argument. */
+    @GuardedBy("mLock")
+    private final SparseArray<V> mTimerArgMap = new SparseArray<>();
+
+    /** The highwater mark of started, but not closed, timers. */
+    @GuardedBy("mLock")
+    private int mMaxStarted = 0;
+
+    /** The total number of timers started. */
     @GuardedBy("mLock")
     private int mTotalStarted = 0;
 
-    /**
-     * The total number of errors detected.
-     */
+    /** The total number of errors detected. */
     @GuardedBy("mLock")
     private int mTotalErrors = 0;
 
-    /**
-     * The handler for messages sent from this instance.
-     */
+    /** The total number of timers that have expired. */
+    @GuardedBy("mLock")
+    private int mTotalExpired = 0;
+
+    /** The handler for messages sent from this instance. */
     private final Handler mHandler;
 
-    /**
-     * The message type for messages sent from this interface.
-     */
+    /** The message type for messages sent from this interface. */
     private final int mWhat;
 
-    /**
-     * A label that identifies the AnrTimer associated with a Timer in log messages.
-     */
+    /** A label that identifies the AnrTimer associated with a Timer in log messages. */
     private final String mLabel;
 
-    /**
-     * Whether this timer instance supports extending timeouts.
-     */
+    /** Whether this timer instance supports extending timeouts. */
     private final boolean mExtend;
 
-    /**
-     * The top-level switch for the feature enabled or disabled.
-     */
+    /** The injector used to create this instance.  This is only used for testing. */
+    private final Injector mInjector;
+
+    /** The top-level switch for the feature enabled or disabled. */
     private final FeatureSwitch mFeature;
 
     /**
@@ -223,7 +251,27 @@
         mWhat = what;
         mLabel = label;
         mExtend = extend;
-        mFeature = new FeatureDisabled();
+        mInjector = injector;
+        boolean enabled = mInjector.anrTimerServiceEnabled() && nativeTimersSupported();
+        mFeature = createFeatureSwitch(enabled);
+    }
+
+    // Return the correct feature.  FeatureEnabled is returned if and only if the feature is
+    // flag-enabled and if the native shadow was successfully created.  Otherwise, FeatureDisabled
+    // is returned.
+    private FeatureSwitch createFeatureSwitch(boolean enabled) {
+        if (!enabled) {
+            return new FeatureDisabled();
+        } else {
+            try {
+                return new FeatureEnabled();
+            } catch (RuntimeException e) {
+                // Something went wrong in the native layer.  Log the error and fall back on the
+                // feature-disabled logic.
+                Log.e(TAG, e.toString());
+                return new FeatureDisabled();
+            }
+        }
     }
 
     /**
@@ -245,7 +293,7 @@
      * @param extend A flag to indicate if expired timers can be granted extensions.
      */
     public AnrTimer(@NonNull Handler handler, int what, @NonNull String label, boolean extend) {
-        this(handler, what, label, extend, new Injector());
+        this(handler, what, label, extend, sDefaultInjector);
     }
 
     /**
@@ -272,19 +320,44 @@
     }
 
     /**
+     * Start a trace on the timer.  The trace is laid down in the AnrTimerTrack.
+     */
+    private void traceBegin(int timerId, int pid, int uid, String what) {
+        if (ENABLE_TRACING) {
+            final String label = formatSimple("%s(%d,%d,%s)", what, pid, uid, mLabel);
+            final int cookie = timerId;
+            Trace.asyncTraceForTrackBegin(TRACE_TAG, TRACK, label, cookie);
+        }
+    }
+
+    /**
+     * End a trace on the timer.
+     */
+    private void traceEnd(int timerId) {
+        if (ENABLE_TRACING) {
+            final int cookie = timerId;
+            Trace.asyncTraceForTrackEnd(TRACE_TAG, TRACK, cookie);
+        }
+    }
+
+    /**
      * The FeatureSwitch class provides a quick switch between feature-enabled behavior and
      * feature-disabled behavior.
      */
     private abstract class FeatureSwitch {
         abstract void start(@NonNull V arg, int pid, int uid, long timeoutMs);
 
-        abstract void cancel(@NonNull V arg);
+        abstract boolean cancel(@NonNull V arg);
 
-        abstract void accept(@NonNull V arg);
+        abstract boolean accept(@NonNull V arg);
 
-        abstract void discard(@NonNull V arg);
+        abstract boolean discard(@NonNull V arg);
 
         abstract boolean enabled();
+
+        abstract void dump(PrintWriter pw, boolean verbose);
+
+        abstract void close();
     }
 
     /**
@@ -301,18 +374,21 @@
 
         /** Cancel a timer by removing the message from the client's handler. */
         @Override
-        void cancel(@NonNull V arg) {
+        boolean cancel(@NonNull V arg) {
             mHandler.removeMessages(mWhat, arg);
+            return true;
         }
 
         /** accept() is a no-op when the feature is disabled. */
         @Override
-        void accept(@NonNull V arg) {
+        boolean accept(@NonNull V arg) {
+            return true;
         }
 
         /** discard() is a no-op when the feature is disabled. */
         @Override
-        void discard(@NonNull V arg) {
+        boolean discard(@NonNull V arg) {
+            return true;
         }
 
         /** The feature is not enabled. */
@@ -320,12 +396,179 @@
         boolean enabled() {
             return false;
         }
+
+        /** dump() is a no-op when the feature is disabled. */
+        @Override
+        void dump(PrintWriter pw, boolean verbose) {
+        }
+
+        /** close() is a no-op when the feature is disabled. */
+        @Override
+        void close() {
+        }
+    }
+
+    /**
+     * A static list of AnrTimer instances.  The list is traversed by dumpsys.  Only instances
+     * using native resources are included.
+     */
+    @GuardedBy("sAnrTimerList")
+    private static final LongSparseArray<WeakReference<AnrTimer>> sAnrTimerList =
+        new LongSparseArray<>();
+
+    /**
+     * The FeatureEnabled class enables the AnrTimer logic.  It is used when the AnrTimer service
+     * is enabled via Flags.anrTimerServiceEnabled.
+     */
+    private class FeatureEnabled extends FeatureSwitch {
+
+        /**
+         * The native timer that supports this instance. The value is set to non-zero when the
+         * native timer is created and it is set back to zero when the native timer is freed.
+         */
+        private long mNative = 0;
+
+        /** Fetch the native tag (an integer) for the given label. */
+        FeatureEnabled() {
+            mNative = nativeAnrTimerCreate(mLabel);
+            if (mNative == 0) throw new IllegalArgumentException("unable to create native timer");
+            synchronized (sAnrTimerList) {
+                sAnrTimerList.put(mNative, new WeakReference(AnrTimer.this));
+            }
+        }
+
+        /**
+         * Start a timer.
+         */
+        @Override
+        void start(@NonNull V arg, int pid, int uid, long timeoutMs) {
+            synchronized (mLock) {
+                if (mTimerIdMap.containsKey(arg)) {
+                    // There is an existing timer.  Cancel it.
+                    cancel(arg);
+                }
+                int timerId = nativeAnrTimerStart(mNative, pid, uid, timeoutMs, mExtend);
+                if (timerId > 0) {
+                    mTimerIdMap.put(arg, timerId);
+                    mTimerArgMap.put(timerId, arg);
+                    mTotalStarted++;
+                    mMaxStarted = Math.max(mMaxStarted, mTimerIdMap.size());
+                } else {
+                    throw new RuntimeException("unable to start timer");
+                }
+            }
+        }
+
+        /**
+         * Cancel a timer.  No error is reported if the timer is not found because some clients
+         * cancel timers from common code that runs even if a timer was never started.
+         */
+        @Override
+        boolean cancel(@NonNull V arg) {
+            synchronized (mLock) {
+                Integer timer = removeLocked(arg);
+                if (timer == null) {
+                    return false;
+                }
+                if (!nativeAnrTimerCancel(mNative, timer)) {
+                    // There may be an expiration message in flight.  Cancel it.
+                    mHandler.removeMessages(mWhat, arg);
+                    return false;
+                }
+                return true;
+            }
+        }
+
+        /**
+         * Accept a timer in the framework-level handler.  The timeout has been accepted and the
+         * timeout handler is executing.
+         */
+        @Override
+        boolean accept(@NonNull V arg) {
+            synchronized (mLock) {
+                Integer timer = removeLocked(arg);
+                if (timer == null) {
+                    notFoundLocked("accept", arg);
+                    return false;
+                }
+                nativeAnrTimerAccept(mNative, timer);
+                traceEnd(timer);
+                return true;
+            }
+        }
+
+        /**
+         * Discard a timer in the framework-level handler.  For whatever reason, the timer is no
+         * longer interesting.  No statistics are collected.  Return false if the time was not
+         * found.
+         */
+        @Override
+        boolean discard(@NonNull V arg) {
+            synchronized (mLock) {
+                Integer timer = removeLocked(arg);
+                if (timer == null) {
+                    notFoundLocked("discard", arg);
+                    return false;
+                }
+                nativeAnrTimerDiscard(mNative, timer);
+                traceEnd(timer);
+                return true;
+            }
+        }
+
+        /** The feature is enabled. */
+        @Override
+        boolean enabled() {
+            return true;
+        }
+
+        /** Dump statistics from the native layer. */
+        @Override
+        void dump(PrintWriter pw, boolean verbose) {
+            synchronized (mLock) {
+                if (mNative != 0) {
+                    nativeAnrTimerDump(mNative, verbose);
+                } else {
+                    pw.println("closed");
+                }
+            }
+        }
+
+        /** Free native resources. */
+        @Override
+        void close() {
+            // Remove self from the list of active timers.
+            synchronized (sAnrTimerList) {
+                sAnrTimerList.remove(mNative);
+            }
+            synchronized (mLock) {
+                if (mNative != 0) nativeAnrTimerClose(mNative);
+                mNative = 0;
+            }
+        }
+
+        /**
+         * Delete the entries associated with arg from the maps and return the ID of the timer, if
+         * any.
+         */
+        @GuardedBy("mLock")
+        private Integer removeLocked(V arg) {
+            Integer r = mTimerIdMap.remove(arg);
+            if (r != null) {
+                synchronized (mTimerArgMap) {
+                    mTimerArgMap.remove(r);
+                }
+            }
+            return r;
+        }
     }
 
     /**
      * Start a timer associated with arg.  The same object must be used to cancel, accept, or
      * discard a timer later.  If a timer already exists with the same arg, then the existing timer
-     * is canceled and a new timer is created.
+     * is canceled and a new timer is created.  The timeout is signed but negative delays are
+     * nonsensical.  Rather than throw an exception, timeouts less than 0ms are forced to 0ms.  This
+     * allows a client to deliver an immediate timeout via the AnrTimer.
      *
      * @param arg The key by which the timer is known.  This is never examined or modified.
      * @param pid The Linux process ID of the target being timed.
@@ -333,25 +576,39 @@
      * @param timeoutMs The timer timeout, in milliseconds.
      */
     public void start(@NonNull V arg, int pid, int uid, long timeoutMs) {
+        if (timeoutMs < 0) timeoutMs = 0;
         mFeature.start(arg, pid, uid, timeoutMs);
     }
 
     /**
      * Cancel the running timer associated with arg.  The timer is forgotten.  If the timer has
-     * expired, the call is treated as a discard.  No errors are reported if the timer does not
-     * exist or if the timer has expired.
+     * expired, the call is treated as a discard.  The function returns true if a running timer was
+     * found, and false if an expired timer was found or if no timer was found.  After this call,
+     * the timer does not exist.
+     *
+     * Note: the return value is always true if the feature is not enabled.
+     *
+     * @param arg The key by which the timer is known.  This is never examined or modified.
+     * @return True if a running timer was canceled.
      */
-    public void cancel(@NonNull V arg) {
-        mFeature.cancel(arg);
+    public boolean cancel(@NonNull V arg) {
+        return mFeature.cancel(arg);
     }
 
     /**
      * Accept the expired timer associated with arg.  This indicates that the caller considers the
-     * timer expiration to be a true ANR.  (See {@link #discard} for an alternate response.)  It is
-     * an error to accept a running timer, however the running timer will be canceled.
+     * timer expiration to be a true ANR.  (See {@link #discard} for an alternate response.)  The
+     * function returns true if an expired timer was found and false if a running timer was found or
+     * if no timer was found.  After this call, the timer does not exist.  It is an error to accept
+     * a running timer, however, the running timer will be canceled.
+     *
+     * Note: the return value is always true if the feature is not enabled.
+     *
+     * @param arg The key by which the timer is known.  This is never examined or modified.
+     * @return True if an expired timer was accepted.
      */
-    public void accept(@NonNull V arg) {
-        mFeature.accept(arg);
+    public boolean accept(@NonNull V arg) {
+        return mFeature.accept(arg);
     }
 
     /**
@@ -359,11 +616,57 @@
      * timer expiration to be a false ANR.  ((See {@link #accept} for an alternate response.)  One
      * reason to discard an expired timer is if the process being timed was also being debugged:
      * such a process could be stopped at a breakpoint and its failure to respond would not be an
-     * error.  It is an error to discard a running timer, however the running timer will be
-     * canceled.
+     * error.  After this call thie timer does not exist. It is an error to discard a running timer,
+     * however the running timer will be canceled.
+     *
+     * Note: the return value is always true if the feature is not enabled.
+     *
+     * @param arg The key by which the timer is known.  This is never examined or modified.
+     * @return True if an expired timer was discarded.
      */
-    public void discard(@NonNull V arg) {
-        mFeature.discard(arg);
+    public boolean discard(@NonNull V arg) {
+        return mFeature.discard(arg);
+    }
+
+    /**
+     * The notifier that a timer has fired.  The timerId and original pid/uid are supplied.  This
+     * method is called from native code.  This method takes mLock so that a timer cannot expire
+     * in the middle of another operation (like start or cancel).
+     */
+    @Keep
+    private boolean expire(int timerId, int pid, int uid) {
+        traceBegin(timerId, pid, uid, "expired");
+        V arg = null;
+        synchronized (mLock) {
+            arg = mTimerArgMap.get(timerId);
+            if (arg == null) {
+                Log.e(TAG, formatSimple("failed to expire timer %s:%d : arg not found",
+                                mLabel, timerId));
+                mTotalErrors++;
+                return false;
+            }
+            mTotalExpired++;
+        }
+        mHandler.sendMessage(Message.obtain(mHandler, mWhat, arg));
+        return true;
+    }
+
+    /**
+     * Close the object and free any native resources.
+     */
+    public void close() {
+        mFeature.close();
+    }
+
+    /**
+     * Ensure any native resources are freed when the object is GC'ed.  Best practice is to close
+     * the object explicitly, but overriding finalize() avoids accidental leaks.
+     */
+    @SuppressWarnings("Finalize")
+    @Override
+    protected void finalize() throws Throwable {
+        close();
+        super.finalize();
     }
 
     /**
@@ -373,8 +676,11 @@
         synchronized (mLock) {
             pw.format("timer: %s\n", mLabel);
             pw.increaseIndent();
-            pw.format("started=%d errors=%d\n", mTotalStarted, mTotalErrors);
+            pw.format("started=%d maxStarted=%d running=%d expired=%d errors=%d\n",
+                    mTotalStarted, mMaxStarted, mTimerIdMap.size(),
+                    mTotalExpired, mTotalErrors);
             pw.decreaseIndent();
+            mFeature.dump(pw, false);
         }
     }
 
@@ -386,6 +692,13 @@
     }
 
     /**
+     * The current time in milliseconds.
+     */
+    private static long now() {
+        return SystemClock.uptimeMillis();
+    }
+
+    /**
      * Dump all errors to the output stream.
      */
     private static void dumpErrors(IndentingPrintWriter ipw) {
@@ -422,23 +735,89 @@
         mTotalErrors++;
     }
 
-    /**
-     * Log an error about  a timer not found.
-     */
+    /** Record an error about a timer not found. */
     @GuardedBy("mLock")
     private void notFoundLocked(String operation, Object arg) {
         recordErrorLocked(operation, "notFound", arg);
     }
 
-    /**
-     * Dumpsys output.
-     */
-    public static void dump(@NonNull PrintWriter pw, boolean verbose) {
+    /** Dumpsys output, allowing for overrides. */
+    @VisibleForTesting
+    static void dump(@NonNull PrintWriter pw, boolean verbose, @NonNull Injector injector) {
+        if (!injector.anrTimerServiceEnabled()) return;
+
         final IndentingPrintWriter ipw = new IndentingPrintWriter(pw);
         ipw.println("AnrTimer statistics");
         ipw.increaseIndent();
+        synchronized (sAnrTimerList) {
+            final int size = sAnrTimerList.size();
+            ipw.println("reporting " + size + " timers");
+            for (int i = 0; i < size; i++) {
+                AnrTimer a = sAnrTimerList.valueAt(i).get();
+                if (a != null) a.dump(ipw);
+            }
+        }
         if (verbose) dumpErrors(ipw);
         ipw.format("AnrTimerEnd\n");
         ipw.decreaseIndent();
     }
+
+    /** Dumpsys output.  There is no output if the feature is not enabled. */
+    public static void dump(@NonNull PrintWriter pw, boolean verbose) {
+        dump(pw, verbose, sDefaultInjector);
+    }
+
+    /**
+     * Return true if the native timers are supported.  Native timers are supported if the method
+     * nativeAnrTimerSupported() can be executed and it returns true.
+     */
+    private static boolean nativeTimersSupported() {
+        try {
+            return nativeAnrTimerSupported();
+        } catch (java.lang.UnsatisfiedLinkError e) {
+            return false;
+        }
+    }
+
+    /**
+     * Native methods
+     */
+
+    /** Return true if the native AnrTimer code is operational. */
+    private static native boolean nativeAnrTimerSupported();
+
+    /**
+     * Create a new native timer with the given key and name.  The key is not used by the native
+     * code but it is returned to the Java layer in the expiration handler.  The name is only for
+     * logging.  Unlike the other methods, this is an instance method: the "this" parameter is
+     * passed into the native layer.
+     */
+    private native long nativeAnrTimerCreate(String name);
+
+    /** Release the native resources.  No further operations are premitted. */
+    private static native int nativeAnrTimerClose(long service);
+
+    /** Start a timer and return its ID.  Zero is returned on error. */
+    private static native int nativeAnrTimerStart(long service, int pid, int uid, long timeoutMs,
+            boolean extend);
+
+    /**
+     * Cancel a timer by ID.  Return true if the timer was running and canceled.  Return false if
+     * the timer was not found or if the timer had already expired.
+     */
+    private static native boolean nativeAnrTimerCancel(long service, int timerId);
+
+    /** Accept an expired timer by ID.  Return true if the timer was found. */
+    private static native boolean nativeAnrTimerAccept(long service, int timerId);
+
+    /** Discard an expired timer by ID.  Return true if the timer was found.  */
+    private static native boolean nativeAnrTimerDiscard(long service, int timerId);
+
+    /** Prod the native library to log a few statistics. */
+    private static native void nativeAnrTimerDump(long service, boolean verbose);
+
+    // This is not a native method but it is a native interface, in the sense that it is called from
+    // the native layer to report timer expiration.  The function must return true if the expiration
+    // message is delivered to the upper layers and false if it could not be delivered.
+    // private boolean expire(int timerId, int pid, int uid);
 }
diff --git a/services/core/java/com/android/server/vibrator/VibrationScaler.java b/services/core/java/com/android/server/vibrator/VibrationScaler.java
index 59b55bf7..0a7872f 100644
--- a/services/core/java/com/android/server/vibrator/VibrationScaler.java
+++ b/services/core/java/com/android/server/vibrator/VibrationScaler.java
@@ -17,11 +17,13 @@
 package com.android.server.vibrator;
 
 import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.content.Context;
 import android.hardware.vibrator.V1_0.EffectStrength;
 import android.os.IExternalVibratorService;
 import android.os.VibrationEffect;
 import android.os.Vibrator;
+import android.os.vibrator.Flags;
 import android.os.vibrator.PrebakedSegment;
 import android.os.vibrator.VibrationEffectSegment;
 import android.util.Slog;
@@ -56,6 +58,8 @@
     private final VibrationSettings mSettingsController;
     private final int mDefaultVibrationAmplitude;
 
+    private SparseArray<Float> mAdaptiveHapticsScales;
+
     VibrationScaler(Context context, VibrationSettings settingsController) {
         mSettingsController = settingsController;
         mDefaultVibrationAmplitude = context.getResources().getInteger(
@@ -140,6 +144,15 @@
             if (scaleLevel != null) {
                 segment = segment.scale(scaleLevel.factor);
             }
+
+            // If adaptive haptics scaling is available for this usage, apply it to the segment.
+            if (Flags.adaptiveHapticsEnabled()
+                    && mAdaptiveHapticsScales != null && mAdaptiveHapticsScales.size() > 0
+                    && mAdaptiveHapticsScales.contains(usageHint)) {
+                float adaptiveScale = mAdaptiveHapticsScales.get(usageHint);
+                segment = segment.scale(adaptiveScale);
+            }
+
             segments.set(i, segment);
         }
         if (segments.equals(composedEffect.getSegments())) {
@@ -173,6 +186,16 @@
         return prebaked.applyEffectStrength(newEffectStrength);
     }
 
+    /**
+     * Updates the adaptive haptics scales.
+     * @param scales the new vibration scales to apply.
+     *
+     * @hide
+     */
+    public void updateAdaptiveHapticsScales(@Nullable SparseArray<Float> scales) {
+        mAdaptiveHapticsScales = scales;
+    }
+
     /** Mapping of Vibrator.VIBRATION_INTENSITY_* values to {@link EffectStrength}. */
     private static int intensityToEffectStrength(int intensity) {
         switch (intensity) {
diff --git a/services/core/java/com/android/server/vibrator/VibratorControlService.java b/services/core/java/com/android/server/vibrator/VibratorControlService.java
index 2eeb903..9d75249 100644
--- a/services/core/java/com/android/server/vibrator/VibratorControlService.java
+++ b/services/core/java/com/android/server/vibrator/VibratorControlService.java
@@ -16,14 +16,26 @@
 
 package com.android.server.vibrator;
 
+import static android.os.VibrationAttributes.USAGE_ALARM;
+import static android.os.VibrationAttributes.USAGE_COMMUNICATION_REQUEST;
+import static android.os.VibrationAttributes.USAGE_HARDWARE_FEEDBACK;
+import static android.os.VibrationAttributes.USAGE_MEDIA;
+import static android.os.VibrationAttributes.USAGE_NOTIFICATION;
+import static android.os.VibrationAttributes.USAGE_RINGTONE;
+import static android.os.VibrationAttributes.USAGE_TOUCH;
+import static android.os.VibrationAttributes.USAGE_UNKNOWN;
+
 import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.annotation.SuppressLint;
 import android.frameworks.vibrator.IVibratorControlService;
 import android.frameworks.vibrator.IVibratorController;
+import android.frameworks.vibrator.ScaleParam;
 import android.frameworks.vibrator.VibrationParam;
 import android.os.IBinder;
 import android.os.RemoteException;
 import android.util.Slog;
+import android.util.SparseArray;
 
 import java.util.Objects;
 
@@ -37,10 +49,13 @@
     private static final String TAG = "VibratorControlService";
 
     private final VibratorControllerHolder mVibratorControllerHolder;
+    private final VibrationScaler mVibrationScaler;
     private final Object mLock;
 
-    public VibratorControlService(VibratorControllerHolder vibratorControllerHolder, Object lock) {
+    public VibratorControlService(VibratorControllerHolder vibratorControllerHolder,
+            VibrationScaler vibrationScaler, Object lock) {
         mVibratorControllerHolder = vibratorControllerHolder;
+        mVibrationScaler = vibrationScaler;
         mLock = lock;
     }
 
@@ -70,25 +85,62 @@
                         + "controller doesn't match the registered one. " + this);
                 return;
             }
+            updateAdaptiveHapticsScales(/* params= */ null);
             mVibratorControllerHolder.setVibratorController(null);
         }
     }
 
     @Override
-    public void setVibrationParams(
-            @SuppressLint("ArrayReturn") VibrationParam[] params, IVibratorController token)
-            throws RemoteException {
-        // TODO(b/305939964): Add set vibration implementation.
+    public void setVibrationParams(@SuppressLint("ArrayReturn") VibrationParam[] params,
+            @NonNull IVibratorController token) throws RemoteException {
+        Objects.requireNonNull(token);
+
+        synchronized (mLock) {
+            if (mVibratorControllerHolder.getVibratorController() == null) {
+                Slog.w(TAG, "Received request to set VibrationParams for IVibratorController = "
+                        + token + ", but no controller was previously registered. Request "
+                        + "Ignored.");
+                return;
+            }
+            if (!Objects.equals(mVibratorControllerHolder.getVibratorController().asBinder(),
+                    token.asBinder())) {
+                Slog.wtf(TAG, "Failed to set new VibrationParams. The provided "
+                        + "controller doesn't match the registered one. " + this);
+                return;
+            }
+
+            updateAdaptiveHapticsScales(params);
+        }
     }
 
     @Override
-    public void clearVibrationParams(int types, IVibratorController token) throws RemoteException {
-        // TODO(b/305939964): Add clear vibration implementation.
+    public void clearVibrationParams(int types, @NonNull IVibratorController token)
+            throws RemoteException {
+        Objects.requireNonNull(token);
+
+        synchronized (mLock) {
+            if (mVibratorControllerHolder.getVibratorController() == null) {
+                Slog.w(TAG, "Received request to clear VibrationParams for IVibratorController = "
+                        + token + ", but no controller was previously registered. Request "
+                        + "Ignored.");
+                return;
+            }
+            if (!Objects.equals(mVibratorControllerHolder.getVibratorController().asBinder(),
+                    token.asBinder())) {
+                Slog.wtf(TAG, "Failed to clear VibrationParams. The provided "
+                        + "controller doesn't match the registered one. " + this);
+                return;
+            }
+            //TODO(305942827): Update this method to only clear the specified vibration types.
+            // Perhaps look into whether it makes more sense to have this clear all scales and
+            // rely on setVibrationParams for clearing the scales for specific vibrations.
+            updateAdaptiveHapticsScales(/* params= */ null);
+        }
     }
 
     @Override
     public void onRequestVibrationParamsComplete(
-            IBinder requestToken, @SuppressLint("ArrayReturn") VibrationParam[] result)
+            @NonNull IBinder requestToken, @SuppressLint("ArrayReturn") VibrationParam[] result)
             throws RemoteException {
         // TODO(305942827): Cache the vibration params in VibrationScaler
     }
@@ -102,4 +154,52 @@
     public String getInterfaceHash() throws RemoteException {
         return this.HASH;
     }
+
+    /**
+     * Extracts the vibration scales and caches them in {@link VibrationScaler}.
+     *
+     * @param params the new vibration params to cache.
+     */
+    private void updateAdaptiveHapticsScales(@Nullable VibrationParam[] params) {
+        if (params == null || params.length == 0) {
+            mVibrationScaler.updateAdaptiveHapticsScales(null);
+            return;
+        }
+
+        SparseArray<Float> vibrationScales = new SparseArray<>();
+        for (int i = 0; i < params.length; i++) {
+            ScaleParam scaleParam = params[i].getScale();
+            extractVibrationScales(scaleParam, vibrationScales);
+        }
+        mVibrationScaler.updateAdaptiveHapticsScales(vibrationScales);
+    }
+
+    /**
+     * Extracts the vibration scales and map them to their corresponding
+     * {@link android.os.VibrationAttributes} usages.
+     */
+    private void extractVibrationScales(ScaleParam scaleParam, SparseArray<Float> vibrationScales) {
+        if ((ScaleParam.TYPE_ALARM & scaleParam.typesMask) != 0) {
+            vibrationScales.put(USAGE_ALARM, scaleParam.scale);
+        }
+
+        if ((ScaleParam.TYPE_NOTIFICATION & scaleParam.typesMask) != 0) {
+            vibrationScales.put(USAGE_NOTIFICATION, scaleParam.scale);
+            vibrationScales.put(USAGE_COMMUNICATION_REQUEST, scaleParam.scale);
+        }
+
+        if ((ScaleParam.TYPE_RINGTONE & scaleParam.typesMask) != 0) {
+            vibrationScales.put(USAGE_RINGTONE, scaleParam.scale);
+        }
+
+        if ((ScaleParam.TYPE_MEDIA & scaleParam.typesMask) != 0) {
+            vibrationScales.put(USAGE_MEDIA, scaleParam.scale);
+            vibrationScales.put(USAGE_UNKNOWN, scaleParam.scale);
+        }
+
+        if ((ScaleParam.TYPE_INTERACTIVE & scaleParam.typesMask) != 0) {
+            vibrationScales.put(USAGE_TOUCH, scaleParam.scale);
+            vibrationScales.put(USAGE_HARDWARE_FEEDBACK, scaleParam.scale);
+        }
+    }
 }
diff --git a/services/core/java/com/android/server/vibrator/VibratorManagerService.java b/services/core/java/com/android/server/vibrator/VibratorManagerService.java
index fc824ab..2c1ab95 100644
--- a/services/core/java/com/android/server/vibrator/VibratorManagerService.java
+++ b/services/core/java/com/android/server/vibrator/VibratorManagerService.java
@@ -273,7 +273,8 @@
         injector.addService(EXTERNAL_VIBRATOR_SERVICE, new ExternalVibratorService());
         if (ServiceManager.isDeclared(VIBRATOR_CONTROL_SERVICE)) {
             injector.addService(VIBRATOR_CONTROL_SERVICE,
-                    new VibratorControlService(new VibratorControllerHolder(), mLock));
+                    new VibratorControlService(new VibratorControllerHolder(), mVibrationScaler,
+                            mLock));
         }
 
     }
diff --git a/services/core/java/com/android/server/wm/AccessibilityController.java b/services/core/java/com/android/server/wm/AccessibilityController.java
index 77b4a74..1577cef 100644
--- a/services/core/java/com/android/server/wm/AccessibilityController.java
+++ b/services/core/java/com/android/server/wm/AccessibilityController.java
@@ -448,16 +448,16 @@
         }
     }
 
-    void drawMagnifiedRegionBorderIfNeeded(int displayId) {
+    void drawMagnifiedRegionBorderIfNeeded(int displayId, SurfaceControl.Transaction t) {
         if (mAccessibilityTracing.isTracingEnabled(FLAGS_MAGNIFICATION_CALLBACK)) {
             mAccessibilityTracing.logTrace(
                     TAG + ".drawMagnifiedRegionBorderIfNeeded",
                     FLAGS_MAGNIFICATION_CALLBACK,
-                    "displayId=" + displayId);
+                    "displayId=" + displayId + "; transaction={" + t + "}");
         }
         final DisplayMagnifier displayMagnifier = mDisplayMagnifiers.get(displayId);
         if (displayMagnifier != null) {
-            displayMagnifier.drawMagnifiedRegionBorderIfNeeded();
+            displayMagnifier.drawMagnifiedRegionBorderIfNeeded(t);
         }
         // Not relevant for the window observer.
     }
@@ -855,12 +855,12 @@
                     .sendToTarget();
         }
 
-        void drawMagnifiedRegionBorderIfNeeded() {
+        void drawMagnifiedRegionBorderIfNeeded(SurfaceControl.Transaction t) {
             if (mAccessibilityTracing.isTracingEnabled(FLAGS_MAGNIFICATION_CALLBACK)) {
                 mAccessibilityTracing.logTrace(LOG_TAG + ".drawMagnifiedRegionBorderIfNeeded",
-                        FLAGS_MAGNIFICATION_CALLBACK);
+                        FLAGS_MAGNIFICATION_CALLBACK, "transition={" + t + "}");
             }
-            mMagnifedViewport.drawWindowIfNeeded();
+            mMagnifedViewport.drawWindowIfNeeded(t);
         }
 
         void dump(PrintWriter pw, String prefix) {
@@ -1106,11 +1106,11 @@
             }
 
             void setMagnifiedRegionBorderShown(boolean shown, boolean animate) {
-                if (mWindow.setShown(shown, animate)) {
+                if (shown) {
                     mFullRedrawNeeded = true;
-                    // Clear the old region, so recomputeBounds will refresh the current region.
                     mOldMagnificationRegion.set(0, 0, 0, 0);
                 }
+                mWindow.setShown(shown, animate);
             }
 
             void getMagnifiedFrameInContentCoords(Rect rect) {
@@ -1128,9 +1128,9 @@
                 return mMagnificationSpec;
             }
 
-            void drawWindowIfNeeded() {
+            void drawWindowIfNeeded(SurfaceControl.Transaction t) {
                 recomputeBounds();
-                mWindow.postDrawIfNeeded();
+                mWindow.drawIfNeeded(t);
             }
 
             void destroyWindow() {
@@ -1158,7 +1158,7 @@
                 mWindow.dump(pw, prefix);
             }
 
-            private final class ViewportWindow implements Runnable {
+            private final class ViewportWindow {
                 private static final String SURFACE_TITLE = "Magnification Overlay";
 
                 private final Region mBounds = new Region();
@@ -1166,18 +1166,15 @@
                 private final Paint mPaint = new Paint();
 
                 private final SurfaceControl mSurfaceControl;
-                /** After initialization, it should only be accessed from animation thread. */
-                private final SurfaceControl.Transaction mTransaction;
                 private final BLASTBufferQueue mBlastBufferQueue;
                 private final Surface mSurface;
 
                 private final AnimationController mAnimationController;
 
                 private boolean mShown;
-                private boolean mLastSurfaceShown;
                 private int mAlpha;
 
-                private volatile boolean mInvalidated;
+                private boolean mInvalidated;
 
                 ViewportWindow(Context context) {
                     SurfaceControl surfaceControl = null;
@@ -1205,7 +1202,6 @@
                     InputMonitor.setTrustedOverlayInputInfo(mSurfaceControl, t,
                             mDisplayContent.getDisplayId(), "Magnification Overlay");
                     t.apply();
-                    mTransaction = t;
                     mSurface = mBlastBufferQueue.createSurface();
 
                     mAnimationController = new AnimationController(context,
@@ -1223,11 +1219,10 @@
                     mInvalidated = true;
                 }
 
-                /** Returns {@code true} if the shown state is changed. */
-                boolean setShown(boolean shown, boolean animate) {
+                void setShown(boolean shown, boolean animate) {
                     synchronized (mService.mGlobalLock) {
                         if (mShown == shown) {
-                            return false;
+                            return;
                         }
                         mShown = shown;
                         mAnimationController.onFrameShownStateChanged(shown, animate);
@@ -1235,7 +1230,6 @@
                             Slog.i(LOG_TAG, "ViewportWindow shown: " + mShown);
                         }
                     }
-                    return true;
                 }
 
                 @SuppressWarnings("unused")
@@ -1291,22 +1285,7 @@
                     mService.scheduleAnimationLocked();
                 }
 
-                void postDrawIfNeeded() {
-                    if (mInvalidated) {
-                        mService.mAnimationHandler.post(this);
-                    }
-                }
-
-                @Override
-                public void run() {
-                    drawIfNeeded();
-                }
-
-                /**
-                 * This method must only be called by animation handler directly to make sure
-                 * thread safe and there is no lock held outside.
-                 */
-                private void drawIfNeeded() {
+                void drawIfNeeded(SurfaceControl.Transaction t) {
                     // Drawing variables (alpha, dirty rect, and bounds) access is synchronized
                     // using WindowManagerGlobalLock. Grab copies of these values before
                     // drawing on the canvas so that drawing can be performed outside of the lock.
@@ -1335,7 +1314,6 @@
                         }
                     }
 
-                    final boolean showSurface;
                     // Draw without holding WindowManagerGlobalLock.
                     if (alpha > 0) {
                         Canvas canvas = null;
@@ -1351,17 +1329,9 @@
                         mPaint.setAlpha(alpha);
                         canvas.drawPath(drawingBounds.getBoundaryPath(), mPaint);
                         mSurface.unlockCanvasAndPost(canvas);
-                        showSurface = true;
+                        t.show(mSurfaceControl);
                     } else {
-                        showSurface = false;
-                    }
-
-                    if (showSurface && !mLastSurfaceShown) {
-                        mTransaction.show(mSurfaceControl).apply();
-                        mLastSurfaceShown = true;
-                    } else if (!showSurface && mLastSurfaceShown) {
-                        mTransaction.hide(mSurfaceControl).apply();
-                        mLastSurfaceShown = false;
+                        t.hide(mSurfaceControl);
                     }
                 }
 
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index a43e7d5..febcc05 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -183,6 +183,8 @@
 import static com.android.server.wm.ActivityRecordProto.REPORTED_DRAWN;
 import static com.android.server.wm.ActivityRecordProto.REPORTED_VISIBLE;
 import static com.android.server.wm.ActivityRecordProto.SHOULD_FORCE_ROTATE_FOR_CAMERA_COMPAT;
+import static com.android.server.wm.ActivityRecordProto.SHOULD_IGNORE_ORIENTATION_REQUEST_LOOP;
+import static com.android.server.wm.ActivityRecordProto.SHOULD_OVERRIDE_FORCE_RESIZE_APP;
 import static com.android.server.wm.ActivityRecordProto.SHOULD_OVERRIDE_MIN_ASPECT_RATIO;
 import static com.android.server.wm.ActivityRecordProto.SHOULD_REFRESH_ACTIVITY_FOR_CAMERA_COMPAT;
 import static com.android.server.wm.ActivityRecordProto.SHOULD_REFRESH_ACTIVITY_VIA_PAUSE_FOR_CAMERA_COMPAT;
@@ -4312,7 +4314,6 @@
         mTaskSupervisor.getActivityMetricsLogger().notifyActivityRemoved(this);
         mTaskSupervisor.mStoppingActivities.remove(this);
         mLetterboxUiController.destroy();
-        waitingToShow = false;
 
         // Defer removal of this activity when either a child is animating, or app transition is on
         // going. App transition animation might be applied on the parent task not on the activity,
@@ -5386,7 +5387,6 @@
         final DisplayContent displayContent = getDisplayContent();
         displayContent.mOpeningApps.remove(this);
         displayContent.mClosingApps.remove(this);
-        waitingToShow = false;
         setVisibleRequested(visible);
         mLastDeferHidingClient = deferHidingClient;
 
@@ -5411,25 +5411,16 @@
             // stopped, then we need to set up to wait for its windows to be ready.
             if (!isVisible() || mAppStopped) {
                 clearAllDrawn();
-
-                // If the app was already visible, don't reset the waitingToShow state.
-                if (!isVisible()) {
-                    waitingToShow = true;
-
-                    // If the client isn't hidden, we don't need to reset the drawing state.
-                    if (!isClientVisible()) {
-                        // Let's reset the draw state in order to prevent the starting window to be
-                        // immediately dismissed when the app still has the surface.
-                        forAllWindows(w -> {
-                            if (w.mWinAnimator.mDrawState == HAS_DRAWN) {
-                                w.mWinAnimator.resetDrawState();
-
-                                // Force add to mResizingWindows, so that we are guaranteed to get
-                                // another reportDrawn callback.
-                                w.forceReportingResized();
-                            }
-                        }, true /* traverseTopToBottom */);
-                    }
+                // Reset the draw state in order to prevent the starting window to be immediately
+                // dismissed when the app still has the surface.
+                if (!isVisible() && !isClientVisible()) {
+                    forAllWindows(w -> {
+                        if (w.mWinAnimator.mDrawState == HAS_DRAWN) {
+                            w.mWinAnimator.resetDrawState();
+                            // Force add to mResizingWindows, so the window will report drawn.
+                            w.forceReportingResized();
+                        }
+                    }, true /* traverseTopToBottom */);
                 }
             }
 
@@ -10341,6 +10332,10 @@
                 mLetterboxUiController.shouldRefreshActivityViaPauseForCameraCompat());
         proto.write(SHOULD_OVERRIDE_MIN_ASPECT_RATIO,
                 mLetterboxUiController.shouldOverrideMinAspectRatio());
+        proto.write(SHOULD_IGNORE_ORIENTATION_REQUEST_LOOP,
+                mLetterboxUiController.shouldIgnoreOrientationRequestLoop());
+        proto.write(SHOULD_OVERRIDE_FORCE_RESIZE_APP,
+                mLetterboxUiController.shouldOverrideForceResizeApp());
     }
 
     @Override
diff --git a/services/core/java/com/android/server/wm/ActivityStarter.java b/services/core/java/com/android/server/wm/ActivityStarter.java
index d90d017..13f7152 100644
--- a/services/core/java/com/android/server/wm/ActivityStarter.java
+++ b/services/core/java/com/android/server/wm/ActivityStarter.java
@@ -993,17 +993,6 @@
             }
         }
 
-        if (Flags.archiving()) {
-            PackageArchiver packageArchiver = mService
-                    .getPackageManagerInternalLocked()
-                    .getPackageArchiver();
-            if (packageArchiver.isIntentResolvedToArchivedApp(intent, mRequest.userId)) {
-                return packageArchiver
-                        .requestUnarchiveOnActivityStart(
-                                intent, callingPackage, mRequest.userId, realCallingUid);
-            }
-        }
-
         final int launchFlags = intent.getFlags();
         if ((launchFlags & Intent.FLAG_ACTIVITY_FORWARD_RESULT) != 0 && sourceRecord != null) {
             // Transfer the result target from the source activity to the new one being started,
@@ -1045,6 +1034,17 @@
         }
 
         if (err == ActivityManager.START_SUCCESS && aInfo == null) {
+            if (Flags.archiving()) {
+                PackageArchiver packageArchiver = mService
+                        .getPackageManagerInternalLocked()
+                        .getPackageArchiver();
+                if (packageArchiver.isIntentResolvedToArchivedApp(intent, mRequest.userId)) {
+                    return packageArchiver
+                            .requestUnarchiveOnActivityStart(
+                                    intent, callingPackage, mRequest.userId, realCallingUid);
+                }
+            }
+
             // We couldn't find the specific class specified in the Intent.
             // Also the end of the line.
             err = ActivityManager.START_CLASS_NOT_FOUND;
diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
index f43c1b0..3959a5e 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
@@ -3691,19 +3691,13 @@
             return false;
         }
 
-        // If the app is using legacy-entry (not auto-enter), then we will get a client-request
-        // that was actually a server-request (via pause(userLeaving=true)). This happens when
-        // the app is PAUSING, so detect that case here.
-        boolean originallyFromClient = fromClient
-                && (!r.isState(PAUSING) || params.isAutoEnterEnabled());
-
-        // If PiP2 flag is on and client-request to enter PiP came via onUserLeaveHint(),
-        // we request a direct transition from Shell to TRANSIT_PIP_LEGACY to get the startWct
-        // with the right entry bounds.
-        if (isPip2ExperimentEnabled() && !originallyFromClient && !params.isAutoEnterEnabled()) {
+        // If PiP2 flag is on and client-request to enter PiP comes in,
+        // we request a direct transition from Shell to TRANSIT_PIP to get the startWct
+        // with the right entry bounds. So PiP activity isn't moved to a pinned task until after
+        // Shell calls back into Core with the entry bounds passed through.
+        if (isPip2ExperimentEnabled()) {
             final Transition legacyEnterPipTransition = new Transition(TRANSIT_PIP,
-                    0 /* flags */, getTransitionController(),
-                    mWindowManager.mSyncEngine);
+                    0 /* flags */, getTransitionController(), mWindowManager.mSyncEngine);
             legacyEnterPipTransition.setPipActivity(r);
             getTransitionController().startCollectOrQueue(legacyEnterPipTransition, (deferred) -> {
                 getTransitionController().requestStartTransition(legacyEnterPipTransition,
@@ -3712,6 +3706,12 @@
             return true;
         }
 
+        // If the app is using legacy-entry (not auto-enter), then we will get a client-request
+        // that was actually a server-request (via pause(userLeaving=true)). This happens when
+        // the app is PAUSING, so detect that case here.
+        boolean originallyFromClient = fromClient
+                && (!r.isState(PAUSING) || params.isAutoEnterEnabled());
+
         // Create a transition only for this pip entry if it is coming from the app without the
         // system requesting that the app enter-pip. If the system requested it, that means it
         // should be part of that transition if possible.
diff --git a/services/core/java/com/android/server/wm/AppTransitionController.java b/services/core/java/com/android/server/wm/AppTransitionController.java
index 05087f8..939babc 100644
--- a/services/core/java/com/android/server/wm/AppTransitionController.java
+++ b/services/core/java/com/android/server/wm/AppTransitionController.java
@@ -1176,7 +1176,6 @@
                 mDisplayContent.mNoAnimationNotifyOnTransitionFinished.add(app.token);
             }
             app.updateReportedVisibilityLocked();
-            app.waitingToShow = false;
             app.showAllWindowsLocked();
 
             if (mDisplayContent.mAppTransition.isNextAppTransitionThumbnailUp()) {
diff --git a/services/core/java/com/android/server/wm/BackgroundActivityStartController.java b/services/core/java/com/android/server/wm/BackgroundActivityStartController.java
index eed46fe..fc3a338 100644
--- a/services/core/java/com/android/server/wm/BackgroundActivityStartController.java
+++ b/services/core/java/com/android/server/wm/BackgroundActivityStartController.java
@@ -61,6 +61,7 @@
 
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.util.FrameworkStatsLog;
+import com.android.internal.util.Preconditions;
 import com.android.server.UiThread;
 import com.android.server.am.PendingIntentRecord;
 import com.android.window.flags.Flags;
@@ -219,6 +220,9 @@
         private final WindowProcessController mCallerApp;
         private final WindowProcessController mRealCallerApp;
         private final boolean mIsCallForResult;
+        private final ActivityOptions mCheckedOptions;
+        private BalVerdict mResultForCaller;
+        private BalVerdict mResultForRealCaller;
 
         private BalState(int callingUid, int callingPid, final String callingPackage,
                  int realCallingUid, int realCallingPid,
@@ -239,6 +243,7 @@
             mIntent = intent;
             mRealCallingPackage = mService.getPackageNameIfUnique(realCallingUid, realCallingPid);
             mIsCallForResult = resultRecord != null;
+            mCheckedOptions = checkedOptions;
             if (balRequireOptInByPendingIntentCreator() // auto-opt in introduced with this feature
                     && (originatingPendingIntent == null // not a PendingIntent
                     || mIsCallForResult) // sent for result
@@ -369,8 +374,19 @@
             return mCallingUid == mRealCallingUid;
         }
 
-        private String dump(BalVerdict resultIfPiCreatorAllowsBal,
-                            BalVerdict resultIfPiSenderAllowsBal) {
+        public void setResultForCaller(BalVerdict resultForCaller) {
+            Preconditions.checkState(mResultForCaller == null,
+                    "mResultForCaller can only be set once");
+            this.mResultForCaller = resultForCaller;
+        }
+
+        public void setResultForRealCaller(BalVerdict resultForRealCaller) {
+            Preconditions.checkState(mResultForRealCaller == null,
+                    "mResultForRealCaller can only be set once");
+            this.mResultForRealCaller = resultForRealCaller;
+        }
+
+        private String dump() {
             StringBuilder sb = new StringBuilder(2048);
             sb.append("[callingPackage: ")
                     .append(getDebugPackageName(mCallingPackage, mCallingUid));
@@ -392,7 +408,7 @@
             sb.append("; balAllowedByPiCreator: ").append(mBalAllowedByPiCreator);
             sb.append("; balAllowedByPiCreatorWithHardening: ")
                     .append(mBalAllowedByPiCreatorWithHardening);
-            sb.append("; resultIfPiCreatorAllowsBal: ").append(resultIfPiCreatorAllowsBal);
+            sb.append("; resultIfPiCreatorAllowsBal: ").append(mResultForCaller);
             sb.append("; hasRealCaller: ").append(hasRealCaller());
             sb.append("; isCallForResult: ").append(mIsCallForResult);
             sb.append("; isPendingIntent: ").append(isPendingIntent());
@@ -416,7 +432,7 @@
                             .append(mRealCallerApp.hasActivityInVisibleTask());
                 }
                 sb.append("; balAllowedByPiSender: ").append(mBalAllowedByPiSender);
-                sb.append("; resultIfPiSenderAllowsBal: ").append(resultIfPiSenderAllowsBal);
+                sb.append("; resultIfPiSenderAllowsBal: ").append(mResultForRealCaller);
             }
             sb.append("]");
             return sb.toString();
@@ -559,23 +575,25 @@
             // realCallingSdkSandboxUidToAppUid should probably just be used instead (or in addition
             // to realCallingUid when calculating resultForRealCaller below.
             if (mService.hasActiveVisibleWindow(realCallingSdkSandboxUidToAppUid)) {
-                BalVerdict balVerdict = new BalVerdict(BAL_ALLOW_SDK_SANDBOX, /*background*/ false,
-                        "uid in SDK sandbox has visible (non-toast) window");
-                return statsLog(balVerdict, state);
+                state.setResultForRealCaller(new BalVerdict(BAL_ALLOW_SDK_SANDBOX,
+                        /*background*/ false,
+                        "uid in SDK sandbox has visible (non-toast) window"));
+                return allowBasedOnRealCaller(state);
             }
         }
 
         BalVerdict resultForCaller = checkBackgroundActivityStartAllowedByCaller(state);
+        state.setResultForCaller(resultForCaller);
 
         if (!state.hasRealCaller()) {
             if (resultForCaller.allows()) {
                 if (DEBUG_ACTIVITY_STARTS) {
                     Slog.d(TAG, "Background activity start allowed. "
-                            + state.dump(resultForCaller, resultForCaller));
+                            + state.dump());
                 }
-                return statsLog(resultForCaller, state);
+                return allowBasedOnCaller(state);
             }
-            return abortLaunch(state, resultForCaller, resultForCaller);
+            return abortLaunch(state);
         }
 
         // The realCaller result is only calculated for PendingIntents (indicated by a valid
@@ -589,6 +607,8 @@
                 ? resultForCaller
                 : checkBackgroundActivityStartAllowedBySender(state, checkedOptions)
                         .setBasedOnRealCaller();
+        state.setResultForRealCaller(resultForRealCaller);
+
         if (state.isPendingIntent()) {
             resultForCaller.setOnlyCreatorAllows(
                     resultForCaller.allows() && resultForRealCaller.blocks());
@@ -600,18 +620,18 @@
                 == ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED) {
             if (DEBUG_ACTIVITY_STARTS) {
                 Slog.d(TAG, "Activity start explicitly allowed by caller. "
-                        + state.dump(resultForCaller, resultForRealCaller));
+                        + state.dump());
             }
-            return statsLog(resultForCaller, state);
+            return allowBasedOnCaller(state);
         }
         if (resultForRealCaller.allows()
                 && checkedOptions.getPendingIntentBackgroundActivityStartMode()
                 == ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED) {
             if (DEBUG_ACTIVITY_STARTS) {
                 Slog.d(TAG, "Activity start explicitly allowed by real caller. "
-                        + state.dump(resultForCaller, resultForRealCaller));
+                        + state.dump());
             }
-            return statsLog(resultForRealCaller, state);
+            return allowBasedOnRealCaller(state);
         }
         // Handle PendingIntent cases with default behavior next
         boolean callerCanAllow = resultForCaller.allows()
@@ -626,26 +646,24 @@
                 // Will be allowed even with BAL hardening.
                 if (DEBUG_ACTIVITY_STARTS) {
                     Slog.d(TAG, "Activity start allowed by caller. "
-                            + state.dump(resultForCaller, resultForRealCaller));
+                            + state.dump());
                 }
-                // return the realCaller result for backwards compatibility
-                return statsLog(resultForRealCaller, state);
+                return allowBasedOnCaller(state);
             }
             if (state.mBalAllowedByPiCreator.allowsBackgroundActivityStarts()) {
                 Slog.wtf(TAG,
                         "With Android 15 BAL hardening this activity start may be blocked"
                                 + " if the PI creator upgrades target_sdk to 35+"
                                 + " AND the PI sender upgrades target_sdk to 34+! "
-                                + state.dump(resultForCaller, resultForRealCaller));
+                                + state.dump());
                 showBalRiskToast();
-                // return the realCaller result for backwards compatibility
-                return statsLog(resultForRealCaller, state);
+                return allowBasedOnCaller(state);
             }
             Slog.wtf(TAG,
                     "Without Android 15 BAL hardening this activity start would be allowed"
                             + " (missing opt in by PI creator or sender)! "
-                            + state.dump(resultForCaller, resultForRealCaller));
-            return abortLaunch(state, resultForCaller, resultForRealCaller);
+                            + state.dump());
+            return abortLaunch(state);
         }
         if (callerCanAllow) {
             // Allowed before V by creator
@@ -653,24 +671,24 @@
                 // Will be allowed even with BAL hardening.
                 if (DEBUG_ACTIVITY_STARTS) {
                     Slog.d(TAG, "Activity start allowed by caller. "
-                            + state.dump(resultForCaller, resultForRealCaller));
+                            + state.dump());
                 }
-                return statsLog(resultForCaller, state);
+                return allowBasedOnCaller(state);
             }
             if (state.mBalAllowedByPiCreator.allowsBackgroundActivityStarts()) {
                 Slog.wtf(TAG,
                         "With Android 15 BAL hardening this activity start may be blocked"
                                 + " if the PI creator upgrades target_sdk to 35+! "
                                 + " (missing opt in by PI creator)! "
-                                + state.dump(resultForCaller, resultForRealCaller));
+                                + state.dump());
                 showBalRiskToast();
-                return statsLog(resultForCaller, state);
+                return allowBasedOnCaller(state);
             }
             Slog.wtf(TAG,
                     "Without Android 15 BAL hardening this activity start would be allowed"
                             + " (missing opt in by PI creator)! "
-                            + state.dump(resultForCaller, resultForRealCaller));
-            return abortLaunch(state, resultForCaller, resultForRealCaller);
+                            + state.dump());
+            return abortLaunch(state);
         }
         if (realCallerCanAllow) {
             // Allowed before U by sender
@@ -679,23 +697,38 @@
                         "With Android 14 BAL hardening this activity start will be blocked"
                                 + " if the PI sender upgrades target_sdk to 34+! "
                                 + " (missing opt in by PI sender)! "
-                                + state.dump(resultForCaller, resultForRealCaller));
+                                + state.dump());
                 showBalRiskToast();
-                return statsLog(resultForRealCaller, state);
+                return allowBasedOnRealCaller(state);
             }
             Slog.wtf(TAG, "Without Android 14 BAL hardening this activity start would be allowed"
                     + " (missing opt in by PI sender)! "
-                    + state.dump(resultForCaller, resultForRealCaller));
-            return abortLaunch(state, resultForCaller, resultForRealCaller);
+                    + state.dump());
+            return abortLaunch(state);
         }
         // neither the caller not the realCaller can allow or have explicitly opted out
-        return abortLaunch(state, resultForCaller, resultForRealCaller);
+        return abortLaunch(state);
     }
 
-    private BalVerdict abortLaunch(BalState state, BalVerdict resultForCaller,
-            BalVerdict resultForRealCaller) {
+    private BalVerdict allowBasedOnCaller(BalState state) {
+        if (DEBUG_ACTIVITY_STARTS) {
+            Slog.d(TAG, "Background activity launch allowed based on caller. "
+                    + state.dump());
+        }
+        return statsLog(state.mResultForCaller, state);
+    }
+
+    private BalVerdict allowBasedOnRealCaller(BalState state) {
+        if (DEBUG_ACTIVITY_STARTS) {
+            Slog.d(TAG, "Background activity launch allowed based on real caller. "
+                    + state.dump());
+        }
+        return statsLog(state.mResultForRealCaller, state);
+    }
+
+    private BalVerdict abortLaunch(BalState state) {
         Slog.w(TAG, "Background activity launch blocked! "
-                + state.dump(resultForCaller, resultForRealCaller));
+                + state.dump());
         showBalBlockedToast();
         return statsLog(BalVerdict.BLOCK, state);
     }
@@ -1471,24 +1504,36 @@
                 && (callingUid == Process.SYSTEM_UID || realCallingUid == Process.SYSTEM_UID)) {
             String activityName = intent != null
                     ? requireNonNull(intent.getComponent()).flattenToShortString() : "";
-            FrameworkStatsLog.write(FrameworkStatsLog.BAL_ALLOWED,
-                    activityName,
-                    BAL_ALLOW_PENDING_INTENT,
-                    callingUid,
-                    realCallingUid);
+            writeBalAllowedLog(activityName, BAL_ALLOW_PENDING_INTENT,
+                    state);
         }
         if (code == BAL_ALLOW_PERMISSION || code == BAL_ALLOW_FOREGROUND
-                    || code == BAL_ALLOW_SAW_PERMISSION) {
+                || code == BAL_ALLOW_SAW_PERMISSION) {
             // We don't need to know which activity in this case.
-            FrameworkStatsLog.write(FrameworkStatsLog.BAL_ALLOWED,
-                    /*activityName*/ "",
-                    code,
-                    callingUid,
-                    realCallingUid);
+            writeBalAllowedLog("", code, state);
+
         }
         return finalVerdict;
     }
 
+    private static void writeBalAllowedLog(String activityName, int code, BalState state) {
+        FrameworkStatsLog.write(FrameworkStatsLog.BAL_ALLOWED,
+                activityName,
+                code,
+                state.mCallingUid,
+                state.mRealCallingUid,
+                state.mResultForCaller == null ? BAL_BLOCK : state.mResultForCaller.getRawCode(),
+                state.mBalAllowedByPiCreator.allowsBackgroundActivityStarts(),
+                state.mCheckedOptions.getPendingIntentCreatorBackgroundActivityStartMode()
+                        != ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_SYSTEM_DEFINED,
+                state.mResultForRealCaller == null ? BAL_BLOCK
+                        : state.mResultForRealCaller.getRawCode(),
+                state.mBalAllowedByPiSender.allowsBackgroundActivityStarts(),
+                state.mCheckedOptions.getPendingIntentBackgroundActivityStartMode()
+                        != ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_SYSTEM_DEFINED
+        );
+    }
+
     /**
      * Called whenever an activity finishes. Stores the record, so it can be used by ASM grace
      * period checks.
diff --git a/services/core/java/com/android/server/wm/LetterboxUiController.java b/services/core/java/com/android/server/wm/LetterboxUiController.java
index 97cc982..47972b3 100644
--- a/services/core/java/com/android/server/wm/LetterboxUiController.java
+++ b/services/core/java/com/android/server/wm/LetterboxUiController.java
@@ -173,7 +173,7 @@
     // Corresponds to OVERRIDE_ANY_ORIENTATION
     private final boolean mIsOverrideAnyOrientationEnabled;
     // Corresponds to OVERRIDE_ANY_ORIENTATION_TO_USER
-    private final boolean mIsOverrideToUserOrientationEnabled;
+    private final boolean mIsSystemOverrideToFullscreenEnabled;
     // Corresponds to OVERRIDE_UNDEFINED_ORIENTATION_TO_PORTRAIT
     private final boolean mIsOverrideToPortraitOrientationEnabled;
     // Corresponds to OVERRIDE_UNDEFINED_ORIENTATION_TO_NOSENSOR
@@ -358,7 +358,7 @@
                         PROPERTY_COMPAT_ALLOW_USER_ASPECT_RATIO_FULLSCREEN_OVERRIDE);
 
         mIsOverrideAnyOrientationEnabled = isCompatChangeEnabled(OVERRIDE_ANY_ORIENTATION);
-        mIsOverrideToUserOrientationEnabled =
+        mIsSystemOverrideToFullscreenEnabled =
                 isCompatChangeEnabled(OVERRIDE_ANY_ORIENTATION_TO_USER);
         mIsOverrideToPortraitOrientationEnabled =
                 isCompatChangeEnabled(OVERRIDE_UNDEFINED_ORIENTATION_TO_PORTRAIT);
@@ -513,7 +513,6 @@
      *     timer and activity is not letterboxed for fixed orientation
      * </ul>
      */
-    @VisibleForTesting
     boolean shouldIgnoreOrientationRequestLoop() {
         if (!shouldEnableWithOptInOverrideAndOptOutProperty(
                 /* gatingCondition */ mLetterboxConfiguration
@@ -671,8 +670,7 @@
         final DisplayContent displayContent = mActivityRecord.mDisplayContent;
         final boolean isIgnoreOrientationRequestEnabled = displayContent != null
                 && displayContent.getIgnoreOrientationRequest();
-        if (shouldApplyUserFullscreenOverride()
-                && isIgnoreOrientationRequestEnabled) {
+        if (shouldApplyUserFullscreenOverride() && isIgnoreOrientationRequestEnabled) {
             Slog.v(TAG, "Requested orientation " + screenOrientationToString(candidate) + " for "
                     + mActivityRecord + " is overridden to "
                     + screenOrientationToString(SCREEN_ORIENTATION_USER)
@@ -707,8 +705,7 @@
         // mUserAspectRatio is always initialized first in shouldApplyUserFullscreenOverride(),
         // which will always come first before this check as user override > device
         // manufacturer override.
-        if (mUserAspectRatio == PackageManager.USER_MIN_ASPECT_RATIO_UNSET
-                && mIsOverrideToUserOrientationEnabled && isIgnoreOrientationRequestEnabled) {
+        if (isSystemOverrideToFullscreenEnabled() && isIgnoreOrientationRequestEnabled) {
             Slog.v(TAG, "Requested orientation  " + screenOrientationToString(candidate) + " for "
                     + mActivityRecord + " is overridden to "
                     + screenOrientationToString(SCREEN_ORIENTATION_USER));
@@ -1202,6 +1199,13 @@
         return mUserAspectRatio == USER_MIN_ASPECT_RATIO_FULLSCREEN;
     }
 
+    boolean isSystemOverrideToFullscreenEnabled() {
+        return mIsSystemOverrideToFullscreenEnabled
+                && !FALSE.equals(mBooleanPropertyAllowOrientationOverride)
+                && (mUserAspectRatio == USER_MIN_ASPECT_RATIO_UNSET
+                    || mUserAspectRatio == USER_MIN_ASPECT_RATIO_FULLSCREEN);
+    }
+
     float getUserMinAspectRatio() {
         switch (mUserAspectRatio) {
             case USER_MIN_ASPECT_RATIO_DISPLAY_SIZE:
diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java
index c674176..d556f09 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -3506,6 +3506,8 @@
         appCompatTaskInfo.topActivityLetterboxHeight = TaskInfo.PROPERTY_VALUE_UNSET;
         appCompatTaskInfo.isUserFullscreenOverrideEnabled = top != null
                 && top.mLetterboxUiController.shouldApplyUserFullscreenOverride();
+        appCompatTaskInfo.isSystemFullscreenOverrideEnabled = top != null
+                && top.mLetterboxUiController.isSystemOverrideToFullscreenEnabled();
         appCompatTaskInfo.isFromLetterboxDoubleTap = top != null
                 && top.mLetterboxUiController.isFromDoubleTap();
         if (appCompatTaskInfo.isLetterboxDoubleTapEnabled) {
diff --git a/services/core/java/com/android/server/wm/TaskFragment.java b/services/core/java/com/android/server/wm/TaskFragment.java
index d425bdf..f51bd1b 100644
--- a/services/core/java/com/android/server/wm/TaskFragment.java
+++ b/services/core/java/com/android/server/wm/TaskFragment.java
@@ -16,6 +16,7 @@
 
 package com.android.server.wm;
 
+import static android.Manifest.permission.MANAGE_ACTIVITY_TASKS;
 import static android.app.ActivityTaskManager.INVALID_TASK_ID;
 import static android.app.WindowConfiguration.ACTIVITY_TYPE_ASSISTANT;
 import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
@@ -57,6 +58,7 @@
 import static com.android.server.wm.ActivityTaskManagerDebugConfig.POSTFIX_TRANSITION;
 import static com.android.server.wm.ActivityTaskManagerDebugConfig.TAG_ATM;
 import static com.android.server.wm.ActivityTaskManagerDebugConfig.TAG_WITH_CLASS_NAME;
+import static com.android.server.wm.ActivityTaskManagerService.checkPermission;
 import static com.android.server.wm.ActivityTaskSupervisor.printThisActivity;
 import static com.android.server.wm.IdentifierProto.HASH_CODE;
 import static com.android.server.wm.IdentifierProto.TITLE;
@@ -80,7 +82,9 @@
 import android.app.servertransaction.NewIntentItem;
 import android.app.servertransaction.PauseActivityItem;
 import android.app.servertransaction.ResumeActivityItem;
+import android.content.PermissionChecker;
 import android.content.pm.ActivityInfo;
+import android.content.pm.PackageManager;
 import android.content.res.Configuration;
 import android.graphics.Point;
 import android.graphics.Rect;
@@ -744,7 +748,17 @@
         // The system is trusted to embed other apps securely and for all users.
         return UserHandle.getAppId(uid) == SYSTEM_UID
                 // Activities from the same UID can be embedded freely by the host.
-                || a.isUid(uid);
+                || a.isUid(uid)
+                // Apps which have the signature MANAGE_ACTIVITY_TASK permission are trusted.
+                || hasManageTaskPermission(uid);
+    }
+
+    /**
+     * Checks if a particular app uid has the {@link MANAGE_ACTIVITY_TASKS} permission.
+     */
+    private static boolean hasManageTaskPermission(int uid) {
+        return checkPermission(MANAGE_ACTIVITY_TASKS, PermissionChecker.PID_UNKNOWN, uid)
+                == PackageManager.PERMISSION_GRANTED;
     }
 
     /**
@@ -926,10 +940,14 @@
     boolean sleepIfPossible(boolean shuttingDown) {
         boolean shouldSleep = true;
         if (mResumedActivity != null) {
-            // Still have something resumed; can't sleep until it is paused.
-            ProtoLog.v(WM_DEBUG_STATES, "Sleep needs to pause %s", mResumedActivity);
-            startPausing(false /* userLeaving */, true /* uiSleeping */, null /* resuming */,
-                    "sleep");
+            if (!shuttingDown && mResumedActivity.canTurnScreenOn()) {
+                ProtoLog.v(WM_DEBUG_STATES, "Waiting for screen on due to %s", mResumedActivity);
+            } else {
+                // Still have something resumed; can't sleep until it is paused.
+                ProtoLog.v(WM_DEBUG_STATES, "Sleep needs to pause %s", mResumedActivity);
+                startPausing(false /* userLeaving */, true /* uiSleeping */, null /* resuming */,
+                        "sleep");
+            }
             shouldSleep = false;
         } else if (mPausingActivity != null) {
             // Still waiting for something to pause; can't sleep yet.
@@ -2980,7 +2998,7 @@
     @Override
     Dimmer getDimmer() {
         // If this is in an embedded TaskFragment and we want the dim applies on the TaskFragment.
-        if (mIsEmbedded && mEmbeddedDimArea == EMBEDDED_DIM_AREA_TASK_FRAGMENT) {
+        if (mIsEmbedded && !isDimmingOnParentTask()) {
             return mDimmer;
         }
 
@@ -2989,7 +3007,9 @@
 
     /** Bounds to be used for dimming, as well as touch related tests. */
     void getDimBounds(@NonNull Rect out) {
-        if (mIsEmbedded && mEmbeddedDimArea == EMBEDDED_DIM_AREA_PARENT_TASK) {
+        if (mIsEmbedded && isDimmingOnParentTask() && getDimmer().getDimBounds() != null) {
+            // Return the task bounds if the dimmer is showing and should cover on the Task (not
+            // just on this embedded TaskFragment).
             out.set(getTask().getBounds());
         } else {
             out.set(getBounds());
@@ -3000,6 +3020,11 @@
         mEmbeddedDimArea = embeddedDimArea;
     }
 
+    @VisibleForTesting
+    boolean isDimmingOnParentTask() {
+        return mEmbeddedDimArea == EMBEDDED_DIM_AREA_PARENT_TASK;
+    }
+
     @Override
     void prepareSurfaces() {
         if (asTask() != null) {
diff --git a/services/core/java/com/android/server/wm/Transition.java b/services/core/java/com/android/server/wm/Transition.java
index b12855e..56bef33 100644
--- a/services/core/java/com/android/server/wm/Transition.java
+++ b/services/core/java/com/android/server/wm/Transition.java
@@ -1906,7 +1906,6 @@
         for (int i = mParticipants.size() - 1; i >= 0; --i) {
             final WallpaperWindowToken wallpaper = mParticipants.valueAt(i).asWallpaperToken();
             if (wallpaper != null) {
-                wallpaper.waitingToShow = false;
                 if (!wallpaper.isVisible() && wallpaper.isVisibleRequested()) {
                     wallpaper.commitVisibility(showWallpaper);
                 }
diff --git a/services/core/java/com/android/server/wm/WindowAnimator.java b/services/core/java/com/android/server/wm/WindowAnimator.java
index c4e1d6e..750fd50 100644
--- a/services/core/java/com/android/server/wm/WindowAnimator.java
+++ b/services/core/java/com/android/server/wm/WindowAnimator.java
@@ -148,7 +148,8 @@
 
                 dc.checkAppWindowsReadyToShow();
                 if (accessibilityController.hasCallbacks()) {
-                    accessibilityController.drawMagnifiedRegionBorderIfNeeded(dc.mDisplayId);
+                    accessibilityController.drawMagnifiedRegionBorderIfNeeded(dc.mDisplayId,
+                            mTransaction);
                 }
 
                 if (dc.isAnimating(animationFlags, ANIMATION_TYPE_ALL)) {
diff --git a/services/core/java/com/android/server/wm/WindowOrganizerController.java b/services/core/java/com/android/server/wm/WindowOrganizerController.java
index 9e4a31c..59d0210 100644
--- a/services/core/java/com/android/server/wm/WindowOrganizerController.java
+++ b/services/core/java/com/android/server/wm/WindowOrganizerController.java
@@ -34,6 +34,7 @@
 import static android.window.TaskFragmentOperation.OP_TYPE_SET_ADJACENT_TASK_FRAGMENTS;
 import static android.window.TaskFragmentOperation.OP_TYPE_SET_ANIMATION_PARAMS;
 import static android.window.TaskFragmentOperation.OP_TYPE_SET_COMPANION_TASK_FRAGMENT;
+import static android.window.TaskFragmentOperation.OP_TYPE_SET_DIM_ON_TASK;
 import static android.window.TaskFragmentOperation.OP_TYPE_SET_ISOLATED_NAVIGATION;
 import static android.window.TaskFragmentOperation.OP_TYPE_SET_RELATIVE_BOUNDS;
 import static android.window.TaskFragmentOperation.OP_TYPE_START_ACTIVITY_IN_TASK_FRAGMENT;
@@ -68,6 +69,8 @@
 import static com.android.server.wm.ActivityTaskSupervisor.REMOVE_FROM_RECENTS;
 import static com.android.server.wm.Task.FLAG_FORCE_HIDDEN_FOR_PINNED_TASK;
 import static com.android.server.wm.Task.FLAG_FORCE_HIDDEN_FOR_TASK_ORG;
+import static com.android.server.wm.TaskFragment.EMBEDDED_DIM_AREA_PARENT_TASK;
+import static com.android.server.wm.TaskFragment.EMBEDDED_DIM_AREA_TASK_FRAGMENT;
 import static com.android.server.wm.TaskFragment.EMBEDDING_ALLOWED;
 import static com.android.server.wm.TaskFragment.FLAG_FORCE_HIDDEN_FOR_TASK_FRAGMENT_ORG;
 import static com.android.server.wm.WindowContainer.POSITION_BOTTOM;
@@ -1493,6 +1496,12 @@
                 task.removeDecorSurface();
                 break;
             }
+            case OP_TYPE_SET_DIM_ON_TASK: {
+                final boolean dimOnTask = operation.isDimOnTask();
+                taskFragment.setEmbeddedDimArea(dimOnTask ? EMBEDDED_DIM_AREA_PARENT_TASK
+                        : EMBEDDED_DIM_AREA_TASK_FRAGMENT);
+                break;
+            }
         }
         return effects;
     }
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index 315c00f..0b43be7 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -1929,9 +1929,6 @@
      * of a transition that has not yet been started.
      */
     boolean isReadyForDisplay() {
-        if (mToken.waitingToShow && getDisplayContent().mAppTransition.isTransitionSet()) {
-            return false;
-        }
         final boolean parentAndClientVisible = !isParentWindowHidden()
                 && mViewVisibility == View.VISIBLE && mToken.isVisible();
         return mHasSurface && isVisibleByPolicy() && !mDestroying
diff --git a/services/core/java/com/android/server/wm/WindowToken.java b/services/core/java/com/android/server/wm/WindowToken.java
index 7d21dbf..5048cef 100644
--- a/services/core/java/com/android/server/wm/WindowToken.java
+++ b/services/core/java/com/android/server/wm/WindowToken.java
@@ -28,7 +28,6 @@
 import static com.android.server.wm.WindowManagerService.UPDATE_FOCUS_NORMAL;
 import static com.android.server.wm.WindowTokenProto.HASH_CODE;
 import static com.android.server.wm.WindowTokenProto.PAUSED;
-import static com.android.server.wm.WindowTokenProto.WAITING_TO_SHOW;
 import static com.android.server.wm.WindowTokenProto.WINDOW_CONTAINER;
 
 import android.annotation.CallSuper;
@@ -91,10 +90,6 @@
     // Is key dispatching paused for this token?
     boolean paused = false;
 
-    // Set to true when this token is in a pending transaction where it
-    // will be shown.
-    boolean waitingToShow;
-
     /** The owner has {@link android.Manifest.permission#MANAGE_APP_TOKENS} */
     final boolean mOwnerCanManageAppTokens;
 
@@ -702,7 +697,6 @@
         final long token = proto.start(fieldId);
         super.dumpDebug(proto, WINDOW_CONTAINER, logLevel);
         proto.write(HASH_CODE, System.identityHashCode(this));
-        proto.write(WAITING_TO_SHOW, waitingToShow);
         proto.write(PAUSED, paused);
         proto.end(token);
     }
@@ -716,9 +710,6 @@
         super.dump(pw, prefix, dumpAll);
         pw.print(prefix); pw.print("windows="); pw.println(mChildren);
         pw.print(prefix); pw.print("windowType="); pw.print(windowType);
-        if (waitingToShow) {
-            pw.print(" waitingToShow=true");
-        }
         pw.println();
         if (hasFixedRotationTransform()) {
             pw.print(prefix);
diff --git a/services/core/jni/Android.bp b/services/core/jni/Android.bp
index b19f3d8..dfa9dce 100644
--- a/services/core/jni/Android.bp
+++ b/services/core/jni/Android.bp
@@ -79,6 +79,7 @@
         ":lib_cachedAppOptimizer_native",
         ":lib_gameManagerService_native",
         ":lib_oomConnection_native",
+        ":lib_anrTimer_native",
     ],
 
     include_dirs: [
@@ -246,3 +247,10 @@
     name: "lib_oomConnection_native",
     srcs: ["com_android_server_am_OomConnection.cpp"],
 }
+
+filegroup {
+    name: "lib_anrTimer_native",
+    srcs: [
+        "com_android_server_utils_AnrTimer.cpp",
+    ],
+}
diff --git a/services/core/jni/com_android_server_input_InputManagerService.cpp b/services/core/jni/com_android_server_input_InputManagerService.cpp
index 9ba0a2a..afb0b20 100644
--- a/services/core/jni/com_android_server_input_InputManagerService.cpp
+++ b/services/core/jni/com_android_server_input_InputManagerService.cpp
@@ -114,6 +114,7 @@
     jmethodID notifyFocusChanged;
     jmethodID notifySensorEvent;
     jmethodID notifySensorAccuracy;
+    jmethodID notifyStickyModifierStateChanged;
     jmethodID notifyStylusGestureStarted;
     jmethodID isInputMethodConnectionActive;
     jmethodID notifyVibratorState;
@@ -270,7 +271,8 @@
 class NativeInputManager : public virtual InputReaderPolicyInterface,
                            public virtual InputDispatcherPolicyInterface,
                            public virtual PointerControllerPolicyInterface,
-                           public virtual PointerChoreographerPolicyInterface {
+                           public virtual PointerChoreographerPolicyInterface,
+                           public virtual InputFilterPolicyInterface {
 protected:
     virtual ~NativeInputManager();
 
@@ -388,6 +390,10 @@
             PointerControllerInterface::ControllerType type) override;
     void notifyPointerDisplayIdChanged(int32_t displayId, const FloatPoint& position) override;
 
+    /* --- InputFilterPolicyInterface implementation --- */
+    void notifyStickyModifierStateChanged(uint32_t modifierState,
+                                          uint32_t lockedModifierState) override;
+
 private:
     sp<InputManagerInterface> mInputManager;
 
@@ -477,7 +483,7 @@
 
     mServiceObj = env->NewGlobalRef(serviceObj);
 
-    InputManager* im = new InputManager(this, *this, *this);
+    InputManager* im = new InputManager(this, *this, *this, *this);
     mInputManager = im;
     defaultServiceManager()->addService(String16("inputflinger"), im);
 }
@@ -806,6 +812,14 @@
     checkAndClearExceptionFromCallback(env, "onPointerDisplayIdChanged");
 }
 
+void NativeInputManager::notifyStickyModifierStateChanged(uint32_t modifierState,
+                                                          uint32_t lockedModifierState) {
+    JNIEnv* env = jniEnv();
+    env->CallVoidMethod(mServiceObj, gServiceClassInfo.notifyStickyModifierStateChanged,
+                        modifierState, lockedModifierState);
+    checkAndClearExceptionFromCallback(env, "notifyStickyModifierStateChanged");
+}
+
 sp<SurfaceControl> NativeInputManager::getParentSurfaceForPointers(int displayId) {
     JNIEnv* env = jniEnv();
     jlong nativeSurfaceControlPtr =
@@ -2957,6 +2971,9 @@
     GET_METHOD_ID(gServiceClassInfo.onPointerDisplayIdChanged, clazz, "onPointerDisplayIdChanged",
                   "(IFF)V");
 
+    GET_METHOD_ID(gServiceClassInfo.notifyStickyModifierStateChanged, clazz,
+                  "notifyStickyModifierStateChanged", "(II)V");
+
     GET_METHOD_ID(gServiceClassInfo.onPointerDownOutsideFocus, clazz,
             "onPointerDownOutsideFocus", "(Landroid/os/IBinder;)V");
 
diff --git a/services/core/jni/com_android_server_utils_AnrTimer.cpp b/services/core/jni/com_android_server_utils_AnrTimer.cpp
new file mode 100644
index 0000000..97b18fa
--- /dev/null
+++ b/services/core/jni/com_android_server_utils_AnrTimer.cpp
@@ -0,0 +1,918 @@
+/*
+ * 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.
+ */
+
+#include <time.h>
+#include <pthread.h>
+#include <sys/timerfd.h>
+#include <inttypes.h>
+
+#include <algorithm>
+#include <list>
+#include <memory>
+#include <set>
+#include <string>
+#include <vector>
+
+#define LOG_TAG "AnrTimerService"
+
+#include <jni.h>
+#include <nativehelper/JNIHelp.h>
+#include "android_runtime/AndroidRuntime.h"
+#include "core_jni_helpers.h"
+
+#include <utils/Mutex.h>
+#include <utils/Timers.h>
+
+#include <utils/Log.h>
+#include <utils/Timers.h>
+#include <android-base/logging.h>
+#include <android-base/stringprintf.h>
+#include <android-base/unique_fd.h>
+
+using ::android::base::StringPrintf;
+
+
+// Native support is unavailable on WIN32 platforms.  This macro preemptively disables it.
+#ifdef _WIN32
+#define NATIVE_SUPPORT 0
+#else
+#define NATIVE_SUPPORT 1
+#endif
+
+namespace android {
+
+// using namespace android;
+
+// Almost nothing in this module needs to be in the android namespace.
+namespace {
+
+// If not on a Posix system, create stub timerfd methods.  These are defined to allow
+// compilation.  They are not functional.  Also, they do not leak outside this compilation unit.
+#ifdef _WIN32
+int timer_create() {
+  return -1;
+}
+int timer_settime(int, int, void const *, void *) {
+  return -1;
+}
+#else
+int timer_create() {
+  return timerfd_create(CLOCK_MONOTONIC, TFD_CLOEXEC);
+}
+int timer_settime(int fd, int flags, const struct itimerspec *new_value,
+                  struct itimerspec *_Nullable old_value) {
+  return timerfd_settime(fd, flags, new_value, old_value);
+}
+#endif
+
+// A local debug flag that gates a set of log messages for debug only.  This is normally const
+// false so the debug statements are not included in the image.  The flag can be set true in a
+// unit test image to debug test failures.
+const bool DEBUG = false;
+
+// Return the current time in nanoseconds.  This time is relative to system boot.
+nsecs_t now() {
+    return systemTime(SYSTEM_TIME_MONOTONIC);
+}
+
+/**
+ * This class encapsulates the anr timer service.  The service manages a list of individual
+ * timers.  A timer is either Running or Expired.  Once started, a timer may be canceled or
+ * accepted.  Both actions collect statistics about the timer and then delete it.  An expired
+ * timer may also be discarded, which deletes the timer without collecting any statistics.
+ *
+ * All public methods in this class are thread-safe.
+ */
+class AnrTimerService {
+  private:
+    class ProcessStats;
+    class Timer;
+
+  public:
+
+    // The class that actually runs the clock.
+    class Ticker;
+
+    // A timer is identified by a timer_id_t.  Timer IDs are unique in the moment.
+    using timer_id_t = uint32_t;
+
+    // A manifest constant.  No timer is ever created with this ID.
+    static const timer_id_t NOTIMER = 0;
+
+    // A notifier is called with a timer ID, the timer's tag, and the client's cookie.  The pid
+    // and uid that were originally assigned to the timer are passed as well.
+    using notifier_t = bool (*)(timer_id_t, int pid, int uid, void* cookie, jweak object);
+
+    enum Status {
+        Invalid,
+        Running,
+        Expired,
+        Canceled
+    };
+
+    /**
+     * Create a timer service.  The service is initialized with a name used for logging.  The
+     * constructor is also given the notifier callback, and two cookies for the callback: the
+     * traditional void* and an int.
+     */
+    AnrTimerService(char const* label, notifier_t notifier, void* cookie, jweak jtimer, Ticker*);
+
+    // Delete the service and clean up memory.
+    ~AnrTimerService();
+
+    // Start a timer and return the associated timer ID.  It does not matter if the same pid/uid
+    // are already in the running list.  Once start() is called, one of cancel(), accept(), or
+    // discard() must be called to clean up the internal data structures.
+    timer_id_t start(int pid, int uid, nsecs_t timeout, bool extend);
+
+    // Cancel a timer and remove it from all lists.  This is called when the event being timed
+    // has occurred.  If the timer was Running, the function returns true.  The other
+    // possibilities are that the timer was Expired or non-existent; in both cases, the function
+    // returns false.
+    bool cancel(timer_id_t timerId);
+
+    // Accept a timer and remove it from all lists.  This is called when the upper layers accept
+    // that a timer has expired.  If the timer was Expired, the function returns true.  The
+    // other possibilities are tha the timer was Running or non-existing; in both cases, the
+    // function returns false.
+    bool accept(timer_id_t timerId);
+
+    // Discard a timer without collecting any statistics.  This is called when the upper layers
+    // recognize that a timer expired but decide the expiration is not significant.  If the
+    // timer was Expired, the function returns true.  The other possibilities are tha the timer
+    // was Running or non-existing; in both cases, the function returns false.
+    bool discard(timer_id_t timerId);
+
+    // A timer has expired.
+    void expire(timer_id_t);
+
+    // Dump a small amount of state to the log file.
+    void dump(bool verbose) const;
+
+    // Return the Java object associated with this instance.
+    jweak jtimer() const {
+        return notifierObject_;
+    }
+
+  private:
+    // The service cannot be copied.
+    AnrTimerService(AnrTimerService const &) = delete;
+
+    // Insert a timer into the running list.  The lock must be held by the caller.
+    void insert(const Timer&);
+
+    // Remove a timer from the lists and return it. The lock must be held by the caller.
+    Timer remove(timer_id_t timerId);
+
+    // Return a string representation of a status value.
+    static char const *statusString(Status);
+
+    // The name of this service, for logging.
+    std::string const label_;
+
+    // The callback that is invoked when a timer expires.
+    notifier_t const notifier_;
+
+    // The two cookies passed to the notifier.
+    void* notifierCookie_;
+    jweak notifierObject_;
+
+    // The global lock
+    mutable Mutex lock_;
+
+    // The list of all timers that are still running.  This is sorted by ID for fast lookup.
+    std::set<Timer> running_;
+
+    // The maximum number of active timers.
+    size_t maxActive_;
+
+    // Simple counters
+    struct Counters {
+        // The number of timers started, canceled, accepted, discarded, and expired.
+        size_t started;
+        size_t canceled;
+        size_t accepted;
+        size_t discarded;
+        size_t expired;
+
+        // The number of times there were zero active timers.
+        size_t drained;
+
+        // The number of times a protocol error was seen.
+        size_t error;
+    };
+
+    Counters counters_;
+
+    // The clock used by this AnrTimerService.
+    Ticker *ticker_;
+};
+
+class AnrTimerService::ProcessStats {
+  public:
+    nsecs_t cpu_time;
+    nsecs_t cpu_delay;
+
+    ProcessStats() :
+            cpu_time(0),
+            cpu_delay(0) {
+    }
+
+    // Collect all statistics for a process.  Return true if the fill succeeded and false if it
+    // did not.  If there is any problem, the statistics are zeroed.
+    bool fill(int pid) {
+        cpu_time = 0;
+        cpu_delay = 0;
+
+        char path[PATH_MAX];
+        snprintf(path, sizeof(path), "/proc/%u/schedstat", pid);
+        ::android::base::unique_fd fd(open(path, O_RDONLY | O_CLOEXEC));
+        if (!fd.ok()) {
+            return false;
+        }
+        char buffer[128];
+        ssize_t len = read(fd, buffer, sizeof(buffer));
+        if (len <= 0) {
+            return false;
+        }
+        if (len >= sizeof(buffer)) {
+            ALOGE("proc file too big: %s", path);
+            return false;
+        }
+        buffer[len] = 0;
+        unsigned long t1;
+        unsigned long t2;
+        if (sscanf(buffer, "%lu %lu", &t1, &t2) != 2) {
+            return false;
+        }
+        cpu_time = t1;
+        cpu_delay = t2;
+        return true;
+    }
+};
+
+class AnrTimerService::Timer {
+  public:
+    // A unique ID assigned when the Timer is created.
+    timer_id_t const id;
+
+    // The creation parameters.  The timeout is the original, relative timeout.
+    int const pid;
+    int const uid;
+    nsecs_t const timeout;
+    bool const extend;
+
+    // The state of this timer.
+    Status status;
+
+    // The scheduled timeout.  This is an absolute time.  It may be extended.
+    nsecs_t scheduled;
+
+    // True if this timer has been extended.
+    bool extended;
+
+    // Bookkeeping for extensions.  The initial state of the process.  This is collected only if
+    // the timer is extensible.
+    ProcessStats initial;
+
+    // The default constructor is used to create timers that are Invalid, representing the "not
+    // found" condition when a collection is searched.
+    Timer() :
+            id(NOTIMER),
+            pid(0),
+            uid(0),
+            timeout(0),
+            extend(false),
+            status(Invalid),
+            scheduled(0),
+            extended(false) {
+    }
+
+    // This constructor creates a timer with the specified id.  This can be used as the argument
+    // to find().
+    Timer(timer_id_t id) :
+            id(id),
+            pid(0),
+            uid(0),
+            timeout(0),
+            extend(false),
+            status(Invalid),
+            scheduled(0),
+            extended(false) {
+    }
+
+    // Create a new timer.  This starts the timer.
+    Timer(int pid, int uid, nsecs_t timeout, bool extend) :
+            id(nextId()),
+            pid(pid),
+            uid(uid),
+            timeout(timeout),
+            extend(extend),
+            status(Running),
+            scheduled(now() + timeout),
+            extended(false) {
+        if (extend && pid != 0) {
+            initial.fill(pid);
+        }
+    }
+
+    // Cancel a timer.  Return the headroom (which may be negative).  This does not, as yet,
+    // account for extensions.
+    void cancel() {
+        ALOGW_IF(DEBUG && status != Running, "cancel %s", toString().c_str());
+        status = Canceled;
+    }
+
+    // Expire a timer. Return true if the timer is expired and false otherwise.  The function
+    // returns false if the timer is eligible for extension.  If the function returns false, the
+    // scheduled time is updated.
+    bool expire() {
+        ALOGI_IF(DEBUG, "expire %s", toString().c_str());
+        nsecs_t extension = 0;
+        if (extend && !extended) {
+            // Only one extension is permitted.
+            extended = true;
+            ProcessStats current;
+            current.fill(pid);
+            extension = current.cpu_delay - initial.cpu_delay;
+            if (extension < 0) extension = 0;
+            if (extension > timeout) extension = timeout;
+        }
+        if (extension == 0) {
+            status = Expired;
+        } else {
+            scheduled += extension;
+        }
+        return status == Expired;
+    }
+
+    // Accept a timeout.
+    void accept() {
+    }
+
+    // Discard a timeout.
+    void discard() {
+    }
+
+    // Timers are sorted by id, which is unique.  This provides fast lookups.
+    bool operator<(Timer const &r) const {
+        return id < r.id;
+    }
+
+    bool operator==(timer_id_t r) const {
+        return id == r;
+    }
+
+    std::string toString() const {
+        return StringPrintf("timer id=%d pid=%d status=%s", id, pid, statusString(status));
+    }
+
+    std::string toString(nsecs_t now) const {
+        uint32_t ms = nanoseconds_to_milliseconds(now - scheduled);
+        return StringPrintf("timer id=%d pid=%d status=%s scheduled=%ums",
+                            id, pid, statusString(status), -ms);
+    }
+
+    static int maxId() {
+        return idGen;
+    }
+
+  private:
+    // Get the next free ID.  NOTIMER is never returned.
+    static timer_id_t nextId() {
+        timer_id_t id = idGen.fetch_add(1);
+        while (id == NOTIMER) {
+            id = idGen.fetch_add(1);
+        }
+        return id;
+    }
+
+    // IDs start at 1.  A zero ID is invalid.
+    static std::atomic<timer_id_t> idGen;
+};
+
+// IDs start at 1.
+std::atomic<AnrTimerService::timer_id_t> AnrTimerService::Timer::idGen(1);
+
+/**
+ * Manage a set of timers and notify clients when there is a timeout.
+ */
+class AnrTimerService::Ticker {
+  private:
+    struct Entry {
+        const nsecs_t scheduled;
+        const timer_id_t id;
+        AnrTimerService* const service;
+
+        Entry(nsecs_t scheduled, timer_id_t id, AnrTimerService* service) :
+                scheduled(scheduled), id(id), service(service) {};
+
+        bool operator<(const Entry &r) const {
+            return scheduled == r.scheduled ? id < r.id : scheduled < r.scheduled;
+        }
+    };
+
+  public:
+
+    // Construct the ticker.  This creates the timerfd file descriptor and starts the monitor
+    // thread.  The monitor thread is given a unique name.
+    Ticker() {
+        timerFd_ = timer_create();
+        if (timerFd_ < 0) {
+            ALOGE("failed to create timerFd: %s", strerror(errno));
+            return;
+        }
+
+        if (pthread_create(&watcher_, 0, run, this) != 0) {
+            ALOGE("failed to start thread: %s", strerror(errno));
+            watcher_ = 0;
+            ::close(timerFd_);
+            return;
+        }
+
+        // 16 is a magic number from the kernel.  Thread names may not be longer than this many
+        // bytes, including the terminating null.  The snprintf() method will truncate properly.
+        char name[16];
+        snprintf(name, sizeof(name), "AnrTimerService");
+        pthread_setname_np(watcher_, name);
+
+        ready_ = true;
+    }
+
+    ~Ticker() {
+        // Closing the file descriptor will close the monitor process, if any.
+        if (timerFd_ >= 0) ::close(timerFd_);
+        timerFd_ = -1;
+        watcher_ = 0;
+    }
+
+    // Insert a timer.  Unless canceled, the timer will expire at the scheduled time.  If it
+    // expires, the service will be notified with the id.
+    void insert(nsecs_t scheduled, timer_id_t id, AnrTimerService *service) {
+        Entry e(scheduled, id, service);
+        AutoMutex _l(lock_);
+        timer_id_t front = headTimerId();
+        running_.insert(e);
+        if (front != headTimerId()) restartLocked();
+        maxRunning_ = std::max(maxRunning_, running_.size());
+    }
+
+    // Remove a timer.  The timer is identified by its scheduled timeout and id.  Technically,
+    // the id is sufficient (because timer IDs are unique) but using the timeout is more
+    // efficient.
+    void remove(nsecs_t scheduled, timer_id_t id) {
+        Entry key(scheduled, id, 0);
+        AutoMutex _l(lock_);
+        timer_id_t front = headTimerId();
+        auto found = running_.find(key);
+        if (found != running_.end()) running_.erase(found);
+        if (front != headTimerId()) restartLocked();
+    }
+
+    // Remove every timer associated with the service.
+    void remove(AnrTimerService const* service) {
+        AutoMutex _l(lock_);
+        timer_id_t front = headTimerId();
+        for (auto i = running_.begin(); i != running_.end(); i++) {
+            if (i->service == service) {
+                running_.erase(i);
+            }
+        }
+        if (front != headTimerId()) restartLocked();
+    }
+
+    // Return the number of timers still running.
+    size_t running() const {
+        AutoMutex _l(lock_);
+        return running_.size();
+    }
+
+    // Return the high-water mark of timers running.
+    size_t maxRunning() const {
+        AutoMutex _l(lock_);
+        return maxRunning_;
+    }
+
+  private:
+
+    // Return the head of the running list.  The lock must be held by the caller.
+    timer_id_t headTimerId() {
+        return running_.empty() ? NOTIMER : running_.cbegin()->id;
+    }
+
+    // A simple wrapper that meets the requirements of pthread_create.
+    static void* run(void* arg) {
+        reinterpret_cast<Ticker*>(arg)->monitor();
+        ALOGI("monitor exited");
+        return 0;
+    }
+
+    // Loop (almost) forever.  Whenever the timerfd expires, expire as many entries as
+    // possible.  The loop terminates when the read fails; this generally indicates that the
+    // file descriptor has been closed and the thread can exit.
+    void monitor() {
+        uint64_t token = 0;
+        while (read(timerFd_, &token, sizeof(token)) == sizeof(token)) {
+            // Move expired timers into the local ready list.  This is done inside
+            // the lock.  Then, outside the lock, expire them.
+            nsecs_t current = now();
+            std::vector<Entry> ready;
+            {
+                AutoMutex _l(lock_);
+                while (!running_.empty()) {
+                    Entry timer = *(running_.begin());
+                    if (timer.scheduled <= current) {
+                        ready.push_back(timer);
+                        running_.erase(running_.cbegin());
+                    } else {
+                        break;
+                    }
+                }
+                restartLocked();
+            }
+            // Call the notifiers outside the lock.  Calling the notifiers with the lock held
+            // can lead to deadlock, if the Java-side handler also takes a lock.  Note that the
+            // timerfd is already running.
+            for (auto i = ready.begin(); i != ready.end(); i++) {
+                Entry e = *i;
+                e.service->expire(e.id);
+            }
+        }
+    }
+
+    // Restart the ticker.  The caller must be holding the lock.  This method updates the
+    // timerFd_ to expire at the time of the first Entry in the running list.  This method does
+    // not check to see if the currently programmed expiration time is different from the
+    // scheduled expiration time of the first entry.
+    void restartLocked() {
+        if (!running_.empty()) {
+            Entry const x = *(running_.cbegin());
+            nsecs_t delay = x.scheduled - now();
+            // Force a minimum timeout of 10ns.
+            if (delay < 10) delay = 10;
+            time_t sec = nanoseconds_to_seconds(delay);
+            time_t ns = delay - seconds_to_nanoseconds(sec);
+            struct itimerspec setting = {
+                .it_interval = { 0, 0 },
+                .it_value = { sec, ns },
+            };
+            timer_settime(timerFd_, 0, &setting, nullptr);
+            restarted_++;
+            ALOGI_IF(DEBUG, "restarted timerfd for %ld.%09ld", sec, ns);
+        } else {
+            const struct itimerspec setting = {
+                .it_interval = { 0, 0 },
+                .it_value = { 0, 0 },
+            };
+            timer_settime(timerFd_, 0, &setting, nullptr);
+            drained_++;
+            ALOGI_IF(DEBUG, "drained timer list");
+        }
+    }
+
+    // The usual lock.
+    mutable Mutex lock_;
+
+    // True if the object was initialized properly.  Android does not support throwing C++
+    // exceptions, so clients should check this flag after constructing the object.  This is
+    // effectively const after the instance has been created.
+    bool ready_ = false;
+
+    // The file descriptor of the timer.
+    int timerFd_ = -1;
+
+    // The thread that monitors the timer.
+    pthread_t watcher_ = 0;
+
+    // The number of times the timer was restarted.
+    size_t restarted_ = 0;
+
+    // The number of times the timer list was exhausted.
+    size_t drained_ = 0;
+
+    // The highwater mark of timers that are running.
+    size_t maxRunning_ = 0;
+
+    // The list of timers that are scheduled.  This set is sorted by timeout and then by timer
+    // ID.  A set is sufficient (as opposed to a multiset) because timer IDs are unique.
+    std::set<Entry> running_;
+};
+
+
+AnrTimerService::AnrTimerService(char const* label,
+            notifier_t notifier, void* cookie, jweak jtimer, Ticker* ticker) :
+        label_(label),
+        notifier_(notifier),
+        notifierCookie_(cookie),
+        notifierObject_(jtimer),
+        ticker_(ticker) {
+
+    // Zero the statistics
+    maxActive_ = 0;
+    memset(&counters_, 0, sizeof(counters_));
+
+    ALOGI_IF(DEBUG, "initialized %s", label);
+}
+
+AnrTimerService::~AnrTimerService() {
+    AutoMutex _l(lock_);
+    ticker_->remove(this);
+}
+
+char const *AnrTimerService::statusString(Status s) {
+    switch (s) {
+        case Invalid: return "invalid";
+        case Running: return "running";
+        case Expired: return "expired";
+        case Canceled: return "canceled";
+    }
+    return "unknown";
+}
+
+AnrTimerService::timer_id_t AnrTimerService::start(int pid, int uid,
+        nsecs_t timeout, bool extend) {
+    ALOGI_IF(DEBUG, "starting");
+    AutoMutex _l(lock_);
+    Timer t(pid, uid, timeout, extend);
+    insert(t);
+    counters_.started++;
+
+    ALOGI_IF(DEBUG, "started timer %u timeout=%zu", t.id, static_cast<size_t>(timeout));
+    return t.id;
+}
+
+bool AnrTimerService::cancel(timer_id_t timerId) {
+    ALOGI_IF(DEBUG, "canceling %u", timerId);
+    if (timerId == NOTIMER) return false;
+    AutoMutex _l(lock_);
+    Timer timer = remove(timerId);
+
+    bool result = timer.status == Running;
+    if (timer.status != Invalid) {
+        timer.cancel();
+    } else {
+        counters_.error++;
+    }
+    counters_.canceled++;
+    ALOGI_IF(DEBUG, "canceled timer %u", timerId);
+    return result;
+}
+
+bool AnrTimerService::accept(timer_id_t timerId) {
+    ALOGI_IF(DEBUG, "accepting %u", timerId);
+    if (timerId == NOTIMER) return false;
+    AutoMutex _l(lock_);
+    Timer timer = remove(timerId);
+
+    bool result = timer.status == Expired;
+    if (timer.status == Expired) {
+        timer.accept();
+    } else {
+        counters_.error++;
+    }
+    counters_.accepted++;
+    ALOGI_IF(DEBUG, "accepted timer %u", timerId);
+    return result;
+}
+
+bool AnrTimerService::discard(timer_id_t timerId) {
+    ALOGI_IF(DEBUG, "discarding %u", timerId);
+    if (timerId == NOTIMER) return false;
+    AutoMutex _l(lock_);
+    Timer timer = remove(timerId);
+
+    bool result = timer.status == Expired;
+    if (timer.status == Expired) {
+        timer.discard();
+    } else {
+        counters_.error++;
+    }
+    counters_.discarded++;
+    ALOGI_IF(DEBUG, "discarded timer %u", timerId);
+    return result;
+}
+
+// Hold the lock in order to manage the running list.
+// the listener.
+void AnrTimerService::expire(timer_id_t timerId) {
+    ALOGI_IF(DEBUG, "expiring %u", timerId);
+    // Save the timer attributes for the notification
+    int pid = 0;
+    int uid = 0;
+    bool expired = false;
+    {
+        AutoMutex _l(lock_);
+        Timer t = remove(timerId);
+        expired = t.expire();
+        if (t.status == Invalid) {
+            ALOGW_IF(DEBUG, "error: expired invalid timer %u", timerId);
+            return;
+        } else {
+            // The timer is either Running (because it was extended) or expired (and is awaiting an
+            // accept or discard).
+            insert(t);
+        }
+    }
+
+    // Deliver the notification outside of the lock.
+    if (expired) {
+        if (!notifier_(timerId, pid, uid, notifierCookie_, notifierObject_)) {
+            AutoMutex _l(lock_);
+            // Notification failed, which means the listener will never call accept() or
+            // discard().  Do not reinsert the timer.
+            remove(timerId);
+        }
+    }
+    ALOGI_IF(DEBUG, "expired timer %u", timerId);
+}
+
+void AnrTimerService::insert(const Timer& t) {
+    running_.insert(t);
+    if (t.status == Running) {
+        // Only forward running timers to the ticker.  Expired timers are handled separately.
+        ticker_->insert(t.scheduled, t.id, this);
+        maxActive_ = std::max(maxActive_, running_.size());
+    }
+}
+
+AnrTimerService::Timer AnrTimerService::remove(timer_id_t timerId) {
+    Timer key(timerId);
+    auto found = running_.find(key);
+    if (found != running_.end()) {
+        Timer result = *found;
+        running_.erase(found);
+        ticker_->remove(result.scheduled, result.id);
+        return result;
+    }
+    return Timer();
+}
+
+void AnrTimerService::dump(bool verbose) const {
+    AutoMutex _l(lock_);
+    ALOGI("timer %s ops started=%zu canceled=%zu accepted=%zu discarded=%zu expired=%zu",
+          label_.c_str(),
+          counters_.started, counters_.canceled, counters_.accepted,
+          counters_.discarded, counters_.expired);
+    ALOGI("timer %s stats max-active=%zu/%zu running=%zu/%zu errors=%zu",
+          label_.c_str(),
+          maxActive_, ticker_->maxRunning(), running_.size(), ticker_->running(),
+          counters_.error);
+
+    if (verbose) {
+        nsecs_t time = now();
+        for (auto i = running_.begin(); i != running_.end(); i++) {
+            Timer t = *i;
+            ALOGI("   running %s", t.toString(time).c_str());
+        }
+    }
+}
+
+/**
+ * True if the native methods are supported in this process.  Native methods are supported only
+ * if the initialization succeeds.
+ */
+bool nativeSupportEnabled = false;
+
+/**
+ * Singleton/globals for the anr timer.  Among other things, this includes a Ticker* and a use
+ * count.  The JNI layer creates a single Ticker for all operational AnrTimers.  The Ticker is
+ * created when the first AnrTimer is created, and is deleted when the last AnrTimer is closed.
+ */
+static Mutex gAnrLock;
+struct AnrArgs {
+    jclass clazz = NULL;
+    jmethodID func = NULL;
+    JavaVM* vm = NULL;
+    AnrTimerService::Ticker* ticker = nullptr;
+    int tickerUseCount = 0;;
+};
+static AnrArgs gAnrArgs;
+
+// The cookie is the address of the AnrArgs object to which the notification should be sent.
+static bool anrNotify(AnrTimerService::timer_id_t timerId, int pid, int uid,
+                      void* cookie, jweak jtimer) {
+    AutoMutex _l(gAnrLock);
+    AnrArgs* target = reinterpret_cast<AnrArgs* >(cookie);
+    JNIEnv *env;
+    if (target->vm->AttachCurrentThread(&env, 0) != JNI_OK) {
+        ALOGE("failed to attach thread to JavaVM");
+        return false;
+    }
+    jboolean r = false;
+    jobject timer = env->NewGlobalRef(jtimer);
+    if (timer != nullptr) {
+        r = env->CallBooleanMethod(timer, target->func, timerId, pid, uid);
+        env->DeleteGlobalRef(timer);
+    }
+    target->vm->DetachCurrentThread();
+    return r;
+}
+
+jboolean anrTimerSupported(JNIEnv* env, jclass) {
+    return nativeSupportEnabled;
+}
+
+jlong anrTimerCreate(JNIEnv* env, jobject jtimer, jstring jname) {
+    if (!nativeSupportEnabled) return 0;
+    AutoMutex _l(gAnrLock);
+    if (!gAnrArgs.ticker) {
+        gAnrArgs.ticker = new AnrTimerService::Ticker();
+    }
+    gAnrArgs.tickerUseCount++;
+
+    ScopedUtfChars name(env, jname);
+    jobject timer = env->NewWeakGlobalRef(jtimer);
+    AnrTimerService* service =
+            new AnrTimerService(name.c_str(), anrNotify, &gAnrArgs, timer, gAnrArgs.ticker);
+    return reinterpret_cast<jlong>(service);
+}
+
+AnrTimerService *toService(jlong pointer) {
+    return reinterpret_cast<AnrTimerService*>(pointer);
+}
+
+jint anrTimerClose(JNIEnv* env, jclass, jlong ptr) {
+    if (!nativeSupportEnabled) return -1;
+    if (ptr == 0) return -1;
+    AutoMutex _l(gAnrLock);
+    AnrTimerService *s = toService(ptr);
+    env->DeleteWeakGlobalRef(s->jtimer());
+    delete s;
+    if (--gAnrArgs.tickerUseCount <= 0) {
+        delete gAnrArgs.ticker;
+        gAnrArgs.ticker = nullptr;
+    }
+    return 0;
+}
+
+jint anrTimerStart(JNIEnv* env, jclass, jlong ptr,
+        jint pid, jint uid, jlong timeout, jboolean extend) {
+    if (!nativeSupportEnabled) return 0;
+    // On the Java side, timeouts are expressed in milliseconds and must be converted to
+    // nanoseconds before being passed to the library code.
+    return toService(ptr)->start(pid, uid, milliseconds_to_nanoseconds(timeout), extend);
+}
+
+jboolean anrTimerCancel(JNIEnv* env, jclass, jlong ptr, jint timerId) {
+    if (!nativeSupportEnabled) return false;
+    return toService(ptr)->cancel(timerId);
+}
+
+jboolean anrTimerAccept(JNIEnv* env, jclass, jlong ptr, jint timerId) {
+    if (!nativeSupportEnabled) return false;
+    return toService(ptr)->accept(timerId);
+}
+
+jboolean anrTimerDiscard(JNIEnv* env, jclass, jlong ptr, jint timerId) {
+    if (!nativeSupportEnabled) return false;
+    return toService(ptr)->discard(timerId);
+}
+
+jint anrTimerDump(JNIEnv *env, jclass, jlong ptr, jboolean verbose) {
+    if (!nativeSupportEnabled) return -1;
+    toService(ptr)->dump(verbose);
+    return 0;
+}
+
+static const JNINativeMethod methods[] = {
+    {"nativeAnrTimerSupported", "()Z",  (void*) anrTimerSupported},
+    {"nativeAnrTimerCreate", "(Ljava/lang/String;)J", (void*) anrTimerCreate},
+    {"nativeAnrTimerClose", "(J)I",     (void*) anrTimerClose},
+    {"nativeAnrTimerStart", "(JIIJZ)I", (void*) anrTimerStart},
+    {"nativeAnrTimerCancel", "(JI)Z",   (void*) anrTimerCancel},
+    {"nativeAnrTimerAccept", "(JI)Z",   (void*) anrTimerAccept},
+    {"nativeAnrTimerDiscard", "(JI)Z",  (void*) anrTimerDiscard},
+    {"nativeAnrTimerDump", "(JZ)V",     (void*) anrTimerDump},
+};
+
+} // anonymous namespace
+
+int register_android_server_utils_AnrTimer(JNIEnv* env)
+{
+    static const char *className = "com/android/server/utils/AnrTimer";
+    jniRegisterNativeMethods(env, className, methods, NELEM(methods));
+
+    jclass service = FindClassOrDie(env, className);
+    gAnrArgs.clazz = MakeGlobalRefOrDie(env, service);
+    gAnrArgs.func = env->GetMethodID(gAnrArgs.clazz, "expire", "(III)Z");
+    env->GetJavaVM(&gAnrArgs.vm);
+
+    nativeSupportEnabled = NATIVE_SUPPORT;
+
+    return 0;
+}
+
+} // namespace android
diff --git a/services/core/jni/onload.cpp b/services/core/jni/onload.cpp
index 11734da..f3158d1 100644
--- a/services/core/jni/onload.cpp
+++ b/services/core/jni/onload.cpp
@@ -52,6 +52,7 @@
 int register_android_server_HardwarePropertiesManagerService(JNIEnv* env);
 int register_android_server_SyntheticPasswordManager(JNIEnv* env);
 int register_android_hardware_display_DisplayViewport(JNIEnv* env);
+int register_android_server_utils_AnrTimer(JNIEnv *env);
 int register_android_server_am_OomConnection(JNIEnv* env);
 int register_android_server_am_CachedAppOptimizer(JNIEnv* env);
 int register_android_server_am_LowMemDetector(JNIEnv* env);
@@ -113,6 +114,7 @@
     register_android_server_storage_AppFuse(env);
     register_android_server_SyntheticPasswordManager(env);
     register_android_hardware_display_DisplayViewport(env);
+    register_android_server_utils_AnrTimer(env);
     register_android_server_am_OomConnection(env);
     register_android_server_am_CachedAppOptimizer(env);
     register_android_server_am_LowMemDetector(env);
diff --git a/services/credentials/java/com/android/server/credentials/CreateRequestSession.java b/services/credentials/java/com/android/server/credentials/CreateRequestSession.java
index 6899ad4..31409ab 100644
--- a/services/credentials/java/com/android/server/credentials/CreateRequestSession.java
+++ b/services/credentials/java/com/android/server/credentials/CreateRequestSession.java
@@ -109,7 +109,7 @@
                             PermissionUtils.hasPermission(mContext, mClientAppInfo.getPackageName(),
                                     Manifest.permission.CREDENTIAL_MANAGER_SET_ALLOWED_PROVIDERS),
                             /*defaultProviderId=*/flattenedPrimaryProviders),
-                    providerDataList);
+                    providerDataList, /*isRequestForAllOptions=*/ false);
             mClientCallback.onPendingIntent(mPendingIntent);
         } catch (RemoteException e) {
             mRequestSessionMetric.collectUiReturnedFinalPhase(/*uiReturned=*/ false);
diff --git a/services/credentials/java/com/android/server/credentials/CredentialManagerService.java b/services/credentials/java/com/android/server/credentials/CredentialManagerService.java
index 686b2a8..dfb5a57 100644
--- a/services/credentials/java/com/android/server/credentials/CredentialManagerService.java
+++ b/services/credentials/java/com/android/server/credentials/CredentialManagerService.java
@@ -63,6 +63,7 @@
 import android.util.Pair;
 import android.util.Slog;
 import android.util.SparseArray;
+import android.view.autofill.IAutoFillManagerClient;
 
 import com.android.internal.annotations.GuardedBy;
 import com.android.server.credentials.metrics.ApiName;
@@ -483,6 +484,7 @@
         public ICancellationSignal getCandidateCredentials(
                 GetCredentialRequest request,
                 IGetCandidateCredentialsCallback callback,
+                IAutoFillManagerClient clientCallback,
                 final String callingPackage) {
             Slog.i(TAG, "starting getCandidateCredentials with callingPackage: "
                     + callingPackage);
@@ -503,7 +505,8 @@
                             request,
                             constructCallingAppInfo(callingPackage, userId, request.getOrigin()),
                             getEnabledProvidersForUser(userId),
-                            CancellationSignal.fromTransport(cancelTransport)
+                            CancellationSignal.fromTransport(cancelTransport),
+                            clientCallback
                     );
             addSessionLocked(userId, session);
 
diff --git a/services/credentials/java/com/android/server/credentials/CredentialManagerUi.java b/services/credentials/java/com/android/server/credentials/CredentialManagerUi.java
index 3c190bf..f092dcc 100644
--- a/services/credentials/java/com/android/server/credentials/CredentialManagerUi.java
+++ b/services/credentials/java/com/android/server/credentials/CredentialManagerUi.java
@@ -150,9 +150,12 @@
      *
      * @param requestInfo      the information about the request
      * @param providerDataList the list of provider data from remote providers
+     * @param isRequestForAllOptions whether the bottom sheet should directly navigate to the
+     *                               all options page
      */
     public PendingIntent createPendingIntent(
-            RequestInfo requestInfo, ArrayList<ProviderData> providerDataList) {
+            RequestInfo requestInfo, ArrayList<ProviderData> providerDataList,
+            boolean isRequestForAllOptions) {
         List<CredentialProviderInfo> allProviders =
                 CredentialProviderInfoFactory.getCredentialProviderServices(
                         mContext,
@@ -168,7 +171,8 @@
                         disabledProvider.getComponentName().flattenToString())).toList();
 
         Intent intent = IntentFactory.createCredentialSelectorIntent(requestInfo, providerDataList,
-                        new ArrayList<>(disabledProviderDataList), mResultReceiver)
+                        new ArrayList<>(disabledProviderDataList), mResultReceiver,
+                        isRequestForAllOptions)
                 .setAction(UUID.randomUUID().toString());
         //TODO: Create unique pending intent using request code and cancel any pre-existing pending
         // intents
diff --git a/services/credentials/java/com/android/server/credentials/GetCandidateRequestSession.java b/services/credentials/java/com/android/server/credentials/GetCandidateRequestSession.java
index 6d9b7e8..d165171 100644
--- a/services/credentials/java/com/android/server/credentials/GetCandidateRequestSession.java
+++ b/services/credentials/java/com/android/server/credentials/GetCandidateRequestSession.java
@@ -16,6 +16,7 @@
 
 package com.android.server.credentials;
 
+import android.Manifest;
 import android.annotation.Nullable;
 import android.content.ComponentName;
 import android.content.Context;
@@ -23,6 +24,7 @@
 import android.credentials.GetCandidateCredentialsException;
 import android.credentials.GetCandidateCredentialsResponse;
 import android.credentials.GetCredentialRequest;
+import android.credentials.GetCredentialResponse;
 import android.credentials.IGetCandidateCredentialsCallback;
 import android.credentials.ui.GetCredentialProviderData;
 import android.credentials.ui.ProviderData;
@@ -30,7 +32,9 @@
 import android.os.CancellationSignal;
 import android.os.RemoteException;
 import android.service.credentials.CallingAppInfo;
+import android.service.credentials.PermissionUtils;
 import android.util.Slog;
+import android.view.autofill.IAutoFillManagerClient;
 
 import java.util.ArrayList;
 import java.util.List;
@@ -42,18 +46,22 @@
  */
 public class GetCandidateRequestSession extends RequestSession<GetCredentialRequest,
         IGetCandidateCredentialsCallback, GetCandidateCredentialsResponse>
-        implements ProviderSession.ProviderInternalCallback<GetCandidateCredentialsResponse> {
+        implements ProviderSession.ProviderInternalCallback<GetCredentialResponse> {
     private static final String TAG = "GetCandidateRequestSession";
 
+    private final IAutoFillManagerClient mAutoFillCallback;
+
     public GetCandidateRequestSession(
             Context context, SessionLifetime sessionCallback,
             Object lock, int userId, int callingUid,
             IGetCandidateCredentialsCallback callback, GetCredentialRequest request,
             CallingAppInfo callingAppInfo, Set<ComponentName> enabledProviders,
-            CancellationSignal cancellationSignal) {
+            CancellationSignal cancellationSignal,
+            IAutoFillManagerClient autoFillCallback) {
         super(context, sessionCallback, lock, userId, callingUid, request, callback,
                 RequestInfo.TYPE_GET, callingAppInfo, enabledProviders,
                 cancellationSignal, 0L);
+        mAutoFillCallback = autoFillCallback;
     }
 
     /**
@@ -92,12 +100,26 @@
             return;
         }
 
+        cancelExistingPendingIntent();
+        mPendingIntent = mCredentialManagerUi.createPendingIntent(
+                RequestInfo.newGetRequestInfo(
+                        mRequestId, mClientRequest, mClientAppInfo.getPackageName(),
+                        PermissionUtils.hasPermission(mContext, mClientAppInfo.getPackageName(),
+                                Manifest.permission.CREDENTIAL_MANAGER_SET_ALLOWED_PROVIDERS)),
+                providerDataList,
+                /*isRequestForAllOptions=*/ true);
+
         List<GetCredentialProviderData> candidateProviderDataList = new ArrayList<>();
         for (ProviderData providerData : providerDataList) {
             candidateProviderDataList.add((GetCredentialProviderData) (providerData));
         }
-        respondToClientWithResponseAndFinish(new GetCandidateCredentialsResponse(
-                candidateProviderDataList));
+
+        try {
+            invokeClientCallbackSuccess(new GetCandidateCredentialsResponse(
+                    candidateProviderDataList, mPendingIntent));
+        } catch (RemoteException e) {
+            Slog.e(TAG, "Issue while responding to client with error : " + e);
+        }
     }
 
     @Override
@@ -151,7 +173,8 @@
 
     @Override
     public void onFinalResponseReceived(ComponentName componentName,
-            GetCandidateCredentialsResponse response) {
-        // Not applicable for session without UI
+            GetCredentialResponse response) {
+        Slog.d(TAG, "onFinalResponseReceived");
+        respondToClientWithResponseAndFinish(new GetCandidateCredentialsResponse(response));
     }
 }
diff --git a/services/credentials/java/com/android/server/credentials/GetRequestSession.java b/services/credentials/java/com/android/server/credentials/GetRequestSession.java
index c9e691e..3f57c80 100644
--- a/services/credentials/java/com/android/server/credentials/GetRequestSession.java
+++ b/services/credentials/java/com/android/server/credentials/GetRequestSession.java
@@ -99,21 +99,24 @@
     protected void launchUiWithProviderData(ArrayList<ProviderData> providerDataList) {
         mRequestSessionMetric.collectUiCallStartTime(System.nanoTime());
         mCredentialManagerUi.setStatus(CredentialManagerUi.UiStatus.USER_INTERACTION);
-        Binder.withCleanCallingIdentity(()-> {
-        try {
+        Binder.withCleanCallingIdentity(() -> {
+            try {
                 cancelExistingPendingIntent();
-            mPendingIntent = mCredentialManagerUi.createPendingIntent(
-                    RequestInfo.newGetRequestInfo(
-                            mRequestId, mClientRequest, mClientAppInfo.getPackageName(),
-                            PermissionUtils.hasPermission(mContext, mClientAppInfo.getPackageName(),
-                                    Manifest.permission.CREDENTIAL_MANAGER_SET_ALLOWED_PROVIDERS)),
-                    providerDataList);
-            mClientCallback.onPendingIntent(mPendingIntent);
-        } catch (RemoteException e) {
-            mRequestSessionMetric.collectUiReturnedFinalPhase(/*uiReturned=*/ false);
-            mCredentialManagerUi.setStatus(CredentialManagerUi.UiStatus.TERMINATED);
-            String exception = GetCredentialException.TYPE_UNKNOWN;
-            mRequestSessionMetric.collectFrameworkException(exception);
+                mPendingIntent = mCredentialManagerUi.createPendingIntent(
+                        RequestInfo.newGetRequestInfo(
+                                mRequestId, mClientRequest, mClientAppInfo.getPackageName(),
+                                PermissionUtils.hasPermission(mContext,
+                                        mClientAppInfo.getPackageName(),
+                                        Manifest.permission
+                                                .CREDENTIAL_MANAGER_SET_ALLOWED_PROVIDERS)),
+                        providerDataList,
+                        /*isRequestForAllOptions=*/ false);
+                mClientCallback.onPendingIntent(mPendingIntent);
+            } catch (RemoteException e) {
+                mRequestSessionMetric.collectUiReturnedFinalPhase(/*uiReturned=*/ false);
+                mCredentialManagerUi.setStatus(CredentialManagerUi.UiStatus.TERMINATED);
+                String exception = GetCredentialException.TYPE_UNKNOWN;
+                mRequestSessionMetric.collectFrameworkException(exception);
                 respondToClientWithErrorAndFinish(exception, "Unable to instantiate selector");
             }
         });
diff --git a/services/credentials/java/com/android/server/credentials/PrepareGetRequestSession.java b/services/credentials/java/com/android/server/credentials/PrepareGetRequestSession.java
index f447c1f..fbfc9ca 100644
--- a/services/credentials/java/com/android/server/credentials/PrepareGetRequestSession.java
+++ b/services/credentials/java/com/android/server/credentials/PrepareGetRequestSession.java
@@ -192,7 +192,7 @@
                             mRequestId, mClientRequest, mClientAppInfo.getPackageName(),
                             PermissionUtils.hasPermission(mContext, mClientAppInfo.getPackageName(),
                                     Manifest.permission.CREDENTIAL_MANAGER_SET_ALLOWED_PROVIDERS)),
-                    providerDataList);
+                    providerDataList, /*isRequestForAllOptions=*/ false);
         } else {
             return null;
         }
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index a490013..f288103 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -6243,9 +6243,8 @@
 
         final long id = mInjector.binderClearCallingIdentity();
         try {
-            final KeyChainConnection keyChainConnection =
-                    KeyChain.bindAsUser(mContext, caller.getUserHandle());
-            try {
+            try (KeyChainConnection keyChainConnection =
+                         KeyChain.bindAsUser(mContext, caller.getUserHandle())) {
                 IKeyChainService keyChain = keyChainConnection.getService();
                 if (!keyChain.installKeyPair(privKey, cert, chain, alias, KeyStore.UID_SELF)) {
                     logInstallKeyPairFailure(caller, isCredentialManagementApp);
@@ -6263,10 +6262,8 @@
                                 ? CREDENTIAL_MANAGEMENT_APP : NOT_CREDENTIAL_MANAGEMENT_APP)
                         .write();
                 return true;
-            } catch (RemoteException e) {
+            } catch (RemoteException | AssertionError e) {
                 Slogf.e(LOG_TAG, "Installing certificate", e);
-            } finally {
-                keyChainConnection.close();
             }
         } catch (InterruptedException e) {
             Slogf.w(LOG_TAG, "Interrupted while installing certificate", e);
@@ -6313,9 +6310,8 @@
 
         final long id = Binder.clearCallingIdentity();
         try {
-            final KeyChainConnection keyChainConnection =
-                    KeyChain.bindAsUser(mContext, caller.getUserHandle());
-            try {
+            try (KeyChainConnection keyChainConnection =
+                         KeyChain.bindAsUser(mContext, caller.getUserHandle())) {
                 IKeyChainService keyChain = keyChainConnection.getService();
                 DevicePolicyEventLogger
                         .createEvent(DevicePolicyEnums.REMOVE_KEY_PAIR)
@@ -6325,10 +6321,8 @@
                                 ? CREDENTIAL_MANAGEMENT_APP : NOT_CREDENTIAL_MANAGEMENT_APP)
                         .write();
                 return keyChain.removeKeyPair(alias);
-            } catch (RemoteException e) {
+            } catch (RemoteException | AssertionError e) {
                 Slogf.e(LOG_TAG, "Removing keypair", e);
-            } finally {
-                keyChainConnection.close();
             }
         } catch (InterruptedException e) {
             Slogf.w(LOG_TAG, "Interrupted while removing keypair", e);
@@ -6355,7 +6349,7 @@
             try (KeyChainConnection keyChainConnection =
                          KeyChain.bindAsUser(mContext, caller.getUserHandle())) {
                 return keyChainConnection.getService().containsKeyPair(alias);
-            } catch (RemoteException e) {
+            } catch (RemoteException | AssertionError e) {
                 Slogf.e(LOG_TAG, "Querying keypair", e);
             } catch (InterruptedException e) {
                 Slogf.w(LOG_TAG, "Interrupted while querying keypair", e);
@@ -6417,7 +6411,7 @@
                     }
                 }
                 return false;
-            } catch (RemoteException e) {
+            } catch (RemoteException | AssertionError e) {
                 Slogf.e(LOG_TAG, "Querying grant to wifi auth.", e);
                 return false;
             }
@@ -6497,7 +6491,7 @@
                     }
                     result.put(uid, new ArraySet<String>(packages));
                 }
-            } catch (RemoteException e) {
+            } catch (RemoteException | AssertionError e) {
                 Slogf.e(LOG_TAG, "Querying keypair grants", e);
             } catch (InterruptedException e) {
                 Slogf.w(LOG_TAG, "Interrupted while querying keypair grants", e);
@@ -6667,7 +6661,7 @@
                         .write();
                 return true;
             }
-        } catch (RemoteException e) {
+        } catch (RemoteException | AssertionError e) {
             Slogf.e(LOG_TAG, "KeyChain error while generating a keypair", e);
         } catch (InterruptedException e) {
             Slogf.w(LOG_TAG, "Interrupted while generating keypair", e);
@@ -6742,7 +6736,7 @@
         } catch (InterruptedException e) {
             Slogf.w(LOG_TAG, "Interrupted while setting keypair certificate", e);
             Thread.currentThread().interrupt();
-        } catch (RemoteException e) {
+        } catch (RemoteException | AssertionError e) {
             Slogf.e(LOG_TAG, "Failed setting keypair certificate", e);
         } finally {
             mInjector.binderRestoreCallingIdentity(id);
@@ -7227,7 +7221,7 @@
                         connection.getService().getCredentialManagementAppPolicy();
                 return policy != null && !policy.getAppAndUriMappings().isEmpty()
                         && containsAlias(policy, alias);
-            } catch (RemoteException | InterruptedException e) {
+            } catch (RemoteException | InterruptedException | AssertionError e) {
                 return false;
             }
         });
diff --git a/services/print/Android.bp b/services/print/Android.bp
index 5b4349a..0dfceaa 100644
--- a/services/print/Android.bp
+++ b/services/print/Android.bp
@@ -19,4 +19,7 @@
     defaults: ["platform_service_defaults"],
     srcs: [":services.print-sources"],
     libs: ["services.core"],
+    lint: {
+        baseline_filename: "lint-baseline.xml",
+    },
 }
diff --git a/services/tests/displayservicetests/src/com/android/server/display/DisplayDeviceConfigTest.java b/services/tests/displayservicetests/src/com/android/server/display/DisplayDeviceConfigTest.java
index 7a84406..e370f55 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/DisplayDeviceConfigTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/DisplayDeviceConfigTest.java
@@ -759,6 +759,15 @@
                         AUTO_BRIGHTNESS_MODE_DEFAULT,
                         Settings.System.SCREEN_BRIGHTNESS_AUTOMATIC_DIM), SMALL_DELTA);
 
+        assertArrayEquals(new float[]{0.0f, 80},
+                mDisplayDeviceConfig.getAutoBrightnessBrighteningLevelsLux(
+                        AUTO_BRIGHTNESS_MODE_DEFAULT,
+                        Settings.System.SCREEN_BRIGHTNESS_AUTOMATIC_BRIGHT), ZERO_DELTA);
+        assertArrayEquals(new float[]{0.6f, 0.7f},
+                mDisplayDeviceConfig.getAutoBrightnessBrighteningLevels(
+                        AUTO_BRIGHTNESS_MODE_DEFAULT,
+                        Settings.System.SCREEN_BRIGHTNESS_AUTOMATIC_BRIGHT), SMALL_DELTA);
+
         assertArrayEquals(new float[]{0.0f, 95},
                 mDisplayDeviceConfig.getAutoBrightnessBrighteningLevelsLux(
                         AUTO_BRIGHTNESS_MODE_DOZE,
@@ -1197,6 +1206,20 @@
                 +           "</map>\n"
                 +       "</luxToBrightnessMapping>\n"
                 +       "<luxToBrightnessMapping>\n"
+                +           "<mode>default</mode>\n"
+                +           "<setting>bright</setting>\n"
+                +           "<map>\n"
+                +               "<point>\n"
+                +                   "<first>0</first>\n"
+                +                   "<second>0.6</second>\n"
+                +               "</point>\n"
+                +               "<point>\n"
+                +                   "<first>80</first>\n"
+                +                   "<second>0.7</second>\n"
+                +               "</point>\n"
+                +           "</map>\n"
+                +       "</luxToBrightnessMapping>\n"
+                +       "<luxToBrightnessMapping>\n"
                 +           "<mode>doze</mode>\n"
                 +           "<map>\n"
                 +               "<point>\n"
diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/UserManagerServiceDemoModeTest.kt b/services/tests/mockingservicestests/src/com/android/server/pm/UserManagerServiceDemoModeTest.kt
new file mode 100644
index 0000000..dfdb0c7
--- /dev/null
+++ b/services/tests/mockingservicestests/src/com/android/server/pm/UserManagerServiceDemoModeTest.kt
@@ -0,0 +1,80 @@
+/*
+ * 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.server.pm
+
+import android.content.res.Configuration
+import android.os.Looper
+import android.os.SystemProperties
+import android.os.UserHandle
+import android.util.ArrayMap
+import com.android.server.LockGuard
+import com.android.server.extendedtestutils.wheneverStatic
+import com.android.server.testutils.whenever
+import com.google.common.truth.Truth.assertThat
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+import org.mockito.MockitoAnnotations
+
+@RunWith(JUnit4::class)
+class UserManagerServiceDemoModeTest {
+    private lateinit var ums: UserManagerService
+
+    @Rule
+    @JvmField
+    val rule = MockSystemRule()
+
+    @Before
+    @Throws(Exception::class)
+    fun setUp() {
+        MockitoAnnotations.initMocks(this)
+        rule.system().stageNominalSystemState()
+
+        if (Looper.myLooper() == null) {
+            Looper.prepare()
+        }
+
+        wheneverStatic { LockGuard.installNewLock(LockGuard.INDEX_USER) }.thenReturn(Object())
+        whenever(rule.mocks().systemConfig.getAndClearPackageToUserTypeWhitelist()).thenReturn(ArrayMap<String, Set<String>>())
+        whenever(rule.mocks().systemConfig.getAndClearPackageToUserTypeBlacklist()).thenReturn(ArrayMap<String, Set<String>>())
+        whenever(rule.mocks().resources.getStringArray(com.android.internal.R.array.config_defaultFirstUserRestrictions)).thenReturn(arrayOf<String>())
+        whenever(rule.mocks().resources.configuration).thenReturn(Configuration())
+
+        ums = UserManagerService(rule.mocks().context)
+    }
+
+    @Test
+    fun isDemoUser_returnsTrue_whenSystemPropertyIsSet() {
+        wheneverStatic { SystemProperties.getBoolean("ro.boot.arc_demo_mode", false) }.thenReturn(true)
+
+        assertThat(ums.isDemoUser(0)).isTrue()
+    }
+
+    @Test
+    fun isDemoUser_returnsFalse_whenSystemPropertyIsSet() {
+        wheneverStatic { SystemProperties.getBoolean("ro.boot.arc_demo_mode", false) }.thenReturn(false)
+
+        assertThat(ums.isDemoUser(0)).isFalse()
+    }
+
+    @Test
+    fun isDemoUser_returnsFalse_whenSystemPropertyIsNotSet() {
+        assertThat(ums.isDemoUser(0)).isFalse()
+    }
+}
\ No newline at end of file
diff --git a/services/tests/servicestests/jni/Android.bp b/services/tests/servicestests/jni/Android.bp
index 174beb8..c30e4eb 100644
--- a/services/tests/servicestests/jni/Android.bp
+++ b/services/tests/servicestests/jni/Android.bp
@@ -23,6 +23,7 @@
         ":lib_cachedAppOptimizer_native",
         ":lib_gameManagerService_native",
         ":lib_oomConnection_native",
+        ":lib_anrTimer_native",
         "onload.cpp",
     ],
 
@@ -55,4 +56,4 @@
         "android.hardware.graphics.mapper@4.0",
         "android.hidl.token@1.0-utils",
     ],
-}
\ No newline at end of file
+}
diff --git a/services/tests/servicestests/jni/onload.cpp b/services/tests/servicestests/jni/onload.cpp
index f160b3d..25487c5 100644
--- a/services/tests/servicestests/jni/onload.cpp
+++ b/services/tests/servicestests/jni/onload.cpp
@@ -27,6 +27,7 @@
 int register_android_server_am_CachedAppOptimizer(JNIEnv* env);
 int register_android_server_app_GameManagerService(JNIEnv* env);
 int register_android_server_am_OomConnection(JNIEnv* env);
+int register_android_server_utils_AnrTimer(JNIEnv *env);
 };
 
 using namespace android;
@@ -44,5 +45,6 @@
     register_android_server_am_CachedAppOptimizer(env);
     register_android_server_app_GameManagerService(env);
     register_android_server_am_OomConnection(env);
+    register_android_server_utils_AnrTimer(env);
     return JNI_VERSION_1_4;
 }
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/gestures/TouchExplorerTest.java b/services/tests/servicestests/src/com/android/server/accessibility/gestures/TouchExplorerTest.java
index efcdbd4..1cd61e9 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/gestures/TouchExplorerTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/gestures/TouchExplorerTest.java
@@ -44,10 +44,6 @@
 import android.graphics.PointF;
 import android.os.Looper;
 import android.os.SystemClock;
-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.testing.DexmakerShareClassLoaderRule;
 import android.view.InputDevice;
 import android.view.MotionEvent;
@@ -60,7 +56,6 @@
 import com.android.server.accessibility.AccessibilityManagerService;
 import com.android.server.accessibility.AccessibilityTraceManager;
 import com.android.server.accessibility.EventStreamTransformation;
-import com.android.server.accessibility.Flags;
 import com.android.server.accessibility.utils.GestureLogParser;
 import com.android.server.testutils.OffsettableClock;
 
@@ -81,7 +76,6 @@
 import java.util.ArrayList;
 import java.util.List;
 
-
 @RunWith(AndroidJUnit4.class)
 public class TouchExplorerTest {
 
@@ -125,9 +119,6 @@
     public final DexmakerShareClassLoaderRule mDexmakerShareClassLoaderRule =
             new DexmakerShareClassLoaderRule();
 
-    @Rule
-    public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule();
-
     /**
      * {@link TouchExplorer#sendDownForAllNotInjectedPointers} injecting events with the same object
      * is resulting {@link ArgumentCaptor} to capture events with last state. Before implementation
@@ -170,16 +161,11 @@
         goFromStateClearTo(STATE_TOUCH_EXPLORING_1FINGER);
         // Wait for transiting to touch exploring state.
         mHandler.fastForward(2 * USER_INTENT_TIMEOUT);
-        assertState(STATE_TOUCH_EXPLORING);
-        // Manually construct the next move event. Using moveEachPointers() will batch the move
-        // event which produces zero movement for some reason.
-        float[] x = new float[1];
-        float[] y = new float[1];
-        x[0] = mLastEvent.getX(0) + mTouchSlop;
-        y[0] = mLastEvent.getY(0) + mTouchSlop;
-        send(manyPointerEvent(ACTION_MOVE, x, y));
+        moveEachPointers(mLastEvent, p(10, 10));
+        send(mLastEvent);
         goToStateClearFrom(STATE_TOUCH_EXPLORING_1FINGER);
         assertCapturedEvents(ACTION_HOVER_ENTER, ACTION_HOVER_MOVE, ACTION_HOVER_EXIT);
+        assertState(STATE_TOUCH_EXPLORING);
     }
 
     /**
@@ -187,8 +173,7 @@
      * change the coordinates.
      */
     @Test
-    @RequiresFlagsEnabled(Flags.FLAG_REDUCE_TOUCH_EXPLORATION_SENSITIVITY)
-    public void testOneFingerMoveWithExtraMoveEvents_generatesOneMoveEvent() {
+    public void testOneFingerMoveWithExtraMoveEvents() {
         goFromStateClearTo(STATE_TOUCH_EXPLORING_1FINGER);
         // Inject a set of move events that have the same coordinates as the down event.
         moveEachPointers(mLastEvent, p(0, 0));
@@ -196,33 +181,7 @@
         // Wait for transition to touch exploring state.
         mHandler.fastForward(2 * USER_INTENT_TIMEOUT);
         // Now move for real.
-        moveAtLeastTouchSlop(mLastEvent);
-        send(mLastEvent);
-        // One more move event with no change.
-        moveEachPointers(mLastEvent, p(0, 0));
-        send(mLastEvent);
-        goToStateClearFrom(STATE_TOUCH_EXPLORING_1FINGER);
-        assertCapturedEvents(
-                ACTION_HOVER_ENTER,
-                ACTION_HOVER_MOVE,
-                ACTION_HOVER_EXIT);
-    }
-
-    /**
-     * Test the case where ACTION_DOWN is followed by a number of ACTION_MOVE events that do not
-     * change the coordinates.
-     */
-    @Test
-    @RequiresFlagsDisabled(Flags.FLAG_REDUCE_TOUCH_EXPLORATION_SENSITIVITY)
-    public void testOneFingerMoveWithExtraMoveEvents_generatesThreeMoveEvent() {
-        goFromStateClearTo(STATE_TOUCH_EXPLORING_1FINGER);
-        // Inject a set of move events that have the same coordinates as the down event.
-        moveEachPointers(mLastEvent, p(0, 0));
-        send(mLastEvent);
-        // Wait for transition to touch exploring state.
-        mHandler.fastForward(2 * USER_INTENT_TIMEOUT);
-        // Now move for real.
-        moveAtLeastTouchSlop(mLastEvent);
+        moveEachPointers(mLastEvent, p(10, 10));
         send(mLastEvent);
         // One more move event with no change.
         moveEachPointers(mLastEvent, p(0, 0));
@@ -283,7 +242,7 @@
         moveEachPointers(mLastEvent, p(0, 0), p(0, 0));
         send(mLastEvent);
         // Now move for real.
-        moveEachPointers(mLastEvent, p(mTouchSlop, mTouchSlop), p(mTouchSlop, mTouchSlop));
+        moveEachPointers(mLastEvent, p(10, 10), p(10, 10));
         send(mLastEvent);
         goToStateClearFrom(STATE_DRAGGING_2FINGERS);
         assertCapturedEvents(ACTION_DOWN, ACTION_MOVE, ACTION_MOVE, ACTION_MOVE, ACTION_UP);
@@ -292,7 +251,7 @@
     @Test
     public void testUpEvent_OneFingerMove_clearStateAndInjectHoverEvents() {
         goFromStateClearTo(STATE_TOUCH_EXPLORING_1FINGER);
-        moveAtLeastTouchSlop(mLastEvent);
+        moveEachPointers(mLastEvent, p(10, 10));
         send(mLastEvent);
         // Wait 10 ms to make sure that hover enter and exit are not scheduled for the same moment.
         mHandler.fastForward(10);
@@ -318,7 +277,7 @@
 
         // Wait for the finger moving to the second view.
         mHandler.fastForward(oneThirdUserIntentTimeout);
-        moveAtLeastTouchSlop(mLastEvent);
+        moveEachPointers(mLastEvent, p(10, 10));
         send(mLastEvent);
 
         // Wait for the finger lifting from the second view.
@@ -443,6 +402,7 @@
         // Manually construct the next move event. Using moveEachPointers() will batch the move
         // event onto the pointer up event which will mean that the move event still has a pointer
         // count of 3.
+        // Todo: refactor to avoid using batching as there is no special reason to do it that way.
         float[] x = new float[2];
         float[] y = new float[2];
         x[0] = mLastEvent.getX(0) + 100;
@@ -774,9 +734,6 @@
         }
     }
 
-    private void moveAtLeastTouchSlop(MotionEvent event) {
-        moveEachPointers(event, p(2 * mTouchSlop, 0));
-    }
     /**
      * A {@link android.os.Handler} that doesn't process messages until {@link #fastForward(int)} is
      * invoked.
diff --git a/services/tests/servicestests/src/com/android/server/inputmethod/InputMethodUtilsTest.java b/services/tests/servicestests/src/com/android/server/inputmethod/InputMethodUtilsTest.java
index 6dd9171..a2f8c8b 100644
--- a/services/tests/servicestests/src/com/android/server/inputmethod/InputMethodUtilsTest.java
+++ b/services/tests/servicestests/src/com/android/server/inputmethod/InputMethodUtilsTest.java
@@ -1239,9 +1239,6 @@
                 methodMap, 0 /* userId */);
         assertEquals(0, settings.getCurrentUserId());
 
-        settings.isShowImeWithHardKeyboardEnabled();
-        verify(ownerUserContext.getContentResolver(), atLeastOnce()).getAttributionSource();
-
         settings.getEnabledInputMethodSubtypeListLocked(nonSystemIme, true);
         verify(ownerUserContext.getResources(), atLeastOnce()).getConfiguration();
 
@@ -1250,10 +1247,6 @@
         settings.switchCurrentUser(10 /* userId */);
         assertEquals(10, settings.getCurrentUserId());
 
-        settings.isShowImeWithHardKeyboardEnabled();
-        verify(TestContext.getSecondaryUserContext().getContentResolver(),
-                atLeastOnce()).getAttributionSource();
-
         settings.getEnabledInputMethodSubtypeListLocked(nonSystemIme, true);
         verify(TestContext.getSecondaryUserContext().getResources(),
                 atLeastOnce()).getConfiguration();
diff --git a/services/tests/servicestests/src/com/android/server/power/batterysaver/BatterySaverPolicyTest.java b/services/tests/servicestests/src/com/android/server/power/batterysaver/BatterySaverPolicyTest.java
index 835ccf0..6fffd75 100644
--- a/services/tests/servicestests/src/com/android/server/power/batterysaver/BatterySaverPolicyTest.java
+++ b/services/tests/servicestests/src/com/android/server/power/batterysaver/BatterySaverPolicyTest.java
@@ -112,7 +112,7 @@
         testServiceDefaultValue_On(ServiceType.NULL);
     }
 
-    @Suppress
+    @Suppress // TODO: b/317823111 - Remove once test fixed.
     @SmallTest
     public void testGetBatterySaverPolicy_PolicyVibration_DefaultValueCorrect() {
         testDefaultValue(
@@ -219,7 +219,7 @@
                 ServiceType.QUICK_DOZE);
     }
 
-    @Suppress
+    @Suppress // TODO: b/317823111 - Remove once test fixed.
     @SmallTest
     public void testUpdateConstants_getCorrectData() {
         mBatterySaverPolicy.updateConstantsLocked(BATTERY_SAVER_CONSTANTS, "");
@@ -327,6 +327,7 @@
         }
     }
 
+    @Suppress // TODO: b/317823111 - Remove once test fixed.
     public void testSetPolicyLevel_Adaptive() {
         mBatterySaverPolicy.setPolicyLevel(POLICY_LEVEL_ADAPTIVE);
 
diff --git a/services/tests/servicestests/src/com/android/server/utils/AnrTimerTest.java b/services/tests/servicestests/src/com/android/server/utils/AnrTimerTest.java
index 861d14a..6c085e0 100644
--- a/services/tests/servicestests/src/com/android/server/utils/AnrTimerTest.java
+++ b/services/tests/servicestests/src/com/android/server/utils/AnrTimerTest.java
@@ -23,17 +23,21 @@
 import android.os.Handler;
 import android.os.Looper;
 import android.os.Message;
+import android.util.Log;
 
 import android.platform.test.annotations.Presubmit;
 import androidx.test.filters.SmallTest;
 
 import com.android.internal.annotations.GuardedBy;
 
+import org.junit.Ignore;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.junit.runners.Parameterized;
 import org.junit.runners.Parameterized.Parameters;
 
+import java.io.PrintWriter;
+import java.io.StringWriter;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collection;
@@ -45,6 +49,9 @@
 @RunWith(Parameterized.class)
 public class AnrTimerTest {
 
+    // A log tag.
+    private static final String TAG = "AnrTimerTest";
+
     // The commonly used message timeout key.
     private static final int MSG_TIMEOUT = 1;
 
@@ -63,9 +70,7 @@
         }
     }
 
-    /**
-     * The test handler is a self-contained object for a single test.
-     */
+    /** The test helper is a self-contained object for a single test. */
     private static class Helper {
         final Object mLock = new Object();
 
@@ -114,7 +119,7 @@
     /**
      * Force AnrTimer to use the test parameter for the feature flag.
      */
-    class TestInjector extends AnrTimer.Injector {
+    private class TestInjector extends AnrTimer.Injector {
         @Override
         boolean anrTimerServiceEnabled() {
             return mEnabled;
@@ -124,9 +129,9 @@
     /**
      * An instrumented AnrTimer.
      */
-    private static class TestAnrTimer extends AnrTimer<TestArg> {
+    private class TestAnrTimer extends AnrTimer<TestArg> {
         private TestAnrTimer(Handler h, int key, String tag) {
-            super(h, key, tag);
+            super(h, key, tag, false, new TestInjector());
         }
 
         TestAnrTimer(Helper helper) {
@@ -173,35 +178,103 @@
     @Test
     public void testSimpleTimeout() throws Exception {
         Helper helper = new Helper(1);
-        TestAnrTimer timer = new TestAnrTimer(helper);
-        TestArg t = new TestArg(1, 1);
-        timer.start(t, 10);
-        // Delivery is immediate but occurs on a different thread.
-        assertTrue(helper.await(5000));
-        TestArg[] result = helper.messages(1);
-        validate(t, result[0]);
+        try (TestAnrTimer timer = new TestAnrTimer(helper)) {
+            // One-time check that the injector is working as expected.
+            assertEquals(mEnabled, timer.serviceEnabled());
+            TestArg t = new TestArg(1, 1);
+            timer.start(t, 10);
+            // Delivery is immediate but occurs on a different thread.
+            assertTrue(helper.await(5000));
+            TestArg[] result = helper.messages(1);
+            validate(t, result[0]);
+        }
     }
 
     /**
-     * Verify that if three timers are scheduled, they are delivered in time order.
+     * Verify that a restarted timer is delivered exactly once.  The initial timer value is very
+     * large, to ensure it does not expire before the timer can be restarted.
+     */
+    @Test
+    public void testTimerRestart() throws Exception {
+        Helper helper = new Helper(1);
+        try (TestAnrTimer timer = new TestAnrTimer(helper)) {
+            TestArg t = new TestArg(1, 1);
+            timer.start(t, 10000);
+            // Briefly pause.
+            assertFalse(helper.await(10));
+            timer.start(t, 10);
+            // Delivery is immediate but occurs on a different thread.
+            assertTrue(helper.await(5000));
+            TestArg[] result = helper.messages(1);
+            validate(t, result[0]);
+        }
+    }
+
+    /**
+     * Verify that a restarted timer is delivered exactly once.  The initial timer value is very
+     * large, to ensure it does not expire before the timer can be restarted.
+     */
+    @Test
+    public void testTimerZero() throws Exception {
+        Helper helper = new Helper(1);
+        try (TestAnrTimer timer = new TestAnrTimer(helper)) {
+            TestArg t = new TestArg(1, 1);
+            timer.start(t, 0);
+            // Delivery is immediate but occurs on a different thread.
+            assertTrue(helper.await(5000));
+            TestArg[] result = helper.messages(1);
+            validate(t, result[0]);
+        }
+    }
+
+    /**
+     * Verify that if three timers are scheduled on a single AnrTimer, they are delivered in time
+     * order.
      */
     @Test
     public void testMultipleTimers() throws Exception {
         // Expect three messages.
         Helper helper = new Helper(3);
-        TestAnrTimer timer = new TestAnrTimer(helper);
         TestArg t1 = new TestArg(1, 1);
         TestArg t2 = new TestArg(1, 2);
         TestArg t3 = new TestArg(1, 3);
-        timer.start(t1, 50);
-        timer.start(t2, 60);
-        timer.start(t3, 40);
-        // Delivery is immediate but occurs on a different thread.
-        assertTrue(helper.await(5000));
-        TestArg[] result = helper.messages(3);
-        validate(t3, result[0]);
-        validate(t1, result[1]);
-        validate(t2, result[2]);
+        try (TestAnrTimer timer = new TestAnrTimer(helper)) {
+            timer.start(t1, 50);
+            timer.start(t2, 60);
+            timer.start(t3, 40);
+            // Delivery is immediate but occurs on a different thread.
+            assertTrue(helper.await(5000));
+            TestArg[] result = helper.messages(3);
+            validate(t3, result[0]);
+            validate(t1, result[1]);
+            validate(t2, result[2]);
+        }
+    }
+
+    /**
+     * Verify that if three timers are scheduled on three separate AnrTimers, they are delivered
+     * in time order.
+     */
+    @Test
+    public void testMultipleServices() throws Exception {
+        // Expect three messages.
+        Helper helper = new Helper(3);
+        TestArg t1 = new TestArg(1, 1);
+        TestArg t2 = new TestArg(1, 2);
+        TestArg t3 = new TestArg(1, 3);
+        try (TestAnrTimer x1 = new TestAnrTimer(helper);
+             TestAnrTimer x2 = new TestAnrTimer(helper);
+             TestAnrTimer x3 = new TestAnrTimer(helper)) {
+            x1.start(t1, 50);
+            x2.start(t2, 60);
+            x3.start(t3, 40);
+            // Delivery is immediate but occurs on a different thread.
+            assertTrue(helper.await(5000));
+            TestArg[] result = helper.messages(3);
+            validate(t3, result[0]);
+            validate(t1, result[1]);
+            validate(t2, result[2]);
+        }
     }
 
     /**
@@ -211,20 +284,109 @@
     public void testCancelTimer() throws Exception {
         // Expect two messages.
         Helper helper = new Helper(2);
-        TestAnrTimer timer = new TestAnrTimer(helper);
         TestArg t1 = new TestArg(1, 1);
         TestArg t2 = new TestArg(1, 2);
         TestArg t3 = new TestArg(1, 3);
-        timer.start(t1, 50);
-        timer.start(t2, 60);
-        timer.start(t3, 40);
-        // Briefly pause.
-        assertFalse(helper.await(10));
-        timer.cancel(t1);
-        // Delivery is immediate but occurs on a different thread.
-        assertTrue(helper.await(5000));
-        TestArg[] result = helper.messages(2);
-        validate(t3, result[0]);
-        validate(t2, result[1]);
+        try (TestAnrTimer timer = new TestAnrTimer(helper)) {
+            timer.start(t1, 50);
+            timer.start(t2, 60);
+            timer.start(t3, 40);
+            // Briefly pause.
+            assertFalse(helper.await(10));
+            timer.cancel(t1);
+            // Delivery is immediate but occurs on a different thread.
+            assertTrue(helper.await(5000));
+            TestArg[] result = helper.messages(2);
+            validate(t3, result[0]);
+            validate(t2, result[1]);
+        }
+    }
+
+    /**
+     * Return the dump string.
+     */
+    private String getDumpOutput() {
+        StringWriter sw = new StringWriter();
+        PrintWriter pw = new PrintWriter(sw);
+        AnrTimer.dump(pw, true, new TestInjector());
+        pw.close();
+        return sw.getBuffer().toString();
+    }
+
+    /**
+     * Verify the dump output.
+     */
+    @Test
+    public void testDumpOutput() throws Exception {
+        String r1 = getDumpOutput();
+        assertEquals(false, r1.contains("timer:"));
+
+        Helper helper = new Helper(2);
+        TestArg t1 = new TestArg(1, 1);
+        TestArg t2 = new TestArg(1, 2);
+        TestArg t3 = new TestArg(1, 3);
+        try (TestAnrTimer timer = new TestAnrTimer(helper)) {
+            timer.start(t1, 5000);
+            timer.start(t2, 5000);
+            timer.start(t3, 5000);
+
+            String r2 = getDumpOutput();
+            // There are timers in the list if and only if the feature is enabled.
+            final boolean expected = mEnabled;
+            assertEquals(expected, r2.contains("timer:"));
+        }
+
+        String r3 = getDumpOutput();
+        assertEquals(false, r3.contains("timer:"));
+    }
+
+    /**
+     * Verify that GC works as expected.  This test will almost certainly be flaky, since it
+     * relies on the finalizers running, which is a best-effort on the part of the JVM.
+     * Therefore, the test is marked @Ignore.  Remove that annotation to run the test locally.
+     */
+    @Ignore
+    @Test
+    public void testGarbageCollection() throws Exception {
+        if (!mEnabled) return;
+
+        String r1 = getDumpOutput();
+        assertEquals(false, r1.contains("timer:"));
+
+        Helper helper = new Helper(2);
+        TestArg t1 = new TestArg(1, 1);
+        TestArg t2 = new TestArg(1, 2);
+        TestArg t3 = new TestArg(1, 3);
+        // The timer is explicitly not closed.  It is, however, scoped to the next block.
+        {
+            TestAnrTimer timer = new TestAnrTimer(helper);
+            timer.start(t1, 5000);
+            timer.start(t2, 5000);
+            timer.start(t3, 5000);
+
+            String r2 = getDumpOutput();
+            // There are timers in the list if and only if the feature is enabled.
+            final boolean expected = mEnabled;
+            assertEquals(expected, r2.contains("timer:"));
+        }
+
+        // Try to make finalizers run.  The timer object above should be a candidate.  Finalizers
+        // are run on their own thread, so pause this thread to give that thread some time.
+        String r3 = getDumpOutput();
+        for (int i = 0; i < 10 && r3.contains("timer:"); i++) {
+            Log.i(TAG, "requesting finalization " + i);
+            System.gc();
+            System.runFinalization();
+            Thread.sleep(4 * 1000);
+            r3 = getDumpOutput();
+        }
+
+        // The timer was not explicitly closed but it should have been implicitly closed by GC.
+        assertEquals(false, r3.contains("timer:"));
+    }
+
+    // TODO: [b/302724778] Remove manual JNI load
+    static {
+        System.loadLibrary("servicestestjni");
     }
 }
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
index 39779b0..f1edd9a 100755
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
@@ -303,7 +303,6 @@
 import org.junit.Assert;
 import org.junit.Before;
 import org.junit.ClassRule;
-import org.junit.Ignore;
 import org.junit.Rule;
 import org.junit.Test;
 import org.junit.rules.TestRule;
@@ -14061,7 +14060,6 @@
     }
 
     @Test
-    @Ignore("b/316989461")
     public void cancelNotificationsFromListener_rapidClear_oldNew_cancelOne()
             throws RemoteException {
         mSetFlagsRule.enableFlags(android.view.contentprotection.flags.Flags
@@ -14073,7 +14071,8 @@
         mService.addNotification(nr1);
 
         // Create old notification.
-        final NotificationRecord nr2 = generateNotificationRecord(mTestNotificationChannel, 0);
+        final NotificationRecord nr2 = generateNotificationRecord(mTestNotificationChannel,
+                System.currentTimeMillis() - 60000);
         mService.addNotification(nr2);
 
         // Cancel specific notifications via listener.
@@ -14091,16 +14090,17 @@
     }
 
     @Test
-    @Ignore("b/316989461")
     public void cancelNotificationsFromListener_rapidClear_old_cancelOne() throws RemoteException {
         mSetFlagsRule.enableFlags(android.view.contentprotection.flags.Flags
                 .FLAG_RAPID_CLEAR_NOTIFICATIONS_BY_LISTENER_APP_OP_ENABLED);
 
         // Create old notifications.
-        final NotificationRecord nr1 = generateNotificationRecord(mTestNotificationChannel, 0);
+        final NotificationRecord nr1 = generateNotificationRecord(mTestNotificationChannel,
+                System.currentTimeMillis() - 60000);
         mService.addNotification(nr1);
 
-        final NotificationRecord nr2 = generateNotificationRecord(mTestNotificationChannel, 0);
+        final NotificationRecord nr2 = generateNotificationRecord(mTestNotificationChannel,
+                System.currentTimeMillis() - 60000);
         mService.addNotification(nr2);
 
         // Cancel specific notifications via listener.
@@ -14119,7 +14119,6 @@
     }
 
     @Test
-    @Ignore("b/316989461")
     public void cancelNotificationsFromListener_rapidClear_oldNew_cancelOne_flagDisabled()
             throws RemoteException {
         mSetFlagsRule.disableFlags(android.view.contentprotection.flags.Flags
@@ -14131,7 +14130,8 @@
         mService.addNotification(nr1);
 
         // Create old notification.
-        final NotificationRecord nr2 = generateNotificationRecord(mTestNotificationChannel, 0);
+        final NotificationRecord nr2 = generateNotificationRecord(mTestNotificationChannel,
+                System.currentTimeMillis() - 60000);
         mService.addNotification(nr2);
 
         // Cancel specific notifications via listener.
@@ -14150,7 +14150,6 @@
     }
 
     @Test
-    @Ignore("b/316989461")
     public void cancelNotificationsFromListener_rapidClear_oldNew_cancelAll()
             throws RemoteException {
         mSetFlagsRule.enableFlags(android.view.contentprotection.flags.Flags
@@ -14162,7 +14161,8 @@
         mService.addNotification(nr1);
 
         // Create old notification.
-        final NotificationRecord nr2 = generateNotificationRecord(mTestNotificationChannel, 0);
+        final NotificationRecord nr2 = generateNotificationRecord(mTestNotificationChannel,
+                System.currentTimeMillis() - 60000);
         mService.addNotification(nr2);
 
         // Cancel all notifications via listener.
@@ -14179,16 +14179,17 @@
     }
 
     @Test
-    @Ignore("b/316989461")
     public void cancelNotificationsFromListener_rapidClear_old_cancelAll() throws RemoteException {
         mSetFlagsRule.enableFlags(android.view.contentprotection.flags.Flags
                 .FLAG_RAPID_CLEAR_NOTIFICATIONS_BY_LISTENER_APP_OP_ENABLED);
 
         // Create old notifications.
-        final NotificationRecord nr1 = generateNotificationRecord(mTestNotificationChannel, 0);
+        final NotificationRecord nr1 = generateNotificationRecord(mTestNotificationChannel,
+                System.currentTimeMillis() - 60000);
         mService.addNotification(nr1);
 
-        final NotificationRecord nr2 = generateNotificationRecord(mTestNotificationChannel, 0);
+        final NotificationRecord nr2 = generateNotificationRecord(mTestNotificationChannel,
+                System.currentTimeMillis() - 60000);
         mService.addNotification(nr2);
 
         // Cancel all notifications via listener.
@@ -14206,7 +14207,6 @@
     }
 
     @Test
-    @Ignore("b/316989461")
     public void cancelNotificationsFromListener_rapidClear_oldNew_cancelAll_flagDisabled()
             throws RemoteException {
         mSetFlagsRule.disableFlags(android.view.contentprotection.flags.Flags
@@ -14218,7 +14218,8 @@
         mService.addNotification(nr1);
 
         // Create old notification.
-        final NotificationRecord nr2 = generateNotificationRecord(mTestNotificationChannel, 0);
+        final NotificationRecord nr2 = generateNotificationRecord(mTestNotificationChannel,
+                System.currentTimeMillis() - 60000);
         mService.addNotification(nr2);
 
         // Cancel all notifications via listener.
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/ZenAdaptersTest.java b/services/tests/uiservicestests/src/com/android/server/notification/ZenAdaptersTest.java
index 6cc1c43..08af09c 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/ZenAdaptersTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/ZenAdaptersTest.java
@@ -20,7 +20,11 @@
 
 import static com.google.common.truth.Truth.assertThat;
 
+import android.app.Flags;
 import android.app.NotificationManager.Policy;
+import android.platform.test.annotations.DisableFlags;
+import android.platform.test.annotations.EnableFlags;
+import android.platform.test.flag.junit.SetFlagsRule;
 import android.service.notification.ZenPolicy;
 
 import androidx.test.filters.SmallTest;
@@ -28,6 +32,7 @@
 
 import com.android.server.UiServiceTestCase;
 
+import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
@@ -35,6 +40,9 @@
 @RunWith(AndroidJUnit4.class)
 public class ZenAdaptersTest extends UiServiceTestCase {
 
+    @Rule
+    public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
+
     @Test
     public void notificationPolicyToZenPolicy_allCallers() {
         Policy policy = new Policy(Policy.PRIORITY_CATEGORY_CALLS, Policy.PRIORITY_SENDERS_ANY, 0);
@@ -127,4 +135,35 @@
         assertThat(zenPolicy.getVisualEffectPeek()).isEqualTo(ZenPolicy.STATE_UNSET);
         assertThat(zenPolicy.getVisualEffectStatusBar()).isEqualTo(ZenPolicy.STATE_UNSET);
     }
+
+    @Test
+    @EnableFlags(Flags.FLAG_MODES_API)
+    public void notificationPolicyToZenPolicy_modesApi_priorityChannels() {
+        Policy policy = new Policy(0, 0, 0, 0,
+                Policy.policyState(false, true), 0);
+
+        ZenPolicy zenPolicy = notificationPolicyToZenPolicy(policy);
+        assertThat(zenPolicy.getAllowedChannels()).isEqualTo(ZenPolicy.CHANNEL_TYPE_PRIORITY);
+
+        Policy notAllowed = new Policy(0, 0, 0, 0,
+                Policy.policyState(false, false), 0);
+        ZenPolicy zenPolicyNotAllowed = notificationPolicyToZenPolicy(notAllowed);
+        assertThat(zenPolicyNotAllowed.getAllowedChannels()).isEqualTo(ZenPolicy.CHANNEL_TYPE_NONE);
+    }
+
+    @Test
+    @DisableFlags(Flags.FLAG_MODES_API)
+    public void notificationPolicyToZenPolicy_noModesApi_priorityChannelsUnset() {
+        Policy policy = new Policy(0, 0, 0, 0,
+                Policy.policyState(false, true), 0);
+
+        ZenPolicy zenPolicy = notificationPolicyToZenPolicy(policy);
+        assertThat(zenPolicy.getAllowedChannels()).isEqualTo(ZenPolicy.CHANNEL_TYPE_UNSET);
+
+        Policy notAllowed = new Policy(0, 0, 0, 0,
+                Policy.policyState(false, false), 0);
+        ZenPolicy zenPolicyNotAllowed = notificationPolicyToZenPolicy(notAllowed);
+        assertThat(zenPolicyNotAllowed.getAllowedChannels())
+                .isEqualTo(ZenPolicy.CHANNEL_TYPE_UNSET);
+    }
 }
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java
index 25c0cd9..f84d8e9 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java
@@ -3876,6 +3876,7 @@
                 .allowCalls(PEOPLE_TYPE_CONTACTS)
                 .allowConversations(CONVERSATION_SENDERS_IMPORTANT)
                 .hideAllVisualEffects()
+                .allowChannels(ZenPolicy.CHANNEL_TYPE_PRIORITY)
                 .build();
         assertThat(mZenModeHelper.mConfig.automaticRules.values())
                 .comparingElementsUsing(IGNORE_TIMESTAMPS)
@@ -3907,6 +3908,7 @@
                 .allowCalls(PEOPLE_TYPE_STARRED)
                 .allowConversations(CONVERSATION_SENDERS_IMPORTANT)
                 .hideAllVisualEffects()
+                .allowChannels(ZenPolicy.CHANNEL_TYPE_PRIORITY)
                 .build();
         assertThat(mZenModeHelper.mConfig.automaticRules.values())
                 .comparingElementsUsing(IGNORE_TIMESTAMPS)
diff --git a/services/tests/vibrator/Android.bp b/services/tests/vibrator/Android.bp
index 6f37967..66dcaff 100644
--- a/services/tests/vibrator/Android.bp
+++ b/services/tests/vibrator/Android.bp
@@ -31,13 +31,13 @@
         "frameworks-base-testutils",
         "frameworks-services-vibrator-testutils",
         "junit",
-        "mockito-target-minus-junit4",
+        "mockito-target-inline-minus-junit4",
         "platform-test-annotations",
         "service-permission.stubs.system_server",
         "services.core",
         "flag-junit",
     ],
-
+    jni_libs: ["libdexmakerjvmtiagent"],
     platform_apis: true,
     certificate: "platform",
     dxflags: ["--multi-dex"],
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 bbca704e..f9fe6a9 100644
--- a/services/tests/vibrator/src/com/android/server/vibrator/VibrationScalerTest.java
+++ b/services/tests/vibrator/src/com/android/server/vibrator/VibrationScalerTest.java
@@ -43,12 +43,17 @@
 import android.os.VibrationEffect;
 import android.os.Vibrator;
 import android.os.test.TestLooper;
+import android.os.vibrator.Flags;
 import android.os.vibrator.PrebakedSegment;
 import android.os.vibrator.PrimitiveSegment;
 import android.os.vibrator.StepSegment;
 import android.os.vibrator.VibrationConfig;
 import android.os.vibrator.VibrationEffectSegment;
+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;
 
@@ -68,6 +73,9 @@
 
     @Rule public MockitoRule mMockitoRule = MockitoJUnit.rule();
     @Rule public FakeSettingsProviderRule mSettingsProviderRule = FakeSettingsProvider.rule();
+    @Rule
+    public final CheckFlagsRule mCheckFlagsRule =
+            DeviceFlagsValueProvider.createCheckFlagsRule();
 
     @Mock private PowerManagerInternal mPowerManagerInternalMock;
     @Mock private PackageManagerInternal mPackageManagerInternalMock;
@@ -256,6 +264,29 @@
         assertEquals(0.5, scaled.getScale(), 1e-5);
     }
 
+    @Test
+    @RequiresFlagsEnabled(Flags.FLAG_ADAPTIVE_HAPTICS_ENABLED)
+    public void scale_withAdaptiveHaptics_scalesVibrationsCorrectly() {
+        setDefaultIntensity(USAGE_RINGTONE, VIBRATION_INTENSITY_HIGH);
+        setDefaultIntensity(USAGE_NOTIFICATION, VIBRATION_INTENSITY_HIGH);
+
+        SparseArray<Float> adaptiveHapticsScales = new SparseArray<>();
+        adaptiveHapticsScales.put(USAGE_RINGTONE, 0.5f);
+        adaptiveHapticsScales.put(USAGE_NOTIFICATION, 0.5f);
+        mVibrationScaler.updateAdaptiveHapticsScales(adaptiveHapticsScales);
+
+        StepSegment scaled = getFirstSegment(mVibrationScaler.scale(
+                VibrationEffect.createOneShot(128, 128), USAGE_RINGTONE));
+        // Ringtone scales down.
+        assertTrue(scaled.getAmplitude() < 0.5);
+
+        scaled = getFirstSegment(mVibrationScaler.scale(
+                VibrationEffect.createWaveform(new long[]{128}, new int[]{128}, -1),
+                USAGE_NOTIFICATION));
+        // Notification scales down.
+        assertTrue(scaled.getAmplitude() < 0.5);
+    }
+
     private void setDefaultIntensity(@VibrationAttributes.Usage int usage,
             @Vibrator.VibrationIntensity int intensity) {
         when(mVibrationConfigMock.getDefaultVibrationIntensity(eq(usage))).thenReturn(intensity);
diff --git a/services/tests/vibrator/src/com/android/server/vibrator/VibratorControlServiceTest.java b/services/tests/vibrator/src/com/android/server/vibrator/VibratorControlServiceTest.java
index 49efd1b..1e0b1df 100644
--- a/services/tests/vibrator/src/com/android/server/vibrator/VibratorControlServiceTest.java
+++ b/services/tests/vibrator/src/com/android/server/vibrator/VibratorControlServiceTest.java
@@ -16,21 +16,49 @@
 
 package com.android.server.vibrator;
 
+import static android.os.VibrationAttributes.USAGE_ALARM;
+import static android.os.VibrationAttributes.USAGE_COMMUNICATION_REQUEST;
+import static android.os.VibrationAttributes.USAGE_NOTIFICATION;
+
 import static com.google.common.truth.Truth.assertThat;
 
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyZeroInteractions;
+
+import android.frameworks.vibrator.ScaleParam;
+import android.frameworks.vibrator.VibrationParam;
 import android.os.RemoteException;
+import android.util.SparseArray;
 
 import org.junit.Before;
+import org.junit.Rule;
 import org.junit.Test;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
+
+import java.util.ArrayList;
+import java.util.List;
 
 public class VibratorControlServiceTest {
 
+    @Rule
+    public MockitoRule rule = MockitoJUnit.rule();
+
+    @Mock
+    private VibrationScaler mMockVibrationScaler;
+    @Captor
+    private ArgumentCaptor<SparseArray<Float>> mVibrationScalesCaptor;
+
     private VibratorControlService mVibratorControlService;
     private final Object mLock = new Object();
 
     @Before
     public void setUp() throws Exception {
-        mVibratorControlService = new VibratorControlService(new VibratorControllerHolder(), mLock);
+        mVibratorControlService = new VibratorControlService(new VibratorControllerHolder(),
+                mMockVibrationScaler, mLock);
     }
 
     @Test
@@ -47,6 +75,8 @@
         FakeVibratorController fakeController = new FakeVibratorController();
         mVibratorControlService.registerVibratorController(fakeController);
         mVibratorControlService.unregisterVibratorController(fakeController);
+
+        verify(mMockVibrationScaler).updateAdaptiveHapticsScales(null);
         assertThat(fakeController.isLinkedToDeath).isFalse();
     }
 
@@ -56,8 +86,91 @@
         FakeVibratorController fakeController1 = new FakeVibratorController();
         FakeVibratorController fakeController2 = new FakeVibratorController();
         mVibratorControlService.registerVibratorController(fakeController1);
-
         mVibratorControlService.unregisterVibratorController(fakeController2);
+
+        verifyZeroInteractions(mMockVibrationScaler);
         assertThat(fakeController1.isLinkedToDeath).isTrue();
     }
+
+    @Test
+    public void testSetVibrationParams_cachesAdaptiveHapticsScalesCorrectly()
+            throws RemoteException {
+        FakeVibratorController fakeController = new FakeVibratorController();
+        mVibratorControlService.registerVibratorController(fakeController);
+        SparseArray<Float> vibrationScales = new SparseArray<>();
+        vibrationScales.put(ScaleParam.TYPE_ALARM, 0.7f);
+        vibrationScales.put(ScaleParam.TYPE_NOTIFICATION, 0.4f);
+
+        mVibratorControlService.setVibrationParams(generateVibrationParams(vibrationScales),
+                fakeController);
+
+        verify(mMockVibrationScaler).updateAdaptiveHapticsScales(mVibrationScalesCaptor.capture());
+        SparseArray<Float> cachedVibrationScales = mVibrationScalesCaptor.getValue();
+        assertThat(cachedVibrationScales.size()).isEqualTo(3);
+        assertThat(cachedVibrationScales.keyAt(0)).isEqualTo(USAGE_ALARM);
+        assertThat(cachedVibrationScales.valueAt(0)).isEqualTo(0.7f);
+        assertThat(cachedVibrationScales.keyAt(1)).isEqualTo(USAGE_NOTIFICATION);
+        assertThat(cachedVibrationScales.valueAt(1)).isEqualTo(0.4f);
+        // Setting ScaleParam.TYPE_NOTIFICATION will update vibration scaling for both
+        // notification and communication request usages.
+        assertThat(cachedVibrationScales.keyAt(2)).isEqualTo(USAGE_COMMUNICATION_REQUEST);
+        assertThat(cachedVibrationScales.valueAt(2)).isEqualTo(0.4f);
+    }
+
+    @Test
+    public void testSetVibrationParams_withUnregisteredController_ignoresRequest()
+            throws RemoteException {
+        FakeVibratorController fakeController = new FakeVibratorController();
+
+        SparseArray<Float> vibrationScales = new SparseArray<>();
+        vibrationScales.put(ScaleParam.TYPE_ALARM, 0.7f);
+        vibrationScales.put(ScaleParam.TYPE_NOTIFICATION, 0.4f);
+
+        mVibratorControlService.setVibrationParams(generateVibrationParams(vibrationScales),
+                fakeController);
+
+        verifyZeroInteractions(mMockVibrationScaler);
+    }
+
+    @Test
+    public void testClearVibrationParams_clearsCachedAdaptiveHapticsScales()
+            throws RemoteException {
+        FakeVibratorController fakeController = new FakeVibratorController();
+        mVibratorControlService.registerVibratorController(fakeController);
+        mVibratorControlService.clearVibrationParams(ScaleParam.TYPE_ALARM, fakeController);
+
+        verify(mMockVibrationScaler).updateAdaptiveHapticsScales(null);
+    }
+
+    @Test
+    public void testClearVibrationParams_withUnregisteredController_ignoresRequest()
+            throws RemoteException {
+        FakeVibratorController fakeController = new FakeVibratorController();
+
+        mVibratorControlService.clearVibrationParams(ScaleParam.TYPE_ALARM, fakeController);
+
+        verifyZeroInteractions(mMockVibrationScaler);
+    }
+
+    private VibrationParam[] generateVibrationParams(SparseArray<Float> vibrationScales) {
+        List<VibrationParam> vibrationParamList = new ArrayList<>();
+        for (int i = 0; i < vibrationScales.size(); i++) {
+            int type = vibrationScales.keyAt(i);
+            float scale = vibrationScales.valueAt(i);
+
+            vibrationParamList.add(generateVibrationParam(type, scale));
+        }
+
+        return vibrationParamList.toArray(new VibrationParam[0]);
+    }
+
+    private VibrationParam generateVibrationParam(int type, float scale) {
+        ScaleParam scaleParam = new ScaleParam();
+        scaleParam.typesMask = type;
+        scaleParam.scale = scale;
+        VibrationParam vibrationParam = new VibrationParam();
+        vibrationParam.setScale(scaleParam);
+
+        return vibrationParam;
+    }
 }
diff --git a/services/tests/wmtests/src/com/android/server/policy/PhoneWindowManagerTests.java b/services/tests/wmtests/src/com/android/server/policy/PhoneWindowManagerTests.java
index 5363583..29467f2 100644
--- a/services/tests/wmtests/src/com/android/server/policy/PhoneWindowManagerTests.java
+++ b/services/tests/wmtests/src/com/android/server/policy/PhoneWindowManagerTests.java
@@ -16,6 +16,10 @@
 
 package com.android.server.policy;
 
+import static android.view.WindowManager.LayoutParams.TYPE_ACCESSIBILITY_OVERLAY;
+import static android.view.WindowManager.LayoutParams.TYPE_WALLPAPER;
+import static android.view.WindowManagerGlobal.ADD_OKAY;
+
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.doNothing;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.mock;
@@ -26,11 +30,16 @@
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.when;
 
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.junit.Assert.assertEquals;
 import static org.mockito.ArgumentMatchers.anyBoolean;
 import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.ArgumentMatchers.anyString;
 
 import android.app.ActivityManager;
+import android.app.AppOpsManager;
+import android.platform.test.flag.junit.SetFlagsRule;
 
 import androidx.test.filters.SmallTest;
 
@@ -39,6 +48,7 @@
 
 import org.junit.After;
 import org.junit.Before;
+import org.junit.Rule;
 import org.junit.Test;
 
 /**
@@ -50,6 +60,9 @@
 @SmallTest
 public class PhoneWindowManagerTests {
 
+    @Rule
+    public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
+
     PhoneWindowManager mPhoneWindowManager;
 
     @Before
@@ -85,6 +98,36 @@
         verify(mPhoneWindowManager).createHomeDockIntent();
     }
 
+    @Test
+    public void testCheckAddPermission_withoutAccessibilityOverlay_noAccessibilityAppOpLogged() {
+        mSetFlagsRule.enableFlags(android.view.contentprotection.flags.Flags
+                .FLAG_CREATE_ACCESSIBILITY_OVERLAY_APP_OP_ENABLED);
+        int[] outAppOp = new int[1];
+        assertEquals(ADD_OKAY, mPhoneWindowManager.checkAddPermission(TYPE_WALLPAPER,
+                /* isRoundedCornerOverlay= */ false, "test.pkg", outAppOp));
+        assertThat(outAppOp[0]).isEqualTo(AppOpsManager.OP_NONE);
+    }
+
+    @Test
+    public void testCheckAddPermission_withAccessibilityOverlay() {
+        mSetFlagsRule.enableFlags(android.view.contentprotection.flags.Flags
+                .FLAG_CREATE_ACCESSIBILITY_OVERLAY_APP_OP_ENABLED);
+        int[] outAppOp = new int[1];
+        assertEquals(ADD_OKAY, mPhoneWindowManager.checkAddPermission(TYPE_ACCESSIBILITY_OVERLAY,
+                /* isRoundedCornerOverlay= */ false, "test.pkg", outAppOp));
+        assertThat(outAppOp[0]).isEqualTo(AppOpsManager.OP_CREATE_ACCESSIBILITY_OVERLAY);
+    }
+
+    @Test
+    public void testCheckAddPermission_withAccessibilityOverlay_flagDisabled() {
+        mSetFlagsRule.disableFlags(android.view.contentprotection.flags.Flags
+                .FLAG_CREATE_ACCESSIBILITY_OVERLAY_APP_OP_ENABLED);
+        int[] outAppOp = new int[1];
+        assertEquals(ADD_OKAY, mPhoneWindowManager.checkAddPermission(TYPE_ACCESSIBILITY_OVERLAY,
+                /* isRoundedCornerOverlay= */ false, "test.pkg", outAppOp));
+        assertThat(outAppOp[0]).isEqualTo(AppOpsManager.OP_NONE);
+    }
+
     private void mockStartDockOrHome() throws Exception {
         doNothing().when(ActivityManager.getService()).stopAppSwitches();
         ActivityTaskManagerInternal mMockActivityTaskManagerInternal =
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java
index 8a9c05d..c82f751 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java
@@ -88,6 +88,7 @@
 import android.app.ActivityOptions;
 import android.app.AppOpsManager;
 import android.app.BackgroundStartPrivileges;
+import android.app.ComponentOptions.BackgroundActivityStartMode;
 import android.app.IApplicationThread;
 import android.app.PictureInPictureParams;
 import android.content.ComponentName;
@@ -914,24 +915,78 @@
                 .mockStatic(FrameworkStatsLog.class)
                 .strictness(Strictness.LENIENT)
                 .startMocking();
-        doReturn(PERMISSION_GRANTED).when(() -> ActivityTaskManagerService.checkPermission(
-                eq(START_ACTIVITIES_FROM_BACKGROUND),
-                anyInt(), anyInt()));
-        runAndVerifyBackgroundActivityStartsSubtest(
-                "allowed_notAborted", false,
-                UNIMPORTANT_UID, false, PROCESS_STATE_BOUND_TOP,
-                UNIMPORTANT_UID2, false, PROCESS_STATE_BOUND_TOP,
-                false, true, false, false, false, false, false, false);
-        verify(() -> FrameworkStatsLog.write(FrameworkStatsLog.BAL_ALLOWED,
-                "",  // activity name
-                BackgroundActivityStartController.BAL_ALLOW_PERMISSION,
-                UNIMPORTANT_UID,
-                UNIMPORTANT_UID2));
-        mockingSession.finishMocking();
+        try {
+            doReturn(PERMISSION_GRANTED).when(() -> ActivityTaskManagerService.checkPermission(
+                    eq(START_ACTIVITIES_FROM_BACKGROUND),
+                    anyInt(), anyInt()));
+            runAndVerifyBackgroundActivityStartsSubtest(
+                    "allowed_notAborted", false,
+                    UNIMPORTANT_UID, false, PROCESS_STATE_BOUND_TOP,
+                    UNIMPORTANT_UID2, false, PROCESS_STATE_BOUND_TOP,
+                    false, true, false, false, false, false, false, false);
+            verify(() -> FrameworkStatsLog.write(FrameworkStatsLog.BAL_ALLOWED,
+                    "",  // activity name
+                    BackgroundActivityStartController.BAL_ALLOW_PERMISSION,
+                    UNIMPORTANT_UID,
+                    UNIMPORTANT_UID2,
+                    BackgroundActivityStartController.BAL_ALLOW_PERMISSION,
+                    true, // opt in
+                    false, // but no explicit opt in
+                    BackgroundActivityStartController.BAL_BLOCK,
+                    true, // opt in
+                    false // but no explicit opt in
+            ));
+        } finally {
+            mockingSession.finishMocking();
+        }
     }
 
     /**
-     * This test ensures proper logging for BAL_ALLOW_PENDING_INTENT.
+     * This test ensures proper logging for BAL_ALLOW_PENDING_INTENT, when the PendingIntent sender
+     * is the only reason BAL is allowed.
+     */
+    @Test
+    public void testBackgroundActivityStartsAllowed_loggingOnlyPendingIntentAllowed() {
+        doReturn(false).when(mAtm).isBackgroundActivityStartsEnabled();
+        MockitoSession mockingSession = mockitoSession()
+                .mockStatic(ActivityTaskManagerService.class)
+                .mockStatic(FrameworkStatsLog.class)
+                .mockStatic(PendingIntentRecord.class)
+                .strictness(Strictness.LENIENT)
+                .startMocking();
+        try {
+            doReturn(PERMISSION_GRANTED).when(() -> ActivityTaskManagerService.checkPermission(
+                    eq(START_ACTIVITIES_FROM_BACKGROUND),
+                    anyInt(), anyInt()));
+            doReturn(BackgroundStartPrivileges.allowBackgroundActivityStarts(null)).when(
+                    () -> PendingIntentRecord.getBackgroundStartPrivilegesAllowedByCaller(
+                            anyObject(), anyInt(), anyObject()));
+            runAndVerifyBackgroundActivityStartsSubtest(
+                    "allowed_notAborted", false,
+                    UNIMPORTANT_UID, false, PROCESS_STATE_BOUND_TOP,
+                    Process.SYSTEM_UID, true, PROCESS_STATE_BOUND_TOP,
+                    false, true, false, false, false, false, false, false,
+                    ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_DENIED);
+            verify(() -> FrameworkStatsLog.write(FrameworkStatsLog.BAL_ALLOWED,
+                    DEFAULT_COMPONENT_PACKAGE_NAME + "/" + DEFAULT_COMPONENT_PACKAGE_NAME,
+                    BackgroundActivityStartController.BAL_ALLOW_PENDING_INTENT,
+                    UNIMPORTANT_UID,
+                    Process.SYSTEM_UID,
+                    BackgroundActivityStartController.BAL_ALLOW_PERMISSION,
+                    false, // opt in
+                    true, // explicit opt out
+                    BackgroundActivityStartController.BAL_ALLOW_VISIBLE_WINDOW,
+                    true, // opt in
+                    false // but no explicit opt in
+            ));
+        } finally {
+            mockingSession.finishMocking();
+        }
+    }
+
+    /**
+     * This test ensures proper logging for BAL_ALLOW_PENDING_INTENT, when the PendingIntent sender
+     * is not the primary reason to allow BAL (but the creator).
      */
     @Test
     public void testBackgroundActivityStartsAllowed_loggingPendingIntentAllowed() {
@@ -942,23 +997,34 @@
                 .mockStatic(PendingIntentRecord.class)
                 .strictness(Strictness.LENIENT)
                 .startMocking();
-        doReturn(PERMISSION_GRANTED).when(() -> ActivityTaskManagerService.checkPermission(
-                eq(START_ACTIVITIES_FROM_BACKGROUND),
-                anyInt(), anyInt()));
-        doReturn(BackgroundStartPrivileges.allowBackgroundActivityStarts(null)).when(
-                () -> PendingIntentRecord.getBackgroundStartPrivilegesAllowedByCaller(
-                anyObject(), anyInt(), anyObject()));
-        runAndVerifyBackgroundActivityStartsSubtest(
-                "allowed_notAborted", false,
-                UNIMPORTANT_UID, false, PROCESS_STATE_BOUND_TOP,
-                Process.SYSTEM_UID, true, PROCESS_STATE_BOUND_TOP,
-                false, true, false, false, false, false, false, false);
-        verify(() -> FrameworkStatsLog.write(FrameworkStatsLog.BAL_ALLOWED,
-                DEFAULT_COMPONENT_PACKAGE_NAME + "/" + DEFAULT_COMPONENT_PACKAGE_NAME,
-                BackgroundActivityStartController.BAL_ALLOW_PENDING_INTENT,
-                UNIMPORTANT_UID,
-                Process.SYSTEM_UID));
-        mockingSession.finishMocking();
+        try {
+            doReturn(PERMISSION_GRANTED).when(() -> ActivityTaskManagerService.checkPermission(
+                    eq(START_ACTIVITIES_FROM_BACKGROUND),
+                    anyInt(), anyInt()));
+            doReturn(BackgroundStartPrivileges.allowBackgroundActivityStarts(null)).when(
+                    () -> PendingIntentRecord.getBackgroundStartPrivilegesAllowedByCaller(
+                            anyObject(), anyInt(), anyObject()));
+            runAndVerifyBackgroundActivityStartsSubtest(
+                    "allowed_notAborted", false,
+                    UNIMPORTANT_UID, false, PROCESS_STATE_BOUND_TOP,
+                    Process.SYSTEM_UID, true, PROCESS_STATE_BOUND_TOP,
+                    false, true, false, false, false, false, false, false,
+                    ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_ALLOWED);
+            verify(() -> FrameworkStatsLog.write(FrameworkStatsLog.BAL_ALLOWED,
+                    "",
+                    BackgroundActivityStartController.BAL_ALLOW_PERMISSION,
+                    UNIMPORTANT_UID,
+                    Process.SYSTEM_UID,
+                    BackgroundActivityStartController.BAL_ALLOW_PERMISSION,
+                    true, // opt in
+                    true, // explicit opt in
+                    BackgroundActivityStartController.BAL_ALLOW_VISIBLE_WINDOW,
+                    true, // opt in
+                    false // but no explicit opt in
+            ));
+        } finally {
+            mockingSession.finishMocking();
+        }
     }
 
     private void runAndVerifyBackgroundActivityStartsSubtest(String name, boolean shouldHaveAborted,
@@ -971,6 +1037,27 @@
             boolean isCallingUidAffiliatedProfileOwner,
             boolean isPinnedSingleInstance,
             boolean hasSystemExemptAppOp) {
+        runAndVerifyBackgroundActivityStartsSubtest(name, shouldHaveAborted, callingUid,
+                callingUidHasVisibleWindow, callingUidProcState, realCallingUid,
+                realCallingUidHasVisibleWindow,  realCallingUidProcState, hasForegroundActivities,
+                callerIsRecents, callerIsTempAllowed,
+                callerIsInstrumentingWithBackgroundActivityStartPrivileges,
+                isCallingUidDeviceOwner, isCallingUidAffiliatedProfileOwner, isPinnedSingleInstance,
+                hasSystemExemptAppOp,
+                ActivityOptions.MODE_BACKGROUND_ACTIVITY_START_SYSTEM_DEFINED);
+    }
+
+    private void runAndVerifyBackgroundActivityStartsSubtest(String name, boolean shouldHaveAborted,
+            int callingUid, boolean callingUidHasVisibleWindow, int callingUidProcState,
+            int realCallingUid, boolean realCallingUidHasVisibleWindow, int realCallingUidProcState,
+            boolean hasForegroundActivities, boolean callerIsRecents,
+            boolean callerIsTempAllowed,
+            boolean callerIsInstrumentingWithBackgroundActivityStartPrivileges,
+            boolean isCallingUidDeviceOwner,
+            boolean isCallingUidAffiliatedProfileOwner,
+            boolean isPinnedSingleInstance,
+            boolean hasSystemExemptAppOp,
+            @BackgroundActivityStartMode int pendingIntentCreatorBackgroundActivityStartMode) {
         // window visibility
         doReturn(callingUidHasVisibleWindow).when(mAtm).hasActiveVisibleWindow(callingUid);
         doReturn(realCallingUidHasVisibleWindow).when(mAtm).hasActiveVisibleWindow(realCallingUid);
@@ -1022,7 +1109,10 @@
             launchMode = LAUNCH_SINGLE_INSTANCE;
         }
 
-        final ActivityOptions options = spy(ActivityOptions.makeBasic());
+        ActivityOptions rawOptions = ActivityOptions.makeBasic()
+                .setPendingIntentCreatorBackgroundActivityStartMode(
+                        pendingIntentCreatorBackgroundActivityStartMode);
+        final ActivityOptions options = spy(rawOptions);
         ActivityRecord[] outActivity = new ActivityRecord[1];
         ActivityStarter starter = prepareStarter(
                 FLAG_ACTIVITY_NEW_TASK, true, launchMode)
diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
index 6497ee9..782d89c 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
@@ -115,6 +115,9 @@
 import android.os.RemoteException;
 import android.os.SystemClock;
 import android.platform.test.annotations.Presubmit;
+import android.platform.test.annotations.RequiresFlagsDisabled;
+import android.platform.test.flag.junit.CheckFlagsRule;
+import android.platform.test.flag.junit.DeviceFlagsValueProvider;
 import android.util.ArraySet;
 import android.util.DisplayMetrics;
 import android.view.Display;
@@ -146,6 +149,7 @@
 import com.android.server.policy.WindowManagerPolicy;
 import com.android.server.wm.utils.WmDisplayCutout;
 
+import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.ArgumentCaptor;
@@ -172,6 +176,10 @@
 @RunWith(WindowTestRunner.class)
 public class DisplayContentTests extends WindowTestsBase {
 
+    @Rule
+    public final CheckFlagsRule mCheckFlagsRule =
+            DeviceFlagsValueProvider.createCheckFlagsRule();
+
     @SetupWindows(addAllCommonWindows = true)
     @Test
     public void testForAllWindows() {
@@ -508,6 +516,7 @@
      * Tests tapping on a root task in different display results in window gaining focus.
      */
     @Test
+    @RequiresFlagsDisabled(com.android.input.flags.Flags.FLAG_REMOVE_POINTER_EVENT_TRACKING_IN_WM)
     public void testInputEventBringsCorrectDisplayInFocus() {
         DisplayContent dc0 = mWm.getDefaultDisplayContentLocked();
         // Create a second display
diff --git a/services/tests/wmtests/src/com/android/server/wm/LetterboxUiControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/LetterboxUiControllerTest.java
index 985be42..4e4bbfe 100644
--- a/services/tests/wmtests/src/com/android/server/wm/LetterboxUiControllerTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/LetterboxUiControllerTest.java
@@ -697,6 +697,31 @@
 
     @Test
     @EnableCompatChanges({OVERRIDE_ANY_ORIENTATION_TO_USER})
+    public void testOverrideOrientationIfNeeded_fullscreenOverrides_optOutSystem_returnsUser()
+            throws Exception {
+        mockThatProperty(PROPERTY_COMPAT_ALLOW_ORIENTATION_OVERRIDE, /* value */ false);
+        prepareActivityThatShouldApplyUserFullscreenOverride();
+
+        // fullscreen override still applied
+        assertEquals(SCREEN_ORIENTATION_USER, mController.overrideOrientationIfNeeded(
+                /* candidate */ SCREEN_ORIENTATION_PORTRAIT));
+    }
+
+    @Test
+    @EnableCompatChanges({OVERRIDE_ANY_ORIENTATION_TO_USER})
+    public void testOverrideOrientationIfNeeded_fullscreenOverrides_optOutUser_returnsUser()
+            throws Exception {
+        mockThatProperty(PROPERTY_COMPAT_ALLOW_USER_ASPECT_RATIO_FULLSCREEN_OVERRIDE,
+                /* value */ false);
+        prepareActivityThatShouldApplyUserFullscreenOverride();
+
+        // fullscreen override still applied
+        assertEquals(SCREEN_ORIENTATION_USER, mController.overrideOrientationIfNeeded(
+                /* candidate */ SCREEN_ORIENTATION_PORTRAIT));
+    }
+
+    @Test
+    @EnableCompatChanges({OVERRIDE_ANY_ORIENTATION_TO_USER})
     public void testOverrideOrientationIfNeeded_fullscreenOverrideEnabled_returnsUnchanged()
             throws Exception {
         mDisplayContent.setIgnoreOrientationRequest(false);
diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java
index d36ee2c..a88285a 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java
@@ -34,6 +34,7 @@
 import static android.window.TaskFragmentOperation.OP_TYPE_REPARENT_ACTIVITY_TO_TASK_FRAGMENT;
 import static android.window.TaskFragmentOperation.OP_TYPE_SET_ADJACENT_TASK_FRAGMENTS;
 import static android.window.TaskFragmentOperation.OP_TYPE_SET_ANIMATION_PARAMS;
+import static android.window.TaskFragmentOperation.OP_TYPE_SET_DIM_ON_TASK;
 import static android.window.TaskFragmentOperation.OP_TYPE_START_ACTIVITY_IN_TASK_FRAGMENT;
 import static android.window.TaskFragmentOrganizer.KEY_ERROR_CALLBACK_OP_TYPE;
 import static android.window.TaskFragmentOrganizer.KEY_ERROR_CALLBACK_THROWABLE;
@@ -870,12 +871,19 @@
                 .setAnimationParams(animationParams)
                 .build();
         mTransaction.addTaskFragmentOperation(mFragmentToken, operation);
+        final TaskFragmentOperation dimOperation = new TaskFragmentOperation.Builder(
+                OP_TYPE_SET_DIM_ON_TASK)
+                .setDimOnTask(true)
+                .build();
+        mTransaction.addTaskFragmentOperation(mFragmentToken, dimOperation);
         mOrganizer.applyTransaction(mTransaction, TASK_FRAGMENT_TRANSIT_CHANGE,
                 false /* shouldApplyIndependently */);
         assertApplyTransactionAllowed(mTransaction);
 
         assertEquals(animationParams, mTaskFragment.getAnimationParams());
         assertEquals(Color.GREEN, mTaskFragment.getAnimationParams().getAnimationBackgroundColor());
+
+        assertTrue(mTaskFragment.isDimmingOnParentTask());
     }
 
     @Test
diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskFragmentTest.java b/services/tests/wmtests/src/com/android/server/wm/TaskFragmentTest.java
index 875e708..e9fe4bb 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskFragmentTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskFragmentTest.java
@@ -684,6 +684,9 @@
         // Return Task bounds if dimming on parent Task.
         final Rect dimBounds = new Rect();
         mTaskFragment.setEmbeddedDimArea(EMBEDDED_DIM_AREA_PARENT_TASK);
+        final Dimmer dimmer = mTaskFragment.getDimmer();
+        spyOn(dimmer);
+        doReturn(taskBounds).when(dimmer).getDimBounds();
         mTaskFragment.getDimBounds(dimBounds);
         assertEquals(taskBounds, dimBounds);
 
diff --git a/telephony/java/android/telephony/SmsManager.java b/telephony/java/android/telephony/SmsManager.java
index df349f8..c958aba 100644
--- a/telephony/java/android/telephony/SmsManager.java
+++ b/telephony/java/android/telephony/SmsManager.java
@@ -563,7 +563,10 @@
      *  raw pdu of the status report is in the extended data ("pdu").
      *
      * @throws IllegalArgumentException if destinationAddress or text are empty
+     * @throws UnsupportedOperationException If the device does not have
+     *          {@link PackageManager#FEATURE_TELEPHONY_MESSAGING}.
      */
+    @RequiresFeature(PackageManager.FEATURE_TELEPHONY_MESSAGING)
     public void sendTextMessage(
             String destinationAddress, String scAddress, String text,
             PendingIntent sentIntent, PendingIntent deliveryIntent) {
@@ -581,8 +584,11 @@
      * Used for logging and diagnostics purposes. The id may be 0.
      *
      * @throws IllegalArgumentException if destinationAddress or text are empty
+     * @throws UnsupportedOperationException If the device does not have
+     *          {@link PackageManager#FEATURE_TELEPHONY_MESSAGING}.
      *
      */
+    @RequiresFeature(PackageManager.FEATURE_TELEPHONY_MESSAGING)
     public void sendTextMessage(
             @NonNull String destinationAddress, @Nullable String scAddress, @NonNull String text,
             @Nullable PendingIntent sentIntent, @Nullable PendingIntent deliveryIntent,
@@ -788,12 +794,16 @@
      * </p>
      *
      * @see #sendTextMessage(String, String, String, PendingIntent, PendingIntent)
+     *
+     * @throws UnsupportedOperationException If the device does not have
+     *          {@link PackageManager#FEATURE_TELEPHONY_MESSAGING}.
      */
     @SuppressAutoDoc // Blocked by b/72967236 - no support for carrier privileges
     @RequiresPermission(allOf = {
             android.Manifest.permission.MODIFY_PHONE_STATE,
             android.Manifest.permission.SEND_SMS
     })
+    @RequiresFeature(PackageManager.FEATURE_TELEPHONY_MESSAGING)
     public void sendTextMessageWithoutPersisting(
             String destinationAddress, String scAddress, String text,
             PendingIntent sentIntent, PendingIntent deliveryIntent) {
@@ -908,7 +918,10 @@
      *  {@link #RESULT_REMOTE_EXCEPTION} for error.
      *
      * @throws IllegalArgumentException if the format is invalid.
+     * @throws UnsupportedOperationException If the device does not have
+     *          {@link PackageManager#FEATURE_TELEPHONY_MESSAGING}.
      */
+    @RequiresFeature(PackageManager.FEATURE_TELEPHONY_MESSAGING)
     public void injectSmsPdu(
             byte[] pdu, @SmsMessage.Format String format, PendingIntent receivedIntent) {
         if (!format.equals(SmsMessage.FORMAT_3GPP) && !format.equals(SmsMessage.FORMAT_3GPP2)) {
@@ -940,6 +953,7 @@
      * @return an <code>ArrayList</code> of strings that, in order, comprise the original message.
      * @throws IllegalArgumentException if text is null.
      */
+    @RequiresFeature(PackageManager.FEATURE_TELEPHONY_MESSAGING)
     public ArrayList<String> divideMessage(String text) {
         if (null == text) {
             throw new IllegalArgumentException("text is null");
@@ -1046,7 +1060,10 @@
      *  extended data ("pdu").
      *
      * @throws IllegalArgumentException if destinationAddress or data are empty
+     * @throws UnsupportedOperationException If the device does not have
+     *          {@link PackageManager#FEATURE_TELEPHONY_MESSAGING}.
      */
+    @RequiresFeature(PackageManager.FEATURE_TELEPHONY_MESSAGING)
     public void sendMultipartTextMessage(
             String destinationAddress, String scAddress, ArrayList<String> parts,
             ArrayList<PendingIntent> sentIntents, ArrayList<PendingIntent> deliveryIntents) {
@@ -1062,8 +1079,10 @@
      * Used for logging and diagnostics purposes. The id may be 0.
      *
      * @throws IllegalArgumentException if destinationAddress or data are empty
-     *
+     * @throws UnsupportedOperationException If the device does not have
+     *          {@link PackageManager#FEATURE_TELEPHONY_MESSAGING}.
      */
+    @RequiresFeature(PackageManager.FEATURE_TELEPHONY_MESSAGING)
     public void sendMultipartTextMessage(
             @NonNull String destinationAddress, @Nullable String scAddress,
             @NonNull List<String> parts, @Nullable List<PendingIntent> sentIntents,
@@ -1089,7 +1108,11 @@
      *
      * @param packageName serves as the default package name if the package name that is
      *        associated with the user id is null.
+     *
+     * @throws UnsupportedOperationException If the device does not have
+     *          {@link PackageManager#FEATURE_TELEPHONY_MESSAGING}.
      */
+    @RequiresFeature(PackageManager.FEATURE_TELEPHONY_MESSAGING)
     public void sendMultipartTextMessage(
             @NonNull String destinationAddress, @Nullable String scAddress,
             @NonNull List<String> parts, @Nullable List<PendingIntent> sentIntents,
@@ -1191,10 +1214,14 @@
      * </p>
      *
      * @see #sendMultipartTextMessage(String, String, ArrayList, ArrayList, ArrayList)
+     *
+     * @throws UnsupportedOperationException If the device does not have
+     *          {@link PackageManager#FEATURE_TELEPHONY_MESSAGING}.
      * @hide
      **/
     @SystemApi
     @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE)
+    @RequiresFeature(PackageManager.FEATURE_TELEPHONY_MESSAGING)
     public void sendMultipartTextMessageWithoutPersisting(
             String destinationAddress, String scAddress, List<String> parts,
             List<PendingIntent> sentIntents, List<PendingIntent> deliveryIntents) {
@@ -1498,7 +1525,10 @@
      *  raw pdu of the status report is in the extended data ("pdu").
      *
      * @throws IllegalArgumentException if destinationAddress or data are empty
+     * @throws UnsupportedOperationException If the device does not have
+     *          {@link PackageManager#FEATURE_TELEPHONY_MESSAGING}.
      */
+    @RequiresFeature(PackageManager.FEATURE_TELEPHONY_MESSAGING)
     public void sendDataMessage(
             String destinationAddress, String scAddress, short destinationPort,
             byte[] data, PendingIntent sentIntent, PendingIntent deliveryIntent) {
@@ -1609,6 +1639,7 @@
      * .{@link #createForSubscriptionId createForSubscriptionId(subId)} instead
      */
     @Deprecated
+    @RequiresFeature(PackageManager.FEATURE_TELEPHONY_MESSAGING)
     public static SmsManager getSmsManagerForSubscriptionId(int subId) {
         return getSmsManagerForContextAndSubscriptionId(null, subId);
     }
@@ -1626,6 +1657,7 @@
      * @see SubscriptionManager#getActiveSubscriptionInfoList()
      * @see SubscriptionManager#getDefaultSmsSubscriptionId()
      */
+    @RequiresFeature(PackageManager.FEATURE_TELEPHONY_MESSAGING)
     public @NonNull SmsManager createForSubscriptionId(int subId) {
         return getSmsManagerForContextAndSubscriptionId(mContext, subId);
     }
@@ -1651,7 +1683,11 @@
      * @return associated subscription ID or {@link SubscriptionManager#INVALID_SUBSCRIPTION_ID} if
      * the default subscription id cannot be determined or the device has multiple active
      * subscriptions and and no default is set ("ask every time") by the user.
+     *
+     * @throws UnsupportedOperationException If the device does not have
+     *          {@link PackageManager#FEATURE_TELEPHONY_MESSAGING}.
      */
+    @RequiresFeature(PackageManager.FEATURE_TELEPHONY_MESSAGING)
     public int getSubscriptionId() {
         try {
             return (mSubId == SubscriptionManager.DEFAULT_SUBSCRIPTION_ID)
@@ -2018,10 +2054,14 @@
      *
      * @throws IllegalArgumentException if endMessageId < startMessageId
      * @deprecated Use {@link TelephonyManager#setCellBroadcastIdRanges} instead.
+     *
+     * @throws UnsupportedOperationException If the device does not have
+     *          {@link PackageManager#FEATURE_TELEPHONY_MESSAGING}.
      * {@hide}
      */
     @Deprecated
     @SystemApi
+    @RequiresFeature(PackageManager.FEATURE_TELEPHONY_MESSAGING)
     public boolean enableCellBroadcastRange(int startMessageId, int endMessageId,
             @android.telephony.SmsCbMessage.MessageFormat int ranType) {
         boolean success = false;
@@ -2079,11 +2119,15 @@
      * @see #enableCellBroadcastRange(int, int, int)
      *
      * @throws IllegalArgumentException if endMessageId < startMessageId
+     * @throws UnsupportedOperationException If the device does not have
+     *          {@link PackageManager#FEATURE_TELEPHONY_MESSAGING}.
+     *
      * @deprecated Use {@link TelephonyManager#setCellBroadcastIdRanges} instead.
      * {@hide}
      */
     @Deprecated
     @SystemApi
+    @RequiresFeature(PackageManager.FEATURE_TELEPHONY_MESSAGING)
     public boolean disableCellBroadcastRange(int startMessageId, int endMessageId,
             @android.telephony.SmsCbMessage.MessageFormat int ranType) {
         boolean success = false;
@@ -2223,7 +2267,11 @@
      * @return the user-defined default SMS subscription id, or the active subscription id if
      * there's only one active subscription available, otherwise
      * {@link SubscriptionManager#INVALID_SUBSCRIPTION_ID}.
+     *
+     * @throws UnsupportedOperationException If the device does not have
+     *          {@link PackageManager#FEATURE_TELEPHONY_MESSAGING}.
      */
+    @RequiresFeature(PackageManager.FEATURE_TELEPHONY_MESSAGING)
     public static int getDefaultSmsSubscriptionId() {
         try {
             return getISmsService().getPreferredSmsSubscription();
@@ -2271,10 +2319,14 @@
      * </p>
      *
      * @return the total number of SMS records which can be stored on the SIM card.
+     *
+     * @throws UnsupportedOperationException If the device does not have
+     *          {@link PackageManager#FEATURE_TELEPHONY_MESSAGING}.
      */
     @RequiresPermission(anyOf = {android.Manifest.permission.READ_PHONE_STATE,
             android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE})
     @IntRange(from = 0)
+    @RequiresFeature(PackageManager.FEATURE_TELEPHONY_MESSAGING)
     public int getSmsCapacityOnIcc() {
         int ret = 0;
         try {
@@ -2819,7 +2871,10 @@
      * <code>MMS_ERROR_DATA_DISABLED</code><br>
      * <code>MMS_ERROR_MMS_DISABLED_BY_CARRIER</code><br>
      * @throws IllegalArgumentException if contentUri is empty
+     * @throws UnsupportedOperationException If the device does not have
+     *          {@link PackageManager#FEATURE_TELEPHONY_MESSAGING}.
      */
+    @RequiresFeature(PackageManager.FEATURE_TELEPHONY_MESSAGING)
     public void sendMultimediaMessage(Context context, Uri contentUri, String locationUrl,
             Bundle configOverrides, PendingIntent sentIntent) {
         sendMultimediaMessage(context, contentUri, locationUrl, configOverrides, sentIntent,
@@ -2863,7 +2918,10 @@
      * @param messageId an id that uniquely identifies the message requested to be sent.
      * Used for logging and diagnostics purposes. The id may be 0.
      * @throws IllegalArgumentException if contentUri is empty
+     * @throws UnsupportedOperationException If the device does not have
+     *          {@link PackageManager#FEATURE_TELEPHONY_MESSAGING}.
      */
+    @RequiresFeature(PackageManager.FEATURE_TELEPHONY_MESSAGING)
     public void sendMultimediaMessage(@NonNull Context context, @NonNull Uri contentUri,
             @Nullable String locationUrl,
             @SuppressWarnings("NullableCollection") @Nullable Bundle configOverrides,
@@ -2922,7 +2980,10 @@
      * <code>MMS_ERROR_DATA_DISABLED</code><br>
      * <code>MMS_ERROR_MMS_DISABLED_BY_CARRIER</code><br>
      * @throws IllegalArgumentException if locationUrl or contentUri is empty
+     * @throws UnsupportedOperationException If the device does not have
+     *          {@link PackageManager#FEATURE_TELEPHONY_MESSAGING}.
      */
+    @RequiresFeature(PackageManager.FEATURE_TELEPHONY_MESSAGING)
     public void downloadMultimediaMessage(Context context, String locationUrl, Uri contentUri,
             Bundle configOverrides, PendingIntent downloadedIntent) {
         downloadMultimediaMessage(context, locationUrl, contentUri, configOverrides,
@@ -2968,7 +3029,10 @@
      * @param messageId an id that uniquely identifies the message requested to be downloaded.
      * Used for logging and diagnostics purposes. The id may be 0.
      * @throws IllegalArgumentException if locationUrl or contentUri is empty
+     * @throws UnsupportedOperationException If the device does not have
+     *          {@link PackageManager#FEATURE_TELEPHONY_MESSAGING}.
      */
+    @RequiresFeature(PackageManager.FEATURE_TELEPHONY_MESSAGING)
     public void downloadMultimediaMessage(@NonNull Context context, @NonNull String locationUrl,
             @NonNull Uri contentUri,
             @SuppressWarnings("NullableCollection") @Nullable Bundle configOverrides,
@@ -3079,7 +3143,11 @@
      *
      * @return the bundle key/values pairs that contains MMS configuration values
      *  or an empty Bundle if they cannot be found.
+     *
+     * @throws UnsupportedOperationException If the device does not have
+     *          {@link PackageManager#FEATURE_TELEPHONY_MESSAGING}.
      */
+    @RequiresFeature(PackageManager.FEATURE_TELEPHONY_MESSAGING)
     @NonNull public Bundle getCarrierConfigValues() {
         try {
             ISms iSms = getISmsService();
@@ -3115,7 +3183,11 @@
      *
      * @return Token to include in an SMS message. The token will be 11 characters long.
      * @see android.provider.Telephony.Sms.Intents#getMessagesFromIntent
+     *
+     * @throws UnsupportedOperationException If the device does not have
+     *          {@link PackageManager#FEATURE_TELEPHONY_MESSAGING}.
      */
+    @RequiresFeature(PackageManager.FEATURE_TELEPHONY_MESSAGING)
     public String createAppSpecificSmsToken(PendingIntent intent) {
         try {
             ISms iccSms = getISmsServiceOrThrow();
@@ -3233,7 +3305,11 @@
      *  message.
      * @param intent this intent is sent when the matching SMS message is received.
      * @return Token to include in an SMS message.
+     *
+     * @throws UnsupportedOperationException If the device does not have
+     *          {@link PackageManager#FEATURE_TELEPHONY_MESSAGING}.
      */
+    @RequiresFeature(PackageManager.FEATURE_TELEPHONY_MESSAGING)
     @Nullable
     public String createAppSpecificSmsTokenWithPackageInfo(
             @Nullable String prefixes, @NonNull PendingIntent intent) {
@@ -3393,9 +3469,13 @@
      * </p>
      *
      * @return the SMSC address string, null if failed.
+     *
+     * @throws UnsupportedOperationException If the device does not have
+     *          {@link PackageManager#FEATURE_TELEPHONY_MESSAGING}.
      */
     @SuppressAutoDoc // for carrier privileges and default SMS application.
     @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
+    @RequiresFeature(PackageManager.FEATURE_TELEPHONY_MESSAGING)
     @Nullable
     public String getSmscAddress() {
         String smsc = null;
@@ -3430,9 +3510,13 @@
      *
      * @param smsc the SMSC address string.
      * @return true for success, false otherwise. Failure can be due modem returning an error.
+     *
+     * @throws UnsupportedOperationException If the device does not have
+     *          {@link PackageManager#FEATURE_TELEPHONY_MESSAGING}.
      */
     @SuppressAutoDoc // for carrier privileges and default SMS application.
     @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE)
+    @RequiresFeature(PackageManager.FEATURE_TELEPHONY_MESSAGING)
     public boolean setSmscAddress(@NonNull String smsc) {
         try {
             ISms iSms = getISmsService();
@@ -3455,10 +3539,14 @@
      *  {@link SmsManager#PREMIUM_SMS_CONSENT_ASK_USER},
      *  {@link SmsManager#PREMIUM_SMS_CONSENT_NEVER_ALLOW}, or
      *  {@link SmsManager#PREMIUM_SMS_CONSENT_ALWAYS_ALLOW}
+     *
+     * @throws UnsupportedOperationException If the device does not have
+     *          {@link PackageManager#FEATURE_TELEPHONY_MESSAGING}.
      * @hide
      */
     @SystemApi
     @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
+    @RequiresFeature(PackageManager.FEATURE_TELEPHONY_MESSAGING)
     public @PremiumSmsConsent int getPremiumSmsConsent(@NonNull String packageName) {
         int permission = 0;
         try {
@@ -3479,10 +3567,14 @@
      * @param permission one of {@link SmsManager#PREMIUM_SMS_CONSENT_ASK_USER},
      *  {@link SmsManager#PREMIUM_SMS_CONSENT_NEVER_ALLOW}, or
      *  {@link SmsManager#PREMIUM_SMS_CONSENT_ALWAYS_ALLOW}
+     *
+     * @throws UnsupportedOperationException If the device does not have
+     *          {@link PackageManager#FEATURE_TELEPHONY_MESSAGING}.
      * @hide
      */
     @SystemApi
     @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE)
+    @RequiresFeature(PackageManager.FEATURE_TELEPHONY_MESSAGING)
     public void setPremiumSmsConsent(
             @NonNull String packageName, @PremiumSmsConsent int permission) {
         try {
@@ -3498,11 +3590,15 @@
     /**
      * Reset all cell broadcast ranges. Previously enabled ranges will become invalid after this.
      * @deprecated Use {@link TelephonyManager#setCellBroadcastIdRanges} with empty list instead
+     *
+     * @throws UnsupportedOperationException If the device does not have
+     *          {@link PackageManager#FEATURE_TELEPHONY_MESSAGING}.
      * @hide
      */
     @Deprecated
     @SystemApi
     @RequiresPermission(android.Manifest.permission.MODIFY_CELL_BROADCASTS)
+    @RequiresFeature(PackageManager.FEATURE_TELEPHONY_MESSAGING)
     public void resetAllCellBroadcastRanges() {
         try {
             ISms iSms = getISmsService();
@@ -3530,6 +3626,8 @@
      * available.
      * @throws SecurityException if the caller does not have the required permission/privileges.
      * @throws IllegalStateException in case of telephony service is not available.
+     * @throws UnsupportedOperationException If the device does not have
+     *          {@link PackageManager#FEATURE_TELEPHONY_SUBSCRIPTION}.
      * @hide
      */
     @NonNull
diff --git a/telephony/java/android/telephony/SubscriptionManager.java b/telephony/java/android/telephony/SubscriptionManager.java
index 6c8663a..5615602 100644
--- a/telephony/java/android/telephony/SubscriptionManager.java
+++ b/telephony/java/android/telephony/SubscriptionManager.java
@@ -1757,6 +1757,9 @@
      *
      * @param subId The unique SubscriptionInfo key in database.
      * @return SubscriptionInfo, maybe null if its not active.
+     *
+     * @throws UnsupportedOperationException If the device does not have
+     *          {@link PackageManager#FEATURE_TELEPHONY_SUBSCRIPTION}.
      */
     @SuppressAutoDoc // Blocked by b/72967236 - no support for carrier privileges
     @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE)
@@ -1790,6 +1793,8 @@
      * @param iccId the IccId of SIM card
      * @return SubscriptionInfo, maybe null if its not active
      *
+     * @throws UnsupportedOperationException If the device does not have
+     *          {@link PackageManager#FEATURE_TELEPHONY_SUBSCRIPTION}.
      * @hide
      */
     @RequiresPermission(android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE)
@@ -1826,6 +1831,9 @@
      *
      * @param slotIndex the slot which the subscription is inserted
      * @return SubscriptionInfo, maybe null if its not active
+     *
+     * @throws UnsupportedOperationException If the device does not have
+     *          {@link PackageManager#FEATURE_TELEPHONY_SUBSCRIPTION}.
      */
     @SuppressAutoDoc // Blocked by b/72967236 - no support for carrier privileges
     @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE)
@@ -1870,6 +1878,8 @@
      * {@link SubscriptionInfo#getSubscriptionId()}.
      *
      * @throws SecurityException if callers do not hold the required permission.
+     * @throws UnsupportedOperationException If the device does not have
+     *          {@link PackageManager#FEATURE_TELEPHONY_SUBSCRIPTION}.
      */
     @NonNull
     @RequiresPermission(anyOf = {
@@ -1929,6 +1939,9 @@
      * then by {@link SubscriptionInfo#getSubscriptionId}.
      * </li>
      * </ul>
+     *
+     * @throws UnsupportedOperationException If the device does not have
+     *          {@link PackageManager#FEATURE_TELEPHONY_SUBSCRIPTION}.
      */
     @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE)
     // @RequiresPermission(TODO(b/308809058))
@@ -1972,6 +1985,8 @@
      * This is similar to {@link #getActiveSubscriptionInfoList} except that it will return
      * both active and hidden SubscriptionInfos.
      *
+     * @throws UnsupportedOperationException If the device does not have
+     *          {@link PackageManager#FEATURE_TELEPHONY_SUBSCRIPTION}.
      */
     public @NonNull List<SubscriptionInfo> getCompleteActiveSubscriptionInfoList() {
         List<SubscriptionInfo> completeList = getActiveSubscriptionInfoList(
@@ -2056,6 +2071,9 @@
      * <p>
      * Permissions android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE is required
      * for #getAvailableSubscriptionInfoList to be invoked.
+     *
+     * @throws UnsupportedOperationException If the device does not have
+     *          {@link PackageManager#FEATURE_TELEPHONY_SUBSCRIPTION}.
      * @hide
      */
     @SystemApi
@@ -2097,6 +2115,9 @@
      * if the list is non-empty the list is sorted by {@link SubscriptionInfo#getSimSlotIndex}
      * then by {@link SubscriptionInfo#getSubscriptionId}.
      * </ul>
+     *
+     * @throws UnsupportedOperationException If the device does not have
+     *          {@link PackageManager#FEATURE_TELEPHONY_EUICC}.
      */
     public List<SubscriptionInfo> getAccessibleSubscriptionInfoList() {
         List<SubscriptionInfo> result = null;
@@ -2125,6 +2146,8 @@
      *
      * @see TelephonyManager#getCardIdForDefaultEuicc() for more information on the card ID.
      *
+     * @throws UnsupportedOperationException If the device does not have
+     *          {@link PackageManager#FEATURE_TELEPHONY_EUICC}.
      * @hide
      */
     @SystemApi
@@ -2155,6 +2178,8 @@
      *
      * @see TelephonyManager#getCardIdForDefaultEuicc() for more information on the card ID.
      *
+     * @throws UnsupportedOperationException If the device does not have
+     *          {@link PackageManager#FEATURE_TELEPHONY_EUICC}.
      * @hide
      */
     @SystemApi
@@ -2177,6 +2202,9 @@
      * @return The current number of active subscriptions.
      *
      * @see #getActiveSubscriptionInfoList()
+     *
+     * @throws UnsupportedOperationException If the device does not have
+     *          {@link PackageManager#FEATURE_TELEPHONY_SUBSCRIPTION}.
      */
     @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE)
     // @RequiresPermission(TODO(b/308809058))
@@ -2247,6 +2275,9 @@
      * @param slotIndex the slot assigned to this subscription. It is ignored for subscriptionType
      *                  of {@link #SUBSCRIPTION_TYPE_REMOTE_SIM}.
      * @param subscriptionType the {@link #SUBSCRIPTION_TYPE}
+     *
+     * @throws UnsupportedOperationException If the device does not have
+     *          {@link PackageManager#FEATURE_TELEPHONY_SUBSCRIPTION}.
      * @hide
      */
     @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
@@ -2289,6 +2320,8 @@
      * @throws NullPointerException if {@code uniqueId} is {@code null}.
      * @throws SecurityException if callers do not hold the required permission.
      *
+     * @throws UnsupportedOperationException If the device does not have
+     *          {@link PackageManager#FEATURE_TELEPHONY_SUBSCRIPTION}.
      * @hide
      */
     @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES)
@@ -2435,6 +2468,7 @@
      * @deprecated Use {@link #getSubscriptionId(int)} instead.
      * @hide
      */
+    @Deprecated
     public static int[] getSubId(int slotIndex) {
         if (!isValidSlotIndex(slotIndex)) {
             return null;
@@ -2489,6 +2523,9 @@
      * On a data only device or on error, will return INVALID_SUBSCRIPTION_ID.
      *
      * @return the default voice subscription Id.
+     *
+     * @throws UnsupportedOperationException If the device does not have
+     *          {@link PackageManager#FEATURE_TELEPHONY_SUBSCRIPTION}.
      */
     public static int getDefaultVoiceSubscriptionId() {
         int subId = INVALID_SUBSCRIPTION_ID;
@@ -2516,6 +2553,9 @@
      *
      * @param subscriptionId A valid subscription ID to set as the system default, or
      *                       {@link SubscriptionManager#INVALID_SUBSCRIPTION_ID}
+     *
+     * @throws UnsupportedOperationException If the device does not have
+     *          {@link PackageManager#FEATURE_TELEPHONY_SUBSCRIPTION}.
      * @hide
      */
     @SystemApi
@@ -2535,6 +2575,9 @@
     /**
      * Same as {@link #setDefaultVoiceSubscriptionId(int)}, but preserved for backwards
      * compatibility.
+     *
+     * @throws UnsupportedOperationException If the device does not have
+     *          {@link PackageManager#FEATURE_TELEPHONY_SUBSCRIPTION}.
      * @hide
      */
     public void setDefaultVoiceSubId(int subId) {
@@ -2578,6 +2621,8 @@
      *
      * @param subscriptionId the supplied subscription ID
      *
+     * @throws UnsupportedOperationException If the device does not have
+     *          {@link PackageManager#FEATURE_TELEPHONY_SUBSCRIPTION}.
      * @hide
      */
     @SystemApi
@@ -2612,6 +2657,8 @@
      *
      * @param subscriptionId the supplied subscription ID
      *
+     * @throws UnsupportedOperationException If the device does not have
+     *          {@link PackageManager#FEATURE_TELEPHONY_SUBSCRIPTION}.
      * @hide
      */
     @SystemApi
@@ -2634,6 +2681,9 @@
      * Will return null on voice only devices, or on error.
      *
      * @return the SubscriptionInfo for the default data subscription.
+     *
+     * @throws UnsupportedOperationException If the device does not have
+     *          {@link PackageManager#FEATURE_TELEPHONY_SUBSCRIPTION}.
      * @hide
      */
     @UnsupportedAppUsage
@@ -2720,6 +2770,9 @@
      *
      * @return the list of subId's that are active,
      *         is never null but the length may be 0.
+     *
+     * @throws UnsupportedOperationException If the device does not have
+     *          {@link PackageManager#FEATURE_TELEPHONY_SUBSCRIPTION}.
      * @hide
      */
     @SystemApi
@@ -2738,6 +2791,9 @@
      *
      * @return the list of subId's that are active,
      *         is never null but the length may be 0.
+     *
+     * @throws UnsupportedOperationException If the device does not have
+     *          {@link PackageManager#FEATURE_TELEPHONY_SUBSCRIPTION}.
      * @hide
      */
     @SystemApi
@@ -2987,6 +3043,9 @@
      * @param context Context object
      * @param subId Subscription Id of Subscription whose resources are required
      * @return Resources associated with Subscription.
+     *
+     * @throws UnsupportedOperationException If the device does not have
+     *          {@link PackageManager#FEATURE_TELEPHONY_SUBSCRIPTION}.
      * @hide
      */
     @NonNull
@@ -3069,6 +3128,9 @@
      * @return {@code true} if the supplied subscription ID corresponds to an active subscription;
      * {@code false} if it does not correspond to an active subscription; or throw a
      * SecurityException if the caller hasn't got the right permission.
+     *i
+     * @throws UnsupportedOperationException If the device does not have
+     *          {@link PackageManager#FEATURE_TELEPHONY_SUBSCRIPTION}.
      */
     @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE)
     public boolean isActiveSubscriptionId(int subscriptionId) {
@@ -3377,6 +3439,8 @@
      *
      * @throws IllegalStateException when subscription manager service is not available.
      * @throws SecurityException when clients do not have MODIFY_PHONE_STATE permission.
+     * @throws UnsupportedOperationException If the device does not have
+     *          {@link PackageManager#FEATURE_TELEPHONY_SUBSCRIPTION}.
      * @hide
      */
     @SystemApi
@@ -3454,6 +3518,9 @@
      * {@link TelephonyManager#hasCarrierPrivileges}).
      *
      * @return the list of opportunistic subscription info. If none exists, an empty list.
+     *
+     * @throws UnsupportedOperationException If the device does not have
+     *          {@link PackageManager#FEATURE_TELEPHONY_SUBSCRIPTION}.
      */
     @SuppressAutoDoc // Blocked by b/72967236 - no support for carrier privileges
     @RequiresPermission(android.Manifest.permission.READ_PHONE_STATE)
@@ -3489,8 +3556,12 @@
      *  PendingIntent)} and does not support Multiple Enabled Profile(MEP). Apps should use
      *  {@link EuiccManager#switchToSubscription(int, PendingIntent)} or
      *  {@link EuiccManager#switchToSubscription(int, int, PendingIntent)} instead.
+     *
+     * @throws UnsupportedOperationException If the device does not have
+     *          {@link PackageManager#FEATURE_TELEPHONY_EUICC}.
      */
     @RequiresPermission(android.Manifest.permission.WRITE_EMBEDDED_SUBSCRIPTIONS)
+    @RequiresFeature(PackageManager.FEATURE_TELEPHONY_EUICC)
     @Deprecated
     public void switchToSubscription(int subId, @NonNull PendingIntent callbackIntent) {
         Preconditions.checkNotNull(callbackIntent, "callbackIntent cannot be null");
@@ -3518,6 +3589,9 @@
      * @param opportunistic whether it’s opportunistic subscription.
      * @param subId the unique SubscriptionInfo index in database
      * @return {@code true} if the operation is succeed, {@code false} otherwise.
+     *
+     * @throws UnsupportedOperationException If the device does not have
+     *          {@link PackageManager#FEATURE_TELEPHONY_SUBSCRIPTION}.
      */
     @SuppressAutoDoc // Blocked by b/72967236 - no support for carrier privileges
     @RequiresPermission(android.Manifest.permission.MODIFY_PHONE_STATE)
@@ -3554,6 +3628,8 @@
      *             outlined above.
      * @throws IllegalArgumentException if any of the subscriptions in the list doesn't exist.
      * @throws IllegalStateException if Telephony service is in bad state.
+     * @throws UnsupportedOperationException If the device does not have
+     *          {@link PackageManager#FEATURE_TELEPHONY_SUBSCRIPTION}.
      *
      * @param subIdList list of subId that will be in the same group
      * @return groupUUID a UUID assigned to the subscription group.
@@ -3598,6 +3674,8 @@
      *             outlined above.
      * @throws IllegalArgumentException if the some subscriptions in the list doesn't exist.
      * @throws IllegalStateException if Telephony service is in bad state.
+     * @throws UnsupportedOperationException If the device does not have
+     *          {@link PackageManager#FEATURE_TELEPHONY_SUBSCRIPTION}.
      *
      * @param subIdList list of subId that need adding into the group
      * @param groupUuid the groupUuid the subscriptions are being added to.
@@ -3647,6 +3725,8 @@
      * @throws IllegalArgumentException if the some subscriptions in the list doesn't belong the
      * specified group.
      * @throws IllegalStateException if Telephony service is in bad state.
+     * @throws UnsupportedOperationException If the device does not have
+     *          {@link PackageManager#FEATURE_TELEPHONY_SUBSCRIPTION}.
      *
      * @see #createSubscriptionGroup(List)
      */
@@ -3696,6 +3776,8 @@
      * @throws IllegalStateException if Telephony service is in bad state.
      * @throws SecurityException if the caller doesn't meet the requirements
      *             outlined above.
+     * @throws UnsupportedOperationException If the device does not have
+     *          {@link PackageManager#FEATURE_TELEPHONY_SUBSCRIPTION}.
      *
      * @param groupUuid of which list of subInfo will be returned.
      * @return list of subscriptionInfo that belong to the same group, including the given
@@ -3785,9 +3867,9 @@
             Map<ParcelUuid, SubscriptionInfo> groupMap = new HashMap<>();
 
             for (SubscriptionInfo info : availableList) {
-                // Opportunistic subscriptions are considered invisible
+                // Grouped opportunistic subscriptions are considered invisible
                 // to users so they should never be returned.
-                if (!isSubscriptionVisible(info)) continue;
+                if (info.getGroupUuid() != null && info.isOpportunistic()) continue;
 
                 ParcelUuid groupUuid = info.getGroupUuid();
                 if (groupUuid == null) {
@@ -3817,6 +3899,8 @@
      *
      * @return whether the operation is successful.
      *
+     * @throws UnsupportedOperationException If the device does not have
+     *          {@link PackageManager#FEATURE_TELEPHONY_SUBSCRIPTION}.
      * @hide
      */
     @SystemApi
@@ -3844,6 +3928,9 @@
      *
      * @param subscriptionId which subscription to operate on.
      * @param enabled whether uicc applications are enabled or disabled.
+     *
+     * @throws UnsupportedOperationException If the device does not have
+     *          {@link PackageManager#FEATURE_TELEPHONY_SUBSCRIPTION}.
      * @hide
      */
     @SystemApi
@@ -3872,6 +3959,8 @@
      *
      * @return whether can disable subscriptions on physical SIMs.
      *
+     * @throws UnsupportedOperationException If the device does not have
+     *          {@link PackageManager#FEATURE_TELEPHONY_SUBSCRIPTION}.
      * @hide
      */
     @SystemApi
@@ -3897,6 +3986,8 @@
      *
      * @param subscriptionId The subscription id.
      *
+     * @throws UnsupportedOperationException If the device does not have
+     *          {@link PackageManager#FEATURE_TELEPHONY_SUBSCRIPTION}.
      * @hide
      */
     @SystemApi
@@ -3923,6 +4014,8 @@
      * @param sharing The status sharing preference.
      *
      * @throws SecurityException if the caller doesn't have permissions required.
+     * @throws UnsupportedOperationException If the device does not have
+     *          {@link PackageManager#FEATURE_TELEPHONY_SUBSCRIPTION}.
      */
     @RequiresPermission(Manifest.permission.MODIFY_PHONE_STATE)
     public void setDeviceToDeviceStatusSharingPreference(int subscriptionId,
@@ -3941,6 +4034,8 @@
      * @return The device to device status sharing preference
      *
      * @throws SecurityException if the caller doesn't have permissions required.
+     * @throws UnsupportedOperationException If the device does not have
+     *          {@link PackageManager#FEATURE_TELEPHONY_SUBSCRIPTION}.
      */
     public @DeviceToDeviceStatusSharingPreference int getDeviceToDeviceStatusSharingPreference(
             int subscriptionId) {
@@ -3960,6 +4055,8 @@
      * @param contacts The list of contacts that allow device to device status sharing.
      *
      * @throws SecurityException if the caller doesn't have permissions required.
+     * @throws UnsupportedOperationException If the device does not have
+     *          {@link PackageManager#FEATURE_TELEPHONY_SUBSCRIPTION}.
      */
     @RequiresPermission(Manifest.permission.MODIFY_PHONE_STATE)
     public void setDeviceToDeviceStatusSharingContacts(int subscriptionId,
@@ -3980,6 +4077,9 @@
      * @param subscriptionId Subscription id.
      *
      * @return The list of contacts that allow device to device status sharing.
+     *
+     * @throws UnsupportedOperationException If the device does not have
+     *          {@link PackageManager#FEATURE_TELEPHONY_SUBSCRIPTION}.
      */
     public @NonNull List<Uri> getDeviceToDeviceStatusSharingContacts(int subscriptionId) {
         String result = getStringSubscriptionProperty(mContext, subscriptionId,
@@ -4012,6 +4112,8 @@
      *
      * @throws IllegalArgumentException if the provided slot index is invalid.
      * @throws SecurityException if callers do not hold the required permission.
+     * @throws UnsupportedOperationException If the device does not have
+     *          {@link PackageManager#FEATURE_TELEPHONY_SUBSCRIPTION}.
      *
      * @hide
      */
@@ -4152,6 +4254,8 @@
      *
      * @param data with the sim specific configs to be backed up.
      *
+     * @throws UnsupportedOperationException If the device does not have
+     *          {@link PackageManager#FEATURE_TELEPHONY_SUBSCRIPTION}.
      * @hide
      */
     @SystemApi
@@ -4206,6 +4310,8 @@
      * @throws IllegalArgumentException if {@code source} is invalid.
      * @throws IllegalStateException if the telephony process is not currently available.
      * @throws SecurityException if the caller doesn't have permissions required.
+     * @throws UnsupportedOperationException If the device does not have
+     *          {@link PackageManager#FEATURE_TELEPHONY_SUBSCRIPTION}.
      *
      * @see #PHONE_NUMBER_SOURCE_UICC
      * @see #PHONE_NUMBER_SOURCE_CARRIER
@@ -4266,6 +4372,8 @@
      *
      * @throws IllegalStateException if the telephony process is not currently available.
      * @throws SecurityException if the caller doesn't have permissions required.
+     * @throws UnsupportedOperationException If the device does not have
+     *          {@link PackageManager#FEATURE_TELEPHONY_SUBSCRIPTION}.
      *
      * @see #getPhoneNumber(int, int)
      */
@@ -4309,6 +4417,8 @@
      * @throws IllegalStateException if the telephony process is not currently available.
      * @throws NullPointerException if {@code number} is {@code null}.
      * @throws SecurityException if the caller doesn't have permissions required.
+     * @throws UnsupportedOperationException If the device does not have
+     *          {@link PackageManager#FEATURE_TELEPHONY_SUBSCRIPTION}.
      */
     @RequiresPermission("carrier privileges")
     public void setCarrierPhoneNumber(int subscriptionId, @NonNull String number) {
diff --git a/tests/CtsSurfaceControlTestsStaging/src/main/java/android/view/surfacecontroltests/GraphicsActivity.java b/tests/CtsSurfaceControlTestsStaging/src/main/java/android/view/surfacecontroltests/GraphicsActivity.java
index 60b5ce7..80c1e5be 100644
--- a/tests/CtsSurfaceControlTestsStaging/src/main/java/android/view/surfacecontroltests/GraphicsActivity.java
+++ b/tests/CtsSurfaceControlTestsStaging/src/main/java/android/view/surfacecontroltests/GraphicsActivity.java
@@ -810,6 +810,7 @@
 
     private FpsRange convertCategory(int category) {
         switch (category) {
+            case Surface.FRAME_RATE_CATEGORY_HIGH_HINT:
             case Surface.FRAME_RATE_CATEGORY_HIGH:
                 return FRAME_RATE_CATEGORY_HIGH;
             case Surface.FRAME_RATE_CATEGORY_NORMAL:
diff --git a/tests/CtsSurfaceControlTestsStaging/src/main/java/android/view/surfacecontroltests/SurfaceControlTest.java b/tests/CtsSurfaceControlTestsStaging/src/main/java/android/view/surfacecontroltests/SurfaceControlTest.java
index 4b56c10..caaee63 100644
--- a/tests/CtsSurfaceControlTestsStaging/src/main/java/android/view/surfacecontroltests/SurfaceControlTest.java
+++ b/tests/CtsSurfaceControlTestsStaging/src/main/java/android/view/surfacecontroltests/SurfaceControlTest.java
@@ -93,6 +93,12 @@
     }
 
     @Test
+    public void testSurfaceControlFrameRateCategoryHighHint() throws InterruptedException {
+        GraphicsActivity activity = mActivityRule.getActivity();
+        activity.testSurfaceControlFrameRateCategory(Surface.FRAME_RATE_CATEGORY_HIGH_HINT);
+    }
+
+    @Test
     public void testSurfaceControlFrameRateCategoryNormal() throws InterruptedException {
         GraphicsActivity activity = mActivityRule.getActivity();
         activity.testSurfaceControlFrameRateCategory(Surface.FRAME_RATE_CATEGORY_NORMAL);
diff --git a/tests/Input/src/android/hardware/input/StickyModifierStateListenerTest.kt b/tests/Input/src/android/hardware/input/StickyModifierStateListenerTest.kt
new file mode 100644
index 0000000..e2b0c36
--- /dev/null
+++ b/tests/Input/src/android/hardware/input/StickyModifierStateListenerTest.kt
@@ -0,0 +1,215 @@
+/*
+ * 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.assertTrue
+import kotlin.test.fail
+
+/**
+ * Tests for [InputManager.StickyModifierStateListener].
+ *
+ * Build/Install/Run:
+ * atest InputTests:StickyModifierStateListenerTest
+ */
+@Presubmit
+@RunWith(MockitoJUnitRunner::class)
+class StickyModifierStateListenerTest {
+
+    @get:Rule
+    val rule = SetFlagsRule()
+
+    private val testLooper = TestLooper()
+    private val executor = HandlerExecutor(Handler(testLooper.looper))
+    private var registeredListener: IStickyModifierStateListener? = 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() {
+        // Enable Sticky keys feature
+        rule.enableFlags(com.android.hardware.input.Flags.FLAG_KEYBOARD_A11Y_STICKY_KEYS_FLAG)
+        rule.enableFlags(com.android.input.flags.Flags.FLAG_ENABLE_INPUT_FILTER_RUST_IMPL)
+
+        context = Mockito.spy(ContextWrapper(ApplicationProvider.getApplicationContext()))
+        inputManagerGlobalSession = InputManagerGlobal.createTestSession(iInputManagerMock)
+        inputManager = InputManager(context)
+        `when`(context.getSystemService(Mockito.eq(Context.INPUT_SERVICE)))
+                .thenReturn(inputManager)
+
+        // Handle sticky modifier state listener registration.
+        doAnswer {
+            val listener = it.getArgument(0) as IStickyModifierStateListener
+            if (registeredListener != null &&
+                    registeredListener!!.asBinder() != listener.asBinder()) {
+                // There can only be one registered sticky modifier state listener per process.
+                fail("Trying to register a new listener when one already exists")
+            }
+            registeredListener = listener
+            null
+        }.`when`(iInputManagerMock).registerStickyModifierStateListener(any())
+
+        // Handle sticky modifier state listener being unregistered.
+        doAnswer {
+            val listener = it.getArgument(0) as IStickyModifierStateListener
+            if (registeredListener == null ||
+                    registeredListener!!.asBinder() != listener.asBinder()) {
+                fail("Trying to unregister a listener that is not registered")
+            }
+            registeredListener = null
+            null
+        }.`when`(iInputManagerMock).unregisterStickyModifierStateListener(any())
+    }
+
+    @After
+    fun tearDown() {
+        if (this::inputManagerGlobalSession.isInitialized) {
+            inputManagerGlobalSession.close()
+        }
+    }
+
+    private fun notifyStickyModifierStateChanged(modifierState: Int, lockedModifierState: Int) {
+        registeredListener!!.onStickyModifierStateChanged(modifierState, lockedModifierState)
+    }
+
+    @Test
+    fun testListenerIsNotifiedOnModifierStateChanged() {
+        var callbackCount = 0
+
+        // Add a sticky modifier state listener
+        inputManager.registerStickyModifierStateListener(executor) {
+            callbackCount++
+        }
+
+        // Notifying sticky modifier state change will notify the listener.
+        notifyStickyModifierStateChanged(0, 0)
+        testLooper.dispatchNext()
+        assertEquals(1, callbackCount)
+    }
+
+    @Test
+    fun testListenerHasCorrectModifierStateNotified() {
+        // Add a sticky modifier state listener
+        inputManager.registerStickyModifierStateListener(executor) {
+            state: StickyModifierState ->
+            assertTrue(state.isAltModifierOn)
+            assertTrue(state.isAltModifierLocked)
+            assertTrue(state.isShiftModifierOn)
+            assertTrue(!state.isShiftModifierLocked)
+            assertTrue(!state.isCtrlModifierOn)
+            assertTrue(!state.isCtrlModifierLocked)
+            assertTrue(!state.isMetaModifierOn)
+            assertTrue(!state.isMetaModifierLocked)
+            assertTrue(!state.isAltGrModifierOn)
+            assertTrue(!state.isAltGrModifierLocked)
+        }
+
+        // Notifying sticky modifier state change will notify the listener.
+        notifyStickyModifierStateChanged(
+                KeyEvent.META_ALT_ON or KeyEvent.META_ALT_LEFT_ON or
+                        KeyEvent.META_SHIFT_ON or KeyEvent.META_SHIFT_LEFT_ON,
+                KeyEvent.META_ALT_ON or KeyEvent.META_ALT_LEFT_ON
+        )
+        testLooper.dispatchNext()
+    }
+
+    @Test
+    fun testAddingListenersRegistersInternalCallbackListener() {
+        // Set up two callbacks.
+        val callback1 = InputManager.StickyModifierStateListener {}
+        val callback2 = InputManager.StickyModifierStateListener {}
+
+        assertNull(registeredListener)
+
+        // Adding the listener should register the callback with InputManagerService.
+        inputManager.registerStickyModifierStateListener(executor, callback1)
+        assertNotNull(registeredListener)
+
+        // Adding another listener should not register new internal listener.
+        val currListener = registeredListener
+        inputManager.registerStickyModifierStateListener(executor, callback2)
+        assertEquals(currListener, registeredListener)
+    }
+
+    @Test
+    fun testRemovingListenersUnregistersInternalCallbackListener() {
+        // Set up two callbacks.
+        val callback1 = InputManager.StickyModifierStateListener {}
+        val callback2 = InputManager.StickyModifierStateListener {}
+
+        inputManager.registerStickyModifierStateListener(executor, callback1)
+        inputManager.registerStickyModifierStateListener(executor, callback2)
+
+        // Only removing all listeners should remove the internal callback
+        inputManager.unregisterStickyModifierStateListener(callback1)
+        assertNotNull(registeredListener)
+        inputManager.unregisterStickyModifierStateListener(callback2)
+        assertNull(registeredListener)
+    }
+
+    @Test
+    fun testMultipleListeners() {
+        // Set up two callbacks.
+        var callbackCount1 = 0
+        var callbackCount2 = 0
+        val callback1 = InputManager.StickyModifierStateListener { _ -> callbackCount1++ }
+        val callback2 = InputManager.StickyModifierStateListener { _ -> callbackCount2++ }
+
+        // Add both sticky modifier state listeners
+        inputManager.registerStickyModifierStateListener(executor, callback1)
+        inputManager.registerStickyModifierStateListener(executor, callback2)
+
+        // Notifying sticky modifier state change trigger the both callbacks.
+        notifyStickyModifierStateChanged(0, 0)
+        testLooper.dispatchAll()
+        assertEquals(1, callbackCount1)
+        assertEquals(1, callbackCount2)
+
+        inputManager.unregisterStickyModifierStateListener(callback2)
+        // Notifying sticky modifier state change should still trigger callback1 but not callback2.
+        notifyStickyModifierStateChanged(0, 0)
+        testLooper.dispatchAll()
+        assertEquals(2, callbackCount1)
+        assertEquals(1, callbackCount2)
+    }
+}