Merge changes I8fca0e6f,I7fb46a09 into main

* changes:
  [CS] Remove CentralSurfaces screen pinning methods.
  Update NavBarController's CommandQueue callbacks to be separate.
diff --git a/AconfigFlags.bp b/AconfigFlags.bp
index 09225a5..a5178cf 100644
--- a/AconfigFlags.bp
+++ b/AconfigFlags.bp
@@ -19,6 +19,7 @@
     // Add java_aconfig_libraries to here to add them to the core framework
     srcs: [
         ":android.app.usage.flags-aconfig-java{.generated_srcjars}",
+        ":android.content.pm.flags-aconfig-java{.generated_srcjars}",
         ":android.os.flags-aconfig-java{.generated_srcjars}",
         ":android.os.vibrator.flags-aconfig-java{.generated_srcjars}",
         ":android.security.flags-aconfig-java{.generated_srcjars}",
@@ -28,10 +29,12 @@
         ":com.android.hardware.input-aconfig-java{.generated_srcjars}",
         ":com.android.text.flags-aconfig-java{.generated_srcjars}",
         ":telecom_flags_core_java_lib{.generated_srcjars}",
+        ":telephony_flags_core_java_lib{.generated_srcjars}",
         ":android.companion.virtual.flags-aconfig-java{.generated_srcjars}",
         ":android.view.inputmethod.flags-aconfig-java{.generated_srcjars}",
         ":android.widget.flags-aconfig-java{.generated_srcjars}",
         ":com.android.media.flags.bettertogether-aconfig-java{.generated_srcjars}",
+        ":sdk_sandbox_flags_lib{.generated_srcjars}",
     ],
     // Add aconfig-annotations-lib as a dependency for the optimization
     libs: ["aconfig-annotations-lib"],
@@ -59,6 +62,13 @@
     defaults: ["framework-minus-apex-aconfig-java-defaults"],
 }
 
+// Telephony
+java_aconfig_library {
+    name: "telephony_flags_core_java_lib",
+    aconfig_declarations: "telephony_flags",
+    defaults: ["framework-minus-apex-aconfig-java-defaults"],
+}
+
 // Window
 aconfig_declarations {
     name: "com.android.window.flags.window-aconfig",
@@ -215,6 +225,19 @@
     defaults: ["framework-minus-apex-aconfig-java-defaults"],
 }
 
+// Package Manager
+aconfig_declarations {
+    name: "android.content.pm.flags-aconfig",
+    package: "android.content.pm",
+    srcs: ["core/java/android/content/pm/*.aconfig"],
+}
+
+java_aconfig_library {
+    name: "android.content.pm.flags-aconfig-java",
+    aconfig_declarations: "android.content.pm.flags-aconfig",
+    defaults: ["framework-minus-apex-aconfig-java-defaults"],
+}
+
 // Media BetterTogether
 aconfig_declarations {
     name: "com.android.media.flags.bettertogether-aconfig",
diff --git a/apex/jobscheduler/framework/java/android/os/PowerExemptionManager.java b/apex/jobscheduler/framework/java/android/os/PowerExemptionManager.java
index 66c1efc..24d815f 100644
--- a/apex/jobscheduler/framework/java/android/os/PowerExemptionManager.java
+++ b/apex/jobscheduler/framework/java/android/os/PowerExemptionManager.java
@@ -420,7 +420,7 @@
     public static final int REASON_SYSTEM_EXEMPT_APP_OP = 327;
 
     /**
-     * Granted by {@link com.android.server.pm.PackageArchiverService} to the installer responsible
+     * Granted by {@link com.android.server.pm.PackageArchiver} to the installer responsible
      * for unarchiving an app.
      *
      * @hide
diff --git a/core/api/current.txt b/core/api/current.txt
index 078c46f..1879da5 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -17653,9 +17653,13 @@
 package android.graphics.text {
 
   public final class LineBreakConfig {
+    method @FlaggedApi(FLAG_NO_BREAK_NO_HYPHENATION_SPAN) public int getHyphenation();
     method public int getLineBreakStyle();
     method public int getLineBreakWordStyle();
     method @NonNull public android.graphics.text.LineBreakConfig merge(@NonNull android.graphics.text.LineBreakConfig);
+    field @FlaggedApi(FLAG_NO_BREAK_NO_HYPHENATION_SPAN) public static final int HYPHENATION_DISABLED = 0; // 0x0
+    field @FlaggedApi(FLAG_NO_BREAK_NO_HYPHENATION_SPAN) public static final int HYPHENATION_ENABLED = 1; // 0x1
+    field @FlaggedApi(FLAG_NO_BREAK_NO_HYPHENATION_SPAN) public static final int HYPHENATION_UNSPECIFIED = -1; // 0xffffffff
     field public static final int LINE_BREAK_STYLE_LOOSE = 1; // 0x1
     field public static final int LINE_BREAK_STYLE_NONE = 0; // 0x0
     field public static final int LINE_BREAK_STYLE_NORMAL = 2; // 0x2
@@ -17670,6 +17674,7 @@
     ctor public LineBreakConfig.Builder();
     method @NonNull public android.graphics.text.LineBreakConfig build();
     method @NonNull public android.graphics.text.LineBreakConfig.Builder merge(@NonNull android.graphics.text.LineBreakConfig);
+    method @FlaggedApi(FLAG_NO_BREAK_NO_HYPHENATION_SPAN) @NonNull public android.graphics.text.LineBreakConfig.Builder setHyphenation(int);
     method @NonNull public android.graphics.text.LineBreakConfig.Builder setLineBreakStyle(int);
     method @NonNull public android.graphics.text.LineBreakConfig.Builder setLineBreakWordStyle(int);
   }
@@ -46581,9 +46586,9 @@
   }
 
   public class BoringLayout extends android.text.Layout implements android.text.TextUtils.EllipsizeCallback {
-    ctor @Deprecated public BoringLayout(CharSequence, android.text.TextPaint, int, android.text.Layout.Alignment, float, float, android.text.BoringLayout.Metrics, boolean);
-    ctor @Deprecated public BoringLayout(CharSequence, android.text.TextPaint, int, android.text.Layout.Alignment, float, float, android.text.BoringLayout.Metrics, boolean, android.text.TextUtils.TruncateAt, int);
-    ctor @Deprecated public BoringLayout(@NonNull CharSequence, @NonNull android.text.TextPaint, @IntRange(from=0) int, @NonNull android.text.Layout.Alignment, float, float, @NonNull android.text.BoringLayout.Metrics, boolean, @Nullable android.text.TextUtils.TruncateAt, @IntRange(from=0) int, boolean);
+    ctor public BoringLayout(CharSequence, android.text.TextPaint, int, android.text.Layout.Alignment, float, float, android.text.BoringLayout.Metrics, boolean);
+    ctor public BoringLayout(CharSequence, android.text.TextPaint, int, android.text.Layout.Alignment, float, float, android.text.BoringLayout.Metrics, boolean, android.text.TextUtils.TruncateAt, int);
+    ctor public BoringLayout(@NonNull CharSequence, @NonNull android.text.TextPaint, @IntRange(from=0) int, @NonNull android.text.Layout.Alignment, float, float, @NonNull android.text.BoringLayout.Metrics, boolean, @Nullable android.text.TextUtils.TruncateAt, @IntRange(from=0) int, boolean);
     method public void ellipsized(int, int);
     method public int getBottomPadding();
     method public int getEllipsisCount(int);
@@ -46596,12 +46601,12 @@
     method public int getLineTop(int);
     method public int getParagraphDirection(int);
     method public int getTopPadding();
-    method @Deprecated public static android.text.BoringLayout.Metrics isBoring(CharSequence, android.text.TextPaint);
-    method @Deprecated public static android.text.BoringLayout.Metrics isBoring(CharSequence, android.text.TextPaint, android.text.BoringLayout.Metrics);
-    method @Deprecated @Nullable public static android.text.BoringLayout.Metrics isBoring(@NonNull CharSequence, @NonNull android.text.TextPaint, @NonNull android.text.TextDirectionHeuristic, boolean, @Nullable android.text.BoringLayout.Metrics);
-    method @Deprecated public static android.text.BoringLayout make(CharSequence, android.text.TextPaint, int, android.text.Layout.Alignment, float, float, android.text.BoringLayout.Metrics, boolean);
-    method @Deprecated public static android.text.BoringLayout make(CharSequence, android.text.TextPaint, int, android.text.Layout.Alignment, float, float, android.text.BoringLayout.Metrics, boolean, android.text.TextUtils.TruncateAt, int);
-    method @Deprecated @NonNull public static android.text.BoringLayout make(@NonNull CharSequence, @NonNull android.text.TextPaint, @IntRange(from=0) int, @NonNull android.text.Layout.Alignment, @NonNull android.text.BoringLayout.Metrics, boolean, @Nullable android.text.TextUtils.TruncateAt, @IntRange(from=0) int, boolean);
+    method public static android.text.BoringLayout.Metrics isBoring(CharSequence, android.text.TextPaint);
+    method public static android.text.BoringLayout.Metrics isBoring(CharSequence, android.text.TextPaint, android.text.BoringLayout.Metrics);
+    method @Nullable public static android.text.BoringLayout.Metrics isBoring(@NonNull CharSequence, @NonNull android.text.TextPaint, @NonNull android.text.TextDirectionHeuristic, boolean, @Nullable android.text.BoringLayout.Metrics);
+    method public static android.text.BoringLayout make(CharSequence, android.text.TextPaint, int, android.text.Layout.Alignment, float, float, android.text.BoringLayout.Metrics, boolean);
+    method public static android.text.BoringLayout make(CharSequence, android.text.TextPaint, int, android.text.Layout.Alignment, float, float, android.text.BoringLayout.Metrics, boolean, android.text.TextUtils.TruncateAt, int);
+    method @NonNull public static android.text.BoringLayout make(@NonNull CharSequence, @NonNull android.text.TextPaint, @IntRange(from=0) int, @NonNull android.text.Layout.Alignment, @NonNull android.text.BoringLayout.Metrics, boolean, @Nullable android.text.TextUtils.TruncateAt, @IntRange(from=0) int, boolean);
     method public android.text.BoringLayout replaceOrMake(CharSequence, android.text.TextPaint, int, android.text.Layout.Alignment, float, float, android.text.BoringLayout.Metrics, boolean);
     method @NonNull public android.text.BoringLayout replaceOrMake(@NonNull CharSequence, @NonNull android.text.TextPaint, @IntRange(from=0) int, @NonNull android.text.Layout.Alignment, @NonNull android.text.BoringLayout.Metrics, boolean, @Nullable android.text.TextUtils.TruncateAt, @IntRange(from=0) int, boolean);
     method public android.text.BoringLayout replaceOrMake(CharSequence, android.text.TextPaint, int, android.text.Layout.Alignment, float, float, android.text.BoringLayout.Metrics, boolean, android.text.TextUtils.TruncateAt, int);
@@ -46609,7 +46614,7 @@
 
   public static class BoringLayout.Metrics extends android.graphics.Paint.FontMetricsInt {
     ctor public BoringLayout.Metrics();
-    method @NonNull public android.graphics.RectF getDrawingBoundingBox();
+    method @FlaggedApi(FLAG_USE_BOUNDS_FOR_WIDTH) @NonNull public android.graphics.RectF getDrawingBoundingBox();
     field public int width;
   }
 
@@ -46794,7 +46799,7 @@
 
   public abstract class Layout {
     ctor protected Layout(CharSequence, android.text.TextPaint, int, android.text.Layout.Alignment, float, float);
-    method @NonNull public android.graphics.RectF computeDrawingBoundingBox();
+    method @FlaggedApi(FLAG_USE_BOUNDS_FOR_WIDTH) @NonNull public android.graphics.RectF computeDrawingBoundingBox();
     method public void draw(android.graphics.Canvas);
     method public void draw(android.graphics.Canvas, android.graphics.Path, android.graphics.Paint, int);
     method public void draw(@NonNull android.graphics.Canvas, @Nullable java.util.List<android.graphics.Path>, @Nullable java.util.List<android.graphics.Paint>, @Nullable android.graphics.Path, @Nullable android.graphics.Paint, int);
@@ -46803,24 +46808,24 @@
     method public void fillCharacterBounds(@IntRange(from=0) int, @IntRange(from=0) int, @NonNull float[], @IntRange(from=0) int);
     method @NonNull public final android.text.Layout.Alignment getAlignment();
     method public abstract int getBottomPadding();
-    method public final int getBreakStrategy();
+    method @FlaggedApi(FLAG_USE_BOUNDS_FOR_WIDTH) public final int getBreakStrategy();
     method public void getCursorPath(int, android.graphics.Path, CharSequence);
     method public static float getDesiredWidth(CharSequence, android.text.TextPaint);
     method public static float getDesiredWidth(CharSequence, int, int, android.text.TextPaint);
     method public abstract int getEllipsisCount(int);
     method public abstract int getEllipsisStart(int);
-    method @Nullable public final android.text.TextUtils.TruncateAt getEllipsize();
+    method @FlaggedApi(FLAG_USE_BOUNDS_FOR_WIDTH) @Nullable public final android.text.TextUtils.TruncateAt getEllipsize();
     method @IntRange(from=0) public int getEllipsizedWidth();
     method public int getHeight();
-    method public final int getHyphenationFrequency();
-    method public final int getJustificationMode();
-    method @Nullable public final int[] getLeftIndents();
+    method @FlaggedApi(FLAG_USE_BOUNDS_FOR_WIDTH) public final int getHyphenationFrequency();
+    method @FlaggedApi(FLAG_USE_BOUNDS_FOR_WIDTH) public final int getJustificationMode();
+    method @FlaggedApi(FLAG_USE_BOUNDS_FOR_WIDTH) @Nullable public final int[] getLeftIndents();
     method public final int getLineAscent(int);
     method public final int getLineBaseline(int);
     method public final int getLineBottom(int);
     method public int getLineBottom(int, boolean);
     method public int getLineBounds(int, android.graphics.Rect);
-    method @NonNull public android.graphics.text.LineBreakConfig getLineBreakConfig();
+    method @FlaggedApi(FLAG_USE_BOUNDS_FOR_WIDTH) @NonNull public android.graphics.text.LineBreakConfig getLineBreakConfig();
     method public abstract boolean getLineContainsTab(int);
     method public abstract int getLineCount();
     method public abstract int getLineDescent(int);
@@ -46831,13 +46836,13 @@
     method public float getLineLeft(int);
     method public float getLineMax(int);
     method public float getLineRight(int);
-    method public final float getLineSpacingAmount();
-    method public final float getLineSpacingMultiplier();
+    method @FlaggedApi(FLAG_USE_BOUNDS_FOR_WIDTH) public final float getLineSpacingAmount();
+    method @FlaggedApi(FLAG_USE_BOUNDS_FOR_WIDTH) public final float getLineSpacingMultiplier();
     method public abstract int getLineStart(int);
     method public abstract int getLineTop(int);
     method public int getLineVisibleEnd(int);
     method public float getLineWidth(int);
-    method @IntRange(from=1) public final int getMaxLines();
+    method @FlaggedApi(FLAG_USE_BOUNDS_FOR_WIDTH) @IntRange(from=1) public final int getMaxLines();
     method public int getOffsetForHorizontal(int, float);
     method public int getOffsetToLeftOf(int);
     method public int getOffsetToRightOf(int);
@@ -46848,19 +46853,19 @@
     method public final int getParagraphRight(int);
     method public float getPrimaryHorizontal(int);
     method @Nullable public int[] getRangeForRect(@NonNull android.graphics.RectF, @NonNull android.text.SegmentFinder, @NonNull android.text.Layout.TextInclusionStrategy);
-    method @Nullable public final int[] getRightIndents();
+    method @FlaggedApi(FLAG_USE_BOUNDS_FOR_WIDTH) @Nullable public final int[] getRightIndents();
     method public float getSecondaryHorizontal(int);
     method public void getSelectionPath(int, int, android.graphics.Path);
     method public final float getSpacingAdd();
     method public final float getSpacingMultiplier();
-    method @NonNull public final CharSequence getText();
-    method @NonNull public final android.text.TextDirectionHeuristic getTextDirectionHeuristic();
+    method @FlaggedApi(FLAG_USE_BOUNDS_FOR_WIDTH) @NonNull public final CharSequence getText();
+    method @FlaggedApi(FLAG_USE_BOUNDS_FOR_WIDTH) @NonNull public final android.text.TextDirectionHeuristic getTextDirectionHeuristic();
     method public abstract int getTopPadding();
-    method public boolean getUseBoundsForWidth();
+    method @FlaggedApi(FLAG_USE_BOUNDS_FOR_WIDTH) public boolean getUseBoundsForWidth();
     method @IntRange(from=0) public final int getWidth();
     method public final void increaseWidthTo(int);
     method public boolean isFallbackLineSpacingEnabled();
-    method public final boolean isFontPaddingIncluded();
+    method @FlaggedApi(FLAG_USE_BOUNDS_FOR_WIDTH) public final boolean isFontPaddingIncluded();
     method public boolean isRtlCharAt(int);
     method protected final boolean isSpanned();
     field public static final int BREAK_STRATEGY_BALANCED = 2; // 0x2
@@ -46888,7 +46893,7 @@
     enum_constant public static final android.text.Layout.Alignment ALIGN_OPPOSITE;
   }
 
-  public static final class Layout.Builder {
+  @FlaggedApi(FLAG_USE_BOUNDS_FOR_WIDTH) public static final class Layout.Builder {
     ctor public Layout.Builder(@NonNull CharSequence, @IntRange(from=0) int, @IntRange(from=0) int, @NonNull android.text.TextPaint, @IntRange(from=0) int);
     method @NonNull public android.text.Layout build();
     method @NonNull public android.text.Layout.Builder setAlignment(@NonNull android.text.Layout.Alignment);
@@ -46906,7 +46911,7 @@
     method @NonNull public android.text.Layout.Builder setMaxLines(@IntRange(from=1) int);
     method @NonNull public android.text.Layout.Builder setRightIndents(@Nullable int[]);
     method @NonNull public android.text.Layout.Builder setTextDirectionHeuristic(@NonNull android.text.TextDirectionHeuristic);
-    method @NonNull public android.text.Layout.Builder setUseBoundsForWidth(boolean);
+    method @FlaggedApi(FLAG_USE_BOUNDS_FOR_WIDTH) @NonNull public android.text.Layout.Builder setUseBoundsForWidth(boolean);
   }
 
   public static class Layout.Directions {
@@ -47883,11 +47888,15 @@
     method public void writeToParcel(@NonNull android.os.Parcel, int);
   }
 
-  public class LineBreakConfigSpan {
+  @FlaggedApi(FLAG_NO_BREAK_NO_HYPHENATION_SPAN) public class LineBreakConfigSpan {
     ctor public LineBreakConfigSpan(@NonNull android.graphics.text.LineBreakConfig);
     method @NonNull public android.graphics.text.LineBreakConfig getLineBreakConfig();
   }
 
+  @FlaggedApi(FLAG_NO_BREAK_NO_HYPHENATION_SPAN) public static final class LineBreakConfigSpan.NoHyphenationSpan extends android.text.style.LineBreakConfigSpan {
+    ctor @FlaggedApi(FLAG_NO_BREAK_NO_HYPHENATION_SPAN) public LineBreakConfigSpan.NoHyphenationSpan();
+  }
+
   public interface LineHeightSpan extends android.text.style.ParagraphStyle android.text.style.WrapTogetherSpan {
     method public void chooseHeight(CharSequence, int, int, int, int, android.graphics.Paint.FontMetricsInt);
   }
@@ -59879,7 +59888,7 @@
     method public final android.text.method.TransformationMethod getTransformationMethod();
     method public android.graphics.Typeface getTypeface();
     method public android.text.style.URLSpan[] getUrls();
-    method public boolean getUseBoundsForWidth();
+    method @FlaggedApi(FLAG_USE_BOUNDS_FOR_WIDTH) public boolean getUseBoundsForWidth();
     method public boolean hasSelection();
     method public boolean isAllCaps();
     method public boolean isCursorVisible();
@@ -60022,7 +60031,7 @@
     method public final void setTransformationMethod(android.text.method.TransformationMethod);
     method public void setTypeface(@Nullable android.graphics.Typeface, int);
     method public void setTypeface(@Nullable android.graphics.Typeface);
-    method public void setUseBoundsForWidth(boolean);
+    method @FlaggedApi(FLAG_USE_BOUNDS_FOR_WIDTH) public void setUseBoundsForWidth(boolean);
     method public void setWidth(int);
     field public static final int AUTO_SIZE_TEXT_TYPE_NONE = 0; // 0x0
     field public static final int AUTO_SIZE_TEXT_TYPE_UNIFORM = 1; // 0x1
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index eba1fbe..fcfa41a6 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -3798,13 +3798,6 @@
     field @RequiresPermission(android.Manifest.permission.ACCESS_SHORTCUTS) public static final int FLAG_GET_PERSONS_DATA = 2048; // 0x800
   }
 
-  public class PackageArchiver {
-    method @RequiresPermission(anyOf={android.Manifest.permission.DELETE_PACKAGES, android.Manifest.permission.REQUEST_DELETE_PACKAGES}) public void requestArchive(@NonNull String, @NonNull android.content.IntentSender) throws android.content.pm.PackageManager.NameNotFoundException;
-    method @RequiresPermission(anyOf={android.Manifest.permission.INSTALL_PACKAGES, android.Manifest.permission.REQUEST_INSTALL_PACKAGES}) public void requestUnarchive(@NonNull String) throws android.content.pm.PackageManager.NameNotFoundException;
-    field public static final String EXTRA_UNARCHIVE_ALL_USERS = "android.content.pm.extra.UNARCHIVE_ALL_USERS";
-    field public static final String EXTRA_UNARCHIVE_PACKAGE_NAME = "android.content.pm.extra.UNARCHIVE_PACKAGE_NAME";
-  }
-
   public class PackageInfo implements android.os.Parcelable {
     field public boolean isArchived;
   }
@@ -3812,6 +3805,8 @@
   public class PackageInstaller {
     method @NonNull public android.content.pm.PackageInstaller.InstallInfo readInstallInfo(@NonNull java.io.File, int) throws android.content.pm.PackageInstaller.PackageParsingException;
     method @NonNull public android.content.pm.PackageInstaller.InstallInfo readInstallInfo(@NonNull android.os.ParcelFileDescriptor, @Nullable String, int) throws android.content.pm.PackageInstaller.PackageParsingException;
+    method @FlaggedApi(Flags.FLAG_ARCHIVING) @RequiresPermission(anyOf={android.Manifest.permission.DELETE_PACKAGES, android.Manifest.permission.REQUEST_DELETE_PACKAGES}) public void requestArchive(@NonNull String, @NonNull android.content.IntentSender) throws android.content.pm.PackageManager.NameNotFoundException;
+    method @FlaggedApi(Flags.FLAG_ARCHIVING) @RequiresPermission(anyOf={android.Manifest.permission.INSTALL_PACKAGES, android.Manifest.permission.REQUEST_INSTALL_PACKAGES}) public void requestUnarchive(@NonNull String) throws android.content.pm.PackageManager.NameNotFoundException;
     method @RequiresPermission(android.Manifest.permission.INSTALL_PACKAGES) public void setPermissionsResult(int, boolean);
     field public static final String ACTION_CONFIRM_INSTALL = "android.content.pm.action.CONFIRM_INSTALL";
     field public static final String ACTION_CONFIRM_PRE_APPROVAL = "android.content.pm.action.CONFIRM_PRE_APPROVAL";
@@ -3822,6 +3817,8 @@
     field public static final String EXTRA_DATA_LOADER_TYPE = "android.content.pm.extra.DATA_LOADER_TYPE";
     field public static final String EXTRA_LEGACY_STATUS = "android.content.pm.extra.LEGACY_STATUS";
     field public static final String EXTRA_RESOLVED_BASE_PATH = "android.content.pm.extra.RESOLVED_BASE_PATH";
+    field @FlaggedApi(Flags.FLAG_ARCHIVING) public static final String EXTRA_UNARCHIVE_ALL_USERS = "android.content.pm.extra.UNARCHIVE_ALL_USERS";
+    field @FlaggedApi(Flags.FLAG_ARCHIVING) public static final String EXTRA_UNARCHIVE_PACKAGE_NAME = "android.content.pm.extra.UNARCHIVE_PACKAGE_NAME";
     field public static final int LOCATION_DATA_APP = 0; // 0x0
     field public static final int LOCATION_MEDIA_DATA = 2; // 0x2
     field public static final int LOCATION_MEDIA_OBB = 1; // 0x1
@@ -3906,7 +3903,6 @@
     method @NonNull @RequiresPermission(android.Manifest.permission.ACCESS_INSTANT_APPS) public abstract java.util.List<android.content.pm.InstantAppInfo> getInstantApps();
     method @Deprecated @NonNull public abstract java.util.List<android.content.pm.IntentFilterVerificationInfo> getIntentFilterVerifications(@NonNull String);
     method @Deprecated @RequiresPermission(android.Manifest.permission.INTERACT_ACROSS_USERS_FULL) public abstract int getIntentVerificationStatusAsUser(@NonNull String, int);
-    method @NonNull public android.content.pm.PackageArchiver getPackageArchiver();
     method @RequiresPermission(android.Manifest.permission.INTERACT_ACROSS_USERS_FULL) public int getPackageUidAsUser(@NonNull String, @NonNull android.content.pm.PackageManager.PackageInfoFlags, int) throws android.content.pm.PackageManager.NameNotFoundException;
     method @android.content.pm.PackageManager.PermissionFlags @RequiresPermission(anyOf={android.Manifest.permission.GRANT_RUNTIME_PERMISSIONS, android.Manifest.permission.REVOKE_RUNTIME_PERMISSIONS, android.Manifest.permission.GET_RUNTIME_PERMISSIONS}) public abstract int getPermissionFlags(@NonNull String, @NonNull String, @NonNull android.os.UserHandle);
     method @NonNull @RequiresPermission(android.Manifest.permission.SUSPEND_APPS) public String[] getUnsuspendablePackages(@NonNull String[]);
@@ -3932,6 +3928,7 @@
     method @RequiresPermission(android.Manifest.permission.SET_HARMFUL_APP_WARNINGS) public void setHarmfulAppWarning(@NonNull String, @Nullable CharSequence);
     method @Deprecated @Nullable @RequiresPermission(android.Manifest.permission.SUSPEND_APPS) public String[] setPackagesSuspended(@Nullable String[], boolean, @Nullable android.os.PersistableBundle, @Nullable android.os.PersistableBundle, @Nullable String);
     method @Nullable @RequiresPermission(value=android.Manifest.permission.SUSPEND_APPS, conditional=true) public String[] setPackagesSuspended(@Nullable String[], boolean, @Nullable android.os.PersistableBundle, @Nullable android.os.PersistableBundle, @Nullable android.content.pm.SuspendDialogInfo);
+    method @FlaggedApi(android.content.pm.Flags.FLAG_QUARANTINED_ENABLED) @Nullable @RequiresPermission(value=android.Manifest.permission.SUSPEND_APPS, conditional=true) public String[] setPackagesSuspended(@Nullable String[], boolean, @Nullable android.os.PersistableBundle, @Nullable android.os.PersistableBundle, @Nullable android.content.pm.SuspendDialogInfo, int);
     method @RequiresPermission(value=android.Manifest.permission.CHANGE_COMPONENT_ENABLED_STATE, conditional=true) public void setSyntheticAppDetailsActivityEnabled(@NonNull String, boolean);
     method public void setSystemAppState(@NonNull String, int);
     method @RequiresPermission(android.Manifest.permission.INSTALL_PACKAGES) public abstract void setUpdateAvailable(@NonNull String, boolean);
@@ -3982,6 +3979,7 @@
     field public static final int FLAG_PERMISSION_USER_SENSITIVE_WHEN_DENIED = 512; // 0x200
     field public static final int FLAG_PERMISSION_USER_SENSITIVE_WHEN_GRANTED = 256; // 0x100
     field public static final int FLAG_PERMISSION_USER_SET = 1; // 0x1
+    field @FlaggedApi(android.content.pm.Flags.FLAG_QUARANTINED_ENABLED) public static final int FLAG_SUSPEND_QUARANTINED = 1; // 0x1
     field public static final int INSTALL_FAILED_ALREADY_EXISTS = -1; // 0xffffffff
     field public static final int INSTALL_FAILED_CONFLICTING_PROVIDER = -13; // 0xfffffff3
     field public static final int INSTALL_FAILED_CONTAINER_ERROR = -18; // 0xffffffee
diff --git a/core/java/Android.bp b/core/java/Android.bp
index 70cb597..13a1bd6 100644
--- a/core/java/Android.bp
+++ b/core/java/Android.bp
@@ -9,6 +9,11 @@
     default_applicable_licenses: ["frameworks_base_license"],
 }
 
+aidl_library {
+    name: "HardwareBuffer_aidl",
+    hdrs: ["android/hardware/HardwareBuffer.aidl"],
+}
+
 filegroup {
     name: "framework-core-sources",
     srcs: [
diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java
index a366464..7bc6f9bf 100644
--- a/core/java/android/app/Activity.java
+++ b/core/java/android/app/Activity.java
@@ -23,7 +23,7 @@
 import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
 import static android.app.WindowConfiguration.inMultiWindowMode;
 import static android.os.Process.myUid;
-
+import static com.android.sdksandbox.flags.Flags.sandboxActivitySdkBasedContext;
 import static java.lang.Character.MIN_VALUE;
 
 import android.annotation.AnimRes;
@@ -8631,6 +8631,12 @@
             Configuration config, String referrer, IVoiceInteractor voiceInteractor,
             Window window, ActivityConfigCallback activityConfigCallback, IBinder assistToken,
             IBinder shareableActivityToken) {
+        if (sandboxActivitySdkBasedContext()) {
+            // Sandbox activities extract a token from the intent's extra to identify the related
+            // SDK as part of overriding attachBaseContext, then it wraps the passed context in an
+            // SDK ContextWrapper, so mIntent has to be set before calling attachBaseContext.
+            mIntent = intent;
+        }
         attachBaseContext(context);
 
         mFragments.attachHost(null /*parent*/);
@@ -8656,6 +8662,7 @@
         mShareableActivityToken = shareableActivityToken;
         mIdent = ident;
         mApplication = application;
+        //TODO(b/300059435): do not set the mIntent again as part of the flag clean up.
         mIntent = intent;
         mReferrer = referrer;
         mComponent = intent.getComponent();
diff --git a/core/java/android/app/ActivityOptions.java b/core/java/android/app/ActivityOptions.java
index 2ae7216..895dde7 100644
--- a/core/java/android/app/ActivityOptions.java
+++ b/core/java/android/app/ActivityOptions.java
@@ -1507,8 +1507,9 @@
     }
 
     /** @hide */
-    public void setRemoteTransition(@Nullable RemoteTransition remoteTransition) {
+    public ActivityOptions setRemoteTransition(@Nullable RemoteTransition remoteTransition) {
         mRemoteTransition = remoteTransition;
+        return this;
     }
 
     /** @hide */
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index f28b4b4..00e546a 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -38,6 +38,7 @@
 import static android.window.ConfigurationHelper.shouldUpdateResources;
 import static com.android.internal.annotations.VisibleForTesting.Visibility.PACKAGE;
 import static com.android.internal.os.SafeZipPathValidatorCallback.VALIDATE_ZIP_PATH_FOR_PATH_TRAVERSAL;
+import static com.android.sdksandbox.flags.Flags.sandboxActivitySdkBasedContext;
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
@@ -53,6 +54,8 @@
 import android.app.backup.BackupAnnotations.BackupDestination;
 import android.app.backup.BackupAnnotations.OperationType;
 import android.app.compat.CompatChanges;
+import android.app.sdksandbox.sandboxactivity.ActivityContextInfo;
+import android.app.sdksandbox.sandboxactivity.ActivityContextInfoProvider;
 import android.app.servertransaction.ActivityLifecycleItem;
 import android.app.servertransaction.ActivityLifecycleItem.LifecycleState;
 import android.app.servertransaction.ActivityRelaunchItem;
@@ -3655,15 +3658,16 @@
     }
 
     @UnsupportedAppUsage
-    public final void sendActivityResult(
-            IBinder token, String id, int requestCode,
+    public void sendActivityResult(
+            IBinder activityToken, String id, int requestCode,
             int resultCode, Intent data) {
         if (DEBUG_RESULTS) Slog.v(TAG, "sendActivityResult: id=" + id
                 + " req=" + requestCode + " res=" + resultCode + " data=" + data);
         ArrayList<ResultInfo> list = new ArrayList<ResultInfo>();
         list.add(new ResultInfo(id, requestCode, resultCode, data));
-        final ClientTransaction clientTransaction = ClientTransaction.obtain(mAppThread, token);
-        clientTransaction.addCallback(ActivityResultItem.obtain(list));
+        final ClientTransaction clientTransaction = ClientTransaction.obtain(mAppThread,
+                activityToken);
+        clientTransaction.addCallback(ActivityResultItem.obtain(activityToken, list));
         try {
             mAppThread.scheduleTransaction(clientTransaction);
         } catch (RemoteException e) {
@@ -3733,16 +3737,38 @@
                     r.activityInfo.targetActivity);
         }
 
-        ContextImpl appContext = createBaseContextForActivity(r);
+        boolean isSandboxActivityContext = sandboxActivitySdkBasedContext()
+                && r.intent.isSandboxActivity(mSystemContext);
+        boolean isSandboxedSdkContextUsed = false;
+        ContextImpl activityBaseContext;
+        if (isSandboxActivityContext) {
+            activityBaseContext = createBaseContextForSandboxActivity(r);
+            if (activityBaseContext == null) {
+                // Failed to retrieve the SDK based sandbox activity context, falling back to the
+                // app based context.
+                activityBaseContext = createBaseContextForActivity(r);
+            } else {
+                isSandboxedSdkContextUsed = true;
+            }
+        } else {
+            activityBaseContext = createBaseContextForActivity(r);
+        }
         Activity activity = null;
         try {
-            java.lang.ClassLoader cl = appContext.getClassLoader();
+            java.lang.ClassLoader cl;
+            if (isSandboxedSdkContextUsed) {
+                // In case of sandbox activity, the context refers to the an SDK with no visibility
+                // on the SandboxedActivity java class, the App context should be used instead.
+                cl = activityBaseContext.getApplicationContext().getClassLoader();
+            } else {
+                cl = activityBaseContext.getClassLoader();
+            }
             activity = mInstrumentation.newActivity(
                     cl, component.getClassName(), r.intent);
             StrictMode.incrementExpectedActivityCount(activity.getClass());
             r.intent.setExtrasClassLoader(cl);
             r.intent.prepareToEnterProcess(isProtectedComponent(r.activityInfo),
-                    appContext.getAttributionSource());
+                    activityBaseContext.getAttributionSource());
             if (r.state != null) {
                 r.state.setClassLoader(cl);
             }
@@ -3773,7 +3799,8 @@
             }
 
             if (activity != null) {
-                CharSequence title = r.activityInfo.loadLabel(appContext.getPackageManager());
+                CharSequence title =
+                        r.activityInfo.loadLabel(activityBaseContext.getPackageManager());
                 Configuration config =
                         new Configuration(mConfigurationController.getCompatConfiguration());
                 if (r.overrideConfig != null) {
@@ -3790,11 +3817,11 @@
 
                 // Activity resources must be initialized with the same loaders as the
                 // application context.
-                appContext.getResources().addLoaders(
+                activityBaseContext.getResources().addLoaders(
                         app.getResources().getLoaders().toArray(new ResourcesLoader[0]));
 
-                appContext.setOuterContext(activity);
-                activity.attach(appContext, this, getInstrumentation(), r.token,
+                activityBaseContext.setOuterContext(activity);
+                activity.attach(activityBaseContext, this, getInstrumentation(), r.token,
                         r.ident, app, r.intent, r.activityInfo, title, r.parent,
                         r.embeddedID, r.lastNonConfigurationInstances, config,
                         r.referrer, r.voiceInteractor, window, r.activityConfigCallback,
@@ -3951,6 +3978,44 @@
     }
 
     /**
+     * Creates the base context for the sandbox activity based on its corresponding SDK {@link
+     * ApplicationInfo} and flags.
+     */
+    @Nullable
+    private ContextImpl createBaseContextForSandboxActivity(@NonNull ActivityClientRecord r) {
+        ActivityContextInfoProvider contextInfoProvider = ActivityContextInfoProvider.getInstance();
+
+        ActivityContextInfo contextInfo;
+        try {
+            contextInfo = contextInfoProvider.getActivityContextInfo(r.intent);
+        } catch (IllegalArgumentException e) {
+            Log.e(TAG, "Passed intent does not match an expected sandbox activity", e);
+            return null;
+        } catch (IllegalStateException e) {
+            Log.e(TAG, "SDK customized context flag is disabled", e);
+            return null;
+        } catch (Exception e) { // generic catch to unexpected exceptions
+            Log.e(TAG, "Failed to create context for sandbox activity", e);
+            return null;
+        }
+
+        final int displayId = ActivityClient.getInstance().getDisplayId(r.token);
+        final LoadedApk sdkApk = getPackageInfo(
+                contextInfo.getSdkApplicationInfo(),
+                r.packageInfo.getCompatibilityInfo(),
+                ActivityContextInfo.CONTEXT_FLAGS);
+
+        final ContextImpl activityContext = ContextImpl.createActivityContext(
+                this, sdkApk, r.activityInfo, r.token, displayId, r.overrideConfig);
+
+        // Set sandbox app's context as the application context for sdk context
+        activityContext.mPackageInfo.makeApplicationInner(
+                /*forceDefaultAppClass=*/false, mInstrumentation);
+
+        return activityContext;
+    }
+
+    /**
      * Extended implementation of activity launch. Used when server requests a launch or relaunch.
      */
     @Override
@@ -4365,16 +4430,16 @@
 
     private void schedulePauseWithUserLeavingHint(ActivityClientRecord r) {
         final ClientTransaction transaction = ClientTransaction.obtain(this.mAppThread, r.token);
-        transaction.setLifecycleStateRequest(PauseActivityItem.obtain(r.activity.isFinishing(),
-                /* userLeaving */ true, r.activity.mConfigChangeFlags, /* dontReport */ false,
-                /* autoEnteringPip */ false));
+        transaction.setLifecycleStateRequest(PauseActivityItem.obtain(r.token,
+                r.activity.isFinishing(), /* userLeaving */ true, r.activity.mConfigChangeFlags,
+                /* dontReport */ false, /* autoEnteringPip */ false));
         executeTransaction(transaction);
     }
 
     private void scheduleResume(ActivityClientRecord r) {
         final ClientTransaction transaction = ClientTransaction.obtain(this.mAppThread, r.token);
-        transaction.setLifecycleStateRequest(ResumeActivityItem.obtain(/* isForward */ false,
-                /* shouldSendCompatFakeFocus */ false));
+        transaction.setLifecycleStateRequest(ResumeActivityItem.obtain(r.token,
+                /* isForward */ false, /* shouldSendCompatFakeFocus */ false));
         executeTransaction(transaction);
     }
 
@@ -5958,8 +6023,8 @@
                         ? r.createdConfig : mConfigurationController.getConfiguration(),
                 r.overrideConfig);
         final ActivityRelaunchItem activityRelaunchItem = ActivityRelaunchItem.obtain(
-                null /* pendingResults */, null /* pendingIntents */, 0 /* configChanges */,
-                mergedConfiguration, r.mPreserveWindow);
+                r.token, null /* pendingResults */, null /* pendingIntents */,
+                0 /* configChanges */, mergedConfiguration, r.mPreserveWindow);
         // Make sure to match the existing lifecycle state in the end of the transaction.
         final ActivityLifecycleItem lifecycleRequest =
                 TransactionExecutorHelper.getLifecycleRequestForCurrentState(r);
diff --git a/core/java/android/app/ApplicationPackageManager.java b/core/java/android/app/ApplicationPackageManager.java
index fcd13b8..0255860 100644
--- a/core/java/android/app/ApplicationPackageManager.java
+++ b/core/java/android/app/ApplicationPackageManager.java
@@ -64,7 +64,6 @@
 import android.content.pm.IntentFilterVerificationInfo;
 import android.content.pm.KeySet;
 import android.content.pm.ModuleInfo;
-import android.content.pm.PackageArchiver;
 import android.content.pm.PackageInfo;
 import android.content.pm.PackageInstaller;
 import android.content.pm.PackageItemInfo;
@@ -173,7 +172,6 @@
     private volatile UserManager mUserManager;
     private volatile PermissionManager mPermissionManager;
     private volatile PackageInstaller mInstaller;
-    private volatile PackageArchiver mPackageArchiver;
     private volatile ArtManager mArtManager;
     private volatile DevicePolicyManager mDevicePolicyManager;
     private volatile String mPermissionsControllerPackageName;
@@ -3284,18 +3282,6 @@
     }
 
     @Override
-    public PackageArchiver getPackageArchiver() {
-        if (mPackageArchiver == null) {
-            try {
-                mPackageArchiver = new PackageArchiver(mContext, mPM.getPackageArchiverService());
-            } catch (RemoteException e) {
-                throw e.rethrowFromSystemServer();
-            }
-        }
-        return mPackageArchiver;
-    }
-
-    @Override
     public boolean isPackageAvailable(String packageName) {
         try {
             return mPM.isPackageAvailable(packageName, getUserId());
diff --git a/core/java/android/app/servertransaction/ActivityConfigurationChangeItem.java b/core/java/android/app/servertransaction/ActivityConfigurationChangeItem.java
index e409254..c2c5427 100644
--- a/core/java/android/app/servertransaction/ActivityConfigurationChangeItem.java
+++ b/core/java/android/app/servertransaction/ActivityConfigurationChangeItem.java
@@ -45,7 +45,7 @@
         CompatibilityInfo.applyOverrideScaleIfNeeded(mConfiguration);
         // Notify the client of an upcoming change in the token configuration. This ensures that
         // batches of config change items only process the newest configuration.
-        client.updatePendingActivityConfiguration(token, mConfiguration);
+        client.updatePendingActivityConfiguration(getActivityToken(), mConfiguration);
     }
 
     @Override
@@ -61,8 +61,7 @@
     @Override
     public Context getContextToUpdate(@NonNull ClientTransactionHandler client,
             @Nullable IBinder token) {
-        // TODO(b/260873529): Update ClientTransaction to bundle multiple activity config updates.
-        return client.getActivity(token);
+        return client.getActivity(getActivityToken());
     }
 
     // ObjectPoolItem implementation
@@ -70,7 +69,9 @@
     private ActivityConfigurationChangeItem() {}
 
     /** Obtain an instance initialized with provided params. */
-    public static ActivityConfigurationChangeItem obtain(@NonNull Configuration config) {
+    @NonNull
+    public static ActivityConfigurationChangeItem obtain(@NonNull IBinder activityToken,
+            @NonNull Configuration config) {
         if (config == null) {
             throw new IllegalArgumentException("Config must not be null.");
         }
@@ -80,6 +81,7 @@
         if (instance == null) {
             instance = new ActivityConfigurationChangeItem();
         }
+        instance.setActivityToken(activityToken);
         instance.mConfiguration = config;
 
         return instance;
@@ -87,6 +89,7 @@
 
     @Override
     public void recycle() {
+        super.recycle();
         mConfiguration = Configuration.EMPTY;
         ObjectPool.recycle(this);
     }
@@ -96,32 +99,34 @@
 
     /** Write to Parcel. */
     @Override
-    public void writeToParcel(Parcel dest, int flags) {
+    public void writeToParcel(@NonNull Parcel dest, int flags) {
+        super.writeToParcel(dest, flags);
         dest.writeTypedObject(mConfiguration, flags);
     }
 
     /** Read from Parcel. */
-    private ActivityConfigurationChangeItem(Parcel in) {
+    private ActivityConfigurationChangeItem(@NonNull Parcel in) {
+        super(in);
         mConfiguration = in.readTypedObject(Configuration.CREATOR);
     }
 
     public static final @NonNull Creator<ActivityConfigurationChangeItem> CREATOR =
-            new Creator<ActivityConfigurationChangeItem>() {
-        public ActivityConfigurationChangeItem createFromParcel(Parcel in) {
-            return new ActivityConfigurationChangeItem(in);
-        }
+            new Creator<>() {
+                public ActivityConfigurationChangeItem createFromParcel(@NonNull Parcel in) {
+                    return new ActivityConfigurationChangeItem(in);
+                }
 
-        public ActivityConfigurationChangeItem[] newArray(int size) {
-            return new ActivityConfigurationChangeItem[size];
-        }
-    };
+                public ActivityConfigurationChangeItem[] newArray(int size) {
+                    return new ActivityConfigurationChangeItem[size];
+                }
+            };
 
     @Override
     public boolean equals(@Nullable Object o) {
         if (this == o) {
             return true;
         }
-        if (o == null || getClass() != o.getClass()) {
+        if (!super.equals(o)) {
             return false;
         }
         final ActivityConfigurationChangeItem other = (ActivityConfigurationChangeItem) o;
@@ -130,11 +135,15 @@
 
     @Override
     public int hashCode() {
-        return mConfiguration.hashCode();
+        int result = 17;
+        result = 31 * result + super.hashCode();
+        result = 31 * result + Objects.hashCode(mConfiguration);
+        return result;
     }
 
     @Override
     public String toString() {
-        return "ActivityConfigurationChange{config=" + mConfiguration + "}";
+        return "ActivityConfigurationChange{" + super.toString()
+                + ",config=" + mConfiguration + "}";
     }
 }
diff --git a/core/java/android/app/servertransaction/ActivityLifecycleItem.java b/core/java/android/app/servertransaction/ActivityLifecycleItem.java
index cadb660..b34f678 100644
--- a/core/java/android/app/servertransaction/ActivityLifecycleItem.java
+++ b/core/java/android/app/servertransaction/ActivityLifecycleItem.java
@@ -17,6 +17,8 @@
 package android.app.servertransaction;
 
 import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.os.Parcel;
 
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
@@ -50,12 +52,13 @@
     public static final int ON_DESTROY = 6;
     public static final int ON_RESTART = 7;
 
+    ActivityLifecycleItem() {}
+
+    ActivityLifecycleItem(@NonNull Parcel in) {
+        super(in);
+    }
+
     /** A final lifecycle state that an activity should reach. */
     @LifecycleState
     public abstract int getTargetState();
-
-    /** Called by subclasses to make sure base implementation is cleaned up */
-    @Override
-    public void recycle() {
-    }
 }
diff --git a/core/java/android/app/servertransaction/ActivityRelaunchItem.java b/core/java/android/app/servertransaction/ActivityRelaunchItem.java
index a8b058a..491d026 100644
--- a/core/java/android/app/servertransaction/ActivityRelaunchItem.java
+++ b/core/java/android/app/servertransaction/ActivityRelaunchItem.java
@@ -56,18 +56,18 @@
     private ActivityClientRecord mActivityClientRecord;
 
     @Override
-    public void preExecute(ClientTransactionHandler client, IBinder token) {
+    public void preExecute(@NonNull ClientTransactionHandler client, @NonNull IBinder token) {
         // The local config is already scaled so only apply if this item is from server side.
         if (!client.isExecutingLocalTransaction()) {
             CompatibilityInfo.applyOverrideScaleIfNeeded(mConfig);
         }
-        mActivityClientRecord = client.prepareRelaunchActivity(token, mPendingResults,
+        mActivityClientRecord = client.prepareRelaunchActivity(getActivityToken(), mPendingResults,
                 mPendingNewIntents, mConfigChanges, mConfig, mPreserveWindow);
     }
 
     @Override
-    public void execute(ClientTransactionHandler client, ActivityClientRecord r,
-            PendingTransactionActions pendingActions) {
+    public void execute(@NonNull ClientTransactionHandler client, @NonNull ActivityClientRecord r,
+            @NonNull PendingTransactionActions pendingActions) {
         if (mActivityClientRecord == null) {
             if (DEBUG_ORDER) Slog.d(TAG, "Activity relaunch cancelled");
             return;
@@ -78,9 +78,9 @@
     }
 
     @Override
-    public void postExecute(ClientTransactionHandler client, IBinder token,
-            PendingTransactionActions pendingActions) {
-        final ActivityClientRecord r = getActivityClientRecord(client, token);
+    public void postExecute(@NonNull ClientTransactionHandler client, @NonNull IBinder token,
+            @NonNull PendingTransactionActions pendingActions) {
+        final ActivityClientRecord r = getActivityClientRecord(client);
         client.reportRelaunch(r);
     }
 
@@ -89,13 +89,16 @@
     private ActivityRelaunchItem() {}
 
     /** Obtain an instance initialized with provided params. */
-    public static ActivityRelaunchItem obtain(List<ResultInfo> pendingResults,
-            List<ReferrerIntent> pendingNewIntents, int configChanges, MergedConfiguration config,
-            boolean preserveWindow) {
+    @NonNull
+    public static ActivityRelaunchItem obtain(@NonNull IBinder activityToken,
+            @Nullable List<ResultInfo> pendingResults,
+            @Nullable List<ReferrerIntent> pendingNewIntents, int configChanges,
+            @NonNull MergedConfiguration config, boolean preserveWindow) {
         ActivityRelaunchItem instance = ObjectPool.obtain(ActivityRelaunchItem.class);
         if (instance == null) {
             instance = new ActivityRelaunchItem();
         }
+        instance.setActivityToken(activityToken);
         instance.mPendingResults = pendingResults;
         instance.mPendingNewIntents = pendingNewIntents;
         instance.mConfigChanges = configChanges;
@@ -107,6 +110,7 @@
 
     @Override
     public void recycle() {
+        super.recycle();
         mPendingResults = null;
         mPendingNewIntents = null;
         mConfigChanges = 0;
@@ -121,7 +125,8 @@
 
     /** Write to Parcel. */
     @Override
-    public void writeToParcel(Parcel dest, int flags) {
+    public void writeToParcel(@NonNull Parcel dest, int flags) {
+        super.writeToParcel(dest, flags);
         dest.writeTypedList(mPendingResults, flags);
         dest.writeTypedList(mPendingNewIntents, flags);
         dest.writeInt(mConfigChanges);
@@ -130,7 +135,8 @@
     }
 
     /** Read from Parcel. */
-    private ActivityRelaunchItem(Parcel in) {
+    private ActivityRelaunchItem(@NonNull Parcel in) {
+        super(in);
         mPendingResults = in.createTypedArrayList(ResultInfo.CREATOR);
         mPendingNewIntents = in.createTypedArrayList(ReferrerIntent.CREATOR);
         mConfigChanges = in.readInt();
@@ -139,22 +145,22 @@
     }
 
     public static final @NonNull Creator<ActivityRelaunchItem> CREATOR =
-            new Creator<ActivityRelaunchItem>() {
-        public ActivityRelaunchItem createFromParcel(Parcel in) {
-            return new ActivityRelaunchItem(in);
-        }
+            new Creator<>() {
+                public ActivityRelaunchItem createFromParcel(@NonNull Parcel in) {
+                    return new ActivityRelaunchItem(in);
+                }
 
-        public ActivityRelaunchItem[] newArray(int size) {
-            return new ActivityRelaunchItem[size];
-        }
-    };
+                public ActivityRelaunchItem[] newArray(int size) {
+                    return new ActivityRelaunchItem[size];
+                }
+            };
 
     @Override
     public boolean equals(@Nullable Object o) {
         if (this == o) {
             return true;
         }
-        if (o == null || getClass() != o.getClass()) {
+        if (!super.equals(o)) {
             return false;
         }
         final ActivityRelaunchItem other = (ActivityRelaunchItem) o;
@@ -167,6 +173,7 @@
     @Override
     public int hashCode() {
         int result = 17;
+        result = 31 * result + super.hashCode();
         result = 31 * result + Objects.hashCode(mPendingResults);
         result = 31 * result + Objects.hashCode(mPendingNewIntents);
         result = 31 * result + mConfigChanges;
@@ -177,8 +184,11 @@
 
     @Override
     public String toString() {
-        return "ActivityRelaunchItem{pendingResults=" + mPendingResults
-                + ",pendingNewIntents=" + mPendingNewIntents + ",configChanges="  + mConfigChanges
-                + ",config=" + mConfig + ",preserveWindow" + mPreserveWindow + "}";
+        return "ActivityRelaunchItem{" + super.toString()
+                + ",pendingResults=" + mPendingResults
+                + ",pendingNewIntents=" + mPendingNewIntents
+                + ",configChanges="  + mConfigChanges
+                + ",config=" + mConfig
+                + ",preserveWindow" + mPreserveWindow + "}";
     }
 }
diff --git a/core/java/android/app/servertransaction/ActivityResultItem.java b/core/java/android/app/servertransaction/ActivityResultItem.java
index 27d104b..24fced4 100644
--- a/core/java/android/app/servertransaction/ActivityResultItem.java
+++ b/core/java/android/app/servertransaction/ActivityResultItem.java
@@ -30,6 +30,7 @@
 import android.compat.annotation.EnabledAfter;
 import android.compat.annotation.UnsupportedAppUsage;
 import android.os.Build;
+import android.os.IBinder;
 import android.os.Parcel;
 import android.os.Parcelable;
 import android.os.Trace;
@@ -61,24 +62,26 @@
     }
 
     @Override
-    public void execute(ClientTransactionHandler client, ActivityClientRecord r,
-            PendingTransactionActions pendingActions) {
+    public void execute(@NonNull ClientTransactionHandler client, @NonNull ActivityClientRecord r,
+            @NonNull PendingTransactionActions pendingActions) {
         Trace.traceBegin(TRACE_TAG_ACTIVITY_MANAGER, "activityDeliverResult");
         client.handleSendResult(r, mResultInfoList, "ACTIVITY_RESULT");
         Trace.traceEnd(TRACE_TAG_ACTIVITY_MANAGER);
     }
 
-
     // ObjectPoolItem implementation
 
     private ActivityResultItem() {}
 
     /** Obtain an instance initialized with provided params. */
-    public static ActivityResultItem obtain(List<ResultInfo> resultInfoList) {
+    @NonNull
+    public static ActivityResultItem obtain(@NonNull IBinder activityToken,
+            @NonNull List<ResultInfo> resultInfoList) {
         ActivityResultItem instance = ObjectPool.obtain(ActivityResultItem.class);
         if (instance == null) {
             instance = new ActivityResultItem();
         }
+        instance.setActivityToken(activityToken);
         instance.mResultInfoList = resultInfoList;
 
         return instance;
@@ -86,41 +89,43 @@
 
     @Override
     public void recycle() {
+        super.recycle();
         mResultInfoList = null;
         ObjectPool.recycle(this);
     }
 
-
     // Parcelable implementation
 
     /** Write to Parcel. */
     @Override
-    public void writeToParcel(Parcel dest, int flags) {
+    public void writeToParcel(@NonNull Parcel dest, int flags) {
+        super.writeToParcel(dest, flags);
         dest.writeTypedList(mResultInfoList, flags);
     }
 
     /** Read from Parcel. */
-    private ActivityResultItem(Parcel in) {
+    private ActivityResultItem(@NonNull Parcel in) {
+        super(in);
         mResultInfoList = in.createTypedArrayList(ResultInfo.CREATOR);
     }
 
     public static final @NonNull Parcelable.Creator<ActivityResultItem> CREATOR =
-            new Parcelable.Creator<ActivityResultItem>() {
-        public ActivityResultItem createFromParcel(Parcel in) {
-            return new ActivityResultItem(in);
-        }
+            new Parcelable.Creator<>() {
+                public ActivityResultItem createFromParcel(@NonNull Parcel in) {
+                    return new ActivityResultItem(in);
+                }
 
-        public ActivityResultItem[] newArray(int size) {
-            return new ActivityResultItem[size];
-        }
-    };
+                public ActivityResultItem[] newArray(int size) {
+                    return new ActivityResultItem[size];
+                }
+            };
 
     @Override
     public boolean equals(@Nullable Object o) {
         if (this == o) {
             return true;
         }
-        if (o == null || getClass() != o.getClass()) {
+        if (!super.equals(o)) {
             return false;
         }
         final ActivityResultItem other = (ActivityResultItem) o;
@@ -129,11 +134,15 @@
 
     @Override
     public int hashCode() {
-        return mResultInfoList.hashCode();
+        int result = 17;
+        result = 31 * result + super.hashCode();
+        result = 31 * result + Objects.hashCode(mResultInfoList);
+        return result;
     }
 
     @Override
     public String toString() {
-        return "ActivityResultItem{resultInfoList=" + mResultInfoList + "}";
+        return "ActivityResultItem{" + super.toString()
+                + ",resultInfoList=" + mResultInfoList + "}";
     }
 }
diff --git a/core/java/android/app/servertransaction/ActivityTransactionItem.java b/core/java/android/app/servertransaction/ActivityTransactionItem.java
index 469a9bf..0f8879e 100644
--- a/core/java/android/app/servertransaction/ActivityTransactionItem.java
+++ b/core/java/android/app/servertransaction/ActivityTransactionItem.java
@@ -18,13 +18,18 @@
 
 import static com.android.internal.annotations.VisibleForTesting.Visibility.PACKAGE;
 
+import android.annotation.CallSuper;
 import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.app.ActivityThread.ActivityClientRecord;
 import android.app.ClientTransactionHandler;
 import android.os.IBinder;
+import android.os.Parcel;
 
 import com.android.internal.annotations.VisibleForTesting;
 
+import java.util.Objects;
+
 /**
  * An activity-targeting callback message to a client that can be scheduled and executed.
  * It also provides nullity-free version of
@@ -37,11 +42,16 @@
  * @hide
  */
 public abstract class ActivityTransactionItem extends ClientTransactionItem {
-    @Override
-    public final void execute(ClientTransactionHandler client, IBinder token,
-            PendingTransactionActions pendingActions) {
-        final ActivityClientRecord r = getActivityClientRecord(client, token);
 
+    /** Target client activity. */
+    private IBinder mActivityToken;
+
+    ActivityTransactionItem() {}
+
+    @Override
+    public final void execute(@NonNull ClientTransactionHandler client, @NonNull IBinder token,
+            @NonNull PendingTransactionActions pendingActions) {
+        final ActivityClientRecord r = getActivityClientRecord(client);
         execute(client, r, pendingActions);
     }
 
@@ -51,25 +61,80 @@
      */
     @VisibleForTesting(visibility = PACKAGE)
     public abstract void execute(@NonNull ClientTransactionHandler client,
-            @NonNull ActivityClientRecord r, PendingTransactionActions pendingActions);
+            @NonNull ActivityClientRecord r, @NonNull PendingTransactionActions pendingActions);
 
     /**
-     * Gets the {@link ActivityClientRecord} instance that corresponds to the provided token.
+     * Gets the {@link ActivityClientRecord} instance that this transaction item is for.
      * @param client Target client handler.
-     * @param token Target activity token.
-     * @return The {@link ActivityClientRecord} instance that corresponds to the provided token.
+     * @return The {@link ActivityClientRecord} instance that this transaction item is for.
      */
-    @NonNull ActivityClientRecord getActivityClientRecord(
-            @NonNull ClientTransactionHandler client, IBinder token) {
-        final ActivityClientRecord r = client.getActivityClient(token);
+    @NonNull
+    final ActivityClientRecord getActivityClientRecord(@NonNull ClientTransactionHandler client) {
+        final ActivityClientRecord r = client.getActivityClient(getActivityToken());
         if (r == null) {
             throw new IllegalArgumentException("Activity client record must not be null to execute "
                     + "transaction item: " + this);
         }
-        if (client.getActivity(token) == null) {
+        if (client.getActivity(getActivityToken()) == null) {
             throw new IllegalArgumentException("Activity must not be null to execute "
                     + "transaction item: " + this);
         }
         return r;
     }
+
+    @VisibleForTesting(visibility = PACKAGE)
+    @NonNull
+    @Override
+    public IBinder getActivityToken() {
+        return mActivityToken;
+    }
+
+    void setActivityToken(@NonNull IBinder activityToken) {
+        mActivityToken = activityToken;
+    }
+
+    // To be overridden
+
+    ActivityTransactionItem(@NonNull Parcel in) {
+        mActivityToken = in.readStrongBinder();
+    }
+
+    @CallSuper
+    @Override
+    public void writeToParcel(@NonNull Parcel dest, int flags) {
+        dest.writeStrongBinder(mActivityToken);
+    }
+
+    @CallSuper
+    @Override
+    public void recycle() {
+        mActivityToken = null;
+    }
+
+    // Subclass must override and call super.equals to compare the mActivityToken.
+    @SuppressWarnings("EqualsGetClass")
+    @CallSuper
+    @Override
+    public boolean equals(@Nullable Object o) {
+        if (this == o) {
+            return true;
+        }
+        if (o == null || getClass() != o.getClass()) {
+            return false;
+        }
+        final ActivityTransactionItem other = (ActivityTransactionItem) o;
+        return Objects.equals(mActivityToken, other.mActivityToken);
+    }
+
+    @CallSuper
+    @Override
+    public int hashCode() {
+        return Objects.hashCode(mActivityToken);
+    }
+
+    @CallSuper
+    @Override
+    public String toString() {
+        return "mActivityToken=" + mActivityToken;
+    }
 }
diff --git a/core/java/android/app/servertransaction/ClientTransactionItem.java b/core/java/android/app/servertransaction/ClientTransactionItem.java
index fe75d89..30fc104 100644
--- a/core/java/android/app/servertransaction/ClientTransactionItem.java
+++ b/core/java/android/app/servertransaction/ClientTransactionItem.java
@@ -19,6 +19,8 @@
 import static android.app.servertransaction.ActivityLifecycleItem.LifecycleState;
 import static android.app.servertransaction.ActivityLifecycleItem.UNDEFINED;
 
+import static com.android.internal.annotations.VisibleForTesting.Visibility.PACKAGE;
+
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.app.ClientTransactionHandler;
@@ -26,13 +28,15 @@
 import android.os.IBinder;
 import android.os.Parcelable;
 
+import com.android.internal.annotations.VisibleForTesting;
+
 /**
  * A callback message to a client that can be scheduled and executed.
  * Examples of these might be activity configuration change, multi-window mode change, activity
  * result delivery etc.
  *
  * @see ClientTransaction
- * @see com.android.server.am.ClientLifecycleManager
+ * @see com.android.server.wm.ClientLifecycleManager
  * @hide
  */
 public abstract class ClientTransactionItem implements BaseClientRequest, Parcelable {
@@ -57,6 +61,16 @@
         return null;
     }
 
+    /**
+     * Returns the activity token if this transaction item is activity-targeting. Otherwise,
+     * returns {@code null}.
+     */
+    @VisibleForTesting(visibility = PACKAGE)
+    @Nullable
+    public IBinder getActivityToken() {
+        return null;
+    }
+
     // Parcelable
 
     @Override
diff --git a/core/java/android/app/servertransaction/DestroyActivityItem.java b/core/java/android/app/servertransaction/DestroyActivityItem.java
index a074286..a327a99 100644
--- a/core/java/android/app/servertransaction/DestroyActivityItem.java
+++ b/core/java/android/app/servertransaction/DestroyActivityItem.java
@@ -36,13 +36,13 @@
     private int mConfigChanges;
 
     @Override
-    public void preExecute(ClientTransactionHandler client, IBinder token) {
-        client.getActivitiesToBeDestroyed().put(token, this);
+    public void preExecute(@NonNull ClientTransactionHandler client, @NonNull IBinder token) {
+        client.getActivitiesToBeDestroyed().put(getActivityToken(), this);
     }
 
     @Override
-    public void execute(ClientTransactionHandler client, ActivityClientRecord r,
-            PendingTransactionActions pendingActions) {
+    public void execute(@NonNull ClientTransactionHandler client, @NonNull ActivityClientRecord r,
+            @NonNull PendingTransactionActions pendingActions) {
         Trace.traceBegin(TRACE_TAG_ACTIVITY_MANAGER, "activityDestroy");
         client.handleDestroyActivity(r, mFinished, mConfigChanges,
                 false /* getNonConfigInstance */, "DestroyActivityItem");
@@ -54,17 +54,19 @@
         return ON_DESTROY;
     }
 
-
     // ObjectPoolItem implementation
 
     private DestroyActivityItem() {}
 
     /** Obtain an instance initialized with provided params. */
-    public static DestroyActivityItem obtain(boolean finished, int configChanges) {
+    @NonNull
+    public static DestroyActivityItem obtain(@NonNull IBinder activityToken, boolean finished,
+            int configChanges) {
         DestroyActivityItem instance = ObjectPool.obtain(DestroyActivityItem.class);
         if (instance == null) {
             instance = new DestroyActivityItem();
         }
+        instance.setActivityToken(activityToken);
         instance.mFinished = finished;
         instance.mConfigChanges = configChanges;
 
@@ -79,25 +81,25 @@
         ObjectPool.recycle(this);
     }
 
-
     // Parcelable implementation
 
     /** Write to Parcel. */
     @Override
-    public void writeToParcel(Parcel dest, int flags) {
+    public void writeToParcel(@NonNull Parcel dest, int flags) {
+        super.writeToParcel(dest, flags);
         dest.writeBoolean(mFinished);
         dest.writeInt(mConfigChanges);
     }
 
     /** Read from Parcel. */
-    private DestroyActivityItem(Parcel in) {
+    private DestroyActivityItem(@NonNull Parcel in) {
+        super(in);
         mFinished = in.readBoolean();
         mConfigChanges = in.readInt();
     }
 
-    public static final @NonNull Creator<DestroyActivityItem> CREATOR =
-            new Creator<DestroyActivityItem>() {
-        public DestroyActivityItem createFromParcel(Parcel in) {
+    public static final @NonNull Creator<DestroyActivityItem> CREATOR = new Creator<>() {
+        public DestroyActivityItem createFromParcel(@NonNull Parcel in) {
             return new DestroyActivityItem(in);
         }
 
@@ -111,7 +113,7 @@
         if (this == o) {
             return true;
         }
-        if (o == null || getClass() != o.getClass()) {
+        if (!super.equals(o)) {
             return false;
         }
         final DestroyActivityItem other = (DestroyActivityItem) o;
@@ -121,6 +123,7 @@
     @Override
     public int hashCode() {
         int result = 17;
+        result = 31 * result + super.hashCode();
         result = 31 * result + (mFinished ? 1 : 0);
         result = 31 * result + mConfigChanges;
         return result;
@@ -128,7 +131,8 @@
 
     @Override
     public String toString() {
-        return "DestroyActivityItem{finished=" + mFinished + ",mConfigChanges="
-                + mConfigChanges + "}";
+        return "DestroyActivityItem{" + super.toString()
+                + ",finished=" + mFinished
+                + ",mConfigChanges=" + mConfigChanges + "}";
     }
 }
diff --git a/core/java/android/app/servertransaction/EnterPipRequestedItem.java b/core/java/android/app/servertransaction/EnterPipRequestedItem.java
index 7dcae65..743653f 100644
--- a/core/java/android/app/servertransaction/EnterPipRequestedItem.java
+++ b/core/java/android/app/servertransaction/EnterPipRequestedItem.java
@@ -16,9 +16,10 @@
 
 package android.app.servertransaction;
 
-import android.annotation.Nullable;
+import android.annotation.NonNull;
 import android.app.ActivityThread.ActivityClientRecord;
 import android.app.ClientTransactionHandler;
+import android.os.IBinder;
 import android.os.Parcel;
 
 /**
@@ -28,8 +29,8 @@
 public final class EnterPipRequestedItem extends ActivityTransactionItem {
 
     @Override
-    public void execute(ClientTransactionHandler client, ActivityClientRecord r,
-            PendingTransactionActions pendingActions) {
+    public void execute(@NonNull ClientTransactionHandler client, @NonNull ActivityClientRecord r,
+            @NonNull PendingTransactionActions pendingActions) {
         client.handlePictureInPictureRequested(r);
     }
 
@@ -38,28 +39,32 @@
     private EnterPipRequestedItem() {}
 
     /** Obtain an instance initialized with provided params. */
-    public static EnterPipRequestedItem obtain() {
+    @NonNull
+    public static EnterPipRequestedItem obtain(@NonNull IBinder activityToken) {
         EnterPipRequestedItem instance = ObjectPool.obtain(EnterPipRequestedItem.class);
         if (instance == null) {
             instance = new EnterPipRequestedItem();
         }
+        instance.setActivityToken(activityToken);
         return instance;
     }
 
     @Override
     public void recycle() {
+        super.recycle();
         ObjectPool.recycle(this);
     }
 
     // Parcelable implementation
 
-    @Override
-    public void writeToParcel(Parcel dest, int flags) { }
+    private EnterPipRequestedItem(@NonNull Parcel in) {
+        super(in);
+    }
 
-    public static final @android.annotation.NonNull Creator<EnterPipRequestedItem> CREATOR =
-            new Creator<EnterPipRequestedItem>() {
-                public EnterPipRequestedItem createFromParcel(Parcel in) {
-                    return new EnterPipRequestedItem();
+    public static final @NonNull Creator<EnterPipRequestedItem> CREATOR =
+            new Creator<>() {
+                public EnterPipRequestedItem createFromParcel(@NonNull Parcel in) {
+                    return new EnterPipRequestedItem(in);
                 }
 
                 public EnterPipRequestedItem[] newArray(int size) {
@@ -68,12 +73,7 @@
             };
 
     @Override
-    public boolean equals(@Nullable Object o) {
-        return this == o;
-    }
-
-    @Override
     public String toString() {
-        return "EnterPipRequestedItem{}";
+        return "EnterPipRequestedItem{" + super.toString() + "}";
     }
 }
diff --git a/core/java/android/app/servertransaction/LaunchActivityItem.java b/core/java/android/app/servertransaction/LaunchActivityItem.java
index 5833f1b..9b37a35 100644
--- a/core/java/android/app/servertransaction/LaunchActivityItem.java
+++ b/core/java/android/app/servertransaction/LaunchActivityItem.java
@@ -18,6 +18,8 @@
 
 import static android.os.Trace.TRACE_TAG_ACTIVITY_MANAGER;
 
+import static com.android.internal.annotations.VisibleForTesting.Visibility.PACKAGE;
+
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.app.ActivityClient;
@@ -39,6 +41,7 @@
 import android.os.PersistableBundle;
 import android.os.Trace;
 
+import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.app.IVoiceInteractor;
 import com.android.internal.content.ReferrerIntent;
 
@@ -51,6 +54,7 @@
  */
 public class LaunchActivityItem extends ClientTransactionItem {
 
+    private IBinder mActivityToken;
     @UnsupportedAppUsage
     private Intent mIntent;
     private int mIdent;
@@ -80,7 +84,7 @@
     private IActivityClientController mActivityClientController;
 
     @Override
-    public void preExecute(ClientTransactionHandler client, IBinder token) {
+    public void preExecute(@NonNull ClientTransactionHandler client, @NonNull IBinder token) {
         client.countLaunchingActivities(1);
         client.updateProcessState(mProcState, false);
         CompatibilityInfo.applyOverrideScaleIfNeeded(mCurConfig);
@@ -92,10 +96,10 @@
     }
 
     @Override
-    public void execute(ClientTransactionHandler client, IBinder token,
-            PendingTransactionActions pendingActions) {
+    public void execute(@NonNull ClientTransactionHandler client, @NonNull IBinder token,
+            @NonNull PendingTransactionActions pendingActions) {
         Trace.traceBegin(TRACE_TAG_ACTIVITY_MANAGER, "activityStart");
-        ActivityClientRecord r = new ActivityClientRecord(token, mIntent, mIdent, mInfo,
+        ActivityClientRecord r = new ActivityClientRecord(mActivityToken, mIntent, mIdent, mInfo,
                 mOverrideConfig, mReferrer, mVoiceInteractor, mState, mPersistentState,
                 mPendingResults, mPendingNewIntents, mActivityOptions, mIsForward, mProfilerInfo,
                 client, mAssistToken, mShareableActivityToken, mLaunchedFromBubble,
@@ -105,31 +109,34 @@
     }
 
     @Override
-    public void postExecute(ClientTransactionHandler client, IBinder token,
-            PendingTransactionActions pendingActions) {
+    public void postExecute(@NonNull ClientTransactionHandler client, @NonNull IBinder token,
+            @NonNull PendingTransactionActions pendingActions) {
         client.countLaunchingActivities(-1);
     }
 
-
     // ObjectPoolItem implementation
 
     private LaunchActivityItem() {}
 
     /** Obtain an instance initialized with provided params. */
-    public static LaunchActivityItem obtain(Intent intent, int ident, ActivityInfo info,
-            Configuration curConfig, Configuration overrideConfig, int deviceId,
-            String referrer, IVoiceInteractor voiceInteractor, int procState, Bundle state,
-            PersistableBundle persistentState, List<ResultInfo> pendingResults,
-            List<ReferrerIntent> pendingNewIntents, ActivityOptions activityOptions,
-            boolean isForward, ProfilerInfo profilerInfo, IBinder assistToken,
-            IActivityClientController activityClientController, IBinder shareableActivityToken,
-            boolean launchedFromBubble, IBinder taskFragmentToken) {
+    @NonNull
+    public static LaunchActivityItem obtain(@NonNull IBinder activityToken, @NonNull Intent intent,
+            int ident, @NonNull ActivityInfo info, @NonNull Configuration curConfig,
+            @NonNull Configuration overrideConfig, int deviceId, @Nullable String referrer,
+            @Nullable IVoiceInteractor voiceInteractor, int procState, @Nullable Bundle state,
+            @Nullable PersistableBundle persistentState, @Nullable List<ResultInfo> pendingResults,
+            @Nullable List<ReferrerIntent> pendingNewIntents,
+            @Nullable ActivityOptions activityOptions,
+            boolean isForward, @Nullable ProfilerInfo profilerInfo, @NonNull IBinder assistToken,
+            @Nullable IActivityClientController activityClientController,
+            @NonNull IBinder shareableActivityToken, boolean launchedFromBubble,
+            @Nullable IBinder taskFragmentToken) {
         LaunchActivityItem instance = ObjectPool.obtain(LaunchActivityItem.class);
         if (instance == null) {
             instance = new LaunchActivityItem();
         }
-        setValues(instance, intent, ident, info, curConfig, overrideConfig, deviceId, referrer,
-                voiceInteractor, procState, state, persistentState, pendingResults,
+        setValues(instance, activityToken, intent, ident, info, curConfig, overrideConfig, deviceId,
+                referrer, voiceInteractor, procState, state, persistentState, pendingResults,
                 pendingNewIntents, activityOptions, isForward, profilerInfo, assistToken,
                 activityClientController, shareableActivityToken,
                 launchedFromBubble, taskFragmentToken);
@@ -137,19 +144,26 @@
         return instance;
     }
 
+    @VisibleForTesting(visibility = PACKAGE)
+    @NonNull
+    @Override
+    public IBinder getActivityToken() {
+        return mActivityToken;
+    }
+
     @Override
     public void recycle() {
-        setValues(this, null, 0, null, null, null, 0, null, null, 0, null, null, null, null,
+        setValues(this, null, null, 0, null, null, null, 0, null, null, 0, null, null, null, null,
                 null, false, null, null, null, null, false, null);
         ObjectPool.recycle(this);
     }
 
-
     // Parcelable implementation
 
     /** Write from Parcel. */
     @Override
-    public void writeToParcel(Parcel dest, int flags) {
+    public void writeToParcel(@NonNull Parcel dest, int flags) {
+        dest.writeStrongBinder(mActivityToken);
         dest.writeTypedObject(mIntent, flags);
         dest.writeInt(mIdent);
         dest.writeTypedObject(mInfo, flags);
@@ -174,8 +188,8 @@
     }
 
     /** Read from Parcel. */
-    private LaunchActivityItem(Parcel in) {
-        setValues(this, in.readTypedObject(Intent.CREATOR), in.readInt(),
+    private LaunchActivityItem(@NonNull Parcel in) {
+        setValues(this, in.readStrongBinder(), in.readTypedObject(Intent.CREATOR), in.readInt(),
                 in.readTypedObject(ActivityInfo.CREATOR), in.readTypedObject(Configuration.CREATOR),
                 in.readTypedObject(Configuration.CREATOR), in.readInt(), in.readString(),
                 IVoiceInteractor.Stub.asInterface(in.readStrongBinder()), in.readInt(),
@@ -192,9 +206,8 @@
                 in.readStrongBinder());
     }
 
-    public static final @NonNull Creator<LaunchActivityItem> CREATOR =
-            new Creator<LaunchActivityItem>() {
-        public LaunchActivityItem createFromParcel(Parcel in) {
+    public static final @NonNull Creator<LaunchActivityItem> CREATOR = new Creator<>() {
+        public LaunchActivityItem createFromParcel(@NonNull Parcel in) {
             return new LaunchActivityItem(in);
         }
 
@@ -214,7 +227,8 @@
         final LaunchActivityItem other = (LaunchActivityItem) o;
         final boolean intentsEqual = (mIntent == null && other.mIntent == null)
                 || (mIntent != null && mIntent.filterEquals(other.mIntent));
-        return intentsEqual && mIdent == other.mIdent
+        return intentsEqual
+                && Objects.equals(mActivityToken, other.mActivityToken) && mIdent == other.mIdent
                 && activityInfoEqual(other.mInfo) && Objects.equals(mCurConfig, other.mCurConfig)
                 && Objects.equals(mOverrideConfig, other.mOverrideConfig)
                 && mDeviceId == other.mDeviceId
@@ -234,6 +248,7 @@
     @Override
     public int hashCode() {
         int result = 17;
+        result = 31 * result + Objects.hashCode(mActivityToken);
         result = 31 * result + mIntent.filterHashCode();
         result = 31 * result + mIdent;
         result = 31 * result + Objects.hashCode(mCurConfig);
@@ -254,7 +269,7 @@
         return result;
     }
 
-    private boolean activityInfoEqual(ActivityInfo other) {
+    private boolean activityInfoEqual(@Nullable ActivityInfo other) {
         if (mInfo == null) {
             return other == null;
         }
@@ -270,36 +285,51 @@
      * unparceling if a customized class loader is not set to the bundle. So the hash code is
      * simply determined by the bundle is empty or not.
      */
-    private static int getRoughBundleHashCode(BaseBundle bundle) {
+    private static int getRoughBundleHashCode(@Nullable BaseBundle bundle) {
         return (bundle == null || bundle.isDefinitelyEmpty()) ? 0 : 1;
     }
 
     /** Compares the bundles without unparceling them (avoid BadParcelableException). */
-    private static boolean areBundlesEqualRoughly(BaseBundle a, BaseBundle b) {
+    private static boolean areBundlesEqualRoughly(@Nullable BaseBundle a, @Nullable BaseBundle b) {
         return getRoughBundleHashCode(a) == getRoughBundleHashCode(b);
     }
 
     @Override
     public String toString() {
-        return "LaunchActivityItem{intent=" + mIntent + ",ident=" + mIdent + ",info=" + mInfo
-                + ",curConfig=" + mCurConfig + ",overrideConfig=" + mOverrideConfig
-                + ",deviceId=" + mDeviceId + ",referrer=" + mReferrer + ",procState=" + mProcState
-                + ",state=" + mState + ",persistentState=" + mPersistentState
-                + ",pendingResults=" + mPendingResults + ",pendingNewIntents=" + mPendingNewIntents
-                + ",options=" + mActivityOptions + ",profilerInfo=" + mProfilerInfo
-                + ",assistToken=" + mAssistToken + ",shareableActivityToken="
-                + mShareableActivityToken + "}";
+        return "LaunchActivityItem{activityToken=" + mActivityToken
+                + ",intent=" + mIntent
+                + ",ident=" + mIdent
+                + ",info=" + mInfo
+                + ",curConfig=" + mCurConfig
+                + ",overrideConfig=" + mOverrideConfig
+                + ",deviceId=" + mDeviceId
+                + ",referrer=" + mReferrer
+                + ",procState=" + mProcState
+                + ",state=" + mState
+                + ",persistentState=" + mPersistentState
+                + ",pendingResults=" + mPendingResults
+                + ",pendingNewIntents=" + mPendingNewIntents
+                + ",options=" + mActivityOptions
+                + ",profilerInfo=" + mProfilerInfo
+                + ",assistToken=" + mAssistToken
+                + ",shareableActivityToken=" + mShareableActivityToken + "}";
     }
 
     // Using the same method to set and clear values to make sure we don't forget anything
-    private static void setValues(LaunchActivityItem instance, Intent intent, int ident,
-            ActivityInfo info, Configuration curConfig, Configuration overrideConfig, int deviceId,
-            String referrer, IVoiceInteractor voiceInteractor,
-            int procState, Bundle state, PersistableBundle persistentState,
-            List<ResultInfo> pendingResults, List<ReferrerIntent> pendingNewIntents,
-            ActivityOptions activityOptions, boolean isForward, ProfilerInfo profilerInfo,
-            IBinder assistToken, IActivityClientController activityClientController,
-            IBinder shareableActivityToken, boolean launchedFromBubble, IBinder taskFragmentToken) {
+    private static void setValues(@Nullable LaunchActivityItem instance,
+            @Nullable IBinder activityToken, @Nullable Intent intent, int ident,
+            @Nullable ActivityInfo info, @Nullable Configuration curConfig,
+            @Nullable Configuration overrideConfig, int deviceId,
+            @Nullable String referrer, @Nullable IVoiceInteractor voiceInteractor,
+            int procState, @Nullable Bundle state, @Nullable PersistableBundle persistentState,
+            @Nullable List<ResultInfo> pendingResults,
+            @Nullable List<ReferrerIntent> pendingNewIntents,
+            @Nullable ActivityOptions activityOptions, boolean isForward,
+            @Nullable ProfilerInfo profilerInfo, @Nullable IBinder assistToken,
+            @Nullable IActivityClientController activityClientController,
+            @Nullable IBinder shareableActivityToken, boolean launchedFromBubble,
+            @Nullable IBinder taskFragmentToken) {
+        instance.mActivityToken = activityToken;
         instance.mIntent = intent;
         instance.mIdent = ident;
         instance.mInfo = info;
diff --git a/core/java/android/app/servertransaction/MoveToDisplayItem.java b/core/java/android/app/servertransaction/MoveToDisplayItem.java
index f13bd74..fb57bed 100644
--- a/core/java/android/app/servertransaction/MoveToDisplayItem.java
+++ b/core/java/android/app/servertransaction/MoveToDisplayItem.java
@@ -40,37 +40,34 @@
     private Configuration mConfiguration;
 
     @Override
-    public void preExecute(ClientTransactionHandler client, IBinder token) {
+    public void preExecute(@NonNull ClientTransactionHandler client, @NonNull IBinder token) {
         CompatibilityInfo.applyOverrideScaleIfNeeded(mConfiguration);
         // Notify the client of an upcoming change in the token configuration. This ensures that
         // batches of config change items only process the newest configuration.
-        client.updatePendingActivityConfiguration(token, mConfiguration);
+        client.updatePendingActivityConfiguration(getActivityToken(), mConfiguration);
     }
 
     @Override
-    public void execute(ClientTransactionHandler client, ActivityClientRecord r,
-            PendingTransactionActions pendingActions) {
+    public void execute(@NonNull ClientTransactionHandler client, @NonNull ActivityClientRecord r,
+            @NonNull PendingTransactionActions pendingActions) {
         Trace.traceBegin(TRACE_TAG_ACTIVITY_MANAGER, "activityMovedToDisplay");
         client.handleActivityConfigurationChanged(r, mConfiguration, mTargetDisplayId);
         Trace.traceEnd(TRACE_TAG_ACTIVITY_MANAGER);
     }
 
-
     // ObjectPoolItem implementation
 
     private MoveToDisplayItem() {}
 
     /** Obtain an instance initialized with provided params. */
-    public static MoveToDisplayItem obtain(int targetDisplayId,
+    @NonNull
+    public static MoveToDisplayItem obtain(@NonNull IBinder activityToken, int targetDisplayId,
             @NonNull Configuration configuration) {
-        if (configuration == null) {
-            throw new IllegalArgumentException("Configuration must not be null");
-        }
-
         MoveToDisplayItem instance = ObjectPool.obtain(MoveToDisplayItem.class);
         if (instance == null) {
             instance = new MoveToDisplayItem();
         }
+        instance.setActivityToken(activityToken);
         instance.mTargetDisplayId = targetDisplayId;
         instance.mConfiguration = configuration;
 
@@ -79,30 +76,31 @@
 
     @Override
     public void recycle() {
+        super.recycle();
         mTargetDisplayId = 0;
         mConfiguration = Configuration.EMPTY;
         ObjectPool.recycle(this);
     }
 
-
     // Parcelable implementation
 
     /** Write to Parcel. */
     @Override
-    public void writeToParcel(Parcel dest, int flags) {
+    public void writeToParcel(@NonNull Parcel dest, int flags) {
+        super.writeToParcel(dest, flags);
         dest.writeInt(mTargetDisplayId);
         dest.writeTypedObject(mConfiguration, flags);
     }
 
     /** Read from Parcel. */
-    private MoveToDisplayItem(Parcel in) {
+    private MoveToDisplayItem(@NonNull Parcel in) {
+        super(in);
         mTargetDisplayId = in.readInt();
         mConfiguration = in.readTypedObject(Configuration.CREATOR);
     }
 
-    public static final @NonNull Creator<MoveToDisplayItem> CREATOR =
-            new Creator<MoveToDisplayItem>() {
-        public MoveToDisplayItem createFromParcel(Parcel in) {
+    public static final @NonNull Creator<MoveToDisplayItem> CREATOR = new Creator<>() {
+        public MoveToDisplayItem createFromParcel(@NonNull Parcel in) {
             return new MoveToDisplayItem(in);
         }
 
@@ -116,7 +114,7 @@
         if (this == o) {
             return true;
         }
-        if (o == null || getClass() != o.getClass()) {
+        if (!super.equals(o)) {
             return false;
         }
         final MoveToDisplayItem other = (MoveToDisplayItem) o;
@@ -127,6 +125,7 @@
     @Override
     public int hashCode() {
         int result = 17;
+        result = 31 * result + super.hashCode();
         result = 31 * result + mTargetDisplayId;
         result = 31 * result + mConfiguration.hashCode();
         return result;
@@ -134,7 +133,8 @@
 
     @Override
     public String toString() {
-        return "MoveToDisplayItem{targetDisplayId=" + mTargetDisplayId
+        return "MoveToDisplayItem{" + super.toString()
+                + ",targetDisplayId=" + mTargetDisplayId
                 + ",configuration=" + mConfiguration + "}";
     }
 }
diff --git a/core/java/android/app/servertransaction/NewIntentItem.java b/core/java/android/app/servertransaction/NewIntentItem.java
index 723fa01..8e995aa 100644
--- a/core/java/android/app/servertransaction/NewIntentItem.java
+++ b/core/java/android/app/servertransaction/NewIntentItem.java
@@ -25,6 +25,7 @@
 import android.app.ClientTransactionHandler;
 import android.compat.annotation.UnsupportedAppUsage;
 import android.os.Build;
+import android.os.IBinder;
 import android.os.Parcel;
 import android.os.Parcelable;
 import android.os.Trace;
@@ -50,24 +51,26 @@
     }
 
     @Override
-    public void execute(ClientTransactionHandler client, ActivityClientRecord r,
-            PendingTransactionActions pendingActions) {
+    public void execute(@NonNull ClientTransactionHandler client, @NonNull ActivityClientRecord r,
+            @NonNull PendingTransactionActions pendingActions) {
         Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "activityNewIntent");
         client.handleNewIntent(r, mIntents);
         Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
     }
 
-
     // ObjectPoolItem implementation
 
     private NewIntentItem() {}
 
     /** Obtain an instance initialized with provided params. */
-    public static NewIntentItem obtain(List<ReferrerIntent> intents, boolean resume) {
+    @NonNull
+    public static NewIntentItem obtain(@NonNull IBinder activityToken,
+            @NonNull List<ReferrerIntent> intents, boolean resume) {
         NewIntentItem instance = ObjectPool.obtain(NewIntentItem.class);
         if (instance == null) {
             instance = new NewIntentItem();
         }
+        instance.setActivityToken(activityToken);
         instance.mIntents = intents;
         instance.mResume = resume;
 
@@ -76,44 +79,46 @@
 
     @Override
     public void recycle() {
+        super.recycle();
         mIntents = null;
         mResume = false;
         ObjectPool.recycle(this);
     }
 
-
     // Parcelable implementation
 
     /** Write to Parcel. */
     @Override
-    public void writeToParcel(Parcel dest, int flags) {
+    public void writeToParcel(@NonNull Parcel dest, int flags) {
+        super.writeToParcel(dest, flags);
         dest.writeBoolean(mResume);
         dest.writeTypedList(mIntents, flags);
     }
 
     /** Read from Parcel. */
-    private NewIntentItem(Parcel in) {
+    private NewIntentItem(@NonNull Parcel in) {
+        super(in);
         mResume = in.readBoolean();
         mIntents = in.createTypedArrayList(ReferrerIntent.CREATOR);
     }
 
     public static final @NonNull Parcelable.Creator<NewIntentItem> CREATOR =
-            new Parcelable.Creator<NewIntentItem>() {
-        public NewIntentItem createFromParcel(Parcel in) {
-            return new NewIntentItem(in);
-        }
+            new Parcelable.Creator<>() {
+                public NewIntentItem createFromParcel(@NonNull Parcel in) {
+                    return new NewIntentItem(in);
+                }
 
-        public NewIntentItem[] newArray(int size) {
-            return new NewIntentItem[size];
-        }
-    };
+                public NewIntentItem[] newArray(int size) {
+                    return new NewIntentItem[size];
+                }
+            };
 
     @Override
     public boolean equals(@Nullable Object o) {
         if (this == o) {
             return true;
         }
-        if (o == null || getClass() != o.getClass()) {
+        if (!super.equals(o)) {
             return false;
         }
         final NewIntentItem other = (NewIntentItem) o;
@@ -123,6 +128,7 @@
     @Override
     public int hashCode() {
         int result = 17;
+        result = 31 * result + super.hashCode();
         result = 31 * result + (mResume ? 1 : 0);
         result = 31 * result + mIntents.hashCode();
         return result;
@@ -130,6 +136,8 @@
 
     @Override
     public String toString() {
-        return "NewIntentItem{intents=" + mIntents + ",resume=" + mResume + "}";
+        return "NewIntentItem{" + super.toString()
+                + ",intents=" + mIntents
+                + ",resume=" + mResume + "}";
     }
 }
diff --git a/core/java/android/app/servertransaction/PauseActivityItem.java b/core/java/android/app/servertransaction/PauseActivityItem.java
index 965e761..a8e6772 100644
--- a/core/java/android/app/servertransaction/PauseActivityItem.java
+++ b/core/java/android/app/servertransaction/PauseActivityItem.java
@@ -42,8 +42,8 @@
     private boolean mAutoEnteringPip;
 
     @Override
-    public void execute(ClientTransactionHandler client, ActivityClientRecord r,
-            PendingTransactionActions pendingActions) {
+    public void execute(@NonNull ClientTransactionHandler client, @NonNull ActivityClientRecord r,
+            @NonNull PendingTransactionActions pendingActions) {
         Trace.traceBegin(TRACE_TAG_ACTIVITY_MANAGER, "activityPause");
         client.handlePauseActivity(r, mFinished, mUserLeaving, mConfigChanges, mAutoEnteringPip,
                 pendingActions, "PAUSE_ACTIVITY_ITEM");
@@ -56,27 +56,28 @@
     }
 
     @Override
-    public void postExecute(ClientTransactionHandler client, IBinder token,
-            PendingTransactionActions pendingActions) {
+    public void postExecute(@NonNull ClientTransactionHandler client, @NonNull IBinder token,
+            @NonNull PendingTransactionActions pendingActions) {
         if (mDontReport) {
             return;
         }
         // TODO(lifecycler): Use interface callback instead of actual implementation.
-        ActivityClient.getInstance().activityPaused(token);
+        ActivityClient.getInstance().activityPaused(getActivityToken());
     }
 
-
     // ObjectPoolItem implementation
 
     private PauseActivityItem() {}
 
     /** Obtain an instance initialized with provided params. */
-    public static PauseActivityItem obtain(boolean finished, boolean userLeaving, int configChanges,
-            boolean dontReport, boolean autoEnteringPip) {
+    @NonNull
+    public static PauseActivityItem obtain(@NonNull IBinder activityToken, boolean finished,
+            boolean userLeaving, int configChanges, boolean dontReport, boolean autoEnteringPip) {
         PauseActivityItem instance = ObjectPool.obtain(PauseActivityItem.class);
         if (instance == null) {
             instance = new PauseActivityItem();
         }
+        instance.setActivityToken(activityToken);
         instance.mFinished = finished;
         instance.mUserLeaving = userLeaving;
         instance.mConfigChanges = configChanges;
@@ -87,18 +88,10 @@
     }
 
     /** Obtain an instance initialized with default params. */
-    public static PauseActivityItem obtain() {
-        PauseActivityItem instance = ObjectPool.obtain(PauseActivityItem.class);
-        if (instance == null) {
-            instance = new PauseActivityItem();
-        }
-        instance.mFinished = false;
-        instance.mUserLeaving = false;
-        instance.mConfigChanges = 0;
-        instance.mDontReport = true;
-        instance.mAutoEnteringPip = false;
-
-        return instance;
+    @NonNull
+    public static PauseActivityItem obtain(@NonNull IBinder activityToken) {
+        return obtain(activityToken, false /* finished */, false /* userLeaving */,
+                0 /* configChanges */, true /* dontReport */, false /* autoEnteringPip*/);
     }
 
     @Override
@@ -116,7 +109,8 @@
 
     /** Write to Parcel. */
     @Override
-    public void writeToParcel(Parcel dest, int flags) {
+    public void writeToParcel(@NonNull Parcel dest, int flags) {
+        super.writeToParcel(dest, flags);
         dest.writeBoolean(mFinished);
         dest.writeBoolean(mUserLeaving);
         dest.writeInt(mConfigChanges);
@@ -125,7 +119,8 @@
     }
 
     /** Read from Parcel. */
-    private PauseActivityItem(Parcel in) {
+    private PauseActivityItem(@NonNull Parcel in) {
+        super(in);
         mFinished = in.readBoolean();
         mUserLeaving = in.readBoolean();
         mConfigChanges = in.readInt();
@@ -133,9 +128,8 @@
         mAutoEnteringPip = in.readBoolean();
     }
 
-    public static final @NonNull Creator<PauseActivityItem> CREATOR =
-            new Creator<PauseActivityItem>() {
-        public PauseActivityItem createFromParcel(Parcel in) {
+    public static final @NonNull Creator<PauseActivityItem> CREATOR = new Creator<>() {
+        public PauseActivityItem createFromParcel(@NonNull Parcel in) {
             return new PauseActivityItem(in);
         }
 
@@ -149,7 +143,7 @@
         if (this == o) {
             return true;
         }
-        if (o == null || getClass() != o.getClass()) {
+        if (!super.equals(o)) {
             return false;
         }
         final PauseActivityItem other = (PauseActivityItem) o;
@@ -161,6 +155,7 @@
     @Override
     public int hashCode() {
         int result = 17;
+        result = 31 * result + super.hashCode();
         result = 31 * result + (mFinished ? 1 : 0);
         result = 31 * result + (mUserLeaving ? 1 : 0);
         result = 31 * result + mConfigChanges;
@@ -171,8 +166,11 @@
 
     @Override
     public String toString() {
-        return "PauseActivityItem{finished=" + mFinished + ",userLeaving=" + mUserLeaving
-                + ",configChanges=" + mConfigChanges + ",dontReport=" + mDontReport
+        return "PauseActivityItem{" + super.toString()
+                + ",finished=" + mFinished
+                + ",userLeaving=" + mUserLeaving
+                + ",configChanges=" + mConfigChanges
+                + ",dontReport=" + mDontReport
                 + ",autoEnteringPip=" + mAutoEnteringPip + "}";
     }
 }
diff --git a/core/java/android/app/servertransaction/PipStateTransactionItem.java b/core/java/android/app/servertransaction/PipStateTransactionItem.java
index 167f5a4..30289ef 100644
--- a/core/java/android/app/servertransaction/PipStateTransactionItem.java
+++ b/core/java/android/app/servertransaction/PipStateTransactionItem.java
@@ -16,12 +16,16 @@
 
 package android.app.servertransaction;
 
+import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.app.ActivityThread.ActivityClientRecord;
 import android.app.ClientTransactionHandler;
 import android.app.PictureInPictureUiState;
+import android.os.IBinder;
 import android.os.Parcel;
 
+import java.util.Objects;
+
 /**
  * Request an activity to enter picture-in-picture mode.
  * @hide
@@ -31,8 +35,8 @@
     private PictureInPictureUiState mPipState;
 
     @Override
-    public void execute(ClientTransactionHandler client, ActivityClientRecord r,
-            PendingTransactionActions pendingActions) {
+    public void execute(@NonNull ClientTransactionHandler client, @NonNull ActivityClientRecord r,
+            @NonNull PendingTransactionActions pendingActions) {
         client.handlePictureInPictureStateChanged(r, mPipState);
     }
 
@@ -41,11 +45,14 @@
     private PipStateTransactionItem() {}
 
     /** Obtain an instance initialized with provided params. */
-    public static PipStateTransactionItem obtain(PictureInPictureUiState pipState) {
+    @NonNull
+    public static PipStateTransactionItem obtain(@NonNull IBinder activityToken,
+            @NonNull PictureInPictureUiState pipState) {
         PipStateTransactionItem instance = ObjectPool.obtain(PipStateTransactionItem.class);
         if (instance == null) {
             instance = new PipStateTransactionItem();
         }
+        instance.setActivityToken(activityToken);
         instance.mPipState = pipState;
 
         return instance;
@@ -53,6 +60,7 @@
 
     @Override
     public void recycle() {
+        super.recycle();
         mPipState = null;
         ObjectPool.recycle(this);
     }
@@ -61,33 +69,49 @@
 
     /** Write to Parcel. */
     @Override
-    public void writeToParcel(Parcel dest, int flags) {
+    public void writeToParcel(@NonNull Parcel dest, int flags) {
+        super.writeToParcel(dest, flags);
         mPipState.writeToParcel(dest, flags);
     }
 
     /** Read from Parcel. */
-    private PipStateTransactionItem(Parcel in) {
+    private PipStateTransactionItem(@NonNull Parcel in) {
+        super(in);
         mPipState = PictureInPictureUiState.CREATOR.createFromParcel(in);
     }
 
-    public static final @android.annotation.NonNull Creator<PipStateTransactionItem> CREATOR =
-            new Creator<PipStateTransactionItem>() {
-                public PipStateTransactionItem createFromParcel(Parcel in) {
-                    return new PipStateTransactionItem(in);
-                }
+    public static final @NonNull Creator<PipStateTransactionItem> CREATOR = new Creator<>() {
+        public PipStateTransactionItem createFromParcel(@NonNull Parcel in) {
+            return new PipStateTransactionItem(in);
+        }
 
-                public PipStateTransactionItem[] newArray(int size) {
-                    return new PipStateTransactionItem[size];
-                }
-            };
+        public PipStateTransactionItem[] newArray(int size) {
+            return new PipStateTransactionItem[size];
+        }
+    };
 
     @Override
     public boolean equals(@Nullable Object o) {
-        return this == o;
+        if (this == o) {
+            return true;
+        }
+        if (!super.equals(o)) {
+            return false;
+        }
+        final PipStateTransactionItem other = (PipStateTransactionItem) o;
+        return Objects.equals(mPipState, other.mPipState);
+    }
+
+    @Override
+    public int hashCode() {
+        int result = 17;
+        result = 31 * result + super.hashCode();
+        result = 31 * result + Objects.hashCode(mPipState);
+        return result;
     }
 
     @Override
     public String toString() {
-        return "PipStateTransactionItem{}";
+        return "PipStateTransactionItem{" + super.toString() + "}";
     }
 }
diff --git a/core/java/android/app/servertransaction/RefreshCallbackItem.java b/core/java/android/app/servertransaction/RefreshCallbackItem.java
index 74abab2..00128f0 100644
--- a/core/java/android/app/servertransaction/RefreshCallbackItem.java
+++ b/core/java/android/app/servertransaction/RefreshCallbackItem.java
@@ -48,12 +48,12 @@
 
     @Override
     public void execute(@NonNull ClientTransactionHandler client,
-            @NonNull ActivityClientRecord r, PendingTransactionActions pendingActions) {}
+            @NonNull ActivityClientRecord r, @NonNull PendingTransactionActions pendingActions) {}
 
     @Override
-    public void postExecute(ClientTransactionHandler client, IBinder token,
-            PendingTransactionActions pendingActions) {
-        final ActivityClientRecord r = getActivityClientRecord(client, token);
+    public void postExecute(@NonNull ClientTransactionHandler client, @NonNull IBinder token,
+            @NonNull PendingTransactionActions pendingActions) {
+        final ActivityClientRecord r = getActivityClientRecord(client);
         client.reportRefresh(r);
     }
 
@@ -71,6 +71,7 @@
 
     @Override
     public void recycle() {
+        super.recycle();
         ObjectPool.recycle(this);
     }
 
@@ -79,7 +80,9 @@
     * @param postExecutionState indicating whether refresh should happen using the
     *        "stopped -> resumed" cycle or "paused -> resumed" cycle.
     */
-    public static RefreshCallbackItem obtain(@LifecycleState int postExecutionState) {
+    @NonNull
+    public static RefreshCallbackItem obtain(@NonNull IBinder activityToken,
+            @LifecycleState int postExecutionState) {
         if (postExecutionState != ON_STOP && postExecutionState != ON_PAUSE) {
             throw new IllegalArgumentException(
                     "Only ON_STOP or ON_PAUSE are allowed as a post execution state for "
@@ -90,6 +93,7 @@
         if (instance == null) {
             instance = new RefreshCallbackItem();
         }
+        instance.setActivityToken(activityToken);
         instance.mPostExecutionState = postExecutionState;
         return instance;
     }
@@ -99,7 +103,8 @@
     // Parcelable implementation
 
     @Override
-    public void writeToParcel(Parcel dest, int flags) {
+    public void writeToParcel(@NonNull Parcel dest, int flags) {
+        super.writeToParcel(dest, flags);
         dest.writeInt(mPostExecutionState);
     }
 
@@ -108,7 +113,7 @@
         if (this == o) {
             return true;
         }
-        if (o == null || getClass() != o.getClass()) {
+        if (!super.equals(o)) {
             return false;
         }
         final RefreshCallbackItem other = (RefreshCallbackItem) o;
@@ -118,23 +123,25 @@
     @Override
     public int hashCode() {
         int result = 17;
+        result = 31 * result + super.hashCode();
         result = 31 * result + mPostExecutionState;
         return result;
     }
 
     @Override
     public String toString() {
-        return "RefreshCallbackItem{mPostExecutionState=" + mPostExecutionState + "}";
+        return "RefreshCallbackItem{" + super.toString()
+                + ",mPostExecutionState=" + mPostExecutionState + "}";
     }
 
-    private RefreshCallbackItem(Parcel in) {
+    private RefreshCallbackItem(@NonNull Parcel in) {
+        super(in);
         mPostExecutionState = in.readInt();
     }
 
-    public static final @NonNull Creator<RefreshCallbackItem> CREATOR =
-            new Creator<RefreshCallbackItem>() {
+    public static final @NonNull Creator<RefreshCallbackItem> CREATOR = new Creator<>() {
 
-        public RefreshCallbackItem createFromParcel(Parcel in) {
+        public RefreshCallbackItem createFromParcel(@NonNull Parcel in) {
             return new RefreshCallbackItem(in);
         }
 
diff --git a/core/java/android/app/servertransaction/ResumeActivityItem.java b/core/java/android/app/servertransaction/ResumeActivityItem.java
index 222f8ca..b11e73c 100644
--- a/core/java/android/app/servertransaction/ResumeActivityItem.java
+++ b/core/java/android/app/servertransaction/ResumeActivityItem.java
@@ -44,15 +44,15 @@
     private boolean mShouldSendCompatFakeFocus;
 
     @Override
-    public void preExecute(ClientTransactionHandler client, IBinder token) {
+    public void preExecute(@NonNull ClientTransactionHandler client, @NonNull IBinder token) {
         if (mUpdateProcState) {
             client.updateProcessState(mProcState, false);
         }
     }
 
     @Override
-    public void execute(ClientTransactionHandler client, ActivityClientRecord r,
-            PendingTransactionActions pendingActions) {
+    public void execute(@NonNull ClientTransactionHandler client, @NonNull ActivityClientRecord r,
+            @NonNull PendingTransactionActions pendingActions) {
         Trace.traceBegin(TRACE_TAG_ACTIVITY_MANAGER, "activityResume");
         client.handleResumeActivity(r, true /* finalStateRequest */, mIsForward,
                 mShouldSendCompatFakeFocus, "RESUME_ACTIVITY");
@@ -60,10 +60,11 @@
     }
 
     @Override
-    public void postExecute(ClientTransactionHandler client, IBinder token,
-            PendingTransactionActions pendingActions) {
+    public void postExecute(@NonNull ClientTransactionHandler client, IBinder token,
+            @NonNull PendingTransactionActions pendingActions) {
         // TODO(lifecycler): Use interface callback instead of actual implementation.
-        ActivityClient.getInstance().activityResumed(token, client.isHandleSplashScreenExit(token));
+        ActivityClient.getInstance().activityResumed(getActivityToken(),
+                client.isHandleSplashScreenExit(getActivityToken()));
     }
 
     @Override
@@ -71,18 +72,19 @@
         return ON_RESUME;
     }
 
-
     // ObjectPoolItem implementation
 
     private ResumeActivityItem() {}
 
     /** Obtain an instance initialized with provided params. */
-    public static ResumeActivityItem obtain(int procState, boolean isForward,
-            boolean shouldSendCompatFakeFocus) {
+    @NonNull
+    public static ResumeActivityItem obtain(@NonNull IBinder activityToken, int procState,
+            boolean isForward, boolean shouldSendCompatFakeFocus) {
         ResumeActivityItem instance = ObjectPool.obtain(ResumeActivityItem.class);
         if (instance == null) {
             instance = new ResumeActivityItem();
         }
+        instance.setActivityToken(activityToken);
         instance.mProcState = procState;
         instance.mUpdateProcState = true;
         instance.mIsForward = isForward;
@@ -92,11 +94,14 @@
     }
 
     /** Obtain an instance initialized with provided params. */
-    public static ResumeActivityItem obtain(boolean isForward, boolean shouldSendCompatFakeFocus) {
+    @NonNull
+    public static ResumeActivityItem obtain(@NonNull IBinder activityToken, boolean isForward,
+            boolean shouldSendCompatFakeFocus) {
         ResumeActivityItem instance = ObjectPool.obtain(ResumeActivityItem.class);
         if (instance == null) {
             instance = new ResumeActivityItem();
         }
+        instance.setActivityToken(activityToken);
         instance.mProcState = ActivityManager.PROCESS_STATE_UNKNOWN;
         instance.mUpdateProcState = false;
         instance.mIsForward = isForward;
@@ -115,12 +120,12 @@
         ObjectPool.recycle(this);
     }
 
-
     // Parcelable implementation
 
     /** Write to Parcel. */
     @Override
-    public void writeToParcel(Parcel dest, int flags) {
+    public void writeToParcel(@NonNull Parcel dest, int flags) {
+        super.writeToParcel(dest, flags);
         dest.writeInt(mProcState);
         dest.writeBoolean(mUpdateProcState);
         dest.writeBoolean(mIsForward);
@@ -128,15 +133,15 @@
     }
 
     /** Read from Parcel. */
-    private ResumeActivityItem(Parcel in) {
+    private ResumeActivityItem(@NonNull Parcel in) {
+        super(in);
         mProcState = in.readInt();
         mUpdateProcState = in.readBoolean();
         mIsForward = in.readBoolean();
         mShouldSendCompatFakeFocus = in.readBoolean();
     }
 
-    public static final @NonNull Creator<ResumeActivityItem> CREATOR =
-            new Creator<ResumeActivityItem>() {
+    public static final @NonNull Creator<ResumeActivityItem> CREATOR = new Creator<>() {
         public ResumeActivityItem createFromParcel(Parcel in) {
             return new ResumeActivityItem(in);
         }
@@ -151,7 +156,7 @@
         if (this == o) {
             return true;
         }
-        if (o == null || getClass() != o.getClass()) {
+        if (!super.equals(o)) {
             return false;
         }
         final ResumeActivityItem other = (ResumeActivityItem) o;
@@ -163,6 +168,7 @@
     @Override
     public int hashCode() {
         int result = 17;
+        result = 31 * result + super.hashCode();
         result = 31 * result + mProcState;
         result = 31 * result + (mUpdateProcState ? 1 : 0);
         result = 31 * result + (mIsForward ? 1 : 0);
@@ -172,8 +178,10 @@
 
     @Override
     public String toString() {
-        return "ResumeActivityItem{procState=" + mProcState
-                + ",updateProcState=" + mUpdateProcState + ",isForward=" + mIsForward
+        return "ResumeActivityItem{" + super.toString()
+                + ",procState=" + mProcState
+                + ",updateProcState=" + mUpdateProcState
+                + ",isForward=" + mIsForward
                 + ",shouldSendCompatFakeFocus=" + mShouldSendCompatFakeFocus + "}";
     }
 }
diff --git a/core/java/android/app/servertransaction/StartActivityItem.java b/core/java/android/app/servertransaction/StartActivityItem.java
index 15f65f6..8b98b21 100644
--- a/core/java/android/app/servertransaction/StartActivityItem.java
+++ b/core/java/android/app/servertransaction/StartActivityItem.java
@@ -23,6 +23,7 @@
 import android.app.ActivityOptions;
 import android.app.ActivityThread.ActivityClientRecord;
 import android.app.ClientTransactionHandler;
+import android.os.IBinder;
 import android.os.Parcel;
 import android.os.Trace;
 
@@ -37,8 +38,8 @@
     private ActivityOptions mActivityOptions;
 
     @Override
-    public void execute(ClientTransactionHandler client, ActivityClientRecord r,
-            PendingTransactionActions pendingActions) {
+    public void execute(@NonNull ClientTransactionHandler client, @NonNull ActivityClientRecord r,
+            @NonNull PendingTransactionActions pendingActions) {
         Trace.traceBegin(TRACE_TAG_ACTIVITY_MANAGER, "startActivityItem");
         client.handleStartActivity(r, pendingActions, mActivityOptions);
         Trace.traceEnd(TRACE_TAG_ACTIVITY_MANAGER);
@@ -49,17 +50,19 @@
         return ON_START;
     }
 
-
     // ObjectPoolItem implementation
 
     private StartActivityItem() {}
 
     /** Obtain an instance initialized with provided params. */
-    public static StartActivityItem obtain(ActivityOptions activityOptions) {
+    @NonNull
+    public static StartActivityItem obtain(@NonNull IBinder activityToken,
+            @Nullable ActivityOptions activityOptions) {
         StartActivityItem instance = ObjectPool.obtain(StartActivityItem.class);
         if (instance == null) {
             instance = new StartActivityItem();
         }
+        instance.setActivityToken(activityToken);
         instance.mActivityOptions = activityOptions;
 
         return instance;
@@ -72,37 +75,37 @@
         ObjectPool.recycle(this);
     }
 
-
     // Parcelable implementation
 
     /** Write to Parcel. */
     @Override
-    public void writeToParcel(Parcel dest, int flags) {
+    public void writeToParcel(@NonNull Parcel dest, int flags) {
+        super.writeToParcel(dest, flags);
         dest.writeBundle(mActivityOptions != null ? mActivityOptions.toBundle() : null);
     }
 
     /** Read from Parcel. */
-    private StartActivityItem(Parcel in) {
+    private StartActivityItem(@NonNull Parcel in) {
+        super(in);
         mActivityOptions = ActivityOptions.fromBundle(in.readBundle());
     }
 
-    public static final @NonNull Creator<StartActivityItem> CREATOR =
-            new Creator<StartActivityItem>() {
-                public StartActivityItem createFromParcel(Parcel in) {
-                    return new StartActivityItem(in);
-                }
+    public static final @NonNull Creator<StartActivityItem> CREATOR = new Creator<>() {
+        public StartActivityItem createFromParcel(@NonNull Parcel in) {
+            return new StartActivityItem(in);
+        }
 
-                public StartActivityItem[] newArray(int size) {
-                    return new StartActivityItem[size];
-                }
-            };
+        public StartActivityItem[] newArray(int size) {
+            return new StartActivityItem[size];
+        }
+    };
 
     @Override
     public boolean equals(@Nullable Object o) {
         if (this == o) {
             return true;
         }
-        if (o == null || getClass() != o.getClass()) {
+        if (!super.equals(o)) {
             return false;
         }
         final StartActivityItem other = (StartActivityItem) o;
@@ -112,13 +115,15 @@
     @Override
     public int hashCode() {
         int result = 17;
+        result = 31 * result + super.hashCode();
         result = 31 * result + (mActivityOptions != null ? 1 : 0);
         return result;
     }
 
     @Override
     public String toString() {
-        return "StartActivityItem{options=" + mActivityOptions + "}";
+        return "StartActivityItem{" + super.toString()
+                + ",options=" + mActivityOptions + "}";
     }
 }
 
diff --git a/core/java/android/app/servertransaction/StopActivityItem.java b/core/java/android/app/servertransaction/StopActivityItem.java
index 7e9116d..f432567 100644
--- a/core/java/android/app/servertransaction/StopActivityItem.java
+++ b/core/java/android/app/servertransaction/StopActivityItem.java
@@ -37,8 +37,8 @@
     private int mConfigChanges;
 
     @Override
-    public void execute(ClientTransactionHandler client, ActivityClientRecord r,
-            PendingTransactionActions pendingActions) {
+    public void execute(@NonNull ClientTransactionHandler client, @NonNull ActivityClientRecord r,
+            @NonNull PendingTransactionActions pendingActions) {
         Trace.traceBegin(TRACE_TAG_ACTIVITY_MANAGER, "activityStop");
         client.handleStopActivity(r, mConfigChanges, pendingActions,
                 true /* finalStateRequest */, "STOP_ACTIVITY_ITEM");
@@ -46,8 +46,8 @@
     }
 
     @Override
-    public void postExecute(ClientTransactionHandler client, IBinder token,
-            PendingTransactionActions pendingActions) {
+    public void postExecute(@NonNull ClientTransactionHandler client, @NonNull IBinder token,
+            @NonNull PendingTransactionActions pendingActions) {
         client.reportStop(pendingActions);
     }
 
@@ -56,20 +56,22 @@
         return ON_STOP;
     }
 
-
     // ObjectPoolItem implementation
 
     private StopActivityItem() {}
 
     /**
      * Obtain an instance initialized with provided params.
+     * @param activityToken the activity that stops.
      * @param configChanges Configuration pieces that changed.
      */
-    public static StopActivityItem obtain(int configChanges) {
+    @NonNull
+    public static StopActivityItem obtain(@NonNull IBinder activityToken, int configChanges) {
         StopActivityItem instance = ObjectPool.obtain(StopActivityItem.class);
         if (instance == null) {
             instance = new StopActivityItem();
         }
+        instance.setActivityToken(activityToken);
         instance.mConfigChanges = configChanges;
 
         return instance;
@@ -82,23 +84,23 @@
         ObjectPool.recycle(this);
     }
 
-
     // Parcelable implementation
 
     /** Write to Parcel. */
     @Override
-    public void writeToParcel(Parcel dest, int flags) {
+    public void writeToParcel(@NonNull Parcel dest, int flags) {
+        super.writeToParcel(dest, flags);
         dest.writeInt(mConfigChanges);
     }
 
     /** Read from Parcel. */
-    private StopActivityItem(Parcel in) {
+    private StopActivityItem(@NonNull Parcel in) {
+        super(in);
         mConfigChanges = in.readInt();
     }
 
-    public static final @NonNull Creator<StopActivityItem> CREATOR =
-            new Creator<StopActivityItem>() {
-        public StopActivityItem createFromParcel(Parcel in) {
+    public static final @NonNull Creator<StopActivityItem> CREATOR = new Creator<>() {
+        public StopActivityItem createFromParcel(@NonNull Parcel in) {
             return new StopActivityItem(in);
         }
 
@@ -112,7 +114,7 @@
         if (this == o) {
             return true;
         }
-        if (o == null || getClass() != o.getClass()) {
+        if (!super.equals(o)) {
             return false;
         }
         final StopActivityItem other = (StopActivityItem) o;
@@ -122,12 +124,14 @@
     @Override
     public int hashCode() {
         int result = 17;
+        result = 31 * result + super.hashCode();
         result = 31 * result + mConfigChanges;
         return result;
     }
 
     @Override
     public String toString() {
-        return "StopActivityItem{configChanges=" + mConfigChanges + "}";
+        return "StopActivityItem{" + super.toString()
+                + ",configChanges=" + mConfigChanges + "}";
     }
 }
diff --git a/core/java/android/app/servertransaction/TopResumedActivityChangeItem.java b/core/java/android/app/servertransaction/TopResumedActivityChangeItem.java
index 5cd3d68f..693599f 100644
--- a/core/java/android/app/servertransaction/TopResumedActivityChangeItem.java
+++ b/core/java/android/app/servertransaction/TopResumedActivityChangeItem.java
@@ -35,16 +35,16 @@
     private boolean mOnTop;
 
     @Override
-    public void execute(ClientTransactionHandler client, ActivityClientRecord r,
-            PendingTransactionActions pendingActions) {
+    public void execute(@NonNull ClientTransactionHandler client, @NonNull ActivityClientRecord r,
+            @NonNull PendingTransactionActions pendingActions) {
         Trace.traceBegin(TRACE_TAG_ACTIVITY_MANAGER, "topResumedActivityChangeItem");
         client.handleTopResumedActivityChanged(r, mOnTop, "topResumedActivityChangeItem");
         Trace.traceEnd(TRACE_TAG_ACTIVITY_MANAGER);
     }
 
     @Override
-    public void postExecute(ClientTransactionHandler client, IBinder token,
-            PendingTransactionActions pendingActions) {
+    public void postExecute(@NonNull ClientTransactionHandler client, @NonNull IBinder token,
+            @NonNull PendingTransactionActions pendingActions) {
         if (mOnTop) {
             return;
         }
@@ -58,18 +58,20 @@
         ActivityClient.getInstance().activityTopResumedStateLost();
     }
 
-
     // ObjectPoolItem implementation
 
     private TopResumedActivityChangeItem() {}
 
     /** Obtain an instance initialized with provided params. */
-    public static TopResumedActivityChangeItem obtain(boolean onTop) {
+    @NonNull
+    public static TopResumedActivityChangeItem obtain(@NonNull IBinder activityToken,
+            boolean onTop) {
         TopResumedActivityChangeItem instance =
                 ObjectPool.obtain(TopResumedActivityChangeItem.class);
         if (instance == null) {
             instance = new TopResumedActivityChangeItem();
         }
+        instance.setActivityToken(activityToken);
         instance.mOnTop = onTop;
 
         return instance;
@@ -77,27 +79,28 @@
 
     @Override
     public void recycle() {
+        super.recycle();
         mOnTop = false;
         ObjectPool.recycle(this);
     }
 
-
     // Parcelable implementation
 
     /** Write to Parcel. */
     @Override
-    public void writeToParcel(Parcel dest, int flags) {
+    public void writeToParcel(@NonNull Parcel dest, int flags) {
+        super.writeToParcel(dest, flags);
         dest.writeBoolean(mOnTop);
     }
 
     /** Read from Parcel. */
-    private TopResumedActivityChangeItem(Parcel in) {
+    private TopResumedActivityChangeItem(@NonNull Parcel in) {
+        super(in);
         mOnTop = in.readBoolean();
     }
 
-    public static final @NonNull Creator<TopResumedActivityChangeItem> CREATOR =
-            new Creator<TopResumedActivityChangeItem>() {
-        public TopResumedActivityChangeItem createFromParcel(Parcel in) {
+    public static final @NonNull Creator<TopResumedActivityChangeItem> CREATOR = new Creator<>() {
+        public TopResumedActivityChangeItem createFromParcel(@NonNull Parcel in) {
             return new TopResumedActivityChangeItem(in);
         }
 
@@ -111,7 +114,7 @@
         if (this == o) {
             return true;
         }
-        if (o == null || getClass() != o.getClass()) {
+        if (!super.equals(o)) {
             return false;
         }
         final TopResumedActivityChangeItem other = (TopResumedActivityChangeItem) o;
@@ -121,12 +124,14 @@
     @Override
     public int hashCode() {
         int result = 17;
+        result = 31 * result + super.hashCode();
         result = 31 * result + (mOnTop ? 1 : 0);
         return result;
     }
 
     @Override
     public String toString() {
-        return "TopResumedActivityChangeItem{onTop=" + mOnTop + "}";
+        return "TopResumedActivityChangeItem{" + super.toString()
+                + ",onTop=" + mOnTop + "}";
     }
 }
diff --git a/core/java/android/app/servertransaction/TransactionExecutorHelper.java b/core/java/android/app/servertransaction/TransactionExecutorHelper.java
index baf2a47..0f9c517 100644
--- a/core/java/android/app/servertransaction/TransactionExecutorHelper.java
+++ b/core/java/android/app/servertransaction/TransactionExecutorHelper.java
@@ -196,13 +196,13 @@
                 // Fall through to return the PAUSE item to ensure the activity is properly
                 // resumed while relaunching.
             case ON_PAUSE:
-                lifecycleItem = PauseActivityItem.obtain();
+                lifecycleItem = PauseActivityItem.obtain(r.token);
                 break;
             case ON_STOP:
-                lifecycleItem = StopActivityItem.obtain(0 /* configChanges */);
+                lifecycleItem = StopActivityItem.obtain(r.token, 0 /* configChanges */);
                 break;
             default:
-                lifecycleItem = ResumeActivityItem.obtain(false /* isForward */,
+                lifecycleItem = ResumeActivityItem.obtain(r.token, false /* isForward */,
                         false /* shouldSendCompatFakeFocus */);
                 break;
         }
diff --git a/core/java/android/app/servertransaction/TransferSplashScreenViewStateItem.java b/core/java/android/app/servertransaction/TransferSplashScreenViewStateItem.java
index 767fd28..11947e9 100644
--- a/core/java/android/app/servertransaction/TransferSplashScreenViewStateItem.java
+++ b/core/java/android/app/servertransaction/TransferSplashScreenViewStateItem.java
@@ -20,10 +20,13 @@
 import android.annotation.Nullable;
 import android.app.ActivityThread;
 import android.app.ClientTransactionHandler;
+import android.os.IBinder;
 import android.os.Parcel;
 import android.view.SurfaceControl;
 import android.window.SplashScreenView.SplashScreenViewParcelable;
 
+import java.util.Objects;
+
 /**
  * Transfer a splash screen view to an Activity.
  * @hide
@@ -36,36 +39,44 @@
     @Override
     public void execute(@NonNull ClientTransactionHandler client,
             @NonNull ActivityThread.ActivityClientRecord r,
-            PendingTransactionActions pendingActions) {
+            @NonNull PendingTransactionActions pendingActions) {
         client.handleAttachSplashScreenView(r, mSplashScreenViewParcelable, mStartingWindowLeash);
     }
 
     @Override
     public void recycle() {
+        super.recycle();
+        mSplashScreenViewParcelable = null;
+        mStartingWindowLeash = null;
         ObjectPool.recycle(this);
     }
 
     @Override
-    public void writeToParcel(Parcel dest, int flags) {
+    public void writeToParcel(@NonNull Parcel dest, int flags) {
+        super.writeToParcel(dest, flags);
         dest.writeTypedObject(mSplashScreenViewParcelable, flags);
         dest.writeTypedObject(mStartingWindowLeash, flags);
     }
 
     private TransferSplashScreenViewStateItem() {}
-    private TransferSplashScreenViewStateItem(Parcel in) {
+
+    private TransferSplashScreenViewStateItem(@NonNull Parcel in) {
+        super(in);
         mSplashScreenViewParcelable = in.readTypedObject(SplashScreenViewParcelable.CREATOR);
         mStartingWindowLeash = in.readTypedObject(SurfaceControl.CREATOR);
     }
 
     /** Obtain an instance initialized with provided params. */
+    @NonNull
     public static TransferSplashScreenViewStateItem obtain(
-            @Nullable SplashScreenViewParcelable parcelable,
+            @NonNull IBinder activityToken, @Nullable SplashScreenViewParcelable parcelable,
             @Nullable SurfaceControl startingWindowLeash) {
         TransferSplashScreenViewStateItem instance =
                 ObjectPool.obtain(TransferSplashScreenViewStateItem.class);
         if (instance == null) {
             instance = new TransferSplashScreenViewStateItem();
         }
+        instance.setActivityToken(activityToken);
         instance.mSplashScreenViewParcelable = parcelable;
         instance.mStartingWindowLeash = startingWindowLeash;
 
@@ -73,8 +84,8 @@
     }
 
     public static final @NonNull Creator<TransferSplashScreenViewStateItem> CREATOR =
-            new Creator<TransferSplashScreenViewStateItem>() {
-                public TransferSplashScreenViewStateItem createFromParcel(Parcel in) {
+            new Creator<>() {
+                public TransferSplashScreenViewStateItem createFromParcel(@NonNull Parcel in) {
                     return new TransferSplashScreenViewStateItem(in);
                 }
 
@@ -82,4 +93,33 @@
                     return new TransferSplashScreenViewStateItem[size];
                 }
             };
+
+    @Override
+    public boolean equals(@Nullable Object o) {
+        if (this == o) {
+            return true;
+        }
+        if (!super.equals(o)) {
+            return false;
+        }
+        final TransferSplashScreenViewStateItem other = (TransferSplashScreenViewStateItem) o;
+        return Objects.equals(mSplashScreenViewParcelable, other.mSplashScreenViewParcelable)
+                && Objects.equals(mStartingWindowLeash, other.mStartingWindowLeash);
+    }
+
+    @Override
+    public int hashCode() {
+        int result = 17;
+        result = 31 * result + super.hashCode();
+        result = 31 * result + Objects.hashCode(mSplashScreenViewParcelable);
+        result = 31 * result + Objects.hashCode(mStartingWindowLeash);
+        return result;
+    }
+
+    @Override
+    public String toString() {
+        return "TransferSplashScreenViewStateItem{" + super.toString()
+                + ",splashScreenViewParcelable=" + mSplashScreenViewParcelable
+                + ",startingWindowLeash=" + mStartingWindowLeash + "}";
+    }
 }
diff --git a/core/java/android/companion/CompanionDeviceManager.java b/core/java/android/companion/CompanionDeviceManager.java
index 3aa2877..2fb428b 100644
--- a/core/java/android/companion/CompanionDeviceManager.java
+++ b/core/java/android/companion/CompanionDeviceManager.java
@@ -39,6 +39,7 @@
 import android.app.PendingIntent;
 import android.bluetooth.BluetoothAdapter;
 import android.bluetooth.BluetoothDevice;
+import android.companion.datatransfer.PermissionSyncRequest;
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
@@ -180,7 +181,7 @@
     public @interface DataSyncTypes {}
 
     /**
-     * Used by {@link #enableSystemDataSync(int, int)}}.
+     * Used by {@link #enableSystemDataSyncForTypes(int, int)}}.
      * Sync call metadata like muting, ending and silencing a call.
      *
      */
@@ -552,6 +553,39 @@
     }
 
     /**
+     * @hide
+     */
+    public void enablePermissionsSync(int associationId) {
+        try {
+            mService.enablePermissionsSync(associationId);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * @hide
+     */
+    public void disablePermissionsSync(int associationId) {
+        try {
+            mService.disablePermissionsSync(associationId);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * @hide
+     */
+    public PermissionSyncRequest getPermissionSyncRequest(int associationId) {
+        try {
+            return mService.getPermissionSyncRequest(associationId);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
      * <p>Calling this API requires a uses-feature
      * {@link PackageManager#FEATURE_COMPANION_DEVICE_SETUP} declaration in the manifest</p>
      *
diff --git a/core/java/android/companion/ICompanionDeviceManager.aidl b/core/java/android/companion/ICompanionDeviceManager.aidl
index a3b202a..c5a1988 100644
--- a/core/java/android/companion/ICompanionDeviceManager.aidl
+++ b/core/java/android/companion/ICompanionDeviceManager.aidl
@@ -24,6 +24,7 @@
 import android.companion.ISystemDataTransferCallback;
 import android.companion.AssociationInfo;
 import android.companion.AssociationRequest;
+import android.companion.datatransfer.PermissionSyncRequest;
 import android.content.ComponentName;
 
 /**
@@ -113,6 +114,12 @@
 
     void disableSystemDataSync(int associationId, int flags);
 
+    void enablePermissionsSync(int associationId);
+
+    void disablePermissionsSync(int associationId);
+
+    PermissionSyncRequest getPermissionSyncRequest(int associationId);
+
     @EnforcePermission("MANAGE_COMPANION_DEVICES")
     void enableSecureTransport(boolean enabled);
 
diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java
index f156878..e9bbed3 100644
--- a/core/java/android/content/Intent.java
+++ b/core/java/android/content/Intent.java
@@ -5274,7 +5274,7 @@
      * Broadcast Action: Sent to the responsible installer of an archived package when unarchival
      * is requested.
      *
-     * @see android.content.pm.PackageArchiver
+     * @see android.content.pm.PackageInstaller#requestUnarchive(String)
      * @hide
      */
     @SystemApi
diff --git a/core/java/android/content/pm/IPackageArchiverService.aidl b/core/java/android/content/pm/IPackageArchiverService.aidl
deleted file mode 100644
index dc6491d..0000000
--- a/core/java/android/content/pm/IPackageArchiverService.aidl
+++ /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 android.content.pm;
-
-import android.content.IntentSender;
-import android.os.UserHandle;
-
-/** {@hide} */
-interface IPackageArchiverService {
-
-    @JavaPassthrough(annotation="@android.annotation.RequiresPermission(anyOf={android.Manifest.permission.DELETE_PACKAGES,android.Manifest.permission.REQUEST_DELETE_PACKAGES})")
-    void requestArchive(String packageName, String callerPackageName, in IntentSender statusReceiver, in UserHandle userHandle);
-
-    @JavaPassthrough(annotation="@android.annotation.RequiresPermission(anyOf={android.Manifest.permission.INSTALL_PACKAGES,android.Manifest.permission.REQUEST_INSTALL_PACKAGES})")
-    void requestUnarchive(String packageName, String callerPackageName, in UserHandle userHandle);
-}
\ No newline at end of file
diff --git a/core/java/android/content/pm/IPackageInstaller.aidl b/core/java/android/content/pm/IPackageInstaller.aidl
index ebe2aa3..edb07ce 100644
--- a/core/java/android/content/pm/IPackageInstaller.aidl
+++ b/core/java/android/content/pm/IPackageInstaller.aidl
@@ -24,6 +24,7 @@
 import android.content.pm.VersionedPackage;
 import android.content.IntentSender;
 import android.os.RemoteCallback;
+import android.os.UserHandle;
 
 import android.graphics.Bitmap;
 
@@ -75,4 +76,10 @@
     void waitForInstallConstraints(String installerPackageName, in List<String> packageNames,
             in PackageInstaller.InstallConstraints constraints, in IntentSender callback,
             long timeout);
+
+    @JavaPassthrough(annotation="@android.annotation.RequiresPermission(anyOf={android.Manifest.permission.DELETE_PACKAGES,android.Manifest.permission.REQUEST_DELETE_PACKAGES})")
+    void requestArchive(String packageName, String callerPackageName, in IntentSender statusReceiver, in UserHandle userHandle);
+
+    @JavaPassthrough(annotation="@android.annotation.RequiresPermission(anyOf={android.Manifest.permission.INSTALL_PACKAGES,android.Manifest.permission.REQUEST_INSTALL_PACKAGES})")
+    void requestUnarchive(String packageName, String callerPackageName, in UserHandle userHandle);
 }
diff --git a/core/java/android/content/pm/IPackageManager.aidl b/core/java/android/content/pm/IPackageManager.aidl
index 916c249..556c794 100644
--- a/core/java/android/content/pm/IPackageManager.aidl
+++ b/core/java/android/content/pm/IPackageManager.aidl
@@ -26,7 +26,6 @@
 import android.content.pm.ChangedPackages;
 import android.content.pm.InstantAppInfo;
 import android.content.pm.FeatureInfo;
-import android.content.pm.IPackageArchiverService;
 import android.content.pm.IDexModuleRegisterCallback;
 import android.content.pm.InstallSourceInfo;
 import android.content.pm.IOnChecksumsReadyListener;
@@ -652,8 +651,6 @@
     @UnsupportedAppUsage(maxTargetSdk = 30, trackingBug = 170729553)
     IPackageInstaller getPackageInstaller();
 
-    IPackageArchiverService getPackageArchiverService();
-
     @EnforcePermission("DELETE_PACKAGES")
     boolean setBlockUninstallForUser(String packageName, boolean blockUninstall, int userId);
     @UnsupportedAppUsage
diff --git a/core/java/android/content/pm/PackageArchiver.java b/core/java/android/content/pm/PackageArchiver.java
deleted file mode 100644
index b065231..0000000
--- a/core/java/android/content/pm/PackageArchiver.java
+++ /dev/null
@@ -1,129 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package android.content.pm;
-
-import android.Manifest;
-import android.annotation.NonNull;
-import android.annotation.RequiresPermission;
-import android.annotation.SystemApi;
-import android.content.Context;
-import android.content.IntentSender;
-import android.content.pm.PackageManager.NameNotFoundException;
-import android.os.ParcelableException;
-import android.os.RemoteException;
-
-/**
- * {@code ArchiveManager} is used to archive apps. During the archival process, the apps APKs and
- * cache are removed from the device while the user data is kept. Through the
- * {@code requestUnarchive()} call, apps can be restored again through their responsible app store.
- *
- * <p> Archived apps are returned as displayable apps through the {@link LauncherApps} APIs and
- * will be displayed to users with UI treatment to highlight that said apps are archived. If
- * a user taps on an archived app, the app will be unarchived and the restoration process is
- * communicated.
- *
- * @hide
- */
-// TODO(b/278560219) Improve public documentation.
-@SystemApi
-public class PackageArchiver {
-
-    /**
-     * Extra field for the package name of a package that is requested to be unarchived. Sent as
-     * part of the {@link android.content.Intent#ACTION_UNARCHIVE_PACKAGE} intent.
-     *
-     * @hide
-     */
-    @SystemApi
-    public static final String EXTRA_UNARCHIVE_PACKAGE_NAME =
-            "android.content.pm.extra.UNARCHIVE_PACKAGE_NAME";
-
-    /**
-     * If true, the requestor of the unarchival has specified that the app should be unarchived
-     * for {@link android.os.UserHandle#ALL}.
-     *
-     * @hide
-     */
-    @SystemApi
-    public static final String EXTRA_UNARCHIVE_ALL_USERS =
-            "android.content.pm.extra.UNARCHIVE_ALL_USERS";
-
-    private final Context mContext;
-    private final IPackageArchiverService mService;
-
-    /**
-     * @hide
-     */
-    public PackageArchiver(Context context, IPackageArchiverService service) {
-        mContext = context;
-        mService = service;
-    }
-
-    /**
-     * Requests to archive a package which is currently installed.
-     *
-     * @param statusReceiver Callback used to notify when the operation is completed.
-     * @throws NameNotFoundException If {@code packageName} isn't found or not available to the
-     *                               caller or isn't archived.
-     * @hide
-     */
-    @RequiresPermission(anyOf = {
-            Manifest.permission.DELETE_PACKAGES,
-            Manifest.permission.REQUEST_DELETE_PACKAGES})
-    @SystemApi
-    public void requestArchive(@NonNull String packageName, @NonNull IntentSender statusReceiver)
-            throws NameNotFoundException {
-        try {
-            mService.requestArchive(packageName, mContext.getPackageName(), statusReceiver,
-                    mContext.getUser());
-        } catch (ParcelableException e) {
-            e.maybeRethrow(NameNotFoundException.class);
-        } catch (RemoteException e) {
-            throw e.rethrowFromSystemServer();
-        }
-    }
-
-    /**
-     * Requests to unarchive a currently archived package.
-     *
-     * <p> Sends a request to unarchive an app to the responsible installer. The installer is
-     * determined by {@link InstallSourceInfo#getUpdateOwnerPackageName()}, or
-     * {@link InstallSourceInfo#getInstallingPackageName()} if the former value is null.
-     *
-     * <p> The installation will happen asynchronously and can be observed through
-     * {@link android.content.Intent#ACTION_PACKAGE_ADDED}.
-     *
-     * @throws NameNotFoundException If {@code packageName} isn't found or not visible to the
-     *                               caller or if the package has no installer on the device
-     *                               anymore to unarchive it.
-     * @hide
-     */
-    @RequiresPermission(anyOf = {
-            Manifest.permission.INSTALL_PACKAGES,
-            Manifest.permission.REQUEST_INSTALL_PACKAGES})
-    @SystemApi
-    public void requestUnarchive(@NonNull String packageName)
-            throws NameNotFoundException {
-        try {
-            mService.requestUnarchive(packageName, mContext.getPackageName(), mContext.getUser());
-        } catch (ParcelableException e) {
-            e.maybeRethrow(NameNotFoundException.class);
-        } catch (RemoteException e) {
-            throw e.rethrowFromSystemServer();
-        }
-    }
-}
diff --git a/core/java/android/content/pm/PackageInfo.java b/core/java/android/content/pm/PackageInfo.java
index cdb8b46..1fe1923 100644
--- a/core/java/android/content/pm/PackageInfo.java
+++ b/core/java/android/content/pm/PackageInfo.java
@@ -20,6 +20,7 @@
 import android.annotation.Nullable;
 import android.annotation.SystemApi;
 import android.compat.annotation.UnsupportedAppUsage;
+import android.content.IntentSender;
 import android.os.Build;
 import android.os.Parcel;
 import android.os.Parcelable;
@@ -492,7 +493,8 @@
     /**
      * Whether the package is currently in an archived state.
      *
-     * <p>Packages can be archived through {@link PackageArchiver} and do not have any APKs stored
+     * <p>Packages can be archived through
+     * {@link PackageInstaller#requestArchive(String, IntentSender)} and do not have any APKs stored
      * on the device, but do keep the data directory.
      * @hide
      */
diff --git a/core/java/android/content/pm/PackageInstaller.java b/core/java/android/content/pm/PackageInstaller.java
index f417480..390efd4 100644
--- a/core/java/android/content/pm/PackageInstaller.java
+++ b/core/java/android/content/pm/PackageInstaller.java
@@ -34,6 +34,7 @@
 import android.annotation.CallbackExecutor;
 import android.annotation.CurrentTimeMillisLong;
 import android.annotation.DurationMillisLong;
+import android.annotation.FlaggedApi;
 import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
@@ -348,6 +349,28 @@
             "android.content.pm.extra.RESOLVED_BASE_PATH";
 
     /**
+     * Extra field for the package name of a package that is requested to be unarchived. Sent as
+     * part of the {@link android.content.Intent#ACTION_UNARCHIVE_PACKAGE} intent.
+     *
+     * @hide
+     */
+    @SystemApi
+    @FlaggedApi(Flags.FLAG_ARCHIVING)
+    public static final String EXTRA_UNARCHIVE_PACKAGE_NAME =
+            "android.content.pm.extra.UNARCHIVE_PACKAGE_NAME";
+
+    /**
+     * If true, the requestor of the unarchival has specified that the app should be unarchived
+     * for {@link android.os.UserHandle#ALL}.
+     *
+     * @hide
+     */
+    @SystemApi
+    @FlaggedApi(Flags.FLAG_ARCHIVING)
+    public static final String EXTRA_UNARCHIVE_ALL_USERS =
+            "android.content.pm.extra.UNARCHIVE_ALL_USERS";
+
+    /**
      * Streaming installation pending.
      * Caller should make sure DataLoader is able to prepare image and reinitiate the operation.
      *
@@ -2158,6 +2181,72 @@
         return new InstallInfo(result);
     }
 
+    /**
+     * Requests to archive a package which is currently installed.
+     *
+     * <p> During the archival process, the apps APKs and cache are removed from the device while
+     * the user data is kept. Through the {@link #requestUnarchive(String)} call, apps can be
+     * restored again through their responsible installer.
+     *
+     * <p> Archived apps are returned as displayable apps through the {@link LauncherApps} APIs and
+     * will be displayed to users with UI treatment to highlight that said apps are archived. If
+     * a user taps on an archived app, the app will be unarchived and the restoration process is
+     * communicated.
+     *
+     * @param statusReceiver Callback used to notify when the operation is completed.
+     * @throws PackageManager.NameNotFoundException If {@code packageName} isn't found or not
+     *                                              available to the caller or isn't archived.
+     * @hide
+     */
+    @RequiresPermission(anyOf = {
+            Manifest.permission.DELETE_PACKAGES,
+            Manifest.permission.REQUEST_DELETE_PACKAGES})
+    @SystemApi
+    @FlaggedApi(Flags.FLAG_ARCHIVING)
+    public void requestArchive(@NonNull String packageName, @NonNull IntentSender statusReceiver)
+            throws PackageManager.NameNotFoundException {
+        try {
+            mInstaller.requestArchive(packageName, mInstallerPackageName, statusReceiver,
+                    new UserHandle(mUserId));
+        } catch (ParcelableException e) {
+            e.maybeRethrow(PackageManager.NameNotFoundException.class);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Requests to unarchive a currently archived package.
+     *
+     * <p> Sends a request to unarchive an app to the responsible installer. The installer is
+     * determined by {@link InstallSourceInfo#getUpdateOwnerPackageName()}, or
+     * {@link InstallSourceInfo#getInstallingPackageName()} if the former value is null.
+     *
+     * <p> The installation will happen asynchronously and can be observed through
+     * {@link android.content.Intent#ACTION_PACKAGE_ADDED}.
+     *
+     * @throws PackageManager.NameNotFoundException If {@code packageName} isn't found or not
+     *                                              visible to the caller or if the package has no
+     *                                              installer on the device anymore to unarchive it.
+     * @hide
+     */
+    @RequiresPermission(anyOf = {
+            Manifest.permission.INSTALL_PACKAGES,
+            Manifest.permission.REQUEST_INSTALL_PACKAGES})
+    @SystemApi
+    @FlaggedApi(Flags.FLAG_ARCHIVING)
+    public void requestUnarchive(@NonNull String packageName)
+            throws PackageManager.NameNotFoundException {
+        try {
+            mInstaller.requestUnarchive(packageName, mInstallerPackageName,
+                    new UserHandle(mUserId));
+        } catch (ParcelableException e) {
+            e.maybeRethrow(PackageManager.NameNotFoundException.class);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
     // (b/239722738) This class serves as a bridge between the PackageLite class, which
     // is a hidden class, and the consumers of this class. (e.g. InstallInstalling.java)
     // This is a part of an effort to remove dependency on hidden APIs and use SystemAPIs or
diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java
index 9a53a2a6..cdab431 100644
--- a/core/java/android/content/pm/PackageManager.java
+++ b/core/java/android/content/pm/PackageManager.java
@@ -20,6 +20,7 @@
 import android.annotation.CallbackExecutor;
 import android.annotation.CheckResult;
 import android.annotation.DrawableRes;
+import android.annotation.FlaggedApi;
 import android.annotation.IntDef;
 import android.annotation.IntRange;
 import android.annotation.LongDef;
@@ -29,6 +30,7 @@
 import android.annotation.SdkConstant;
 import android.annotation.SdkConstant.SdkConstantType;
 import android.annotation.StringRes;
+import android.annotation.SuppressLint;
 import android.annotation.SystemApi;
 import android.annotation.TestApi;
 import android.annotation.UserIdInt;
@@ -1238,8 +1240,9 @@
 
     /**
      * Flag parameter to also retrieve some information about archived packages.
-     * Packages can be archived through {@link PackageArchiver} and do not have any APKs stored on
-     * the device, but do keep the data directory.
+     * Packages can be archived through
+     * {@link PackageInstaller#requestArchive(String, IntentSender)} and do not have any APKs stored
+     * on the device, but do keep the data directory.
      * <p> Note: Archived apps are a subset of apps returned by {@link #MATCH_UNINSTALLED_PACKAGES}.
      * <p> Note: this flag may cause less information about currently installed
      * applications to be returned.
@@ -1754,6 +1757,8 @@
      *
      * @hide
      */
+    @SystemApi
+    @FlaggedApi(android.content.pm.Flags.FLAG_QUARANTINED_ENABLED)
     public static final int FLAG_SUSPEND_QUARANTINED = 0x00000001;
 
     /** @hide */
@@ -9751,7 +9756,10 @@
      *
      * @hide
      */
+    @SystemApi
+    @FlaggedApi(android.content.pm.Flags.FLAG_QUARANTINED_ENABLED)
     @RequiresPermission(value=Manifest.permission.SUSPEND_APPS, conditional=true)
+    @SuppressLint("NullableCollection")
     @Nullable
     public String[] setPackagesSuspended(@Nullable String[] packageNames, boolean suspended,
             @Nullable PersistableBundle appExtras, @Nullable PersistableBundle launcherExtras,
@@ -9958,16 +9966,6 @@
     public abstract @NonNull PackageInstaller getPackageInstaller();
 
     /**
-     * {@link PackageArchiver} can be used to archive and restore archived packages.
-     *
-     * @hide
-     */
-    @SystemApi
-    public @NonNull PackageArchiver getPackageArchiver() {
-        throw new UnsupportedOperationException(
-                "getPackageArchiver not implemented in subclass");
-    }
-    /**
      * Adds a {@code CrossProfileIntentFilter}. After calling this method all
      * intents sent from the user with id sourceUserId can also be be resolved
      * by activities in the user with id targetUserId if they match the
diff --git a/core/java/android/content/pm/PackageParser.java b/core/java/android/content/pm/PackageParser.java
index 41ba1dc..4b579e7 100644
--- a/core/java/android/content/pm/PackageParser.java
+++ b/core/java/android/content/pm/PackageParser.java
@@ -1445,7 +1445,7 @@
                     verified.getPublicKeys(),
                     verified.getPastSigningCertificates());
         } else {
-            if (!Signature.areExactMatch(pkg.mSigningDetails.signatures,
+            if (!Signature.areExactArraysMatch(pkg.mSigningDetails.signatures,
                     verified.getSignatures())) {
                 throw new PackageParserException(
                         INSTALL_PARSE_FAILED_INCONSISTENT_CERTIFICATES,
@@ -6468,7 +6468,7 @@
                     }
                 }
             } else {
-                return Signature.areEffectiveMatch(oldDetails.signatures, signatures);
+                return Signature.areEffectiveArraysMatch(oldDetails.signatures, signatures);
             }
             return false;
         }
@@ -6616,7 +6616,7 @@
 
         /** Returns true if the signatures in this and other match exactly. */
         public boolean signaturesMatchExactly(SigningDetails other) {
-            return Signature.areExactMatch(this.signatures, other.signatures);
+            return Signature.areExactArraysMatch(this.signatures, other.signatures);
         }
 
         @Override
@@ -6668,7 +6668,7 @@
             SigningDetails that = (SigningDetails) o;
 
             if (signatureSchemeVersion != that.signatureSchemeVersion) return false;
-            if (!Signature.areExactMatch(signatures, that.signatures)) return false;
+            if (!Signature.areExactArraysMatch(signatures, that.signatures)) return false;
             if (publicKeys != null) {
                 if (!publicKeys.equals((that.publicKeys))) {
                     return false;
@@ -6677,7 +6677,8 @@
                 return false;
             }
 
-            // can't use Signature.areExactMatch() because order matters with the past signing certs
+            // can't use Signature.areExactArraysMatch() because order matters with the past
+            // signing certs
             if (!Arrays.equals(pastSigningCertificates, that.pastSigningCertificates)) {
                 return false;
             }
diff --git a/core/java/android/content/pm/Signature.java b/core/java/android/content/pm/Signature.java
index b049880..a69eee7 100644
--- a/core/java/android/content/pm/Signature.java
+++ b/core/java/android/content/pm/Signature.java
@@ -307,11 +307,27 @@
     }
 
     /**
-     * Test if given {@link Signature} sets are exactly equal.
-     *
+     * Test if given {@link SigningDetails} are exactly equal.
      * @hide
      */
-    public static boolean areExactMatch(Signature[] a, Signature[] b) {
+    public static boolean areExactMatch(SigningDetails ad, SigningDetails bd) {
+        return areExactArraysMatch(ad.getSignatures(), bd.getSignatures());
+    }
+
+    /**
+     * Test if given {@link SigningDetails} and {@link Signature} set are exactly equal.
+     * @hide
+     */
+    public static boolean areExactMatch(SigningDetails ad, Signature[] b) {
+        return areExactArraysMatch(ad.getSignatures(), b);
+    }
+
+
+    /**
+     * Test if given {@link Signature} sets are exactly equal.
+     * @hide
+     */
+    static boolean areExactArraysMatch(Signature[] a, Signature[] b) {
         return (ArrayUtils.size(a) == ArrayUtils.size(b)) && ArrayUtils.containsAll(a, b)
                 && ArrayUtils.containsAll(b, a);
     }
@@ -329,7 +345,12 @@
      *             substantially, usually a signal of something fishy going on.
      * @hide
      */
-    public static boolean areEffectiveMatch(Signature[] a, Signature[] b)
+    public static boolean areEffectiveMatch(SigningDetails a, SigningDetails b)
+            throws CertificateException {
+        return areEffectiveArraysMatch(a.getSignatures(), b.getSignatures());
+    }
+
+    static boolean areEffectiveArraysMatch(Signature[] a, Signature[] b)
             throws CertificateException {
         final CertificateFactory cf = CertificateFactory.getInstance("X.509");
 
@@ -342,7 +363,7 @@
             bPrime[i] = bounce(cf, b[i]);
         }
 
-        return areExactMatch(aPrime, bPrime);
+        return areExactArraysMatch(aPrime, bPrime);
     }
 
     /**
diff --git a/core/java/android/content/pm/SigningDetails.java b/core/java/android/content/pm/SigningDetails.java
index af2649f..8c21974 100644
--- a/core/java/android/content/pm/SigningDetails.java
+++ b/core/java/android/content/pm/SigningDetails.java
@@ -656,7 +656,7 @@
                 }
             }
         } else {
-            return Signature.areEffectiveMatch(oldDetails.mSignatures, mSignatures);
+            return Signature.areEffectiveMatch(oldDetails, this);
         }
         return false;
     }
@@ -800,7 +800,7 @@
 
     /** Returns true if the signatures in this and other match exactly. */
     public boolean signaturesMatchExactly(@NonNull SigningDetails other) {
-        return Signature.areExactMatch(mSignatures, other.mSignatures);
+        return Signature.areExactMatch(this, other);
     }
 
     @Override
@@ -853,7 +853,7 @@
         final SigningDetails that = (SigningDetails) o;
 
         if (mSignatureSchemeVersion != that.mSignatureSchemeVersion) return false;
-        if (!Signature.areExactMatch(mSignatures, that.mSignatures)) return false;
+        if (!Signature.areExactMatch(this, that)) return false;
         if (mPublicKeys != null) {
             if (!mPublicKeys.equals((that.mPublicKeys))) {
                 return false;
diff --git a/core/java/android/content/pm/flags.aconfig b/core/java/android/content/pm/flags.aconfig
new file mode 100644
index 0000000..0c8eb02
--- /dev/null
+++ b/core/java/android/content/pm/flags.aconfig
@@ -0,0 +1,15 @@
+package: "android.content.pm"
+
+flag {
+    name: "quarantined_enabled"
+    namespace: "package_manager_service"
+    description: "Feature flag for Quarantined state"
+    bug: "269127435"
+}
+
+flag {
+    name: "archiving"
+    namespace: "package_manager_service"
+    description: "Feature flag to enable the archiving feature."
+    bug: "278553670"
+}
diff --git a/core/java/android/content/pm/parsing/FrameworkParsingPackageUtils.java b/core/java/android/content/pm/parsing/FrameworkParsingPackageUtils.java
index 3e1c5bb..153dd9a 100644
--- a/core/java/android/content/pm/parsing/FrameworkParsingPackageUtils.java
+++ b/core/java/android/content/pm/parsing/FrameworkParsingPackageUtils.java
@@ -253,8 +253,8 @@
         if (existingSigningDetails == SigningDetails.UNKNOWN) {
             return verified;
         } else {
-            if (!Signature.areExactMatch(existingSigningDetails.getSignatures(),
-                    verified.getResult().getSignatures())) {
+            if (!Signature.areExactMatch(existingSigningDetails,
+                    verified.getResult())) {
                 return input.error(INSTALL_PARSE_FAILED_INCONSISTENT_CERTIFICATES,
                         baseCodePath + " has mismatched certificates");
             }
diff --git a/core/java/android/database/DatabaseUtils.java b/core/java/android/database/DatabaseUtils.java
index d41df4f..7b874cc 100644
--- a/core/java/android/database/DatabaseUtils.java
+++ b/core/java/android/database/DatabaseUtils.java
@@ -77,6 +77,16 @@
     /** One of the values returned by {@link #getSqlStatementType(String)}. */
     public static final int STATEMENT_OTHER = 99;
 
+    // The following statement types are "extended" and are for internal use only.  These types
+    // are not public and are never returned by {@link #getSqlStatementType(String)}.
+
+    /** An internal statement type @hide **/
+    public static final int STATEMENT_WITH = 100;
+    /** An internal statement type @hide **/
+    public static final int STATEMENT_CREATE = 101;
+    /** An internal statement type denoting a comment. @hide **/
+    public static final int STATEMENT_COMMENT = 102;
+
     /**
      * Special function for writing an exception result at the header of
      * a parcel, to be used when returning an exception from a transaction.
@@ -1564,6 +1574,79 @@
     }
 
     /**
+     * The legacy prefix matcher.
+     */
+    private static String getSqlStatementPrefixSimple(@NonNull String sql) {
+        sql = sql.trim();
+        if (sql.length() < 3) {
+            return null;
+        }
+        return sql.substring(0, 3).toUpperCase(Locale.ROOT);
+    }
+
+    /**
+     * Return the extended statement type for the SQL statement.  This is not a public API and it
+     * can return values that are not publicly visible.
+     * @hide
+     */
+    private static int categorizeStatement(@NonNull String prefix, @NonNull String sql) {
+        if (prefix == null) return STATEMENT_OTHER;
+
+        switch (prefix) {
+            case "SEL": return STATEMENT_SELECT;
+            case "INS":
+            case "UPD":
+            case "REP":
+            case "DEL": return STATEMENT_UPDATE;
+            case "ATT": return STATEMENT_ATTACH;
+            case "COM":
+            case "END": return STATEMENT_COMMIT;
+            case "ROL":
+                if (sql.toUpperCase(Locale.ROOT).contains(" TO ")) {
+                    // Rollback to savepoint.
+                    return STATEMENT_OTHER;
+                }
+                return STATEMENT_ABORT;
+            case "BEG": return STATEMENT_BEGIN;
+            case "PRA": return STATEMENT_PRAGMA;
+            case "CRE": return STATEMENT_CREATE;
+            case "DRO":
+            case "ALT": return STATEMENT_DDL;
+            case "ANA":
+            case "DET": return STATEMENT_UNPREPARED;
+            case "WIT": return STATEMENT_WITH;
+            default:
+                if (prefix.startsWith("--") || prefix.startsWith("/*")) {
+                    return STATEMENT_COMMENT;
+                }
+                return STATEMENT_OTHER;
+        }
+    }
+
+    /**
+     * Return the extended statement type for the SQL statement.  This is not a public API and it
+     * can return values that are not publicly visible.
+     * @hide
+     */
+    public static int getSqlStatementTypeExtended(@NonNull String sql) {
+        int type = categorizeStatement(getSqlStatementPrefixSimple(sql), sql);
+        return type;
+    }
+
+    /**
+     * Convert an extended statement type to a public SQL statement type value.
+     * @hide
+     */
+    public static int getSqlStatementType(int extended) {
+        switch (extended) {
+            case STATEMENT_CREATE: return STATEMENT_DDL;
+            case STATEMENT_WITH: return STATEMENT_OTHER;
+            case STATEMENT_COMMENT: return STATEMENT_OTHER;
+        }
+        return extended;
+    }
+
+    /**
      * Returns one of the following which represent the type of the given SQL statement.
      * <ol>
      *   <li>{@link #STATEMENT_SELECT}</li>
@@ -1572,49 +1655,16 @@
      *   <li>{@link #STATEMENT_BEGIN}</li>
      *   <li>{@link #STATEMENT_COMMIT}</li>
      *   <li>{@link #STATEMENT_ABORT}</li>
+     *   <li>{@link #STATEMENT_PRAGMA}</li>
+     *   <li>{@link #STATEMENT_DDL}</li>
+     *   <li>{@link #STATEMENT_UNPREPARED}</li>
      *   <li>{@link #STATEMENT_OTHER}</li>
      * </ol>
      * @param sql the SQL statement whose type is returned by this method
      * @return one of the values listed above
      */
     public static int getSqlStatementType(String sql) {
-        sql = sql.trim();
-        if (sql.length() < 3) {
-            return STATEMENT_OTHER;
-        }
-        String prefixSql = sql.substring(0, 3).toUpperCase(Locale.ROOT);
-        if (prefixSql.equals("SEL")) {
-            return STATEMENT_SELECT;
-        } else if (prefixSql.equals("INS") ||
-                prefixSql.equals("UPD") ||
-                prefixSql.equals("REP") ||
-                prefixSql.equals("DEL")) {
-            return STATEMENT_UPDATE;
-        } else if (prefixSql.equals("ATT")) {
-            return STATEMENT_ATTACH;
-        } else if (prefixSql.equals("COM")) {
-            return STATEMENT_COMMIT;
-        } else if (prefixSql.equals("END")) {
-            return STATEMENT_COMMIT;
-        } else if (prefixSql.equals("ROL")) {
-            boolean isRollbackToSavepoint = sql.toUpperCase(Locale.ROOT).contains(" TO ");
-            if (isRollbackToSavepoint) {
-                Log.w(TAG, "Statement '" + sql
-                        + "' may not work on API levels 16-27, use ';" + sql + "' instead");
-                return STATEMENT_OTHER;
-            }
-            return STATEMENT_ABORT;
-        } else if (prefixSql.equals("BEG")) {
-            return STATEMENT_BEGIN;
-        } else if (prefixSql.equals("PRA")) {
-            return STATEMENT_PRAGMA;
-        } else if (prefixSql.equals("CRE") || prefixSql.equals("DRO") ||
-                prefixSql.equals("ALT")) {
-            return STATEMENT_DDL;
-        } else if (prefixSql.equals("ANA") || prefixSql.equals("DET")) {
-            return STATEMENT_UNPREPARED;
-        }
-        return STATEMENT_OTHER;
+        return getSqlStatementType(getSqlStatementTypeExtended(sql));
     }
 
     /**
diff --git a/core/java/android/database/sqlite/SQLiteConnection.java b/core/java/android/database/sqlite/SQLiteConnection.java
index f2980f4..b96d832 100644
--- a/core/java/android/database/sqlite/SQLiteConnection.java
+++ b/core/java/android/database/sqlite/SQLiteConnection.java
@@ -1096,7 +1096,7 @@
         seqNum = mPreparedStatementCache.getLastSeqNum();
         try {
             final int numParameters = nativeGetParameterCount(mConnectionPtr, statementPtr);
-            final int type = DatabaseUtils.getSqlStatementType(sql);
+            final int type = DatabaseUtils.getSqlStatementTypeExtended(sql);
             final boolean readOnly = nativeIsReadOnly(mConnectionPtr, statementPtr);
             statement = obtainPreparedStatement(sql, statementPtr, numParameters, type, readOnly,
                     seqNum);
@@ -1279,7 +1279,8 @@
 
     private static boolean isCacheable(int statementType) {
         if (statementType == DatabaseUtils.STATEMENT_UPDATE
-                || statementType == DatabaseUtils.STATEMENT_SELECT) {
+            || statementType == DatabaseUtils.STATEMENT_SELECT
+            || statementType == DatabaseUtils.STATEMENT_WITH) {
             return true;
         }
         return false;
diff --git a/core/java/android/inputmethodservice/InputMethodService.java b/core/java/android/inputmethodservice/InputMethodService.java
index 7967db6..582c5c0 100644
--- a/core/java/android/inputmethodservice/InputMethodService.java
+++ b/core/java/android/inputmethodservice/InputMethodService.java
@@ -685,12 +685,7 @@
     private ImeTracker.Token mCurStatsToken;
 
     final ViewTreeObserver.OnComputeInternalInsetsListener mInsetsComputer = info -> {
-        Log.i("b/297000797", "IME#OnComputeInternalInsetsListener, start info: " + info
-                + " before onComputeInsets, tmpInsets: " + mTmpInsets,
-                new Throwable());
         onComputeInsets(mTmpInsets);
-        Log.i("b/297000797", "IME#OnComputeInternalInsetsListener,"
-                + " after onComputeInsets, tmpInsets: " + mTmpInsets);
         if (!mViewsCreated) {
             // The IME views are not ready, keep visible insets untouched.
             mTmpInsets.visibleTopInsets = 0;
@@ -710,7 +705,6 @@
         }
         mNavigationBarController.updateTouchableInsets(mTmpInsets, info);
 
-        Log.i("b/297000797", "IME#OnComputeInternalInsetsListener, end info: " + info);
         if (mInputFrame != null) {
             setImeExclusionRect(mTmpInsets.visibleTopInsets);
         }
@@ -1469,15 +1463,6 @@
             proto.write(TOUCHABLE_REGION, touchableRegion.toString());
             proto.end(token);
         }
-
-        @Override
-        public String toString() {
-            return "Insets{contentTopInsets=" + contentTopInsets
-                    + " visibleTopInsets=" + visibleTopInsets
-                    + " touchableInsets=" + touchableInsets
-                    + " touchableRegion=" + touchableRegion.getBounds()
-                    + "}";
-        }
     }
 
     /**
@@ -4154,13 +4139,13 @@
         p.println("  mExtractedToken=" + mExtractedToken);
         p.println("  mIsInputViewShown=" + mIsInputViewShown
                 + " mStatusIcon=" + mStatusIcon);
-        p.println("Last computed insets:");
-        p.println("  contentTopInsets=" + mTmpInsets.contentTopInsets
+        p.println("  Last computed insets:");
+        p.println("    contentTopInsets=" + mTmpInsets.contentTopInsets
                 + " visibleTopInsets=" + mTmpInsets.visibleTopInsets
                 + " touchableInsets=" + mTmpInsets.touchableInsets
                 + " touchableRegion=" + mTmpInsets.touchableRegion);
-        p.println(" mSettingsObserver=" + mSettingsObserver);
-        p.println(" mNavigationBarController=" + mNavigationBarController.toDebugString());
+        p.println("  mSettingsObserver=" + mSettingsObserver);
+        p.println("  mNavigationBarController=" + mNavigationBarController.toDebugString());
     }
 
     private final ImeTracing.ServiceDumper mDumper = new ImeTracing.ServiceDumper() {
diff --git a/core/java/android/inputmethodservice/NavigationBarController.java b/core/java/android/inputmethodservice/NavigationBarController.java
index 22792a5..8be4c58 100644
--- a/core/java/android/inputmethodservice/NavigationBarController.java
+++ b/core/java/android/inputmethodservice/NavigationBarController.java
@@ -31,7 +31,6 @@
 import android.graphics.Region;
 import android.inputmethodservice.navigationbar.NavigationBarFrame;
 import android.inputmethodservice.navigationbar.NavigationBarView;
-import android.util.Log;
 import android.view.Gravity;
 import android.view.LayoutInflater;
 import android.view.View;
@@ -115,8 +114,6 @@
     }
 
     void onNavButtonFlagsChanged(@InputMethodNavButtonFlags int navButtonFlags) {
-        Log.i("b/297000797", "NavigationBarController#onNavButtonFlagsChanged: " + navButtonFlags,
-                new Throwable());
         mImpl.onNavButtonFlagsChanged(navButtonFlags);
     }
 
@@ -238,10 +235,6 @@
 
             if (ENABLE_HIDE_IME_CAPTION_BAR) {
                 mNavigationBarFrame.setOnApplyWindowInsetsListener((view, insets) -> {
-                    Log.i("b/297000797", "NavigationBarController#onApplyWindowInsetsListener:"
-                            + " mNavigationBarFrame: " + mNavigationBarFrame
-                            + " captionBar visible: " + insets.isVisible(captionBar())
-                            + " insets: " + insets);
                     if (mNavigationBarFrame != null) {
                         boolean visible = insets.isVisible(captionBar());
                         mNavigationBarFrame.setVisibility(visible ? View.VISIBLE : View.GONE);
@@ -460,10 +453,6 @@
             mShouldShowImeSwitcherWhenImeIsShown = shouldShowImeSwitcherWhenImeIsShown;
 
             if (ENABLE_HIDE_IME_CAPTION_BAR) {
-                Log.i("b/297000797", "NavigationBarController#onNavButtonFlagsChanged,"
-                                + " calling setImeCaptionBarInsetsHeight"
-                                + " with: " + getImeCaptionBarHeight(),
-                        new Throwable());
                 mService.mWindow.getWindow().getDecorView().getWindowInsetsController()
                         .setImeCaptionBarInsetsHeight(getImeCaptionBarHeight());
             }
diff --git a/core/java/android/os/flags.aconfig b/core/java/android/os/flags.aconfig
index 851aa6d..febe6f7 100644
--- a/core/java/android/os/flags.aconfig
+++ b/core/java/android/os/flags.aconfig
@@ -6,3 +6,17 @@
     description: "Guards a new UserManager user restriction that admins can use to require cellular encryption on their managed devices."
     bug: "276752881"
 }
+
+flag {
+    name: "remove_app_profiler_pss_collection"
+    namespace: "android_platform_power_optimization"
+    description: "Replaces background PSS collection in AppProfiler with RSS"
+    bug: "297542292"
+}
+
+flag {
+    name: "allow_private_profile"
+    namespace: "private_profile"
+    description: "Guards a new Private Profile type in UserManager - everything from its setup to config to deletion."
+    bug: "299069460"
+}
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index b962d76..7e71134f 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -10664,6 +10664,14 @@
                 "search_press_hold_nav_handle_enabled";
 
         /**
+         * Whether long-pressing on the home button can trigger search.
+         *
+         * @hide
+         */
+        public static final String SEARCH_LONG_PRESS_HOME_ENABLED =
+                "search_long_press_home_enabled";
+
+        /**
          * Control whether Night display is currently activated.
          * @hide
          */
diff --git a/core/java/android/service/wallpaper/WallpaperService.java b/core/java/android/service/wallpaper/WallpaperService.java
index 637770c..04ae0af 100644
--- a/core/java/android/service/wallpaper/WallpaperService.java
+++ b/core/java/android/service/wallpaper/WallpaperService.java
@@ -2556,7 +2556,7 @@
         private void doDetachEngine() {
             // Some wallpapers will not trigger the rendering threads of the remaining engines even
             // if they are visible, so we need to toggle the state to get their attention.
-            if (!mEngine.mDestroyed) {
+            if (mEngine != null && !mEngine.mDestroyed) {
                 mEngine.detach();
                 synchronized (mActiveEngines) {
                     for (IWallpaperEngineWrapper engineWrapper : mActiveEngines.values()) {
diff --git a/core/java/android/text/BoringLayout.java b/core/java/android/text/BoringLayout.java
index 14fc585..65a1da6 100644
--- a/core/java/android/text/BoringLayout.java
+++ b/core/java/android/text/BoringLayout.java
@@ -16,6 +16,9 @@
 
 package android.text;
 
+import static com.android.text.flags.Flags.FLAG_USE_BOUNDS_FOR_WIDTH;
+
+import android.annotation.FlaggedApi;
 import android.annotation.IntRange;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
@@ -55,9 +58,7 @@
      *                line width
      * @param includePad set whether to include extra space beyond font ascent and descent which is
      *                   needed to avoid clipping in some scripts
-     * @deprecated Use {@link android.text.Layout.Builder} instead.
      */
-    @Deprecated
     public static BoringLayout make(CharSequence source, TextPaint paint, int outerWidth,
             Alignment align, float spacingMult, float spacingAdd, BoringLayout.Metrics metrics,
             boolean includePad) {
@@ -83,9 +84,7 @@
      * @param ellipsizedWidth the width to which this Layout is ellipsizing. If {@code ellipsize} is
      *                        {@code null}, or is {@link TextUtils.TruncateAt#MARQUEE} this value is
      *                        not used, {@code outerWidth} is used instead
-     * @deprecated Use {@link android.text.Layout.Builder} instead.
      */
-    @Deprecated
     public static BoringLayout make(CharSequence source, TextPaint paint, int outerWidth,
             Alignment align, float spacingmult, float spacingadd, BoringLayout.Metrics metrics,
             boolean includePad, TextUtils.TruncateAt ellipsize, int ellipsizedWidth) {
@@ -117,9 +116,7 @@
      *                              False for keeping the first font's line height. If some glyphs
      *                              requires larger vertical spaces, by passing true to this
      *                              argument, the layout increase the line height to fit all glyphs.
-     * @deprecated Use {@link android.text.Layout.Builder} instead.
      */
-    @Deprecated
     public static @NonNull BoringLayout make(
             @NonNull CharSequence source, @NonNull TextPaint paint,
             @IntRange(from = 0) int outerWidth,
@@ -266,9 +263,7 @@
      *                line width
      * @param includePad set whether to include extra space beyond font ascent and descent which is
      *                   needed to avoid clipping in some scripts
-     * @deprecated Use {@link android.text.Layout.Builder} instead.
      */
-    @Deprecated
     public BoringLayout(CharSequence source, TextPaint paint, int outerwidth, Alignment align,
             float spacingMult, float spacingAdd, BoringLayout.Metrics metrics, boolean includePad) {
         super(source, paint, outerwidth, align, TextDirectionHeuristics.LTR, spacingMult,
@@ -302,9 +297,7 @@
      * @param ellipsizedWidth the width to which this Layout is ellipsizing. If {@code ellipsize} is
      *                        {@code null}, or is {@link TextUtils.TruncateAt#MARQUEE} this value is
      *                        not used, {@code outerWidth} is used instead
-     * @deprecated Use {@link android.text.Layout.Builder} instead.
      */
-    @Deprecated
     public BoringLayout(CharSequence source, TextPaint paint, int outerWidth, Alignment align,
             float spacingMult, float spacingAdd, BoringLayout.Metrics metrics, boolean includePad,
             TextUtils.TruncateAt ellipsize, int ellipsizedWidth) {
@@ -333,9 +326,7 @@
      *                              False for keeping the first font's line height. If some glyphs
      *                              requires larger vertical spaces, by passing true to this
      *                              argument, the layout increase the line height to fit all glyphs.
-     * @deprecated Use {@link android.text.Layout.Builder} instead.
      */
-    @Deprecated
     public BoringLayout(
             @NonNull CharSequence source, @NonNull TextPaint paint,
             @IntRange(from = 0) int outerWidth, @NonNull Alignment align, float spacingMult,
@@ -478,9 +469,7 @@
      * @param paint a paint
      * @return layout metric for the given text. null if given text is unable to be handled by
      *         BoringLayout.
-     * @deprecated Use {@link android.text.Layout.Builder} instead.
      */
-    @Deprecated
     public static Metrics isBoring(CharSequence text, TextPaint paint) {
         return isBoring(text, paint, TextDirectionHeuristics.FIRSTSTRONG_LTR, null);
     }
@@ -495,9 +484,7 @@
      * @return layout metric for the given text. If metrics is not null, this method fills values
      *         to given metrics object instead of allocating new metrics object. null if given text
      *         is unable to be handled by BoringLayout.
-     * @deprecated Use {@link android.text.Layout.Builder} instead.
      */
-    @Deprecated
     public static Metrics isBoring(CharSequence text, TextPaint paint, Metrics metrics) {
         return isBoring(text, paint, TextDirectionHeuristics.FIRSTSTRONG_LTR, metrics);
     }
@@ -557,9 +544,7 @@
      *                              argument, the layout increase the line height to fit all glyphs.
      * @param metrics the out metrics.
      * @return metrics on success. null if text cannot be rendered by BoringLayout.
-     * @deprecated Use {@link android.text.Layout.Builder} instead.
      */
-    @Deprecated
     public static @Nullable Metrics isBoring(@NonNull CharSequence text, @NonNull TextPaint paint,
             @NonNull TextDirectionHeuristic textDir, boolean useFallbackLineSpacing,
             @Nullable Metrics metrics) {
@@ -746,6 +731,7 @@
          *
          * @return a drawing bounding box.
          */
+        @FlaggedApi(FLAG_USE_BOUNDS_FOR_WIDTH)
         @NonNull public RectF getDrawingBoundingBox() {
             return mDrawingBounds;
         }
diff --git a/core/java/android/text/Layout.java b/core/java/android/text/Layout.java
index 469e166..4f4dea7 100644
--- a/core/java/android/text/Layout.java
+++ b/core/java/android/text/Layout.java
@@ -16,6 +16,9 @@
 
 package android.text;
 
+import static com.android.text.flags.Flags.FLAG_USE_BOUNDS_FOR_WIDTH;
+
+import android.annotation.FlaggedApi;
 import android.annotation.FloatRange;
 import android.annotation.IntDef;
 import android.annotation.IntRange;
@@ -1010,6 +1013,7 @@
      * @return bounding rectangle
      */
     @NonNull
+    @FlaggedApi(FLAG_USE_BOUNDS_FOR_WIDTH)
     public RectF computeDrawingBoundingBox() {
         float left = 0;
         float right = 0;
@@ -3436,6 +3440,7 @@
      *
      * @see StaticLayout.Builder
      */
+    @FlaggedApi(FLAG_USE_BOUNDS_FOR_WIDTH)
     public static final class Builder {
         /**
          * Construct a builder class.
@@ -3776,6 +3781,7 @@
         // The corresponding getter is getUseBoundsForWidth
         @NonNull
         @SuppressLint("MissingGetterMatchingBuilder")
+        @FlaggedApi(FLAG_USE_BOUNDS_FOR_WIDTH)
         public Builder setUseBoundsForWidth(boolean useBoundsForWidth) {
             mUseBoundsForWidth = useBoundsForWidth;
             return this;
@@ -3865,6 +3871,7 @@
      * @see Layout.Builder
      */
     @NonNull
+    @FlaggedApi(FLAG_USE_BOUNDS_FOR_WIDTH)
     public final CharSequence getText() {
         return mText;
     }
@@ -3914,6 +3921,7 @@
      * @see StaticLayout.Builder#setTextDirection(TextDirectionHeuristic)
      */
     @NonNull
+    @FlaggedApi(FLAG_USE_BOUNDS_FOR_WIDTH)
     public final TextDirectionHeuristic getTextDirectionHeuristic() {
         return mTextDir;
     }
@@ -3940,6 +3948,7 @@
      * @see StaticLayout.Builder#setLineSpacing(float, float)
      * @see Layout#getSpacingMultiplier()
      */
+    @FlaggedApi(FLAG_USE_BOUNDS_FOR_WIDTH)
     public final float getLineSpacingMultiplier() {
         return mSpacingMult;
     }
@@ -3966,6 +3975,7 @@
      * @see StaticLayout.Builder#setLineSpacing(float, float)
      * @see Layout#getSpacingAdd()
      */
+    @FlaggedApi(FLAG_USE_BOUNDS_FOR_WIDTH)
     public final float getLineSpacingAmount() {
         return mSpacingAdd;
     }
@@ -3977,6 +3987,7 @@
      * @see Layout.Builder#setFontPaddingIncluded(boolean)
      * @see StaticLayout.Builder#setIncludePad(boolean)
      */
+    @FlaggedApi(FLAG_USE_BOUNDS_FOR_WIDTH)
     public final boolean isFontPaddingIncluded() {
         return mIncludePad;
     }
@@ -4024,6 +4035,7 @@
      * @see Layout#getEllipsizedWidth()
      */
     @Nullable
+    @FlaggedApi(FLAG_USE_BOUNDS_FOR_WIDTH)
     public final TextUtils.TruncateAt getEllipsize() {
         return mEllipsize;
     }
@@ -4039,6 +4051,7 @@
      * @see StaticLayout.Builder#setMaxLines(int)
      */
     @IntRange(from = 1)
+    @FlaggedApi(FLAG_USE_BOUNDS_FOR_WIDTH)
     public final int getMaxLines() {
         return mMaxLines;
     }
@@ -4051,6 +4064,7 @@
      * @see StaticLayout.Builder#setBreakStrategy(int)
      */
     @BreakStrategy
+    @FlaggedApi(FLAG_USE_BOUNDS_FOR_WIDTH)
     public final int getBreakStrategy() {
         return mBreakStrategy;
     }
@@ -4063,6 +4077,7 @@
      * @see StaticLayout.Builder#setHyphenationFrequency(int)
      */
     @HyphenationFrequency
+    @FlaggedApi(FLAG_USE_BOUNDS_FOR_WIDTH)
     public final int getHyphenationFrequency() {
         return mHyphenationFrequency;
     }
@@ -4078,6 +4093,7 @@
      * @see StaticLayout.Builder#setIndents(int[], int[])
      */
     @Nullable
+    @FlaggedApi(FLAG_USE_BOUNDS_FOR_WIDTH)
     public final int[] getLeftIndents() {
         if (mLeftIndents == null) {
             return null;
@@ -4098,6 +4114,7 @@
      * @see StaticLayout.Builder#setIndents(int[], int[])
      */
     @Nullable
+    @FlaggedApi(FLAG_USE_BOUNDS_FOR_WIDTH)
     public final int[] getRightIndents() {
         if (mRightIndents == null) {
             return null;
@@ -4115,6 +4132,7 @@
      * @see StaticLayout.Builder#setJustificationMode(int)
      */
     @JustificationMode
+    @FlaggedApi(FLAG_USE_BOUNDS_FOR_WIDTH)
     public final int getJustificationMode() {
         return mJustificationMode;
     }
@@ -4128,6 +4146,7 @@
      */
     // not being final because of subclass has already published API.
     @NonNull
+    @FlaggedApi(FLAG_USE_BOUNDS_FOR_WIDTH)
     public LineBreakConfig getLineBreakConfig() {
         return mLineBreakConfig;
     }
@@ -4141,6 +4160,7 @@
      * @see StaticLayout.Builder#setUseBoundsForWidth(boolean)
      * @see DynamicLayout.Builder#setUseBoundsForWidth(boolean)
      */
+    @FlaggedApi(FLAG_USE_BOUNDS_FOR_WIDTH)
     public boolean getUseBoundsForWidth() {
         return mUseBoundsForWidth;
     }
diff --git a/core/java/android/text/flags/no_break_no_hyphenation_span.aconfig b/core/java/android/text/flags/no_break_no_hyphenation_span.aconfig
new file mode 100644
index 0000000..60f1e88
--- /dev/null
+++ b/core/java/android/text/flags/no_break_no_hyphenation_span.aconfig
@@ -0,0 +1,8 @@
+package: "com.android.text.flags"
+
+flag {
+  name: "no_break_no_hyphenation_span"
+  namespace: "text"
+  description: "A feature flag that adding new spans that prevents line breaking and hyphenation."
+  bug: "283193586"
+}
diff --git a/core/java/android/text/style/LineBreakConfigSpan.java b/core/java/android/text/style/LineBreakConfigSpan.java
index 90a79c6..b8033a9 100644
--- a/core/java/android/text/style/LineBreakConfigSpan.java
+++ b/core/java/android/text/style/LineBreakConfigSpan.java
@@ -16,6 +16,9 @@
 
 package android.text.style;
 
+import static com.android.text.flags.Flags.FLAG_NO_BREAK_NO_HYPHENATION_SPAN;
+
+import android.annotation.FlaggedApi;
 import android.annotation.NonNull;
 import android.graphics.text.LineBreakConfig;
 
@@ -24,6 +27,7 @@
 /**
  * LineBreakSpan for changing line break style of the specific region of the text.
  */
+@FlaggedApi(FLAG_NO_BREAK_NO_HYPHENATION_SPAN)
 public class LineBreakConfigSpan {
     private final LineBreakConfig mLineBreakConfig;
 
@@ -60,4 +64,22 @@
     public String toString() {
         return "LineBreakConfigSpan{mLineBreakConfig=" + mLineBreakConfig + '}';
     }
+
+    private static final LineBreakConfig sNoHyphenationConfig = new LineBreakConfig.Builder()
+            .setHyphenation(LineBreakConfig.HYPHENATION_DISABLED)
+            .build();
+
+    /**
+     * A specialized {@link LineBreakConfigSpan} that used for preventing hyphenation.
+     */
+    @FlaggedApi(FLAG_NO_BREAK_NO_HYPHENATION_SPAN)
+    public static final class NoHyphenationSpan extends LineBreakConfigSpan {
+        /**
+         * Construct a new {@link NoHyphenationSpan}.
+         */
+        @FlaggedApi(FLAG_NO_BREAK_NO_HYPHENATION_SPAN)
+        public NoHyphenationSpan() {
+            super(sNoHyphenationConfig);
+        }
+    }
 }
diff --git a/core/java/android/util/apk/ApkSignatureVerifier.java b/core/java/android/util/apk/ApkSignatureVerifier.java
index d2a18dd..a6724da 100644
--- a/core/java/android/util/apk/ApkSignatureVerifier.java
+++ b/core/java/android/util/apk/ApkSignatureVerifier.java
@@ -48,6 +48,7 @@
 import java.security.cert.Certificate;
 import java.security.cert.CertificateEncodingException;
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.Iterator;
 import java.util.List;
 import java.util.Map;
@@ -428,7 +429,7 @@
 
                     // make sure all entries use the same signing certs
                     final Signature[] entrySigs = convertToSignatures(entryCerts);
-                    if (!Signature.areExactMatch(lastSigs, entrySigs)) {
+                    if (!Arrays.equals(lastSigs, entrySigs)) {
                         return input.error(
                                 INSTALL_PARSE_FAILED_INCONSISTENT_CERTIFICATES,
                                 "Package " + apkPath + " has mismatched certificates at entry "
diff --git a/core/java/android/view/InputWindowHandle.java b/core/java/android/view/InputWindowHandle.java
index 2761aae..bc83750 100644
--- a/core/java/android/view/InputWindowHandle.java
+++ b/core/java/android/view/InputWindowHandle.java
@@ -35,6 +35,8 @@
  * @hide
  */
 public final class InputWindowHandle {
+    // TODO (b/300094445): Convert to use correct flagging infrastructure
+    public static final boolean USE_SURFACE_TRUSTED_OVERLAY = true;
 
     /**
      * An internal annotation for all the {@link android.os.InputConfig} flags that can be
@@ -59,7 +61,6 @@
             InputConfig.DUPLICATE_TOUCH_TO_WALLPAPER,
             InputConfig.IS_WALLPAPER,
             InputConfig.PAUSE_DISPATCHING,
-            InputConfig.TRUSTED_OVERLAY,
             InputConfig.WATCH_OUTSIDE_TOUCH,
             InputConfig.SLIPPERY,
             InputConfig.DISABLE_USER_ACTIVITY,
@@ -272,4 +273,13 @@
         }
         this.inputConfig &= ~inputConfig;
     }
+
+    public void setTrustedOverlay(SurfaceControl.Transaction t, SurfaceControl sc,
+            boolean isTrusted) {
+        if (USE_SURFACE_TRUSTED_OVERLAY) {
+            t.setTrustedOverlay(sc, isTrusted);
+        } else if (isTrusted) {
+            inputConfig |= InputConfig.TRUSTED_OVERLAY;
+        }
+    }
 }
diff --git a/core/java/android/view/InsetsController.java b/core/java/android/view/InsetsController.java
index 9186e49..fb24211 100644
--- a/core/java/android/view/InsetsController.java
+++ b/core/java/android/view/InsetsController.java
@@ -1856,8 +1856,6 @@
             return;
         }
         Rect newFrame = new Rect(mFrame.left, mFrame.bottom - height, mFrame.right, mFrame.bottom);
-        Log.i("b/297000797", "InsetsController#setImeCaptionBarInsetsHeight,"
-                + " height: " + height + " frame: " + mFrame);
         InsetsSource source = mState.peekSource(ID_IME_CAPTION_BAR);
         if (mImeCaptionBarInsetsHeight != height
                 || (source != null && !newFrame.equals(source.getFrame()))) {
diff --git a/core/java/android/view/InsetsSource.java b/core/java/android/view/InsetsSource.java
index 3e435ae..0d5704e 100644
--- a/core/java/android/view/InsetsSource.java
+++ b/core/java/android/view/InsetsSource.java
@@ -31,7 +31,6 @@
 import android.graphics.Rect;
 import android.os.Parcel;
 import android.os.Parcelable;
-import android.util.Log;
 import android.util.proto.ProtoOutputStream;
 import android.view.WindowInsets.Type.InsetsType;
 
@@ -197,12 +196,6 @@
      *         source.
      */
     public Insets calculateInsets(Rect relativeFrame, boolean ignoreVisibility) {
-        if (getType() == WindowInsets.Type.ime()) {
-            Log.i("b/297000797", "InsetsSource#calculateInsets tmpFrame: " + mTmpFrame
-                    + " ignoreVisibility: " + ignoreVisibility
-                    + " frame: " + mFrame
-                    + " relativeFrame: " + relativeFrame, new Throwable());
-        }
         return calculateInsets(relativeFrame, mFrame, ignoreVisibility);
     }
 
@@ -210,12 +203,6 @@
      * Like {@link #calculateInsets(Rect, boolean)}, but will return visible insets.
      */
     public Insets calculateVisibleInsets(Rect relativeFrame) {
-        if (getType() == WindowInsets.Type.ime()) {
-            Log.i("b/297000797", "InsetsSource#calculateVisibleInsets tmpFrame: " + mTmpFrame
-                    + " frame: " + mFrame
-                    + " visibleFrame: " + mVisibleFrame
-                    + " relativeFrame: " + relativeFrame, new Throwable());
-        }
         return calculateInsets(relativeFrame, mVisibleFrame != null ? mVisibleFrame : mFrame,
                 false /* ignoreVisibility */);
     }
diff --git a/core/java/android/view/InsetsState.java b/core/java/android/view/InsetsState.java
index eac7408..59e0932 100644
--- a/core/java/android/view/InsetsState.java
+++ b/core/java/android/view/InsetsState.java
@@ -45,7 +45,6 @@
 import android.graphics.Rect;
 import android.os.Parcel;
 import android.os.Parcelable;
-import android.util.Log;
 import android.util.SparseArray;
 import android.util.SparseIntArray;
 import android.util.proto.ProtoOutputStream;
@@ -381,9 +380,6 @@
             @InternalInsetsSide @Nullable SparseIntArray idSideMap,
             @Nullable boolean[] typeVisibilityMap, Insets insets, int type) {
         int index = indexOf(type);
-        if (source.getId() == InsetsSource.ID_IME) {
-            Log.i("b/297000797", "InsetsState#processSourceAsPublicType, ime insets: " + insets);
-        }
 
         // Don't put Insets.NONE into typeInsetsMap. Otherwise, two WindowInsets can be considered
         // as non-equal while they provide the same insets of each type from WindowInsets#getInsets
diff --git a/core/java/android/view/ViewTreeObserver.java b/core/java/android/view/ViewTreeObserver.java
index 9e8ca20..c9526fd 100644
--- a/core/java/android/view/ViewTreeObserver.java
+++ b/core/java/android/view/ViewTreeObserver.java
@@ -315,15 +315,6 @@
         }
 
         @Override
-        public String toString() {
-            return "InternalInsetsInfo{contentInsets=" + contentInsets
-                    + " visibleInsets=" + visibleInsets
-                    + " touchableRegion=" + touchableRegion.getBounds()
-                    + "}";
-
-        }
-
-        @Override
         public int hashCode() {
             int result = contentInsets.hashCode();
             result = 31 * result + visibleInsets.hashCode();
diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java
index f6b337c..59344b0 100644
--- a/core/java/android/widget/TextView.java
+++ b/core/java/android/widget/TextView.java
@@ -27,12 +27,14 @@
 import static android.view.accessibility.AccessibilityNodeInfo.EXTRA_DATA_TEXT_CHARACTER_LOCATION_ARG_START_INDEX;
 import static android.view.accessibility.AccessibilityNodeInfo.EXTRA_DATA_TEXT_CHARACTER_LOCATION_KEY;
 import static android.view.inputmethod.CursorAnchorInfo.FLAG_HAS_VISIBLE_REGION;
+import static com.android.text.flags.Flags.FLAG_USE_BOUNDS_FOR_WIDTH;
 
 import android.R;
 import android.annotation.CallSuper;
 import android.annotation.CheckResult;
 import android.annotation.ColorInt;
 import android.annotation.DrawableRes;
+import android.annotation.FlaggedApi;
 import android.annotation.FloatRange;
 import android.annotation.IntDef;
 import android.annotation.IntRange;
@@ -238,6 +240,7 @@
 import com.android.internal.util.ArrayUtils;
 import com.android.internal.util.FastMath;
 import com.android.internal.util.Preconditions;
+import com.android.text.flags.Flags;
 
 import libcore.util.EmptyArray;
 
@@ -523,6 +526,15 @@
     @EnabledSince(targetSdkVersion = Build.VERSION_CODES.P)
     public static final long STATICLAYOUT_FALLBACK_LINESPACING = 37756858; // buganizer id
 
+
+    /**
+     * This change ID enables the bounding box based layout.
+     * @hide
+     */
+    @ChangeId
+    @EnabledSince(targetSdkVersion = VERSION_CODES.VANILLA_ICE_CREAM)
+    public static final long USE_BOUNDS_FOR_WIDTH = 63938206;  // buganizer id
+
     // System wide time for last cut, copy or text changed action.
     static long sLastCutCopyOrTextChangedTime;
 
@@ -1621,7 +1633,11 @@
             mUseFallbackLineSpacing = FALLBACK_LINE_SPACING_NONE;
         }
 
-        mUseBoundsForWidth = false;  // TODO: Make enable this by default.
+        if (CompatChanges.isChangeEnabled(USE_BOUNDS_FOR_WIDTH)) {
+            mUseBoundsForWidth = Flags.useBoundsForWidth();
+        } else {
+            mUseBoundsForWidth = false;
+        }
 
         // TODO(b/179693024): Use a ChangeId instead.
         mUseTextPaddingForUiTranslation = targetSdkVersion <= Build.VERSION_CODES.R;
@@ -4848,6 +4864,7 @@
      *                          width.
      * @see #getUseBoundsForWidth()
      */
+    @FlaggedApi(FLAG_USE_BOUNDS_FOR_WIDTH)
     public void setUseBoundsForWidth(boolean useBoundsForWidth) {
         if (mUseBoundsForWidth != useBoundsForWidth) {
             mUseBoundsForWidth = useBoundsForWidth;
@@ -4865,6 +4882,7 @@
      * @see #setUseBoundsForWidth(boolean)
      * @return True if using bounding box for width, false if using advance for width.
      */
+    @FlaggedApi(FLAG_USE_BOUNDS_FOR_WIDTH)
     public boolean getUseBoundsForWidth() {
         return mUseBoundsForWidth;
     }
diff --git a/core/java/android/widget/ToastPresenter.java b/core/java/android/widget/ToastPresenter.java
index 89271b5..6884e63 100644
--- a/core/java/android/widget/ToastPresenter.java
+++ b/core/java/android/widget/ToastPresenter.java
@@ -89,9 +89,10 @@
         return view;
     }
 
+    private final WeakReference<Context> mContext;
     private final Resources mResources;
     private final WeakReference<WindowManager> mWindowManager;
-    private final WeakReference<AccessibilityManager> mAccessibilityManager;
+    private final IAccessibilityManager mAccessibilityManagerService;
     private final INotificationManager mNotificationManager;
     private final String mPackageName;
     private final String mContextPackageName;
@@ -101,21 +102,14 @@
 
     public ToastPresenter(Context context, IAccessibilityManager accessibilityManager,
             INotificationManager notificationManager, String packageName) {
+        mContext = new WeakReference<>(context);
         mResources = context.getResources();
         mWindowManager = new WeakReference<>(context.getSystemService(WindowManager.class));
         mNotificationManager = notificationManager;
         mPackageName = packageName;
         mContextPackageName = context.getPackageName();
         mParams = createLayoutParams();
-
-        // We obtain AccessibilityManager manually via its constructor instead of using method
-        // AccessibilityManager.getInstance() for 2 reasons:
-        //   1. We want to be able to inject IAccessibilityManager in tests to verify behavior.
-        //   2. getInstance() caches the instance for the process even if we pass a different
-        //      context to it. This is problematic for multi-user because callers can pass a context
-        //      created via Context.createContextAsUser().
-        mAccessibilityManager = new WeakReference<>(
-                new AccessibilityManager(context, accessibilityManager, context.getUserId()));
+        mAccessibilityManagerService = accessibilityManager;
     }
 
     public String getPackageName() {
@@ -306,11 +300,20 @@
      * enabled.
      */
     public void trySendAccessibilityEvent(View view, String packageName) {
-        final AccessibilityManager accessibilityManager = mAccessibilityManager.get();
-        if (accessibilityManager == null) {
+        final Context context = mContext.get();
+        if (context == null) {
             return;
         }
 
+        // We obtain AccessibilityManager manually via its constructor instead of using method
+        // AccessibilityManager.getInstance() for 2 reasons:
+        //   1. We want to be able to inject IAccessibilityManager in tests to verify behavior.
+        //   2. getInstance() caches the instance for the process even if we pass a different
+        //      context to it. This is problematic for multi-user because callers can pass a context
+        //      created via Context.createContextAsUser().
+        final AccessibilityManager accessibilityManager = new AccessibilityManager(context,
+                    mAccessibilityManagerService, context.getUserId());
+
         if (!accessibilityManager.isEnabled()) {
             accessibilityManager.removeClient();
             return;
diff --git a/core/java/android/window/flags/large_screen_experiences_app_compat.aconfig b/core/java/android/window/flags/large_screen_experiences_app_compat.aconfig
new file mode 100644
index 0000000..f1d981a
--- /dev/null
+++ b/core/java/android/window/flags/large_screen_experiences_app_compat.aconfig
@@ -0,0 +1,8 @@
+package: "com.android.window.flags"
+
+flag {
+  name: "letterbox_background_wallpaper_flag"
+  namespace: "large_screen_experiences_app_compat"
+  description: "Whether the letterbox wallpaper style is enabled by default"
+  bug: "297195682"
+}
diff --git a/core/java/com/android/internal/app/AbstractMultiProfilePagerAdapter.java b/core/java/com/android/internal/app/AbstractMultiProfilePagerAdapter.java
index 5e2eceb..dee4935 100644
--- a/core/java/com/android/internal/app/AbstractMultiProfilePagerAdapter.java
+++ b/core/java/com/android/internal/app/AbstractMultiProfilePagerAdapter.java
@@ -177,7 +177,7 @@
      * <code>1</code> would return the work profile {@link ProfileDescriptor}.</li>
      * </ul>
      */
-    abstract ProfileDescriptor getItem(int pageIndex);
+    public abstract ProfileDescriptor getItem(int pageIndex);
 
     /**
      * Returns the number of {@link ProfileDescriptor} objects.
@@ -438,8 +438,8 @@
                     && isQuietModeEnabled(mWorkProfileUserHandle));
     }
 
-    protected class ProfileDescriptor {
-        final ViewGroup rootView;
+    public static class ProfileDescriptor {
+        public final ViewGroup rootView;
         private final ViewGroup mEmptyStateView;
         ProfileDescriptor(ViewGroup rootView) {
             this.rootView = rootView;
diff --git a/core/java/com/android/internal/app/ChooserMultiProfilePagerAdapter.java b/core/java/com/android/internal/app/ChooserMultiProfilePagerAdapter.java
index 7beb059..8197e26 100644
--- a/core/java/com/android/internal/app/ChooserMultiProfilePagerAdapter.java
+++ b/core/java/com/android/internal/app/ChooserMultiProfilePagerAdapter.java
@@ -94,7 +94,7 @@
     }
 
     @Override
-    ChooserProfileDescriptor getItem(int pageIndex) {
+    public ChooserProfileDescriptor getItem(int pageIndex) {
         return mItems[pageIndex];
     }
 
diff --git a/core/java/com/android/internal/app/ResolverActivity.java b/core/java/com/android/internal/app/ResolverActivity.java
index ac15f11..7534d29 100644
--- a/core/java/com/android/internal/app/ResolverActivity.java
+++ b/core/java/com/android/internal/app/ResolverActivity.java
@@ -2623,13 +2623,13 @@
      * An a11y delegate that expands resolver drawer when gesture navigation reaches a partially
      * invisible target in the list.
      */
-    private static class AppListAccessibilityDelegate extends View.AccessibilityDelegate {
+    public static class AppListAccessibilityDelegate extends View.AccessibilityDelegate {
         private final ResolverDrawerLayout mDrawer;
         @Nullable
         private final View mBottomBar;
         private final Rect mRect = new Rect();
 
-        private AppListAccessibilityDelegate(ResolverDrawerLayout drawer) {
+        public AppListAccessibilityDelegate(ResolverDrawerLayout drawer) {
             mDrawer = drawer;
             mBottomBar = mDrawer.findViewById(R.id.button_bar_container);
         }
diff --git a/core/java/com/android/internal/app/ResolverMultiProfilePagerAdapter.java b/core/java/com/android/internal/app/ResolverMultiProfilePagerAdapter.java
index 7677912..031f9d3 100644
--- a/core/java/com/android/internal/app/ResolverMultiProfilePagerAdapter.java
+++ b/core/java/com/android/internal/app/ResolverMultiProfilePagerAdapter.java
@@ -79,7 +79,7 @@
     }
 
     @Override
-    ResolverProfileDescriptor getItem(int pageIndex) {
+    public ResolverProfileDescriptor getItem(int pageIndex) {
         return mItems[pageIndex];
     }
 
diff --git a/core/java/com/android/internal/jank/InteractionJankMonitor.java b/core/java/com/android/internal/jank/InteractionJankMonitor.java
index 1ed06b4..1bfb51c 100644
--- a/core/java/com/android/internal/jank/InteractionJankMonitor.java
+++ b/core/java/com/android/internal/jank/InteractionJankMonitor.java
@@ -70,7 +70,6 @@
 import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SHADE_APP_LAUNCH_FROM_SETTINGS_BUTTON;
 import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SHADE_CLEAR_ALL;
 import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SHADE_DIALOG_OPEN;
-import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SHADE_EXPAND_FROM_STATUS_BAR;
 import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SHADE_HEADS_UP_APPEAR;
 import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SHADE_HEADS_UP_DISAPPEAR;
 import static com.android.internal.util.FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SHADE_NOTIFICATION_ADD;
@@ -269,7 +268,6 @@
      * eg: Exit the app using back gesture.
      */
     public static final int CUJ_LAUNCHER_APP_CLOSE_TO_HOME_FALLBACK = 78;
-    public static final int CUJ_SHADE_EXPAND_FROM_STATUS_BAR = 79;
     public static final int CUJ_IME_INSETS_SHOW_ANIMATION = 80;
     public static final int CUJ_IME_INSETS_HIDE_ANIMATION = 81;
 
@@ -364,7 +362,7 @@
         CUJ_TO_STATSD_INTERACTION_TYPE[76] = NO_STATSD_LOGGING;
         CUJ_TO_STATSD_INTERACTION_TYPE[77] = NO_STATSD_LOGGING;
         CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_LAUNCHER_APP_CLOSE_TO_HOME_FALLBACK] = UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_APP_CLOSE_TO_HOME_FALLBACK;
-        CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_SHADE_EXPAND_FROM_STATUS_BAR] = UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SHADE_EXPAND_FROM_STATUS_BAR;
+        CUJ_TO_STATSD_INTERACTION_TYPE[79] = NO_STATSD_LOGGING; // This is deprecated.
         CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_IME_INSETS_SHOW_ANIMATION] = UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__IME_INSETS_SHOW_ANIMATION;
         CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_IME_INSETS_HIDE_ANIMATION] = UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__IME_INSETS_HIDE_ANIMATION;
         CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_SPLIT_SCREEN_DOUBLE_TAP_DIVIDER] = UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__SPLIT_SCREEN_DOUBLE_TAP_DIVIDER;
@@ -467,7 +465,6 @@
             CUJ_LOCKSCREEN_CLOCK_MOVE_ANIMATION,
             CUJ_LAUNCHER_OPEN_SEARCH_RESULT,
             CUJ_LAUNCHER_APP_CLOSE_TO_HOME_FALLBACK,
-            CUJ_SHADE_EXPAND_FROM_STATUS_BAR,
             CUJ_IME_INSETS_SHOW_ANIMATION,
             CUJ_IME_INSETS_HIDE_ANIMATION,
             CUJ_SPLIT_SCREEN_DOUBLE_TAP_DIVIDER,
@@ -1082,8 +1079,6 @@
                 return "LAUNCHER_OPEN_SEARCH_RESULT";
             case CUJ_LAUNCHER_APP_CLOSE_TO_HOME_FALLBACK:
                 return "LAUNCHER_APP_CLOSE_TO_HOME_FALLBACK";
-            case CUJ_SHADE_EXPAND_FROM_STATUS_BAR:
-                return "SHADE_EXPAND_FROM_STATUS_BAR";
             case CUJ_IME_INSETS_SHOW_ANIMATION:
                 return "IME_INSETS_SHOW_ANIMATION";
             case CUJ_IME_INSETS_HIDE_ANIMATION:
diff --git a/core/java/com/android/internal/policy/DecorView.java b/core/java/com/android/internal/policy/DecorView.java
index 9d66174..3e16df4d 100644
--- a/core/java/com/android/internal/policy/DecorView.java
+++ b/core/java/com/android/internal/policy/DecorView.java
@@ -1123,7 +1123,6 @@
                 final Insets systemInsets = clearsCompatInsets
                         ? Insets.NONE
                         : Insets.min(insets.getInsets(compatInsetsTypes), stableBarInsets);
-                Log.i("b/297000797", "DecorView#updateColorViews, systemInsets: " + systemInsets);
                 mLastTopInset = systemInsets.top;
                 mLastBottomInset = systemInsets.bottom;
                 mLastRightInset = systemInsets.right;
diff --git a/core/java/com/android/internal/policy/PhoneWindow.java b/core/java/com/android/internal/policy/PhoneWindow.java
index 9b9e010..9b5a3f7 100644
--- a/core/java/com/android/internal/policy/PhoneWindow.java
+++ b/core/java/com/android/internal/policy/PhoneWindow.java
@@ -333,6 +333,8 @@
     private long mBackgroundFadeDurationMillis = -1;
     private Boolean mSharedElementsUseOverlay;
 
+    private Boolean mAllowFloatingWindowsFillScreen;
+
     private boolean mIsStartingWindow;
     private int mTheme = -1;
 
@@ -361,6 +363,8 @@
         mRenderShadowsInCompositor = Settings.Global.getInt(context.getContentResolver(),
                 DEVELOPMENT_RENDER_SHADOWS_IN_COMPOSITOR, 1) != 0;
         mProxyOnBackInvokedDispatcher = new ProxyOnBackInvokedDispatcher(context);
+        mAllowFloatingWindowsFillScreen = context.getResources().getBoolean(
+                com.android.internal.R.bool.config_allowFloatingWindowsFillScreen);
     }
 
     /**
@@ -2422,7 +2426,7 @@
         mIsFloating = a.getBoolean(R.styleable.Window_windowIsFloating, false);
         int flagsToUpdate = (FLAG_LAYOUT_IN_SCREEN|FLAG_LAYOUT_INSET_DECOR)
                 & (~getForcedWindowFlags());
-        if (mIsFloating) {
+        if (mIsFloating && !mAllowFloatingWindowsFillScreen) {
             setLayout(WRAP_CONTENT, WRAP_CONTENT);
             setFlags(0, flagsToUpdate);
         } else {
diff --git a/core/jni/com_android_internal_os_Zygote.cpp b/core/jni/com_android_internal_os_Zygote.cpp
index c368fa8..56066b2 100644
--- a/core/jni/com_android_internal_os_Zygote.cpp
+++ b/core/jni/com_android_internal_os_Zygote.cpp
@@ -1806,15 +1806,10 @@
     if (!is_system_server && getuid() == 0) {
         const int rc = createProcessGroup(uid, getpid());
         if (rc != 0) {
-            if (rc == -ESRCH) {
-                // If process is dead, treat this as a non-fatal error
-                ALOGE("createProcessGroup(%d, %d) failed: %s", uid, /* pid= */ 0, strerror(-rc));
-            } else {
-                fail_fn(rc == -EROFS ? CREATE_ERROR("createProcessGroup failed, kernel missing "
-                                                    "CONFIG_CGROUP_CPUACCT?")
-                                     : CREATE_ERROR("createProcessGroup(%d, %d) failed: %s", uid,
-                                                    /* pid= */ 0, strerror(-rc)));
-            }
+            fail_fn(rc == -EROFS ? CREATE_ERROR("createProcessGroup failed, kernel missing "
+                                                "CONFIG_CGROUP_CPUACCT?")
+                                 : CREATE_ERROR("createProcessGroup(%d, %d) failed: %s", uid,
+                                                /* pid= */ 0, strerror(-rc)));
         }
     }
 
diff --git a/core/proto/android/providers/settings/secure.proto b/core/proto/android/providers/settings/secure.proto
index ad88092..6c93680 100644
--- a/core/proto/android/providers/settings/secure.proto
+++ b/core/proto/android/providers/settings/secure.proto
@@ -139,6 +139,7 @@
         optional SettingProto touch_gesture_enabled = 10 [ (android.privacy).dest = DEST_AUTOMATIC ];
         optional SettingProto long_press_home_enabled = 11 [ (android.privacy).dest = DEST_AUTOMATIC ];
         optional SettingProto search_press_hold_nav_handle_enabled = 12 [ (android.privacy).dest = DEST_AUTOMATIC ];
+        optional SettingProto search_long_press_home_enabled = 13 [ (android.privacy).dest = DEST_AUTOMATIC ];
     }
     optional Assist assist = 7;
 
diff --git a/core/res/res/values-watch/config.xml b/core/res/res/values-watch/config.xml
index b0d9b67..5319762 100644
--- a/core/res/res/values-watch/config.xml
+++ b/core/res/res/values-watch/config.xml
@@ -77,4 +77,8 @@
 
     <!-- Default hyphenation frequency setting (0=NONE, 1=NORMAL, 2=FULL). -->
     <item name="config_preferredHyphenationFrequency" format="integer" type="dimen">1</item>
+
+    <!-- Floating windows can be fullscreen (i.e. windowIsFloating can still have fullscreen
+         window that does not wrap content). -->
+    <bool name="config_allowFloatingWindowsFillScreen">true</bool>
 </resources>
diff --git a/core/res/res/values-watch/dimens_material.xml b/core/res/res/values-watch/dimens_material.xml
index 40673c1..2ab2d91 100644
--- a/core/res/res/values-watch/dimens_material.xml
+++ b/core/res/res/values-watch/dimens_material.xml
@@ -58,4 +58,7 @@
     <dimen name="screen_percentage_10">0dp</dimen>
     <dimen name="screen_percentage_12">0dp</dimen>
     <dimen name="screen_percentage_15">0dp</dimen>
+
+    <!-- dialog elevation [overrides 18dp] -->
+    <dimen name="floating_window_z">2dp</dimen>
   </resources>
diff --git a/core/res/res/values-watch/themes_device_defaults.xml b/core/res/res/values-watch/themes_device_defaults.xml
index c4c1ed9..df8158d 100644
--- a/core/res/res/values-watch/themes_device_defaults.xml
+++ b/core/res/res/values-watch/themes_device_defaults.xml
@@ -52,6 +52,7 @@
 
     <!-- Variant of {@link #Theme_DeviceDefault} with no action bar -->
     <style name="Theme.DeviceDefault.NoActionBar" parent="Theme.Material.NoActionBar">
+        <item name="android:windowActionBar">false</item>
         <!-- Color palette Dark -->
         <item name="colorPrimary">@color/primary_device_default_dark</item>
         <item name="colorPrimaryDark">@color/primary_dark_device_default_dark</item>
@@ -70,6 +71,8 @@
     <!-- Variant of {@link #Theme_DeviceDefault} with no action bar and no status bar.  This theme
          sets {@link android.R.attr#windowFullscreen} to true.  -->
     <style name="Theme.DeviceDefault.NoActionBar.Fullscreen" parent="Theme.Material.NoActionBar.Fullscreen">
+        <item name="android:windowFullscreen">true</item>
+        <item name="android:windowActionBar">false</item>
         <!-- Color palette Dark -->
         <item name="colorPrimary">@color/primary_device_default_dark</item>
         <item name="colorPrimaryDark">@color/primary_dark_device_default_dark</item>
@@ -90,6 +93,7 @@
     sets {@link android.R.attr#windowFullscreen} and {@link android.R.attr#windowOverscan}
     to true. -->
     <style name="Theme.DeviceDefault.NoActionBar.Overscan" parent="Theme.Material.NoActionBar.Overscan">
+        <item name="android:windowActionBar">false</item>
         <!-- Color palette Dark -->
         <item name="colorPrimary">@color/primary_device_default_dark</item>
         <item name="colorPrimaryDark">@color/primary_dark_device_default_dark</item>
@@ -149,8 +153,8 @@
     watch theme is not floating. You can set this theme on an activity if you would like to make
     an activity that looks like a Dialog.-->
     <style name="Theme.DeviceDefault.Dialog" parent="Theme.Material.Dialog" >
-        <item name="windowIsFloating">false</item>
-        <item name="windowElevation">0dp</item>
+        <item name="android:windowFullscreen">true</item>
+
         <item name="windowTitleStyle">@style/DialogWindowTitle.DeviceDefault</item>
         <item name="windowAnimationStyle">@style/Animation.DeviceDefault.Dialog</item>
 
@@ -175,6 +179,23 @@
         <item name="secondaryContentAlpha">@dimen/secondary_content_alpha_device_default</item>
     </style>
 
+    <style name="Theme.DeviceDefault.Dialog.Alert" parent="Theme.Material.Dialog.Alert">
+        <item name="android:windowFullscreen">true</item>
+        <!-- Color palette Dark -->
+        <item name="colorPrimary">@color/primary_device_default_dark</item>
+        <item name="colorPrimaryDark">@color/primary_dark_device_default_dark</item>
+        <item name="colorForeground">@color/foreground_device_default_dark</item>
+        <item name="colorAccent">@color/accent_device_default_dark</item>
+        <item name="colorBackground">@color/background_device_default_dark</item>
+        <item name="colorBackgroundFloating">@color/background_floating_device_default_dark</item>
+        <item name="colorBackgroundCacheHint">@color/background_cache_hint_selector_device_default</item>
+        <item name="colorButtonNormal">@color/button_normal_device_default_dark</item>
+        <item name="colorError">@color/error_color_device_default_dark</item>
+        <item name="disabledAlpha">@dimen/disabled_alpha_device_default</item>
+        <item name="primaryContentAlpha">@dimen/primary_content_alpha_device_default</item>
+        <item name="secondaryContentAlpha">@dimen/secondary_content_alpha_device_default</item>
+    </style>
+
     <!-- DeviceDefault theme for a window that should look like the Settings app.  -->
     <style name="Theme.DeviceDefault.Settings" parent="Theme.DeviceDefault"/>
     <style name="Theme.DeviceDefault.Settings.NoActionBar" parent="Theme.DeviceDefault"/>
@@ -185,9 +206,9 @@
     <style name="Theme.DeviceDefault.Settings.Dialog.Presentation" parent="Theme.DeviceDefault.Dialog.Presentation"/>
     <style name="Theme.DeviceDefault.Settings.SearchBar" parent="Theme.DeviceDefault.SearchBar"/>
 
-    <style name="Theme.DeviceDefault.Settings.Dialog.Alert" parent="Theme.Material.Dialog.Alert">
-        <item name="windowIsFloating">false</item>
-        <item name="windowElevation">0dp</item>
+    <style name="Theme.DeviceDefault.Settings.Dialog.Alert" parent="Theme.DeviceDefault.Dialog.Alert">
+        <item name="android:windowFullscreen">true</item>
+        <item name="windowTitleStyle">@style/DialogWindowTitle.DeviceDefault</item>
         <!-- Color palette Dark -->
         <item name="colorPrimary">@color/primary_device_default_dark</item>
         <item name="colorPrimaryDark">@color/primary_dark_device_default_dark</item>
@@ -204,8 +225,6 @@
     </style>
 
     <style name="Theme.DeviceDefault.Settings.CompactMenu" parent="Theme.Material.CompactMenu">
-        <item name="windowIsFloating">false</item>
-        <item name="windowElevation">0dp</item>
         <!-- Color palette Dark -->
         <item name="colorPrimary">@color/primary_device_default_dark</item>
         <item name="colorPrimaryDark">@color/primary_dark_device_default_dark</item>
@@ -224,8 +243,7 @@
     <!-- Variant of {@link #Theme_DeviceDefault_Dialog} that has a nice minimum width for a
     regular dialog. -->
     <style name="Theme.DeviceDefault.Dialog.MinWidth" parent="Theme.Material.Dialog.MinWidth">
-        <item name="windowIsFloating">false</item>
-        <item name="windowElevation">0dp</item>
+        <item name="android:windowFullscreen">true</item>
         <!-- Color palette Dark -->
         <item name="colorPrimary">@color/primary_device_default_dark</item>
         <item name="colorPrimaryDark">@color/primary_dark_device_default_dark</item>
@@ -243,8 +261,8 @@
 
     <!-- Variant of {@link #Theme_DeviceDefault_Dialog} without an action bar -->
     <style name="Theme.DeviceDefault.Dialog.NoActionBar" parent="Theme.Material.Dialog.NoActionBar">
-        <item name="windowIsFloating">false</item>
-        <item name="windowElevation">0dp</item>
+        <item name="android:windowFullscreen">true</item>
+        <item name="android:windowActionBar">false</item>
         <!-- Color palette Dark -->
         <item name="colorPrimary">@color/primary_device_default_dark</item>
         <item name="colorPrimaryDark">@color/primary_dark_device_default_dark</item>
@@ -263,8 +281,8 @@
     <!-- Variant of {@link #Theme_DeviceDefault_Dialog_NoActionBar} that has a nice minimum width
     for a regular dialog. -->
     <style name="Theme.DeviceDefault.Dialog.NoActionBar.MinWidth" parent="Theme.Material.Dialog.NoActionBar.MinWidth">
-        <item name="windowIsFloating">false</item>
-        <item name="windowElevation">0dp</item>
+        <item name="android:windowFullscreen">true</item>
+        <item name="android:windowActionBar">false</item>
         <!-- Color palette Dark -->
         <item name="colorPrimary">@color/primary_device_default_dark</item>
         <item name="colorPrimaryDark">@color/primary_dark_device_default_dark</item>
@@ -302,6 +320,8 @@
     full-screen on smaller screens (small, normal) or as a dialog on larger screens (large,
     xlarge). -->
     <style name="Theme.DeviceDefault.DialogWhenLarge.NoActionBar" parent="Theme.Material.DialogWhenLarge.NoActionBar">
+        <item name="android:windowFullscreen">true</item>
+        <item name="android:windowActionBar">false</item>
         <!-- Color palette Dark -->
         <item name="colorPrimary">@color/primary_device_default_dark</item>
         <item name="colorPrimaryDark">@color/primary_dark_device_default_dark</item>
@@ -319,6 +339,7 @@
 
     <!-- DeviceDefault theme for a presentation window on a secondary display. -->
     <style name="Theme.DeviceDefault.Dialog.Presentation" parent="Theme.Material.Dialog.Presentation">
+        <item name="android:windowFullscreen">true</item>
         <!-- Color palette Dark -->
         <item name="colorPrimary">@color/primary_device_default_dark</item>
         <item name="colorPrimaryDark">@color/primary_dark_device_default_dark</item>
@@ -338,6 +359,8 @@
     decorations, so you basically have an empty rectangle in which to place your content. It makes
     the window floating, with a transparent background, and turns off dimming behind the window. -->
     <style name="Theme.DeviceDefault.Panel" parent="Theme.Material.Panel">
+        <item name="android:windowFullscreen">true</item>
+        <item name="android:windowActionBar">false</item>
         <!-- Color palette Dark -->
         <item name="colorPrimary">@color/primary_device_default_dark</item>
         <item name="colorPrimaryDark">@color/primary_dark_device_default_dark</item>
@@ -407,24 +430,6 @@
         <item name="secondaryContentAlpha">@dimen/secondary_content_alpha_device_default</item>
     </style>
 
-    <style name="Theme.DeviceDefault.Dialog.Alert" parent="Theme.Material.Dialog.Alert">
-        <item name="windowTitleStyle">@style/DialogWindowTitle.DeviceDefault</item>
-
-        <!-- Color palette Dialog -->
-        <item name="colorPrimary">@color/primary_device_default_dark</item>
-        <item name="colorPrimaryDark">@color/primary_dark_device_default_dark</item>
-        <item name="colorForeground">@color/foreground_device_default_dark</item>
-        <item name="colorAccent">@color/accent_device_default_dark</item>
-        <item name="colorBackground">@color/background_device_default_dark</item>
-        <item name="colorBackgroundFloating">@color/background_floating_device_default_dark</item>
-        <item name="colorBackgroundCacheHint">@color/background_cache_hint_selector_device_default</item>
-        <item name="colorButtonNormal">@color/button_normal_device_default_dark</item>
-        <item name="colorError">@color/error_color_device_default_dark</item>
-        <item name="disabledAlpha">@dimen/disabled_alpha_device_default</item>
-        <item name="primaryContentAlpha">@dimen/primary_content_alpha_device_default</item>
-        <item name="secondaryContentAlpha">@dimen/secondary_content_alpha_device_default</item>
-    </style>
-
     <!-- Theme for the dialog shown when an app crashes or ANRs. Override to make it dark. -->
     <style name="Theme.DeviceDefault.Dialog.AppError" parent="Theme.DeviceDefault.Dialog.Alert">
         <item name="alertDialogStyle">@style/BaseErrorDialog.DeviceDefault</item>
@@ -451,8 +456,6 @@
     </style>
 
     <style name="Theme.DeviceDefault.Dialog.NoFrame" parent="Theme.Material.Dialog.NoFrame">
-        <item name="windowIsFloating">false</item>
-        <item name="windowElevation">0dp</item>
         <!-- Color palette Dialog -->
         <item name="colorPrimary">@color/primary_device_default_dark</item>
         <item name="colorPrimaryDark">@color/primary_dark_device_default_dark</item>
diff --git a/core/res/res/values-watch/themes_material.xml b/core/res/res/values-watch/themes_material.xml
index 40a249a..674b3bc 100644
--- a/core/res/res/values-watch/themes_material.xml
+++ b/core/res/res/values-watch/themes_material.xml
@@ -42,26 +42,31 @@
 
     <!-- Override behaviour to set the theme colours for dialogs, keep them the same. -->
     <style name="ThemeOverlay.Material.Dialog" parent="ThemeOverlay.Material.BaseDialog">
-        <item name="windowIsFloating">false</item>
-        <item name="windowElevation">0dp</item>
+        <item name="android:windowFullscreen">true</item>
     </style>
 
     <!-- Force the background and floating colours to be the default colours. -->
     <style name="Theme.Material.Dialog" parent="Theme.Material.BaseDialog">
+        <item name="android:windowFullscreen">true</item>
         <item name="colorBackground">@color/background_material_dark</item>
         <item name="colorBackgroundFloating">@color/background_floating_material_dark</item>
         <item name="colorBackgroundCacheHint">@color/background_cache_hint_selector_material_dark</item>
-        <item name="windowIsFloating">false</item>
-        <item name="windowElevation">0dp</item>
+    </style>
+
+    <!-- Force the background and floating colours to be the default colours. -->
+    <style name="Theme.Material.Dialog.Alert" parent="Theme.Material.Dialog.BaseAlert">
+        <item name="android:windowFullscreen">true</item>
+        <item name="colorBackground">@color/background_material_dark</item>
+        <item name="colorBackgroundFloating">@color/background_floating_material_dark</item>
+        <item name="colorBackgroundCacheHint">@color/background_cache_hint_selector_material_dark</item>
     </style>
 
     <!-- Force the background and floating colours to be the default colours. -->
     <style name="Theme.Material.Light.Dialog" parent="Theme.Material.Light.BaseDialog">
+        <item name="android:windowFullscreen">true</item>
         <item name="colorBackground">@color/background_material_light</item>
         <item name="colorBackgroundFloating">@color/background_floating_material_light</item>
         <item name="colorBackgroundCacheHint">@color/background_cache_hint_selector_material_light</item>
-        <item name="windowIsFloating">false</item>
-        <item name="windowElevation">0dp</item>
     </style>
 
     <!-- Force all settings themes to use normal Material theme. -->
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index e7764d8..d09bf44 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -6699,6 +6699,10 @@
     <!-- Whether unlocking and waking a device are sequenced -->
     <bool name="config_orderUnlockAndWake">false</bool>
 
+    <!-- Floating windows can be fullscreen (i.e. windowIsFloating can still have fullscreen
+     window that does not wrap content). -->
+    <bool name="config_allowFloatingWindowsFillScreen">false</bool>
+
     <!-- Whether scroll haptic feedback is enabled for rotary encoder scrolls on
          {@link MotionEvent#AXIS_SCROLL} generated by {@link InputDevice#SOURCE_ROTARY_ENCODER}
          devices. -->
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 80c2fbf..7f1a6f9 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -5226,6 +5226,9 @@
   <!-- Whether we order unlocking and waking -->
   <java-symbol type="bool" name="config_orderUnlockAndWake" />
 
+  <!-- Allow windowIsFloating to fill screen. -->
+  <java-symbol type="bool" name="config_allowFloatingWindowsFillScreen" />
+
   <!-- External TV Input Logging Configs -->
   <java-symbol type="bool" name="config_tvExternalInputLoggingDisplayNameFilterEnabled" />
   <java-symbol type="array" name="config_tvExternalInputLoggingDeviceOnScreenDisplayNames" />
diff --git a/core/tests/coretests/src/android/app/activity/ActivityThreadTest.java b/core/tests/coretests/src/android/app/activity/ActivityThreadTest.java
index 819178f..8935507 100644
--- a/core/tests/coretests/src/android/app/activity/ActivityThreadTest.java
+++ b/core/tests/coretests/src/android/app/activity/ActivityThreadTest.java
@@ -245,7 +245,7 @@
             newConfig.smallestScreenWidthDp++;
             transaction = newTransaction(activityThread, activity.getActivityToken());
             transaction.addCallback(ActivityConfigurationChangeItem.obtain(
-                    new Configuration(newConfig)));
+                    activity.getActivityToken(), new Configuration(newConfig)));
             appThread.scheduleTransaction(transaction);
             InstrumentationRegistry.getInstrumentation().waitForIdleSync();
 
@@ -450,10 +450,12 @@
         appThread.scheduleTransaction(transaction);
 
         transaction = newTransaction(activityThread, activity.getActivityToken());
-        transaction.addCallback(ActivityConfigurationChangeItem.obtain(activityConfigLandscape));
+        transaction.addCallback(ActivityConfigurationChangeItem.obtain(
+                activity.getActivityToken(), activityConfigLandscape));
         transaction.addCallback(ConfigurationChangeItem.obtain(
                 processConfigPortrait, DEVICE_ID_INVALID));
-        transaction.addCallback(ActivityConfigurationChangeItem.obtain(activityConfigPortrait));
+        transaction.addCallback(ActivityConfigurationChangeItem.obtain(
+                activity.getActivityToken(), activityConfigPortrait));
         appThread.scheduleTransaction(transaction);
 
         activity.mTestLatch.await(TIMEOUT_SEC, TimeUnit.SECONDS);
@@ -829,11 +831,12 @@
 
     private static ClientTransaction newRelaunchResumeTransaction(Activity activity) {
         final Configuration currentConfig = activity.getResources().getConfiguration();
-        final ClientTransactionItem callbackItem = ActivityRelaunchItem.obtain(null,
-                null, 0, new MergedConfiguration(currentConfig, currentConfig),
+        final ClientTransactionItem callbackItem = ActivityRelaunchItem.obtain(
+                activity.getActivityToken(), null, null, 0,
+                new MergedConfiguration(currentConfig, currentConfig),
                 false /* preserveWindow */);
         final ResumeActivityItem resumeStateRequest =
-                ResumeActivityItem.obtain(true /* isForward */,
+                ResumeActivityItem.obtain(activity.getActivityToken(), true /* isForward */,
                         false /* shouldSendCompatFakeFocus*/);
 
         final ClientTransaction transaction = newTransaction(activity);
@@ -845,7 +848,7 @@
 
     private static ClientTransaction newResumeTransaction(Activity activity) {
         final ResumeActivityItem resumeStateRequest =
-                ResumeActivityItem.obtain(true /* isForward */,
+                ResumeActivityItem.obtain(activity.getActivityToken(), true /* isForward */,
                         false /* shouldSendCompatFakeFocus */);
 
         final ClientTransaction transaction = newTransaction(activity);
@@ -855,7 +858,8 @@
     }
 
     private static ClientTransaction newStopTransaction(Activity activity) {
-        final StopActivityItem stopStateRequest = StopActivityItem.obtain(0 /* configChanges */);
+        final StopActivityItem stopStateRequest = StopActivityItem.obtain(
+                activity.getActivityToken(), 0 /* configChanges */);
 
         final ClientTransaction transaction = newTransaction(activity);
         transaction.setLifecycleStateRequest(stopStateRequest);
@@ -865,7 +869,8 @@
 
     private static ClientTransaction newActivityConfigTransaction(Activity activity,
             Configuration config) {
-        final ActivityConfigurationChangeItem item = ActivityConfigurationChangeItem.obtain(config);
+        final ActivityConfigurationChangeItem item = ActivityConfigurationChangeItem.obtain(
+                activity.getActivityToken(), config);
 
         final ClientTransaction transaction = newTransaction(activity);
         transaction.addCallback(item);
@@ -875,7 +880,8 @@
 
     private static ClientTransaction newNewIntentTransaction(Activity activity,
             List<ReferrerIntent> intents, boolean resume) {
-        final NewIntentItem item = NewIntentItem.obtain(intents, resume);
+        final NewIntentItem item = NewIntentItem.obtain(activity.getActivityToken(), intents,
+                resume);
 
         final ClientTransaction transaction = newTransaction(activity);
         transaction.addCallback(item);
diff --git a/core/tests/coretests/src/android/app/servertransaction/ActivityConfigurationChangeItemTest.java b/core/tests/coretests/src/android/app/servertransaction/ActivityConfigurationChangeItemTest.java
index 1560c0c..08033cc 100644
--- a/core/tests/coretests/src/android/app/servertransaction/ActivityConfigurationChangeItemTest.java
+++ b/core/tests/coretests/src/android/app/servertransaction/ActivityConfigurationChangeItemTest.java
@@ -65,7 +65,7 @@
         doReturn(mActivity).when(mHandler).getActivity(mToken);
 
         final ActivityConfigurationChangeItem item = ActivityConfigurationChangeItem
-                .obtain(mConfiguration);
+                .obtain(mToken, mConfiguration);
         final Context context = item.getContextToUpdate(mHandler, mToken);
 
         assertEquals(mActivity, context);
diff --git a/core/tests/coretests/src/android/app/servertransaction/ObjectPoolTests.java b/core/tests/coretests/src/android/app/servertransaction/ObjectPoolTests.java
index ca6735b..c8d8f4b 100644
--- a/core/tests/coretests/src/android/app/servertransaction/ObjectPoolTests.java
+++ b/core/tests/coretests/src/android/app/servertransaction/ObjectPoolTests.java
@@ -22,7 +22,6 @@
 import static android.app.servertransaction.TestUtils.resultInfoList;
 
 import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotEquals;
 import static org.junit.Assert.assertNotSame;
 import static org.junit.Assert.assertSame;
@@ -61,39 +60,44 @@
 @Presubmit
 public class ObjectPoolTests {
 
+    private final IBinder mActivityToken = new Binder();
+
     // 1. Check if two obtained objects from pool are not the same.
     // 2. Check if the state of the object is cleared after recycling.
     // 3. Check if the same object is obtained from pool after recycling.
 
     @Test
     public void testRecycleActivityConfigurationChangeItem() {
-        ActivityConfigurationChangeItem emptyItem =
-                ActivityConfigurationChangeItem.obtain(Configuration.EMPTY);
-        ActivityConfigurationChangeItem item = ActivityConfigurationChangeItem.obtain(config());
+        ActivityConfigurationChangeItem emptyItem = ActivityConfigurationChangeItem.obtain(
+                null /* activityToken */, Configuration.EMPTY);
+        ActivityConfigurationChangeItem item = ActivityConfigurationChangeItem.obtain(
+                mActivityToken, config());
         assertNotSame(item, emptyItem);
-        assertFalse(item.equals(emptyItem));
+        assertNotEquals(item, emptyItem);
 
         item.recycle();
         assertEquals(item, emptyItem);
 
-        ActivityConfigurationChangeItem item2 = ActivityConfigurationChangeItem.obtain(config());
+        ActivityConfigurationChangeItem item2 = ActivityConfigurationChangeItem.obtain(
+                mActivityToken, config());
         assertSame(item, item2);
-        assertFalse(item2.equals(emptyItem));
+        assertNotEquals(item2, emptyItem);
     }
 
     @Test
     public void testRecycleActivityResultItem() {
-        ActivityResultItem emptyItem = ActivityResultItem.obtain(null);
-        ActivityResultItem item = ActivityResultItem.obtain(resultInfoList());
+        ActivityResultItem emptyItem = ActivityResultItem.obtain(
+                null /* activityToken */, null /* resultInfoList */);
+        ActivityResultItem item = ActivityResultItem.obtain(mActivityToken, resultInfoList());
         assertNotSame(item, emptyItem);
-        assertFalse(item.equals(emptyItem));
+        assertNotEquals(item, emptyItem);
 
         item.recycle();
         assertEquals(item, emptyItem);
 
-        ActivityResultItem item2 = ActivityResultItem.obtain(resultInfoList());
+        ActivityResultItem item2 = ActivityResultItem.obtain(mActivityToken, resultInfoList());
         assertSame(item, item2);
-        assertFalse(item2.equals(emptyItem));
+        assertNotEquals(item2, emptyItem);
     }
 
     @Test
@@ -101,166 +105,188 @@
         ConfigurationChangeItem emptyItem = ConfigurationChangeItem.obtain(null, 0);
         ConfigurationChangeItem item = ConfigurationChangeItem.obtain(config(), 1);
         assertNotSame(item, emptyItem);
-        assertFalse(item.equals(emptyItem));
+        assertNotEquals(item, emptyItem);
 
         item.recycle();
         assertEquals(item, emptyItem);
 
         ConfigurationChangeItem item2 = ConfigurationChangeItem.obtain(config(), 1);
         assertSame(item, item2);
-        assertFalse(item2.equals(emptyItem));
+        assertNotEquals(item2, emptyItem);
     }
 
     @Test
     public void testRecycleDestroyActivityItem() {
-        DestroyActivityItem emptyItem = DestroyActivityItem.obtain(false, 0);
-        DestroyActivityItem item = DestroyActivityItem.obtain(true, 117);
-        assertNotSame(item, emptyItem);
-        assertFalse(item.equals(emptyItem));
-
-        item.recycle();
-        assertEquals(item, emptyItem);
-
-        DestroyActivityItem item2 = DestroyActivityItem.obtain(true, 14);
-        assertSame(item, item2);
-        assertFalse(item2.equals(emptyItem));
-    }
-
-    @Test
-    public void testRecycleLaunchActivityItem() {
-        Intent intent = new Intent("action");
-        int ident = 57;
-        ActivityInfo activityInfo = new ActivityInfo();
-        activityInfo.flags = 42;
-        activityInfo.setMaxAspectRatio(2.4f);
-        activityInfo.launchToken = "token";
-        activityInfo.applicationInfo = new ApplicationInfo();
-        activityInfo.packageName = "packageName";
-        activityInfo.name = "name";
-        Configuration overrideConfig = new Configuration();
-        overrideConfig.assetsSeq = 5;
-        String referrer = "referrer";
-        int procState = 4;
-        Bundle bundle = new Bundle();
-        bundle.putString("key", "value");
-        PersistableBundle persistableBundle = new PersistableBundle();
-        persistableBundle.putInt("k", 4);
-        IBinder assistToken = new Binder();
-        IBinder shareableActivityToken = new Binder();
-        int deviceId = 3;
-
-        Supplier<LaunchActivityItem> itemSupplier = () -> new LaunchActivityItemBuilder()
-                .setIntent(intent).setIdent(ident).setInfo(activityInfo).setCurConfig(config())
-                .setOverrideConfig(overrideConfig).setReferrer(referrer)
-                .setProcState(procState).setState(bundle).setPersistentState(persistableBundle)
-                .setPendingResults(resultInfoList()).setPendingNewIntents(referrerIntentList())
-                .setIsForward(true).setAssistToken(assistToken)
-                .setShareableActivityToken(shareableActivityToken)
-                .setTaskFragmentToken(new Binder()).setDeviceId(deviceId).build();
-
-        LaunchActivityItem emptyItem = new LaunchActivityItemBuilder().build();
-        LaunchActivityItem item = itemSupplier.get();
-        assertNotSame(item, emptyItem);
-        assertFalse(item.equals(emptyItem));
-
-        item.recycle();
-        assertEquals(item, emptyItem);
-
-        LaunchActivityItem item2 = itemSupplier.get();
-        assertSame(item, item2);
-        assertFalse(item2.equals(emptyItem));
-    }
-
-    @Test
-    public void testRecycleActivityRelaunchItem() {
-        ActivityRelaunchItem emptyItem = ActivityRelaunchItem.obtain(null, null, 0, null, false);
-        Configuration overrideConfig = new Configuration();
-        overrideConfig.assetsSeq = 5;
-        ActivityRelaunchItem item = ActivityRelaunchItem.obtain(resultInfoList(),
-                referrerIntentList(), 42, mergedConfig(), true);
-        assertNotSame(item, emptyItem);
-        assertFalse(item.equals(emptyItem));
-
-        item.recycle();
-        assertEquals(item, emptyItem);
-
-        ActivityRelaunchItem item2 = ActivityRelaunchItem.obtain(resultInfoList(),
-                referrerIntentList(), 42, mergedConfig(), true);
-        assertSame(item, item2);
-        assertFalse(item2.equals(emptyItem));
-    }
-
-    @Test
-    public void testRecycleMoveToDisplayItem() {
-        MoveToDisplayItem emptyItem = MoveToDisplayItem.obtain(0, Configuration.EMPTY);
-        MoveToDisplayItem item = MoveToDisplayItem.obtain(4, config());
-        assertNotSame(item, emptyItem);
-        assertFalse(item.equals(emptyItem));
-
-        item.recycle();
-        assertEquals(item, emptyItem);
-
-        MoveToDisplayItem item2 = MoveToDisplayItem.obtain(3, config());
-        assertSame(item, item2);
-        assertFalse(item2.equals(emptyItem));
-    }
-
-    @Test
-    public void testRecycleNewIntentItem() {
-        NewIntentItem emptyItem = NewIntentItem.obtain(null, false);
-        NewIntentItem item = NewIntentItem.obtain(referrerIntentList(), false);
-        assertNotSame(item, emptyItem);
-        assertFalse(item.equals(emptyItem));
-
-        item.recycle();
-        assertEquals(item, emptyItem);
-
-        NewIntentItem item2 = NewIntentItem.obtain(referrerIntentList(), false);
-        assertSame(item, item2);
-        assertFalse(item2.equals(emptyItem));
-    }
-
-    @Test
-    public void testRecyclePauseActivityItemItem() {
-        PauseActivityItem emptyItem = PauseActivityItem.obtain(false, false, 0, false, false);
-        PauseActivityItem item = PauseActivityItem.obtain(true, true, 5, true, true);
-        assertNotSame(item, emptyItem);
-        assertFalse(item.equals(emptyItem));
-
-        item.recycle();
-        assertEquals(item, emptyItem);
-
-        PauseActivityItem item2 = PauseActivityItem.obtain(true, false, 5, true, true);
-        assertSame(item, item2);
-        assertFalse(item2.equals(emptyItem));
-    }
-
-    @Test
-    public void testRecycleResumeActivityItem() {
-        ResumeActivityItem emptyItem = ResumeActivityItem.obtain(false, false);
-        ResumeActivityItem item = ResumeActivityItem.obtain(3, true, false);
-        assertNotSame(item, emptyItem);
-        assertFalse(item.equals(emptyItem));
-
-        item.recycle();
-        assertEquals(item, emptyItem);
-
-        ResumeActivityItem item2 = ResumeActivityItem.obtain(2, true, false);
-        assertSame(item, item2);
-        assertFalse(item2.equals(emptyItem));
-    }
-
-    @Test
-    public void testRecycleStartActivityItem() {
-        StartActivityItem emptyItem = StartActivityItem.obtain(null /* activityOptions */);
-        StartActivityItem item = StartActivityItem.obtain(ActivityOptions.makeBasic());
+        DestroyActivityItem emptyItem = DestroyActivityItem.obtain(
+                null /* activityToken */, false, 0);
+        DestroyActivityItem item = DestroyActivityItem.obtain(mActivityToken, true, 117);
         assertNotSame(item, emptyItem);
         assertNotEquals(item, emptyItem);
 
         item.recycle();
         assertEquals(item, emptyItem);
 
-        StartActivityItem item2 = StartActivityItem.obtain(
+        DestroyActivityItem item2 = DestroyActivityItem.obtain(mActivityToken, true, 14);
+        assertSame(item, item2);
+        assertNotEquals(item2, emptyItem);
+    }
+
+    @Test
+    public void testRecycleLaunchActivityItem() {
+        final IBinder activityToken = new Binder();
+        final Intent intent = new Intent("action");
+        int ident = 57;
+        final ActivityInfo activityInfo = new ActivityInfo();
+        activityInfo.flags = 42;
+        activityInfo.setMaxAspectRatio(2.4f);
+        activityInfo.launchToken = "token";
+        activityInfo.applicationInfo = new ApplicationInfo();
+        activityInfo.packageName = "packageName";
+        activityInfo.name = "name";
+        final Configuration overrideConfig = new Configuration();
+        overrideConfig.assetsSeq = 5;
+        final String referrer = "referrer";
+        int procState = 4;
+        final Bundle bundle = new Bundle();
+        bundle.putString("key", "value");
+        final PersistableBundle persistableBundle = new PersistableBundle();
+        persistableBundle.putInt("k", 4);
+        final IBinder assistToken = new Binder();
+        final IBinder shareableActivityToken = new Binder();
+        int deviceId = 3;
+
+        final Supplier<LaunchActivityItem> itemSupplier = () -> new LaunchActivityItemBuilder()
+                .setActivityToken(activityToken)
+                .setIntent(intent)
+                .setIdent(ident)
+                .setInfo(activityInfo)
+                .setCurConfig(config())
+                .setOverrideConfig(overrideConfig)
+                .setReferrer(referrer)
+                .setProcState(procState)
+                .setState(bundle)
+                .setPersistentState(persistableBundle)
+                .setPendingResults(resultInfoList())
+                .setPendingNewIntents(referrerIntentList())
+                .setIsForward(true)
+                .setAssistToken(assistToken)
+                .setShareableActivityToken(shareableActivityToken)
+                .setTaskFragmentToken(new Binder())
+                .setDeviceId(deviceId)
+                .build();
+
+        LaunchActivityItem emptyItem = new LaunchActivityItemBuilder().build();
+        LaunchActivityItem item = itemSupplier.get();
+        assertNotSame(item, emptyItem);
+        assertNotEquals(item, emptyItem);
+
+        item.recycle();
+        assertEquals(item, emptyItem);
+
+        LaunchActivityItem item2 = itemSupplier.get();
+        assertSame(item, item2);
+        assertNotEquals(item2, emptyItem);
+    }
+
+    @Test
+    public void testRecycleActivityRelaunchItem() {
+        ActivityRelaunchItem emptyItem = ActivityRelaunchItem.obtain(
+                null /* activityToken */, null, null, 0, null, false);
+        Configuration overrideConfig = new Configuration();
+        overrideConfig.assetsSeq = 5;
+        ActivityRelaunchItem item = ActivityRelaunchItem.obtain(mActivityToken, resultInfoList(),
+                referrerIntentList(), 42, mergedConfig(), true);
+        assertNotSame(item, emptyItem);
+        assertNotEquals(item, emptyItem);
+
+        item.recycle();
+        assertEquals(item, emptyItem);
+
+        ActivityRelaunchItem item2 = ActivityRelaunchItem.obtain(mActivityToken, resultInfoList(),
+                referrerIntentList(), 42, mergedConfig(), true);
+        assertSame(item, item2);
+        assertNotEquals(item2, emptyItem);
+    }
+
+    @Test
+    public void testRecycleMoveToDisplayItem() {
+        MoveToDisplayItem emptyItem = MoveToDisplayItem.obtain(
+                null /* activityToken */, 0, Configuration.EMPTY);
+        MoveToDisplayItem item = MoveToDisplayItem.obtain(mActivityToken, 4, config());
+        assertNotSame(item, emptyItem);
+        assertNotEquals(item, emptyItem);
+
+        item.recycle();
+        assertEquals(item, emptyItem);
+
+        MoveToDisplayItem item2 = MoveToDisplayItem.obtain(mActivityToken, 3, config());
+        assertSame(item, item2);
+        assertNotEquals(item2, emptyItem);
+    }
+
+    @Test
+    public void testRecycleNewIntentItem() {
+        NewIntentItem emptyItem = NewIntentItem.obtain(
+                null /* activityToken */, null /* intents */, false /* resume */);
+        NewIntentItem item = NewIntentItem.obtain(mActivityToken, referrerIntentList(), false);
+        assertNotSame(item, emptyItem);
+        assertNotEquals(item, emptyItem);
+
+        item.recycle();
+        assertEquals(item, emptyItem);
+
+        NewIntentItem item2 = NewIntentItem.obtain(mActivityToken, referrerIntentList(), false);
+        assertSame(item, item2);
+        assertNotEquals(item2, emptyItem);
+    }
+
+    @Test
+    public void testRecyclePauseActivityItemItem() {
+        PauseActivityItem emptyItem = PauseActivityItem.obtain(
+                null /* activityToken */, false, false, 0, false, false);
+        PauseActivityItem item = PauseActivityItem.obtain(
+                mActivityToken, true, true, 5, true, true);
+        assertNotSame(item, emptyItem);
+        assertNotEquals(item, emptyItem);
+
+        item.recycle();
+        assertEquals(item, emptyItem);
+
+        PauseActivityItem item2 = PauseActivityItem.obtain(
+                mActivityToken, true, false, 5, true, true);
+        assertSame(item, item2);
+        assertNotEquals(item2, emptyItem);
+    }
+
+    @Test
+    public void testRecycleResumeActivityItem() {
+        ResumeActivityItem emptyItem = ResumeActivityItem.obtain(
+                null /* activityToken */, false, false);
+        ResumeActivityItem item = ResumeActivityItem.obtain(mActivityToken, 3, true, false);
+        assertNotSame(item, emptyItem);
+        assertNotEquals(item, emptyItem);
+
+        item.recycle();
+        assertEquals(item, emptyItem);
+
+        ResumeActivityItem item2 = ResumeActivityItem.obtain(mActivityToken, 2, true, false);
+        assertSame(item, item2);
+        assertNotEquals(item2, emptyItem);
+    }
+
+    @Test
+    public void testRecycleStartActivityItem() {
+        StartActivityItem emptyItem = StartActivityItem.obtain(
+                null /* activityToken */, null /* activityOptions */);
+        StartActivityItem item = StartActivityItem.obtain(mActivityToken,
+                ActivityOptions.makeBasic());
+        assertNotSame(item, emptyItem);
+        assertNotEquals(item, emptyItem);
+
+        item.recycle();
+        assertEquals(item, emptyItem);
+
+        StartActivityItem item2 = StartActivityItem.obtain(mActivityToken,
                 ActivityOptions.makeBasic().setLaunchDisplayId(10));
         assertSame(item, item2);
         assertNotEquals(item2, emptyItem);
@@ -268,17 +294,17 @@
 
     @Test
     public void testRecycleStopItem() {
-        StopActivityItem emptyItem = StopActivityItem.obtain(0);
-        StopActivityItem item = StopActivityItem.obtain(4);
+        StopActivityItem emptyItem = StopActivityItem.obtain(null /* activityToken */, 0);
+        StopActivityItem item = StopActivityItem.obtain(mActivityToken, 4);
         assertNotSame(item, emptyItem);
-        assertFalse(item.equals(emptyItem));
+        assertNotEquals(item, emptyItem);
 
         item.recycle();
         assertEquals(item, emptyItem);
 
-        StopActivityItem item2 = StopActivityItem.obtain(3);
+        StopActivityItem item2 = StopActivityItem.obtain(mActivityToken, 3);
         assertSame(item, item2);
-        assertFalse(item2.equals(emptyItem));
+        assertNotEquals(item2, emptyItem);
     }
 
     @Test
@@ -286,13 +312,13 @@
         ClientTransaction emptyItem = ClientTransaction.obtain(null, null);
         ClientTransaction item = ClientTransaction.obtain(null, new Binder());
         assertNotSame(item, emptyItem);
-        assertFalse(item.equals(emptyItem));
+        assertNotEquals(item, emptyItem);
 
         item.recycle();
         assertEquals(item, emptyItem);
 
         ClientTransaction item2 = ClientTransaction.obtain(null, new Binder());
         assertSame(item, item2);
-        assertFalse(item2.equals(emptyItem));
+        assertNotEquals(item2, emptyItem);
     }
 }
diff --git a/core/tests/coretests/src/android/app/servertransaction/TestUtils.java b/core/tests/coretests/src/android/app/servertransaction/TestUtils.java
index 0ed6a29..5a88bad 100644
--- a/core/tests/coretests/src/android/app/servertransaction/TestUtils.java
+++ b/core/tests/coretests/src/android/app/servertransaction/TestUtils.java
@@ -18,6 +18,8 @@
 
 import static android.content.res.Configuration.ORIENTATION_LANDSCAPE;
 
+import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.app.ActivityOptions;
 import android.app.ProfilerInfo;
 import android.app.ResultInfo;
@@ -90,129 +92,175 @@
     }
 
     static class LaunchActivityItemBuilder {
+        @Nullable
+        private IBinder mActivityToken;
+        @Nullable
         private Intent mIntent;
         private int mIdent;
+        @Nullable
         private ActivityInfo mInfo;
+        @Nullable
         private Configuration mCurConfig;
+        @Nullable
         private Configuration mOverrideConfig;
         private int mDeviceId;
+        @Nullable
         private String mReferrer;
+        @Nullable
         private IVoiceInteractor mVoiceInteractor;
         private int mProcState;
+        @Nullable
         private Bundle mState;
+        @Nullable
         private PersistableBundle mPersistentState;
+        @Nullable
         private List<ResultInfo> mPendingResults;
+        @Nullable
         private List<ReferrerIntent> mPendingNewIntents;
+        @Nullable
         private ActivityOptions mActivityOptions;
         private boolean mIsForward;
+        @Nullable
         private ProfilerInfo mProfilerInfo;
+        @Nullable
         private IBinder mAssistToken;
+        @Nullable
         private IBinder mShareableActivityToken;
         private boolean mLaunchedFromBubble;
+        @Nullable
         private IBinder mTaskFragmentToken;
 
-        LaunchActivityItemBuilder setIntent(Intent intent) {
+        @NonNull
+        LaunchActivityItemBuilder setActivityToken(@Nullable IBinder activityToken) {
+            mActivityToken = activityToken;
+            return this;
+        }
+
+        @NonNull
+        LaunchActivityItemBuilder setIntent(@Nullable Intent intent) {
             mIntent = intent;
             return this;
         }
 
+        @NonNull
         LaunchActivityItemBuilder setIdent(int ident) {
             mIdent = ident;
             return this;
         }
 
-        LaunchActivityItemBuilder setInfo(ActivityInfo info) {
+        @NonNull
+        LaunchActivityItemBuilder setInfo(@Nullable ActivityInfo info) {
             mInfo = info;
             return this;
         }
 
-        LaunchActivityItemBuilder setCurConfig(Configuration curConfig) {
+        @NonNull
+        LaunchActivityItemBuilder setCurConfig(@Nullable Configuration curConfig) {
             mCurConfig = curConfig;
             return this;
         }
 
-        LaunchActivityItemBuilder setOverrideConfig(Configuration overrideConfig) {
+        @NonNull
+        LaunchActivityItemBuilder setOverrideConfig(@Nullable Configuration overrideConfig) {
             mOverrideConfig = overrideConfig;
             return this;
         }
 
+        @NonNull
         LaunchActivityItemBuilder setDeviceId(int deviceId) {
             mDeviceId = deviceId;
             return this;
         }
 
-        LaunchActivityItemBuilder setReferrer(String referrer) {
+        @NonNull
+        LaunchActivityItemBuilder setReferrer(@Nullable String referrer) {
             mReferrer = referrer;
             return this;
         }
 
-        LaunchActivityItemBuilder setVoiceInteractor(IVoiceInteractor voiceInteractor) {
+        @NonNull
+        LaunchActivityItemBuilder setVoiceInteractor(@Nullable IVoiceInteractor voiceInteractor) {
             mVoiceInteractor = voiceInteractor;
             return this;
         }
 
+        @NonNull
         LaunchActivityItemBuilder setProcState(int procState) {
             mProcState = procState;
             return this;
         }
 
-        LaunchActivityItemBuilder setState(Bundle state) {
+        @NonNull
+        LaunchActivityItemBuilder setState(@Nullable Bundle state) {
             mState = state;
             return this;
         }
 
-        LaunchActivityItemBuilder setPersistentState(PersistableBundle persistentState) {
+        @NonNull
+        LaunchActivityItemBuilder setPersistentState(@Nullable PersistableBundle persistentState) {
             mPersistentState = persistentState;
             return this;
         }
 
-        LaunchActivityItemBuilder setPendingResults(List<ResultInfo> pendingResults) {
+        @NonNull
+        LaunchActivityItemBuilder setPendingResults(@Nullable List<ResultInfo> pendingResults) {
             mPendingResults = pendingResults;
             return this;
         }
 
-        LaunchActivityItemBuilder setPendingNewIntents(List<ReferrerIntent> pendingNewIntents) {
+        @NonNull
+        LaunchActivityItemBuilder setPendingNewIntents(
+                @Nullable List<ReferrerIntent> pendingNewIntents) {
             mPendingNewIntents = pendingNewIntents;
             return this;
         }
 
-        LaunchActivityItemBuilder setActivityOptions(ActivityOptions activityOptions) {
+        @NonNull
+        LaunchActivityItemBuilder setActivityOptions(@Nullable ActivityOptions activityOptions) {
             mActivityOptions = activityOptions;
             return this;
         }
 
+        @NonNull
         LaunchActivityItemBuilder setIsForward(boolean isForward) {
             mIsForward = isForward;
             return this;
         }
 
-        LaunchActivityItemBuilder setProfilerInfo(ProfilerInfo profilerInfo) {
+        @NonNull
+        LaunchActivityItemBuilder setProfilerInfo(@Nullable ProfilerInfo profilerInfo) {
             mProfilerInfo = profilerInfo;
             return this;
         }
 
-        LaunchActivityItemBuilder setAssistToken(IBinder assistToken) {
+        @NonNull
+        LaunchActivityItemBuilder setAssistToken(@Nullable IBinder assistToken) {
             mAssistToken = assistToken;
             return this;
         }
 
-        LaunchActivityItemBuilder setShareableActivityToken(IBinder shareableActivityToken) {
+        @NonNull
+        LaunchActivityItemBuilder setShareableActivityToken(
+                @Nullable IBinder shareableActivityToken) {
             mShareableActivityToken = shareableActivityToken;
             return this;
         }
 
+        @NonNull
         LaunchActivityItemBuilder setLaunchedFromBubble(boolean launchedFromBubble) {
             mLaunchedFromBubble = launchedFromBubble;
             return this;
         }
 
-        LaunchActivityItemBuilder setTaskFragmentToken(IBinder taskFragmentToken) {
+        @NonNull
+        LaunchActivityItemBuilder setTaskFragmentToken(@Nullable IBinder taskFragmentToken) {
             mTaskFragmentToken = taskFragmentToken;
             return this;
         }
 
+        @NonNull
         LaunchActivityItem build() {
-            return LaunchActivityItem.obtain(mIntent, mIdent, mInfo,
+            return LaunchActivityItem.obtain(mActivityToken, mIntent, mIdent, mInfo,
                     mCurConfig, mOverrideConfig, mDeviceId, mReferrer, mVoiceInteractor,
                     mProcState, mState, mPersistentState, mPendingResults, mPendingNewIntents,
                     mActivityOptions, mIsForward, mProfilerInfo, mAssistToken,
diff --git a/core/tests/coretests/src/android/app/servertransaction/TransactionExecutorTests.java b/core/tests/coretests/src/android/app/servertransaction/TransactionExecutorTests.java
index 32f8929..a998b26 100644
--- a/core/tests/coretests/src/android/app/servertransaction/TransactionExecutorTests.java
+++ b/core/tests/coretests/src/android/app/servertransaction/TransactionExecutorTests.java
@@ -227,6 +227,7 @@
         when(callback2.getPostExecutionState()).thenReturn(UNDEFINED);
         ActivityLifecycleItem stateRequest = mock(ActivityLifecycleItem.class);
         IBinder token = mock(IBinder.class);
+        when(stateRequest.getActivityToken()).thenReturn(token);
         when(mTransactionHandler.getActivity(token)).thenReturn(mock(Activity.class));
 
         ClientTransaction transaction = ClientTransaction.obtain(null /* client */,
@@ -256,7 +257,7 @@
         final ClientTransaction destroyTransaction = ClientTransaction.obtain(null /* client */,
                 token /* activityToken */);
         destroyTransaction.setLifecycleStateRequest(
-                DestroyActivityItem.obtain(false /* finished */, 0 /* configChanges */));
+                DestroyActivityItem.obtain(token, false /* finished */, 0 /* configChanges */));
         destroyTransaction.preExecute(mTransactionHandler);
         // The activity should be added to to-be-destroyed container.
         assertEquals(1, mTransactionHandler.getActivitiesToBeDestroyed().size());
@@ -264,7 +265,8 @@
         // A previous queued launch transaction runs on main thread (execute).
         final ClientTransaction launchTransaction = ClientTransaction.obtain(null /* client */,
                 token /* activityToken */);
-        final LaunchActivityItem launchItem = spy(new LaunchActivityItemBuilder().build());
+        final LaunchActivityItem launchItem =
+                spy(new LaunchActivityItemBuilder().setActivityToken(token).build());
         launchTransaction.addCallback(launchItem);
         mExecutor.execute(launchTransaction);
 
@@ -450,9 +452,11 @@
         final ClientTransaction transaction = ClientTransaction.obtain(null /* client */, token);
         final ActivityTransactionItem activityItem = mock(ActivityTransactionItem.class);
         when(activityItem.getPostExecutionState()).thenReturn(UNDEFINED);
+        when(activityItem.getActivityToken()).thenReturn(token);
         transaction.addCallback(activityItem);
         final ActivityLifecycleItem stateRequest = mock(ActivityLifecycleItem.class);
         transaction.setLifecycleStateRequest(stateRequest);
+        when(stateRequest.getActivityToken()).thenReturn(token);
         when(mTransactionHandler.getActivity(token)).thenReturn(mock(Activity.class));
 
         mExecutor.execute(transaction);
diff --git a/core/tests/coretests/src/android/app/servertransaction/TransactionParcelTests.java b/core/tests/coretests/src/android/app/servertransaction/TransactionParcelTests.java
index 48a8249..abc5d6b 100644
--- a/core/tests/coretests/src/android/app/servertransaction/TransactionParcelTests.java
+++ b/core/tests/coretests/src/android/app/servertransaction/TransactionParcelTests.java
@@ -22,7 +22,6 @@
 import static android.app.servertransaction.TestUtils.resultInfoList;
 
 import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertTrue;
 
 import android.app.ActivityOptions;
 import android.app.servertransaction.TestUtils.LaunchActivityItemBuilder;
@@ -32,6 +31,7 @@
 import android.content.res.Configuration;
 import android.os.Binder;
 import android.os.Bundle;
+import android.os.IBinder;
 import android.os.Parcel;
 import android.os.Parcelable;
 import android.os.PersistableBundle;
@@ -61,10 +61,12 @@
 public class TransactionParcelTests {
 
     private Parcel mParcel;
+    private IBinder mActivityToken;
 
     @Before
     public void setUp() throws Exception {
         mParcel = Parcel.obtain();
+        mActivityToken = new Binder();
     }
 
     @Test
@@ -77,13 +79,14 @@
         ConfigurationChangeItem result = ConfigurationChangeItem.CREATOR.createFromParcel(mParcel);
 
         assertEquals(item.hashCode(), result.hashCode());
-        assertTrue(item.equals(result));
+        assertEquals(item, result);
     }
 
     @Test
     public void testActivityConfigChange() {
         // Write to parcel
-        ActivityConfigurationChangeItem item = ActivityConfigurationChangeItem.obtain(config());
+        ActivityConfigurationChangeItem item = ActivityConfigurationChangeItem.obtain(
+                mActivityToken, config());
         writeAndPrepareForReading(item);
 
         // Read from parcel and assert
@@ -91,51 +94,52 @@
                 ActivityConfigurationChangeItem.CREATOR.createFromParcel(mParcel);
 
         assertEquals(item.hashCode(), result.hashCode());
-        assertTrue(item.equals(result));
+        assertEquals(item, result);
     }
 
     @Test
     public void testMoveToDisplay() {
         // Write to parcel
-        MoveToDisplayItem item = MoveToDisplayItem.obtain(4 /* targetDisplayId */, config());
+        MoveToDisplayItem item = MoveToDisplayItem.obtain(mActivityToken, 4 /* targetDisplayId */,
+                config());
         writeAndPrepareForReading(item);
 
         // Read from parcel and assert
         MoveToDisplayItem result = MoveToDisplayItem.CREATOR.createFromParcel(mParcel);
 
         assertEquals(item.hashCode(), result.hashCode());
-        assertTrue(item.equals(result));
+        assertEquals(item, result);
     }
 
     @Test
     public void testNewIntent() {
         // Write to parcel
-        NewIntentItem item = NewIntentItem.obtain(referrerIntentList(), false);
+        NewIntentItem item = NewIntentItem.obtain(mActivityToken, referrerIntentList(), false);
         writeAndPrepareForReading(item);
 
         // Read from parcel and assert
         NewIntentItem result = NewIntentItem.CREATOR.createFromParcel(mParcel);
 
         assertEquals(item.hashCode(), result.hashCode());
-        assertTrue(item.equals(result));
+        assertEquals(item, result);
     }
 
     @Test
     public void testActivityResult() {
         // Write to parcel
-        ActivityResultItem item = ActivityResultItem.obtain(resultInfoList());
+        ActivityResultItem item = ActivityResultItem.obtain(mActivityToken, resultInfoList());
         writeAndPrepareForReading(item);
 
         // Read from parcel and assert
         ActivityResultItem result = ActivityResultItem.CREATOR.createFromParcel(mParcel);
 
         assertEquals(item.hashCode(), result.hashCode());
-        assertTrue(item.equals(result));
+        assertEquals(item, result);
     }
 
     @Test
     public void testDestroy() {
-        DestroyActivityItem item = DestroyActivityItem.obtain(true /* finished */,
+        DestroyActivityItem item = DestroyActivityItem.obtain(mActivityToken, true /* finished */,
                 135 /* configChanges */);
         writeAndPrepareForReading(item);
 
@@ -143,48 +147,59 @@
         DestroyActivityItem result = DestroyActivityItem.CREATOR.createFromParcel(mParcel);
 
         assertEquals(item.hashCode(), result.hashCode());
-        assertTrue(item.equals(result));
+        assertEquals(item, result);
     }
 
     @Test
     public void testLaunch() {
         // Write to parcel
-        Intent intent = new Intent("action");
+        final IBinder activityToken = new Binder();
+        final Intent intent = new Intent("action");
         int ident = 57;
-        ActivityInfo activityInfo = new ActivityInfo();
+        final ActivityInfo activityInfo = new ActivityInfo();
         activityInfo.flags = 42;
         activityInfo.setMaxAspectRatio(2.4f);
         activityInfo.launchToken = "token";
         activityInfo.applicationInfo = new ApplicationInfo();
         activityInfo.packageName = "packageName";
         activityInfo.name = "name";
-        Configuration overrideConfig = new Configuration();
+        final Configuration overrideConfig = new Configuration();
         overrideConfig.assetsSeq = 5;
-        String referrer = "referrer";
+        final String referrer = "referrer";
         int procState = 4;
-        Bundle bundle = new Bundle();
+        final Bundle bundle = new Bundle();
         bundle.putString("key", "value");
         bundle.putParcelable("data", new ParcelableData(1));
-        PersistableBundle persistableBundle = new PersistableBundle();
+        final PersistableBundle persistableBundle = new PersistableBundle();
         persistableBundle.putInt("k", 4);
 
-        LaunchActivityItem item = new LaunchActivityItemBuilder()
-                .setIntent(intent).setIdent(ident).setInfo(activityInfo).setCurConfig(config())
-                .setOverrideConfig(overrideConfig).setReferrer(referrer)
-                .setProcState(procState).setState(bundle).setPersistentState(persistableBundle)
-                .setPendingResults(resultInfoList()).setActivityOptions(ActivityOptions.makeBasic())
-                .setPendingNewIntents(referrerIntentList()).setIsForward(true)
-                .setAssistToken(new Binder()).setShareableActivityToken(new Binder())
+        final LaunchActivityItem item = new LaunchActivityItemBuilder()
+                .setActivityToken(activityToken)
+                .setIntent(intent)
+                .setIdent(ident)
+                .setInfo(activityInfo)
+                .setCurConfig(config())
+                .setOverrideConfig(overrideConfig)
+                .setReferrer(referrer)
+                .setProcState(procState)
+                .setState(bundle)
+                .setPersistentState(persistableBundle)
+                .setPendingResults(resultInfoList())
+                .setActivityOptions(ActivityOptions.makeBasic())
+                .setPendingNewIntents(referrerIntentList())
+                .setIsForward(true)
+                .setAssistToken(new Binder())
+                .setShareableActivityToken(new Binder())
                 .setTaskFragmentToken(new Binder())
                 .build();
 
         writeAndPrepareForReading(item);
 
         // Read from parcel and assert
-        LaunchActivityItem result = LaunchActivityItem.CREATOR.createFromParcel(mParcel);
+        final LaunchActivityItem result = LaunchActivityItem.CREATOR.createFromParcel(mParcel);
 
         assertEquals(item.hashCode(), result.hashCode());
-        assertTrue(item.equals(result));
+        assertEquals(item, result);
     }
 
     @Test
@@ -192,7 +207,7 @@
         // Write to parcel
         Configuration overrideConfig = new Configuration();
         overrideConfig.assetsSeq = 5;
-        ActivityRelaunchItem item = ActivityRelaunchItem.obtain(resultInfoList(),
+        ActivityRelaunchItem item = ActivityRelaunchItem.obtain(mActivityToken, resultInfoList(),
                 referrerIntentList(), 35, mergedConfig(), true);
         writeAndPrepareForReading(item);
 
@@ -200,13 +215,13 @@
         ActivityRelaunchItem result = ActivityRelaunchItem.CREATOR.createFromParcel(mParcel);
 
         assertEquals(item.hashCode(), result.hashCode());
-        assertTrue(item.equals(result));
+        assertEquals(item, result);
     }
 
     @Test
     public void testPause() {
         // Write to parcel
-        PauseActivityItem item = PauseActivityItem.obtain(true /* finished */,
+        PauseActivityItem item = PauseActivityItem.obtain(mActivityToken, true /* finished */,
                 true /* userLeaving */, 135 /* configChanges */, true /* dontReport */,
                 true /* autoEnteringPip */);
         writeAndPrepareForReading(item);
@@ -215,13 +230,13 @@
         PauseActivityItem result = PauseActivityItem.CREATOR.createFromParcel(mParcel);
 
         assertEquals(item.hashCode(), result.hashCode());
-        assertTrue(item.equals(result));
+        assertEquals(item, result);
     }
 
     @Test
     public void testResume() {
         // Write to parcel
-        ResumeActivityItem item = ResumeActivityItem.obtain(27 /* procState */,
+        ResumeActivityItem item = ResumeActivityItem.obtain(mActivityToken, 27 /* procState */,
                 true /* isForward */, false /* shouldSendCompatFakeFocus */);
         writeAndPrepareForReading(item);
 
@@ -229,26 +244,27 @@
         ResumeActivityItem result = ResumeActivityItem.CREATOR.createFromParcel(mParcel);
 
         assertEquals(item.hashCode(), result.hashCode());
-        assertTrue(item.equals(result));
+        assertEquals(item, result);
     }
 
     @Test
     public void testStop() {
         // Write to parcel
-        StopActivityItem item = StopActivityItem.obtain(14 /* configChanges */);
+        StopActivityItem item = StopActivityItem.obtain(mActivityToken, 14 /* configChanges */);
         writeAndPrepareForReading(item);
 
         // Read from parcel and assert
         StopActivityItem result = StopActivityItem.CREATOR.createFromParcel(mParcel);
 
         assertEquals(item.hashCode(), result.hashCode());
-        assertTrue(item.equals(result));
+        assertEquals(item, result);
     }
 
     @Test
     public void testStart() {
         // Write to parcel
-        StartActivityItem item = StartActivityItem.obtain(ActivityOptions.makeBasic());
+        StartActivityItem item = StartActivityItem.obtain(mActivityToken,
+                ActivityOptions.makeBasic());
         writeAndPrepareForReading(item);
 
         // Read from parcel and assert
@@ -261,11 +277,12 @@
     @Test
     public void testClientTransaction() {
         // Write to parcel
-        NewIntentItem callback1 = NewIntentItem.obtain(new ArrayList<>(), true);
+        NewIntentItem callback1 = NewIntentItem.obtain(mActivityToken, new ArrayList<>(), true);
         ActivityConfigurationChangeItem callback2 = ActivityConfigurationChangeItem.obtain(
-                config());
+                mActivityToken, config());
 
-        StopActivityItem lifecycleRequest = StopActivityItem.obtain(78 /* configChanges */);
+        StopActivityItem lifecycleRequest = StopActivityItem.obtain(mActivityToken,
+                78 /* configChanges */);
 
         Binder activityToken = new Binder();
 
@@ -280,15 +297,15 @@
         ClientTransaction result = ClientTransaction.CREATOR.createFromParcel(mParcel);
 
         assertEquals(transaction.hashCode(), result.hashCode());
-        assertTrue(transaction.equals(result));
+        assertEquals(transaction, result);
     }
 
     @Test
     public void testClientTransactionCallbacksOnly() {
         // Write to parcel
-        NewIntentItem callback1 = NewIntentItem.obtain(new ArrayList<>(), true);
+        NewIntentItem callback1 = NewIntentItem.obtain(mActivityToken, new ArrayList<>(), true);
         ActivityConfigurationChangeItem callback2 = ActivityConfigurationChangeItem.obtain(
-                config());
+                mActivityToken, config());
 
         Binder activityToken = new Binder();
 
@@ -302,13 +319,14 @@
         ClientTransaction result = ClientTransaction.CREATOR.createFromParcel(mParcel);
 
         assertEquals(transaction.hashCode(), result.hashCode());
-        assertTrue(transaction.equals(result));
+        assertEquals(transaction, result);
     }
 
     @Test
     public void testClientTransactionLifecycleOnly() {
         // Write to parcel
-        StopActivityItem lifecycleRequest = StopActivityItem.obtain(78 /* configChanges */);
+        StopActivityItem lifecycleRequest = StopActivityItem.obtain(mActivityToken,
+                78 /* configChanges */);
 
         Binder activityToken = new Binder();
 
@@ -321,7 +339,7 @@
         ClientTransaction result = ClientTransaction.CREATOR.createFromParcel(mParcel);
 
         assertEquals(transaction.hashCode(), result.hashCode());
-        assertTrue(transaction.equals(result));
+        assertEquals(transaction, result);
     }
 
     /** Write to {@link #mParcel} and reset its position to prepare for reading from the start. */
diff --git a/core/tests/coretests/src/android/content/pm/SignatureTest.java b/core/tests/coretests/src/android/content/pm/SignatureTest.java
index fb0a435..4dd7b40 100644
--- a/core/tests/coretests/src/android/content/pm/SignatureTest.java
+++ b/core/tests/coretests/src/android/content/pm/SignatureTest.java
@@ -33,28 +33,44 @@
     /** Cert B with valid syntax */
     private static final Signature B = new Signature("308204a830820390a003020102020900a1573d0f45bea193300d06092a864886f70d0101050500308194310b3009060355040613025553311330110603550408130a43616c69666f726e6961311630140603550407130d4d6f756e7461696e20566965773110300e060355040a1307416e64726f69643110300e060355040b1307416e64726f69643110300e06035504031307416e64726f69643122302006092a864886f70d0109011613616e64726f696440616e64726f69642e636f6d301e170d3131303931393138343232355a170d3339303230343138343232355a308194310b3009060355040613025553311330110603550408130a43616c69666f726e6961311630140603550407130d4d6f756e7461696e20566965773110300e060355040a1307416e64726f69643110300e060355040b1307416e64726f69643110300e06035504031307416e64726f69643122302006092a864886f70d0109011613616e64726f696440616e64726f69642e636f6d30820120300d06092a864886f70d01010105000382010d00308201080282010100de1b51336afc909d8bcca5920fcdc8940578ec5c253898930e985481cfdea75ba6fc54b1f7bb492a03d98db471ab4200103a8314e60ee25fef6c8b83bc1b2b45b084874cffef148fa2001bb25c672b6beba50b7ac026b546da762ea223829a22b80ef286131f059d2c9b4ca71d54e515a8a3fd6bf5f12a2493dfc2619b337b032a7cf8bbd34b833f2b93aeab3d325549a93272093943bb59dfc0197ae4861ff514e019b73f5cf10023ad1a032adb4b9bbaeb4debecb4941d6a02381f1165e1ac884c1fca9525c5854dce2ad8ec839b8ce78442c16367efc07778a337d3ca2cdf9792ac722b95d67c345f1c00976ec372f02bfcbef0262cc512a6845e71cfea0d020103a381fc3081f9301d0603551d0e0416041478a0fc4517fb70ff52210df33c8d32290a44b2bb3081c90603551d230481c13081be801478a0fc4517fb70ff52210df33c8d32290a44b2bba1819aa48197308194310b3009060355040613025553311330110603550408130a43616c69666f726e6961311630140603550407130d4d6f756e7461696e20566965773110300e060355040a1307416e64726f69643110300e060355040b1307416e64726f69643110300e06035504031307416e64726f69643122302006092a864886f70d0109011613616e64726f696440616e64726f69642e636f6d820900a1573d0f45bea193300c0603551d13040530030101ff300d06092a864886f70d01010505000382010100977302dfbf668d7c61841c9c78d2563bcda1b199e95e6275a799939981416909722713531157f3cdcfea94eea7bb79ca3ca972bd8058a36ad1919291df42d7190678d4ea47a4b9552c9dfb260e6d0d9129b44615cd641c1080580e8a990dd768c6ab500c3b964e185874e4105109d94c5bd8c405deb3cf0f7960a563bfab58169a956372167a7e2674a04c4f80015d8f7869a7a4139aecbbdca2abc294144ee01e4109f0e47a518363cf6e9bf41f7560e94bdd4a5d085234796b05c7a1389adfd489feec2a107955129d7991daa49afb3d327dc0dc4fe959789372b093a89c8dbfa41554f771c18015a6cb242a17e04d19d55d3b4664eae12caf2a11cd2b836e");
 
+    private boolean areExactMatch(Signature[] a, Signature[] b) throws Exception {
+        SigningDetails ad1 = new SigningDetails(a,
+                SigningDetails.SignatureSchemeVersion.SIGNING_BLOCK_V3);
+        SigningDetails bd1 = new SigningDetails(b,
+                SigningDetails.SignatureSchemeVersion.SIGNING_BLOCK_V3);
+        return Signature.areExactMatch(ad1, bd1);
+    }
+
     public void testExactlyEqual() throws Exception {
-        assertTrue(Signature.areExactMatch(asArray(A), asArray(A)));
-        assertTrue(Signature.areExactMatch(asArray(M), asArray(M)));
+        assertTrue(areExactMatch(asArray(A), asArray(A)));
+        assertTrue(areExactMatch(asArray(M), asArray(M)));
 
-        assertFalse(Signature.areExactMatch(asArray(A), asArray(B)));
-        assertFalse(Signature.areExactMatch(asArray(A), asArray(M)));
-        assertFalse(Signature.areExactMatch(asArray(M), asArray(A)));
+        assertFalse(areExactMatch(asArray(A), asArray(B)));
+        assertFalse(areExactMatch(asArray(A), asArray(M)));
+        assertFalse(areExactMatch(asArray(M), asArray(A)));
 
-        assertTrue(Signature.areExactMatch(asArray(A, M), asArray(M, A)));
+        assertTrue(areExactMatch(asArray(A, M), asArray(M, A)));
+    }
+
+    private boolean areEffectiveMatch(Signature[] a, Signature[] b) throws Exception {
+        SigningDetails ad1 = new SigningDetails(a,
+                SigningDetails.SignatureSchemeVersion.SIGNING_BLOCK_V3);
+        SigningDetails bd1 = new SigningDetails(b,
+                SigningDetails.SignatureSchemeVersion.SIGNING_BLOCK_V3);
+        return Signature.areEffectiveMatch(ad1, bd1);
     }
 
     public void testEffectiveMatch() throws Exception {
-        assertTrue(Signature.areEffectiveMatch(asArray(A), asArray(A)));
-        assertTrue(Signature.areEffectiveMatch(asArray(M), asArray(M)));
+        assertTrue(areEffectiveMatch(asArray(A), asArray(A)));
+        assertTrue(areEffectiveMatch(asArray(M), asArray(M)));
 
-        assertFalse(Signature.areEffectiveMatch(asArray(A), asArray(B)));
-        assertTrue(Signature.areEffectiveMatch(asArray(A), asArray(M)));
-        assertTrue(Signature.areEffectiveMatch(asArray(M), asArray(A)));
+        assertFalse(areEffectiveMatch(asArray(A), asArray(B)));
+        assertTrue(areEffectiveMatch(asArray(A), asArray(M)));
+        assertTrue(areEffectiveMatch(asArray(M), asArray(A)));
 
-        assertTrue(Signature.areEffectiveMatch(asArray(A, M), asArray(M, A)));
-        assertTrue(Signature.areEffectiveMatch(asArray(A, B), asArray(M, B)));
-        assertFalse(Signature.areEffectiveMatch(asArray(A, M), asArray(A, B)));
+        assertTrue(areEffectiveMatch(asArray(A, M), asArray(M, A)));
+        assertTrue(areEffectiveMatch(asArray(A, B), asArray(M, B)));
+        assertFalse(areEffectiveMatch(asArray(A, M), asArray(A, B)));
     }
 
     public void testHashCode_doesNotIncludeFlags() throws Exception {
diff --git a/core/tests/coretests/src/android/database/DatabaseUtilsTest.java b/core/tests/coretests/src/android/database/DatabaseUtilsTest.java
index be156c8..13ce253 100644
--- a/core/tests/coretests/src/android/database/DatabaseUtilsTest.java
+++ b/core/tests/coretests/src/android/database/DatabaseUtilsTest.java
@@ -17,6 +17,15 @@
 package android.database;
 
 import static android.database.DatabaseUtils.bindSelection;
+import static android.database.DatabaseUtils.getSqlStatementType;
+import static android.database.DatabaseUtils.getSqlStatementTypeExtended;
+import static android.database.DatabaseUtils.STATEMENT_COMMENT;
+import static android.database.DatabaseUtils.STATEMENT_CREATE;
+import static android.database.DatabaseUtils.STATEMENT_DDL;
+import static android.database.DatabaseUtils.STATEMENT_OTHER;
+import static android.database.DatabaseUtils.STATEMENT_SELECT;
+import static android.database.DatabaseUtils.STATEMENT_UPDATE;
+import static android.database.DatabaseUtils.STATEMENT_WITH;
 
 import static org.junit.Assert.assertEquals;
 
@@ -63,4 +72,39 @@
                 bindSelection("foo=?10 AND bar=? AND meow=?1",
                         1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12));
     }
+
+    @Test
+    public void testStatementType() {
+        final int sel = STATEMENT_SELECT;
+        assertEquals(sel, getSqlStatementType("SELECT"));
+        assertEquals(sel, getSqlStatementType("  SELECT"));
+        assertEquals(sel, getSqlStatementType(" \n SELECT"));
+
+        final int upd = STATEMENT_UPDATE;
+        assertEquals(upd, getSqlStatementType("UPDATE"));
+        assertEquals(upd, getSqlStatementType("  UPDATE"));
+        assertEquals(upd, getSqlStatementType(" \n UPDATE"));
+
+        final int ddl = STATEMENT_DDL;
+        assertEquals(ddl, getSqlStatementType("ALTER TABLE t1 ADD COLUMN j int"));
+        assertEquals(ddl, getSqlStatementType("CREATE TABLE t1 (i int)"));
+
+        // Short statements, leading comments, and WITH are decoded to "other" in the public API.
+        final int othr = STATEMENT_OTHER;
+        assertEquals(othr, getSqlStatementType("SE"));
+        assertEquals(othr, getSqlStatementType("SE LECT"));
+        assertEquals(othr, getSqlStatementType("-- cmt\n SE"));
+        assertEquals(othr, getSqlStatementType("WITH"));
+
+        // Test the extended statement types.
+
+        final int wit = STATEMENT_WITH;
+        assertEquals(wit, getSqlStatementTypeExtended("WITH"));
+
+        final int cmt = STATEMENT_COMMENT;
+        assertEquals(cmt, getSqlStatementTypeExtended("-- cmt\n SELECT"));
+
+        final int cre = STATEMENT_CREATE;
+        assertEquals(cre, getSqlStatementTypeExtended("CREATE TABLE t1 (i int)"));
+    }
 }
diff --git a/core/tests/coretests/src/android/provider/DeviceConfigTest.java b/core/tests/coretests/src/android/provider/DeviceConfigTest.java
index 349fe7b..9840e15 100644
--- a/core/tests/coretests/src/android/provider/DeviceConfigTest.java
+++ b/core/tests/coretests/src/android/provider/DeviceConfigTest.java
@@ -20,6 +20,7 @@
 
 import static com.google.common.truth.Truth.assertThat;
 
+import static org.junit.Assert.assertTrue;
 import static org.testng.Assert.assertThrows;
 
 import android.app.ActivityThread;
@@ -28,6 +29,7 @@
 import android.platform.test.annotations.Presubmit;
 
 import androidx.test.InstrumentationRegistry;
+import androidx.test.filters.FlakyTest;
 import androidx.test.filters.SmallTest;
 import androidx.test.runner.AndroidJUnit4;
 
@@ -625,6 +627,7 @@
     }
 
     @Test
+    @FlakyTest(bugId = 299483542)
     public void setProperties_multipleNamespaces() throws DeviceConfig.BadConfigException {
         final String namespace2 = "namespace2";
         Properties properties1 = new Properties.Builder(NAMESPACE).setString(KEY, VALUE)
@@ -632,8 +635,8 @@
         Properties properties2 = new Properties.Builder(namespace2).setString(KEY2, VALUE)
                 .setString(KEY3, VALUE2).build();
 
-        DeviceConfig.setProperties(properties1);
-        DeviceConfig.setProperties(properties2);
+        assertTrue(DeviceConfig.setProperties(properties1));
+        assertTrue(DeviceConfig.setProperties(properties2));
 
         Properties properties = DeviceConfig.getProperties(NAMESPACE);
         assertThat(properties.getKeyset()).containsExactly(KEY, KEY2);
diff --git a/data/etc/services.core.protolog.json b/data/etc/services.core.protolog.json
index 721a2db..ee2eacf 100644
--- a/data/etc/services.core.protolog.json
+++ b/data/etc/services.core.protolog.json
@@ -1021,6 +1021,12 @@
       "group": "WM_DEBUG_REMOTE_ANIMATIONS",
       "at": "com\/android\/server\/wm\/NonAppWindowAnimationAdapter.java"
     },
+    "-1152771606": {
+      "message": "Content Recording: Display %d was already recording, but pause capture since the task is in PIP",
+      "level": "VERBOSE",
+      "group": "WM_DEBUG_CONTENT_RECORDING",
+      "at": "com\/android\/server\/wm\/ContentRecorder.java"
+    },
     "-1145384901": {
       "message": "shouldWaitAnimatingExit: isTransition: %s",
       "level": "DEBUG",
@@ -2335,6 +2341,12 @@
       "group": "WM_DEBUG_SYNC_ENGINE",
       "at": "com\/android\/server\/wm\/WindowState.java"
     },
+    "1877956": {
+      "message": "Content Recording: Display %d should start recording, but don't yet since the task is in PIP",
+      "level": "VERBOSE",
+      "group": "WM_DEBUG_CONTENT_RECORDING",
+      "at": "com\/android\/server\/wm\/ContentRecorder.java"
+    },
     "3593205": {
       "message": "commitVisibility: %s: visible=%b mVisibleRequested=%b",
       "level": "VERBOSE",
diff --git a/data/keyboards/qwerty.idc b/data/keyboards/qwerty.idc
deleted file mode 100644
index 375d785..0000000
--- a/data/keyboards/qwerty.idc
+++ /dev/null
@@ -1,28 +0,0 @@
-# Copyright (C) 2010 The Android Open Source Project
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-#      http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-#
-# Emulator keyboard configuration file #1.
-#
-
-touch.deviceType = touchScreen
-touch.orientationAware = 1
-
-keyboard.layout = qwerty
-keyboard.characterMap = qwerty
-keyboard.orientationAware = 1
-keyboard.builtIn = 1
-
-cursor.mode = navigation
-cursor.orientationAware = 1
diff --git a/data/keyboards/qwerty.kcm b/data/keyboards/qwerty.kcm
deleted file mode 100644
index f3e15241..0000000
--- a/data/keyboards/qwerty.kcm
+++ /dev/null
@@ -1,508 +0,0 @@
-# Copyright (C) 2010 The Android Open Source Project
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-#      http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-#
-# Emulator keyboard character map #1.
-#
-# This file is no longer used as the platform's default keyboard character map.
-# Refer to Generic.kcm and Virtual.kcm instead.
-#
-
-type ALPHA
-
-key A {
-    label:                              'A'
-    number:                             '2'
-    base:                               'a'
-    shift, capslock:                    'A'
-    alt:                                '#'
-    shift+alt, capslock+alt:            none
-}
-
-key B {
-    label:                              'B'
-    number:                             '2'
-    base:                               'b'
-    shift, capslock:                    'B'
-    alt:                                '<'
-    shift+alt, capslock+alt:            none
-}
-
-key C {
-    label:                              'C'
-    number:                             '2'
-    base:                               'c'
-    shift, capslock:                    'C'
-    alt:                                '9'
-    shift+alt, capslock+alt:            '\u00e7'
-}
-
-key D {
-    label:                              'D'
-    number:                             '3'
-    base:                               'd'
-    shift, capslock:                    'D'
-    alt:                                '5'
-    shift+alt, capslock+alt:            none
-}
-
-key E {
-    label:                              'E'
-    number:                             '3'
-    base:                               'e'
-    shift, capslock:                    'E'
-    alt:                                '2'
-    shift+alt, capslock+alt:            '\u0301'
-}
-
-key F {
-    label:                              'F'
-    number:                             '3'
-    base:                               'f'
-    shift, capslock:                    'F'
-    alt:                                '6'
-    shift+alt, capslock+alt:            '\u00a5'
-}
-
-key G {
-    label:                              'G'
-    number:                             '4'
-    base:                               'g'
-    shift, capslock:                    'G'
-    alt:                                '-'
-    shift+alt, capslock+alt:            '_'
-}
-
-key H {
-    label:                              'H'
-    number:                             '4'
-    base:                               'h'
-    shift, capslock:                    'H'
-    alt:                                '['
-    shift+alt, capslock+alt:            '{'
-}
-
-key I {
-    label:                              'I'
-    number:                             '4'
-    base:                               'i'
-    shift, capslock:                    'I'
-    alt:                                '$'
-    shift+alt, capslock+alt:            '\u0302'
-}
-
-key J {
-    label:                              'J'
-    number:                             '5'
-    base:                               'j'
-    shift, capslock:                    'J'
-    alt:                                ']'
-    shift+alt, capslock+alt:            '}'
-}
-
-key K {
-    label:                              'K'
-    number:                             '5'
-    base:                               'k'
-    shift, capslock:                    'K'
-    alt:                                '"'
-    shift+alt, capslock+alt:            '~'
-}
-
-key L {
-    label:                              'L'
-    number:                             '5'
-    base:                               'l'
-    shift, capslock:                    'L'
-    alt:                                '\''
-    shift+alt, capslock+alt:            '`'
-}
-
-key M {
-    label:                              'M'
-    number:                             '6'
-    base:                               'm'
-    shift, capslock:                    'M'
-    alt:                                '!'
-    shift+alt, capslock+alt:            none
-}
-
-key N {
-    label:                              'N'
-    number:                             '6'
-    base:                               'n'
-    shift, capslock:                    'N'
-    alt:                                '>'
-    shift+alt, capslock+alt:            '\u0303'
-}
-
-key O {
-    label:                              'O'
-    number:                             '6'
-    base:                               'o'
-    shift, capslock:                    'O'
-    alt:                                '('
-    shift+alt, capslock+alt:            none
-}
-
-key P {
-    label:                              'P'
-    number:                             '7'
-    base:                               'p'
-    shift, capslock:                    'P'
-    alt:                                ')'
-    shift+alt, capslock+alt:            none
-}
-
-key Q {
-    label:                              'Q'
-    number:                             '7'
-    base:                               'q'
-    shift, capslock:                    'Q'
-    alt:                                '*'
-    shift+alt, capslock+alt:            '\u0300'
-}
-
-key R {
-    label:                              'R'
-    number:                             '7'
-    base:                               'r'
-    shift, capslock:                    'R'
-    alt:                                '3'
-    shift+alt, capslock+alt:            '\u20ac'
-}
-
-key S {
-    label:                              'S'
-    number:                             '7'
-    base:                               's'
-    shift, capslock:                    'S'
-    alt:                                '4'
-    shift+alt, capslock+alt:            '\u00df'
-}
-
-key T {
-    label:                              'T'
-    number:                             '8'
-    base:                               't'
-    shift, capslock:                    'T'
-    alt:                                '+'
-    shift+alt, capslock+alt:            '\u00a3'
-}
-
-key U {
-    label:                              'U'
-    number:                             '8'
-    base:                               'u'
-    shift, capslock:                    'U'
-    alt:                                '&'
-    shift+alt, capslock+alt:            '\u0308'
-}
-
-key V {
-    label:                              'V'
-    number:                             '8'
-    base:                               'v'
-    shift, capslock:                    'V'
-    alt:                                '='
-    shift+alt, capslock+alt:            '^'
-}
-
-key W {
-    label:                              'W'
-    number:                             '9'
-    base:                               'w'
-    shift, capslock:                    'W'
-    alt:                                '1'
-    shift+alt, capslock+alt:            none
-}
-
-key X {
-    label:                              'X'
-    number:                             '9'
-    base:                               'x'
-    shift, capslock:                    'X'
-    alt:                                '8'
-    shift+alt, capslock+alt:            '\uef00'
-}
-
-key Y {
-    label:                              'Y'
-    number:                             '9'
-    base:                               'y'
-    shift, capslock:                    'Y'
-    alt:                                '%'
-    shift+alt, capslock+alt:            '\u00a1'
-}
-
-key Z {
-    label:                              'Z'
-    number:                             '9'
-    base:                               'z'
-    shift, capslock:                    'Z'
-    alt:                                '7'
-    shift+alt, capslock+alt:            none
-}
-
-key COMMA {
-    label:                              ','
-    number:                             ','
-    base:                               ','
-    shift:                              ';'
-    alt:                                ';'
-    shift+alt:                          '|'
-}
-
-key PERIOD {
-    label:                              '.'
-    number:                             '.'
-    base:                               '.'
-    shift:                              ':'
-    alt:                                ':'
-    shift+alt:                          '\u2026'
-}
-
-key AT {
-    label:                              '@'
-    number:                             '0'
-    base:                               '@'
-    shift:                              '0'
-    alt:                                '0'
-    shift+alt:                          '\u2022'
-}
-
-key SLASH {
-    label:                              '/'
-    number:                             '/'
-    base:                               '/'
-    shift:                              '?'
-    alt:                                '?'
-    shift+alt:                          '\\'
-}
-
-key SPACE {
-    label:                              ' '
-    number:                             ' '
-    base:                               ' '
-    shift:                              ' '
-    alt:                                '\uef01'
-    shift+alt:                          '\uef01'
-}
-
-key ENTER {
-    label:                              '\n'
-    number:                             '\n'
-    base:                               '\n'
-    shift:                              '\n'
-    alt:                                '\n'
-    shift+alt:                          '\n'
-}
-
-key TAB {
-    label:                              '\t'
-    number:                             '\t'
-    base:                               '\t'
-    shift:                              '\t'
-    alt:                                '\t'
-    shift+alt:                          '\t'
-}
-
-key 0 {
-    label:                              '0'
-    number:                             '0'
-    base:                               '0'
-    shift:                              ')'
-    alt:                                ')'
-    shift+alt:                          ')'
-}
-
-key 1 {
-    label:                              '1'
-    number:                             '1'
-    base:                               '1'
-    shift:                              '!'
-    alt:                                '!'
-    shift+alt:                          '!'
-}
-
-key 2 {
-    label:                              '2'
-    number:                             '2'
-    base:                               '2'
-    shift:                              '@'
-    alt:                                '@'
-    shift+alt:                          '@'
-}
-
-key 3 {
-    label:                              '3'
-    number:                             '3'
-    base:                               '3'
-    shift:                              '#'
-    alt:                                '#'
-    shift+alt:                          '#'
-}
-
-key 4 {
-    label:                              '4'
-    number:                             '4'
-    base:                               '4'
-    shift:                              '$'
-    alt:                                '$'
-    shift+alt:                          '$'
-}
-
-key 5 {
-    label:                              '5'
-    number:                             '5'
-    base:                               '5'
-    shift:                              '%'
-    alt:                                '%'
-    shift+alt:                          '%'
-}
-
-key 6 {
-    label:                              '6'
-    number:                             '6'
-    base:                               '6'
-    shift:                              '^'
-    alt:                                '^'
-    shift+alt:                          '^'
-}
-
-key 7 {
-    label:                              '7'
-    number:                             '7'
-    base:                               '7'
-    shift:                              '&'
-    alt:                                '&'
-    shift+alt:                          '&'
-}
-
-key 8 {
-    label:                              '8'
-    number:                             '8'
-    base:                               '8'
-    shift:                              '*'
-    alt:                                '*'
-    shift+alt:                          '*'
-}
-
-key 9 {
-    label:                              '9'
-    number:                             '9'
-    base:                               '9'
-    shift:                              '('
-    alt:                                '('
-    shift+alt:                          '('
-}
-
-key GRAVE {
-    label:                              '`'
-    number:                             '`'
-    base:                               '`'
-    shift:                              '~'
-    alt:                                '`'
-    shift+alt:                          '~'
-}
-
-key MINUS {
-    label:                              '-'
-    number:                             '-'
-    base:                               '-'
-    shift:                              '_'
-    alt:                                '-'
-    shift+alt:                          '_'
-}
-
-key EQUALS {
-    label:                              '='
-    number:                             '='
-    base:                               '='
-    shift:                              '+'
-    alt:                                '='
-    shift+alt:                          '+'
-}
-
-key LEFT_BRACKET {
-    label:                              '['
-    number:                             '['
-    base:                               '['
-    shift:                              '{'
-    alt:                                '['
-    shift+alt:                          '{'
-}
-
-key RIGHT_BRACKET {
-    label:                              ']'
-    number:                             ']'
-    base:                               ']'
-    shift:                              '}'
-    alt:                                ']'
-    shift+alt:                          '}'
-}
-
-key BACKSLASH {
-    label:                              '\\'
-    number:                             '\\'
-    base:                               '\\'
-    shift:                              '|'
-    alt:                                '\\'
-    shift+alt:                          '|'
-}
-
-key SEMICOLON {
-    label:                              ';'
-    number:                             ';'
-    base:                               ';'
-    shift:                              ':'
-    alt:                                ';'
-    shift+alt:                          ':'
-}
-
-key APOSTROPHE {
-    label:                              '\''
-    number:                             '\''
-    base:                               '\''
-    shift:                              '"'
-    alt:                                '\''
-    shift+alt:                          '"'
-}
-
-key STAR {
-    label:                              '*'
-    number:                             '*'
-    base:                               '*'
-    shift:                              '*'
-    alt:                                '*'
-    shift+alt:                          '*'
-}
-
-key POUND {
-    label:                              '#'
-    number:                             '#'
-    base:                               '#'
-    shift:                              '#'
-    alt:                                '#'
-    shift+alt:                          '#'
-}
-
-key PLUS {
-    label:                              '+'
-    number:                             '+'
-    base:                               '+'
-    shift:                              '+'
-    alt:                                '+'
-    shift+alt:                          '+'
-}
diff --git a/data/keyboards/qwerty.kl b/data/keyboards/qwerty.kl
deleted file mode 100644
index 2fd99ab..0000000
--- a/data/keyboards/qwerty.kl
+++ /dev/null
@@ -1,131 +0,0 @@
-# Copyright (C) 2010 The Android Open Source Project
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-#      http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-#
-# Emulator keyboard layout #1.
-#
-# This file is no longer used as the platform's default keyboard layout.
-# Refer to Generic.kl instead.
-#
-
-key 399   GRAVE
-key 2     1
-key 3     2
-key 4     3
-key 5     4
-key 6     5
-key 7     6
-key 8     7
-key 9     8
-key 10    9
-key 11    0
-key 158   BACK
-key 230   SOFT_RIGHT
-key 60    SOFT_LEFT
-key 107   ENDCALL
-key 62    ENDCALL
-key 229   MENU
-key 139   MENU
-key 59    MENU
-key 127   SEARCH
-key 217   SEARCH
-key 228   POUND
-key 227   STAR
-key 231   CALL
-key 61    CALL
-key 232   DPAD_CENTER
-key 108   DPAD_DOWN
-key 103   DPAD_UP
-key 102   HOME
-key 105   DPAD_LEFT
-key 106   DPAD_RIGHT
-key 115   VOLUME_UP
-key 114   VOLUME_DOWN
-key 116   POWER
-key 212   CAMERA
-
-key 16    Q
-key 17    W
-key 18    E
-key 19    R
-key 20    T
-key 21    Y
-key 22    U
-key 23    I
-key 24    O
-key 25    P
-key 26    LEFT_BRACKET
-key 27    RIGHT_BRACKET
-key 43    BACKSLASH
-
-key 30    A
-key 31    S
-key 32    D
-key 33    F
-key 34    G
-key 35    H
-key 36    J
-key 37    K
-key 38    L
-key 39    SEMICOLON
-key 40    APOSTROPHE
-key 14    DEL
-
-key 44    Z
-key 45    X
-key 46    C
-key 47    V
-key 48    B
-key 49    N
-key 50    M
-key 51    COMMA
-key 52    PERIOD
-key 53    SLASH
-key 28    ENTER
-
-key 56    ALT_LEFT
-key 100   ALT_RIGHT
-key 42    SHIFT_LEFT
-key 54    SHIFT_RIGHT
-key 15    TAB
-key 57    SPACE
-key 150   EXPLORER
-key 155   ENVELOPE
-
-key 12    MINUS
-key 13    EQUALS
-key 215   AT
-
-# On an AT keyboard: ESC, F10
-key 1     BACK
-key 68    MENU
-
-# App switch = Overview key
-key 580   APP_SWITCH
-
-# Media control keys
-key 160   MEDIA_CLOSE
-key 161   MEDIA_EJECT
-key 163   MEDIA_NEXT
-key 164   MEDIA_PLAY_PAUSE
-key 165   MEDIA_PREVIOUS
-key 166   MEDIA_STOP
-key 167   MEDIA_RECORD
-key 168   MEDIA_REWIND
-
-key 142   SLEEP
-key 581   STEM_PRIMARY
-key 582   STEM_1
-key 583   STEM_2
-key 584   STEM_3
diff --git a/data/keyboards/qwerty2.idc b/data/keyboards/qwerty2.idc
deleted file mode 100644
index 369205e..0000000
--- a/data/keyboards/qwerty2.idc
+++ /dev/null
@@ -1,28 +0,0 @@
-# Copyright (C) 2010 The Android Open Source Project
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-#      http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-#
-# Emulator keyboard configuration file #2.
-#
-
-touch.deviceType = touchScreen
-touch.orientationAware = 1
-
-keyboard.layout = qwerty
-keyboard.characterMap = qwerty2
-keyboard.orientationAware = 1
-keyboard.builtIn = 1
-
-cursor.mode = navigation
-cursor.orientationAware = 1
diff --git a/data/keyboards/qwerty2.kcm b/data/keyboards/qwerty2.kcm
deleted file mode 100644
index b981d83..0000000
--- a/data/keyboards/qwerty2.kcm
+++ /dev/null
@@ -1,505 +0,0 @@
-# Copyright (C) 2010 The Android Open Source Project
-#
-# Licensed under the Apache License, Version 2.0 (the "License");
-# you may not use this file except in compliance with the License.
-# You may obtain a copy of the License at
-#
-#      http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing, software
-# distributed under the License is distributed on an "AS IS" BASIS,
-# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-# See the License for the specific language governing permissions and
-# limitations under the License.
-
-#
-# Emulator keyboard character map #2.
-#
-
-type ALPHA
-
-key A {
-    label:                              'A'
-    number:                             '2'
-    base:                               'a'
-    shift, capslock:                    'A'
-    alt:                                '\u00e1'
-    shift+alt, capslock+alt:            '\u00c1'
-}
-
-key B {
-    label:                              'B'
-    number:                             '2'
-    base:                               'b'
-    shift, capslock:                    'B'
-    alt:                                'b'
-    shift+alt, capslock+alt:            'B'
-}
-
-key C {
-    label:                              'C'
-    number:                             '2'
-    base:                               'c'
-    shift, capslock:                    'C'
-    alt:                                '\u00a9'
-    shift+alt, capslock+alt:            '\u00a2'
-}
-
-key D {
-    label:                              'D'
-    number:                             '3'
-    base:                               'd'
-    shift, capslock:                    'D'
-    alt:                                '\u00f0'
-    shift+alt, capslock+alt:            '\u00d0'
-}
-
-key E {
-    label:                              'E'
-    number:                             '3'
-    base:                               'e'
-    shift, capslock:                    'E'
-    alt:                                '\u00e9'
-    shift+alt, capslock+alt:            '\u00c9'
-}
-
-key F {
-    label:                              'F'
-    number:                             '3'
-    base:                               'f'
-    shift, capslock:                    'F'
-    alt:                                '['
-    shift+alt, capslock+alt:            '['
-}
-
-key G {
-    label:                              'G'
-    number:                             '4'
-    base:                               'g'
-    shift, capslock:                    'G'
-    alt:                                ']'
-    shift+alt, capslock+alt:            ']'
-}
-
-key H {
-    label:                              'H'
-    number:                             '4'
-    base:                               'h'
-    shift, capslock:                    'H'
-    alt:                                '<'
-    shift+alt, capslock+alt:            '<'
-}
-
-key I {
-    label:                              'I'
-    number:                             '4'
-    base:                               'i'
-    shift, capslock:                    'I'
-    alt:                                '\u00ed'
-    shift+alt, capslock+alt:            '\u00cd'
-}
-
-key J {
-    label:                              'J'
-    number:                             '5'
-    base:                               'j'
-    shift, capslock:                    'J'
-    alt:                                '>'
-    shift+alt, capslock+alt:            '>'
-}
-
-key K {
-    label:                              'K'
-    number:                             '5'
-    base:                               'k'
-    shift, capslock:                    'K'
-    alt:                                ';'
-    shift+alt, capslock+alt:            '~'
-}
-
-key L {
-    label:                              'L'
-    number:                             '5'
-    base:                               'l'
-    shift, capslock:                    'L'
-    alt:                                '\u00f8'
-    shift+alt, capslock+alt:            '\u00d8'
-}
-
-key M {
-    label:                              'M'
-    number:                             '6'
-    base:                               'm'
-    shift, capslock:                    'M'
-    alt:                                '\u00b5'
-    shift+alt, capslock+alt:            none
-}
-
-key N {
-    label:                              'N'
-    number:                             '6'
-    base:                               'n'
-    shift, capslock:                    'N'
-    alt:                                '\u00f1'
-    shift+alt, capslock+alt:            '\u00d1'
-}
-
-key O {
-    label:                              'O'
-    number:                             '6'
-    base:                               'o'
-    shift, capslock:                    'O'
-    alt:                                '\u00f3'
-    shift+alt, capslock+alt:            '\u00d3'
-}
-
-key P {
-    label:                              'P'
-    number:                             '7'
-    base:                               'p'
-    shift, capslock:                    'P'
-    alt:                                '\u00f6'
-    shift+alt, capslock+alt:            '\u00d6'
-}
-
-key Q {
-    label:                              'Q'
-    number:                             '7'
-    base:                               'q'
-    shift, capslock:                    'Q'
-    alt:                                '\u00e4'
-    shift+alt, capslock+alt:            '\u00c4'
-}
-
-key R {
-    label:                              'R'
-    number:                             '7'
-    base:                               'r'
-    shift, capslock:                    'R'
-    alt:                                '\u00ae'
-    shift+alt, capslock+alt:            'R'
-}
-
-key S {
-    label:                              'S'
-    number:                             '7'
-    base:                               's'
-    shift, capslock:                    'S'
-    alt:                                '\u00df'
-    shift+alt, capslock+alt:            '\u00a7'
-}
-
-key T {
-    label:                              'T'
-    number:                             '8'
-    base:                               't'
-    shift, capslock:                    'T'
-    alt:                                '\u00fe'
-    shift+alt, capslock+alt:            '\u00de'
-}
-
-key U {
-    label:                              'U'
-    number:                             '8'
-    base:                               'u'
-    shift, capslock:                    'U'
-    alt:                                '\u00fa'
-    shift+alt, capslock+alt:            '\u00da'
-}
-
-key V {
-    label:                              'V'
-    number:                             '8'
-    base:                               'v'
-    shift, capslock:                    'V'
-    alt:                                'v'
-    shift+alt, capslock+alt:            'V'
-}
-
-key W {
-    label:                              'W'
-    number:                             '9'
-    base:                               'w'
-    shift, capslock:                    'W'
-    alt:                                '\u00e5'
-    shift+alt, capslock+alt:            '\u00c5'
-}
-
-key X {
-    label:                              'X'
-    number:                             '9'
-    base:                               'x'
-    shift, capslock:                    'X'
-    alt:                                'x'
-    shift+alt, capslock+alt:            '\uef00'
-}
-
-key Y {
-    label:                              'Y'
-    number:                             '9'
-    base:                               'y'
-    shift, capslock:                    'Y'
-    alt:                                '\u00fc'
-    shift+alt, capslock+alt:            '\u00dc'
-}
-
-key Z {
-    label:                              'Z'
-    number:                             '9'
-    base:                               'z'
-    shift, capslock:                    'Z'
-    alt:                                '\u00e6'
-    shift+alt, capslock+alt:            '\u00c6'
-}
-
-key COMMA {
-    label:                              ','
-    number:                             ','
-    base:                               ','
-    shift:                              '<'
-    alt:                                '\u00e7'
-    shift+alt:                          '\u00c7'
-}
-
-key PERIOD {
-    label:                              '.'
-    number:                             '.'
-    base:                               '.'
-    shift:                              '>'
-    alt:                                '.'
-    shift+alt:                          '\u2026'
-}
-
-key AT {
-    label:                              '@'
-    number:                             '@'
-    base:                               '@'
-    shift:                              '@'
-    alt:                                '@'
-    shift+alt:                          '\u2022'
-}
-
-key SLASH {
-    label:                              '/'
-    number:                             '/'
-    base:                               '/'
-    shift:                              '?'
-    alt:                                '\u00bf'
-    shift+alt:                          '?'
-}
-
-key SPACE {
-    label:                              ' '
-    number:                             ' '
-    base:                               ' '
-    shift:                              ' '
-    alt:                                '\uef01'
-    shift+alt:                          '\uef01'
-}
-
-key ENTER {
-    label:                              '\n'
-    number:                             '\n'
-    base:                               '\n'
-    shift:                              '\n'
-    alt:                                '\n'
-    shift+alt:                          '\n'
-}
-
-key TAB {
-    label:                              '\t'
-    number:                             '\t'
-    base:                               '\t'
-    shift:                              '\t'
-    alt:                                '\t'
-    shift+alt:                          '\t'
-}
-
-key 0 {
-    label:                              '0'
-    number:                             '0'
-    base:                               '0'
-    shift:                              ')'
-    alt:                                '\u02bc'
-    shift+alt:                          ')'
-}
-
-key 1 {
-    label:                              '1'
-    number:                             '1'
-    base:                               '1'
-    shift:                              '!'
-    alt:                                '\u00a1'
-    shift+alt:                          '\u00b9'
-}
-
-key 2 {
-    label:                              '2'
-    number:                             '2'
-    base:                               '2'
-    shift:                              '@'
-    alt:                                '\u00b2'
-    shift+alt:                          '@'
-}
-
-key 3 {
-    label:                              '3'
-    number:                             '3'
-    base:                               '3'
-    shift:                              '#'
-    alt:                                '\u00b3'
-    shift+alt:                          '#'
-}
-
-key 4 {
-    label:                              '4'
-    number:                             '4'
-    base:                               '4'
-    shift:                              '$'
-    alt:                                '\u00a4'
-    shift+alt:                          '\u00a3'
-}
-
-key 5 {
-    label:                              '5'
-    number:                             '5'
-    base:                               '5'
-    shift:                              '%'
-    alt:                                '\u20ac'
-    shift+alt:                          '%'
-}
-
-key 6 {
-    label:                              '6'
-    number:                             '6'
-    base:                               '6'
-    shift:                              '^'
-    alt:                                '\u00bc'
-    shift+alt:                          '\u0302'
-}
-
-key 7 {
-    label:                              '7'
-    number:                             '7'
-    base:                               '7'
-    shift:                              '&'
-    alt:                                '\u00bd'
-    shift+alt:                          '&'
-}
-
-key 8 {
-    label:                              '8'
-    number:                             '8'
-    base:                               '8'
-    shift:                              '*'
-    alt:                                '\u00be'
-    shift+alt:                          '*'
-}
-
-key 9 {
-    label:                              '9'
-    number:                             '9'
-    base:                               '9'
-    shift:                              '('
-    alt:                                '\u02bb'
-    shift+alt:                          '('
-}
-
-key GRAVE {
-    label:                              '`'
-    number:                             '`'
-    base:                               '`'
-    shift:                              '~'
-    alt:                                '\u0300'
-    shift+alt:                          '\u0303'
-}
-
-key MINUS {
-    label:                              '-'
-    number:                             '-'
-    base:                               '-'
-    shift:                              '_'
-    alt:                                '\u00a5'
-    shift+alt:                          '_'
-}
-
-key EQUALS {
-    label:                              '='
-    number:                             '='
-    base:                               '='
-    shift:                              '+'
-    alt:                                '\u00d7'
-    shift+alt:                          '\u00f7'
-}
-
-key LEFT_BRACKET {
-    label:                              '['
-    number:                             '['
-    base:                               '['
-    shift:                              '{'
-    alt:                                '\u00ab'
-    shift+alt:                          '{'
-}
-
-key RIGHT_BRACKET {
-    label:                              ']'
-    number:                             ']'
-    base:                               ']'
-    shift:                              '}'
-    alt:                                '\u00bb'
-    shift+alt:                          '}'
-}
-
-key BACKSLASH {
-    label:                              '\\'
-    number:                             '\\'
-    base:                               '\\'
-    shift:                              '|'
-    alt:                                '\u00ac'
-    shift+alt:                          '\u00a6'
-}
-
-key SEMICOLON {
-    label:                              ';'
-    number:                             ';'
-    base:                               ';'
-    shift:                              ':'
-    alt:                                '\u00b6'
-    shift+alt:                          '\u00b0'
-}
-
-key APOSTROPHE {
-    label:                              '\''
-    number:                             '\''
-    base:                               '\''
-    shift:                              '"'
-    alt:                                '\u0301'
-    shift+alt:                          '\u0308'
-}
-
-key STAR {
-    label:                              '*'
-    number:                             '*'
-    base:                               '*'
-    shift:                              '*'
-    alt:                                '*'
-    shift+alt:                          '*'
-}
-
-key POUND {
-    label:                              '#'
-    number:                             '#'
-    base:                               '#'
-    shift:                              '#'
-    alt:                                '#'
-    shift+alt:                          '#'
-}
-
-key PLUS {
-    label:                              '+'
-    number:                             '+'
-    base:                               '+'
-    shift:                              '+'
-    alt:                                '+'
-    shift+alt:                          '+'
-}
diff --git a/graphics/java/android/graphics/text/LineBreakConfig.java b/graphics/java/android/graphics/text/LineBreakConfig.java
index 7b204f2..13540e0 100644
--- a/graphics/java/android/graphics/text/LineBreakConfig.java
+++ b/graphics/java/android/graphics/text/LineBreakConfig.java
@@ -16,6 +16,9 @@
 
 package android.graphics.text;
 
+import static com.android.text.flags.Flags.FLAG_NO_BREAK_NO_HYPHENATION_SPAN;
+
+import android.annotation.FlaggedApi;
 import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
@@ -34,6 +37,56 @@
 public final class LineBreakConfig {
 
     /**
+     * No hyphenation preference is specified.
+     *
+     * This is a special value of hyphenation preference indicating no hyphenation preference is
+     * specified. When overriding a {@link LineBreakConfig} with another {@link LineBreakConfig}
+     * with {@link Builder#merge(LineBreakConfig)} function, the hyphenation preference of
+     * overridden config will be kept if the hyphenation preference of overriding config is
+     * {@link #HYPHENATION_UNSPECIFIED}.
+     *
+     * <pre>
+     *     val override = LineBreakConfig.Builder()
+     *          .setLineBreakWordStyle(LineBreakConfig.LINE_BREAK_WORD_STYLE_PHRASE)
+     *          .build();  // UNSPECIFIED if no setHyphenation is called.
+     *     val config = LineBreakConfig.Builder()
+     *          .setHyphenation(LineBreakConfig.HYPHENATION_DISABLED)
+     *          .merge(override)
+     *          .build()
+     *     // Here, config has HYPHENATION_DISABLED for line break config and
+     *     // LINE_BREAK_WORD_STYLE_PHRASE for line break word style.
+     * </pre>
+     *
+     * This value is resolved to {@link #HYPHENATION_ENABLED} if this value is used for text
+     * layout/rendering.
+     */
+    @FlaggedApi(FLAG_NO_BREAK_NO_HYPHENATION_SPAN)
+    public static final int HYPHENATION_UNSPECIFIED = -1;
+
+    /**
+     * The hyphenation is disabled.
+     */
+    @FlaggedApi(FLAG_NO_BREAK_NO_HYPHENATION_SPAN)
+    public static final int HYPHENATION_DISABLED = 0;
+
+    /**
+     * The hyphenation is enabled.
+     *
+     * Note: Even if the hyphenation is enabled with a line break strategy
+     * {@link LineBreaker#BREAK_STRATEGY_SIMPLE}, the hyphenation will not be performed unless a
+     * single word cannot meet width constraints.
+     */
+    @FlaggedApi(FLAG_NO_BREAK_NO_HYPHENATION_SPAN)
+    public static final int HYPHENATION_ENABLED = 1;
+
+    /** @hide */
+    @IntDef(prefix = { "HYPHENATION_" }, value = {
+            HYPHENATION_UNSPECIFIED, HYPHENATION_ENABLED, HYPHENATION_DISABLED,
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface Hyphenation {}
+
+    /**
      * No line break style is specified.
      *
      * This is a special value of line break style indicating no style value is specified.
@@ -147,6 +200,8 @@
         private @LineBreakWordStyle int mLineBreakWordStyle =
                 LineBreakConfig.LINE_BREAK_WORD_STYLE_UNSPECIFIED;
 
+        private @Hyphenation int mHyphenation = LineBreakConfig.HYPHENATION_UNSPECIFIED;
+
         /**
          * Builder constructor.
          */
@@ -188,6 +243,9 @@
             if (config.mLineBreakWordStyle != LINE_BREAK_WORD_STYLE_UNSPECIFIED) {
                 mLineBreakWordStyle = config.mLineBreakWordStyle;
             }
+            if (config.mHyphenation != HYPHENATION_UNSPECIFIED) {
+                mHyphenation = config.mHyphenation;
+            }
             return this;
         }
 
@@ -201,9 +259,11 @@
             if (config == null) {
                 mLineBreakStyle = LINE_BREAK_STYLE_UNSPECIFIED;
                 mLineBreakWordStyle = LINE_BREAK_WORD_STYLE_UNSPECIFIED;
+                mHyphenation = HYPHENATION_UNSPECIFIED;
             } else {
                 mLineBreakStyle = config.mLineBreakStyle;
                 mLineBreakWordStyle = config.mLineBreakWordStyle;
+                mHyphenation = config.mHyphenation;
             }
             return this;
         }
@@ -215,6 +275,11 @@
          * {@link #LINE_BREAK_STYLE_UNSPECIFIED}, the line break style is reset to
          * {@link #LINE_BREAK_STYLE_UNSPECIFIED}.
          *
+         * @see <a href="https://unicode.org/reports/tr35/#UnicodeLineBreakStyleIdentifier">
+         *     Unicode Line Break Style Identifier</a>
+         * @see <a href="https://drafts.csswg.org/css-text/#line-break-property">
+         *     CSS Line Break Property</a>
+         *
          * @param lineBreakStyle The new line-break style.
          * @return This {@code Builder}.
          */
@@ -230,6 +295,11 @@
          * with {@link #LINE_BREAK_WORD_STYLE_UNSPECIFIED}, the line break style is reset to
          * {@link #LINE_BREAK_WORD_STYLE_UNSPECIFIED}.
          *
+         * @see <a href="https://unicode.org/reports/tr35/#UnicodeLineBreakWordIdentifier">
+         *     Unicode Line Break Word Identifier</a>
+         * @see <a href="https://drafts.csswg.org/css-text/#word-break-property">
+         *     CSS Word Break Property</a>
+         *
          * @param lineBreakWordStyle The new line-break word style.
          * @return This {@code Builder}.
          */
@@ -239,6 +309,27 @@
         }
 
         /**
+         * Sets the hyphenation preference
+         *
+         * Note: Even if the {@link LineBreakConfig#HYPHENATION_ENABLED} is specified, the
+         * hyphenation will not be performed if the {@link android.widget.TextView} or underlying
+         * {@link android.text.StaticLayout}, {@link LineBreaker} are configured with
+         * {@link LineBreaker#HYPHENATION_FREQUENCY_NONE}.
+         *
+         * Note: Even if the hyphenation is enabled with a line break strategy
+         * {@link LineBreaker#BREAK_STRATEGY_SIMPLE}, the hyphenation will not be performed unless a
+         * single word cannot meet width constraints.
+         *
+         * @param hyphenation The hyphenation preference.
+         * @return This {@code Builder}.
+         */
+        @FlaggedApi(FLAG_NO_BREAK_NO_HYPHENATION_SPAN)
+        public @NonNull Builder setHyphenation(@Hyphenation int hyphenation) {
+            mHyphenation = hyphenation;
+            return this;
+        }
+
+        /**
          * Builds a {@link LineBreakConfig} instance.
          *
          * This method can be called multiple times for generating multiple {@link LineBreakConfig}
@@ -247,7 +338,7 @@
          * @return The {@code LineBreakConfig} instance.
          */
         public @NonNull LineBreakConfig build() {
-            return new LineBreakConfig(mLineBreakStyle, mLineBreakWordStyle);
+            return new LineBreakConfig(mLineBreakStyle, mLineBreakWordStyle, mHyphenation);
         }
     }
 
@@ -275,6 +366,7 @@
 
     private final @LineBreakStyle int mLineBreakStyle;
     private final @LineBreakWordStyle int mLineBreakWordStyle;
+    private final @Hyphenation int mHyphenation;
 
     /**
      * Constructor with line-break parameters.
@@ -283,9 +375,11 @@
      * {@code LineBreakConfig} instance.
      */
     private LineBreakConfig(@LineBreakStyle int lineBreakStyle,
-            @LineBreakWordStyle int lineBreakWordStyle) {
+            @LineBreakWordStyle int lineBreakWordStyle,
+            @Hyphenation int hyphenation) {
         mLineBreakStyle = lineBreakStyle;
         mLineBreakWordStyle = lineBreakWordStyle;
+        mHyphenation = hyphenation;
     }
 
     /**
@@ -340,6 +434,34 @@
     }
 
     /**
+     * Returns a hyphenation preference.
+     *
+     * @return A hyphenation preference.
+     */
+    @FlaggedApi(FLAG_NO_BREAK_NO_HYPHENATION_SPAN)
+    public @Hyphenation  int getHyphenation() {
+        return mHyphenation;
+    }
+
+    /**
+     * Returns a hyphenation preference.
+     *
+     * This method never returns {@link #HYPHENATION_UNSPECIFIED}.
+     *
+     * @return A hyphenation preference.
+     * @hide
+     */
+    public static @Hyphenation int getResolvedHyphenation(
+            @Nullable LineBreakConfig config) {
+        if (config == null) {
+            return HYPHENATION_ENABLED;
+        }
+        return config.mHyphenation == HYPHENATION_UNSPECIFIED
+                ? HYPHENATION_ENABLED : config.mHyphenation;
+    }
+
+
+    /**
      * Generates a new {@link LineBreakConfig} instance merged with given {@code config}.
      *
      * If values of passing {@code config} are unspecified, the original values are kept. For
@@ -366,7 +488,9 @@
                 config.mLineBreakStyle == LINE_BREAK_STYLE_UNSPECIFIED
                         ? mLineBreakStyle : config.mLineBreakStyle,
                 config.mLineBreakWordStyle == LINE_BREAK_WORD_STYLE_UNSPECIFIED
-                        ? mLineBreakWordStyle : config.mLineBreakWordStyle);
+                        ? mLineBreakWordStyle : config.mLineBreakWordStyle,
+                config.mHyphenation == HYPHENATION_UNSPECIFIED
+                        ? mHyphenation : config.mHyphenation);
     }
 
     @Override
@@ -376,12 +500,13 @@
         if (!(o instanceof LineBreakConfig)) return false;
         LineBreakConfig that = (LineBreakConfig) o;
         return (mLineBreakStyle == that.mLineBreakStyle)
-                && (mLineBreakWordStyle == that.mLineBreakWordStyle);
+                && (mLineBreakWordStyle == that.mLineBreakWordStyle)
+                && (mHyphenation == that.mHyphenation);
     }
 
     @Override
     public int hashCode() {
-        return Objects.hash(mLineBreakStyle, mLineBreakWordStyle);
+        return Objects.hash(mLineBreakStyle, mLineBreakWordStyle, mHyphenation);
     }
 
     @Override
@@ -389,6 +514,7 @@
         return "LineBreakConfig{"
                 + "mLineBreakStyle=" + mLineBreakStyle
                 + ", mLineBreakWordStyle=" + mLineBreakWordStyle
+                + ", mHyphenation= " + mHyphenation
                 + '}';
     }
 }
diff --git a/graphics/java/android/graphics/text/MeasuredText.java b/graphics/java/android/graphics/text/MeasuredText.java
index 8317985..2d33e8d 100644
--- a/graphics/java/android/graphics/text/MeasuredText.java
+++ b/graphics/java/android/graphics/text/MeasuredText.java
@@ -301,7 +301,9 @@
             Preconditions.checkArgument(end <= mText.length, "Style exceeds the text length");
             int lbStyle = LineBreakConfig.getResolvedLineBreakStyle(lineBreakConfig);
             int lbWordStyle = LineBreakConfig.getResolvedLineBreakWordStyle(lineBreakConfig);
-            nAddStyleRun(mNativePtr, paint.getNativeInstance(), lbStyle, lbWordStyle,
+            boolean hyphenation = LineBreakConfig.getResolvedHyphenation(lineBreakConfig)
+                    == LineBreakConfig.HYPHENATION_ENABLED;
+            nAddStyleRun(mNativePtr, paint.getNativeInstance(), lbStyle, lbWordStyle, hyphenation,
                     mCurrentOffset, end, isRtl);
             mCurrentOffset = end;
 
@@ -510,6 +512,7 @@
                                                 /* Non Zero */ long paintPtr,
                                                 int lineBreakStyle,
                                                 int lineBreakWordStyle,
+                                                boolean hyphenation,
                                                 @IntRange(from = 0) int start,
                                                 @IntRange(from = 0) int end,
                                                 boolean isRtl);
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/layout/WindowLayoutComponentImpl.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/layout/WindowLayoutComponentImpl.java
index e03e1ec..ccf9552 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/layout/WindowLayoutComponentImpl.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/layout/WindowLayoutComponentImpl.java
@@ -319,17 +319,13 @@
             return features;
         }
 
-        // We will transform the feature bounds to the Activity window, so using the rotation
-        // from the same source (WindowConfiguration) to make sure they are synchronized.
-        final int rotation = windowConfiguration.getDisplayRotation();
-
         for (CommonFoldingFeature baseFeature : storedFeatures) {
             Integer state = convertToExtensionState(baseFeature.getState());
             if (state == null) {
                 continue;
             }
             Rect featureRect = baseFeature.getRect();
-            rotateRectToDisplayRotation(displayId, rotation, featureRect);
+            rotateRectToDisplayRotation(displayId, featureRect);
             transformToWindowSpaceRect(windowConfiguration, featureRect);
 
             if (isZero(featureRect)) {
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/sidecar/SampleSidecarImpl.java b/libs/WindowManager/Jetpack/src/androidx/window/sidecar/SampleSidecarImpl.java
index 15a329bd..5bfb0ebd 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/sidecar/SampleSidecarImpl.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/sidecar/SampleSidecarImpl.java
@@ -120,12 +120,10 @@
         }
 
         List<SidecarDisplayFeature> features = new ArrayList<>();
-        final int rotation = activity.getResources().getConfiguration().windowConfiguration
-                .getDisplayRotation();
         for (CommonFoldingFeature baseFeature : mStoredFeatures) {
             SidecarDisplayFeature feature = new SidecarDisplayFeature();
             Rect featureRect = baseFeature.getRect();
-            rotateRectToDisplayRotation(displayId, rotation, featureRect);
+            rotateRectToDisplayRotation(displayId, featureRect);
             transformToWindowSpaceRect(activity, featureRect);
             feature.setRect(featureRect);
             feature.setType(baseFeature.getType());
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/util/ExtensionHelper.java b/libs/WindowManager/Jetpack/src/androidx/window/util/ExtensionHelper.java
index 6b193fc..9e2611f 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/util/ExtensionHelper.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/util/ExtensionHelper.java
@@ -16,6 +16,8 @@
 
 package androidx.window.util;
 
+import static android.view.Surface.ROTATION_0;
+import static android.view.Surface.ROTATION_180;
 import static android.view.Surface.ROTATION_270;
 import static android.view.Surface.ROTATION_90;
 
@@ -23,14 +25,12 @@
 import android.content.Context;
 import android.graphics.Rect;
 import android.hardware.display.DisplayManagerGlobal;
-import android.util.RotationUtils;
 import android.view.DisplayInfo;
 import android.view.Surface;
 import android.view.WindowManager;
 
 import androidx.annotation.NonNull;
 import androidx.annotation.UiContext;
-import androidx.annotation.VisibleForTesting;
 
 /**
  * Util class for both Sidecar and Extensions.
@@ -44,41 +44,47 @@
     /**
      * Rotates the input rectangle specified in default display orientation to the current display
      * rotation.
-     *
-     * @param displayId the display id.
-     * @param rotation the target rotation relative to the default display orientation.
-     * @param inOutRect the input/output Rect as specified in the default display orientation.
      */
-    public static void rotateRectToDisplayRotation(
-            int displayId, @Surface.Rotation int rotation, @NonNull Rect inOutRect) {
-        final DisplayManagerGlobal dmGlobal = DisplayManagerGlobal.getInstance();
-        final DisplayInfo displayInfo = dmGlobal.getDisplayInfo(displayId);
+    public static void rotateRectToDisplayRotation(int displayId, Rect inOutRect) {
+        DisplayManagerGlobal dmGlobal = DisplayManagerGlobal.getInstance();
+        DisplayInfo displayInfo = dmGlobal.getDisplayInfo(displayId);
+        int rotation = displayInfo.rotation;
 
-        rotateRectToDisplayRotation(displayInfo, rotation, inOutRect);
+        boolean isSideRotation = rotation == ROTATION_90 || rotation == ROTATION_270;
+        int displayWidth = isSideRotation ? displayInfo.logicalHeight : displayInfo.logicalWidth;
+        int displayHeight = isSideRotation ? displayInfo.logicalWidth : displayInfo.logicalHeight;
+
+        inOutRect.intersect(0, 0, displayWidth, displayHeight);
+
+        rotateBounds(inOutRect, displayWidth, displayHeight, rotation);
     }
 
-    @VisibleForTesting
-    static void rotateRectToDisplayRotation(@NonNull DisplayInfo displayInfo,
-            @Surface.Rotation int rotation, @NonNull Rect inOutRect) {
-        // The inOutRect is specified in the default display orientation, so here we need to get
-        // the display width and height in the default orientation to perform the intersection and
-        // rotation.
-        final boolean isSideRotation =
-                displayInfo.rotation == ROTATION_90 || displayInfo.rotation == ROTATION_270;
-        final int baseDisplayWidth =
-                isSideRotation ? displayInfo.logicalHeight : displayInfo.logicalWidth;
-        final int baseDisplayHeight =
-                isSideRotation ? displayInfo.logicalWidth : displayInfo.logicalHeight;
-
-        final boolean success = inOutRect.intersect(0, 0, baseDisplayWidth, baseDisplayHeight);
-        if (!success) {
-            throw new IllegalArgumentException("inOutRect must intersect with the display."
-                    + " inOutRect: " + inOutRect
-                    + ", baseDisplayWidth: " + baseDisplayWidth
-                    + ", baseDisplayHeight: " + baseDisplayHeight);
+    /**
+     * Rotates the input rectangle within parent bounds for a given delta.
+     */
+    private static void rotateBounds(Rect inOutRect, int parentWidth, int parentHeight,
+            @Surface.Rotation int delta) {
+        int origLeft = inOutRect.left;
+        switch (delta) {
+            case ROTATION_0:
+                return;
+            case ROTATION_90:
+                inOutRect.left = inOutRect.top;
+                inOutRect.top = parentWidth - inOutRect.right;
+                inOutRect.right = inOutRect.bottom;
+                inOutRect.bottom = parentWidth - origLeft;
+                return;
+            case ROTATION_180:
+                inOutRect.left = parentWidth - inOutRect.right;
+                inOutRect.right = parentWidth - origLeft;
+                return;
+            case ROTATION_270:
+                inOutRect.left = parentHeight - inOutRect.bottom;
+                inOutRect.bottom = inOutRect.right;
+                inOutRect.right = parentHeight - inOutRect.top;
+                inOutRect.top = origLeft;
+                return;
         }
-
-        RotationUtils.rotateBounds(inOutRect, baseDisplayWidth, baseDisplayHeight, rotation);
     }
 
     /** Transforms rectangle from absolute coordinate space to the window coordinate space. */
diff --git a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/WindowExtensionsTest.java b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/WindowExtensionsTest.java
index 0682692..45564cb 100644
--- a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/WindowExtensionsTest.java
+++ b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/WindowExtensionsTest.java
@@ -32,7 +32,7 @@
 import org.junit.runner.RunWith;
 
 /**
- * Test class for {@link WindowExtensions}.
+ * Test class for {@link WindowExtensionsTest}.
  *
  * Build/Install/Run:
  *  atest WMJetpackUnitTests:WindowExtensionsTest
diff --git a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/util/ExtensionHelperTest.java b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/util/ExtensionHelperTest.java
deleted file mode 100644
index ae783de..0000000
--- a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/util/ExtensionHelperTest.java
+++ /dev/null
@@ -1,130 +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 androidx.window.util;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertThrows;
-
-import android.graphics.Rect;
-import android.platform.test.annotations.Presubmit;
-import android.view.DisplayInfo;
-import android.view.Surface;
-
-import androidx.annotation.NonNull;
-import androidx.test.ext.junit.runners.AndroidJUnit4;
-import androidx.test.filters.SmallTest;
-
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-/**
- * Test class for {@link ExtensionHelper}.
- *
- * Build/Install/Run:
- *  atest WMJetpackUnitTests:ExtensionHelperTest
- */
-@Presubmit
-@SmallTest
-@RunWith(AndroidJUnit4.class)
-public class ExtensionHelperTest {
-
-    private static final int MOCK_DISPLAY_HEIGHT = 1000;
-    private static final int MOCK_DISPLAY_WIDTH = 2000;
-    private static final int MOCK_FEATURE_LEFT = 100;
-    private static final int MOCK_FEATURE_RIGHT = 200;
-
-    private static final int[] ROTATIONS = {
-            Surface.ROTATION_0,
-            Surface.ROTATION_90,
-            Surface.ROTATION_180,
-            Surface.ROTATION_270
-    };
-
-    private static final DisplayInfo[] MOCK_DISPLAY_INFOS = {
-            getMockDisplayInfo(Surface.ROTATION_0),
-            getMockDisplayInfo(Surface.ROTATION_90),
-            getMockDisplayInfo(Surface.ROTATION_180),
-            getMockDisplayInfo(Surface.ROTATION_270),
-    };
-
-    @Test
-    public void testRotateRectToDisplayRotation() {
-        for (int rotation : ROTATIONS) {
-            final Rect expectedResult = getExpectedFeatureRectAfterRotation(rotation);
-            // The method should return correctly rotated Rect even if the requested rotation value
-            // differs from the rotation in DisplayInfo. This is because the WindowConfiguration is
-            // not always synced with DisplayInfo.
-            for (DisplayInfo displayInfo : MOCK_DISPLAY_INFOS) {
-                final Rect rect = getMockFeatureRect();
-                ExtensionHelper.rotateRectToDisplayRotation(displayInfo, rotation, rect);
-                assertEquals(
-                        "Result Rect should equal to expected for rotation: " + rotation
-                                + "; displayInfo: " + displayInfo,
-                        expectedResult, rect);
-            }
-        }
-    }
-
-    @Test
-    public void testRotateRectToDisplayRotation_invalidInputRect() {
-        final Rect invalidRect = new Rect(
-                MOCK_DISPLAY_WIDTH + 10, 0, MOCK_DISPLAY_WIDTH + 10, MOCK_DISPLAY_HEIGHT);
-        assertThrows(IllegalArgumentException.class,
-                () -> ExtensionHelper.rotateRectToDisplayRotation(
-                        MOCK_DISPLAY_INFOS[0], ROTATIONS[0], invalidRect));
-    }
-
-
-    @NonNull
-    private static DisplayInfo getMockDisplayInfo(@Surface.Rotation int rotation) {
-        final DisplayInfo displayInfo = new DisplayInfo();
-        displayInfo.rotation = rotation;
-        if (rotation == Surface.ROTATION_0 || rotation == Surface.ROTATION_180) {
-            displayInfo.logicalWidth = MOCK_DISPLAY_WIDTH;
-            displayInfo.logicalHeight = MOCK_DISPLAY_HEIGHT;
-        } else {
-            displayInfo.logicalWidth = MOCK_DISPLAY_HEIGHT;
-            displayInfo.logicalHeight = MOCK_DISPLAY_WIDTH;
-        }
-        return displayInfo;
-    }
-
-    @NonNull
-    private static Rect getMockFeatureRect() {
-        return new Rect(MOCK_FEATURE_LEFT, 0, MOCK_FEATURE_RIGHT, MOCK_DISPLAY_HEIGHT);
-    }
-
-    @NonNull
-    private static Rect getExpectedFeatureRectAfterRotation(@Surface.Rotation int rotation) {
-        switch (rotation) {
-            case Surface.ROTATION_0:
-                return new Rect(
-                        MOCK_FEATURE_LEFT, 0, MOCK_FEATURE_RIGHT, MOCK_DISPLAY_HEIGHT);
-            case Surface.ROTATION_90:
-                return new Rect(0, MOCK_DISPLAY_WIDTH - MOCK_FEATURE_RIGHT,
-                        MOCK_DISPLAY_HEIGHT, MOCK_DISPLAY_WIDTH - MOCK_FEATURE_LEFT);
-            case Surface.ROTATION_180:
-                return new Rect(MOCK_DISPLAY_WIDTH - MOCK_FEATURE_RIGHT, 0,
-                        MOCK_DISPLAY_WIDTH - MOCK_FEATURE_LEFT, MOCK_DISPLAY_HEIGHT);
-            case Surface.ROTATION_270:
-                return new Rect(0, MOCK_FEATURE_LEFT, MOCK_DISPLAY_HEIGHT,
-                        MOCK_FEATURE_RIGHT);
-            default:
-                throw new IllegalArgumentException("Unknown rotation value: " + rotation);
-        }
-    }
-}
diff --git a/libs/WindowManager/Shell/res/drawable/ic_floating_landscape.xml b/libs/WindowManager/Shell/res/drawable/ic_floating_landscape.xml
new file mode 100644
index 0000000..8ef3307
--- /dev/null
+++ b/libs/WindowManager/Shell/res/drawable/ic_floating_landscape.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2023 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License
+  -->
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24dp"
+    android:height="24dp"
+    android:viewportWidth="24"
+    android:viewportHeight="24">
+  <path
+      android:pathData="M4,18H20V6H4V18ZM22,18C22,19.1 21.1,20 20,20H4C2.9,20 2,19.1 2,18V6C2,4.9 2.9,4 4,4H20C21.1,4 22,4.9 22,6V18ZM13,8H18V14H13V8Z"
+      android:fillColor="#455A64"
+      android:fillType="evenOdd"/>
+</vector>
diff --git a/libs/WindowManager/Shell/res/layout/bubble_bar_stack_education.xml b/libs/WindowManager/Shell/res/layout/bubble_bar_stack_education.xml
new file mode 100644
index 0000000..b489a5c
--- /dev/null
+++ b/libs/WindowManager/Shell/res/layout/bubble_bar_stack_education.xml
@@ -0,0 +1,56 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2023 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License
+  -->
+<com.android.wm.shell.common.bubbles.BubblePopupView
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="wrap_content"
+    android:layout_height="wrap_content"
+    android:layout_gravity="bottom|end"
+    android:layout_margin="@dimen/bubble_popup_margin_horizontal"
+    android:layout_marginBottom="120dp"
+    android:elevation="@dimen/bubble_manage_menu_elevation"
+    android:gravity="center_horizontal"
+    android:orientation="vertical">
+
+    <ImageView
+        android:layout_width="32dp"
+        android:layout_height="32dp"
+        android:tint="?android:attr/colorAccent"
+        android:contentDescription="@null"
+        android:src="@drawable/ic_floating_landscape"/>
+
+    <TextView
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_marginTop="16dp"
+        android:maxWidth="@dimen/bubble_popup_content_max_width"
+        android:maxLines="1"
+        android:ellipsize="end"
+        android:textAppearance="@android:style/TextAppearance.DeviceDefault.Headline"
+        android:textColor="?android:attr/textColorPrimary"
+        android:text="@string/bubble_bar_education_stack_title"/>
+
+    <TextView
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_marginTop="16dp"
+        android:maxWidth="@dimen/bubble_popup_content_max_width"
+        android:textAppearance="@android:style/TextAppearance.DeviceDefault"
+        android:textColor="?android:attr/textColorSecondary"
+        android:textAlignment="center"
+        android:text="@string/bubble_bar_education_stack_text"/>
+
+</com.android.wm.shell.common.bubbles.BubblePopupView>
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/res/values-television/config.xml b/libs/WindowManager/Shell/res/values-television/config.xml
index cc0333e..5f9dbdb 100644
--- a/libs/WindowManager/Shell/res/values-television/config.xml
+++ b/libs/WindowManager/Shell/res/values-television/config.xml
@@ -44,12 +44,11 @@
     if a custom action is present before closing it. -->
     <integer name="config_pipForceCloseDelay">5000</integer>
 
-    <!-- Animation duration when exit starting window: fade out icon -->
-    <integer name="starting_window_app_reveal_icon_fade_out_duration">0</integer>
-
     <!-- Animation duration when exit starting window: reveal app -->
-    <integer name="starting_window_app_reveal_anim_delay">0</integer>
+    <integer name="starting_window_app_reveal_anim_duration">500</integer>
 
-    <!-- Animation duration when exit starting window: reveal app -->
-    <integer name="starting_window_app_reveal_anim_duration">0</integer>
+    <!-- Default animation type when hiding the starting window. The possible values are:
+          - 0 for radial vanish + slide up
+          - 1 for fade out -->
+    <integer name="starting_window_exit_animation_type">1</integer>
 </resources>
diff --git a/libs/WindowManager/Shell/res/values/config.xml b/libs/WindowManager/Shell/res/values/config.xml
index a3916b7..97a9d48 100644
--- a/libs/WindowManager/Shell/res/values/config.xml
+++ b/libs/WindowManager/Shell/res/values/config.xml
@@ -83,6 +83,11 @@
     <!-- Animation duration when exit starting window: reveal app -->
     <integer name="starting_window_app_reveal_anim_duration">266</integer>
 
+    <!-- Default animation type when hiding the starting window. The possible values are:
+          - 0 for radial vanish + slide up
+          - 1 for fade out -->
+    <integer name="starting_window_exit_animation_type">0</integer>
+
     <!-- Default insets [LEFT/RIGHTxTOP/BOTTOM] from the screen edge for picture-in-picture windows.
          These values are in DPs and will be converted to pixel sizes internally. -->
     <string translatable="false" name="config_defaultPictureInPictureScreenEdgeInsets">
diff --git a/libs/WindowManager/Shell/res/values/strings.xml b/libs/WindowManager/Shell/res/values/strings.xml
index 00c63d7..eabe3a4 100644
--- a/libs/WindowManager/Shell/res/values/strings.xml
+++ b/libs/WindowManager/Shell/res/values/strings.xml
@@ -163,6 +163,10 @@
     <!-- [CHAR LIMIT=NONE] Empty overflow subtitle -->
     <string name="bubble_overflow_empty_subtitle">Recent bubbles and dismissed bubbles will appear here</string>
 
+    <!-- Title text for the bubble bar feature education cling shown when a bubble is on screen for the first time. [CHAR LIMIT=60]-->
+    <string name="bubble_bar_education_stack_title">Chat using bubbles</string>
+    <!-- Descriptive text for the bubble bar feature education cling shown when a bubble is on screen for the first time. [CHAR LIMIT=NONE] -->
+    <string name="bubble_bar_education_stack_text">New conversations appear as icons in a bottom corner of your screen. Tap to expand them or drag to dismiss them.</string>
     <!-- Title text for the bubble bar "manage" button tool tip highlighting where users can go to control bubble settings. [CHAR LIMIT=60]-->
     <string name="bubble_bar_education_manage_title">Control bubbles anytime</string>
     <!-- Descriptive text for the bubble bar "manage" button tool tip highlighting where users can go to control bubble settings. [CHAR LIMIT=80]-->
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java
index dfdc79e..f259902 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleController.java
@@ -56,6 +56,7 @@
 import android.content.pm.UserInfo;
 import android.content.res.Configuration;
 import android.graphics.PixelFormat;
+import android.graphics.Point;
 import android.graphics.Rect;
 import android.graphics.drawable.Icon;
 import android.os.Binder;
@@ -1063,6 +1064,15 @@
         }
     }
 
+    /**
+     * Show bubble bar user education relative to the reference position.
+     * @param position the reference position in Screen coordinates.
+     */
+    public void showUserEducation(Point position) {
+        if (mLayerView == null) return;
+        mLayerView.showUserEducation(position);
+    }
+
     @VisibleForTesting
     public boolean isBubbleNotificationSuppressedFromShade(String key, String groupKey) {
         boolean isSuppressedBubble = (mBubbleData.hasAnyBubbleWithKey(key)
@@ -1115,6 +1125,16 @@
     }
 
     /**
+     * Expands the stack if the selected bubble is present. This is currently used when user
+     * education view is clicked to expand the selected bubble.
+     */
+    public void expandStackWithSelectedBubble() {
+        if (mBubbleData.getSelectedBubble() != null) {
+            mBubbleData.setExpanded(true);
+        }
+    }
+
+    /**
      * Expands and selects the provided bubble as long as it already exists in the stack or the
      * overflow. This is currently used when opening a bubble via clicking on a conversation widget.
      */
@@ -1730,7 +1750,8 @@
                         + " expandedChanged=" + update.expandedChanged
                         + " selectionChanged=" + update.selectionChanged
                         + " suppressed=" + (update.suppressedBubble != null)
-                        + " unsuppressed=" + (update.unsuppressedBubble != null));
+                        + " unsuppressed=" + (update.unsuppressedBubble != null)
+                        + " shouldShowEducation=" + update.shouldShowEducation);
             }
 
             ensureBubbleViewsAndWindowCreated();
@@ -2155,6 +2176,12 @@
         public void onBubbleDrag(String bubbleKey, boolean isBeingDragged) {
             mMainExecutor.execute(() -> mController.onBubbleDrag(bubbleKey, isBeingDragged));
         }
+
+        @Override
+        public void showUserEducation(int positionX, int positionY) {
+            mMainExecutor.execute(() ->
+                    mController.showUserEducation(new Point(positionX, positionY)));
+        }
     }
 
     private class BubblesImpl implements Bubbles {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleData.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleData.java
index c6f74af..595a4af 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleData.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleData.java
@@ -77,6 +77,7 @@
         boolean orderChanged;
         boolean suppressedSummaryChanged;
         boolean expanded;
+        boolean shouldShowEducation;
         @Nullable BubbleViewProvider selectedBubble;
         @Nullable Bubble addedBubble;
         @Nullable Bubble updatedBubble;
@@ -126,6 +127,7 @@
 
             bubbleBarUpdate.expandedChanged = expandedChanged;
             bubbleBarUpdate.expanded = expanded;
+            bubbleBarUpdate.shouldShowEducation = shouldShowEducation;
             if (selectionChanged) {
                 bubbleBarUpdate.selectedBubbleKey = selectedBubble != null
                         ? selectedBubble.getKey()
@@ -165,6 +167,7 @@
          */
         BubbleBarUpdate getInitialState() {
             BubbleBarUpdate bubbleBarUpdate = new BubbleBarUpdate();
+            bubbleBarUpdate.shouldShowEducation = shouldShowEducation;
             for (int i = 0; i < bubbles.size(); i++) {
                 bubbleBarUpdate.currentBubbleList.add(bubbles.get(i).asBubbleBarBubble());
             }
@@ -187,6 +190,7 @@
 
     private final Context mContext;
     private final BubblePositioner mPositioner;
+    private final BubbleEducationController mEducationController;
     private final Executor mMainExecutor;
     /** Bubbles that are actively in the stack. */
     private final List<Bubble> mBubbles;
@@ -233,10 +237,11 @@
     private HashMap<String, String> mSuppressedGroupKeys = new HashMap<>();
 
     public BubbleData(Context context, BubbleLogger bubbleLogger, BubblePositioner positioner,
-            Executor mainExecutor) {
+            BubbleEducationController educationController, Executor mainExecutor) {
         mContext = context;
         mLogger = bubbleLogger;
         mPositioner = positioner;
+        mEducationController = educationController;
         mMainExecutor = mainExecutor;
         mOverflow = new BubbleOverflow(context, positioner);
         mBubbles = new ArrayList<>();
@@ -447,6 +452,7 @@
         if (bubble.shouldAutoExpand()) {
             bubble.setShouldAutoExpand(false);
             setSelectedBubbleInternal(bubble);
+
             if (!mExpanded) {
                 setExpandedInternal(true);
             }
@@ -877,6 +883,9 @@
 
     private void dispatchPendingChanges() {
         if (mListener != null && mStateChange.anythingChanged()) {
+            mStateChange.shouldShowEducation = mSelectedBubble != null
+                    && mEducationController.shouldShowStackEducation(mSelectedBubble)
+                    && !mExpanded;
             mListener.applyUpdate(mStateChange);
         }
         mStateChange = new Update(mBubbles, mOverflowBubbles);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleDebugConfig.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleDebugConfig.java
index 1c0e052..f56b171 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleDebugConfig.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleDebugConfig.java
@@ -44,7 +44,7 @@
     static final boolean DEBUG_BUBBLE_EXPANDED_VIEW = false;
     static final boolean DEBUG_EXPERIMENTS = true;
     static final boolean DEBUG_OVERFLOW = false;
-    static final boolean DEBUG_USER_EDUCATION = false;
+    public static final boolean DEBUG_USER_EDUCATION = false;
     static final boolean DEBUG_POSITIONER = false;
     public static final boolean DEBUG_COLLAPSE_ANIMATOR = false;
     public static boolean DEBUG_EXPANDED_VIEW_DRAGGING = false;
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java
index d10de83..3f9dcc1 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/BubbleStackView.java
@@ -25,6 +25,7 @@
 import static com.android.wm.shell.bubbles.BubbleDebugConfig.TAG_BUBBLES;
 import static com.android.wm.shell.bubbles.BubbleDebugConfig.TAG_WITH_CLASS_NAME;
 import static com.android.wm.shell.bubbles.BubblePositioner.NUM_VISIBLE_WHEN_RESTING;
+import static com.android.wm.shell.common.bubbles.BubbleConstants.BUBBLE_EXPANDED_SCRIM_ALPHA;
 import static com.android.wm.shell.protolog.ShellProtoLogGroup.WM_SHELL_BUBBLES;
 
 import android.animation.Animator;
@@ -131,9 +132,6 @@
 
     private static final int EXPANDED_VIEW_ALPHA_ANIMATION_DURATION = 150;
 
-    /** Should be kept in sync with value in TaskbarScrimViewController. */
-    private static final float SCRIM_ALPHA = 0.32f;
-
     /** Minimum alpha value for scrim when alpha is being changed via drag */
     private static final float MIN_SCRIM_ALPHA_FOR_DRAG = 0.2f;
 
@@ -780,14 +778,15 @@
         private float getScrimAlphaForDrag(float dragAmount) {
             // dragAmount should be negative as we allow scroll up only
             if (mExpandedBubble != null && mExpandedBubble.getExpandedView() != null) {
-                float alphaRange = SCRIM_ALPHA - MIN_SCRIM_ALPHA_FOR_DRAG;
+                float alphaRange = BUBBLE_EXPANDED_SCRIM_ALPHA - MIN_SCRIM_ALPHA_FOR_DRAG;
 
                 int dragMax = mExpandedBubble.getExpandedView().getContentHeight();
                 float dragFraction = dragAmount / dragMax;
 
-                return Math.max(SCRIM_ALPHA - alphaRange * dragFraction, MIN_SCRIM_ALPHA_FOR_DRAG);
+                return Math.max(BUBBLE_EXPANDED_SCRIM_ALPHA - alphaRange * dragFraction,
+                        MIN_SCRIM_ALPHA_FOR_DRAG);
             }
-            return SCRIM_ALPHA;
+            return BUBBLE_EXPANDED_SCRIM_ALPHA;
         }
     };
 
@@ -2215,7 +2214,7 @@
         if (show) {
             mScrim.animate()
                     .setInterpolator(ALPHA_IN)
-                    .alpha(SCRIM_ALPHA)
+                    .alpha(BUBBLE_EXPANDED_SCRIM_ALPHA)
                     .setListener(listener)
                     .start();
         } else {
@@ -2944,7 +2943,7 @@
         mBubbleController.getSysuiProxy().onManageMenuExpandChanged(show);
         mManageMenuScrim.animate()
                 .setInterpolator(show ? ALPHA_IN : ALPHA_OUT)
-                .alpha(show ? SCRIM_ALPHA : 0f)
+                .alpha(show ? BUBBLE_EXPANDED_SCRIM_ALPHA : 0f)
                 .withEndAction(endAction)
                 .start();
 
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/IBubbles.aidl b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/IBubbles.aidl
index 4dda068..5776ad1 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/IBubbles.aidl
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/IBubbles.aidl
@@ -39,4 +39,6 @@
 
     oneway void onBubbleDrag(in String key, in boolean isBeingDragged) = 7;
 
+    oneway void showUserEducation(in int positionX, in int positionY) = 8;
+
 }
\ No newline at end of file
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerView.java
index 8f11253..e788341 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarLayerView.java
@@ -21,6 +21,7 @@
 
 import android.annotation.Nullable;
 import android.content.Context;
+import android.graphics.Point;
 import android.graphics.Rect;
 import android.graphics.Region;
 import android.graphics.drawable.ColorDrawable;
@@ -36,6 +37,8 @@
 
 import java.util.function.Consumer;
 
+import kotlin.Unit;
+
 /**
  * Similar to {@link com.android.wm.shell.bubbles.BubbleStackView}, this view is added to window
  * manager to display bubbles. However, it is only used when bubbles are being displayed in
@@ -111,7 +114,7 @@
         getViewTreeObserver().removeOnComputeInternalInsetsListener(this);
 
         if (mExpandedView != null) {
-            mEducationViewController.hideManageEducation(/* animated = */ false);
+            mEducationViewController.hideEducation(/* animated = */ false);
             removeView(mExpandedView);
             mExpandedView = null;
         }
@@ -171,7 +174,9 @@
             mExpandedView.setListener(new BubbleBarExpandedView.Listener() {
                 @Override
                 public void onTaskCreated() {
-                    mEducationViewController.maybeShowManageEducation(b, mExpandedView);
+                    if (mEducationViewController != null && mExpandedView != null) {
+                        mEducationViewController.maybeShowManageEducation(b, mExpandedView);
+                    }
                 }
 
                 @Override
@@ -190,6 +195,10 @@
             addView(mExpandedView, new FrameLayout.LayoutParams(width, height));
         }
 
+        if (mEducationViewController.isEducationVisible()) {
+            mEducationViewController.hideEducation(/* animated = */ true);
+        }
+
         mIsExpanded = true;
         mBubbleController.getSysuiProxy().onStackExpandChanged(true);
         mAnimationHelper.animateExpansion(mExpandedBubble, () -> {
@@ -210,7 +219,7 @@
     public void collapse() {
         mIsExpanded = false;
         final BubbleBarExpandedView viewToRemove = mExpandedView;
-        mEducationViewController.hideManageEducation(/* animated = */ true);
+        mEducationViewController.hideEducation(/* animated = */ true);
         mAnimationHelper.animateCollapse(() -> removeView(viewToRemove));
         mBubbleController.getSysuiProxy().onStackExpandChanged(false);
         mExpandedView = null;
@@ -218,6 +227,21 @@
         showScrim(false);
     }
 
+    /**
+     * Show bubble bar user education relative to the reference position.
+     * @param position the reference position in Screen coordinates.
+     */
+    public void showUserEducation(Point position) {
+        mEducationViewController.showStackEducation(position, /* root = */ this, () -> {
+            // When the user education is clicked hide it and expand the selected bubble
+            mEducationViewController.hideEducation(/* animated = */ true, () -> {
+                mBubbleController.expandStackWithSelectedBubble();
+                return Unit.INSTANCE;
+            });
+            return Unit.INSTANCE;
+        });
+    }
+
     /** Sets the function to call to un-bubble the given conversation. */
     public void setUnBubbleConversationCallback(
             @Nullable Consumer<String> unBubbleConversationCallback) {
@@ -226,8 +250,8 @@
 
     /** Hides the current modal education/menu view, expanded view or collapses the bubble stack */
     private void hideMenuOrCollapse() {
-        if (mEducationViewController.isManageEducationVisible()) {
-            mEducationViewController.hideManageEducation(/* animated = */ true);
+        if (mEducationViewController.isEducationVisible()) {
+            mEducationViewController.hideEducation(/* animated = */ true);
         } else if (isExpanded() && mExpandedView != null) {
             mExpandedView.hideMenuOrCollapse();
         } else {
@@ -275,7 +299,7 @@
      */
     private void getTouchableRegion(Region outRegion) {
         mTempRect.setEmpty();
-        if (mIsExpanded) {
+        if (mIsExpanded || mEducationViewController.isEducationVisible()) {
             getBoundsOnScreen(mTempRect);
             outRegion.op(mTempRect, Region.Op.UNION);
         }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleEducationViewController.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleEducationViewController.kt
index 7b39c6f..ee552ae 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleEducationViewController.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleEducationViewController.kt
@@ -15,23 +15,34 @@
  */
 package com.android.wm.shell.bubbles.bar
 
+import android.annotation.LayoutRes
 import android.content.Context
+import android.graphics.Point
+import android.graphics.Rect
+import android.util.Log
 import android.view.LayoutInflater
+import android.view.View
 import android.view.ViewGroup
+import android.widget.FrameLayout
 import androidx.core.view.doOnLayout
 import androidx.dynamicanimation.animation.DynamicAnimation
 import androidx.dynamicanimation.animation.SpringForce
 import com.android.wm.shell.R
 import com.android.wm.shell.animation.PhysicsAnimator
+import com.android.wm.shell.bubbles.BubbleDebugConfig.DEBUG_USER_EDUCATION
+import com.android.wm.shell.bubbles.BubbleDebugConfig.TAG_BUBBLES
+import com.android.wm.shell.bubbles.BubbleDebugConfig.TAG_WITH_CLASS_NAME
 import com.android.wm.shell.bubbles.BubbleEducationController
 import com.android.wm.shell.bubbles.BubbleViewProvider
 import com.android.wm.shell.bubbles.setup
+import com.android.wm.shell.common.bubbles.BubblePopupDrawable
 import com.android.wm.shell.common.bubbles.BubblePopupView
+import kotlin.math.roundToInt
 
 /** Manages bubble education presentation and animation */
 class BubbleEducationViewController(private val context: Context, private val listener: Listener) {
     interface Listener {
-        fun onManageEducationVisibilityChanged(isVisible: Boolean)
+        fun onEducationVisibilityChanged(isVisible: Boolean)
     }
 
     private var rootView: ViewGroup? = null
@@ -45,61 +56,112 @@
         )
     }
 
+    private val scrimView by lazy {
+        View(context).apply {
+            importantForAccessibility = View.IMPORTANT_FOR_ACCESSIBILITY_NO
+            setOnClickListener { hideEducation(animated = true) }
+        }
+    }
+
     private val controller by lazy { BubbleEducationController(context) }
 
     /** Whether the education view is visible or being animated */
-    val isManageEducationVisible: Boolean
+    val isEducationVisible: Boolean
         get() = educationView != null && rootView != null
 
     /**
+     * Hide the current education view if visible
+     *
+     * @param animated whether should hide with animation
+     */
+    @JvmOverloads
+    fun hideEducation(animated: Boolean, endActions: () -> Unit = {}) {
+        log { "hideEducation animated: $animated" }
+
+        if (animated) {
+            animateTransition(show = false) {
+                cleanUp()
+                endActions()
+                listener.onEducationVisibilityChanged(isVisible = false)
+            }
+        } else {
+            cleanUp()
+            endActions()
+            listener.onEducationVisibilityChanged(isVisible = false)
+        }
+    }
+
+    /**
+     * Show bubble bar stack user education.
+     *
+     * @param position the reference position for the user education in Screen coordinates.
+     * @param root the view to show user education in.
+     * @param educationClickHandler the on click handler for the user education view
+     */
+    fun showStackEducation(position: Point, root: ViewGroup, educationClickHandler: () -> Unit) {
+        hideEducation(animated = false)
+        log { "showStackEducation at: $position" }
+
+        educationView =
+            createEducationView(R.layout.bubble_bar_stack_education, root).apply {
+                setArrowDirection(BubblePopupDrawable.ArrowDirection.DOWN)
+                setArrowPosition(BubblePopupDrawable.ArrowPosition.End)
+                updateEducationPosition(view = this, position, root)
+                val arrowToEdgeOffset = popupDrawable?.config?.cornerRadius ?: 0f
+                doOnLayout {
+                    it.pivotX = it.width - arrowToEdgeOffset
+                    it.pivotY = it.height.toFloat()
+                }
+                setOnClickListener { educationClickHandler() }
+            }
+
+        rootView = root
+        animator = createAnimator()
+
+        root.addView(scrimView)
+        root.addView(educationView)
+        animateTransition(show = true) {
+            controller.hasSeenStackEducation = true
+            listener.onEducationVisibilityChanged(isVisible = true)
+        }
+    }
+
+    /**
      * Show manage bubble education if hasn't been shown before
      *
      * @param bubble the bubble used for the manage education check
      * @param root the view to show manage education in
      */
     fun maybeShowManageEducation(bubble: BubbleViewProvider, root: ViewGroup) {
+        log { "maybeShowManageEducation bubble: $bubble" }
         if (!controller.shouldShowManageEducation(bubble)) return
         showManageEducation(root)
     }
 
     /**
-     * Hide the manage education view if visible
-     *
-     * @param animated whether should hide with animation
-     */
-    fun hideManageEducation(animated: Boolean) {
-        rootView?.let {
-            fun cleanUp() {
-                it.removeView(educationView)
-                rootView = null
-                listener.onManageEducationVisibilityChanged(isVisible = false)
-            }
-
-            if (animated) {
-                animateTransition(show = false, ::cleanUp)
-            } else {
-                cleanUp()
-            }
-        }
-    }
-
-    /**
      * Show manage education with animation
      *
      * @param root the view to show manage education in
      */
     private fun showManageEducation(root: ViewGroup) {
-        hideManageEducation(animated = false)
-        if (educationView == null) {
-            val eduView = createEducationView(root)
-            educationView = eduView
-            animator = createAnimation(eduView)
-        }
-        root.addView(educationView)
+        hideEducation(animated = false)
+        log { "showManageEducation" }
+
+        educationView =
+            createEducationView(R.layout.bubble_bar_manage_education, root).apply {
+                pivotY = 0f
+                doOnLayout { it.pivotX = it.width / 2f }
+                setOnClickListener { hideEducation(animated = true) }
+            }
+
         rootView = root
+        animator = createAnimator()
+
+        root.addView(scrimView)
+        root.addView(educationView)
         animateTransition(show = true) {
             controller.hasSeenManageEducation = true
-            listener.onManageEducationVisibilityChanged(isVisible = true)
+            listener.onEducationVisibilityChanged(isVisible = true)
         }
     }
 
@@ -110,39 +172,75 @@
      * @param endActions a closure to be called when the animation completes
      */
     private fun animateTransition(show: Boolean, endActions: () -> Unit) {
-        animator?.let { animator ->
-            animator
-                .spring(DynamicAnimation.ALPHA, if (show) 1f else 0f)
-                .spring(DynamicAnimation.SCALE_X, if (show) 1f else EDU_SCALE_HIDDEN)
-                .spring(DynamicAnimation.SCALE_Y, if (show) 1f else EDU_SCALE_HIDDEN)
-                .withEndActions(endActions)
-                .start()
-        } ?: endActions()
+        animator
+            ?.spring(DynamicAnimation.ALPHA, if (show) 1f else 0f)
+            ?.spring(DynamicAnimation.SCALE_X, if (show) 1f else EDU_SCALE_HIDDEN)
+            ?.spring(DynamicAnimation.SCALE_Y, if (show) 1f else EDU_SCALE_HIDDEN)
+            ?.withEndActions(endActions)
+            ?.start()
+            ?: endActions()
     }
 
-    private fun createEducationView(root: ViewGroup): BubblePopupView {
-        val view =
-            LayoutInflater.from(context).inflate(R.layout.bubble_bar_manage_education, root, false)
-                as BubblePopupView
+    /** Remove education view from the root and clean up all relative properties */
+    private fun cleanUp() {
+        log { "cleanUp" }
+        rootView?.removeView(educationView)
+        rootView?.removeView(scrimView)
+        educationView = null
+        rootView = null
+        animator = null
+    }
 
-        return view.apply {
-            setup()
-            alpha = 0f
-            pivotY = 0f
-            scaleX = EDU_SCALE_HIDDEN
-            scaleY = EDU_SCALE_HIDDEN
-            doOnLayout { it.pivotX = it.width / 2f }
-            setOnClickListener { hideManageEducation(animated = true) }
+    /**
+     * Create education view by inflating layout provided.
+     *
+     * @param layout layout resource id to inflate. The root view should be [BubblePopupView]
+     * @param root view group to use as root for inflation, is not attached to root
+     */
+    private fun createEducationView(@LayoutRes layout: Int, root: ViewGroup): BubblePopupView {
+        val view = LayoutInflater.from(context).inflate(layout, root, false) as BubblePopupView
+        view.setup()
+        view.alpha = 0f
+        view.scaleX = EDU_SCALE_HIDDEN
+        view.scaleY = EDU_SCALE_HIDDEN
+        return view
+    }
+
+    /** Create animator for the user education transitions */
+    private fun createAnimator(): PhysicsAnimator<BubblePopupView>? {
+        return educationView?.let {
+            PhysicsAnimator.getInstance(it).apply { setDefaultSpringConfig(springConfig) }
         }
     }
 
-    private fun createAnimation(view: BubblePopupView): PhysicsAnimator<BubblePopupView> {
-        val animator = PhysicsAnimator.getInstance(view)
-        animator.setDefaultSpringConfig(springConfig)
-        return animator
+    /**
+     * Update user education view position relative to the reference position
+     *
+     * @param view the user education view to layout
+     * @param position the reference position in Screen coordinates
+     * @param root the root view to use for the layout
+     */
+    private fun updateEducationPosition(view: BubblePopupView, position: Point, root: ViewGroup) {
+        val rootBounds = Rect()
+        // Get root bounds on screen as position is in screen coordinates
+        root.getBoundsOnScreen(rootBounds)
+        // Get the offset to the arrow from the edge of the education view
+        val arrowToEdgeOffset =
+            view.popupDrawable?.config?.let { it.cornerRadius + it.arrowWidth / 2f }?.roundToInt()
+                ?: 0
+        // Calculate education view margins
+        val params = view.layoutParams as FrameLayout.LayoutParams
+        params.bottomMargin = rootBounds.bottom - position.y
+        params.rightMargin = rootBounds.right - position.x - arrowToEdgeOffset
+        view.layoutParams = params
+    }
+
+    private fun log(msg: () -> String) {
+        if (DEBUG_USER_EDUCATION) Log.d(TAG, msg())
     }
 
     companion object {
+        private val TAG = if (TAG_WITH_CLASS_NAME) "BubbleEducationViewController" else TAG_BUBBLES
         private const val EDU_SCALE_HIDDEN = 0.5f
     }
 }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/bubbles/BubbleBarUpdate.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/bubbles/BubbleBarUpdate.java
index 8142347..fc627a8 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/bubbles/BubbleBarUpdate.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/bubbles/BubbleBarUpdate.java
@@ -35,6 +35,7 @@
 
     public boolean expandedChanged;
     public boolean expanded;
+    public boolean shouldShowEducation;
     @Nullable
     public String selectedBubbleKey;
     @Nullable
@@ -61,6 +62,7 @@
     public BubbleBarUpdate(Parcel parcel) {
         expandedChanged = parcel.readBoolean();
         expanded = parcel.readBoolean();
+        shouldShowEducation = parcel.readBoolean();
         selectedBubbleKey = parcel.readString();
         addedBubble = parcel.readParcelable(BubbleInfo.class.getClassLoader(),
                 BubbleInfo.class);
@@ -95,6 +97,7 @@
         return "BubbleBarUpdate{ expandedChanged=" + expandedChanged
                 + " expanded=" + expanded
                 + " selectedBubbleKey=" + selectedBubbleKey
+                + " shouldShowEducation=" + shouldShowEducation
                 + " addedBubble=" + addedBubble
                 + " updatedBubble=" + updatedBubble
                 + " suppressedBubbleKey=" + suppressedBubbleKey
@@ -114,6 +117,7 @@
     public void writeToParcel(Parcel parcel, int flags) {
         parcel.writeBoolean(expandedChanged);
         parcel.writeBoolean(expanded);
+        parcel.writeBoolean(shouldShowEducation);
         parcel.writeString(selectedBubbleKey);
         parcel.writeParcelable(addedBubble, flags);
         parcel.writeParcelable(updatedBubble, flags);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/bubbles/BubbleConstants.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/bubbles/BubbleConstants.java
new file mode 100644
index 0000000..0329b8d
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/bubbles/BubbleConstants.java
@@ -0,0 +1,26 @@
+/*
+ * 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.wm.shell.common.bubbles;
+
+/**
+ * Constants shared between bubbles in shell & things we have to do for bubbles in launcher.
+ */
+public class BubbleConstants {
+
+    /** The alpha for the scrim shown when bubbles are expanded. */
+    public static float BUBBLE_EXPANDED_SCRIM_ALPHA = .32f;
+}
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/bubbles/BubblePopupDrawable.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/common/bubbles/BubblePopupDrawable.kt
index 1fd22d0a..887af17 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/bubbles/BubblePopupDrawable.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/bubbles/BubblePopupDrawable.kt
@@ -31,7 +31,7 @@
 import kotlin.properties.Delegates
 
 /** A drawable for the [BubblePopupView] that draws a popup background with a directional arrow */
-class BubblePopupDrawable(private val config: Config) : Drawable() {
+class BubblePopupDrawable(val config: Config) : Drawable() {
     /** The direction of the arrow in the popup drawable */
     enum class ArrowDirection {
         UP,
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/bubbles/BubblePopupView.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/common/bubbles/BubblePopupView.kt
index f8a4946..444fbf7 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/bubbles/BubblePopupView.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/bubbles/BubblePopupView.kt
@@ -29,7 +29,8 @@
     defStyleAttr: Int = 0,
     defStyleRes: Int = 0
 ) : LinearLayout(context, attrs, defStyleAttr, defStyleRes) {
-    private var popupDrawable: BubblePopupDrawable? = null
+    var popupDrawable: BubblePopupDrawable? = null
+        private set
 
     /**
      * Sets up the popup drawable with the config provided. Required to remove dependency on local
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/DividerSnapAlgorithm.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/DividerSnapAlgorithm.java
index 1901e0b..a5000fe 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/DividerSnapAlgorithm.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/DividerSnapAlgorithm.java
@@ -19,6 +19,14 @@
 import static android.view.WindowManager.DOCKED_INVALID;
 import static android.view.WindowManager.DOCKED_LEFT;
 import static android.view.WindowManager.DOCKED_RIGHT;
+import static com.android.wm.shell.common.split.SplitScreenConstants.SNAP_TO_30_70;
+import static com.android.wm.shell.common.split.SplitScreenConstants.SNAP_TO_50_50;
+import static com.android.wm.shell.common.split.SplitScreenConstants.SNAP_TO_70_30;
+import static com.android.wm.shell.common.split.SplitScreenConstants.SNAP_TO_END_AND_DISMISS;
+import static com.android.wm.shell.common.split.SplitScreenConstants.SNAP_TO_MINIMIZE;
+import static com.android.wm.shell.common.split.SplitScreenConstants.SNAP_TO_NONE;
+import static com.android.wm.shell.common.split.SplitScreenConstants.SNAP_TO_START_AND_DISMISS;
+import static com.android.wm.shell.common.split.SplitScreenConstants.SnapPosition;
 
 import android.content.Context;
 import android.content.res.Configuration;
@@ -263,7 +271,7 @@
 
     private SnapTarget snap(int position, boolean hardDismiss) {
         if (shouldApplyFreeSnapMode(position)) {
-            return new SnapTarget(position, position, SnapTarget.FLAG_NONE);
+            return new SnapTarget(position, position, SNAP_TO_NONE);
         }
         int minIndex = -1;
         float minDistance = Float.MAX_VALUE;
@@ -291,8 +299,7 @@
         if (dockedSide == DOCKED_RIGHT) {
             startPos += mInsets.left;
         }
-        mTargets.add(new SnapTarget(startPos, startPos, SnapTarget.FLAG_DISMISS_START,
-                0.35f));
+        mTargets.add(new SnapTarget(startPos, startPos, SNAP_TO_START_AND_DISMISS, 0.35f));
         switch (mSnapMode) {
             case SNAP_MODE_16_9:
                 addRatio16_9Targets(isHorizontalDivision, dividerMax);
@@ -307,15 +314,15 @@
                 addMinimizedTarget(isHorizontalDivision, dockedSide);
                 break;
         }
-        mTargets.add(new SnapTarget(dividerMax, dividerMax, SnapTarget.FLAG_DISMISS_END, 0.35f));
+        mTargets.add(new SnapTarget(dividerMax, dividerMax, SNAP_TO_END_AND_DISMISS, 0.35f));
     }
 
     private void addNonDismissingTargets(boolean isHorizontalDivision, int topPosition,
             int bottomPosition, int dividerMax) {
-        maybeAddTarget(topPosition, topPosition - getStartInset());
+        maybeAddTarget(topPosition, topPosition - getStartInset(), SNAP_TO_30_70);
         addMiddleTarget(isHorizontalDivision);
         maybeAddTarget(bottomPosition,
-                dividerMax - getEndInset() - (bottomPosition + mDividerSize));
+                dividerMax - getEndInset() - (bottomPosition + mDividerSize), SNAP_TO_70_30);
     }
 
     private void addFixedDivisionTargets(boolean isHorizontalDivision, int dividerMax) {
@@ -349,16 +356,16 @@
      * Adds a target at {@param position} but only if the area with size of {@param smallerSize}
      * meets the minimal size requirement.
      */
-    private void maybeAddTarget(int position, int smallerSize) {
+    private void maybeAddTarget(int position, int smallerSize, @SnapPosition int snapTo) {
         if (smallerSize >= mMinimalSizeResizableTask) {
-            mTargets.add(new SnapTarget(position, position, SnapTarget.FLAG_NONE));
+            mTargets.add(new SnapTarget(position, position, snapTo));
         }
     }
 
     private void addMiddleTarget(boolean isHorizontalDivision) {
         int position = DockedDividerUtils.calculateMiddlePosition(isHorizontalDivision,
                 mInsets, mDisplayWidth, mDisplayHeight, mDividerSize);
-        mTargets.add(new SnapTarget(position, position, SnapTarget.FLAG_NONE));
+        mTargets.add(new SnapTarget(position, position, SNAP_TO_50_50));
     }
 
     private void addMinimizedTarget(boolean isHorizontalDivision, int dockedSide) {
@@ -372,7 +379,7 @@
                 position = mDisplayWidth - position - mInsets.right - mDividerSize;
             }
         }
-        mTargets.add(new SnapTarget(position, position, SnapTarget.FLAG_NONE));
+        mTargets.add(new SnapTarget(position, position, SNAP_TO_MINIMIZE));
     }
 
     public SnapTarget getMiddleTarget() {
@@ -435,14 +442,6 @@
      * Represents a snap target for the divider.
      */
     public static class SnapTarget {
-        public static final int FLAG_NONE = 0;
-
-        /** If the divider reaches this value, the left/top task should be dismissed. */
-        public static final int FLAG_DISMISS_START = 1;
-
-        /** If the divider reaches this value, the right/bottom task should be dismissed */
-        public static final int FLAG_DISMISS_END = 2;
-
         /** Position of this snap target. The right/bottom edge of the top/left task snaps here. */
         public final int position;
 
@@ -452,7 +451,10 @@
          */
         public final int taskPosition;
 
-        public final int flag;
+        /**
+         * An int describing the placement of the divider in this snap target.
+         */
+        public final @SnapPosition int snapTo;
 
         public boolean isMiddleTarget;
 
@@ -462,14 +464,15 @@
          */
         private final float distanceMultiplier;
 
-        public SnapTarget(int position, int taskPosition, int flag) {
-            this(position, taskPosition, flag, 1f);
+        public SnapTarget(int position, int taskPosition, @SnapPosition int snapTo) {
+            this(position, taskPosition, snapTo, 1f);
         }
 
-        public SnapTarget(int position, int taskPosition, int flag, float distanceMultiplier) {
+        public SnapTarget(int position, int taskPosition, @SnapPosition int snapTo,
+                float distanceMultiplier) {
             this.position = position;
             this.taskPosition = taskPosition;
-            this.flag = flag;
+            this.snapTo = snapTo;
             this.distanceMultiplier = distanceMultiplier;
         }
     }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java
index 755dba0..4af03fd 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitLayout.java
@@ -23,13 +23,12 @@
 import static android.view.WindowManager.DOCKED_LEFT;
 import static android.view.WindowManager.DOCKED_RIGHT;
 import static android.view.WindowManager.DOCKED_TOP;
-
 import static com.android.internal.jank.InteractionJankMonitor.CUJ_SPLIT_SCREEN_DOUBLE_TAP_DIVIDER;
 import static com.android.internal.jank.InteractionJankMonitor.CUJ_SPLIT_SCREEN_RESIZE;
-import static com.android.wm.shell.common.split.DividerSnapAlgorithm.SnapTarget.FLAG_DISMISS_END;
-import static com.android.wm.shell.common.split.DividerSnapAlgorithm.SnapTarget.FLAG_DISMISS_START;
 import static com.android.wm.shell.animation.Interpolators.DIM_INTERPOLATOR;
 import static com.android.wm.shell.animation.Interpolators.SLOWDOWN_INTERPOLATOR;
+import static com.android.wm.shell.common.split.SplitScreenConstants.SNAP_TO_END_AND_DISMISS;
+import static com.android.wm.shell.common.split.SplitScreenConstants.SNAP_TO_START_AND_DISMISS;
 import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_BOTTOM_OR_RIGHT;
 import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_TOP_OR_LEFT;
 import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_UNDEFINED;
@@ -511,13 +510,13 @@
      * target indicates dismissing split.
      */
     public void snapToTarget(int currentPosition, DividerSnapAlgorithm.SnapTarget snapTarget) {
-        switch (snapTarget.flag) {
-            case FLAG_DISMISS_START:
+        switch (snapTarget.snapTo) {
+            case SNAP_TO_START_AND_DISMISS:
                 flingDividePosition(currentPosition, snapTarget.position, FLING_RESIZE_DURATION,
                         () -> mSplitLayoutHandler.onSnappedToDismiss(false /* bottomOrRight */,
                                 EXIT_REASON_DRAG_DIVIDER));
                 break;
-            case FLAG_DISMISS_END:
+            case SNAP_TO_END_AND_DISMISS:
                 flingDividePosition(currentPosition, snapTarget.position, FLING_RESIZE_DURATION,
                         () -> mSplitLayoutHandler.onSnappedToDismiss(true /* bottomOrRight */,
                                 EXIT_REASON_DRAG_DIVIDER));
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitScreenConstants.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitScreenConstants.java
index be1b9b1..ff38b7e 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitScreenConstants.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitScreenConstants.java
@@ -57,6 +57,38 @@
     public @interface SplitPosition {
     }
 
+    /** The divider doesn't snap to any target and is freely placeable. */
+    public static final int SNAP_TO_NONE = 0;
+
+    /** A snap target positioned near the screen edge for a minimized task */
+    public static final int SNAP_TO_MINIMIZE = 1;
+
+    /** If the divider reaches this value, the left/top task should be dismissed. */
+    public static final int SNAP_TO_START_AND_DISMISS = 2;
+
+    /** A snap target in the first half of the screen, where the split is roughly 30-70. */
+    public static final int SNAP_TO_30_70 = 3;
+
+    /** The 50-50 snap target */
+    public static final int SNAP_TO_50_50 = 4;
+
+    /** A snap target in the latter half of the screen, where the split is roughly 70-30. */
+    public static final int SNAP_TO_70_30 = 5;
+
+    /** If the divider reaches this value, the right/bottom task should be dismissed. */
+    public static final int SNAP_TO_END_AND_DISMISS = 6;
+
+    @IntDef(prefix = { "SNAP_TO_" }, value = {
+            SNAP_TO_NONE,
+            SNAP_TO_MINIMIZE,
+            SNAP_TO_START_AND_DISMISS,
+            SNAP_TO_30_70,
+            SNAP_TO_50_50,
+            SNAP_TO_70_30,
+            SNAP_TO_END_AND_DISMISS
+    })
+    public @interface SnapPosition {}
+
     public static final int[] CONTROLLED_ACTIVITY_TYPES = {ACTIVITY_TYPE_STANDARD};
     public static final int[] CONTROLLED_WINDOWING_MODES =
             {WINDOWING_MODE_FULLSCREEN, WINDOWING_MODE_UNDEFINED};
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 e9f3e1a..fd23d14 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
@@ -35,6 +35,7 @@
 import com.android.wm.shell.bubbles.BubbleController;
 import com.android.wm.shell.bubbles.BubbleData;
 import com.android.wm.shell.bubbles.BubbleDataRepository;
+import com.android.wm.shell.bubbles.BubbleEducationController;
 import com.android.wm.shell.bubbles.BubbleLogger;
 import com.android.wm.shell.bubbles.BubblePositioner;
 import com.android.wm.shell.bubbles.properties.ProdBubbleProperties;
@@ -134,11 +135,18 @@
 
     @WMSingleton
     @Provides
+    static BubbleEducationController provideBubbleEducationProvider(Context context) {
+        return new BubbleEducationController(context);
+    }
+
+    @WMSingleton
+    @Provides
     static BubbleData provideBubbleData(Context context,
             BubbleLogger logger,
             BubblePositioner positioner,
+            BubbleEducationController educationController,
             @ShellMainThread ShellExecutor mainExecutor) {
-        return new BubbleData(context, logger, positioner, mainExecutor);
+        return new BubbleData(context, logger, positioner, educationController, mainExecutor);
     }
 
     // Note: Handler needed for LauncherApps.register
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeTaskRepository.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeTaskRepository.kt
index c05af73..c0fc02fa 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeTaskRepository.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopModeTaskRepository.kt
@@ -52,8 +52,8 @@
     private val activeTasksListeners = ArraySet<ActiveTasksListener>()
     // Track visible tasks separately because a task may be part of the desktop but not visible.
     private val visibleTasksListeners = ArrayMap<VisibleTasksListener, Executor>()
-    // Track corners of desktop tasks, used to determine gesture exclusion
-    private val desktopCorners = SparseArray<Region>()
+    // Track corner/caption regions of desktop tasks, used to determine gesture exclusion
+    private val desktopExclusionRegions = SparseArray<Region>()
     private var desktopGestureExclusionListener: Consumer<Region>? = null
     private var desktopGestureExclusionExecutor: Executor? = null
 
@@ -96,10 +96,11 @@
     }
 
     /**
-     * Add a Consumer which will inform other classes of changes to corners for all Desktop tasks.
+     * Add a Consumer which will inform other classes of changes to exclusion regions for all
+     * Desktop tasks.
      */
-    fun setTaskCornerListener(cornersListener: Consumer<Region>, executor: Executor) {
-        desktopGestureExclusionListener = cornersListener
+    fun setExclusionRegionListener(regionListener: Consumer<Region>, executor: Executor) {
+        desktopGestureExclusionListener = regionListener
         desktopGestureExclusionExecutor = executor
         executor.execute {
             desktopGestureExclusionListener?.accept(calculateDesktopExclusionRegion())
@@ -107,14 +108,14 @@
     }
 
     /**
-     * Create a new merged region representative of all corners in all desktop tasks.
+     * Create a new merged region representative of all exclusion regions in all desktop tasks.
      */
     private fun calculateDesktopExclusionRegion(): Region {
-        val desktopCornersRegion = Region()
-        desktopCorners.valueIterator().forEach { taskCorners ->
-            desktopCornersRegion.op(taskCorners, Region.Op.UNION)
+        val desktopExclusionRegion = Region()
+        desktopExclusionRegions.valueIterator().forEach { taskExclusionRegion ->
+            desktopExclusionRegion.op(taskExclusionRegion, Region.Op.UNION)
         }
-        return desktopCornersRegion
+        return desktopExclusionRegion
     }
 
     /**
@@ -294,22 +295,24 @@
     }
 
     /**
-     * Updates the active desktop corners; if desktopCorners has been accepted by
-     * desktopCornersListener, it will be updated in the appropriate classes.
+     * Updates the active desktop gesture exclusion regions; if desktopExclusionRegions has been
+     * accepted by desktopGestureExclusionListener, it will be updated in the
+     * appropriate classes.
      */
-    fun updateTaskCorners(taskId: Int, taskCorners: Region) {
-        desktopCorners.put(taskId, taskCorners)
+    fun updateTaskExclusionRegions(taskId: Int, taskExclusionRegions: Region) {
+        desktopExclusionRegions.put(taskId, taskExclusionRegions)
         desktopGestureExclusionExecutor?.execute {
             desktopGestureExclusionListener?.accept(calculateDesktopExclusionRegion())
         }
     }
 
     /**
-     * Removes the active desktop corners for the specified task; if desktopCorners has been
-     * accepted by desktopCornersListener, it will be updated in the appropriate classes.
+     * Removes the desktop gesture exclusion region for the specified task; if exclusionRegion
+     * has been accepted by desktopGestureExclusionListener, it will be updated in the
+     * appropriate classes.
      */
-    fun removeTaskCorners(taskId: Int) {
-        desktopCorners.delete(taskId)
+    fun removeExclusionRegion(taskId: Int) {
+        desktopExclusionRegions.delete(taskId)
         desktopGestureExclusionExecutor?.execute {
             desktopGestureExclusionListener?.accept(calculateDesktopExclusionRegion())
         }
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 b918c83..9ce0118 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
@@ -16,7 +16,6 @@
 
 package com.android.wm.shell.desktopmode
 
-import android.R
 import android.app.ActivityManager.RunningTaskInfo
 import android.app.WindowConfiguration
 import android.app.WindowConfiguration.ACTIVITY_TYPE_HOME
@@ -27,7 +26,6 @@
 import android.app.WindowConfiguration.WINDOWING_MODE_UNDEFINED
 import android.app.WindowConfiguration.WindowingMode
 import android.content.Context
-import android.content.res.TypedArray
 import android.graphics.Point
 import android.graphics.PointF
 import android.graphics.Rect
@@ -44,6 +42,7 @@
 import android.window.TransitionRequestInfo
 import android.window.WindowContainerTransaction
 import androidx.annotation.BinderThread
+import com.android.internal.policy.ScreenDecorationsUtils
 import com.android.wm.shell.RootTaskDisplayAreaOrganizer
 import com.android.wm.shell.ShellTaskOrganizer
 import com.android.wm.shell.common.DisplayController
@@ -696,10 +695,7 @@
             finishTransaction: SurfaceControl.Transaction
     ) {
         // Add rounded corners to freeform windows
-        val ta: TypedArray = context.obtainStyledAttributes(
-                intArrayOf(R.attr.dialogCornerRadius))
-        val cornerRadius = ta.getDimensionPixelSize(0, 0).toFloat()
-        ta.recycle()
+        val cornerRadius = ScreenDecorationsUtils.getWindowCornerRadius(context)
         info.changes
                 .filter { it.taskInfo?.windowingMode == WINDOWING_MODE_FREEFORM }
                 .forEach { finishTransaction.setCornerRadius(it.leash, cornerRadius) }
@@ -972,17 +968,17 @@
     }
 
     /**
-     * Update the corner region for a specified task
+     * Update the exclusion region for a specified task
      */
-    fun onTaskCornersChanged(taskId: Int, corner: Region) {
-        desktopModeTaskRepository.updateTaskCorners(taskId, corner)
+    fun onExclusionRegionChanged(taskId: Int, exclusionRegion: Region) {
+        desktopModeTaskRepository.updateTaskExclusionRegions(taskId, exclusionRegion)
     }
 
     /**
-     * Remove a previously tracked corner region for a specified task.
+     * Remove a previously tracked exclusion region for a specified task.
      */
-    fun removeCornersForTask(taskId: Int) {
-        desktopModeTaskRepository.removeTaskCorners(taskId)
+    fun removeExclusionRegionForTask(taskId: Int) {
+        desktopModeTaskRepository.removeExclusionRegion(taskId)
     }
 
     /**
@@ -996,16 +992,16 @@
     }
 
     /**
-     * Adds a listener to track changes to desktop task corners
+     * Adds a listener to track changes to desktop task gesture exclusion regions
      *
      * @param listener the listener to add.
      * @param callbackExecutor the executor to call the listener on.
      */
-    fun setTaskCornerListener(
+    fun setTaskRegionListener(
             listener: Consumer<Region>,
             callbackExecutor: Executor
     ) {
-        desktopModeTaskRepository.setTaskCornerListener(listener, callbackExecutor)
+        desktopModeTaskRepository.setExclusionRegionListener(listener, callbackExecutor)
     }
 
     private fun dump(pw: PrintWriter, prefix: String) {
@@ -1031,7 +1027,7 @@
                 callbackExecutor: Executor
         ) {
             mainExecutor.execute {
-                this@DesktopTasksController.setTaskCornerListener(listener, callbackExecutor)
+                this@DesktopTasksController.setTaskRegionListener(listener, callbackExecutor)
             }
         }
     }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashScreenExitAnimation.java b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashScreenExitAnimation.java
index 20da877..cb76044 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashScreenExitAnimation.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashScreenExitAnimation.java
@@ -20,6 +20,7 @@
 import static com.android.internal.jank.InteractionJankMonitor.CUJ_SPLASHSCREEN_EXIT_ANIM;
 
 import android.animation.Animator;
+import android.annotation.IntDef;
 import android.content.Context;
 import android.graphics.Rect;
 import android.util.Slog;
@@ -46,6 +47,8 @@
     private final int mIconFadeOutDuration;
     private final int mAppRevealDelay;
     private final int mAppRevealDuration;
+    @ExitAnimationType
+    private final int mAnimationType;
     private final int mAnimationDuration;
     private final float mIconStartAlpha;
     private final float mBrandingStartAlpha;
@@ -55,6 +58,24 @@
 
     private Runnable mFinishCallback;
 
+    /**
+    * This splash screen exit animation type uses a radial vanish to hide
+    * the starting window and slides up the main window content.
+    */
+    private static final int TYPE_RADIAL_VANISH_SLIDE_UP = 0;
+
+    /**
+    * This splash screen exit animation type fades out the starting window
+    * to reveal the main window content.
+    */
+    private static final int TYPE_FADE_OUT = 1;
+
+    @IntDef(prefix = { "TYPE_" }, value = {
+        TYPE_RADIAL_VANISH_SLIDE_UP,
+        TYPE_FADE_OUT,
+    })
+    private @interface ExitAnimationType {}
+
     SplashScreenExitAnimation(Context context, SplashScreenView view, SurfaceControl leash,
             Rect frame, int mainWindowShiftLength, TransactionPool pool, Runnable handleFinish,
             float roundedCornerRadius) {
@@ -91,6 +112,8 @@
         }
         mAppRevealDuration = context.getResources().getInteger(
                 R.integer.starting_window_app_reveal_anim_duration);
+        mAnimationType = context.getResources().getInteger(
+                R.integer.starting_window_exit_animation_type);
         mAnimationDuration = Math.max(mIconFadeOutDuration, mAppRevealDelay + mAppRevealDuration);
         mMainWindowShiftLength = mainWindowShiftLength;
         mFinishCallback = handleFinish;
@@ -98,10 +121,15 @@
     }
 
     void startAnimations() {
-        SplashScreenExitAnimationUtils.startAnimations(mSplashScreenView, mFirstWindowSurface,
-                mMainWindowShiftLength, mTransactionPool, mFirstWindowFrame, mAnimationDuration,
-                mIconFadeOutDuration, mIconStartAlpha, mBrandingStartAlpha, mAppRevealDelay,
-                mAppRevealDuration, this, mRoundedCornerRadius);
+        if (mAnimationType == TYPE_FADE_OUT) {
+            SplashScreenExitAnimationUtils.startFadeOutAnimation(mSplashScreenView,
+                    mAnimationDuration, this);
+        } else {
+            SplashScreenExitAnimationUtils.startAnimations(mSplashScreenView, mFirstWindowSurface,
+                    mMainWindowShiftLength, mTransactionPool, mFirstWindowFrame, mAnimationDuration,
+                    mIconFadeOutDuration, mIconStartAlpha, mBrandingStartAlpha, mAppRevealDelay,
+                    mAppRevealDuration, this, mRoundedCornerRadius);
+        }
     }
 
     private void reset() {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashScreenExitAnimationUtils.java b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashScreenExitAnimationUtils.java
index a7e4385..64f09a7 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashScreenExitAnimationUtils.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/startingsurface/SplashScreenExitAnimationUtils.java
@@ -71,11 +71,10 @@
             int iconFadeOutDuration, float iconStartAlpha, float brandingStartAlpha,
             int appRevealDelay, int appRevealDuration, Animator.AnimatorListener animatorListener,
             float roundedCornerRadius) {
-        ValueAnimator animator =
-                createAnimator(splashScreenView, firstWindowSurface, mainWindowShiftLength,
-                        transactionPool, firstWindowFrame, animationDuration, iconFadeOutDuration,
-                        iconStartAlpha, brandingStartAlpha, appRevealDelay, appRevealDuration,
-                        animatorListener, roundedCornerRadius);
+        ValueAnimator animator = createRadialVanishSlideUpAnimator(splashScreenView,
+                firstWindowSurface, mainWindowShiftLength, transactionPool, firstWindowFrame,
+                animationDuration, iconFadeOutDuration, iconStartAlpha, brandingStartAlpha,
+                appRevealDelay, appRevealDuration, animatorListener, roundedCornerRadius);
         animator.start();
     }
 
@@ -99,7 +98,7 @@
      * Creates the animator to fade out the icon, reveal the app, and shift up main window.
      * @hide
      */
-    private static ValueAnimator createAnimator(ViewGroup splashScreenView,
+    private static ValueAnimator createRadialVanishSlideUpAnimator(ViewGroup splashScreenView,
             SurfaceControl firstWindowSurface, int mMainWindowShiftLength,
             TransactionPool transactionPool, Rect firstWindowFrame, int animationDuration,
             int iconFadeOutDuration, float iconStartAlpha, float brandingStartAlpha,
@@ -210,6 +209,20 @@
         return nightMode == Configuration.UI_MODE_NIGHT_YES;
     }
 
+    static void startFadeOutAnimation(ViewGroup splashScreenView,
+            int animationDuration, Animator.AnimatorListener animatorListener) {
+        final ValueAnimator animator = ValueAnimator.ofFloat(1f, 0f);
+        animator.setDuration(animationDuration);
+        animator.setInterpolator(Interpolators.LINEAR);
+        animator.addUpdateListener(animation -> {
+            splashScreenView.setAlpha((float) animation.getAnimatedValue());
+        });
+        if (animatorListener != null) {
+            animator.addListener(animatorListener);
+        }
+        animator.start();
+    }
+
     /**
      * View which creates a circular reveal of the underlying view.
      * @hide
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java
index 87ceaa4..00f6a1c 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/DefaultMixedHandler.java
@@ -19,10 +19,10 @@
 import static android.app.WindowConfiguration.ACTIVITY_TYPE_HOME;
 import static android.app.WindowConfiguration.ACTIVITY_TYPE_RECENTS;
 import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
+import static android.view.Display.DEFAULT_DISPLAY;
 import static android.view.WindowManager.TRANSIT_CHANGE;
 import static android.view.WindowManager.TRANSIT_TO_BACK;
 import static android.window.TransitionInfo.FLAG_IS_WALLPAPER;
-
 import static com.android.wm.shell.common.split.SplitScreenConstants.FLAG_IS_DIVIDER_BAR;
 import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_UNDEFINED;
 import static com.android.wm.shell.pip.PipAnimationController.ANIM_TYPE_ALPHA;
@@ -239,9 +239,14 @@
 
     @Override
     public Transitions.TransitionHandler handleRecentsRequest(WindowContainerTransaction outWCT) {
-        if (mRecentsHandler != null && (mSplitHandler.isSplitScreenVisible()
-                || DesktopModeStatus.isEnabled())) {
-            return this;
+        if (mRecentsHandler != null) {
+            if (mSplitHandler.isSplitScreenVisible()) {
+                return this;
+            } else if (mDesktopTasksController != null
+                    // Check on the default display. Recents/gesture nav is only available there
+                    && mDesktopTasksController.getVisibleTaskCount(DEFAULT_DISPLAY) > 0) {
+                return this;
+            }
         }
         return null;
     }
@@ -662,7 +667,6 @@
         if (!consumed) {
             return false;
         }
-        //Sync desktop mode state (proto 2)
         if (mDesktopTasksController != null) {
             mDesktopTasksController.syncSurfaceState(info, finishTransaction);
             return true;
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 abd2ad4..026d0cd 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
@@ -21,7 +21,6 @@
 import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
 import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
 import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
-
 import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_BOTTOM_OR_RIGHT;
 import static com.android.wm.shell.common.split.SplitScreenConstants.SPLIT_POSITION_TOP_OR_LEFT;
 import static com.android.wm.shell.desktopmode.EnterDesktopTaskTransitionHandler.FINAL_FREEFORM_SCALE;
@@ -79,7 +78,7 @@
 import com.android.wm.shell.sysui.ShellController;
 import com.android.wm.shell.sysui.ShellInit;
 import com.android.wm.shell.transition.Transitions;
-import com.android.wm.shell.windowdecor.DesktopModeWindowDecoration.TaskCornersListener;
+import com.android.wm.shell.windowdecor.DesktopModeWindowDecoration.ExclusionRegionListener;
 
 import java.util.Optional;
 import java.util.function.Supplier;
@@ -106,7 +105,8 @@
 
     private SparseArray<EventReceiver> mEventReceiversByDisplay = new SparseArray<>();
 
-    private final TaskCornersListener mCornersListener = new TaskCornersListenerImpl();
+    private final ExclusionRegionListener mExclusionRegionListener =
+            new ExclusionRegionListenerImpl();
 
     private final SparseArray<DesktopModeWindowDecoration> mWindowDecorByTaskId =
             new SparseArray<>();
@@ -920,7 +920,7 @@
 
         windowDecoration.setCaptionListeners(
                 touchEventListener, touchEventListener, touchEventListener);
-        windowDecoration.setCornersListener(mCornersListener);
+        windowDecoration.setExclusionRegionListener(mExclusionRegionListener);
         windowDecoration.setDragPositioningCallback(dragPositioningCallback);
         windowDecoration.setDragDetector(touchEventListener.mDragDetector);
         windowDecoration.relayout(taskInfo, startT, finishT,
@@ -958,17 +958,17 @@
         }
     }
 
-    private class TaskCornersListenerImpl
-            implements DesktopModeWindowDecoration.TaskCornersListener {
+    private class ExclusionRegionListenerImpl
+            implements ExclusionRegionListener {
 
         @Override
-        public void onTaskCornersChanged(int taskId, Region corner) {
-            mDesktopTasksController.ifPresent(d -> d.onTaskCornersChanged(taskId, corner));
+        public void onExclusionRegionChanged(int taskId, Region region) {
+            mDesktopTasksController.ifPresent(d -> d.onExclusionRegionChanged(taskId, region));
         }
 
         @Override
-        public void onTaskCornersRemoved(int taskId) {
-            mDesktopTasksController.ifPresent(d -> d.removeCornersForTask(taskId));
+        public void onExclusionRegionDismissed(int taskId) {
+            mDesktopTasksController.ifPresent(d -> d.removeExclusionRegionForTask(taskId));
         }
     }
 
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 e954f3b..dbff20e 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
@@ -92,7 +92,7 @@
     private Drawable mAppIcon;
     private CharSequence mAppName;
 
-    private TaskCornersListener mCornersListener;
+    private ExclusionRegionListener mExclusionRegionListener;
 
     private final Set<IBinder> mTransitionsPausingRelayout = new HashSet<>();
     private int mRelayoutBlock;
@@ -128,8 +128,8 @@
         mOnCaptionLongClickListener = onLongClickListener;
     }
 
-    void setCornersListener(TaskCornersListener cornersListener) {
-        mCornersListener = cornersListener;
+    void setExclusionRegionListener(ExclusionRegionListener exclusionRegionListener) {
+        mExclusionRegionListener = exclusionRegionListener;
     }
 
     void setDragPositioningCallback(DragPositioningCallback dragPositioningCallback) {
@@ -229,6 +229,10 @@
         }
 
         if (!isDragResizeable) {
+            if (!mTaskInfo.positionInParent.equals(mPositionInParent)) {
+                // We still want to track caption bar's exclusion region on a non-resizeable task.
+                updateExclusionRegion();
+            }
             closeDragResizeListener();
             return;
         }
@@ -256,13 +260,13 @@
         final int resize_corner = mResult.mRootView.getResources()
                 .getDimensionPixelSize(R.dimen.freeform_resize_corner);
 
-        // If either task geometry or position have changed, update this task's cornersListener
+        // If either task geometry or position have changed, update this task's
+        // exclusion region listener
         if (mDragResizeListener.setGeometry(
                 mResult.mWidth, mResult.mHeight, resize_handle, resize_corner, touchSlop)
                 || !mTaskInfo.positionInParent.equals(mPositionInParent)) {
-            mCornersListener.onTaskCornersChanged(mTaskInfo.taskId, getGlobalCornersRegion());
+            updateExclusionRegion();
         }
-        mPositionInParent.set(mTaskInfo.positionInParent);
 
         if (isMaximizeMenuActive()) {
             if (!mTaskInfo.isVisible()) {
@@ -282,8 +286,7 @@
 
         final int displayWidth = displayLayout.width();
         final int displayHeight = displayLayout.height();
-        final int captionHeight = loadDimensionPixelSize(
-                resources, R.dimen.freeform_decor_caption_height);
+        final int captionHeight = getCaptionHeight();
 
         final ImageButton maximizeWindowButton =
                 mResult.mRootView.findViewById(R.id.maximize_window);
@@ -537,7 +540,7 @@
     public void close() {
         closeDragResizeListener();
         closeHandleMenu();
-        mCornersListener.onTaskCornersRemoved(mTaskInfo.taskId);
+        mExclusionRegionListener.onExclusionRegionDismissed(mTaskInfo.taskId);
         disposeResizeVeil();
         super.close();
     }
@@ -548,13 +551,32 @@
                 : R.layout.desktop_mode_focused_window_decor;
     }
 
+    private void updatePositionInParent() {
+        mPositionInParent.set(mTaskInfo.positionInParent);
+    }
+
+    private void updateExclusionRegion() {
+        // An outdated position in parent is one reason for this to be called; update it here.
+        updatePositionInParent();
+        mExclusionRegionListener
+                .onExclusionRegionChanged(mTaskInfo.taskId, getGlobalExclusionRegion());
+    }
+
     /**
-     * Create a new region out of the corner rects of this task.
+     * Create a new exclusion region from the corner rects (if resizeable) and caption bounds
+     * of this task.
      */
-    Region getGlobalCornersRegion() {
-        Region cornersRegion = mDragResizeListener.getCornersRegion();
-        cornersRegion.translate(mPositionInParent.x, mPositionInParent.y);
-        return cornersRegion;
+    private Region getGlobalExclusionRegion() {
+        Region exclusionRegion;
+        if (mTaskInfo.isResizeable) {
+            exclusionRegion = mDragResizeListener.getCornersRegion();
+        } else {
+            exclusionRegion = new Region();
+        }
+        exclusionRegion.union(new Rect(0, 0, mResult.mWidth,
+                getCaptionHeight()));
+        exclusionRegion.translate(mPositionInParent.x, mPositionInParent.y);
+        return exclusionRegion;
     }
 
     /**
@@ -572,6 +594,10 @@
         return R.dimen.freeform_decor_caption_height;
     }
 
+    private int getCaptionHeight() {
+        return loadDimensionPixelSize(mContext.getResources(), getCaptionHeightId());
+    }
+
     /**
      * Add transition to mTransitionsPausingRelayout
      */
@@ -626,12 +652,14 @@
         }
     }
 
-    interface TaskCornersListener {
-        /** Inform the implementing class of this task's change in corner resize handles */
-        void onTaskCornersChanged(int taskId, Region corner);
+    interface ExclusionRegionListener {
+        /** Inform the implementing class of this task's change in region resize handles */
+        void onExclusionRegionChanged(int taskId, Region region);
 
-        /** Inform the implementing class that this task no longer needs its corners tracked,
-         * likely due to it closing. */
-        void onTaskCornersRemoved(int taskId);
+        /**
+         * Inform the implementing class that this task no longer needs an exclusion region,
+         * likely due to it closing.
+         */
+        void onExclusionRegionDismissed(int taskId);
     }
 }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/ResizeVeil.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/ResizeVeil.java
index bfce72b..09fc3da 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/ResizeVeil.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/ResizeVeil.java
@@ -49,11 +49,13 @@
     private final Supplier<SurfaceControl.Builder> mSurfaceControlBuilderSupplier;
     private final Supplier<SurfaceControl.Transaction> mSurfaceControlTransactionSupplier;
     private final Drawable mAppIcon;
+    private ImageView mIconView;
     private SurfaceControl mParentSurface;
     private SurfaceControl mVeilSurface;
     private final RunningTaskInfo mTaskInfo;
     private SurfaceControlViewHost mViewHost;
     private final Display mDisplay;
+    private ValueAnimator mVeilAnimator;
 
     public ResizeVeil(Context context, Drawable appIcon, RunningTaskInfo taskInfo,
             Supplier<SurfaceControl.Builder> surfaceControlBuilderSupplier, Display display,
@@ -97,8 +99,8 @@
         mViewHost = new SurfaceControlViewHost(mContext, mDisplay, windowManager, "ResizeVeil");
         mViewHost.setView(v, lp);
 
-        final ImageView appIcon = mViewHost.getView().findViewById(R.id.veil_application_icon);
-        appIcon.setImageDrawable(mAppIcon);
+        mIconView = mViewHost.getView().findViewById(R.id.veil_application_icon);
+        mIconView.setImageDrawable(mAppIcon);
     }
 
     /**
@@ -123,17 +125,27 @@
 
         relayout(taskBounds, t);
         if (fadeIn) {
-            final ValueAnimator animator = new ValueAnimator();
-            animator.setFloatValues(0f, 1f);
-            animator.setDuration(RESIZE_ALPHA_DURATION);
-            animator.addUpdateListener(animation -> {
-                t.setAlpha(mVeilSurface, animator.getAnimatedFraction());
+            mVeilAnimator = new ValueAnimator();
+            mVeilAnimator.setFloatValues(0f, 1f);
+            mVeilAnimator.setDuration(RESIZE_ALPHA_DURATION);
+            mVeilAnimator.addUpdateListener(animation -> {
+                t.setAlpha(mVeilSurface, mVeilAnimator.getAnimatedFraction());
                 t.apply();
             });
 
+            final ValueAnimator iconAnimator = new ValueAnimator();
+            iconAnimator.setFloatValues(0f, 1f);
+            iconAnimator.setDuration(RESIZE_ALPHA_DURATION);
+            iconAnimator.addUpdateListener(animation -> {
+                mIconView.setAlpha(animation.getAnimatedFraction());
+            });
+
             t.show(mVeilSurface)
                     .addTransactionCommittedListener(
-                            mContext.getMainExecutor(), () -> animator.start())
+                            mContext.getMainExecutor(), () -> {
+                                mVeilAnimator.start();
+                                iconAnimator.start();
+                            })
                     .setAlpha(mVeilSurface, 0);
         } else {
             // Show the veil immediately at full opacity.
@@ -172,11 +184,17 @@
 
     /**
      * Calls relayout to update task and veil bounds.
+     * Finishes veil fade in if animation is currently running; this is to prevent empty space
+     * being visible behind the transparent veil during a fast resize.
      *
      * @param t a transaction to be applied in sync with the veil draw.
      * @param newBounds bounds to update veil to.
      */
     public void updateResizeVeil(SurfaceControl.Transaction t, Rect newBounds) {
+        if (mVeilAnimator != null && mVeilAnimator.isStarted()) {
+            // TODO(b/300145351): Investigate why ValueAnimator#end does not work here.
+            mVeilAnimator.setCurrentPlayTime(RESIZE_ALPHA_DURATION);
+        }
         relayout(newBounds, t);
         mViewHost.getView().getViewRootImpl().applyTransactionOnDraw(t);
     }
diff --git a/libs/WindowManager/Shell/tests/flicker/Android.bp b/libs/WindowManager/Shell/tests/flicker/Android.bp
index eb650ca..6fd3354 100644
--- a/libs/WindowManager/Shell/tests/flicker/Android.bp
+++ b/libs/WindowManager/Shell/tests/flicker/Android.bp
@@ -44,6 +44,16 @@
 }
 
 filegroup {
+    name: "WMShellFlickerTestsPipCommon-src",
+    srcs: ["src/com/android/wm/shell/flicker/pip/common/*.kt"],
+}
+
+filegroup {
+    name: "WMShellFlickerTestsPipApps-src",
+    srcs: ["src/com/android/wm/shell/flicker/pip/apps/*.kt"],
+}
+
+filegroup {
     name: "WMShellFlickerTestsSplitScreenBase-src",
     srcs: [
         "src/com/android/wm/shell/flicker/splitscreen/benchmark/*.kt",
@@ -152,6 +162,8 @@
     exclude_srcs: [
         ":WMShellFlickerTestsBubbles-src",
         ":WMShellFlickerTestsPip-src",
+        ":WMShellFlickerTestsPipCommon-src",
+        ":WMShellFlickerTestsPipApps-src",
         ":WMShellFlickerTestsSplitScreenGroup1-src",
         ":WMShellFlickerTestsSplitScreenGroup2-src",
         ":WMShellFlickerTestsSplitScreenBase-src",
@@ -181,6 +193,20 @@
     srcs: [
         ":WMShellFlickerTestsBase-src",
         ":WMShellFlickerTestsPip-src",
+        ":WMShellFlickerTestsPipCommon-src",
+    ],
+}
+
+android_test {
+    name: "WMShellFlickerTestsPipApps",
+    defaults: ["WMShellFlickerTestsDefault"],
+    additional_manifests: ["manifests/AndroidManifestPip.xml"],
+    package_name: "com.android.wm.shell.flicker.pip.apps",
+    instrumentation_target_package: "com.android.wm.shell.flicker.pip.apps",
+    srcs: [
+        ":WMShellFlickerTestsBase-src",
+        ":WMShellFlickerTestsPipApps-src",
+        ":WMShellFlickerTestsPipCommon-src",
     ],
 }
 
diff --git a/libs/WindowManager/Shell/tests/flicker/AndroidTestTemplate.xml b/libs/WindowManager/Shell/tests/flicker/AndroidTestTemplate.xml
index 57df4e8..b13e9a2 100644
--- a/libs/WindowManager/Shell/tests/flicker/AndroidTestTemplate.xml
+++ b/libs/WindowManager/Shell/tests/flicker/AndroidTestTemplate.xml
@@ -60,9 +60,9 @@
     <!-- Enable mocking GPS location by the test app -->
     <target_preparer class="com.android.tradefed.targetprep.RunCommandTargetPreparer">
         <option name="run-command"
-                value="appops set com.android.wm.shell.flicker.pip android:mock_location allow"/>
+                value="appops set com.android.wm.shell.flicker.pip.apps android:mock_location allow"/>
         <option name="teardown-command"
-                value="appops set com.android.wm.shell.flicker.pip android:mock_location deny"/>
+                value="appops set com.android.wm.shell.flicker.pip.apps android:mock_location deny"/>
     </target_preparer>
 
     <!-- Needed for pushing the trace config file -->
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ClosePipBySwipingDownTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ClosePipBySwipingDownTest.kt
index ca28f52..9256725 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ClosePipBySwipingDownTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ClosePipBySwipingDownTest.kt
@@ -21,6 +21,7 @@
 import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
 import android.tools.device.flicker.legacy.FlickerBuilder
 import android.tools.device.flicker.legacy.LegacyFlickerTest
+import com.android.wm.shell.flicker.pip.common.ClosePipTransition
 import org.junit.FixMethodOrder
 import org.junit.Test
 import org.junit.runner.RunWith
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ClosePipWithDismissButtonTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ClosePipWithDismissButtonTest.kt
index 4da628c..002c019 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ClosePipWithDismissButtonTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ClosePipWithDismissButtonTest.kt
@@ -20,6 +20,7 @@
 import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
 import android.tools.device.flicker.legacy.FlickerBuilder
 import android.tools.device.flicker.legacy.LegacyFlickerTest
+import com.android.wm.shell.flicker.pip.common.ClosePipTransition
 import org.junit.FixMethodOrder
 import org.junit.Test
 import org.junit.runner.RunWith
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipOnUserLeaveHintTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipOnUserLeaveHintTest.kt
index e0b18de..6dd68b0 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipOnUserLeaveHintTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipOnUserLeaveHintTest.kt
@@ -20,6 +20,7 @@
 import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
 import android.tools.device.flicker.legacy.FlickerBuilder
 import android.tools.device.flicker.legacy.LegacyFlickerTest
+import com.android.wm.shell.flicker.pip.common.EnterPipTransition
 import org.junit.Assume
 import org.junit.FixMethodOrder
 import org.junit.Test
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipToOtherOrientation.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipToOtherOrientation.kt
index b4cedd9..8207b85 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipToOtherOrientation.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipToOtherOrientation.kt
@@ -32,8 +32,9 @@
 import com.android.server.wm.flicker.helpers.FixedOrientationAppHelper
 import com.android.server.wm.flicker.testapp.ActivityOptions.Pip.ACTION_ENTER_PIP
 import com.android.server.wm.flicker.testapp.ActivityOptions.PortraitOnlyActivity.EXTRA_FIXED_ORIENTATION
-import com.android.wm.shell.flicker.pip.PipTransition.BroadcastActionTrigger.Companion.ORIENTATION_LANDSCAPE
-import com.android.wm.shell.flicker.pip.PipTransition.BroadcastActionTrigger.Companion.ORIENTATION_PORTRAIT
+import com.android.wm.shell.flicker.pip.common.PipTransition
+import com.android.wm.shell.flicker.pip.common.PipTransition.BroadcastActionTrigger.Companion.ORIENTATION_LANDSCAPE
+import com.android.wm.shell.flicker.pip.common.PipTransition.BroadcastActionTrigger.Companion.ORIENTATION_PORTRAIT
 import org.junit.Assume
 import org.junit.Before
 import org.junit.FixMethodOrder
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipViaAppUiButtonTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipViaAppUiButtonTest.kt
index f9efffe..cc94367 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipViaAppUiButtonTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipViaAppUiButtonTest.kt
@@ -19,6 +19,7 @@
 import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
 import android.tools.device.flicker.legacy.FlickerBuilder
 import android.tools.device.flicker.legacy.LegacyFlickerTest
+import com.android.wm.shell.flicker.pip.common.EnterPipTransition
 import org.junit.FixMethodOrder
 import org.junit.runner.RunWith
 import org.junit.runners.MethodSorters
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipToAppViaExpandButtonTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipToAppViaExpandButtonTest.kt
index c4e63c3..7da4429 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipToAppViaExpandButtonTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipToAppViaExpandButtonTest.kt
@@ -19,6 +19,7 @@
 import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
 import android.tools.device.flicker.legacy.FlickerBuilder
 import android.tools.device.flicker.legacy.LegacyFlickerTest
+import com.android.wm.shell.flicker.pip.common.ExitPipToAppTransition
 import org.junit.FixMethodOrder
 import org.junit.runner.RunWith
 import org.junit.runners.MethodSorters
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipToAppViaIntentTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipToAppViaIntentTest.kt
index 839bbd4..0ad9e4c 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipToAppViaIntentTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipToAppViaIntentTest.kt
@@ -19,6 +19,7 @@
 import android.tools.device.flicker.junit.FlickerParametersRunnerFactory
 import android.tools.device.flicker.legacy.FlickerBuilder
 import android.tools.device.flicker.legacy.LegacyFlickerTest
+import com.android.wm.shell.flicker.pip.common.ExitPipToAppTransition
 import org.junit.FixMethodOrder
 import org.junit.runner.RunWith
 import org.junit.runners.MethodSorters
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExpandPipOnDoubleClickTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExpandPipOnDoubleClickTest.kt
index ea67e3d..89a6c93 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExpandPipOnDoubleClickTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExpandPipOnDoubleClickTest.kt
@@ -23,6 +23,7 @@
 import android.tools.device.flicker.legacy.FlickerBuilder
 import android.tools.device.flicker.legacy.LegacyFlickerTest
 import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
+import com.android.wm.shell.flicker.pip.common.PipTransition
 import org.junit.FixMethodOrder
 import org.junit.Test
 import org.junit.runner.RunWith
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExpandPipOnPinchOpenTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExpandPipOnPinchOpenTest.kt
index 0f30cef..8978af0 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExpandPipOnPinchOpenTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExpandPipOnPinchOpenTest.kt
@@ -22,6 +22,7 @@
 import android.tools.device.flicker.legacy.FlickerBuilder
 import android.tools.device.flicker.legacy.LegacyFlickerTest
 import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
+import com.android.wm.shell.flicker.pip.common.PipTransition
 import org.junit.FixMethodOrder
 import org.junit.Test
 import org.junit.runner.RunWith
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipDownOnShelfHeightChange.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipDownOnShelfHeightChange.kt
index 421ad75..4776206 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipDownOnShelfHeightChange.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipDownOnShelfHeightChange.kt
@@ -21,6 +21,7 @@
 import android.tools.device.flicker.legacy.FlickerBuilder
 import android.tools.device.flicker.legacy.LegacyFlickerTest
 import androidx.test.filters.RequiresDevice
+import com.android.wm.shell.flicker.pip.common.MovePipShelfHeightTransition
 import com.android.wm.shell.flicker.utils.Direction
 import org.junit.FixMethodOrder
 import org.junit.Test
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipOnImeVisibilityChangeTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipOnImeVisibilityChangeTest.kt
index c10860a..425cbfaff 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipOnImeVisibilityChangeTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipOnImeVisibilityChangeTest.kt
@@ -27,6 +27,7 @@
 import android.tools.device.helpers.WindowUtils
 import com.android.server.wm.flicker.helpers.ImeAppHelper
 import com.android.server.wm.flicker.helpers.setRotation
+import com.android.wm.shell.flicker.pip.common.PipTransition
 import org.junit.FixMethodOrder
 import org.junit.Test
 import org.junit.runner.RunWith
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipUpOnShelfHeightChangeTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipUpOnShelfHeightChangeTest.kt
index 992f1bc..18f30d9 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipUpOnShelfHeightChangeTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipUpOnShelfHeightChangeTest.kt
@@ -21,6 +21,7 @@
 import android.tools.device.flicker.legacy.FlickerBuilder
 import android.tools.device.flicker.legacy.LegacyFlickerTest
 import androidx.test.filters.RequiresDevice
+import com.android.wm.shell.flicker.pip.common.MovePipShelfHeightTransition
 import com.android.wm.shell.flicker.utils.Direction
 import org.junit.FixMethodOrder
 import org.junit.Test
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipDragTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipDragTest.kt
index 0c6fc56..c7f2786 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipDragTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipDragTest.kt
@@ -23,6 +23,7 @@
 import android.tools.device.flicker.legacy.LegacyFlickerTest
 import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
 import com.android.server.wm.flicker.testapp.ActivityOptions
+import com.android.wm.shell.flicker.pip.common.PipTransition
 import org.junit.FixMethodOrder
 import org.junit.Test
 import org.junit.runner.RunWith
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipDragThenSnapTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipDragThenSnapTest.kt
index 43e7696..cabc1cc 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipDragThenSnapTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipDragThenSnapTest.kt
@@ -27,6 +27,7 @@
 import androidx.test.filters.RequiresDevice
 import com.android.server.wm.flicker.helpers.setRotation
 import com.android.server.wm.flicker.testapp.ActivityOptions
+import com.android.wm.shell.flicker.pip.common.PipTransition
 import org.junit.FixMethodOrder
 import org.junit.Test
 import org.junit.runner.RunWith
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipPinchInTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipPinchInTest.kt
index 0295741..6748626 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipPinchInTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipPinchInTest.kt
@@ -23,6 +23,7 @@
 import android.tools.device.flicker.legacy.LegacyFlickerTest
 import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
 import androidx.test.filters.RequiresDevice
+import com.android.wm.shell.flicker.pip.common.PipTransition
 import org.junit.FixMethodOrder
 import org.junit.Test
 import org.junit.runner.RunWith
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/SetRequestedOrientationWhilePinned.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/SetRequestedOrientationWhilePinned.kt
index a236126..1f69847 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/SetRequestedOrientationWhilePinned.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/SetRequestedOrientationWhilePinned.kt
@@ -30,7 +30,8 @@
 import androidx.test.filters.RequiresDevice
 import com.android.server.wm.flicker.testapp.ActivityOptions
 import com.android.server.wm.flicker.testapp.ActivityOptions.PortraitOnlyActivity.EXTRA_FIXED_ORIENTATION
-import com.android.wm.shell.flicker.pip.PipTransition.BroadcastActionTrigger.Companion.ORIENTATION_LANDSCAPE
+import com.android.wm.shell.flicker.pip.common.PipTransition
+import com.android.wm.shell.flicker.pip.common.PipTransition.BroadcastActionTrigger.Companion.ORIENTATION_LANDSCAPE
 import org.junit.Assume
 import org.junit.Before
 import org.junit.FixMethodOrder
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ShowPipAndRotateDisplay.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ShowPipAndRotateDisplay.kt
index e588f87..308ece4 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ShowPipAndRotateDisplay.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ShowPipAndRotateDisplay.kt
@@ -25,6 +25,7 @@
 import android.tools.device.helpers.WindowUtils
 import com.android.server.wm.flicker.helpers.SimpleAppHelper
 import com.android.server.wm.flicker.helpers.setRotation
+import com.android.wm.shell.flicker.pip.common.PipTransition
 import org.junit.FixMethodOrder
 import org.junit.Test
 import org.junit.runner.RunWith
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/AppsEnterPipTransition.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/apps/AppsEnterPipTransition.kt
similarity index 98%
rename from libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/AppsEnterPipTransition.kt
rename to libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/apps/AppsEnterPipTransition.kt
index 9a28439..fb6c093 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/AppsEnterPipTransition.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/apps/AppsEnterPipTransition.kt
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.wm.shell.flicker.pip
+package com.android.wm.shell.flicker.pip.apps
 
 import android.platform.test.annotations.Postsubmit
 import android.tools.common.Rotation
@@ -22,6 +22,7 @@
 import android.tools.device.apphelpers.StandardAppHelper
 import android.tools.device.flicker.legacy.LegacyFlickerTest
 import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
+import com.android.wm.shell.flicker.pip.common.EnterPipTransition
 import org.junit.Test
 import org.junit.runners.Parameterized
 
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MapsEnterPipTest.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/apps/MapsEnterPipTest.kt
similarity index 98%
rename from libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MapsEnterPipTest.kt
rename to libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/apps/MapsEnterPipTest.kt
index 258c0588..54b3e2a8 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MapsEnterPipTest.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/apps/MapsEnterPipTest.kt
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.wm.shell.flicker.pip
+package com.android.wm.shell.flicker.pip.apps
 
 import android.os.Handler
 import android.os.Looper
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ClosePipTransition.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/common/ClosePipTransition.kt
similarity index 96%
rename from libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ClosePipTransition.kt
rename to libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/common/ClosePipTransition.kt
index a17144b..751f2bc 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ClosePipTransition.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/common/ClosePipTransition.kt
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2021 The Android Open Source Project
+ * Copyright (C) 2023 The Android Open Source Project
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.wm.shell.flicker.pip
+package com.android.wm.shell.flicker.pip.common
 
 import android.platform.test.annotations.Presubmit
 import android.tools.common.Rotation
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipTransition.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/common/EnterPipTransition.kt
similarity index 98%
rename from libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipTransition.kt
rename to libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/common/EnterPipTransition.kt
index d547b9c..9c129e4 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/EnterPipTransition.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/common/EnterPipTransition.kt
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.wm.shell.flicker.pip
+package com.android.wm.shell.flicker.pip.common
 
 import android.platform.test.annotations.Presubmit
 import android.tools.common.Rotation
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipToAppTransition.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/common/ExitPipToAppTransition.kt
similarity index 97%
rename from libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipToAppTransition.kt
rename to libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/common/ExitPipToAppTransition.kt
index dfffba8..9450bdd 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/ExitPipToAppTransition.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/common/ExitPipToAppTransition.kt
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2020 The Android Open Source Project
+ * Copyright (C) 2023 The Android Open Source Project
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.wm.shell.flicker.pip
+package com.android.wm.shell.flicker.pip.common
 
 import android.platform.test.annotations.Presubmit
 import android.tools.common.Rotation
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipShelfHeightTransition.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/common/MovePipShelfHeightTransition.kt
similarity index 97%
rename from libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipShelfHeightTransition.kt
rename to libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/common/MovePipShelfHeightTransition.kt
index a8fb63d..7e42bc1 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/MovePipShelfHeightTransition.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/common/MovePipShelfHeightTransition.kt
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2021 The Android Open Source Project
+ * Copyright (C) 2023 The Android Open Source Project
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.wm.shell.flicker.pip
+package com.android.wm.shell.flicker.pip.common
 
 import android.platform.test.annotations.Presubmit
 import android.tools.common.Rotation
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipTransition.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/common/PipTransition.kt
similarity index 97%
rename from libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipTransition.kt
rename to libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/common/PipTransition.kt
index 2008d01..7b7f1d7 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/PipTransition.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/pip/common/PipTransition.kt
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2021 The Android Open Source Project
+ * Copyright (C) 2023 The Android Open Source Project
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.wm.shell.flicker.pip
+package com.android.wm.shell.flicker.pip.common
 
 import android.app.Instrumentation
 import android.content.Intent
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchBackToSplitFromRecentGesturalNavLandscape.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchBackToSplitFromRecentGesturalNavLandscape.kt
index 00f6073..4ff0b4362 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchBackToSplitFromRecentGesturalNavLandscape.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchBackToSplitFromRecentGesturalNavLandscape.kt
@@ -31,7 +31,7 @@
 class SwitchBackToSplitFromRecentGesturalNavLandscape :
     SwitchBackToSplitFromRecent(Rotation.ROTATION_90) {
 
-    @ExpectedScenarios(["QUICKSWITCH"])
+    @ExpectedScenarios(["SPLIT_SCREEN_ENTER"])
     @Test
     override fun switchBackToSplitFromRecent() = super.switchBackToSplitFromRecent()
 
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchBackToSplitFromRecentGesturalNavPortrait.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchBackToSplitFromRecentGesturalNavPortrait.kt
index b3340e7..930f31d 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchBackToSplitFromRecentGesturalNavPortrait.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/SwitchBackToSplitFromRecentGesturalNavPortrait.kt
@@ -31,7 +31,7 @@
 class SwitchBackToSplitFromRecentGesturalNavPortrait :
     SwitchBackToSplitFromRecent(Rotation.ROTATION_0) {
 
-    @ExpectedScenarios(["QUICKSWITCH"])
+    @ExpectedScenarios(["SPLIT_SCREEN_ENTER"])
     @Test
     override fun switchBackToSplitFromRecent() = super.switchBackToSplitFromRecent()
 
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/UnlockKeyguardToSplitScreenGesturalNavLandscape.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/UnlockKeyguardToSplitScreenGesturalNavLandscape.kt
index f4e7298..c744103 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/UnlockKeyguardToSplitScreenGesturalNavLandscape.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/UnlockKeyguardToSplitScreenGesturalNavLandscape.kt
@@ -29,7 +29,7 @@
 @RunWith(FlickerServiceJUnit4ClassRunner::class)
 class UnlockKeyguardToSplitScreenGesturalNavLandscape : UnlockKeyguardToSplitScreen() {
 
-    @ExpectedScenarios(["QUICKSWITCH"])
+    @ExpectedScenarios(["LOCKSCREEN_UNLOCK_ANIMATION"])
     @Test
     override fun unlockKeyguardToSplitScreen() = super.unlockKeyguardToSplitScreen()
 
diff --git a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/UnlockKeyguardToSplitScreenGesturalNavPortrait.kt b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/UnlockKeyguardToSplitScreenGesturalNavPortrait.kt
index f38b2e8..11a4e02 100644
--- a/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/UnlockKeyguardToSplitScreenGesturalNavPortrait.kt
+++ b/libs/WindowManager/Shell/tests/flicker/src/com/android/wm/shell/flicker/service/splitscreen/flicker/UnlockKeyguardToSplitScreenGesturalNavPortrait.kt
@@ -29,7 +29,7 @@
 @RunWith(FlickerServiceJUnit4ClassRunner::class)
 class UnlockKeyguardToSplitScreenGesturalNavPortrait : UnlockKeyguardToSplitScreen() {
 
-    @ExpectedScenarios(["QUICKSWITCH"])
+    @ExpectedScenarios(["LOCKSCREEN_UNLOCK_ANIMATION"])
     @Test
     override fun unlockKeyguardToSplitScreen() = super.unlockKeyguardToSplitScreen()
 
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleDataTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleDataTest.java
index 4a55429..26c7394 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleDataTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/bubbles/BubbleDataTest.java
@@ -111,6 +111,8 @@
     @Mock
     private BubbleLogger mBubbleLogger;
     @Mock
+    private BubbleEducationController mEducationController;
+    @Mock
     private ShellExecutor mMainExecutor;
 
     @Captor
@@ -191,7 +193,7 @@
 
         mPositioner = new TestableBubblePositioner(mContext,
                 mock(WindowManager.class));
-        mBubbleData = new BubbleData(getContext(), mBubbleLogger, mPositioner,
+        mBubbleData = new BubbleData(getContext(), mBubbleLogger, mPositioner, mEducationController,
                 mMainExecutor);
 
         // Used by BubbleData to set lastAccessedTime
@@ -385,6 +387,65 @@
         assertOverflowChangedTo(ImmutableList.of());
     }
 
+    /**
+     * Verifies that the update shouldn't show the user education, if the education is not required
+     */
+    @Test
+    public void test_shouldNotShowEducation() {
+        // Setup
+        when(mEducationController.shouldShowStackEducation(any())).thenReturn(false);
+        mBubbleData.setListener(mListener);
+
+        // Test
+        mBubbleData.notificationEntryUpdated(mBubbleA1, /* suppressFlyout */ true, /* showInShade */
+                true);
+
+        // Verify
+        verifyUpdateReceived();
+        BubbleData.Update update = mUpdateCaptor.getValue();
+        assertThat(update.shouldShowEducation).isFalse();
+    }
+
+    /**
+     * Verifies that the update should show the user education, if the education is required
+     */
+    @Test
+    public void test_shouldShowEducation() {
+        // Setup
+        when(mEducationController.shouldShowStackEducation(any())).thenReturn(true);
+        mBubbleData.setListener(mListener);
+
+        // Test
+        mBubbleData.notificationEntryUpdated(mBubbleA1, /* suppressFlyout */ true, /* showInShade */
+                true);
+
+        // Verify
+        verifyUpdateReceived();
+        BubbleData.Update update = mUpdateCaptor.getValue();
+        assertThat(update.shouldShowEducation).isTrue();
+    }
+
+    /**
+     * Verifies that the update shouldn't show the user education, if the education is required but
+     * the bubble should auto-expand
+     */
+    @Test
+    public void test_shouldShowEducation_shouldAutoExpand() {
+        // Setup
+        when(mEducationController.shouldShowStackEducation(any())).thenReturn(true);
+        mBubbleData.setListener(mListener);
+        mBubbleA1.setShouldAutoExpand(true);
+
+        // Test
+        mBubbleData.notificationEntryUpdated(mBubbleA1, /* suppressFlyout */ true, /* showInShade */
+                true);
+
+        // Verify
+        verifyUpdateReceived();
+        BubbleData.Update update = mUpdateCaptor.getValue();
+        assertThat(update.shouldShowEducation).isFalse();
+    }
+
     // COLLAPSED / ADD
 
     /**
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/split/SplitLayoutTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/split/SplitLayoutTests.java
index fe2da5d..ad84c7f 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/split/SplitLayoutTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/split/SplitLayoutTests.java
@@ -18,9 +18,9 @@
 
 import static android.content.res.Configuration.ORIENTATION_LANDSCAPE;
 import static android.content.res.Configuration.ORIENTATION_PORTRAIT;
-
+import static com.android.wm.shell.common.split.SplitScreenConstants.SNAP_TO_END_AND_DISMISS;
+import static com.android.wm.shell.common.split.SplitScreenConstants.SNAP_TO_START_AND_DISMISS;
 import static com.google.common.truth.Truth.assertThat;
-
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.ArgumentMatchers.eq;
@@ -146,7 +146,7 @@
     public void testSnapToDismissStart() {
         // verify it callbacks properly when the snap target indicates dismissing split.
         DividerSnapAlgorithm.SnapTarget snapTarget = getSnapTarget(0 /* position */,
-                DividerSnapAlgorithm.SnapTarget.FLAG_DISMISS_START);
+                SNAP_TO_START_AND_DISMISS);
 
         mSplitLayout.snapToTarget(mSplitLayout.getDividePosition(), snapTarget);
         waitDividerFlingFinished();
@@ -158,7 +158,7 @@
     public void testSnapToDismissEnd() {
         // verify it callbacks properly when the snap target indicates dismissing split.
         DividerSnapAlgorithm.SnapTarget snapTarget = getSnapTarget(0 /* position */,
-                DividerSnapAlgorithm.SnapTarget.FLAG_DISMISS_END);
+                SNAP_TO_END_AND_DISMISS);
 
         mSplitLayout.snapToTarget(mSplitLayout.getDividePosition(), snapTarget);
         waitDividerFlingFinished();
diff --git a/libs/hwui/Android.bp b/libs/hwui/Android.bp
index ce6b4b7..502ff87 100644
--- a/libs/hwui/Android.bp
+++ b/libs/hwui/Android.bp
@@ -737,6 +737,7 @@
         "tests/unit/EglManagerTests.cpp",
         "tests/unit/FatVectorTests.cpp",
         "tests/unit/GraphicsStatsServiceTests.cpp",
+        "tests/unit/HintSessionWrapperTests.cpp",
         "tests/unit/JankTrackerTests.cpp",
         "tests/unit/FrameMetricsReporterTests.cpp",
         "tests/unit/LayerUpdateQueueTests.cpp",
diff --git a/libs/hwui/HardwareBitmapUploader.cpp b/libs/hwui/HardwareBitmapUploader.cpp
index 19a1dfa..16de21d 100644
--- a/libs/hwui/HardwareBitmapUploader.cpp
+++ b/libs/hwui/HardwareBitmapUploader.cpp
@@ -22,6 +22,7 @@
 #include <GLES2/gl2ext.h>
 #include <GLES3/gl3.h>
 #include <GrDirectContext.h>
+#include <GrTypes.h>
 #include <SkBitmap.h>
 #include <SkCanvas.h>
 #include <SkImage.h>
@@ -265,7 +266,7 @@
           sk_sp<SkImage> image =
               SkImages::TextureFromAHardwareBufferWithData(mGrContext.get(), bitmap.pixmap(),
                                                            ahb);
-          mGrContext->submit(true);
+          mGrContext->submit(GrSyncCpu::kYes);
 
           uploadSucceeded = (image.get() != nullptr);
         });
diff --git a/libs/hwui/jni/text/MeasuredText.cpp b/libs/hwui/jni/text/MeasuredText.cpp
index f6ae169..746745a 100644
--- a/libs/hwui/jni/text/MeasuredText.cpp
+++ b/libs/hwui/jni/text/MeasuredText.cpp
@@ -62,13 +62,14 @@
 
 // Regular JNI
 static void nAddStyleRun(JNIEnv* /* unused */, jclass /* unused */, jlong builderPtr,
-                         jlong paintPtr, jint lbStyle, jint lbWordStyle, jint start, jint end,
-                         jboolean isRtl) {
+                         jlong paintPtr, jint lbStyle, jint lbWordStyle, jboolean hyphenation,
+                         jint start, jint end, jboolean isRtl) {
     Paint* paint = toPaint(paintPtr);
     const Typeface* typeface = Typeface::resolveDefault(paint->getAndroidTypeface());
     minikin::MinikinPaint minikinPaint = MinikinUtils::prepareMinikinPaint(paint, typeface);
     toBuilder(builderPtr)
-            ->addStyleRun(start, end, std::move(minikinPaint), lbStyle, lbWordStyle, isRtl);
+            ->addStyleRun(start, end, std::move(minikinPaint), lbStyle, lbWordStyle, hyphenation,
+                          isRtl);
 }
 
 // Regular JNI
@@ -159,7 +160,7 @@
 static const JNINativeMethod gMTBuilderMethods[] = {
         // MeasuredParagraphBuilder native functions.
         {"nInitBuilder", "()J", (void*)nInitBuilder},
-        {"nAddStyleRun", "(JJIIIIZ)V", (void*)nAddStyleRun},
+        {"nAddStyleRun", "(JJIIZIIZ)V", (void*)nAddStyleRun},
         {"nAddReplacementRun", "(JJIIF)V", (void*)nAddReplacementRun},
         {"nBuildMeasuredText", "(JJ[CZZZZ)J", (void*)nBuildMeasuredText},
         {"nFreeBuilder", "(J)V", (void*)nFreeBuilder},
diff --git a/libs/hwui/renderthread/CacheManager.cpp b/libs/hwui/renderthread/CacheManager.cpp
index 8f81dba..735fc07 100644
--- a/libs/hwui/renderthread/CacheManager.cpp
+++ b/libs/hwui/renderthread/CacheManager.cpp
@@ -17,6 +17,7 @@
 #include "CacheManager.h"
 
 #include <GrContextOptions.h>
+#include <GrTypes.h>
 #include <SkExecutor.h>
 #include <SkGraphics.h>
 #include <math.h>
@@ -110,13 +111,18 @@
     contextOptions->fPersistentCache = &cache;
 }
 
+static GrPurgeResourceOptions toSkiaEnum(bool scratchOnly) {
+    return scratchOnly ? GrPurgeResourceOptions::kScratchResourcesOnly :
+                         GrPurgeResourceOptions::kAllResources;
+}
+
 void CacheManager::trimMemory(TrimLevel mode) {
     if (!mGrContext) {
         return;
     }
 
     // flush and submit all work to the gpu and wait for it to finish
-    mGrContext->flushAndSubmit(/*syncCpu=*/true);
+    mGrContext->flushAndSubmit(GrSyncCpu::kYes);
 
     switch (mode) {
         case TrimLevel::BACKGROUND:
@@ -130,7 +136,7 @@
             // that have persistent data to be purged in LRU order.
             mGrContext->setResourceCacheLimit(mBackgroundResourceBytes);
             SkGraphics::SetFontCacheLimit(mBackgroundCpuFontCacheBytes);
-            mGrContext->purgeUnlockedResources(mMemoryPolicy.purgeScratchOnly);
+            mGrContext->purgeUnlockedResources(toSkiaEnum(mMemoryPolicy.purgeScratchOnly));
             mGrContext->setResourceCacheLimit(mMaxResourceBytes);
             SkGraphics::SetFontCacheLimit(mMaxCpuFontCacheBytes);
             break;
@@ -150,7 +156,7 @@
         case CacheTrimLevel::ALL_CACHES:
             SkGraphics::PurgeAllCaches();
             if (mGrContext) {
-                mGrContext->purgeUnlockedResources(false);
+                mGrContext->purgeUnlockedResources(GrPurgeResourceOptions::kAllResources);
             }
             break;
         default:
@@ -285,7 +291,7 @@
                 ns2ms(std::clamp(frameDiffNanos, mMemoryPolicy.minimumResourceRetention,
                                  mMemoryPolicy.maximumResourceRetention));
         mGrContext->performDeferredCleanup(std::chrono::milliseconds(cleanupMillis),
-                                           mMemoryPolicy.purgeScratchOnly);
+                                           toSkiaEnum(mMemoryPolicy.purgeScratchOnly));
     }
 }
 
diff --git a/libs/hwui/renderthread/HintSessionWrapper.cpp b/libs/hwui/renderthread/HintSessionWrapper.cpp
index 1f338ee..b34da51 100644
--- a/libs/hwui/renderthread/HintSessionWrapper.cpp
+++ b/libs/hwui/renderthread/HintSessionWrapper.cpp
@@ -32,65 +32,30 @@
 namespace uirenderer {
 namespace renderthread {
 
-namespace {
+#define BIND_APH_METHOD(name)                                         \
+    name = (decltype(name))dlsym(handle_, "APerformanceHint_" #name); \
+    LOG_ALWAYS_FATAL_IF(name == nullptr, "Failed to find required symbol APerformanceHint_" #name)
 
-typedef APerformanceHintManager* (*APH_getManager)();
-typedef APerformanceHintSession* (*APH_createSession)(APerformanceHintManager*, const int32_t*,
-                                                      size_t, int64_t);
-typedef void (*APH_closeSession)(APerformanceHintSession* session);
-typedef void (*APH_updateTargetWorkDuration)(APerformanceHintSession*, int64_t);
-typedef void (*APH_reportActualWorkDuration)(APerformanceHintSession*, int64_t);
-typedef void (*APH_sendHint)(APerformanceHintSession* session, int32_t);
-
-bool gAPerformanceHintBindingInitialized = false;
-APH_getManager gAPH_getManagerFn = nullptr;
-APH_createSession gAPH_createSessionFn = nullptr;
-APH_closeSession gAPH_closeSessionFn = nullptr;
-APH_updateTargetWorkDuration gAPH_updateTargetWorkDurationFn = nullptr;
-APH_reportActualWorkDuration gAPH_reportActualWorkDurationFn = nullptr;
-APH_sendHint gAPH_sendHintFn = nullptr;
-
-void ensureAPerformanceHintBindingInitialized() {
-    if (gAPerformanceHintBindingInitialized) return;
+void HintSessionWrapper::HintSessionBinding::init() {
+    if (mInitialized) return;
 
     void* handle_ = dlopen("libandroid.so", RTLD_NOW | RTLD_NODELETE);
     LOG_ALWAYS_FATAL_IF(handle_ == nullptr, "Failed to dlopen libandroid.so!");
 
-    gAPH_getManagerFn = (APH_getManager)dlsym(handle_, "APerformanceHint_getManager");
-    LOG_ALWAYS_FATAL_IF(gAPH_getManagerFn == nullptr,
-                        "Failed to find required symbol APerformanceHint_getManager!");
+    BIND_APH_METHOD(getManager);
+    BIND_APH_METHOD(createSession);
+    BIND_APH_METHOD(closeSession);
+    BIND_APH_METHOD(updateTargetWorkDuration);
+    BIND_APH_METHOD(reportActualWorkDuration);
+    BIND_APH_METHOD(sendHint);
 
-    gAPH_createSessionFn = (APH_createSession)dlsym(handle_, "APerformanceHint_createSession");
-    LOG_ALWAYS_FATAL_IF(gAPH_createSessionFn == nullptr,
-                        "Failed to find required symbol APerformanceHint_createSession!");
-
-    gAPH_closeSessionFn = (APH_closeSession)dlsym(handle_, "APerformanceHint_closeSession");
-    LOG_ALWAYS_FATAL_IF(gAPH_closeSessionFn == nullptr,
-                        "Failed to find required symbol APerformanceHint_closeSession!");
-
-    gAPH_updateTargetWorkDurationFn = (APH_updateTargetWorkDuration)dlsym(
-            handle_, "APerformanceHint_updateTargetWorkDuration");
-    LOG_ALWAYS_FATAL_IF(
-            gAPH_updateTargetWorkDurationFn == nullptr,
-            "Failed to find required symbol APerformanceHint_updateTargetWorkDuration!");
-
-    gAPH_reportActualWorkDurationFn = (APH_reportActualWorkDuration)dlsym(
-            handle_, "APerformanceHint_reportActualWorkDuration");
-    LOG_ALWAYS_FATAL_IF(
-            gAPH_reportActualWorkDurationFn == nullptr,
-            "Failed to find required symbol APerformanceHint_reportActualWorkDuration!");
-
-    gAPH_sendHintFn = (APH_sendHint)dlsym(handle_, "APerformanceHint_sendHint");
-    LOG_ALWAYS_FATAL_IF(gAPH_sendHintFn == nullptr,
-                        "Failed to find required symbol APerformanceHint_sendHint!");
-
-    gAPerformanceHintBindingInitialized = true;
+    mInitialized = true;
 }
 
-}  // namespace
-
 HintSessionWrapper::HintSessionWrapper(pid_t uiThreadId, pid_t renderThreadId)
-        : mUiThreadId(uiThreadId), mRenderThreadId(renderThreadId) {}
+        : mUiThreadId(uiThreadId)
+        , mRenderThreadId(renderThreadId)
+        , mBinding(std::make_shared<HintSessionBinding>()) {}
 
 HintSessionWrapper::~HintSessionWrapper() {
     destroy();
@@ -101,7 +66,7 @@
         mHintSession = mHintSessionFuture.get();
     }
     if (mHintSession) {
-        gAPH_closeSessionFn(mHintSession);
+        mBinding->closeSession(mHintSession);
         mSessionValid = true;
         mHintSession = nullptr;
     }
@@ -133,9 +98,9 @@
     // Assume that if we return before the end, it broke
     mSessionValid = false;
 
-    ensureAPerformanceHintBindingInitialized();
+    mBinding->init();
 
-    APerformanceHintManager* manager = gAPH_getManagerFn();
+    APerformanceHintManager* manager = mBinding->getManager();
     if (!manager) return false;
 
     std::vector<pid_t> tids = CommonPool::getThreadIds();
@@ -145,8 +110,9 @@
     // Use a placeholder target value to initialize,
     // this will always be replaced elsewhere before it gets used
     int64_t defaultTargetDurationNanos = 16666667;
-    mHintSessionFuture = CommonPool::async([=, tids = std::move(tids)] {
-        return gAPH_createSessionFn(manager, tids.data(), tids.size(), defaultTargetDurationNanos);
+    mHintSessionFuture = CommonPool::async([=, this, tids = std::move(tids)] {
+        return mBinding->createSession(manager, tids.data(), tids.size(),
+                                       defaultTargetDurationNanos);
     });
     return false;
 }
@@ -158,7 +124,7 @@
         targetWorkDurationNanos > kSanityCheckLowerBound &&
         targetWorkDurationNanos < kSanityCheckUpperBound) {
         mLastTargetWorkDuration = targetWorkDurationNanos;
-        gAPH_updateTargetWorkDurationFn(mHintSession, targetWorkDurationNanos);
+        mBinding->updateTargetWorkDuration(mHintSession, targetWorkDurationNanos);
     }
     mLastFrameNotification = systemTime();
 }
@@ -168,7 +134,7 @@
     mResetsSinceLastReport = 0;
     if (actualDurationNanos > kSanityCheckLowerBound &&
         actualDurationNanos < kSanityCheckUpperBound) {
-        gAPH_reportActualWorkDurationFn(mHintSession, actualDurationNanos);
+        mBinding->reportActualWorkDuration(mHintSession, actualDurationNanos);
     }
 }
 
@@ -179,14 +145,14 @@
     if (now - mLastFrameNotification > kResetHintTimeout &&
         mResetsSinceLastReport <= kMaxResetsSinceLastReport) {
         ++mResetsSinceLastReport;
-        gAPH_sendHintFn(mHintSession, static_cast<int>(SessionHint::CPU_LOAD_RESET));
+        mBinding->sendHint(mHintSession, static_cast<int32_t>(SessionHint::CPU_LOAD_RESET));
     }
     mLastFrameNotification = now;
 }
 
 void HintSessionWrapper::sendLoadIncreaseHint() {
     if (!init()) return;
-    gAPH_sendHintFn(mHintSession, static_cast<int>(SessionHint::CPU_LOAD_UP));
+    mBinding->sendHint(mHintSession, static_cast<int32_t>(SessionHint::CPU_LOAD_UP));
 }
 
 } /* namespace renderthread */
diff --git a/libs/hwui/renderthread/HintSessionWrapper.h b/libs/hwui/renderthread/HintSessionWrapper.h
index bdb9959..f8b876e 100644
--- a/libs/hwui/renderthread/HintSessionWrapper.h
+++ b/libs/hwui/renderthread/HintSessionWrapper.h
@@ -29,6 +29,8 @@
 
 class HintSessionWrapper {
 public:
+    friend class HintSessionWrapperTests;
+
     HintSessionWrapper(pid_t uiThreadId, pid_t renderThreadId);
     ~HintSessionWrapper();
 
@@ -55,6 +57,28 @@
     static constexpr nsecs_t kResetHintTimeout = 100_ms;
     static constexpr int64_t kSanityCheckLowerBound = 100_us;
     static constexpr int64_t kSanityCheckUpperBound = 10_s;
+
+    // Allows easier stub when testing
+    class HintSessionBinding {
+    public:
+        virtual ~HintSessionBinding() = default;
+        virtual void init();
+        APerformanceHintManager* (*getManager)();
+        APerformanceHintSession* (*createSession)(APerformanceHintManager* manager,
+                                                  const int32_t* tids, size_t tidCount,
+                                                  int64_t defaultTarget) = nullptr;
+        void (*closeSession)(APerformanceHintSession* session) = nullptr;
+        void (*updateTargetWorkDuration)(APerformanceHintSession* session,
+                                         int64_t targetDuration) = nullptr;
+        void (*reportActualWorkDuration)(APerformanceHintSession* session,
+                                         int64_t actualDuration) = nullptr;
+        void (*sendHint)(APerformanceHintSession* session, int32_t hintId) = nullptr;
+
+    private:
+        bool mInitialized = false;
+    };
+
+    std::shared_ptr<HintSessionBinding> mBinding;
 };
 
 } /* namespace renderthread */
diff --git a/libs/hwui/renderthread/VulkanManager.cpp b/libs/hwui/renderthread/VulkanManager.cpp
index ee3b2e4..90850d3 100644
--- a/libs/hwui/renderthread/VulkanManager.cpp
+++ b/libs/hwui/renderthread/VulkanManager.cpp
@@ -515,7 +515,7 @@
                         // The following flush blocks the GPU immediately instead of waiting for
                         // other drawing ops. It seems dequeue_fence is not respected otherwise.
                         // TODO: remove the flush after finding why backendSemaphore is not working.
-                        skgpu::ganesh::FlushAndSubmit(bufferInfo->skSurface);
+                        skgpu::ganesh::FlushAndSubmit(bufferInfo->skSurface.get());
                     }
                 }
             }
diff --git a/libs/hwui/tests/unit/HintSessionWrapperTests.cpp b/libs/hwui/tests/unit/HintSessionWrapperTests.cpp
new file mode 100644
index 0000000..623be1e
--- /dev/null
+++ b/libs/hwui/tests/unit/HintSessionWrapperTests.cpp
@@ -0,0 +1,151 @@
+/*
+ * 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 <gmock/gmock.h>
+#include <gtest/gtest.h>
+#include <private/performance_hint_private.h>
+#include <renderthread/HintSessionWrapper.h>
+#include <utils/Log.h>
+
+#include <chrono>
+
+#include "Properties.h"
+
+using namespace testing;
+using namespace std::chrono_literals;
+
+APerformanceHintManager* managerPtr = reinterpret_cast<APerformanceHintManager*>(123);
+APerformanceHintSession* sessionPtr = reinterpret_cast<APerformanceHintSession*>(456);
+int uiThreadId = 1;
+int renderThreadId = 2;
+
+namespace android::uirenderer::renderthread {
+
+class HintSessionWrapperTests : public testing::Test {
+public:
+    void SetUp() override;
+    void TearDown() override;
+
+protected:
+    std::shared_ptr<HintSessionWrapper> mWrapper;
+
+    class MockHintSessionBinding : public HintSessionWrapper::HintSessionBinding {
+    public:
+        void init() override;
+
+        MOCK_METHOD(APerformanceHintManager*, fakeGetManager, ());
+        MOCK_METHOD(APerformanceHintSession*, fakeCreateSession,
+                    (APerformanceHintManager*, const int32_t*, size_t, int64_t));
+        MOCK_METHOD(void, fakeCloseSession, (APerformanceHintSession*));
+        MOCK_METHOD(void, fakeUpdateTargetWorkDuration, (APerformanceHintSession*, int64_t));
+        MOCK_METHOD(void, fakeReportActualWorkDuration, (APerformanceHintSession*, int64_t));
+        MOCK_METHOD(void, fakeSendHint, (APerformanceHintSession*, int32_t));
+    };
+
+    // Must be static so it can have function pointers we can point to with static methods
+    static std::shared_ptr<MockHintSessionBinding> sMockBinding;
+
+    // Must be static so we can point to them as normal fn pointers with HintSessionBinding
+    static APerformanceHintManager* stubGetManager() { return sMockBinding->fakeGetManager(); };
+    static APerformanceHintSession* stubCreateSession(APerformanceHintManager* manager,
+                                                      const int32_t* ids, size_t idsSize,
+                                                      int64_t initialTarget) {
+        return sMockBinding->fakeCreateSession(manager, ids, idsSize, initialTarget);
+    }
+    static APerformanceHintSession* stubSlowCreateSession(APerformanceHintManager* manager,
+                                                          const int32_t* ids, size_t idsSize,
+                                                          int64_t initialTarget) {
+        std::this_thread::sleep_for(50ms);
+        return sMockBinding->fakeCreateSession(manager, ids, idsSize, initialTarget);
+    }
+    static void stubCloseSession(APerformanceHintSession* session) {
+        sMockBinding->fakeCloseSession(session);
+    };
+    static void stubUpdateTargetWorkDuration(APerformanceHintSession* session,
+                                             int64_t workDuration) {
+        sMockBinding->fakeUpdateTargetWorkDuration(session, workDuration);
+    }
+    static void stubReportActualWorkDuration(APerformanceHintSession* session,
+                                             int64_t workDuration) {
+        sMockBinding->fakeReportActualWorkDuration(session, workDuration);
+    }
+    static void stubSendHint(APerformanceHintSession* session, int32_t hintId) {
+        sMockBinding->fakeSendHint(session, hintId);
+    };
+    void waitForWrapperReady() { mWrapper->mHintSessionFuture.wait(); }
+};
+
+std::shared_ptr<HintSessionWrapperTests::MockHintSessionBinding>
+        HintSessionWrapperTests::sMockBinding;
+
+void HintSessionWrapperTests::SetUp() {
+    // Pretend it's supported even if we're in an emulator
+    Properties::useHintManager = true;
+    sMockBinding = std::make_shared<NiceMock<MockHintSessionBinding>>();
+    mWrapper = std::make_shared<HintSessionWrapper>(uiThreadId, renderThreadId);
+    mWrapper->mBinding = sMockBinding;
+    EXPECT_CALL(*sMockBinding, fakeGetManager).WillOnce(Return(managerPtr));
+    ON_CALL(*sMockBinding, fakeCreateSession).WillByDefault(Return(sessionPtr));
+}
+
+void HintSessionWrapperTests::MockHintSessionBinding::init() {
+    sMockBinding->getManager = &stubGetManager;
+    if (sMockBinding->createSession == nullptr) {
+        sMockBinding->createSession = &stubCreateSession;
+    }
+    sMockBinding->closeSession = &stubCloseSession;
+    sMockBinding->updateTargetWorkDuration = &stubUpdateTargetWorkDuration;
+    sMockBinding->reportActualWorkDuration = &stubReportActualWorkDuration;
+    sMockBinding->sendHint = &stubSendHint;
+}
+
+void HintSessionWrapperTests::TearDown() {
+    mWrapper = nullptr;
+    sMockBinding = nullptr;
+}
+
+TEST_F(HintSessionWrapperTests, destructorClosesBackgroundSession) {
+    EXPECT_CALL(*sMockBinding, fakeCloseSession(sessionPtr)).Times(1);
+    sMockBinding->createSession = stubSlowCreateSession;
+    mWrapper->init();
+    mWrapper = nullptr;
+}
+
+TEST_F(HintSessionWrapperTests, sessionInitializesCorrectly) {
+    EXPECT_CALL(*sMockBinding, fakeCreateSession(managerPtr, _, Gt(1), _)).Times(1);
+    mWrapper->init();
+    waitForWrapperReady();
+}
+
+TEST_F(HintSessionWrapperTests, loadUpHintsSendCorrectly) {
+    EXPECT_CALL(*sMockBinding,
+                fakeSendHint(sessionPtr, static_cast<int32_t>(SessionHint::CPU_LOAD_UP)))
+            .Times(1);
+    mWrapper->init();
+    waitForWrapperReady();
+    mWrapper->sendLoadIncreaseHint();
+}
+
+TEST_F(HintSessionWrapperTests, loadResetHintsSendCorrectly) {
+    EXPECT_CALL(*sMockBinding,
+                fakeSendHint(sessionPtr, static_cast<int32_t>(SessionHint::CPU_LOAD_RESET)))
+            .Times(1);
+    mWrapper->init();
+    waitForWrapperReady();
+    mWrapper->sendLoadResetHint();
+}
+
+}  // namespace android::uirenderer::renderthread
\ No newline at end of file
diff --git a/media/java/android/media/flags/media_better_together.aconfig b/media/java/android/media/flags/media_better_together.aconfig
index 17962ee..86e53f5 100644
--- a/media/java/android/media/flags/media_better_together.aconfig
+++ b/media/java/android/media/flags/media_better_together.aconfig
@@ -1,8 +1,15 @@
 package: "com.android.media.flags"
 
 flag {
-     namespace: "media_solutions"
-     name: "enable_rlp_callbacks_in_media_router2"
-     description: "Make RouteListingPreference getter and callbacks public in MediaRouter2."
-     bug: "281067101"
+    name: "enable_rlp_callbacks_in_media_router2"
+    namespace: "media_solutions"
+    description: "Make RouteListingPreference getter and callbacks public in MediaRouter2."
+    bug: "281067101"
+}
+
+flag {
+    name: "adjust_volume_for_foreground_app_playing_audio_without_media_session"
+    namespace: "media_solutions"
+    description: "Gates whether to adjust local stream volume when the app in the foreground is the last app to play audio or adjust the volume of the last active media session that the user interacted with."
+    bug: "275185436"
 }
diff --git a/packages/CredentialManager/Android.bp b/packages/CredentialManager/Android.bp
index fe26dc3..233aee2 100644
--- a/packages/CredentialManager/Android.bp
+++ b/packages/CredentialManager/Android.bp
@@ -50,3 +50,46 @@
         proguard_compatibility: false,
     },
 }
+
+android_app {
+    name: "ClockworkCredentialManager",
+    defaults: ["platform_app_defaults"],
+    certificate: "platform",
+    manifest: "wear/AndroidManifest.xml",
+    srcs: ["wear/src/**/*.kt"],
+    resource_dirs: ["wear/res"],
+
+    dex_preopt: {
+        profile_guided: true,
+        profile: "wear/profile.txt.prof",
+    },
+
+    static_libs: [
+        "PlatformComposeCore",
+        "androidx.activity_activity-compose",
+        "androidx.appcompat_appcompat",
+        "androidx.compose.foundation_foundation",
+        "androidx.compose.foundation_foundation-layout",
+        "androidx.compose.material_material-icons-core",
+        "androidx.compose.material_material-icons-extended",
+        "androidx.compose.ui_ui",
+        "androidx.core_core-ktx",
+        "androidx.credentials_credentials",
+        "androidx.lifecycle_lifecycle-extensions",
+        "androidx.lifecycle_lifecycle-livedata",
+        "androidx.lifecycle_lifecycle-runtime-ktx",
+        "androidx.lifecycle_lifecycle-viewmodel-compose",
+        "androidx.wear.compose_compose-foundation",
+        "androidx.wear.compose_compose-material",
+        "kotlinx-coroutines-core",
+    ],
+
+    platform_apis: true,
+    privileged: true,
+
+    kotlincflags: ["-Xjvm-default=all"],
+
+    optimize: {
+        proguard_compatibility: false,
+    },
+}
diff --git a/packages/CredentialManager/horologist/OWNERS b/packages/CredentialManager/horologist/OWNERS
new file mode 100644
index 0000000..b679328
--- /dev/null
+++ b/packages/CredentialManager/horologist/OWNERS
@@ -0,0 +1,4 @@
+include /core/java/android/credentials/OWNERS
+
+shuanghao@google.com
+gustavopagani@google.com
diff --git a/packages/CredentialManager/shared/OWNERS b/packages/CredentialManager/shared/OWNERS
new file mode 100644
index 0000000..b679328
--- /dev/null
+++ b/packages/CredentialManager/shared/OWNERS
@@ -0,0 +1,4 @@
+include /core/java/android/credentials/OWNERS
+
+shuanghao@google.com
+gustavopagani@google.com
diff --git a/packages/CredentialManager/src/com/android/credentialmanager/logging/LifecycleEvent.kt b/packages/CredentialManager/src/com/android/credentialmanager/logging/LifecycleEvent.kt
index 1ede64d..145e44d 100644
--- a/packages/CredentialManager/src/com/android/credentialmanager/logging/LifecycleEvent.kt
+++ b/packages/CredentialManager/src/com/android/credentialmanager/logging/LifecycleEvent.kt
@@ -15,8 +15,6 @@
  */
 package com.android.credentialmanager.logging
 
-import com.android.internal.logging.UiEventLogger.UiEventEnum.RESERVE_NEW_UI_EVENT_ID
-
 import com.android.internal.logging.UiEvent
 import com.android.internal.logging.UiEventLogger
 
diff --git a/packages/CredentialManager/wear/AndroidManifest.xml b/packages/CredentialManager/wear/AndroidManifest.xml
new file mode 100644
index 0000000..001a56d
--- /dev/null
+++ b/packages/CredentialManager/wear/AndroidManifest.xml
@@ -0,0 +1,45 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+/*
+ * Copyright (c) 2017 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+-->
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    package="com.android.credentialmanager">
+
+    <uses-permission android:name="android.permission.LAUNCH_CREDENTIAL_SELECTOR"/>
+    <uses-permission android:name="android.permission.QUERY_ALL_PACKAGES"/>
+    <uses-permission android:name="android.permission.HIDE_NON_SYSTEM_OVERLAY_WINDOWS"/>
+
+    <application
+      android:allowBackup="true"
+      android:dataExtractionRules="@xml/data_extraction_rules"
+      android:fullBackupContent="@xml/backup_rules"
+      android:label="@string/app_name"
+      android:supportsRtl="true"
+      android:theme="@style/Theme.CredentialSelector">
+
+        <activity
+            android:name=".CredentialSelectorActivity"
+            android:exported="true"
+            android:permission="android.permission.LAUNCH_CREDENTIAL_SELECTOR"
+            android:launchMode="singleTop"
+            android:label="@string/app_name"
+            android:excludeFromRecents="true"
+            android:theme="@style/Theme.CredentialSelector">
+        </activity>
+  </application>
+
+</manifest>
diff --git a/packages/CredentialManager/wear/OWNERS b/packages/CredentialManager/wear/OWNERS
new file mode 100644
index 0000000..b679328
--- /dev/null
+++ b/packages/CredentialManager/wear/OWNERS
@@ -0,0 +1,4 @@
+include /core/java/android/credentials/OWNERS
+
+shuanghao@google.com
+gustavopagani@google.com
diff --git a/packages/CredentialManager/wear/profile.txt.prof b/packages/CredentialManager/wear/profile.txt.prof
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/packages/CredentialManager/wear/profile.txt.prof
diff --git a/packages/SystemUI/res-keyguard/values-sw600dp-land/donottranslate.xml b/packages/CredentialManager/wear/res/values/strings.xml
similarity index 69%
rename from packages/SystemUI/res-keyguard/values-sw600dp-land/donottranslate.xml
rename to packages/CredentialManager/wear/res/values/strings.xml
index 1a52e93..10ea918 100644
--- a/packages/SystemUI/res-keyguard/values-sw600dp-land/donottranslate.xml
+++ b/packages/CredentialManager/wear/res/values/strings.xml
@@ -1,6 +1,6 @@
 <?xml version="1.0" encoding="utf-8"?>
 <!--
-  ~ Copyright (C) 2021 The Android Open Source Project
+  ~ Copyright (C) 2023 The Android Open Source Project
   ~
   ~ Licensed under the Apache License, Version 2.0 (the "License");
   ~ you may not use this file except in compliance with the License.
@@ -14,8 +14,8 @@
   ~ See the License for the specific language governing permissions and
   ~ limitations under the License.
   -->
-
 <resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <!-- Don't use the smaller PIN pad keys if we have the screen space to support it. -->
-    <string name="num_pad_key_ratio">1.0</string>
-</resources>
+  <!-- The name of this application. Credential Manager is a service that centralizes and provides
+  access to a user's credentials used to sign in to various apps. [CHAR LIMIT=80] -->
+  <string name="app_name">Credential Manager</string>
+</resources>
\ No newline at end of file
diff --git a/packages/CredentialManager/wear/res/values/themes.xml b/packages/CredentialManager/wear/res/values/themes.xml
new file mode 100644
index 0000000..22329e9f
--- /dev/null
+++ b/packages/CredentialManager/wear/res/values/themes.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2023 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+<resources>
+  <style name="Theme.CredentialSelector" parent="@*android:style/ThemeOverlay.DeviceDefault.Accent.DayNight">
+    <item name="android:windowContentOverlay">@null</item>
+    <item name="android:windowNoTitle">true</item>
+    <item name="android:windowBackground">@android:color/transparent</item>
+    <item name="android:windowIsTranslucent">true</item>
+  </style>
+</resources>
\ No newline at end of file
diff --git a/packages/CredentialManager/wear/res/xml/backup_rules.xml b/packages/CredentialManager/wear/res/xml/backup_rules.xml
new file mode 100644
index 0000000..9b42d90
--- /dev/null
+++ b/packages/CredentialManager/wear/res/xml/backup_rules.xml
@@ -0,0 +1,13 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+   Sample backup rules file; uncomment and customize as necessary.
+   See https://developer.android.com/guide/topics/data/autobackup
+   for details.
+   Note: This file is ignored for devices older that API 31
+   See https://developer.android.com/about/versions/12/backup-restore
+-->
+<full-backup-content>
+  <!--
+   <include domain="sharedpref" path="."/>
+   <exclude domain="sharedpref" path="device.xml"/>
+-->
+</full-backup-content>
\ No newline at end of file
diff --git a/packages/CredentialManager/wear/res/xml/data_extraction_rules.xml b/packages/CredentialManager/wear/res/xml/data_extraction_rules.xml
new file mode 100644
index 0000000..c6c3bb0
--- /dev/null
+++ b/packages/CredentialManager/wear/res/xml/data_extraction_rules.xml
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+   Sample data extraction rules file; uncomment and customize as necessary.
+   See https://developer.android.com/about/versions/12/backup-restore#xml-changes
+   for details.
+-->
+<data-extraction-rules>
+  <cloud-backup>
+    <!-- TODO: Use <include> and <exclude> to control what is backed up.
+        <include .../>
+        <exclude .../>
+        -->
+  </cloud-backup>
+  <!--
+    <device-transfer>
+        <include .../>
+        <exclude .../>
+    </device-transfer>
+    -->
+</data-extraction-rules>
\ No newline at end of file
diff --git a/packages/CredentialManager/wear/src/com/android/credentialmanager/CredentialSelectorActivity.kt b/packages/CredentialManager/wear/src/com/android/credentialmanager/CredentialSelectorActivity.kt
new file mode 100644
index 0000000..f7b2499
--- /dev/null
+++ b/packages/CredentialManager/wear/src/com/android/credentialmanager/CredentialSelectorActivity.kt
@@ -0,0 +1,33 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0N
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import android.os.Bundle
+import androidx.activity.compose.setContent
+import androidx.activity.ComponentActivity
+import androidx.wear.compose.material.MaterialTheme
+import androidx.wear.compose.material.Text
+
+class CredentialSelectorActivity : ComponentActivity() {
+    override fun onCreate(savedInstanceState: Bundle?) {
+        super.onCreate(savedInstanceState)
+
+        setContent {
+            MaterialTheme {
+                Text("Credential Manager entry point")
+            }
+        }
+    }
+}
diff --git a/packages/SettingsLib/res/values/strings.xml b/packages/SettingsLib/res/values/strings.xml
index ec24ab7..8dc95f4 100644
--- a/packages/SettingsLib/res/values/strings.xml
+++ b/packages/SettingsLib/res/values/strings.xml
@@ -1665,14 +1665,6 @@
     <!-- Developer setting: enable animations when a back gesture is executed, full explanation[CHAR LIMIT=NONE] -->
     <string name="back_navigation_animation_dialog">This setting enables system animations for predictive gesture animation. It requires setting per-app "enableOnBackInvokedCallback" to true in the manifest file.</string>
 
-    <!-- [CHAR LIMIT=NONE] Messages shown when users press outside of udfps region during -->
-    <string-array name="udfps_accessibility_touch_hints">
-        <item>Move left</item>
-        <item>Move down</item>
-        <item>Move right</item>
-        <item>Move up</item>
-    </string-array>
-
     <!-- Formatting states for the scale of font size, in percent. Double "%" is required to represent the symbol "%". [CHAR LIMIT=20] -->
     <string name="font_scale_percentage"> <xliff:g id="percentage">%1$d</xliff:g> %%</string>
 
diff --git a/packages/SettingsLib/src/com/android/settingslib/RestrictedLockUtilsInternal.java b/packages/SettingsLib/src/com/android/settingslib/RestrictedLockUtilsInternal.java
index ff960f3..03d1cda 100644
--- a/packages/SettingsLib/src/com/android/settingslib/RestrictedLockUtilsInternal.java
+++ b/packages/SettingsLib/src/com/android/settingslib/RestrictedLockUtilsInternal.java
@@ -20,6 +20,8 @@
 import static android.app.admin.DevicePolicyManager.MTE_NOT_CONTROLLED_BY_POLICY;
 import static android.app.admin.DevicePolicyManager.PROFILE_KEYGUARD_FEATURES_AFFECT_OWNER;
 
+import static com.android.settingslib.Utils.getColorAttrDefaultColor;
+
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.UserIdInt;
@@ -125,7 +127,8 @@
             return null;
         }
 
-        final EnforcedAdmin admin = getProfileOrDeviceOwner(context, enforcingUser.getUserHandle());
+        final EnforcedAdmin admin =
+                getProfileOrDeviceOwner(context, userRestriction, enforcingUser.getUserHandle());
         if (admin != null) {
             return admin;
         }
@@ -638,7 +641,8 @@
         removeExistingRestrictedSpans(sb);
 
         if (admin != null) {
-            final int disabledColor = context.getColor(R.color.disabled_text_color);
+            final int disabledColor = getColorAttrDefaultColor(context,
+                    android.R.attr.textColorHint);
             sb.setSpan(new ForegroundColorSpan(disabledColor), 0, sb.length(),
                     Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
             ImageSpan image = new RestrictedLockImageSpan(context);
diff --git a/packages/SettingsLib/src/com/android/settingslib/RestrictedPreferenceHelper.java b/packages/SettingsLib/src/com/android/settingslib/RestrictedPreferenceHelper.java
index dcb0a07..8b0f19d 100644
--- a/packages/SettingsLib/src/com/android/settingslib/RestrictedPreferenceHelper.java
+++ b/packages/SettingsLib/src/com/android/settingslib/RestrictedPreferenceHelper.java
@@ -17,7 +17,6 @@
 package com.android.settingslib;
 
 import static android.app.admin.DevicePolicyResources.Strings.Settings.CONTROLLED_BY_ADMIN_SUMMARY;
-
 import static com.android.settingslib.RestrictedLockUtils.EnforcedAdmin;
 
 import android.app.admin.DevicePolicyManager;
@@ -32,6 +31,7 @@
 import android.widget.TextView;
 
 import androidx.annotation.RequiresApi;
+import androidx.annotation.VisibleForTesting;
 import androidx.preference.Preference;
 import androidx.preference.PreferenceViewHolder;
 
@@ -48,7 +48,8 @@
     int uid;
 
     private boolean mDisabledByAdmin;
-    private EnforcedAdmin mEnforcedAdmin;
+    @VisibleForTesting
+    EnforcedAdmin mEnforcedAdmin;
     private String mAttrUserRestriction = null;
     private boolean mDisabledSummary = false;
 
@@ -193,8 +194,14 @@
      * @return true if the disabled state was changed.
      */
     public boolean setDisabledByAdmin(EnforcedAdmin admin) {
-        final boolean disabled = (admin != null ? true : false);
-        mEnforcedAdmin = admin;
+        boolean disabled = false;
+        mEnforcedAdmin = null;
+        if (admin != null) {
+            disabled = true;
+            // Copy the received instance to prevent pass be reference being overwritten.
+            mEnforcedAdmin = new EnforcedAdmin(admin);
+        }
+
         boolean changed = false;
         if (mDisabledByAdmin != disabled) {
             mDisabledByAdmin = disabled;
diff --git a/packages/SettingsLib/src/com/android/settingslib/Utils.java b/packages/SettingsLib/src/com/android/settingslib/Utils.java
index 9d533fa..412a342 100644
--- a/packages/SettingsLib/src/com/android/settingslib/Utils.java
+++ b/packages/SettingsLib/src/com/android/settingslib/Utils.java
@@ -549,7 +549,12 @@
 
     /**
      * Return the combined service state.
-     * To make behavior consistent with SystemUI and Settings/AboutPhone/SIM status UI
+     * To make behavior consistent with SystemUI and Settings/AboutPhone/SIM status UI.
+     *
+     * This method returns a single service state int if either the voice reg state is
+     * {@link ServiceState#STATE_IN_SERVICE} or if data network is registered via a
+     * WWAN transport type. We consider the combined service state of an IWLAN network
+     * to be OOS.
      *
      * @param serviceState Service state. {@link ServiceState}
      */
@@ -558,23 +563,37 @@
             return ServiceState.STATE_OUT_OF_SERVICE;
         }
 
-        // Consider the device to be in service if either voice or data
-        // service is available. Some SIM cards are marketed as data-only
-        // and do not support voice service, and on these SIM cards, we
-        // want to show signal bars for data service as well as the "no
-        // service" or "emergency calls only" text that indicates that voice
-        // is not available. Note that we ignore the IWLAN service state
-        // because that state indicates the use of VoWIFI and not cell service
-        final int state = serviceState.getState();
-        final int dataState = serviceState.getDataRegistrationState();
+        final int voiceRegState = serviceState.getVoiceRegState();
 
-        if (state == ServiceState.STATE_OUT_OF_SERVICE
-                || state == ServiceState.STATE_EMERGENCY_ONLY) {
-            if (dataState == ServiceState.STATE_IN_SERVICE && isNotInIwlan(serviceState)) {
+        // Consider a mobile connection to be "in service" if either voice is IN_SERVICE
+        // or the data registration reports IN_SERVICE on a transport type of WWAN. This
+        // effectively excludes the IWLAN condition. IWLAN connections imply service via
+        // Wi-Fi rather than cellular, and so we do not consider these transports when
+        // determining if cellular is "in service".
+
+        if (voiceRegState == ServiceState.STATE_OUT_OF_SERVICE
+                || voiceRegState == ServiceState.STATE_EMERGENCY_ONLY) {
+            if (isDataRegInWwanAndInService(serviceState)) {
                 return ServiceState.STATE_IN_SERVICE;
             }
         }
-        return state;
+
+        return voiceRegState;
+    }
+
+    // ServiceState#mDataRegState can be set to IN_SERVICE if the network is registered
+    // on either a WLAN or WWAN network. Since we want to exclude the WLAN network, we can
+    // query the WWAN network directly and check for its registration state
+    private static boolean isDataRegInWwanAndInService(ServiceState serviceState) {
+        final NetworkRegistrationInfo networkRegWwan = serviceState.getNetworkRegistrationInfo(
+                NetworkRegistrationInfo.DOMAIN_PS,
+                AccessNetworkConstants.TRANSPORT_TYPE_WWAN);
+
+        if (networkRegWwan == null) {
+            return false;
+        }
+
+        return networkRegWwan.isInService();
     }
 
     /** Get the corresponding adaptive icon drawable. */
@@ -598,21 +617,6 @@
                 UserHandle.getUserHandleForUid(appInfo.uid));
     }
 
-    private static boolean isNotInIwlan(ServiceState serviceState) {
-        final NetworkRegistrationInfo networkRegWlan = serviceState.getNetworkRegistrationInfo(
-                NetworkRegistrationInfo.DOMAIN_PS,
-                AccessNetworkConstants.TRANSPORT_TYPE_WLAN);
-        if (networkRegWlan == null) {
-            return true;
-        }
-
-        final boolean isInIwlan = (networkRegWlan.getRegistrationState()
-                == NetworkRegistrationInfo.REGISTRATION_STATE_HOME)
-                || (networkRegWlan.getRegistrationState()
-                == NetworkRegistrationInfo.REGISTRATION_STATE_ROAMING);
-        return !isInIwlan;
-    }
-
     /**
      * Returns a bitmap with rounded corner.
      *
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java
index 64c271b..c67df71 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/CachedBluetoothDevice.java
@@ -54,6 +54,7 @@
 import java.util.List;
 import java.util.Set;
 import java.util.concurrent.CopyOnWriteArrayList;
+import java.util.stream.Stream;
 
 /**
  * CachedBluetoothDevice represents a remote Bluetooth device. It contains
@@ -684,6 +685,20 @@
         return mDevice.getBatteryLevel();
     }
 
+    /**
+     * Get the lowest battery level from remote device and its member devices
+     * @return battery level in percentage [0-100] or
+     * {@link BluetoothDevice#BATTERY_LEVEL_UNKNOWN}
+     */
+    public int getMinBatteryLevelWithMemberDevices() {
+        return Stream.concat(Stream.of(this), mMemberDevices.stream())
+                .mapToInt(cachedDevice -> cachedDevice.getBatteryLevel())
+                .filter(batteryLevel -> batteryLevel > BluetoothDevice.BATTERY_LEVEL_UNKNOWN)
+                .min()
+                .orElse(BluetoothDevice.BATTERY_LEVEL_UNKNOWN);
+    }
+
+
     void refresh() {
         ThreadUtils.postOnBackgroundThread(() -> {
             if (BluetoothUtils.isAdvancedDetailsHeader(mDevice)) {
@@ -1185,7 +1200,7 @@
         // BluetoothDevice.BATTERY_LEVEL_BLUETOOTH_OFF, or BluetoothDevice.BATTERY_LEVEL_UNKNOWN,
         // any other value should be a framework bug. Thus assume here that if value is greater
         // than BluetoothDevice.BATTERY_LEVEL_UNKNOWN, it must be valid
-        final int batteryLevel = getBatteryLevel();
+        final int batteryLevel = getMinBatteryLevelWithMemberDevices();
         if (batteryLevel > BluetoothDevice.BATTERY_LEVEL_UNKNOWN) {
             // TODO: name com.android.settingslib.bluetooth.Utils something different
             batteryLevelPercentageString =
@@ -1360,7 +1375,7 @@
         // BluetoothDevice.BATTERY_LEVEL_BLUETOOTH_OFF, or BluetoothDevice.BATTERY_LEVEL_UNKNOWN,
         // any other value should be a framework bug. Thus assume here that if value is greater
         // than BluetoothDevice.BATTERY_LEVEL_UNKNOWN, it must be valid
-        final int batteryLevel = getBatteryLevel();
+        final int batteryLevel = getMinBatteryLevelWithMemberDevices();
         if (batteryLevel > BluetoothDevice.BATTERY_LEVEL_UNKNOWN) {
             // TODO: name com.android.settingslib.bluetooth.Utils something different
             batteryLevelPercentageString =
diff --git a/packages/SettingsLib/src/com/android/settingslib/fuelgauge/BatteryStatus.java b/packages/SettingsLib/src/com/android/settingslib/fuelgauge/BatteryStatus.java
index f90a17a..7f1f3f6 100644
--- a/packages/SettingsLib/src/com/android/settingslib/fuelgauge/BatteryStatus.java
+++ b/packages/SettingsLib/src/com/android/settingslib/fuelgauge/BatteryStatus.java
@@ -40,9 +40,7 @@
  * Stores and computes some battery information.
  */
 public class BatteryStatus {
-    private static final int LOW_BATTERY_THRESHOLD = 20;
-    private static final int SEVERE_LOW_BATTERY_THRESHOLD = 10;
-    private static final int EXTREME_LOW_BATTERY_THRESHOLD = 3;
+
     private static final int DEFAULT_CHARGING_VOLTAGE_MICRO_VOLT = 5000000;
 
     public static final int BATTERY_LEVEL_UNKNOWN = -1;
@@ -50,6 +48,9 @@
     public static final int CHARGING_SLOWLY = 0;
     public static final int CHARGING_REGULAR = 1;
     public static final int CHARGING_FAST = 2;
+    public static final int LOW_BATTERY_THRESHOLD = 20;
+    public static final int SEVERE_LOW_BATTERY_THRESHOLD = 10;
+    public static final int EXTREME_LOW_BATTERY_THRESHOLD = 3;
 
     public final int status;
     public final int level;
@@ -197,9 +198,14 @@
                 : Math.round((level / (float) scale) * 100f);
     }
 
+    /** Returns the plugged type from {@code batteryChangedIntent}. */
+    public static int getPluggedType(Intent batteryChangedIntent) {
+        return batteryChangedIntent.getIntExtra(EXTRA_PLUGGED, 0);
+    }
+
     /** Whether the device is plugged or not. */
     public static boolean isPluggedIn(Intent batteryChangedIntent) {
-        return isPluggedIn(batteryChangedIntent.getIntExtra(EXTRA_PLUGGED, 0));
+        return isPluggedIn(getPluggedType(batteryChangedIntent));
     }
 
     /** Whether the device is plugged or not. */
diff --git a/packages/SettingsLib/src/com/android/settingslib/fuelgauge/BatteryUtils.java b/packages/SettingsLib/src/com/android/settingslib/fuelgauge/BatteryUtils.java
index ad9886e..88dcc0d 100644
--- a/packages/SettingsLib/src/com/android/settingslib/fuelgauge/BatteryUtils.java
+++ b/packages/SettingsLib/src/com/android/settingslib/fuelgauge/BatteryUtils.java
@@ -22,6 +22,9 @@
 
 public final class BatteryUtils {
 
+    /** The key to get the time to full from Settings.Global */
+    public static final String GLOBAL_TIME_TO_FULL_MILLIS = "time_to_full_millis";
+
     /** Gets the latest sticky battery intent from the Android system. */
     public static Intent getBatteryIntent(Context context) {
         return context.registerReceiver(
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/RestrictedLockUtilsTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/RestrictedLockUtilsTest.java
index 94e28f2..a03977c1 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/RestrictedLockUtilsTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/RestrictedLockUtilsTest.java
@@ -21,11 +21,8 @@
 import static android.app.admin.DevicePolicyManager.KEYGUARD_DISABLE_FINGERPRINT;
 import static android.app.admin.DevicePolicyManager.KEYGUARD_DISABLE_REMOTE_INPUT;
 import static android.app.admin.DevicePolicyManager.KEYGUARD_DISABLE_UNREDACTED_NOTIFICATIONS;
-
 import static com.android.settingslib.RestrictedLockUtils.EnforcedAdmin;
-
 import static com.google.common.truth.Truth.assertThat;
-
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.doReturn;
@@ -78,6 +75,8 @@
 
         when(mContext.getSystemService(Context.DEVICE_POLICY_SERVICE))
                 .thenReturn(mDevicePolicyManager);
+        when(mContext.getSystemService(DevicePolicyManager.class))
+                .thenReturn(mDevicePolicyManager);
         when(mContext.getSystemService(Context.USER_SERVICE))
                 .thenReturn(mUserManager);
         when(mContext.getPackageManager())
@@ -87,14 +86,20 @@
     }
 
     @Test
-    public void checkIfRestrictionEnforced_deviceOwner() {
+    public void checkIfRestrictionEnforced_deviceOwner()
+            throws PackageManager.NameNotFoundException {
         UserManager.EnforcingUser enforcingUser = new UserManager.EnforcingUser(mUserId,
                 UserManager.RESTRICTION_SOURCE_DEVICE_OWNER);
         final String userRestriction = UserManager.DISALLOW_UNINSTALL_APPS;
         when(mUserManager.getUserRestrictionSources(userRestriction,
                 UserHandle.of(mUserId))).
                 thenReturn(Collections.singletonList(enforcingUser));
-        setUpDeviceOwner(mAdmin1);
+
+        when(mContext.createPackageContextAsUser(any(), eq(0),
+                eq(UserHandle.of(mUserId))))
+                .thenReturn(mContext);
+
+        setUpDeviceOwner(mAdmin1, mUserId);
 
         EnforcedAdmin enforcedAdmin = RestrictedLockUtilsInternal
                 .checkIfRestrictionEnforced(mContext, userRestriction, mUserId);
@@ -105,14 +110,20 @@
     }
 
     @Test
-    public void checkIfRestrictionEnforced_profileOwner() {
+    public void checkIfRestrictionEnforced_profileOwner()
+            throws PackageManager.NameNotFoundException {
         UserManager.EnforcingUser enforcingUser = new UserManager.EnforcingUser(mUserId,
                 UserManager.RESTRICTION_SOURCE_PROFILE_OWNER);
         final String userRestriction = UserManager.DISALLOW_UNINSTALL_APPS;
         when(mUserManager.getUserRestrictionSources(userRestriction,
                 UserHandle.of(mUserId))).
                 thenReturn(Collections.singletonList(enforcingUser));
-        setUpProfileOwner(mAdmin1, mUserId);
+
+        when(mContext.createPackageContextAsUser(any(), eq(0),
+                eq(UserHandle.of(mUserId))))
+                .thenReturn(mContext);
+
+        setUpProfileOwner(mAdmin1);
 
         EnforcedAdmin enforcedAdmin = RestrictedLockUtilsInternal
                 .checkIfRestrictionEnforced(mContext, userRestriction, mUserId);
@@ -326,11 +337,12 @@
                 .thenReturn(Arrays.asList(activeAdmins));
     }
 
-    private void setUpDeviceOwner(ComponentName admin) {
+    private void setUpDeviceOwner(ComponentName admin, int userId) {
         when(mDevicePolicyManager.getDeviceOwnerComponentOnAnyUser()).thenReturn(admin);
+        when(mDevicePolicyManager.getDeviceOwnerUser()).thenReturn(UserHandle.of(userId));
     }
 
-    private void setUpProfileOwner(ComponentName admin, int userId) {
-        when(mDevicePolicyManager.getProfileOwnerAsUser(userId)).thenReturn(admin);
+    private void setUpProfileOwner(ComponentName admin) {
+        when(mDevicePolicyManager.getProfileOwner()).thenReturn(admin);
     }
 }
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/RestrictedPreferenceHelperTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/RestrictedPreferenceHelperTest.java
index 1b0738f..701f008 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/RestrictedPreferenceHelperTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/RestrictedPreferenceHelperTest.java
@@ -16,6 +16,7 @@
 
 package com.android.settingslib;
 
+import static com.google.common.truth.Truth.assertThat;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.Mockito.RETURNS_DEEP_STUBS;
 import static org.mockito.Mockito.doReturn;
@@ -119,4 +120,26 @@
 
         verify(mRestrictedTopLevelPreference, never()).setEnabled(false);
     }
+
+    /**
+     * Tests if the instance of {@link RestrictedLockUtils.EnforcedAdmin} is received by
+     * {@link RestrictedPreferenceHelper#setDisabledByAdmin(RestrictedLockUtils.EnforcedAdmin)} as a
+     * copy or as a reference.
+     */
+    @Test
+    public void setDisabledByAdmin_disablePreference_receivedEnforcedAdminIsNotAReference() {
+        RestrictedLockUtils.EnforcedAdmin enforcedAdmin =
+                new RestrictedLockUtils.EnforcedAdmin(/* component */ null,
+                        /* enforcedRestriction */ "some_restriction",
+                        /* userHandle */ null);
+
+        mHelper.setDisabledByAdmin(enforcedAdmin);
+
+        // If `setDisabledByAdmin` stored `enforcedAdmin` as a reference, then the following
+        // assignment would be propagated.
+        enforcedAdmin.enforcedRestriction = null;
+        assertThat(mHelper.mEnforcedAdmin.enforcedRestriction).isEqualTo("some_restriction");
+
+        assertThat(mHelper.isDisabledByAdmin()).isTrue();
+    }
 }
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/UtilsTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/UtilsTest.java
index bb72375..29846ac 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/UtilsTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/UtilsTest.java
@@ -200,47 +200,55 @@
 
     @Test
     public void isInService_voiceInService_returnTrue() {
-        when(mServiceState.getState()).thenReturn(ServiceState.STATE_IN_SERVICE);
+        when(mServiceState.getVoiceRegState()).thenReturn(ServiceState.STATE_IN_SERVICE);
 
         assertThat(Utils.isInService(mServiceState)).isTrue();
     }
 
     @Test
     public void isInService_voiceOutOfServiceDataInService_returnTrue() {
-        when(mServiceState.getState()).thenReturn(ServiceState.STATE_OUT_OF_SERVICE);
+        when(mServiceState.getVoiceRegState()).thenReturn(ServiceState.STATE_OUT_OF_SERVICE);
         when(mServiceState.getDataRegistrationState()).thenReturn(ServiceState.STATE_IN_SERVICE);
         when(mServiceState.getNetworkRegistrationInfo(NetworkRegistrationInfo.DOMAIN_PS,
-                AccessNetworkConstants.TRANSPORT_TYPE_WLAN)).thenReturn(mNetworkRegistrationInfo);
-        when(mNetworkRegistrationInfo.getRegistrationState()).thenReturn(
-                NetworkRegistrationInfo.REGISTRATION_STATE_UNKNOWN);
+                AccessNetworkConstants.TRANSPORT_TYPE_WWAN)).thenReturn(mNetworkRegistrationInfo);
+        when(mNetworkRegistrationInfo.isInService()).thenReturn(true);
 
         assertThat(Utils.isInService(mServiceState)).isTrue();
     }
 
     @Test
     public void isInService_voiceOutOfServiceDataInServiceOnIwLan_returnFalse() {
-        when(mServiceState.getState()).thenReturn(ServiceState.STATE_OUT_OF_SERVICE);
+        when(mServiceState.getVoiceRegState()).thenReturn(ServiceState.STATE_OUT_OF_SERVICE);
         when(mServiceState.getNetworkRegistrationInfo(NetworkRegistrationInfo.DOMAIN_PS,
                 AccessNetworkConstants.TRANSPORT_TYPE_WLAN)).thenReturn(mNetworkRegistrationInfo);
-        when(mNetworkRegistrationInfo.getRegistrationState()).thenReturn(
-                NetworkRegistrationInfo.REGISTRATION_STATE_HOME);
         when(mServiceState.getDataRegistrationState()).thenReturn(ServiceState.STATE_IN_SERVICE);
+        when(mNetworkRegistrationInfo.isInService()).thenReturn(true);
+
+        assertThat(Utils.isInService(mServiceState)).isFalse();
+    }
+
+    @Test
+    public void isInService_voiceOutOfServiceDataNull_returnFalse() {
+        when(mServiceState.getVoiceRegState()).thenReturn(ServiceState.STATE_OUT_OF_SERVICE);
+        when(mServiceState.getNetworkRegistrationInfo(NetworkRegistrationInfo.DOMAIN_PS,
+                AccessNetworkConstants.TRANSPORT_TYPE_WWAN)).thenReturn(null);
 
         assertThat(Utils.isInService(mServiceState)).isFalse();
     }
 
     @Test
     public void isInService_voiceOutOfServiceDataOutOfService_returnFalse() {
-        when(mServiceState.getState()).thenReturn(ServiceState.STATE_OUT_OF_SERVICE);
-        when(mServiceState.getDataRegistrationState()).thenReturn(
-                ServiceState.STATE_OUT_OF_SERVICE);
+        when(mServiceState.getVoiceRegState()).thenReturn(ServiceState.STATE_OUT_OF_SERVICE);
+        when(mServiceState.getNetworkRegistrationInfo(NetworkRegistrationInfo.DOMAIN_PS,
+                AccessNetworkConstants.TRANSPORT_TYPE_WWAN)).thenReturn(mNetworkRegistrationInfo);
+        when(mNetworkRegistrationInfo.isInService()).thenReturn(false);
 
         assertThat(Utils.isInService(mServiceState)).isFalse();
     }
 
     @Test
     public void isInService_ServiceStatePowerOff_returnFalse() {
-        when(mServiceState.getState()).thenReturn(ServiceState.STATE_POWER_OFF);
+        when(mServiceState.getVoiceRegState()).thenReturn(ServiceState.STATE_POWER_OFF);
 
         assertThat(Utils.isInService(mServiceState)).isFalse();
     }
@@ -253,7 +261,7 @@
 
     @Test
     public void getCombinedServiceState_ServiceStatePowerOff_returnPowerOff() {
-        when(mServiceState.getState()).thenReturn(ServiceState.STATE_POWER_OFF);
+        when(mServiceState.getVoiceRegState()).thenReturn(ServiceState.STATE_POWER_OFF);
 
         assertThat(Utils.getCombinedServiceState(mServiceState)).isEqualTo(
                 ServiceState.STATE_POWER_OFF);
@@ -261,7 +269,7 @@
 
     @Test
     public void getCombinedServiceState_voiceInService_returnInService() {
-        when(mServiceState.getState()).thenReturn(ServiceState.STATE_IN_SERVICE);
+        when(mServiceState.getVoiceRegState()).thenReturn(ServiceState.STATE_IN_SERVICE);
 
         assertThat(Utils.getCombinedServiceState(mServiceState)).isEqualTo(
                 ServiceState.STATE_IN_SERVICE);
@@ -269,12 +277,10 @@
 
     @Test
     public void getCombinedServiceState_voiceOutOfServiceDataInService_returnInService() {
-        when(mServiceState.getState()).thenReturn(ServiceState.STATE_OUT_OF_SERVICE);
-        when(mServiceState.getDataRegistrationState()).thenReturn(ServiceState.STATE_IN_SERVICE);
+        when(mServiceState.getVoiceRegState()).thenReturn(ServiceState.STATE_OUT_OF_SERVICE);
         when(mServiceState.getNetworkRegistrationInfo(NetworkRegistrationInfo.DOMAIN_PS,
-                AccessNetworkConstants.TRANSPORT_TYPE_WLAN)).thenReturn(mNetworkRegistrationInfo);
-        when(mNetworkRegistrationInfo.getRegistrationState()).thenReturn(
-                NetworkRegistrationInfo.REGISTRATION_STATE_UNKNOWN);
+                AccessNetworkConstants.TRANSPORT_TYPE_WWAN)).thenReturn(mNetworkRegistrationInfo);
+        when(mNetworkRegistrationInfo.isInService()).thenReturn(true);
 
         assertThat(Utils.getCombinedServiceState(mServiceState)).isEqualTo(
                 ServiceState.STATE_IN_SERVICE);
@@ -282,12 +288,10 @@
 
     @Test
     public void getCombinedServiceState_voiceOutOfServiceDataInServiceOnIwLan_returnOutOfService() {
-        when(mServiceState.getState()).thenReturn(ServiceState.STATE_OUT_OF_SERVICE);
-        when(mServiceState.getDataRegistrationState()).thenReturn(ServiceState.STATE_IN_SERVICE);
+        when(mServiceState.getVoiceRegState()).thenReturn(ServiceState.STATE_OUT_OF_SERVICE);
         when(mServiceState.getNetworkRegistrationInfo(NetworkRegistrationInfo.DOMAIN_PS,
                 AccessNetworkConstants.TRANSPORT_TYPE_WLAN)).thenReturn(mNetworkRegistrationInfo);
-        when(mNetworkRegistrationInfo.getRegistrationState()).thenReturn(
-                NetworkRegistrationInfo.REGISTRATION_STATE_HOME);
+        when(mNetworkRegistrationInfo.isInService()).thenReturn(true);
 
         assertThat(Utils.getCombinedServiceState(mServiceState)).isEqualTo(
                 ServiceState.STATE_OUT_OF_SERVICE);
@@ -295,7 +299,7 @@
 
     @Test
     public void getCombinedServiceState_voiceOutOfServiceDataOutOfService_returnOutOfService() {
-        when(mServiceState.getState()).thenReturn(ServiceState.STATE_OUT_OF_SERVICE);
+        when(mServiceState.getVoiceRegState()).thenReturn(ServiceState.STATE_OUT_OF_SERVICE);
         when(mServiceState.getDataRegistrationState()).thenReturn(
                 ServiceState.STATE_OUT_OF_SERVICE);
 
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceTest.java
index 4b61ff1..85efe69 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/CachedBluetoothDeviceTest.java
@@ -529,6 +529,51 @@
     }
 
     @Test
+    public void getConnectionSummary_testMemberDevicesExist_returnMinBattery() {
+        // One device is active with battery level 70.
+        mBatteryLevel = 70;
+        updateProfileStatus(mA2dpProfile, BluetoothProfile.STATE_CONNECTED);
+        mCachedDevice.onActiveDeviceChanged(true, BluetoothProfile.A2DP);
+
+
+        // Add a member device with battery level 30.
+        int lowerBatteryLevel = 30;
+        mCachedDevice.addMemberDevice(mSubCachedDevice);
+        doAnswer((invocation) -> lowerBatteryLevel).when(mSubCachedDevice).getBatteryLevel();
+
+        assertThat(mCachedDevice.getConnectionSummary()).isEqualTo("Active, 30% battery");
+    }
+
+    @Test
+    public void getConnectionSummary_testMemberDevicesBatteryUnknown_returnMinBattery() {
+        // One device is active with battery level 70.
+        mBatteryLevel = 70;
+        updateProfileStatus(mA2dpProfile, BluetoothProfile.STATE_CONNECTED);
+        mCachedDevice.onActiveDeviceChanged(true, BluetoothProfile.A2DP);
+
+        // Add a member device with battery level unknown.
+        mCachedDevice.addMemberDevice(mSubCachedDevice);
+        doAnswer((invocation) -> BluetoothDevice.BATTERY_LEVEL_UNKNOWN).when(
+                mSubCachedDevice).getBatteryLevel();
+
+        assertThat(mCachedDevice.getConnectionSummary()).isEqualTo("Active, 70% battery");
+    }
+
+    @Test
+    public void getConnectionSummary_testAllDevicesBatteryUnknown_returnNoBattery() {
+        // One device is active with battery level unknown.
+        updateProfileStatus(mA2dpProfile, BluetoothProfile.STATE_CONNECTED);
+        mCachedDevice.onActiveDeviceChanged(true, BluetoothProfile.A2DP);
+
+        // Add a member device with battery level unknown.
+        mCachedDevice.addMemberDevice(mSubCachedDevice);
+        doAnswer((invocation) -> BluetoothDevice.BATTERY_LEVEL_UNKNOWN).when(
+                mSubCachedDevice).getBatteryLevel();
+
+        assertThat(mCachedDevice.getConnectionSummary()).isEqualTo("Active");
+    }
+
+    @Test
     public void getConnectionSummary_testMultipleProfilesActiveDevice() {
         // Test without battery level
         // Set A2DP and HFP profiles to be connected and test connection state summary
diff --git a/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java b/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java
index 94a3173..1c33544 100644
--- a/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java
+++ b/packages/SettingsProvider/src/android/provider/settings/backup/SecureSettings.java
@@ -242,6 +242,7 @@
         Settings.Secure.HEARING_AID_MEDIA_ROUTING,
         Settings.Secure.HEARING_AID_SYSTEM_SOUNDS_ROUTING,
         Settings.Secure.ACCESSIBILITY_FONT_SCALING_HAS_BEEN_CHANGED,
-        Settings.Secure.SEARCH_PRESS_HOLD_NAV_HANDLE_ENABLED
+        Settings.Secure.SEARCH_PRESS_HOLD_NAV_HANDLE_ENABLED,
+        Settings.Secure.SEARCH_LONG_PRESS_HOME_ENABLED
     };
 }
diff --git a/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java b/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java
index 8179998..301fd6f 100644
--- a/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java
+++ b/packages/SettingsProvider/src/android/provider/settings/validators/SecureSettingsValidators.java
@@ -201,6 +201,7 @@
         VALIDATORS.put(Secure.ASSIST_TOUCH_GESTURE_ENABLED, BOOLEAN_VALIDATOR);
         VALIDATORS.put(Secure.ASSIST_LONG_PRESS_HOME_ENABLED, BOOLEAN_VALIDATOR);
         VALIDATORS.put(Secure.SEARCH_PRESS_HOLD_NAV_HANDLE_ENABLED, BOOLEAN_VALIDATOR);
+        VALIDATORS.put(Secure.SEARCH_LONG_PRESS_HOME_ENABLED, BOOLEAN_VALIDATOR);
         VALIDATORS.put(Secure.VR_DISPLAY_MODE, new DiscreteValueValidator(new String[] {"0", "1"}));
         VALIDATORS.put(Secure.NOTIFICATION_BADGING, BOOLEAN_VALIDATOR);
         VALIDATORS.put(Secure.NOTIFICATION_DISMISS_RTL, BOOLEAN_VALIDATOR);
diff --git a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java
index 7186aba..3c8d4bc 100644
--- a/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java
+++ b/packages/SettingsProvider/src/com/android/providers/settings/SettingsProtoDumpUtil.java
@@ -1933,6 +1933,9 @@
         dumpSetting(s, p,
                 Settings.Secure.SEARCH_PRESS_HOLD_NAV_HANDLE_ENABLED,
                 SecureSettingsProto.Assist.SEARCH_PRESS_HOLD_NAV_HANDLE_ENABLED);
+        dumpSetting(s, p,
+                Settings.Secure.SEARCH_LONG_PRESS_HOME_ENABLED,
+                SecureSettingsProto.Assist.SEARCH_LONG_PRESS_HOME_ENABLED);
         p.end(assistToken);
 
         final long assistHandlesToken = p.start(SecureSettingsProto.ASSIST_HANDLES);
diff --git a/packages/SystemUI/Android.bp b/packages/SystemUI/Android.bp
index b6bec1f..77925d6 100644
--- a/packages/SystemUI/Android.bp
+++ b/packages/SystemUI/Android.bp
@@ -240,6 +240,9 @@
 
         /* Log fakes */
         "tests/src/com/android/systemui/log/core/FakeLogBuffer.kt",
+
+        /* QS fakes */
+        "tests/src/com/android/systemui/qs/pipeline/domain/interactor/FakeQSTile.kt",
     ],
     path: "tests/src",
 }
@@ -293,8 +296,6 @@
 
         /* Biometric converted tests */
         "tests/src/com/android/systemui/biometrics/BiometricTestExtensions.kt",
-        "tests/src/com/android/systemui/biometrics/AuthBiometricFingerprintAndFaceViewTest.kt",
-        "tests/src/com/android/systemui/biometrics/AuthBiometricFingerprintViewTest.kt",
         "tests/src/com/android/systemui/biometrics/AuthControllerTest.java",
         "tests/src/com/android/systemui/biometrics/BiometricDisplayListenerTest.java",
         "tests/src/com/android/systemui/biometrics/FaceHelpMessageDeferralTest.kt",
@@ -354,6 +355,11 @@
         "tests/src/com/android/systemui/smartspace/DreamSmartspaceControllerTest.kt",
         "tests/src/com/android/systemui/smartspace/LockscreenAndDreamTargetFilterTest.kt",
         "tests/src/com/android/systemui/smartspace/LockscreenPreconditionTest.kt",
+
+        /* Quick Settings new pipeline converted tests */
+        "tests/src/com/android/systemui/qs/pipeline/data/**/*Test.kt",
+        "tests/src/com/android/systemui/qs/pipeline/domain/**/*Test.kt",
+        "tests/src/com/android/systemui/qs/pipeline/shared/TileSpecTest.kt",
     ],
     path: "tests/src",
 }
diff --git a/packages/SystemUI/accessibility/accessibilitymenu/Android.bp b/packages/SystemUI/accessibility/accessibilitymenu/Android.bp
index f358417..ff723e3 100644
--- a/packages/SystemUI/accessibility/accessibilitymenu/Android.bp
+++ b/packages/SystemUI/accessibility/accessibilitymenu/Android.bp
@@ -36,6 +36,7 @@
         "androidx.preference_preference",
         "androidx.viewpager_viewpager",
         "SettingsLibDisplayUtils",
+        "com_android_a11y_menu_flags_lib",
     ],
 
     optimize: {
diff --git a/packages/SystemUI/accessibility/accessibilitymenu/aconfig/Android.bp b/packages/SystemUI/accessibility/accessibilitymenu/aconfig/Android.bp
new file mode 100644
index 0000000..6d63409
--- /dev/null
+++ b/packages/SystemUI/accessibility/accessibilitymenu/aconfig/Android.bp
@@ -0,0 +1,12 @@
+aconfig_declarations {
+    name: "com_android_a11y_menu_flags",
+    package: "com.android.systemui.accessibility.accessibilitymenu",
+    srcs: [
+        "accessibility.aconfig",
+    ],
+}
+
+java_aconfig_library {
+    name: "com_android_a11y_menu_flags_lib",
+    aconfig_declarations: "com_android_a11y_menu_flags",
+}
diff --git a/packages/SystemUI/accessibility/accessibilitymenu/aconfig/accessibility.aconfig b/packages/SystemUI/accessibility/accessibilitymenu/aconfig/accessibility.aconfig
new file mode 100644
index 0000000..03cbc16
--- /dev/null
+++ b/packages/SystemUI/accessibility/accessibilitymenu/aconfig/accessibility.aconfig
@@ -0,0 +1,8 @@
+package: "com.android.systemui.accessibility.accessibilitymenu"
+
+flag {
+    name: "a11y_menu_settings_back_button_fix_and_large_button_sizing"
+    namespace: "accessibility"
+    description: "Provides/restores back button functionality for the a11yMenu settings page. Also, fixes sizing problems with large shortcut buttons."
+    bug: "298467628"
+}
\ No newline at end of file
diff --git a/packages/SystemUI/accessibility/accessibilitymenu/src/com/android/systemui/accessibility/accessibilitymenu/activity/A11yMenuSettingsActivity.java b/packages/SystemUI/accessibility/accessibilitymenu/src/com/android/systemui/accessibility/accessibilitymenu/activity/A11yMenuSettingsActivity.java
index c26cd12..bf51e23 100644
--- a/packages/SystemUI/accessibility/accessibilitymenu/src/com/android/systemui/accessibility/accessibilitymenu/activity/A11yMenuSettingsActivity.java
+++ b/packages/SystemUI/accessibility/accessibilitymenu/src/com/android/systemui/accessibility/accessibilitymenu/activity/A11yMenuSettingsActivity.java
@@ -27,6 +27,7 @@
 import android.provider.Settings;
 import android.view.View;
 import android.widget.TextView;
+import android.window.OnBackInvokedCallback;
 
 import androidx.annotation.Nullable;
 import androidx.fragment.app.FragmentActivity;
@@ -34,12 +35,16 @@
 import androidx.preference.PreferenceFragmentCompat;
 import androidx.preference.PreferenceManager;
 
+import com.android.systemui.accessibility.accessibilitymenu.Flags;
 import com.android.systemui.accessibility.accessibilitymenu.R;
 
 /**
  * Settings activity for AccessibilityMenu.
  */
 public class A11yMenuSettingsActivity extends FragmentActivity {
+    private OnBackInvokedCallback mCallback = () -> {
+        finish();
+    };
 
     @Override
     protected void onCreate(Bundle savedInstanceState) {
@@ -51,6 +56,10 @@
 
         ActionBar actionBar = getActionBar();
         actionBar.setDisplayShowCustomEnabled(true);
+
+        if (Flags.a11yMenuSettingsBackButtonFixAndLargeButtonSizing()) {
+            actionBar.setDisplayHomeAsUpEnabled(true);
+        }
         actionBar.setCustomView(R.layout.preferences_action_bar);
         ((TextView) findViewById(R.id.action_bar_title)).setText(
                 getResources().getString(R.string.accessibility_menu_settings_name)
@@ -61,6 +70,16 @@
                         | ActionBar.DISPLAY_HOME_AS_UP);
     }
 
+    @Override
+    public boolean onNavigateUp() {
+        if (Flags.a11yMenuSettingsBackButtonFixAndLargeButtonSizing()) {
+            mCallback.onBackInvoked();
+            return true;
+        } else {
+            return false;
+        }
+    }
+
     /**
      * Settings/preferences fragment for AccessibilityMenu.
      */
diff --git a/packages/SystemUI/accessibility/accessibilitymenu/src/com/android/systemui/accessibility/accessibilitymenu/view/A11yMenuAdapter.java b/packages/SystemUI/accessibility/accessibilitymenu/src/com/android/systemui/accessibility/accessibilitymenu/view/A11yMenuAdapter.java
index c64ec6f..c4f372c 100644
--- a/packages/SystemUI/accessibility/accessibilitymenu/src/com/android/systemui/accessibility/accessibilitymenu/view/A11yMenuAdapter.java
+++ b/packages/SystemUI/accessibility/accessibilitymenu/src/com/android/systemui/accessibility/accessibilitymenu/view/A11yMenuAdapter.java
@@ -28,6 +28,7 @@
 import android.widget.TextView;
 
 import com.android.systemui.accessibility.accessibilitymenu.AccessibilityMenuService;
+import com.android.systemui.accessibility.accessibilitymenu.Flags;
 import com.android.systemui.accessibility.accessibilitymenu.R;
 import com.android.systemui.accessibility.accessibilitymenu.activity.A11yMenuSettingsActivity.A11yMenuPreferenceFragment;
 import com.android.systemui.accessibility.accessibilitymenu.model.A11yMenuShortcut;
@@ -79,6 +80,11 @@
     public View getView(int position, View convertView, ViewGroup parent) {
         if (convertView == null) {
             convertView = mInflater.inflate(R.layout.grid_item, parent, false);
+
+            if (Flags.a11yMenuSettingsBackButtonFixAndLargeButtonSizing()) {
+                configureShortcutSize(convertView,
+                        A11yMenuPreferenceFragment.isLargeButtonsEnabled(mService));
+            }
         }
 
         A11yMenuShortcut shortcutItem = (A11yMenuShortcut) getItem(position);
@@ -126,16 +132,29 @@
                 });
     }
 
-    private void configureShortcutView(View convertView, A11yMenuShortcut shortcutItem) {
+    private void configureShortcutSize(View convertView, boolean isLargeButtonsEnabled) {
         ImageButton shortcutIconButton = convertView.findViewById(R.id.shortcutIconBtn);
         TextView shortcutLabel = convertView.findViewById(R.id.shortcutLabel);
-
-        if (A11yMenuPreferenceFragment.isLargeButtonsEnabled(mService)) {
+        if (isLargeButtonsEnabled) {
             ViewGroup.LayoutParams params = shortcutIconButton.getLayoutParams();
             params.width = (int) (params.width * LARGE_BUTTON_SCALE);
             params.height = (int) (params.height * LARGE_BUTTON_SCALE);
             shortcutLabel.setTextSize(android.util.TypedValue.COMPLEX_UNIT_PX, mLargeTextSize);
         }
+    }
+
+    private void configureShortcutView(View convertView, A11yMenuShortcut shortcutItem) {
+        ImageButton shortcutIconButton = convertView.findViewById(R.id.shortcutIconBtn);
+        TextView shortcutLabel = convertView.findViewById(R.id.shortcutLabel);
+
+        if (!Flags.a11yMenuSettingsBackButtonFixAndLargeButtonSizing()) {
+            if (A11yMenuPreferenceFragment.isLargeButtonsEnabled(mService)) {
+                ViewGroup.LayoutParams params = shortcutIconButton.getLayoutParams();
+                params.width = (int) (params.width * LARGE_BUTTON_SCALE);
+                params.height = (int) (params.height * LARGE_BUTTON_SCALE);
+                shortcutLabel.setTextSize(android.util.TypedValue.COMPLEX_UNIT_PX, mLargeTextSize);
+            }
+        }
 
         if (shortcutItem.getId() == A11yMenuShortcut.ShortcutId.UNSPECIFIED_ID_VALUE.ordinal()) {
             // Sets empty shortcut icon and label when the shortcut is ADD_ITEM.
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/ActivityLaunchAnimator.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/ActivityLaunchAnimator.kt
index a3a1fa5..0c07616 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/animation/ActivityLaunchAnimator.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/ActivityLaunchAnimator.kt
@@ -24,6 +24,7 @@
 import android.graphics.Path
 import android.graphics.Rect
 import android.graphics.RectF
+import android.os.Build
 import android.os.Looper
 import android.os.RemoteException
 import android.util.Log
@@ -88,6 +89,9 @@
                 contentAfterFadeInInterpolator = PathInterpolator(0f, 0f, 0.6f, 1f)
             )
 
+        // TODO(b/288507023): Remove this flag.
+        @JvmField val DEBUG_LAUNCH_ANIMATION = Build.IS_DEBUGGABLE
+
         private val DEFAULT_LAUNCH_ANIMATOR = LaunchAnimator(TIMINGS, INTERPOLATORS)
         private val DEFAULT_DIALOG_TO_APP_ANIMATOR = LaunchAnimator(DIALOG_TIMINGS, INTERPOLATORS)
 
@@ -244,11 +248,13 @@
                 callOnIntentStartedOnMainThread(willAnimate)
             }
         } else {
-            // TODO(b/288507023): Remove this log.
-            Log.d(
-                TAG,
-                "Calling controller.onIntentStarted(willAnimate=$willAnimate) [controller=$this]"
-            )
+            if (DEBUG_LAUNCH_ANIMATION) {
+                Log.d(
+                    TAG,
+                    "Calling controller.onIntentStarted(willAnimate=$willAnimate) " +
+                            "[controller=$this]"
+                )
+            }
             this.onIntentStarted(willAnimate)
         }
     }
@@ -549,8 +555,12 @@
                 removeTimeout()
                 iCallback?.invoke()
 
-                // TODO(b/288507023): Remove this log.
-                Log.d(TAG, "Calling controller.onLaunchAnimationCancelled() [no window opening]")
+                if (DEBUG_LAUNCH_ANIMATION) {
+                    Log.d(
+                        TAG,
+                        "Calling controller.onLaunchAnimationCancelled() [no window opening]"
+                    )
+                }
                 controller.onLaunchAnimationCancelled()
                 return
             }
@@ -769,8 +779,9 @@
             Log.i(TAG, "Remote animation timed out")
             timedOut = true
 
-            // TODO(b/288507023): Remove this log.
-            Log.d(TAG, "Calling controller.onLaunchAnimationCancelled() [animation timed out]")
+            if (DEBUG_LAUNCH_ANIMATION) {
+                Log.d(TAG, "Calling controller.onLaunchAnimationCancelled() [animation timed out]")
+            }
             controller.onLaunchAnimationCancelled()
         }
 
@@ -786,11 +797,12 @@
 
             animation?.cancel()
 
-            // TODO(b/288507023): Remove this log.
-            Log.d(
-                TAG,
-                "Calling controller.onLaunchAnimationCancelled() [remote animation cancelled]",
-            )
+            if (DEBUG_LAUNCH_ANIMATION) {
+                Log.d(
+                    TAG,
+                    "Calling controller.onLaunchAnimationCancelled() [remote animation cancelled]",
+                )
+            }
             controller.onLaunchAnimationCancelled()
         }
 
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/BouncerScene.kt b/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/BouncerScene.kt
index 81b9eb0..e06a69b 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/BouncerScene.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/bouncer/ui/composable/BouncerScene.kt
@@ -55,6 +55,7 @@
 import com.android.systemui.bouncer.ui.viewmodel.PatternBouncerViewModel
 import com.android.systemui.bouncer.ui.viewmodel.PinBouncerViewModel
 import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.scene.shared.model.Direction
 import com.android.systemui.scene.shared.model.SceneKey
 import com.android.systemui.scene.shared.model.SceneModel
 import com.android.systemui.scene.shared.model.UserAction
@@ -82,9 +83,10 @@
     override val key = SceneKey.Bouncer
 
     override fun destinationScenes(): StateFlow<Map<UserAction, SceneModel>> =
-        MutableStateFlow<Map<UserAction, SceneModel>>(
+        MutableStateFlow(
                 mapOf(
                     UserAction.Back to SceneModel(SceneKey.Lockscreen),
+                    UserAction.Swipe(Direction.DOWN) to SceneModel(SceneKey.Lockscreen),
                 )
             )
             .asStateFlow()
diff --git a/packages/SystemUI/res-keyguard/layout/keyguard_pin_motion_layout.xml b/packages/SystemUI/res-keyguard/layout/keyguard_pin_motion_layout.xml
new file mode 100644
index 0000000..6835d59
--- /dev/null
+++ b/packages/SystemUI/res-keyguard/layout/keyguard_pin_motion_layout.xml
@@ -0,0 +1,243 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+**
+** Copyright 2023, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License")
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+**     http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+
+<!-- This file is needed when flag lockscreen.enable_landscape is on
+     Required for landscape lockscreen on small screens. -->
+<com.android.keyguard.KeyguardPINView xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:androidprv="http://schemas.android.com/apk/res-auto"
+    android:id="@+id/keyguard_pin_view"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:layout_gravity="center_horizontal|bottom"
+    android:clipChildren="false"
+    android:clipToPadding="false"
+    android:orientation="vertical">
+
+    <!-- Layout here is visually identical to the previous keyguard_pin_view.
+         I.E., 'constraints here effectively the same as the previous linear layout '-->
+    <androidx.constraintlayout.motion.widget.MotionLayout
+        android:id="@+id/pin_container"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:clipChildren="false"
+        android:clipToPadding="false"
+        android:layoutDirection="ltr"
+        android:orientation="vertical"
+        androidprv:layoutDescription="@xml/keyguard_pin_scene"
+        android:layout_gravity="center_horizontal">
+
+        <!-- Guideline need to align PIN pad left of centre,
+        when on small screen landscape layout -->
+        <androidx.constraintlayout.widget.Guideline
+            android:id="@+id/pin_pad_center_guideline"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:orientation="vertical"
+            androidprv:layout_constraintGuide_percent="0.5" />
+
+        <!-- Guideline used to place the top row of keys relative to the screen height. This will be
+        updated in KeyguardPINView to reduce the height of the PIN pad. -->
+        <androidx.constraintlayout.widget.Guideline
+            android:id="@+id/pin_pad_top_guideline"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:orientation="horizontal"
+            androidprv:layout_constraintGuide_percent="0" />
+
+        <LinearLayout
+            android:id="@+id/keyguard_bouncer_message_container"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:clipChildren="false"
+            android:clipToPadding="false"
+            android:layoutDirection="ltr"
+            android:orientation="vertical"
+            androidprv:layout_constraintTop_toTopOf="parent">
+
+            <include layout="@layout/keyguard_bouncer_message_area" />
+
+            <com.android.systemui.bouncer.ui.BouncerMessageView
+                android:id="@+id/bouncer_message_view"
+                android:layout_width="match_parent"
+                android:layout_height="wrap_content"
+                android:orientation="vertical" />
+
+        </LinearLayout>
+
+        <!-- Set this to be just above key1. It would be better to introduce a barrier above
+          key1/key2/key3, then place this View above that. Sadly, that doesn't work (the Barrier
+          drops to the bottom of the page, and key1/2/3 all shoot up to the top-left). In any
+          case, the Flow should ensure that key1/2/3 all have the same top, so this should be
+          fine. -->
+        <com.android.keyguard.AlphaOptimizedRelativeLayout
+            android:id="@+id/row0"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:paddingBottom="@dimen/num_pad_entry_row_margin_bottom"
+            androidprv:layout_constraintBottom_toTopOf="@id/key1"
+            androidprv:layout_constraintEnd_toEndOf="parent"
+            androidprv:layout_constraintStart_toStartOf="parent"
+            androidprv:layout_constraintTop_toBottomOf="@id/keyguard_bouncer_message_container"
+            androidprv:layout_constraintVertical_bias="0.5">
+
+            <com.android.keyguard.PasswordTextView
+                android:id="@+id/pinEntry"
+                style="@style/Widget.TextView.Password"
+                android:layout_width="@dimen/keyguard_security_width"
+                android:layout_height="@dimen/keyguard_password_height"
+                android:layout_centerHorizontal="true"
+                android:layout_marginRight="72dp"
+                android:contentDescription="@string/keyguard_accessibility_pin_area"
+                androidprv:scaledTextSize="@integer/scaled_password_text_size" />
+
+        </com.android.keyguard.AlphaOptimizedRelativeLayout>
+
+        <com.android.keyguard.KeyguardPinFlowView
+            android:id="@+id/flow1"
+            android:layout_width="0dp"
+            android:layout_height="0dp"
+            android:layout_marginBottom="8dp"
+            android:clipChildren="false"
+            android:clipToPadding="false"
+            android:orientation="horizontal"
+
+            androidprv:constraint_referenced_ids="key1,key2,key3,key4,key5,key6,key7,key8,key9,delete_button,key0,key_enter"
+
+            androidprv:flow_horizontalGap="@dimen/num_pad_key_margin_end"
+            androidprv:flow_horizontalStyle="packed"
+            androidprv:flow_maxElementsWrap="3"
+
+            androidprv:flow_verticalBias="1.0"
+            androidprv:flow_verticalGap="@dimen/num_pad_entry_row_margin_bottom"
+            androidprv:flow_verticalStyle="packed"
+            androidprv:flow_wrapMode="aligned"
+
+            androidprv:layout_constraintBottom_toTopOf="@+id/keyguard_selector_fade_container"
+            androidprv:layout_constraintEnd_toEndOf="parent"
+            androidprv:layout_constraintStart_toStartOf="parent"
+            androidprv:layout_constraintTop_toBottomOf="@id/pin_pad_top_guideline" />
+
+        <com.android.keyguard.NumPadButton
+            android:id="@+id/delete_button"
+            style="@style/NumPadKey.Delete"
+            android:layout_width="0dp"
+            android:layout_height="0dp"
+            android:accessibilityTraversalBefore="@id/key0"
+            android:contentDescription="@string/keyboardview_keycode_delete" />
+
+        <com.android.keyguard.NumPadButton
+            android:id="@+id/key_enter"
+            style="@style/NumPadKey.Enter"
+            android:layout_width="0dp"
+            android:layout_height="0dp"
+            android:contentDescription="@string/keyboardview_keycode_enter" />
+
+        <com.android.keyguard.NumPadKey
+            android:id="@+id/key1"
+            android:layout_width="0dp"
+            android:layout_height="0dp"
+            android:accessibilityTraversalBefore="@id/key2"
+            androidprv:digit="1"
+            androidprv:textView="@+id/pinEntry" />
+
+        <com.android.keyguard.NumPadKey
+            android:id="@+id/key2"
+            android:layout_width="0dp"
+            android:layout_height="0dp"
+            android:accessibilityTraversalBefore="@id/key3"
+            androidprv:digit="2"
+            androidprv:textView="@+id/pinEntry" />
+
+        <com.android.keyguard.NumPadKey
+            android:id="@+id/key3"
+            android:layout_width="0dp"
+            android:layout_height="0dp"
+            android:accessibilityTraversalBefore="@id/key4"
+            androidprv:digit="3"
+            androidprv:textView="@+id/pinEntry" />
+
+        <com.android.keyguard.NumPadKey
+            android:id="@+id/key4"
+            android:layout_width="0dp"
+            android:layout_height="0dp"
+            android:accessibilityTraversalBefore="@id/key5"
+            androidprv:digit="4"
+            androidprv:textView="@+id/pinEntry" />
+
+        <com.android.keyguard.NumPadKey
+            android:id="@+id/key5"
+            android:layout_width="0dp"
+            android:layout_height="0dp"
+            android:accessibilityTraversalBefore="@id/key6"
+            androidprv:digit="5"
+            androidprv:textView="@+id/pinEntry" />
+
+        <com.android.keyguard.NumPadKey
+            android:id="@+id/key6"
+            android:layout_width="0dp"
+            android:layout_height="0dp"
+            android:accessibilityTraversalBefore="@id/key7"
+            androidprv:digit="6"
+            androidprv:textView="@+id/pinEntry" />
+
+        <com.android.keyguard.NumPadKey
+            android:id="@+id/key7"
+            android:layout_width="0dp"
+            android:layout_height="0dp"
+            android:accessibilityTraversalBefore="@id/key8"
+            androidprv:digit="7"
+            androidprv:textView="@+id/pinEntry" />
+
+        <com.android.keyguard.NumPadKey
+            android:id="@+id/key8"
+            android:layout_width="0dp"
+            android:layout_height="0dp"
+            android:accessibilityTraversalBefore="@id/key9"
+            androidprv:digit="8"
+            androidprv:textView="@+id/pinEntry" />
+
+        <com.android.keyguard.NumPadKey
+            android:id="@+id/key9"
+            android:layout_width="0dp"
+            android:layout_height="0dp"
+            android:accessibilityTraversalBefore="@id/delete_button"
+            androidprv:digit="9"
+            androidprv:textView="@+id/pinEntry" />
+
+        <com.android.keyguard.NumPadKey
+            android:id="@+id/key0"
+            android:layout_width="0dp"
+            android:layout_height="0dp"
+            android:accessibilityTraversalBefore="@id/key_enter"
+            androidprv:digit="0"
+            androidprv:textView="@+id/pinEntry" />
+
+        <include
+            android:id="@+id/keyguard_selector_fade_container"
+            layout="@layout/keyguard_eca"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:layout_marginBottom="@dimen/keyguard_eca_bottom_margin"
+            android:layout_marginTop="@dimen/keyguard_eca_top_margin"
+            android:orientation="vertical"
+            androidprv:layout_constraintBottom_toBottomOf="parent"
+            androidprv:layout_constraintTop_toBottomOf="@+id/flow1" />
+
+    </androidx.constraintlayout.motion.widget.MotionLayout>
+
+</com.android.keyguard.KeyguardPINView>
\ No newline at end of file
diff --git a/packages/SystemUI/res-keyguard/values-land/donottranslate.xml b/packages/SystemUI/res-keyguard/values-land/donottranslate.xml
deleted file mode 100644
index 9912b69..0000000
--- a/packages/SystemUI/res-keyguard/values-land/donottranslate.xml
+++ /dev/null
@@ -1,19 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2021 The Android Open Source Project
-
-     Licensed under the Apache License, Version 2.0 (the "License");
-     you may not use this file except in compliance with the License.
-     You may obtain a copy of the License at
-
-          http://www.apache.org/licenses/LICENSE-2.0
-
-     Unless required by applicable law or agreed to in writing, software
-     distributed under the License is distributed on an "AS IS" BASIS,
-     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-     See the License for the specific language governing permissions and
-     limitations under the License.
--->
-
-<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <string name="num_pad_key_ratio">1.51</string>
-</resources>
diff --git a/packages/SystemUI/res-keyguard/xml/keyguard_pin_scene.xml b/packages/SystemUI/res-keyguard/xml/keyguard_pin_scene.xml
new file mode 100644
index 0000000..44af9ef
--- /dev/null
+++ b/packages/SystemUI/res-keyguard/xml/keyguard_pin_scene.xml
@@ -0,0 +1,73 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+**
+** Copyright 2023, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License")
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+**     http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+-->
+
+<MotionScene
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:motion="http://schemas.android.com/apk/res-auto"
+    xmlns:androidprv="http://schemas.android.com/apk/res-auto">
+
+    <Transition
+        motion:constraintSetStart="@id/single_constraints"
+        motion:constraintSetEnd="@+id/split_constraints"
+        motion:duration="0"
+        motion:autoTransition="none">
+    </Transition>
+
+    <ConstraintSet android:id="@+id/single_constraints">
+        <!-- No changes to default layout -->
+    </ConstraintSet>
+
+    <ConstraintSet android:id="@+id/split_constraints">
+
+        <Constraint
+            android:id="@+id/keyguard_bouncer_message_container"
+            android:layout_width="0dp"
+            android:layout_height="wrap_content"
+            androidprv:layout_constraintEnd_toStartOf="@+id/pin_pad_center_guideline"
+            androidprv:layout_constraintStart_toStartOf="parent"
+            androidprv:layout_constraintTop_toTopOf="parent" />
+        <Constraint
+            android:id="@+id/row0"
+            android:layout_width="0dp"
+            android:layout_height="wrap_content"
+            androidprv:layout_constraintEnd_toStartOf="@+id/pin_pad_center_guideline"
+            androidprv:layout_constraintStart_toStartOf="parent"
+            androidprv:layout_constraintTop_toBottomOf="@+id/keyguard_bouncer_message_container" />
+        <Constraint
+            android:id="@+id/flow1"
+            android:layout_width="0dp"
+            android:layout_height="0dp"
+            androidprv:layout_constraintBottom_toBottomOf="parent"
+            androidprv:layout_constraintEnd_toEndOf="parent"
+            androidprv:layout_constraintStart_toStartOf="@+id/pin_pad_center_guideline"
+            androidprv:layout_constraintTop_toTopOf="parent"
+            android:layout_marginBottom="0dp"
+            androidprv:flow_verticalBias="0.5" />
+        <Constraint
+            android:id="@+id/keyguard_selector_fade_container"
+            android:layout_width="0dp"
+            android:layout_height="wrap_content"
+            androidprv:layout_constraintBottom_toBottomOf="parent"
+            androidprv:layout_constraintEnd_toStartOf="@+id/pin_pad_center_guideline"
+            androidprv:layout_constraintStart_toStartOf="parent"
+            android:layout_marginBottom="@dimen/keyguard_eca_bottom_margin"
+            android:layout_marginTop="@dimen/keyguard_eca_top_margin" />
+
+    </ConstraintSet>
+</MotionScene>
\ No newline at end of file
diff --git a/packages/SystemUI/res/layout/auth_biometric_contents.xml b/packages/SystemUI/res/layout/auth_biometric_contents.xml
deleted file mode 100644
index 50b3bec..0000000
--- a/packages/SystemUI/res/layout/auth_biometric_contents.xml
+++ /dev/null
@@ -1,165 +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.
-  -->
-<!-- TODO(b/251476085): inline in biometric_prompt_layout after Biometric*Views are un-flagged -->
-<merge xmlns:android="http://schemas.android.com/apk/res/android">
-
-    <TextView
-        android:id="@+id/title"
-        android:layout_width="match_parent"
-        android:layout_height="wrap_content"
-        android:gravity="@integer/biometric_dialog_text_gravity"
-        android:singleLine="true"
-        android:marqueeRepeatLimit="1"
-        android:ellipsize="marquee"
-        android:importantForAccessibility="no"
-        style="@style/TextAppearance.AuthCredential.Title"/>
-
-    <TextView
-        android:id="@+id/subtitle"
-        android:layout_width="match_parent"
-        android:layout_height="wrap_content"
-        android:gravity="@integer/biometric_dialog_text_gravity"
-        android:singleLine="true"
-        android:marqueeRepeatLimit="1"
-        android:ellipsize="marquee"
-        android:importantForAccessibility="no"
-        style="@style/TextAppearance.AuthCredential.Subtitle"/>
-
-    <TextView
-        android:id="@+id/description"
-        android:layout_width="match_parent"
-        android:layout_height="wrap_content"
-        android:scrollbars ="vertical"
-        style="@style/TextAppearance.AuthCredential.Description"/>
-
-    <Space android:id="@+id/space_above_icon"
-        android:layout_width="match_parent"
-        android:layout_height="48dp" />
-
-    <FrameLayout
-        android:id="@+id/biometric_icon_frame"
-        android:layout_width="wrap_content"
-        android:layout_height="wrap_content"
-        android:layout_gravity="center">
-
-        <include layout="@layout/auth_biometric_icon"/>
-
-        <com.android.systemui.biometrics.BiometricPromptLottieViewWrapper
-            android:id="@+id/biometric_icon_overlay"
-            android:layout_width="wrap_content"
-            android:layout_height="wrap_content"
-            android:layout_gravity="center"
-            android:contentDescription="@null"
-            android:scaleType="fitXY" />
-    </FrameLayout>
-
-    <!-- For sensors such as UDFPS, this view is used during custom measurement/layout to add extra
-         padding so that the biometric icon is always in the right physical position. -->
-    <Space android:id="@+id/space_below_icon"
-        android:layout_width="match_parent"
-        android:layout_height="12dp" />
-
-    <TextView
-        android:id="@+id/indicator"
-        android:layout_width="match_parent"
-        android:layout_height="wrap_content"
-        android:paddingHorizontal="24dp"
-        android:textSize="12sp"
-        android:gravity="center_horizontal"
-        android:accessibilityLiveRegion="polite"
-        android:singleLine="true"
-        android:ellipsize="marquee"
-        android:marqueeRepeatLimit="marquee_forever"
-        android:scrollHorizontally="true"
-        android:fadingEdge="horizontal"
-        android:textColor="@color/biometric_dialog_gray"/>
-
-    <LinearLayout
-        android:id="@+id/button_bar"
-        android:layout_width="match_parent"
-        android:layout_height="88dp"
-        style="?android:attr/buttonBarStyle"
-        android:orientation="horizontal"
-        android:paddingTop="24dp">
-
-        <Space android:id="@+id/leftSpacer"
-            android:layout_width="8dp"
-            android:layout_height="match_parent"
-            android:visibility="visible" />
-
-        <!-- Negative Button, reserved for app -->
-        <Button android:id="@+id/button_negative"
-            android:layout_width="wrap_content"
-            android:layout_height="wrap_content"
-            style="@*android:style/Widget.DeviceDefault.Button.Borderless.Colored"
-            android:layout_gravity="center_vertical"
-            android:ellipsize="end"
-            android:maxLines="2"
-            android:maxWidth="@dimen/biometric_dialog_button_negative_max_width"/>
-        <!-- Cancel Button, replaces negative button when biometric is accepted -->
-        <Button android:id="@+id/button_cancel"
-            android:layout_width="wrap_content"
-            android:layout_height="wrap_content"
-            style="@*android:style/Widget.DeviceDefault.Button.Borderless.Colored"
-            android:layout_gravity="center_vertical"
-            android:maxWidth="@dimen/biometric_dialog_button_negative_max_width"
-            android:text="@string/cancel"
-            android:visibility="gone"/>
-        <!-- "Use Credential" Button, replaces if device credential is allowed -->
-        <Button android:id="@+id/button_use_credential"
-            android:layout_width="wrap_content"
-            android:layout_height="wrap_content"
-            style="@*android:style/Widget.DeviceDefault.Button.Borderless.Colored"
-            android:layout_gravity="center_vertical"
-            android:maxWidth="@dimen/biometric_dialog_button_negative_max_width"
-            android:visibility="gone"/>
-
-        <Space android:id="@+id/middleSpacer"
-            android:layout_width="0dp"
-            android:layout_height="match_parent"
-            android:layout_weight="1"
-            android:visibility="visible"/>
-
-        <!-- Positive Button -->
-        <Button android:id="@+id/button_confirm"
-            android:layout_width="wrap_content"
-            android:layout_height="wrap_content"
-            style="@*android:style/Widget.DeviceDefault.Button.Colored"
-            android:layout_gravity="center_vertical"
-            android:ellipsize="end"
-            android:maxLines="2"
-            android:maxWidth="@dimen/biometric_dialog_button_positive_max_width"
-            android:text="@string/biometric_dialog_confirm"
-            android:visibility="gone"/>
-        <!-- Try Again Button -->
-        <Button android:id="@+id/button_try_again"
-            android:layout_width="wrap_content"
-            android:layout_height="wrap_content"
-            style="@*android:style/Widget.DeviceDefault.Button.Colored"
-            android:layout_gravity="center_vertical"
-            android:ellipsize="end"
-            android:maxLines="2"
-            android:maxWidth="@dimen/biometric_dialog_button_positive_max_width"
-            android:text="@string/biometric_dialog_try_again"
-            android:visibility="gone"/>
-
-        <Space android:id="@+id/rightSpacer"
-            android:layout_width="8dp"
-            android:layout_height="match_parent"
-            android:visibility="visible" />
-    </LinearLayout>
-
-</merge>
\ No newline at end of file
diff --git a/packages/SystemUI/res/layout/auth_biometric_face_view.xml b/packages/SystemUI/res/layout/auth_biometric_face_view.xml
deleted file mode 100644
index e3d0732..0000000
--- a/packages/SystemUI/res/layout/auth_biometric_face_view.xml
+++ /dev/null
@@ -1,26 +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.
-  -->
-<!-- TODO(b/251476085): remove after BiometricFaceView is un-flagged -->
-<com.android.systemui.biometrics.AuthBiometricFaceView
-    xmlns:android="http://schemas.android.com/apk/res/android"
-    android:id="@+id/contents"
-    android:layout_width="match_parent"
-    android:layout_height="wrap_content"
-    android:orientation="vertical">
-
-    <include layout="@layout/auth_biometric_contents"/>
-
-</com.android.systemui.biometrics.AuthBiometricFaceView>
\ No newline at end of file
diff --git a/packages/SystemUI/res/layout/auth_biometric_fingerprint_and_face_view.xml b/packages/SystemUI/res/layout/auth_biometric_fingerprint_and_face_view.xml
deleted file mode 100644
index 896d836..0000000
--- a/packages/SystemUI/res/layout/auth_biometric_fingerprint_and_face_view.xml
+++ /dev/null
@@ -1,26 +0,0 @@
-<!--
-  ~ Copyright (C) 2021 The Android Open Source Project
-  ~
-  ~ Licensed under the Apache License, Version 2.0 (the "License");
-  ~ you may not use this file except in compliance with the License.
-  ~ You may obtain a copy of the License at
-  ~
-  ~      http://www.apache.org/licenses/LICENSE-2.0
-  ~
-  ~ Unless required by applicable law or agreed to in writing, software
-  ~ distributed under the License is distributed on an "AS IS" BASIS,
-  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-  ~ See the License for the specific language governing permissions and
-  ~ limitations under the License.
-  -->
-<!-- TODO(b/251476085): remove after BiometricFingerprintAndFaceView is un-flagged -->
-<com.android.systemui.biometrics.AuthBiometricFingerprintAndFaceView
-    xmlns:android="http://schemas.android.com/apk/res/android"
-    android:id="@+id/contents"
-    android:layout_width="match_parent"
-    android:layout_height="wrap_content"
-    android:orientation="vertical">
-
-    <include layout="@layout/auth_biometric_contents"/>
-
-</com.android.systemui.biometrics.AuthBiometricFingerprintAndFaceView>
\ No newline at end of file
diff --git a/packages/SystemUI/res/layout/auth_biometric_fingerprint_view.xml b/packages/SystemUI/res/layout/auth_biometric_fingerprint_view.xml
deleted file mode 100644
index e36f9796..0000000
--- a/packages/SystemUI/res/layout/auth_biometric_fingerprint_view.xml
+++ /dev/null
@@ -1,26 +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.
-  -->
-<!-- TODO(b/251476085): remove after BiometricFingerprintView is un-flagged -->
-<com.android.systemui.biometrics.AuthBiometricFingerprintView
-    xmlns:android="http://schemas.android.com/apk/res/android"
-    android:id="@+id/contents"
-    android:layout_width="wrap_content"
-    android:layout_height="wrap_content"
-    android:orientation="vertical">
-
-    <include layout="@layout/auth_biometric_contents"/>
-
-</com.android.systemui.biometrics.AuthBiometricFingerprintView>
\ No newline at end of file
diff --git a/packages/SystemUI/res/values/flags.xml b/packages/SystemUI/res/values/flags.xml
index 0d45422..0903463 100644
--- a/packages/SystemUI/res/values/flags.xml
+++ b/packages/SystemUI/res/values/flags.xml
@@ -38,9 +38,6 @@
          protected. -->
     <bool name="flag_battery_shield_icon">false</bool>
 
-    <!-- Whether face auth will immediately stop when the display state is OFF -->
-    <bool name="flag_stop_face_auth_on_display_off">true</bool>
-
     <!-- Whether we want to stop pulsing while running the face scanning animation -->
     <bool name="flag_stop_pulsing_face_scanning_animation">true</bool>
 </resources>
diff --git a/packages/SystemUI/shared/Android.bp b/packages/SystemUI/shared/Android.bp
index a6517c1..0415341 100644
--- a/packages/SystemUI/shared/Android.bp
+++ b/packages/SystemUI/shared/Android.bp
@@ -46,6 +46,7 @@
         ":wm_shell_util-sources",
     ],
     static_libs: [
+        "BiometricsSharedLib",
         "PluginCoreLib",
         "SystemUIAnimationLib",
         "SystemUIPluginLib",
diff --git a/packages/SystemUI/shared/biometrics/Android.bp b/packages/SystemUI/shared/biometrics/Android.bp
new file mode 100644
index 0000000..2bd7d97
--- /dev/null
+++ b/packages/SystemUI/shared/biometrics/Android.bp
@@ -0,0 +1,20 @@
+package {
+    // See: http://go/android-license-faq
+    // A large-scale-change added 'default_applicable_licenses' to import
+    // all of the 'license_kinds' from "frameworks_base_packages_SystemUI_license"
+    // to get the below license kinds:
+    //   SPDX-license-identifier-Apache-2.0
+    default_applicable_licenses: ["frameworks_base_packages_SystemUI_license"],
+}
+
+android_library {
+    name: "BiometricsSharedLib",
+    srcs: [
+        "src/**/*.java",
+        "src/**/*.kt",
+    ],
+    resource_dirs: [
+        "res",
+    ],
+    min_sdk_version: "current",
+}
diff --git a/packages/SystemUI/res-keyguard/values-sw600dp-land/donottranslate.xml b/packages/SystemUI/shared/biometrics/AndroidManifest.xml
similarity index 68%
copy from packages/SystemUI/res-keyguard/values-sw600dp-land/donottranslate.xml
copy to packages/SystemUI/shared/biometrics/AndroidManifest.xml
index 1a52e93..861321b 100644
--- a/packages/SystemUI/res-keyguard/values-sw600dp-land/donottranslate.xml
+++ b/packages/SystemUI/shared/biometrics/AndroidManifest.xml
@@ -1,6 +1,6 @@
 <?xml version="1.0" encoding="utf-8"?>
 <!--
-  ~ Copyright (C) 2021 The Android Open Source Project
+  ~ Copyright (C) 2023 The Android Open Source Project
   ~
   ~ Licensed under the Apache License, Version 2.0 (the "License");
   ~ you may not use this file except in compliance with the License.
@@ -15,7 +15,6 @@
   ~ limitations under the License.
   -->
 
-<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
-    <!-- Don't use the smaller PIN pad keys if we have the screen space to support it. -->
-    <string name="num_pad_key_ratio">1.0</string>
-</resources>
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+          package="com.android.systemui.shared.biometrics">
+</manifest>
\ No newline at end of file
diff --git a/packages/SystemUI/shared/biometrics/res/values/strings.xml b/packages/SystemUI/shared/biometrics/res/values/strings.xml
new file mode 100644
index 0000000..c15c2b3
--- /dev/null
+++ b/packages/SystemUI/shared/biometrics/res/values/strings.xml
@@ -0,0 +1,27 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  ~ Copyright (C) 2023 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~     http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+
+<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <!-- [CHAR LIMIT=NONE] Messages shown when users press outside udfps region during -->
+    <string name="udfps_accessibility_touch_hints_left"> Move left </string>
+    <!-- [CHAR LIMIT=NONE] Messages shown when users press outside udfps region during -->
+    <string name="udfps_accessibility_touch_hints_down"> Move down </string>
+    <!-- [CHAR LIMIT=NONE] Messages shown when users press outside udfps region during -->
+    <string name="udfps_accessibility_touch_hints_right"> Move right </string>
+    <!-- [CHAR LIMIT=NONE] Messages shown when users press outside udfps region during -->
+    <string name="udfps_accessibility_touch_hints_up"> Move up </string>
+</resources>
\ No newline at end of file
diff --git a/packages/SettingsLib/src/com/android/settingslib/udfps/UdfpsUtils.java b/packages/SystemUI/shared/biometrics/src/com/android/systemui/biometrics/UdfpsUtils.java
similarity index 91%
rename from packages/SettingsLib/src/com/android/settingslib/udfps/UdfpsUtils.java
rename to packages/SystemUI/shared/biometrics/src/com/android/systemui/biometrics/UdfpsUtils.java
index 31f014c..9574fba 100644
--- a/packages/SettingsLib/src/com/android/settingslib/udfps/UdfpsUtils.java
+++ b/packages/SystemUI/shared/biometrics/src/com/android/systemui/biometrics/UdfpsUtils.java
@@ -14,9 +14,10 @@
  * limitations under the License.
  */
 
-package com.android.settingslib.udfps;
+package com.android.systemui.biometrics;
 
 import android.content.Context;
+import android.content.res.Resources;
 import android.graphics.Point;
 import android.util.DisplayUtils;
 import android.util.Log;
@@ -26,7 +27,8 @@
 import android.view.MotionEvent;
 import android.view.Surface;
 
-import com.android.settingslib.R;
+import com.android.systemui.biometrics.shared.model.UdfpsOverlayParams;
+import com.android.systemui.shared.biometrics.R;
 
 /** Utility class for working with udfps. */
 public class UdfpsUtils {
@@ -95,12 +97,13 @@
             return null;
         }
 
-        String[] touchHints = context.getResources().getStringArray(
-                R.array.udfps_accessibility_touch_hints);
-        if (touchHints.length != 4) {
-            Log.e(TAG, "expected exactly 4 touch hints, got " + touchHints.length + "?");
-            return null;
-        }
+        Resources resources = context.getResources();
+        String[] touchHints = new String[] {
+                resources.getString(R.string.udfps_accessibility_touch_hints_left),
+                resources.getString(R.string.udfps_accessibility_touch_hints_down),
+                resources.getString(R.string.udfps_accessibility_touch_hints_right),
+                resources.getString(R.string.udfps_accessibility_touch_hints_up),
+        };
 
         // Scale the coordinates to native resolution.
         float scale = udfpsOverlayParams.getScaleFactor();
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/Utils.kt b/packages/SystemUI/shared/biometrics/src/com/android/systemui/biometrics/Utils.kt
similarity index 89%
rename from packages/SystemUI/src/com/android/systemui/biometrics/Utils.kt
rename to packages/SystemUI/shared/biometrics/src/com/android/systemui/biometrics/Utils.kt
index 1ca57e7..422f02f 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/Utils.kt
+++ b/packages/SystemUI/shared/biometrics/src/com/android/systemui/biometrics/Utils.kt
@@ -47,12 +47,12 @@
     const val CREDENTIAL_PATTERN = 2
     const val CREDENTIAL_PASSWORD = 3
 
-    /** Base set of layout flags for fingerprint overlay widgets.  */
+    /** Base set of layout flags for fingerprint overlay widgets. */
     const val FINGERPRINT_OVERLAY_LAYOUT_PARAM_FLAGS =
-        (WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN
-            or WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
-            or WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
-            or WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED)
+        (WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN or
+            WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL or
+            WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE or
+            WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED)
 
     @JvmStatic
     fun dpToPixels(context: Context, dp: Float): Float {
@@ -61,9 +61,8 @@
     }
 
     /**
-     * Note: Talkback 14.0 has new rate-limitation design to reduce frequency
-     * of TYPE_WINDOW_CONTENT_CHANGED events to once every 30 seconds.
-     * (context: b/281765653#comment18)
+     * Note: Talkback 14.0 has new rate-limitation design to reduce frequency of
+     * TYPE_WINDOW_CONTENT_CHANGED events to once every 30 seconds. (context: b/281765653#comment18)
      * Using {@link View#announceForAccessibility} instead as workaround when sending events
      * exceeding this frequency is required.
      */
@@ -74,8 +73,7 @@
         }
         val event = AccessibilityEvent.obtain()
         event.eventType = AccessibilityEvent.TYPE_WINDOW_CONTENT_CHANGED
-        event.contentChangeTypes =
-            AccessibilityEvent.CONTENT_CHANGE_TYPE_SUBTREE
+        event.contentChangeTypes = AccessibilityEvent.CONTENT_CHANGE_TYPE_SUBTREE
         view.sendAccessibilityEventUnchecked(event)
         view.notifySubtreeAccessibilityStateChanged(
             view,
@@ -119,8 +117,8 @@
     @JvmStatic
     fun isSystem(context: Context, clientPackage: String?): Boolean {
         val hasPermission =
-            (context.checkCallingOrSelfPermission(Manifest.permission.USE_BIOMETRIC_INTERNAL)
-                == PackageManager.PERMISSION_GRANTED)
+            (context.checkCallingOrSelfPermission(Manifest.permission.USE_BIOMETRIC_INTERNAL) ==
+                PackageManager.PERMISSION_GRANTED)
         return hasPermission && "android" == clientPackage
     }
 
@@ -134,5 +132,5 @@
 
     @Retention(RetentionPolicy.SOURCE)
     @IntDef(CREDENTIAL_PIN, CREDENTIAL_PATTERN, CREDENTIAL_PASSWORD)
-    internal annotation class CredentialType
-}
\ No newline at end of file
+    annotation class CredentialType
+}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/domain/model/BiometricModalities.kt b/packages/SystemUI/shared/biometrics/src/com/android/systemui/biometrics/shared/model/BiometricModalities.kt
similarity index 97%
rename from packages/SystemUI/src/com/android/systemui/biometrics/domain/model/BiometricModalities.kt
rename to packages/SystemUI/shared/biometrics/src/com/android/systemui/biometrics/shared/model/BiometricModalities.kt
index 274f58a..db46ccf 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/domain/model/BiometricModalities.kt
+++ b/packages/SystemUI/shared/biometrics/src/com/android/systemui/biometrics/shared/model/BiometricModalities.kt
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.systemui.biometrics.domain.model
+package com.android.systemui.biometrics.shared.model
 
 import android.hardware.biometrics.SensorProperties
 import android.hardware.face.FaceSensorPropertiesInternal
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/shared/model/BiometricModality.kt b/packages/SystemUI/shared/biometrics/src/com/android/systemui/biometrics/shared/model/BiometricModality.kt
similarity index 100%
rename from packages/SystemUI/src/com/android/systemui/biometrics/shared/model/BiometricModality.kt
rename to packages/SystemUI/shared/biometrics/src/com/android/systemui/biometrics/shared/model/BiometricModality.kt
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/shared/model/BiometricUserInfo.kt b/packages/SystemUI/shared/biometrics/src/com/android/systemui/biometrics/shared/model/BiometricUserInfo.kt
similarity index 100%
rename from packages/SystemUI/src/com/android/systemui/biometrics/shared/model/BiometricUserInfo.kt
rename to packages/SystemUI/shared/biometrics/src/com/android/systemui/biometrics/shared/model/BiometricUserInfo.kt
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/shared/model/FingerprintSensorType.kt b/packages/SystemUI/shared/biometrics/src/com/android/systemui/biometrics/shared/model/FingerprintSensorType.kt
similarity index 98%
rename from packages/SystemUI/src/com/android/systemui/biometrics/shared/model/FingerprintSensorType.kt
rename to packages/SystemUI/shared/biometrics/src/com/android/systemui/biometrics/shared/model/FingerprintSensorType.kt
index c6fdcb3..6082fb9 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/shared/model/FingerprintSensorType.kt
+++ b/packages/SystemUI/shared/biometrics/src/com/android/systemui/biometrics/shared/model/FingerprintSensorType.kt
@@ -25,7 +25,7 @@
     UDFPS_ULTRASONIC,
     UDFPS_OPTICAL,
     POWER_BUTTON,
-    HOME_BUTTON,
+    HOME_BUTTON
 }
 
 /** Convert [this] to corresponding [FingerprintSensorType] */
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/shared/model/LockoutMode.kt b/packages/SystemUI/shared/biometrics/src/com/android/systemui/biometrics/shared/model/LockoutMode.kt
similarity index 100%
rename from packages/SystemUI/src/com/android/systemui/biometrics/shared/model/LockoutMode.kt
rename to packages/SystemUI/shared/biometrics/src/com/android/systemui/biometrics/shared/model/LockoutMode.kt
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/shared/model/SensorStrength.kt b/packages/SystemUI/shared/biometrics/src/com/android/systemui/biometrics/shared/model/SensorStrength.kt
similarity index 100%
rename from packages/SystemUI/src/com/android/systemui/biometrics/shared/model/SensorStrength.kt
rename to packages/SystemUI/shared/biometrics/src/com/android/systemui/biometrics/shared/model/SensorStrength.kt
diff --git a/packages/SettingsLib/src/com/android/settingslib/udfps/UdfpsOverlayParams.kt b/packages/SystemUI/shared/biometrics/src/com/android/systemui/biometrics/shared/model/UdfpsOverlayParams.kt
similarity index 96%
rename from packages/SettingsLib/src/com/android/settingslib/udfps/UdfpsOverlayParams.kt
rename to packages/SystemUI/shared/biometrics/src/com/android/systemui/biometrics/shared/model/UdfpsOverlayParams.kt
index b386e5e..a9b4fe8 100644
--- a/packages/SettingsLib/src/com/android/settingslib/udfps/UdfpsOverlayParams.kt
+++ b/packages/SystemUI/shared/biometrics/src/com/android/systemui/biometrics/shared/model/UdfpsOverlayParams.kt
@@ -1,4 +1,4 @@
-package com.android.settingslib.udfps
+package com.android.systemui.biometrics.shared.model
 
 import android.graphics.Rect
 import android.view.Surface
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/InteractionJankMonitorWrapper.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/InteractionJankMonitorWrapper.java
index 77f6d03..eb20669 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/InteractionJankMonitorWrapper.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/InteractionJankMonitorWrapper.java
@@ -61,8 +61,6 @@
             InteractionJankMonitor.CUJ_LAUNCHER_APP_SWIPE_TO_RECENTS;
     public static final int CUJ_OPEN_SEARCH_RESULT =
             InteractionJankMonitor.CUJ_LAUNCHER_OPEN_SEARCH_RESULT;
-    public static final int CUJ_SHADE_EXPAND_FROM_STATUS_BAR =
-            InteractionJankMonitor.CUJ_SHADE_EXPAND_FROM_STATUS_BAR;
 
     @IntDef({
             CUJ_APP_LAUNCH_FROM_RECENTS,
@@ -79,7 +77,6 @@
             CUJ_CLOSE_ALL_APPS_SWIPE,
             CUJ_CLOSE_ALL_APPS_TO_HOME,
             CUJ_OPEN_SEARCH_RESULT,
-            CUJ_SHADE_EXPAND_FROM_STATUS_BAR,
     })
     @Retention(RetentionPolicy.SOURCE)
     public @interface CujType {
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardInputView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardInputView.java
index a72d813..98f082f 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardInputView.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardInputView.java
@@ -64,8 +64,10 @@
         return false;
     }
 
-    /** Change motion layout constraint set based on orientation */
-    protected void updateConstraints(int orientation) {
+    /** Updates the keyguard view's constraints (single or split constraints).
+     *  Split constraints are only used for small landscape screens.
+     *  Only called when flag LANDSCAPE_ENABLE_LOCKSCREEN is enabled. */
+    protected void updateConstraints(boolean useSplitBouncer) {
         //Unless overridden, never update constrains (keeping default portrait constraints)
     }
 
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardInputViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardInputViewController.java
index 42dbc48..8738d33 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardInputViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardInputViewController.java
@@ -16,6 +16,8 @@
 
 package com.android.keyguard;
 
+import static com.android.systemui.flags.Flags.LOCKSCREEN_ENABLE_LANDSCAPE;
+
 import android.annotation.CallSuper;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
@@ -257,6 +259,8 @@
                         mFalsingCollector, mKeyguardViewController,
                         mFeatureFlags);
             } else if (keyguardInputView instanceof KeyguardPINView) {
+                ((KeyguardPINView) keyguardInputView).setIsLockScreenLandscapeEnabled(
+                        mFeatureFlags.isEnabled(LOCKSCREEN_ENABLE_LANDSCAPE));
                 return new KeyguardPinViewController((KeyguardPINView) keyguardInputView,
                         mKeyguardUpdateMonitor, securityMode, mLockPatternUtils,
                         keyguardSecurityCallback, mMessageAreaControllerFactory, mLatencyTracker,
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPINView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPINView.java
index d9b7bde..bb3e759 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardPINView.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPINView.java
@@ -16,12 +16,15 @@
 
 package com.android.keyguard;
 
+import static android.content.res.Configuration.ORIENTATION_LANDSCAPE;
 import static com.android.internal.jank.InteractionJankMonitor.CUJ_LOCKSCREEN_PIN_APPEAR;
 import static com.android.internal.jank.InteractionJankMonitor.CUJ_LOCKSCREEN_PIN_DISAPPEAR;
+import static com.android.systemui.statusbar.policy.DevicePostureController.DEVICE_POSTURE_CLOSED;
 import static com.android.systemui.statusbar.policy.DevicePostureController.DEVICE_POSTURE_HALF_OPENED;
 import static com.android.systemui.statusbar.policy.DevicePostureController.DEVICE_POSTURE_UNKNOWN;
 
 import android.animation.ValueAnimator;
+import android.annotation.Nullable;
 import android.content.Context;
 import android.content.res.Configuration;
 import android.util.AttributeSet;
@@ -30,6 +33,7 @@
 import android.view.animation.AnimationUtils;
 import android.view.animation.Interpolator;
 
+import androidx.constraintlayout.motion.widget.MotionLayout;
 import androidx.constraintlayout.widget.ConstraintLayout;
 import androidx.constraintlayout.widget.ConstraintSet;
 
@@ -46,12 +50,15 @@
     ValueAnimator mAppearAnimator = ValueAnimator.ofFloat(0f, 1f);
     private final DisappearAnimationUtils mDisappearAnimationUtils;
     private final DisappearAnimationUtils mDisappearAnimationUtilsLocked;
-    private ConstraintLayout mContainer;
+    @Nullable private MotionLayout mContainerMotionLayout;
+    @Nullable private ConstraintLayout mContainerConstraintLayout;
     private int mDisappearYTranslation;
     private View[][] mViews;
     private int mYTrans;
     private int mYTransOffset;
     private View mBouncerMessageArea;
+    private boolean mAlreadyUsingSplitBouncer = false;
+    private boolean mIsLockScreenLandscapeEnabled = false;
     @DevicePostureInt private int mLastDevicePosture = DEVICE_POSTURE_UNKNOWN;
     public static final long ANIMATION_DURATION = 650;
 
@@ -76,6 +83,22 @@
         mYTransOffset = getResources().getDimensionPixelSize(R.dimen.pin_view_trans_y_entry_offset);
     }
 
+    /** Use motion layout (new bouncer implementation) if LOCKSCREEN_ENABLE_LANDSCAPE flag is
+     *  enabled, instead of constraint layout (old bouncer implementation) */
+    public void setIsLockScreenLandscapeEnabled(boolean isLockScreenLandscapeEnabled) {
+        mIsLockScreenLandscapeEnabled = isLockScreenLandscapeEnabled;
+        findContainerLayout();
+    }
+
+    private void findContainerLayout() {
+        if (mIsLockScreenLandscapeEnabled) {
+            mContainerMotionLayout = findViewById(R.id.pin_container);
+        } else {
+            mContainerConstraintLayout = findViewById(R.id.pin_container);
+        }
+    }
+
+
     @Override
     protected void onConfigurationChanged(Configuration newConfig) {
         updateMargins();
@@ -84,6 +107,17 @@
     void onDevicePostureChanged(@DevicePostureInt int posture) {
         if (mLastDevicePosture != posture) {
             mLastDevicePosture = posture;
+
+            if (mIsLockScreenLandscapeEnabled) {
+                boolean useSplitBouncerAfterFold =
+                        mLastDevicePosture == DEVICE_POSTURE_CLOSED
+                        &&  getResources().getConfiguration().orientation == ORIENTATION_LANDSCAPE;
+
+                if (mAlreadyUsingSplitBouncer != useSplitBouncerAfterFold) {
+                    updateConstraints(useSplitBouncerAfterFold);
+                }
+            }
+
             updateMargins();
         }
     }
@@ -135,18 +169,40 @@
         float halfOpenPercentage =
                 mContext.getResources().getFloat(R.dimen.half_opened_bouncer_height_ratio);
 
-        ConstraintSet cs = new ConstraintSet();
-        cs.clone(mContainer);
-        cs.setGuidelinePercent(R.id.pin_pad_top_guideline,
-                mLastDevicePosture == DEVICE_POSTURE_HALF_OPENED ? halfOpenPercentage : 0.0f);
-        cs.applyTo(mContainer);
+        if (mIsLockScreenLandscapeEnabled) {
+            ConstraintSet cs = mContainerMotionLayout.getConstraintSet(R.id.single_constraints);
+            cs.setGuidelinePercent(R.id.pin_pad_top_guideline,
+                    mLastDevicePosture == DEVICE_POSTURE_HALF_OPENED ? halfOpenPercentage : 0.0f);
+            cs.applyTo(mContainerMotionLayout);
+        } else {
+            ConstraintSet cs = new ConstraintSet();
+            cs.clone(mContainerConstraintLayout);
+            cs.setGuidelinePercent(R.id.pin_pad_top_guideline,
+                    mLastDevicePosture == DEVICE_POSTURE_HALF_OPENED ? halfOpenPercentage : 0.0f);
+            cs.applyTo(mContainerConstraintLayout);
+        }
+    }
+
+    /** Updates the keyguard view's constraints (single or split constraints).
+     *  Split constraints are only used for small landscape screens.
+     *  Only called when flag LANDSCAPE_ENABLE_LOCKSCREEN is enabled. */
+    @Override
+    protected void updateConstraints(boolean useSplitBouncer) {
+        mAlreadyUsingSplitBouncer = useSplitBouncer;
+        if (useSplitBouncer) {
+            mContainerMotionLayout.jumpToState(R.id.split_constraints);
+            mContainerMotionLayout.setMaxWidth(Integer.MAX_VALUE);
+        } else {
+            mContainerMotionLayout.jumpToState(R.id.single_constraints);
+            mContainerMotionLayout.setMaxWidth(getResources()
+                    .getDimensionPixelSize(R.dimen.keyguard_security_width));
+        }
     }
 
     @Override
     protected void onFinishInflate() {
         super.onFinishInflate();
 
-        mContainer = findViewById(R.id.pin_container);
         mBouncerMessageArea = findViewById(R.id.bouncer_message_area);
         mViews = new View[][]{
                 new View[]{
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java
index a2d8c50..e9dd08c 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java
@@ -17,7 +17,7 @@
 package com.android.keyguard;
 
 import static android.app.StatusBarManager.SESSION_KEYGUARD;
-
+import static android.content.res.Configuration.ORIENTATION_LANDSCAPE;
 import static com.android.keyguard.KeyguardSecurityContainer.BOUNCER_DISMISS_BIOMETRIC;
 import static com.android.keyguard.KeyguardSecurityContainer.BOUNCER_DISMISS_EXTENDED_ACCESS;
 import static com.android.keyguard.KeyguardSecurityContainer.BOUNCER_DISMISS_NONE_SECURITY;
@@ -74,6 +74,7 @@
 import com.android.systemui.biometrics.SideFpsController;
 import com.android.systemui.biometrics.SideFpsUiRequestSource;
 import com.android.systemui.bouncer.domain.interactor.BouncerMessageInteractor;
+import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerInteractor;
 import com.android.systemui.classifier.FalsingA11yDelegate;
 import com.android.systemui.classifier.FalsingCollector;
 import com.android.systemui.flags.FeatureFlags;
@@ -94,6 +95,8 @@
 import com.android.systemui.util.kotlin.JavaAdapter;
 import com.android.systemui.util.settings.GlobalSettings;
 
+import dagger.Lazy;
+
 import java.io.File;
 import java.util.Optional;
 
@@ -201,7 +204,6 @@
     };
 
     private KeyguardSecurityCallback mKeyguardSecurityCallback = new KeyguardSecurityCallback() {
-
         @Override
         public void onUserInput() {
             mBouncerMessageInteractor.onPrimaryBouncerUserInput();
@@ -297,21 +299,23 @@
          */
         @Override
         public void finish(int targetUserId) {
-            // If there's a pending runnable because the user interacted with a widget
-            // and we're leaving keyguard, then run it.
-            boolean deferKeyguardDone = false;
-            mWillRunDismissFromKeyguard = false;
-            if (mDismissAction != null) {
-                deferKeyguardDone = mDismissAction.onDismiss();
-                mWillRunDismissFromKeyguard = mDismissAction.willRunAnimationOnKeyguard();
-                mDismissAction = null;
-                mCancelAction = null;
-            }
-            if (mViewMediatorCallback != null) {
-                if (deferKeyguardDone) {
-                    mViewMediatorCallback.keyguardDonePending(targetUserId);
-                } else {
-                    mViewMediatorCallback.keyguardDone(targetUserId);
+            if (!mFeatureFlags.isEnabled(Flags.REFACTOR_KEYGUARD_DISMISS_INTENT)) {
+                // If there's a pending runnable because the user interacted with a widget
+                // and we're leaving keyguard, then run it.
+                boolean deferKeyguardDone = false;
+                mWillRunDismissFromKeyguard = false;
+                if (mDismissAction != null) {
+                    deferKeyguardDone = mDismissAction.onDismiss();
+                    mWillRunDismissFromKeyguard = mDismissAction.willRunAnimationOnKeyguard();
+                    mDismissAction = null;
+                    mCancelAction = null;
+                }
+                if (mViewMediatorCallback != null) {
+                    if (deferKeyguardDone) {
+                        mViewMediatorCallback.keyguardDonePending(targetUserId);
+                    } else {
+                        mViewMediatorCallback.keyguardDone(targetUserId);
+                    }
                 }
             }
 
@@ -326,7 +330,6 @@
         }
     };
 
-
     private final SwipeListener mSwipeListener = new SwipeListener() {
         @Override
         public void onSwipeUp() {
@@ -373,7 +376,8 @@
                 public void onOrientationChanged(int orientation) {
                     if (mFeatureFlags.isEnabled(LOCKSCREEN_ENABLE_LANDSCAPE)
                             && getResources().getBoolean(R.bool.update_bouncer_constraints)) {
-                        mSecurityViewFlipperController.updateConstraints(orientation);
+                        boolean useSplitBouncer = orientation == ORIENTATION_LANDSCAPE;
+                        mSecurityViewFlipperController.updateConstraints(useSplitBouncer);
                     }
                 }
             };
@@ -416,6 +420,7 @@
     private final Provider<AuthenticationInteractor> mAuthenticationInteractor;
     private final Provider<JavaAdapter> mJavaAdapter;
     private final DeviceProvisionedController mDeviceProvisionedController;
+    private final Lazy<PrimaryBouncerInteractor> mPrimaryBouncerInteractor;
     @Nullable private Job mSceneTransitionCollectionJob;
 
     @Inject
@@ -448,6 +453,7 @@
             DeviceProvisionedController deviceProvisionedController,
             FaceAuthAccessibilityDelegate faceAuthAccessibilityDelegate,
             KeyguardTransitionInteractor keyguardTransitionInteractor,
+            Lazy<PrimaryBouncerInteractor> primaryBouncerInteractor,
             Provider<AuthenticationInteractor> authenticationInteractor
     ) {
         super(view);
@@ -482,6 +488,7 @@
         mJavaAdapter = javaAdapter;
         mKeyguardTransitionInteractor = keyguardTransitionInteractor;
         mDeviceProvisionedController = deviceProvisionedController;
+        mPrimaryBouncerInteractor = primaryBouncerInteractor;
     }
 
     @Override
@@ -618,6 +625,9 @@
      * @param action callback to be invoked when keyguard disappear animation completes.
      */
     public void setOnDismissAction(ActivityStarter.OnDismissAction action, Runnable cancelAction) {
+        if (mFeatureFlags.isEnabled(Flags.REFACTOR_KEYGUARD_DISMISS_INTENT)) {
+            return;
+        }
         if (mCancelAction != null) {
             mCancelAction.run();
         }
@@ -820,7 +830,6 @@
      */
     public boolean showNextSecurityScreenOrFinish(boolean authenticated, int targetUserId,
             boolean bypassSecondaryLockScreen, SecurityMode expectedSecurityMode) {
-
         if (DEBUG) Log.d(TAG, "showNextSecurityScreenOrFinish(" + authenticated + ")");
         if (expectedSecurityMode != SecurityMode.Invalid
                 && expectedSecurityMode != getCurrentSecurityMode()) {
@@ -829,8 +838,8 @@
             return false;
         }
 
+        boolean authenticatedWithPrimaryAuth = false;
         boolean finish = false;
-        boolean primaryAuth = false;
         int eventSubtype = -1;
         BouncerUiEvent uiEvent = BouncerUiEvent.UNKNOWN;
         if (mUpdateMonitor.getUserHasTrust(targetUserId)) {
@@ -855,7 +864,7 @@
                 case Pattern:
                 case Password:
                 case PIN:
-                    primaryAuth = true;
+                    authenticatedWithPrimaryAuth = true;
                     finish = true;
                     eventSubtype = BOUNCER_DISMISS_PASSWORD;
                     uiEvent = BouncerUiEvent.BOUNCER_DISMISS_PASSWORD;
@@ -901,6 +910,17 @@
         if (uiEvent != BouncerUiEvent.UNKNOWN) {
             mUiEventLogger.log(uiEvent, getSessionId());
         }
+
+        if (mFeatureFlags.isEnabled(Flags.REFACTOR_KEYGUARD_DISMISS_INTENT)) {
+            if (authenticatedWithPrimaryAuth) {
+                mPrimaryBouncerInteractor.get()
+                        .notifyKeyguardAuthenticatedPrimaryAuth(targetUserId);
+            } else if (finish) {
+                mPrimaryBouncerInteractor.get().notifyUserRequestedBouncerWhenAlreadyAuthenticated(
+                        targetUserId);
+            }
+        }
+
         if (finish) {
             mKeyguardSecurityCallback.finish(targetUserId);
         }
@@ -1058,12 +1078,15 @@
      * one side).
      */
     private boolean canUseOneHandedBouncer() {
-        if (!(mCurrentSecurityMode == SecurityMode.Pattern
-                || mCurrentSecurityMode == SecurityMode.PIN)) {
-            return false;
+        switch(mCurrentSecurityMode) {
+            case PIN:
+            case Pattern:
+            case SimPin:
+            case SimPuk:
+                return getResources().getBoolean(R.bool.can_use_one_handed_bouncer);
+            default:
+                return false;
         }
-
-        return getResources().getBoolean(R.bool.can_use_one_handed_bouncer);
     }
 
     private boolean canDisplayUserSwitcher() {
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityViewFlipper.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityViewFlipper.java
index 891eb14..74f9006 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityViewFlipper.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityViewFlipper.java
@@ -83,11 +83,13 @@
         return "";
     }
 
-    /** Updates the keyguard view's constraints based on orientation */
-    public void updateConstraints(int orientation) {
+    /** Updates the keyguard view's constraints (single or split constraints).
+     *  Split constraints are only used for small landscape screens.
+     *  Only called when flag LANDSCAPE_ENABLE_LOCKSCREEN is enabled. */
+    public void updateConstraints(boolean useSplitBouncer) {
         KeyguardInputView securityView = getSecurityView();
         if (securityView != null) {
-            securityView.updateConstraints(orientation);
+            securityView.updateConstraints(useSplitBouncer);
         }
     }
 
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityViewFlipperController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityViewFlipperController.java
index 74f0beb..4cc90c2 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityViewFlipperController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityViewFlipperController.java
@@ -16,6 +16,7 @@
 
 package com.android.keyguard;
 
+import static android.content.res.Configuration.ORIENTATION_LANDSCAPE;
 import static com.android.systemui.flags.Flags.LOCKSCREEN_ENABLE_LANDSCAPE;
 
 import android.util.Log;
@@ -136,12 +137,14 @@
                         if (onViewInflatedListener != null) {
                             onViewInflatedListener.onViewInflated(childController);
 
-                            // Portrait constrains are default
+                            // Single bouncer constrains are default
                             if (mFeatureFlags.isEnabled(LOCKSCREEN_ENABLE_LANDSCAPE)
                                     &&
                                     getResources().getBoolean(R.bool.update_bouncer_constraints)) {
-                                // updateConstraints based on orientation (only on small screens)
-                                updateConstraints(getResources().getConfiguration().orientation);
+                                boolean useSplitBouncer =
+                                        getResources().getConfiguration().orientation
+                                                == ORIENTATION_LANDSCAPE;
+                                updateConstraints(useSplitBouncer);
                             }
                         }
                     });
@@ -152,7 +155,7 @@
         // TODO (b/297863911, b/297864907) - implement motion layout for other bouncers
         switch (securityMode) {
             case Pattern: return R.layout.keyguard_pattern_view;
-            case PIN: return R.layout.keyguard_pin_view;
+            case PIN: return R.layout.keyguard_pin_motion_layout;
             case Password: return R.layout.keyguard_password_view;
             case SimPin: return R.layout.keyguard_sim_pin_view;
             case SimPuk: return R.layout.keyguard_sim_puk_view;
@@ -173,9 +176,11 @@
         }
     }
 
-    /** Updates the keyguard view's constraints based on orientation */
-    public void updateConstraints(int orientation) {
-        mView.updateConstraints(orientation);
+    /** Updates the keyguard view's constraints (single or split constraints).
+     *  Split constraints are only used for small landscape screens.
+     *  Only called when flag LANDSCAPE_ENABLE_LOCKSCREEN is enabled. */
+    public void updateConstraints(boolean useSplitBouncer) {
+        mView.updateConstraints(useSplitBouncer);
     }
 
     /** Makes the supplied child visible if it is contained win this view, */
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSimPinViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSimPinViewController.java
index 886a1b5..1fc88ab 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSimPinViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSimPinViewController.java
@@ -141,10 +141,10 @@
         // GSM 02.17 version 5.0.1, Section 5.6 PIN Management
         if ((entry.length() < 4) || (entry.length() > 8)) {
             // otherwise, display a message to the user, and don't submit.
-            mMessageAreaController.setMessage(
-                    com.android.systemui.R.string.kg_invalid_sim_pin_hint);
             mView.resetPasswordText(true /* animate */, true /* announce */);
             getKeyguardSecurityCallback().userActivity();
+            mMessageAreaController.setMessage(
+                    com.android.systemui.R.string.kg_invalid_sim_pin_hint);
             return;
         }
 
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
index 0ba6c05..e4bbd3a 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardUpdateMonitor.java
@@ -35,7 +35,6 @@
 import static android.os.BatteryManager.BATTERY_STATUS_UNKNOWN;
 import static android.os.BatteryManager.CHARGING_POLICY_DEFAULT;
 import static android.os.PowerManager.WAKE_REASON_UNKNOWN;
-
 import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_BOOT;
 import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_DPM_LOCK_NOW;
 import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_LOCKOUT;
@@ -161,8 +160,6 @@
 import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.dump.DumpManager;
 import com.android.systemui.dump.DumpsysTableLogger;
-import com.android.systemui.flags.FeatureFlags;
-import com.android.systemui.flags.Flags;
 import com.android.systemui.keyguard.domain.interactor.FaceAuthenticationListener;
 import com.android.systemui.keyguard.domain.interactor.KeyguardFaceAuthInteractor;
 import com.android.systemui.keyguard.shared.constants.TrustAgentUiEvent;
@@ -417,7 +414,6 @@
     private final ActiveUnlockConfig mActiveUnlockConfig;
     private final IDreamManager mDreamManager;
     private final TelephonyManager mTelephonyManager;
-    private final FeatureFlags mFeatureFlags;
     @Nullable
     private final FingerprintManager mFpm;
     @Nullable
@@ -2365,7 +2361,6 @@
             FaceWakeUpTriggersConfig faceWakeUpTriggersConfig,
             DevicePostureController devicePostureController,
             Optional<FingerprintInteractiveToAuthProvider> interactiveToAuthProvider,
-            FeatureFlags featureFlags,
             TaskStackChangeListeners taskStackChangeListeners,
             IActivityTaskManager activityTaskManagerService,
             DisplayTracker displayTracker) {
@@ -2400,7 +2395,6 @@
         mPackageManager = packageManager;
         mFpm = fingerprintManager;
         mFaceManager = faceManager;
-        mFeatureFlags = featureFlags;
         mActiveUnlockConfig.setKeyguardUpdateMonitor(this);
         mFaceAcquiredInfoIgnoreList = Arrays.stream(
                 mContext.getResources().getIntArray(
@@ -2417,9 +2411,7 @@
         mTaskStackChangeListeners = taskStackChangeListeners;
         mActivityTaskManager = activityTaskManagerService;
         mDisplayTracker = displayTracker;
-        if (mFeatureFlags.isEnabled(Flags.STOP_FACE_AUTH_ON_DISPLAY_OFF)) {
-            mDisplayTracker.addDisplayChangeCallback(mDisplayCallback, mainExecutor);
-        }
+        mDisplayTracker.addDisplayChangeCallback(mDisplayCallback, mainExecutor);
 
         mHandler = new Handler(mainLooper) {
             @Override
@@ -4205,21 +4197,19 @@
         @Override
         public void onTaskStackChangedBackground() {
             try {
-                if (mFeatureFlags.isEnabled(Flags.FP_LISTEN_OCCLUDING_APPS)) {
-                    RootTaskInfo standardTask = mActivityTaskManager.getRootTaskInfo(
-                            WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD);
-                    final boolean previousState = mAllowFingerprintOnCurrentOccludingActivity;
-                    mAllowFingerprintOnCurrentOccludingActivity =
-                            standardTask.topActivity != null
-                                    && !TextUtils.isEmpty(standardTask.topActivity.getPackageName())
-                                    && mAllowFingerprintOnOccludingActivitiesFromPackage.contains(
-                                            standardTask.topActivity.getPackageName())
-                                    && standardTask.visible;
-                    if (mAllowFingerprintOnCurrentOccludingActivity != previousState) {
-                        mLogger.allowFingerprintOnCurrentOccludingActivityChanged(
-                                mAllowFingerprintOnCurrentOccludingActivity);
-                        updateFingerprintListeningState(BIOMETRIC_ACTION_UPDATE);
-                    }
+                RootTaskInfo standardTask = mActivityTaskManager.getRootTaskInfo(
+                        WINDOWING_MODE_FULLSCREEN, ACTIVITY_TYPE_STANDARD);
+                final boolean previousState = mAllowFingerprintOnCurrentOccludingActivity;
+                mAllowFingerprintOnCurrentOccludingActivity =
+                        standardTask.topActivity != null
+                                && !TextUtils.isEmpty(standardTask.topActivity.getPackageName())
+                                && mAllowFingerprintOnOccludingActivitiesFromPackage.contains(
+                                        standardTask.topActivity.getPackageName())
+                                && standardTask.visible;
+                if (mAllowFingerprintOnCurrentOccludingActivity != previousState) {
+                    mLogger.allowFingerprintOnCurrentOccludingActivityChanged(
+                            mAllowFingerprintOnCurrentOccludingActivity);
+                    updateFingerprintListeningState(BIOMETRIC_ACTION_UPDATE);
                 }
 
                 RootTaskInfo assistantTask = mActivityTaskManager.getRootTaskInfo(
diff --git a/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java b/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java
index ab9b647..4649091 100644
--- a/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/LockIconViewController.java
@@ -18,7 +18,6 @@
 
 import static android.hardware.biometrics.BiometricAuthenticator.TYPE_FINGERPRINT;
 import static android.hardware.biometrics.BiometricSourceType.FINGERPRINT;
-
 import static com.android.keyguard.LockIconView.ICON_FINGERPRINT;
 import static com.android.keyguard.LockIconView.ICON_LOCK;
 import static com.android.keyguard.LockIconView.ICON_UNLOCK;
@@ -54,12 +53,12 @@
 import androidx.annotation.VisibleForTesting;
 import androidx.core.view.accessibility.AccessibilityNodeInfoCompat;
 
-import com.android.settingslib.udfps.UdfpsOverlayParams;
 import com.android.systemui.Dumpable;
 import com.android.systemui.R;
 import com.android.systemui.biometrics.AuthController;
 import com.android.systemui.biometrics.AuthRippleController;
 import com.android.systemui.biometrics.UdfpsController;
+import com.android.systemui.biometrics.shared.model.UdfpsOverlayParams;
 import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerInteractor;
 import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.dagger.qualifiers.Main;
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/SystemActions.java b/packages/SystemUI/src/com/android/systemui/accessibility/SystemActions.java
index 03ad132..7a8161e 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/SystemActions.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/SystemActions.java
@@ -17,7 +17,6 @@
 package com.android.systemui.accessibility;
 
 import static android.view.WindowManager.ScreenshotSource.SCREENSHOT_ACCESSIBILITY_ACTIONS;
-
 import static com.android.internal.accessibility.common.ShortcutConstants.CHOOSER_PACKAGE_NAME;
 
 import android.accessibilityservice.AccessibilityService;
@@ -57,8 +56,8 @@
 import com.android.systemui.shade.ShadeViewController;
 import com.android.systemui.statusbar.CommandQueue;
 import com.android.systemui.statusbar.NotificationShadeWindowController;
-import com.android.systemui.statusbar.phone.CentralSurfaces;
 import com.android.systemui.statusbar.phone.StatusBarWindowCallback;
+import com.android.systemui.statusbar.policy.KeyguardStateController;
 import com.android.systemui.util.Assert;
 
 import dagger.Lazy;
@@ -186,8 +185,8 @@
     private final DisplayTracker mDisplayTracker;
     private Locale mLocale;
     private final AccessibilityManager mA11yManager;
-    private final Lazy<Optional<CentralSurfaces>> mCentralSurfacesOptionalLazy;
     private final NotificationShadeWindowController mNotificationShadeController;
+    private final KeyguardStateController mKeyguardStateController;
     private final ShadeController mShadeController;
     private final Lazy<ShadeViewController> mShadeViewController;
     private final StatusBarWindowCallback mNotificationShadeCallback;
@@ -197,13 +196,14 @@
     public SystemActions(Context context,
             UserTracker userTracker,
             NotificationShadeWindowController notificationShadeController,
+            KeyguardStateController keyguardStateController,
             ShadeController shadeController,
             Lazy<ShadeViewController> shadeViewController,
-            Lazy<Optional<CentralSurfaces>> centralSurfacesOptionalLazy,
             Optional<Recents> recentsOptional,
             DisplayTracker displayTracker) {
         mContext = context;
         mUserTracker = userTracker;
+        mKeyguardStateController = keyguardStateController;
         mShadeController = shadeController;
         mShadeViewController = shadeViewController;
         mRecentsOptional = recentsOptional;
@@ -219,7 +219,6 @@
                 (keyguardShowing, keyguardOccluded, keyguardGoingAway, bouncerShowing, mDozing,
                         panelExpanded, isDreaming) ->
                         registerOrUnregisterDismissNotificationShadeAction();
-        mCentralSurfacesOptionalLazy = centralSurfacesOptionalLazy;
     }
 
     @Override
@@ -307,8 +306,8 @@
         mA11yManager.registerSystemAction(actionBack, SYSTEM_ACTION_ID_BACK);
         mA11yManager.registerSystemAction(actionHome, SYSTEM_ACTION_ID_HOME);
         mA11yManager.registerSystemAction(actionRecents, SYSTEM_ACTION_ID_RECENTS);
-        if (mCentralSurfacesOptionalLazy.get().isPresent()) {
-            // These two actions require the CentralSurfaces instance.
+        if (mShadeController.isShadeEnabled()) {
+            // These two actions require the shade to be enabled.
             mA11yManager.registerSystemAction(actionNotifications, SYSTEM_ACTION_ID_NOTIFICATIONS);
             mA11yManager.registerSystemAction(actionQuickSettings, SYSTEM_ACTION_ID_QUICK_SETTINGS);
         }
@@ -329,13 +328,8 @@
     private void registerOrUnregisterDismissNotificationShadeAction() {
         Assert.isMainThread();
 
-        // Saving state in instance variable since this callback is called quite often to avoid
-        // binder calls
-        final Optional<CentralSurfaces> centralSurfacesOptional =
-                mCentralSurfacesOptionalLazy.get();
-        if (centralSurfacesOptional.isPresent()
-                && mShadeViewController.get().isPanelExpanded()
-                && !centralSurfacesOptional.get().isKeyguardShowing()) {
+        if (mShadeViewController.get().isPanelExpanded()
+                && !mKeyguardStateController.isShowing()) {
             if (!mDismissNotificationShadeActionRegistered) {
                 mA11yManager.registerSystemAction(
                         createRemoteAction(
diff --git a/packages/SystemUI/src/com/android/systemui/authentication/data/repository/AuthenticationRepository.kt b/packages/SystemUI/src/com/android/systemui/authentication/data/repository/AuthenticationRepository.kt
index 8d1fc5d..b2433d4 100644
--- a/packages/SystemUI/src/com/android/systemui/authentication/data/repository/AuthenticationRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/authentication/data/repository/AuthenticationRepository.kt
@@ -109,6 +109,9 @@
      */
     val authenticationMethod: Flow<AuthenticationMethodModel>
 
+    /** The minimal length of a pattern. */
+    val minPatternLength: Int
+
     /**
      * Returns the currently-configured authentication method. This determines how the
      * authentication challenge needs to be completed in order to unlock an otherwise locked device.
@@ -227,6 +230,8 @@
                 }
             }
 
+    override val minPatternLength: Int = LockPatternUtils.MIN_LOCK_PATTERN_SIZE
+
     override suspend fun getAuthenticationMethod(): AuthenticationMethodModel {
         return withContext(backgroundDispatcher) {
             blockingAuthenticationMethodInternal(userRepository.selectedUserId)
diff --git a/packages/SystemUI/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractor.kt b/packages/SystemUI/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractor.kt
index ecd7bae..57a4224 100644
--- a/packages/SystemUI/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractor.kt
@@ -185,6 +185,9 @@
     /** Whether the pattern should be visible for the currently-selected user. */
     val isPatternVisible: StateFlow<Boolean> = repository.isPatternVisible
 
+    /** The minimal length of a pattern. */
+    val minPatternLength: Int = repository.minPatternLength
+
     private var throttlingCountdownJob: Job? = null
 
     init {
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFaceIconController.kt b/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFaceIconController.kt
index 0c7d56f..ea8f5d3 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFaceIconController.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFaceIconController.kt
@@ -20,14 +20,7 @@
 import android.util.Log
 import com.airbnb.lottie.LottieAnimationView
 import com.android.systemui.R
-import com.android.systemui.biometrics.AuthBiometricView.BiometricState
-import com.android.systemui.biometrics.AuthBiometricView.STATE_AUTHENTICATED
-import com.android.systemui.biometrics.AuthBiometricView.STATE_AUTHENTICATING
-import com.android.systemui.biometrics.AuthBiometricView.STATE_AUTHENTICATING_ANIMATING_IN
-import com.android.systemui.biometrics.AuthBiometricView.STATE_ERROR
-import com.android.systemui.biometrics.AuthBiometricView.STATE_HELP
-import com.android.systemui.biometrics.AuthBiometricView.STATE_IDLE
-import com.android.systemui.biometrics.AuthBiometricView.STATE_PENDING_CONFIRMATION
+import com.android.systemui.biometrics.ui.binder.Spaghetti.BiometricState
 
 private const val TAG = "AuthBiometricFaceIconController"
 
@@ -40,8 +33,7 @@
     // false = dark to light, true = light to dark
     private var lastPulseLightToDark = false
 
-    @BiometricState
-    private var state = 0
+    private var state: BiometricState = BiometricState.STATE_IDLE
 
     init {
         val size = context.resources.getDimensionPixelSize(R.dimen.biometric_dialog_face_icon_size)
@@ -66,54 +58,54 @@
     }
 
     override fun handleAnimationEnd(drawable: Drawable) {
-        if (state == STATE_AUTHENTICATING || state == STATE_HELP) {
+        if (state == BiometricState.STATE_AUTHENTICATING || state == BiometricState.STATE_HELP) {
             pulseInNextDirection()
         }
     }
 
-    override fun updateIcon(@BiometricState oldState: Int, @BiometricState newState: Int) {
-        val lastStateIsErrorIcon = (oldState == STATE_ERROR || oldState == STATE_HELP)
-        if (newState == STATE_AUTHENTICATING_ANIMATING_IN) {
+    override fun updateIcon(oldState: BiometricState, newState: BiometricState) {
+        val lastStateIsErrorIcon = (oldState == BiometricState.STATE_ERROR || oldState == BiometricState.STATE_HELP)
+        if (newState == BiometricState.STATE_AUTHENTICATING_ANIMATING_IN) {
             showStaticDrawable(R.drawable.face_dialog_pulse_dark_to_light)
             iconView.contentDescription = context.getString(
                     R.string.biometric_dialog_face_icon_description_authenticating
             )
-        } else if (newState == STATE_AUTHENTICATING) {
+        } else if (newState == BiometricState.STATE_AUTHENTICATING) {
             startPulsing()
             iconView.contentDescription = context.getString(
                     R.string.biometric_dialog_face_icon_description_authenticating
             )
-        } else if (oldState == STATE_PENDING_CONFIRMATION && newState == STATE_AUTHENTICATED) {
+        } else if (oldState == BiometricState.STATE_PENDING_CONFIRMATION && newState == BiometricState.STATE_AUTHENTICATED) {
             animateIconOnce(R.drawable.face_dialog_dark_to_checkmark)
             iconView.contentDescription = context.getString(
                     R.string.biometric_dialog_face_icon_description_confirmed
             )
-        } else if (lastStateIsErrorIcon && newState == STATE_IDLE) {
+        } else if (lastStateIsErrorIcon && newState == BiometricState.STATE_IDLE) {
             animateIconOnce(R.drawable.face_dialog_error_to_idle)
             iconView.contentDescription = context.getString(
                     R.string.biometric_dialog_face_icon_description_idle
             )
-        } else if (lastStateIsErrorIcon && newState == STATE_AUTHENTICATED) {
+        } else if (lastStateIsErrorIcon && newState == BiometricState.STATE_AUTHENTICATED) {
             animateIconOnce(R.drawable.face_dialog_dark_to_checkmark)
             iconView.contentDescription = context.getString(
                     R.string.biometric_dialog_face_icon_description_authenticated
             )
-        } else if (newState == STATE_ERROR && oldState != STATE_ERROR) {
+        } else if (newState == BiometricState.STATE_ERROR && oldState != BiometricState.STATE_ERROR) {
             animateIconOnce(R.drawable.face_dialog_dark_to_error)
             iconView.contentDescription = context.getString(
                     R.string.keyguard_face_failed
             )
-        } else if (oldState == STATE_AUTHENTICATING && newState == STATE_AUTHENTICATED) {
+        } else if (oldState == BiometricState.STATE_AUTHENTICATING && newState == BiometricState.STATE_AUTHENTICATED) {
             animateIconOnce(R.drawable.face_dialog_dark_to_checkmark)
             iconView.contentDescription = context.getString(
                     R.string.biometric_dialog_face_icon_description_authenticated
             )
-        } else if (newState == STATE_PENDING_CONFIRMATION) {
+        } else if (newState == BiometricState.STATE_PENDING_CONFIRMATION) {
             animateIconOnce(R.drawable.face_dialog_wink_from_dark)
             iconView.contentDescription = context.getString(
                     R.string.biometric_dialog_face_icon_description_authenticated
             )
-        } else if (newState == STATE_IDLE) {
+        } else if (newState == BiometricState.STATE_IDLE) {
             showStaticDrawable(R.drawable.face_dialog_idle_static)
             iconView.contentDescription = context.getString(
                     R.string.biometric_dialog_face_icon_description_idle
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFaceView.kt b/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFaceView.kt
deleted file mode 100644
index be89d10..0000000
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFaceView.kt
+++ /dev/null
@@ -1,80 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.systemui.biometrics
-
-import android.content.Context
-import android.hardware.biometrics.BiometricAuthenticator.Modality
-import android.util.AttributeSet
-
-/** Face only view for BiometricPrompt. */
-class AuthBiometricFaceView(
-    context: Context,
-    attrs: AttributeSet? = null
-) : AuthBiometricView(context, attrs) {
-
-    override fun getDelayAfterAuthenticatedDurationMs() = HIDE_DELAY_MS
-
-    override fun getStateForAfterError() = STATE_IDLE
-
-    override fun handleResetAfterError() = resetErrorView()
-
-    override fun handleResetAfterHelp() = resetErrorView()
-
-    override fun supportsSmallDialog() = true
-
-    override fun supportsManualRetry() = true
-
-    override fun supportsRequireConfirmation() = true
-
-    override fun createIconController(): AuthIconController =
-        AuthBiometricFaceIconController(mContext, mIconView)
-
-    override fun updateState(@BiometricState newState: Int) {
-        if (newState == STATE_AUTHENTICATING_ANIMATING_IN ||
-            newState == STATE_AUTHENTICATING && size == AuthDialog.SIZE_MEDIUM) {
-            resetErrorView()
-        }
-
-        // Do this last since the state variable gets updated.
-        super.updateState(newState)
-    }
-
-    override fun onAuthenticationFailed(
-        @Modality modality: Int,
-        failureReason: String?
-    ) {
-        if (size == AuthDialog.SIZE_MEDIUM) {
-            if (supportsManualRetry()) {
-                mTryAgainButton.visibility = VISIBLE
-                mConfirmButton.visibility = GONE
-            }
-        }
-
-        // Do this last since we want to know if the button is being animated (in the case of
-        // small -> medium dialog)
-        super.onAuthenticationFailed(modality, failureReason)
-    }
-
-    private fun resetErrorView() {
-        mIndicatorView.setTextColor(mTextColorHint)
-        mIndicatorView.visibility = INVISIBLE
-    }
-
-    companion object {
-        /** Delay before dismissing after being authenticated/confirmed. */
-        const val HIDE_DELAY_MS = 500
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFingerprintAndFaceIconController.kt b/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFingerprintAndFaceIconController.kt
index 95610ae..fb22c6b 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFingerprintAndFaceIconController.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFingerprintAndFaceIconController.kt
@@ -20,11 +20,11 @@
 import android.content.Context
 import com.airbnb.lottie.LottieAnimationView
 import com.android.systemui.R
-import com.android.systemui.biometrics.AuthBiometricView.BiometricState
-import com.android.systemui.biometrics.AuthBiometricView.STATE_AUTHENTICATED
-import com.android.systemui.biometrics.AuthBiometricView.STATE_ERROR
-import com.android.systemui.biometrics.AuthBiometricView.STATE_HELP
-import com.android.systemui.biometrics.AuthBiometricView.STATE_PENDING_CONFIRMATION
+import com.android.systemui.biometrics.ui.binder.Spaghetti.BiometricState
+import com.android.systemui.biometrics.ui.binder.Spaghetti.BiometricState.STATE_AUTHENTICATED
+import com.android.systemui.biometrics.ui.binder.Spaghetti.BiometricState.STATE_ERROR
+import com.android.systemui.biometrics.ui.binder.Spaghetti.BiometricState.STATE_HELP
+import com.android.systemui.biometrics.ui.binder.Spaghetti.BiometricState.STATE_PENDING_CONFIRMATION
 
 /** Face/Fingerprint combined icon animator for BiometricPrompt. */
 open class AuthBiometricFingerprintAndFaceIconController(
@@ -36,8 +36,8 @@
     override val actsAsConfirmButton: Boolean = true
 
     override fun shouldAnimateIconViewForTransition(
-        @BiometricState oldState: Int,
-        @BiometricState newState: Int
+            oldState: BiometricState,
+            newState: BiometricState
     ): Boolean = when (newState) {
         STATE_PENDING_CONFIRMATION -> true
         else -> super.shouldAnimateIconViewForTransition(oldState, newState)
@@ -45,8 +45,8 @@
 
     @RawRes
     override fun getAnimationForTransition(
-        @BiometricState oldState: Int,
-        @BiometricState newState: Int
+        oldState: BiometricState,
+        newState: BiometricState
     ): Int? = when (newState) {
         STATE_AUTHENTICATED -> {
            if (oldState == STATE_PENDING_CONFIRMATION) {
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFingerprintAndFaceView.kt b/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFingerprintAndFaceView.kt
deleted file mode 100644
index 7ce74db..0000000
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFingerprintAndFaceView.kt
+++ /dev/null
@@ -1,58 +0,0 @@
-/*
- * Copyright (C) 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.biometrics
-
-import android.content.Context
-import android.hardware.biometrics.BiometricAuthenticator.Modality
-import android.hardware.biometrics.BiometricAuthenticator.TYPE_FACE
-import android.hardware.biometrics.BiometricConstants
-import android.hardware.face.FaceManager
-import android.util.AttributeSet
-import com.android.systemui.R
-
-/** Face/Fingerprint combined view for BiometricPrompt. */
-class AuthBiometricFingerprintAndFaceView(
-    context: Context,
-    attrs: AttributeSet?
-) : AuthBiometricFingerprintView(context, attrs) {
-    var isFaceClass3 = false
-
-    constructor (context: Context) : this(context, null)
-
-    override fun getConfirmationPrompt() = R.string.biometric_dialog_tap_confirm_with_face
-
-    override fun forceRequireConfirmation(@Modality modality: Int) = modality == TYPE_FACE
-
-    override fun ignoreUnsuccessfulEventsFrom(@Modality modality: Int, unsuccessfulReason: String) =
-        modality == TYPE_FACE && !(isFaceClass3 && isLockoutErrorString(unsuccessfulReason))
-
-    override fun createIconController(): AuthIconController =
-        AuthBiometricFingerprintAndFaceIconController(mContext, mIconView, mIconViewOverlay)
-
-    override fun isCoex() = true
-
-    private fun isLockoutErrorString(unsuccessfulReason: String) =
-        unsuccessfulReason == FaceManager.getErrorString(
-            mContext,
-            BiometricConstants.BIOMETRIC_ERROR_LOCKOUT,
-            0 /*vendorCode */
-        ) || unsuccessfulReason == FaceManager.getErrorString(
-            mContext,
-            BiometricConstants.BIOMETRIC_ERROR_LOCKOUT_PERMANENT,
-            0 /*vendorCode */
-        )
-}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFingerprintIconController.kt b/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFingerprintIconController.kt
index d82f458..683541b 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFingerprintIconController.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFingerprintIconController.kt
@@ -27,14 +27,15 @@
 import com.airbnb.lottie.LottieAnimationView
 import com.android.settingslib.widget.LottieColorUtils
 import com.android.systemui.R
-import com.android.systemui.biometrics.AuthBiometricView.BiometricState
-import com.android.systemui.biometrics.AuthBiometricView.STATE_AUTHENTICATED
-import com.android.systemui.biometrics.AuthBiometricView.STATE_AUTHENTICATING
-import com.android.systemui.biometrics.AuthBiometricView.STATE_AUTHENTICATING_ANIMATING_IN
-import com.android.systemui.biometrics.AuthBiometricView.STATE_ERROR
-import com.android.systemui.biometrics.AuthBiometricView.STATE_HELP
-import com.android.systemui.biometrics.AuthBiometricView.STATE_IDLE
-import com.android.systemui.biometrics.AuthBiometricView.STATE_PENDING_CONFIRMATION
+import com.android.systemui.biometrics.ui.binder.Spaghetti.BiometricState
+import com.android.systemui.biometrics.ui.binder.Spaghetti.BiometricState.STATE_AUTHENTICATED
+import com.android.systemui.biometrics.ui.binder.Spaghetti.BiometricState.STATE_AUTHENTICATING
+import com.android.systemui.biometrics.ui.binder.Spaghetti.BiometricState.STATE_AUTHENTICATING_ANIMATING_IN
+import com.android.systemui.biometrics.ui.binder.Spaghetti.BiometricState.STATE_ERROR
+import com.android.systemui.biometrics.ui.binder.Spaghetti.BiometricState.STATE_HELP
+import com.android.systemui.biometrics.ui.binder.Spaghetti.BiometricState.STATE_IDLE
+import com.android.systemui.biometrics.ui.binder.Spaghetti.BiometricState.STATE_PENDING_CONFIRMATION
+
 
 /** Fingerprint only icon animator for BiometricPrompt.  */
 open class AuthBiometricFingerprintIconController(
@@ -76,7 +77,7 @@
         }
     }
 
-    private fun updateIconSideFps(@BiometricState lastState: Int, @BiometricState newState: Int) {
+    private fun updateIconSideFps(lastState: BiometricState, newState: BiometricState) {
         val displayInfo = DisplayInfo()
         context.display?.getDisplayInfo(displayInfo)
         val rotation = getRotationFromDefault(displayInfo.rotation)
@@ -106,7 +107,7 @@
         LottieColorUtils.applyDynamicColors(context, iconViewOverlay)
     }
 
-    private fun updateIconNormal(@BiometricState lastState: Int, @BiometricState newState: Int) {
+    private fun updateIconNormal(lastState: BiometricState, newState: BiometricState) {
         val icon = getAnimationForTransition(lastState, newState) ?: return
 
         if (!(lastState == STATE_AUTHENTICATING_ANIMATING_IN && newState == STATE_AUTHENTICATING)) {
@@ -125,7 +126,7 @@
         LottieColorUtils.applyDynamicColors(context, iconView)
     }
 
-    override fun updateIcon(@BiometricState lastState: Int, @BiometricState newState: Int) {
+    override fun updateIcon(lastState: BiometricState, newState: BiometricState) {
         if (isSideFps) {
             updateIconSideFps(lastState, newState)
         } else {
@@ -135,7 +136,7 @@
     }
 
     @VisibleForTesting
-    fun getIconContentDescription(@BiometricState newState: Int): CharSequence? {
+    fun getIconContentDescription(newState: BiometricState): CharSequence? {
         val id = when (newState) {
             STATE_IDLE,
             STATE_AUTHENTICATING_ANIMATING_IN,
@@ -160,8 +161,8 @@
     }
 
     protected open fun shouldAnimateIconViewForTransition(
-            @BiometricState oldState: Int,
-            @BiometricState newState: Int
+            oldState: BiometricState,
+            newState: BiometricState
     ) = when (newState) {
         STATE_HELP,
         STATE_ERROR -> true
@@ -172,8 +173,8 @@
     }
 
     private fun shouldAnimateSfpsIconViewForTransition(
-            @BiometricState oldState: Int,
-            @BiometricState newState: Int
+            oldState: BiometricState,
+            newState: BiometricState
     ) = when (newState) {
         STATE_HELP,
         STATE_ERROR -> true
@@ -185,8 +186,8 @@
     }
 
     protected open fun shouldAnimateIconViewOverlayForTransition(
-            @BiometricState oldState: Int,
-            @BiometricState newState: Int
+            oldState: BiometricState,
+            newState: BiometricState
     ) = when (newState) {
         STATE_HELP,
         STATE_ERROR -> true
@@ -198,8 +199,8 @@
 
     @RawRes
     protected open fun getAnimationForTransition(
-            @BiometricState oldState: Int,
-            @BiometricState newState: Int
+            oldState: BiometricState,
+            newState: BiometricState
     ): Int? {
         val id = when (newState) {
             STATE_HELP,
@@ -231,8 +232,8 @@
 
     @RawRes
     private fun getSideFpsOverlayAnimationForTransition(
-            @BiometricState oldState: Int,
-            @BiometricState newState: Int,
+            oldState: BiometricState,
+            newState: BiometricState,
             rotation: Int
     ): Int? = when (newState) {
         STATE_HELP,
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFingerprintView.kt b/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFingerprintView.kt
deleted file mode 100644
index f2e4701..0000000
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricFingerprintView.kt
+++ /dev/null
@@ -1,113 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.systemui.biometrics
-
-import android.content.Context
-import android.hardware.fingerprint.FingerprintSensorPropertiesInternal
-import android.util.AttributeSet
-import android.util.Log
-import android.widget.FrameLayout
-import android.widget.TextView
-import com.android.systemui.R
-import com.android.systemui.biometrics.AuthController.ScaleFactorProvider
-
-private const val TAG = "AuthBiometricFingerprintView"
-
-/** Fingerprint only view for BiometricPrompt.  */
-open class AuthBiometricFingerprintView(
-    context: Context,
-    attrs: AttributeSet? = null
-) : AuthBiometricView(context, attrs) {
-    /** If this view is for a SFPS sensor.  */
-    var isSfps = false
-        private set
-
-    /** If this view is for a UDFPS sensor.  */
-    var isUdfps = false
-        private set
-
-    private var udfpsAdapter: UdfpsDialogMeasureAdapter? = null
-    private var scaleFactorProvider: ScaleFactorProvider? = null
-
-    /** Set the [sensorProps] of this sensor so the view can be customized prior to layout. */
-    fun setSensorProperties(sensorProps: FingerprintSensorPropertiesInternal) {
-        isSfps = sensorProps.isAnySidefpsType
-        isUdfps = sensorProps.isAnyUdfpsType
-        udfpsAdapter = if (isUdfps) UdfpsDialogMeasureAdapter(this, sensorProps) else null
-    }
-
-    fun setScaleFactorProvider(scaleProvider: ScaleFactorProvider?) {
-        scaleFactorProvider = scaleProvider
-    }
-
-    override fun onMeasureInternal(width: Int, height: Int): AuthDialog.LayoutParams {
-        val layoutParams = super.onMeasureInternal(width, height)
-        val scale = scaleFactorProvider?.provide() ?: 1.0f
-        return udfpsAdapter?.onMeasureInternal(width, height, layoutParams,
-            scale) ?: layoutParams
-    }
-
-    override fun onLayout(changed: Boolean, left: Int, top: Int, right: Int, bottom: Int) {
-        super.onLayout(changed, left, top, right, bottom)
-
-        val adapter = udfpsAdapter
-        if (adapter != null) {
-            // Move the UDFPS icon and indicator text if necessary. This probably only needs to happen
-            // for devices where the UDFPS sensor is too low.
-            // TODO(b/201510778): Update this logic to support cases where the sensor or text overlap
-            //  the button bar area.
-            val bottomSpacerHeight = adapter.bottomSpacerHeight
-            Log.w(TAG, "bottomSpacerHeight: $bottomSpacerHeight")
-            if (bottomSpacerHeight < 0) {
-                val iconFrame = findViewById<FrameLayout>(R.id.biometric_icon_frame)!!
-                iconFrame.translationY = -bottomSpacerHeight.toFloat()
-                val indicator = findViewById<TextView>(R.id.indicator)!!
-                indicator.translationY = -bottomSpacerHeight.toFloat()
-            }
-        }
-    }
-
-    override fun getDelayAfterAuthenticatedDurationMs() = 500
-
-    override fun getStateForAfterError() = STATE_AUTHENTICATING
-
-    override fun handleResetAfterError() = showTouchSensorString()
-
-    override fun handleResetAfterHelp() = showTouchSensorString()
-
-    override fun supportsSmallDialog() = false
-
-    override fun createIconController(): AuthIconController =
-        AuthBiometricFingerprintIconController(mContext, mIconView, mIconViewOverlay)
-
-    fun updateOverrideIconLayoutParamsSize() {
-        udfpsAdapter?.let {
-            val sensorDiameter = it.getSensorDiameter(scaleFactorProvider?.provide() ?: 1.0f)
-            (mIconController as? AuthBiometricFingerprintIconController)?.iconLayoutParamSize =
-                    Pair(sensorDiameter, sensorDiameter)
-        }
-    }
-
-    override fun onAttachedToWindow() {
-        super.onAttachedToWindow()
-        showTouchSensorString()
-    }
-
-    private fun showTouchSensorString() {
-        mIndicatorView.setText(R.string.fingerprint_dialog_touch_sensor)
-        mIndicatorView.setTextColor(mTextColorHint)
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricView.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricView.java
deleted file mode 100644
index ed4b91c..0000000
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricView.java
+++ /dev/null
@@ -1,984 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.biometrics;
-
-import static android.hardware.biometrics.BiometricAuthenticator.TYPE_NONE;
-
-import android.animation.Animator;
-import android.animation.AnimatorListenerAdapter;
-import android.animation.AnimatorSet;
-import android.animation.ValueAnimator;
-import android.annotation.IntDef;
-import android.annotation.NonNull;
-import android.annotation.Nullable;
-import android.annotation.StringRes;
-import android.content.Context;
-import android.content.res.Configuration;
-import android.graphics.Insets;
-import android.hardware.biometrics.BiometricAuthenticator.Modality;
-import android.hardware.biometrics.BiometricPrompt;
-import android.hardware.biometrics.PromptInfo;
-import android.os.Bundle;
-import android.os.Handler;
-import android.os.Looper;
-import android.text.TextUtils;
-import android.text.method.ScrollingMovementMethod;
-import android.util.AttributeSet;
-import android.util.Log;
-import android.view.View;
-import android.view.ViewGroup;
-import android.view.accessibility.AccessibilityManager;
-import android.widget.Button;
-import android.widget.LinearLayout;
-import android.widget.TextView;
-
-import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.widget.LockPatternUtils;
-import com.android.systemui.R;
-
-import com.airbnb.lottie.LottieAnimationView;
-
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-import java.util.ArrayList;
-import java.util.List;
-
-/**
- * Contains the Biometric views (title, subtitle, icon, buttons, etc.) and its controllers.
- */
-public abstract class AuthBiometricView extends LinearLayout implements AuthBiometricViewAdapter {
-
-    private static final String TAG = "AuthBiometricView";
-
-    /**
-     * Authentication hardware idle.
-     */
-    public static final int STATE_IDLE = 0;
-    /**
-     * UI animating in, authentication hardware active.
-     */
-    public static final int STATE_AUTHENTICATING_ANIMATING_IN = 1;
-    /**
-     * UI animated in, authentication hardware active.
-     */
-    public static final int STATE_AUTHENTICATING = 2;
-    /**
-     * UI animated in, authentication hardware active.
-     */
-    public static final int STATE_HELP = 3;
-    /**
-     * Hard error, e.g. ERROR_TIMEOUT. Authentication hardware idle.
-     */
-    public static final int STATE_ERROR = 4;
-    /**
-     * Authenticated, waiting for user confirmation. Authentication hardware idle.
-     */
-    public static final int STATE_PENDING_CONFIRMATION = 5;
-    /**
-     * Authenticated, dialog animating away soon.
-     */
-    public static final int STATE_AUTHENTICATED = 6;
-
-    @Retention(RetentionPolicy.SOURCE)
-    @IntDef({STATE_IDLE, STATE_AUTHENTICATING_ANIMATING_IN, STATE_AUTHENTICATING, STATE_HELP,
-            STATE_ERROR, STATE_PENDING_CONFIRMATION, STATE_AUTHENTICATED})
-    @interface BiometricState {}
-
-    /**
-     * Callback to the parent when a user action has occurred.
-     */
-    public interface Callback {
-        int ACTION_AUTHENTICATED = 1;
-        int ACTION_USER_CANCELED = 2;
-        int ACTION_BUTTON_NEGATIVE = 3;
-        int ACTION_BUTTON_TRY_AGAIN = 4;
-        int ACTION_ERROR = 5;
-        int ACTION_USE_DEVICE_CREDENTIAL = 6;
-        int ACTION_START_DELAYED_FINGERPRINT_SENSOR = 7;
-        int ACTION_AUTHENTICATED_AND_CONFIRMED = 8;
-
-        /**
-         * When an action has occurred. The caller will only invoke this when the callback should
-         * be propagated. e.g. the caller will handle any necessary delay.
-         * @param action
-         */
-        void onAction(int action);
-    }
-
-    private final Handler mHandler;
-    private final AccessibilityManager mAccessibilityManager;
-    private final LockPatternUtils mLockPatternUtils;
-    protected final int mTextColorError;
-    protected final int mTextColorHint;
-
-    private AuthPanelController mPanelController;
-
-    private PromptInfo mPromptInfo;
-    private boolean mRequireConfirmation;
-    private int mUserId;
-    private int mEffectiveUserId;
-    private @AuthDialog.DialogSize int mSize = AuthDialog.SIZE_UNKNOWN;
-
-    private TextView mTitleView;
-    private TextView mSubtitleView;
-    private TextView mDescriptionView;
-    private View mIconHolderView;
-    protected LottieAnimationView mIconViewOverlay;
-    protected LottieAnimationView mIconView;
-    protected TextView mIndicatorView;
-
-    @VisibleForTesting @NonNull AuthIconController mIconController;
-    @VisibleForTesting int mAnimationDurationShort = AuthDialog.ANIMATE_SMALL_TO_MEDIUM_DURATION_MS;
-    @VisibleForTesting int mAnimationDurationLong = AuthDialog.ANIMATE_MEDIUM_TO_LARGE_DURATION_MS;
-    @VisibleForTesting int mAnimationDurationHideDialog = BiometricPrompt.HIDE_DIALOG_DELAY;
-
-    // Negative button position, exclusively for the app-specified behavior
-    @VisibleForTesting Button mNegativeButton;
-    // Negative button position, exclusively for cancelling auth after passive auth success
-    @VisibleForTesting Button mCancelButton;
-    // Negative button position, shown if device credentials are allowed
-    @VisibleForTesting Button mUseCredentialButton;
-
-    // Positive button position,
-    @VisibleForTesting Button mConfirmButton;
-    @VisibleForTesting Button mTryAgainButton;
-
-    // Measurements when biometric view is showing text, buttons, etc.
-    @Nullable @VisibleForTesting AuthDialog.LayoutParams mLayoutParams;
-
-    private Callback mCallback;
-    @BiometricState private int mState;
-
-    private float mIconOriginalY;
-
-    protected boolean mDialogSizeAnimating;
-    protected Bundle mSavedState;
-
-    private final Runnable mResetErrorRunnable;
-    private final Runnable mResetHelpRunnable;
-
-    private Animator.AnimatorListener mJankListener;
-
-    private final boolean mUseCustomBpSize;
-    private final int mCustomBpWidth;
-    private final int mCustomBpHeight;
-
-    private final OnClickListener mBackgroundClickListener = (view) -> {
-        if (mState == STATE_AUTHENTICATED) {
-            Log.w(TAG, "Ignoring background click after authenticated");
-            return;
-        } else if (mSize == AuthDialog.SIZE_SMALL) {
-            Log.w(TAG, "Ignoring background click during small dialog");
-            return;
-        } else if (mSize == AuthDialog.SIZE_LARGE) {
-            Log.w(TAG, "Ignoring background click during large dialog");
-            return;
-        }
-        mCallback.onAction(Callback.ACTION_USER_CANCELED);
-    };
-
-    public AuthBiometricView(Context context) {
-        this(context, null);
-    }
-
-    public AuthBiometricView(Context context, AttributeSet attrs) {
-        super(context, attrs);
-        mHandler = new Handler(Looper.getMainLooper());
-        mTextColorError = getResources().getColor(
-                R.color.biometric_dialog_error, context.getTheme());
-        mTextColorHint = getResources().getColor(
-                R.color.biometric_dialog_gray, context.getTheme());
-
-        mAccessibilityManager = context.getSystemService(AccessibilityManager.class);
-        mLockPatternUtils = new LockPatternUtils(context);
-
-        mResetErrorRunnable = () -> {
-            updateState(getStateForAfterError());
-            handleResetAfterError();
-            Utils.notifyAccessibilityContentChanged(mAccessibilityManager, this);
-        };
-
-        mResetHelpRunnable = () -> {
-            updateState(STATE_AUTHENTICATING);
-            handleResetAfterHelp();
-            Utils.notifyAccessibilityContentChanged(mAccessibilityManager, this);
-        };
-
-        mUseCustomBpSize = getResources().getBoolean(R.bool.use_custom_bp_size);
-        mCustomBpWidth = getResources().getDimensionPixelSize(R.dimen.biometric_dialog_width);
-        mCustomBpHeight = getResources().getDimensionPixelSize(R.dimen.biometric_dialog_height);
-    }
-
-    /** Delay after authentication is confirmed, before the dialog should be animated away. */
-    protected int getDelayAfterAuthenticatedDurationMs() {
-        return 0;
-    }
-
-    /** State that the dialog/icon should be in after showing a help message. */
-    protected int getStateForAfterError() {
-        return STATE_IDLE;
-    }
-
-    /** Invoked when the error message is being cleared. */
-    protected void handleResetAfterError() {}
-
-    /** Invoked when the help message is being cleared. */
-    protected void handleResetAfterHelp() {}
-
-    /** True if the dialog supports {@link AuthDialog.DialogSize#SIZE_SMALL}. */
-    protected boolean supportsSmallDialog() {
-        return false;
-    }
-
-    /** The string to show when the user must tap to confirm via the button or icon. */
-    @StringRes
-    protected int getConfirmationPrompt() {
-        return R.string.biometric_dialog_tap_confirm;
-    }
-
-    /** True if require confirmation will be honored when set via the API. */
-    protected boolean supportsRequireConfirmation() {
-        return false;
-    }
-
-    /** True if confirmation will be required even if it was not supported/requested. */
-    protected boolean forceRequireConfirmation(@Modality int modality) {
-        return false;
-    }
-
-    /** Ignore all events from this (secondary) modality except successful authentication. */
-    protected boolean ignoreUnsuccessfulEventsFrom(@Modality int modality,
-            String unsuccessfulReason) {
-        return false;
-    }
-
-    /** Create the controller for managing the icons transitions during the prompt.*/
-    @NonNull
-    protected abstract AuthIconController createIconController();
-
-    @Override
-    public AuthIconController getLegacyIconController() {
-        return mIconController;
-    }
-
-    @Override
-    public void cancelAnimation() {
-        animate().cancel();
-    }
-
-    @Override
-    public View asView() {
-        return this;
-    }
-
-    @Override
-    public boolean isCoex() {
-        return false;
-    }
-
-    void setPanelController(AuthPanelController panelController) {
-        mPanelController = panelController;
-    }
-    void setPromptInfo(PromptInfo promptInfo) {
-        mPromptInfo = promptInfo;
-    }
-
-    void setCallback(Callback callback) {
-        mCallback = callback;
-    }
-
-    void setBackgroundView(View backgroundView) {
-        backgroundView.setOnClickListener(mBackgroundClickListener);
-    }
-
-    void setUserId(int userId) {
-        mUserId = userId;
-    }
-
-    void setEffectiveUserId(int effectiveUserId) {
-        mEffectiveUserId = effectiveUserId;
-    }
-
-    void setRequireConfirmation(boolean requireConfirmation) {
-        mRequireConfirmation = requireConfirmation && supportsRequireConfirmation();
-    }
-
-    void setJankListener(Animator.AnimatorListener jankListener) {
-        mJankListener = jankListener;
-    }
-
-    private void updatePaddings(int size) {
-        final Insets navBarInsets = Utils.getNavbarInsets(mContext);
-        if (size != AuthDialog.SIZE_LARGE) {
-            if (mPanelController.getPosition() == AuthPanelController.POSITION_LEFT) {
-                setPadding(navBarInsets.left, 0, 0, 0);
-            } else if (mPanelController.getPosition() == AuthPanelController.POSITION_RIGHT) {
-                setPadding(0, 0, navBarInsets.right, 0);
-            } else {
-                setPadding(0, 0, 0, navBarInsets.bottom);
-            }
-        } else {
-            setPadding(0, 0, 0, 0);
-        }
-    }
-
-    @VisibleForTesting
-    final void updateSize(@AuthDialog.DialogSize int newSize) {
-        Log.v(TAG, "Current size: " + mSize + " New size: " + newSize);
-        updatePaddings(newSize);
-        if (newSize == AuthDialog.SIZE_SMALL) {
-            mTitleView.setVisibility(View.GONE);
-            mSubtitleView.setVisibility(View.GONE);
-            mDescriptionView.setVisibility(View.GONE);
-            mIndicatorView.setVisibility(View.GONE);
-            mNegativeButton.setVisibility(View.GONE);
-            mUseCredentialButton.setVisibility(View.GONE);
-
-            final float iconPadding = getResources()
-                    .getDimension(R.dimen.biometric_dialog_icon_padding);
-            mIconHolderView.setY(getHeight() - mIconHolderView.getHeight() - iconPadding);
-
-            // Subtract the vertical padding from the new height since it's only used to create
-            // extra space between the other elements, and not part of the actual icon.
-            final int newHeight = mIconHolderView.getHeight() + 2 * (int) iconPadding
-                    - mIconHolderView.getPaddingTop() - mIconHolderView.getPaddingBottom();
-            mPanelController.updateForContentDimensions(mLayoutParams.mMediumWidth, newHeight,
-                    0 /* animateDurationMs */);
-
-            mSize = newSize;
-        } else if (mSize == AuthDialog.SIZE_SMALL && newSize == AuthDialog.SIZE_MEDIUM) {
-            if (mDialogSizeAnimating) {
-                return;
-            }
-            mDialogSizeAnimating = true;
-
-            // Animate the icon back to original position
-            final ValueAnimator iconAnimator =
-                    ValueAnimator.ofFloat(mIconHolderView.getY(), mIconOriginalY);
-            iconAnimator.addUpdateListener((animation) -> {
-                mIconHolderView.setY((float) animation.getAnimatedValue());
-            });
-
-            // Animate the text
-            final ValueAnimator opacityAnimator = ValueAnimator.ofFloat(0, 1);
-            opacityAnimator.addUpdateListener((animation) -> {
-                final float opacity = (float) animation.getAnimatedValue();
-                mTitleView.setAlpha(opacity);
-                mIndicatorView.setAlpha(opacity);
-                mNegativeButton.setAlpha(opacity);
-                mCancelButton.setAlpha(opacity);
-                mTryAgainButton.setAlpha(opacity);
-
-                if (!TextUtils.isEmpty(mSubtitleView.getText())) {
-                    mSubtitleView.setAlpha(opacity);
-                }
-                if (!TextUtils.isEmpty(mDescriptionView.getText())) {
-                    mDescriptionView.setAlpha(opacity);
-                }
-            });
-
-            // Choreograph together
-            final AnimatorSet as = new AnimatorSet();
-            as.setDuration(mAnimationDurationShort);
-            as.addListener(new AnimatorListenerAdapter() {
-                @Override
-                public void onAnimationStart(Animator animation) {
-                    super.onAnimationStart(animation);
-                    mTitleView.setVisibility(View.VISIBLE);
-                    mIndicatorView.setVisibility(View.VISIBLE);
-
-                    if (isDeviceCredentialAllowed()) {
-                        mUseCredentialButton.setVisibility(View.VISIBLE);
-                    } else {
-                        mNegativeButton.setVisibility(View.VISIBLE);
-                    }
-                    if (supportsManualRetry()) {
-                        mTryAgainButton.setVisibility(View.VISIBLE);
-                    }
-
-                    if (!TextUtils.isEmpty(mSubtitleView.getText())) {
-                        mSubtitleView.setVisibility(View.VISIBLE);
-                    }
-                    if (!TextUtils.isEmpty(mDescriptionView.getText())) {
-                        mDescriptionView.setVisibility(View.VISIBLE);
-                    }
-                }
-                @Override
-                public void onAnimationEnd(Animator animation) {
-                    super.onAnimationEnd(animation);
-                    mSize = newSize;
-                    mDialogSizeAnimating = false;
-                    Utils.notifyAccessibilityContentChanged(mAccessibilityManager,
-                            AuthBiometricView.this);
-                }
-            });
-
-            if (mJankListener != null) {
-                as.addListener(mJankListener);
-            }
-            as.play(iconAnimator).with(opacityAnimator);
-            as.start();
-            // Animate the panel
-            mPanelController.updateForContentDimensions(mLayoutParams.mMediumWidth,
-                    mLayoutParams.mMediumHeight,
-                    AuthDialog.ANIMATE_SMALL_TO_MEDIUM_DURATION_MS);
-        } else if (newSize == AuthDialog.SIZE_MEDIUM) {
-            mPanelController.updateForContentDimensions(mLayoutParams.mMediumWidth,
-                    mLayoutParams.mMediumHeight,
-                    0 /* animateDurationMs */);
-            mSize = newSize;
-        } else if (newSize == AuthDialog.SIZE_LARGE) {
-            final float translationY = getResources().getDimension(
-                            R.dimen.biometric_dialog_medium_to_large_translation_offset);
-            final AuthBiometricView biometricView = this;
-
-            // Translate at full duration
-            final ValueAnimator translationAnimator = ValueAnimator.ofFloat(
-                    biometricView.getY(), biometricView.getY() - translationY);
-            translationAnimator.setDuration(mAnimationDurationLong);
-            translationAnimator.addUpdateListener((animation) -> {
-                final float translation = (float) animation.getAnimatedValue();
-                biometricView.setTranslationY(translation);
-            });
-            translationAnimator.addListener(new AnimatorListenerAdapter() {
-                @Override
-                public void onAnimationEnd(Animator animation) {
-                    super.onAnimationEnd(animation);
-                    if (biometricView.getParent() instanceof ViewGroup) {
-                        ((ViewGroup) biometricView.getParent()).removeView(biometricView);
-                    }
-                    mSize = newSize;
-                }
-            });
-
-            // Opacity to 0 in half duration
-            final ValueAnimator opacityAnimator = ValueAnimator.ofFloat(1, 0);
-            opacityAnimator.setDuration(mAnimationDurationLong / 2);
-            opacityAnimator.addUpdateListener((animation) -> {
-                final float opacity = (float) animation.getAnimatedValue();
-                biometricView.setAlpha(opacity);
-            });
-
-            mPanelController.setUseFullScreen(true);
-            mPanelController.updateForContentDimensions(
-                    mPanelController.getContainerWidth(),
-                    mPanelController.getContainerHeight(),
-                    mAnimationDurationLong);
-
-            // Start the animations together
-            AnimatorSet as = new AnimatorSet();
-            List<Animator> animators = new ArrayList<>();
-            animators.add(translationAnimator);
-            animators.add(opacityAnimator);
-
-            if (mJankListener != null) {
-                as.addListener(mJankListener);
-            }
-            as.playTogether(animators);
-            as.setDuration(mAnimationDurationLong * 2 / 3);
-            as.start();
-        } else {
-            Log.e(TAG, "Unknown transition from: " + mSize + " to: " + newSize);
-        }
-        Utils.notifyAccessibilityContentChanged(mAccessibilityManager, this);
-    }
-
-    protected boolean supportsManualRetry() {
-        return false;
-    }
-
-    /**
-     * Updates mIconView animation on updates to fold state, device rotation, or rear display mode
-     * @param animation new asset to use for iconw
-     */
-    public void updateIconViewAnimation(int animation) {
-        mIconView.setAnimation(animation);
-    }
-
-    public void updateState(@BiometricState int newState) {
-        Log.d(TAG, "newState: " + newState);
-
-        mIconController.updateState(mState, newState);
-
-        switch (newState) {
-            case STATE_AUTHENTICATING_ANIMATING_IN:
-            case STATE_AUTHENTICATING:
-                removePendingAnimations();
-                if (mRequireConfirmation) {
-                    mConfirmButton.setEnabled(false);
-                    mConfirmButton.setVisibility(View.VISIBLE);
-                }
-                break;
-
-            case STATE_AUTHENTICATED:
-                removePendingAnimations();
-                if (mSize != AuthDialog.SIZE_SMALL) {
-                    mConfirmButton.setVisibility(View.GONE);
-                    mNegativeButton.setVisibility(View.GONE);
-                    mUseCredentialButton.setVisibility(View.GONE);
-                    mCancelButton.setVisibility(View.GONE);
-                    mIndicatorView.setVisibility(View.INVISIBLE);
-                }
-                announceForAccessibility(getResources()
-                        .getString(R.string.biometric_dialog_authenticated));
-                if (mState == STATE_PENDING_CONFIRMATION) {
-                    mHandler.postDelayed(() -> mCallback.onAction(
-                            Callback.ACTION_AUTHENTICATED_AND_CONFIRMED),
-                            getDelayAfterAuthenticatedDurationMs());
-                } else {
-                    mHandler.postDelayed(() -> mCallback.onAction(Callback.ACTION_AUTHENTICATED),
-                            getDelayAfterAuthenticatedDurationMs());
-                }
-                break;
-
-            case STATE_PENDING_CONFIRMATION:
-                removePendingAnimations();
-                mNegativeButton.setVisibility(View.GONE);
-                mCancelButton.setVisibility(View.VISIBLE);
-                mUseCredentialButton.setVisibility(View.GONE);
-                // forced confirmations (multi-sensor) use the icon view as the confirm button
-                mConfirmButton.setEnabled(mRequireConfirmation);
-                mConfirmButton.setVisibility(mRequireConfirmation ? View.VISIBLE : View.GONE);
-                mIndicatorView.setTextColor(mTextColorHint);
-                mIndicatorView.setText(getConfirmationPrompt());
-                mIndicatorView.setVisibility(View.VISIBLE);
-                break;
-
-            case STATE_ERROR:
-                if (mSize == AuthDialog.SIZE_SMALL) {
-                    updateSize(AuthDialog.SIZE_MEDIUM);
-                }
-                break;
-
-            default:
-                Log.w(TAG, "Unhandled state: " + newState);
-                break;
-        }
-
-        Utils.notifyAccessibilityContentChanged(mAccessibilityManager, this);
-        mState = newState;
-    }
-
-    public void onOrientationChanged() {
-        // Update padding and AuthPanel outline by calling updateSize when the orientation changed.
-        updateSize(mSize);
-    }
-
-    public void onDialogAnimatedIn(boolean fingerprintWasStarted) {
-        updateState(STATE_AUTHENTICATING);
-    }
-
-    public void onAuthenticationSucceeded(@Modality int modality) {
-        removePendingAnimations();
-        if (mRequireConfirmation || forceRequireConfirmation(modality)) {
-            updateState(STATE_PENDING_CONFIRMATION);
-        } else {
-            updateState(STATE_AUTHENTICATED);
-        }
-    }
-
-    /**
-     * Notify the view that auth has failed.
-     *
-     * @param modality sensor modality that failed
-     * @param failureReason message
-     */
-    public void onAuthenticationFailed(
-            @Modality int modality, @Nullable String failureReason) {
-        if (ignoreUnsuccessfulEventsFrom(modality, failureReason)) {
-            return;
-        }
-
-        showTemporaryMessage(failureReason, mResetErrorRunnable);
-        updateState(STATE_ERROR);
-    }
-
-    /**
-     * Notify the view that an error occurred.
-     *
-     * @param modality sensor modality that failed
-     * @param error message
-     */
-    public void onError(@Modality int modality, String error) {
-        if (ignoreUnsuccessfulEventsFrom(modality, error)) {
-            return;
-        }
-
-        showTemporaryMessage(error, mResetErrorRunnable);
-        updateState(STATE_ERROR);
-
-        mHandler.postDelayed(() -> mCallback.onAction(Callback.ACTION_ERROR),
-                mAnimationDurationHideDialog);
-    }
-
-    /**
-     * Show a help message to the user.
-     *
-     * @param modality sensor modality
-     * @param help message
-     */
-    public void onHelp(@Modality int modality, String help) {
-        if (ignoreUnsuccessfulEventsFrom(modality, help)) {
-            return;
-        }
-        if (mSize != AuthDialog.SIZE_MEDIUM) {
-            Log.w(TAG, "Help received in size: " + mSize);
-            return;
-        }
-        if (TextUtils.isEmpty(help)) {
-            Log.w(TAG, "Ignoring blank help message");
-            return;
-        }
-
-        showTemporaryMessage(help, mResetHelpRunnable);
-        updateState(STATE_HELP);
-    }
-
-    public void onSaveState(@NonNull Bundle outState) {
-        outState.putInt(AuthDialog.KEY_BIOMETRIC_CONFIRM_VISIBILITY,
-                mConfirmButton.getVisibility());
-        outState.putInt(AuthDialog.KEY_BIOMETRIC_TRY_AGAIN_VISIBILITY,
-                mTryAgainButton.getVisibility());
-        outState.putInt(AuthDialog.KEY_BIOMETRIC_STATE, mState);
-        outState.putString(AuthDialog.KEY_BIOMETRIC_INDICATOR_STRING,
-                mIndicatorView.getText() != null ? mIndicatorView.getText().toString() : "");
-        outState.putBoolean(AuthDialog.KEY_BIOMETRIC_INDICATOR_ERROR_SHOWING,
-                mHandler.hasCallbacks(mResetErrorRunnable));
-        outState.putBoolean(AuthDialog.KEY_BIOMETRIC_INDICATOR_HELP_SHOWING,
-                mHandler.hasCallbacks(mResetHelpRunnable));
-        outState.putInt(AuthDialog.KEY_BIOMETRIC_DIALOG_SIZE, mSize);
-    }
-
-    /**
-     * Invoked after inflation but before being attached to window.
-     * @param savedState
-     */
-    public void restoreState(@Nullable Bundle savedState) {
-        mSavedState = savedState;
-    }
-    private void setTextOrHide(TextView view, CharSequence charSequence) {
-        if (TextUtils.isEmpty(charSequence)) {
-            view.setVisibility(View.GONE);
-        } else {
-            view.setText(charSequence);
-        }
-
-        Utils.notifyAccessibilityContentChanged(mAccessibilityManager, this);
-    }
-
-    // Remove all pending icon and text animations
-    private void removePendingAnimations() {
-        mHandler.removeCallbacks(mResetHelpRunnable);
-        mHandler.removeCallbacks(mResetErrorRunnable);
-    }
-
-    private void showTemporaryMessage(String message, Runnable resetMessageRunnable) {
-        removePendingAnimations();
-        mIndicatorView.setText(message);
-        mIndicatorView.setTextColor(mTextColorError);
-        mIndicatorView.setVisibility(View.VISIBLE);
-        // select to enable marquee unless a screen reader is enabled
-        mIndicatorView.setSelected(!mAccessibilityManager.isEnabled()
-                || !mAccessibilityManager.isTouchExplorationEnabled());
-        mHandler.postDelayed(resetMessageRunnable, mAnimationDurationHideDialog);
-
-        Utils.notifyAccessibilityContentChanged(mAccessibilityManager, this);
-    }
-
-    @Override
-    protected void onConfigurationChanged(Configuration newConfig) {
-        super.onConfigurationChanged(newConfig);
-        if (mSavedState != null) {
-            updateState(mSavedState.getInt(AuthDialog.KEY_BIOMETRIC_STATE));
-        }
-    }
-
-    @Override
-    protected void onFinishInflate() {
-        super.onFinishInflate();
-
-        mTitleView = findViewById(R.id.title);
-        mSubtitleView = findViewById(R.id.subtitle);
-        mDescriptionView = findViewById(R.id.description);
-        mIconViewOverlay = findViewById(R.id.biometric_icon_overlay);
-        mIconView = findViewById(R.id.biometric_icon);
-        mIconHolderView = findViewById(R.id.biometric_icon_frame);
-        mIndicatorView = findViewById(R.id.indicator);
-
-        // Negative-side (left) buttons
-        mNegativeButton = findViewById(R.id.button_negative);
-        mCancelButton = findViewById(R.id.button_cancel);
-        mUseCredentialButton = findViewById(R.id.button_use_credential);
-
-        // Positive-side (right) buttons
-        mConfirmButton = findViewById(R.id.button_confirm);
-        mTryAgainButton = findViewById(R.id.button_try_again);
-
-        mNegativeButton.setOnClickListener((view) -> {
-            mCallback.onAction(Callback.ACTION_BUTTON_NEGATIVE);
-        });
-
-        mCancelButton.setOnClickListener((view) -> {
-            mCallback.onAction(Callback.ACTION_USER_CANCELED);
-        });
-
-        mUseCredentialButton.setOnClickListener((view) -> {
-            startTransitionToCredentialUI(false /* isError */);
-        });
-
-        mConfirmButton.setOnClickListener((view) -> {
-            updateState(STATE_AUTHENTICATED);
-        });
-
-        mTryAgainButton.setOnClickListener((view) -> {
-            updateState(STATE_AUTHENTICATING);
-            mCallback.onAction(Callback.ACTION_BUTTON_TRY_AGAIN);
-            mTryAgainButton.setVisibility(View.GONE);
-            Utils.notifyAccessibilityContentChanged(mAccessibilityManager, this);
-        });
-
-        mIconController = createIconController();
-        if (mIconController.getActsAsConfirmButton()) {
-            mIconViewOverlay.setOnClickListener((view)->{
-                if (mState == STATE_PENDING_CONFIRMATION) {
-                    updateState(STATE_AUTHENTICATED);
-                }
-            });
-            mIconView.setOnClickListener((view) -> {
-                if (mState == STATE_PENDING_CONFIRMATION) {
-                    updateState(STATE_AUTHENTICATED);
-                }
-            });
-        }
-    }
-
-    /**
-     * Kicks off the animation process and invokes the callback.
-     *
-     * @param isError if this was triggered due to an error and not a user action (unused,
-     *                previously for haptics).
-     */
-    @Override
-    public void startTransitionToCredentialUI(boolean isError) {
-        updateSize(AuthDialog.SIZE_LARGE);
-        mCallback.onAction(Callback.ACTION_USE_DEVICE_CREDENTIAL);
-    }
-
-    @Override
-    protected void onAttachedToWindow() {
-        super.onAttachedToWindow();
-
-        mTitleView.setText(mPromptInfo.getTitle());
-
-        // setSelected could make marquee work
-        mTitleView.setSelected(true);
-        mSubtitleView.setSelected(true);
-        // make description view become scrollable
-        mDescriptionView.setMovementMethod(new ScrollingMovementMethod());
-
-        if (isDeviceCredentialAllowed()) {
-            final CharSequence credentialButtonText;
-            @Utils.CredentialType final int credentialType =
-                    Utils.getCredentialType(mLockPatternUtils, mEffectiveUserId);
-            switch (credentialType) {
-                case Utils.CREDENTIAL_PIN:
-                    credentialButtonText =
-                            getResources().getString(R.string.biometric_dialog_use_pin);
-                    break;
-                case Utils.CREDENTIAL_PATTERN:
-                    credentialButtonText =
-                            getResources().getString(R.string.biometric_dialog_use_pattern);
-                    break;
-                case Utils.CREDENTIAL_PASSWORD:
-                default:
-                    credentialButtonText =
-                            getResources().getString(R.string.biometric_dialog_use_password);
-                    break;
-            }
-
-            mNegativeButton.setVisibility(View.GONE);
-
-            mUseCredentialButton.setText(credentialButtonText);
-            mUseCredentialButton.setVisibility(View.VISIBLE);
-        } else {
-            mNegativeButton.setText(mPromptInfo.getNegativeButtonText());
-        }
-
-        setTextOrHide(mSubtitleView, mPromptInfo.getSubtitle());
-        setTextOrHide(mDescriptionView, mPromptInfo.getDescription());
-
-        if (mSavedState == null) {
-            updateState(STATE_AUTHENTICATING_ANIMATING_IN);
-        } else {
-            // Restore as much state as possible first
-            updateState(mSavedState.getInt(AuthDialog.KEY_BIOMETRIC_STATE));
-
-            // Restore positive button(s) state
-            mConfirmButton.setVisibility(
-                    mSavedState.getInt(AuthDialog.KEY_BIOMETRIC_CONFIRM_VISIBILITY));
-            if (mConfirmButton.getVisibility() == View.GONE) {
-                setRequireConfirmation(false);
-            }
-            mTryAgainButton.setVisibility(
-                    mSavedState.getInt(AuthDialog.KEY_BIOMETRIC_TRY_AGAIN_VISIBILITY));
-
-        }
-    }
-
-    @Override
-    protected void onDetachedFromWindow() {
-        super.onDetachedFromWindow();
-
-        mIconController.setDeactivated(true);
-
-        // Empty the handler, otherwise things like ACTION_AUTHENTICATED may be duplicated once
-        // the new dialog is restored.
-        mHandler.removeCallbacksAndMessages(null /* all */);
-    }
-
-    /**
-     * Contains all of the testable logic that should be invoked when {@link #onMeasure(int, int)}
-     * is invoked. In addition, this allows subclasses to implement custom measuring logic while
-     * allowing the base class to have common code to apply the custom measurements.
-     *
-     * @param width Width to constrain the measurements to.
-     * @param height Height to constrain the measurements to.
-     * @return See {@link AuthDialog.LayoutParams}
-     */
-    @NonNull
-    AuthDialog.LayoutParams onMeasureInternal(int width, int height) {
-        int totalHeight = 0;
-        final int numChildren = getChildCount();
-        for (int i = 0; i < numChildren; i++) {
-            final View child = getChildAt(i);
-
-            if (child.getId() == R.id.space_above_icon
-                    || child.getId() == R.id.space_below_icon
-                    || child.getId() == R.id.button_bar) {
-                child.measure(
-                        MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY),
-                        MeasureSpec.makeMeasureSpec(child.getLayoutParams().height,
-                                MeasureSpec.EXACTLY));
-            } else if (child.getId() == R.id.biometric_icon_frame) {
-                final View iconView = findViewById(R.id.biometric_icon);
-                child.measure(
-                        MeasureSpec.makeMeasureSpec(iconView.getLayoutParams().width,
-                                MeasureSpec.EXACTLY),
-                        MeasureSpec.makeMeasureSpec(iconView.getLayoutParams().height,
-                                MeasureSpec.EXACTLY));
-            } else if (child.getId() == R.id.biometric_icon) {
-                child.measure(
-                        MeasureSpec.makeMeasureSpec(width, MeasureSpec.AT_MOST),
-                        MeasureSpec.makeMeasureSpec(height, MeasureSpec.AT_MOST));
-            } else {
-                child.measure(
-                        MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY),
-                        MeasureSpec.makeMeasureSpec(height, MeasureSpec.AT_MOST));
-            }
-
-            if (child.getVisibility() != View.GONE) {
-                totalHeight += child.getMeasuredHeight();
-            }
-        }
-
-        return new AuthDialog.LayoutParams(width, totalHeight);
-    }
-
-    @Override
-    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
-        int width = MeasureSpec.getSize(widthMeasureSpec);
-        int height = MeasureSpec.getSize(heightMeasureSpec);
-
-        if (mUseCustomBpSize) {
-            width = mCustomBpWidth;
-            height = mCustomBpHeight;
-        } else {
-            width = Math.min(width, height);
-        }
-
-        mLayoutParams = onMeasureInternal(width, height);
-
-        final Insets navBarInsets = Utils.getNavbarInsets(mContext);
-        final int navBarHeight = navBarInsets.bottom;
-        final int navBarWidth;
-        if (mPanelController.getPosition() == AuthPanelController.POSITION_LEFT) {
-            navBarWidth = navBarInsets.left;
-        } else if (mPanelController.getPosition() == AuthPanelController.POSITION_RIGHT) {
-            navBarWidth = navBarInsets.right;
-        } else {
-            navBarWidth = 0;
-        }
-
-        // The actual auth dialog w/h should include navigation bar size.
-        if (navBarWidth != 0 || navBarHeight != 0) {
-            mLayoutParams = new AuthDialog.LayoutParams(
-                    mLayoutParams.mMediumWidth + navBarWidth,
-                    mLayoutParams.mMediumHeight + navBarInsets.bottom);
-        }
-
-        setMeasuredDimension(mLayoutParams.mMediumWidth, mLayoutParams.mMediumHeight);
-    }
-
-    @Override
-    public void onLayout(boolean changed, int left, int top, int right, int bottom) {
-        super.onLayout(changed, left, top, right, bottom);
-
-        // Start with initial size only once. Subsequent layout changes don't matter since we
-        // only care about the initial icon position.
-        if (mIconOriginalY == 0) {
-            mIconOriginalY = mIconHolderView.getY();
-            if (mSavedState == null) {
-                updateSize(!mRequireConfirmation && supportsSmallDialog() ? AuthDialog.SIZE_SMALL
-                        : AuthDialog.SIZE_MEDIUM);
-            } else {
-                updateSize(mSavedState.getInt(AuthDialog.KEY_BIOMETRIC_DIALOG_SIZE));
-
-                // Restore indicator text state only after size has been restored
-                final String indicatorText =
-                        mSavedState.getString(AuthDialog.KEY_BIOMETRIC_INDICATOR_STRING);
-                if (mSavedState.getBoolean(AuthDialog.KEY_BIOMETRIC_INDICATOR_HELP_SHOWING)) {
-                    onHelp(TYPE_NONE, indicatorText);
-                } else if (mSavedState.getBoolean(
-                        AuthDialog.KEY_BIOMETRIC_INDICATOR_ERROR_SHOWING)) {
-                    onAuthenticationFailed(TYPE_NONE, indicatorText);
-                }
-            }
-        }
-    }
-
-    private boolean isDeviceCredentialAllowed() {
-        return Utils.isDeviceCredentialAllowed(mPromptInfo);
-    }
-
-    public LottieAnimationView getIconView() {
-        return mIconView;
-    }
-
-    @AuthDialog.DialogSize int getSize() {
-        return mSize;
-    }
-
-    /** If authentication has successfully occurred and the view is done. */
-    boolean isAuthenticated() {
-        return mState == STATE_AUTHENTICATED;
-    }
-
-    /** If authentication is currently in progress. */
-    boolean isAuthenticating() {
-        return mState == STATE_AUTHENTICATING;
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricViewAdapter.kt b/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricViewAdapter.kt
deleted file mode 100644
index 68db564..0000000
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthBiometricViewAdapter.kt
+++ /dev/null
@@ -1,56 +0,0 @@
-/*
- * Copyright (C) 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.biometrics
-
-import android.hardware.biometrics.BiometricAuthenticator
-import android.os.Bundle
-import android.view.View
-
-/** TODO(b/251476085): Temporary interface while legacy biometric prompt is around. */
-@Deprecated("temporary adapter while migrating biometric prompt - do not expand")
-interface AuthBiometricViewAdapter {
-    val legacyIconController: AuthIconController?
-
-    fun onDialogAnimatedIn(fingerprintWasStarted: Boolean)
-
-    fun onAuthenticationSucceeded(@BiometricAuthenticator.Modality modality: Int)
-
-    fun onAuthenticationFailed(
-        @BiometricAuthenticator.Modality modality: Int,
-        failureReason: String
-    )
-
-    fun onError(@BiometricAuthenticator.Modality modality: Int, error: String)
-
-    fun onHelp(@BiometricAuthenticator.Modality modality: Int, help: String)
-
-    fun startTransitionToCredentialUI(isError: Boolean)
-
-    fun requestLayout()
-
-    fun onSaveState(bundle: Bundle?)
-
-    fun restoreState(bundle: Bundle?)
-
-    fun onOrientationChanged()
-
-    fun cancelAnimation()
-
-    fun isCoex(): Boolean
-
-    fun asView(): View
-}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java
index 7464c88..c7d7fe3 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthContainerView.java
@@ -17,9 +17,7 @@
 package com.android.systemui.biometrics;
 
 import static android.hardware.biometrics.BiometricAuthenticator.TYPE_FACE;
-import static android.hardware.biometrics.SensorProperties.STRENGTH_STRONG;
 import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
-
 import static com.android.internal.jank.InteractionJankMonitor.CUJ_BIOMETRIC_PROMPT_TRANSITION;
 
 import android.animation.Animator;
@@ -35,7 +33,6 @@
 import android.hardware.face.FaceSensorPropertiesInternal;
 import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
 import android.os.Binder;
-import android.os.Bundle;
 import android.os.Handler;
 import android.os.IBinder;
 import android.os.Looper;
@@ -70,15 +67,16 @@
 import com.android.systemui.biometrics.AuthController.ScaleFactorProvider;
 import com.android.systemui.biometrics.domain.interactor.PromptCredentialInteractor;
 import com.android.systemui.biometrics.domain.interactor.PromptSelectorInteractor;
-import com.android.systemui.biometrics.domain.model.BiometricModalities;
+import com.android.systemui.biometrics.shared.model.BiometricModalities;
 import com.android.systemui.biometrics.ui.BiometricPromptLayout;
 import com.android.systemui.biometrics.ui.CredentialView;
 import com.android.systemui.biometrics.ui.binder.BiometricViewBinder;
+import com.android.systemui.biometrics.ui.binder.BiometricViewSizeBinder;
+import com.android.systemui.biometrics.ui.binder.Spaghetti;
 import com.android.systemui.biometrics.ui.viewmodel.CredentialViewModel;
 import com.android.systemui.biometrics.ui.viewmodel.PromptViewModel;
 import com.android.systemui.dagger.qualifiers.Background;
 import com.android.systemui.flags.FeatureFlags;
-import com.android.systemui.flags.Flags;
 import com.android.systemui.keyguard.WakefulnessLifecycle;
 import com.android.systemui.statusbar.VibratorHelper;
 import com.android.systemui.util.concurrency.DelayableExecutor;
@@ -96,7 +94,10 @@
 
 /**
  * Top level container/controller for the BiometricPrompt UI.
+ *
+ * @deprecated TODO(b/287311775): remove and merge view/layouts into new prompt.
  */
+@Deprecated
 public class AuthContainerView extends LinearLayout
         implements AuthDialog, WakefulnessLifecycle.Observer, CredentialView.Host {
 
@@ -104,6 +105,7 @@
 
     private static final int ANIMATION_DURATION_SHOW_MS = 250;
     private static final int ANIMATION_DURATION_AWAY_MS = 350;
+    private static final int ANIMATE_CREDENTIAL_START_DELAY_MS = 300;
 
     private static final int STATE_UNKNOWN = 0;
     private static final int STATE_ANIMATING_IN = 1;
@@ -138,16 +140,16 @@
     private final InteractionJankMonitor mInteractionJankMonitor;
     private final CoroutineScope mApplicationCoroutineScope;
 
-    // TODO: these should be migrated out once ready
+    // TODO(b/287311775): these should be migrated out once ready
     private final Provider<PromptCredentialInteractor> mPromptCredentialInteractor;
     private final @NonNull Provider<PromptSelectorInteractor> mPromptSelectorInteractorProvider;
-    // TODO(b/251476085): these should be migrated out of the view
+    // TODO(b/287311775): these should be migrated out of the view
     private final Provider<CredentialViewModel> mCredentialViewModelProvider;
     private final PromptViewModel mPromptViewModel;
 
     @VisibleForTesting final BiometricCallback mBiometricCallback;
 
-    @Nullable private AuthBiometricViewAdapter mBiometricView;
+    @Nullable private Spaghetti mBiometricView;
     @Nullable private View mCredentialView;
     private final AuthPanelController mPanelController;
     private final FrameLayout mFrameLayout;
@@ -166,7 +168,7 @@
     // HAT received from LockSettingsService when credential is verified.
     @Nullable private byte[] mCredentialAttestation;
 
-    // TODO(b/251476085): remove when legacy prompt is replaced
+    // TODO(b/287311775): remove when legacy prompt is replaced
     @Deprecated
     static class Config {
         Context mContext;
@@ -184,42 +186,50 @@
     }
 
     @VisibleForTesting
-    final class BiometricCallback implements AuthBiometricView.Callback {
+    final class BiometricCallback implements Spaghetti.Callback {
         @Override
-        public void onAction(int action) {
-            switch (action) {
-                case AuthBiometricView.Callback.ACTION_AUTHENTICATED:
-                    animateAway(AuthDialogCallback.DISMISSED_BIOMETRIC_AUTHENTICATED);
-                    break;
-                case AuthBiometricView.Callback.ACTION_USER_CANCELED:
-                    sendEarlyUserCanceled();
-                    animateAway(AuthDialogCallback.DISMISSED_USER_CANCELED);
-                    break;
-                case AuthBiometricView.Callback.ACTION_BUTTON_NEGATIVE:
-                    animateAway(AuthDialogCallback.DISMISSED_BUTTON_NEGATIVE);
-                    break;
-                case AuthBiometricView.Callback.ACTION_BUTTON_TRY_AGAIN:
-                    mFailedModalities.clear();
-                    mConfig.mCallback.onTryAgainPressed(getRequestId());
-                    break;
-                case AuthBiometricView.Callback.ACTION_ERROR:
-                    animateAway(AuthDialogCallback.DISMISSED_ERROR);
-                    break;
-                case AuthBiometricView.Callback.ACTION_USE_DEVICE_CREDENTIAL:
-                    mConfig.mCallback.onDeviceCredentialPressed(getRequestId());
-                    mHandler.postDelayed(() -> {
-                        addCredentialView(false /* animatePanel */, true /* animateContents */);
-                    }, mConfig.mSkipAnimation ? 0 : AuthDialog.ANIMATE_CREDENTIAL_START_DELAY_MS);
-                    break;
-                case AuthBiometricView.Callback.ACTION_START_DELAYED_FINGERPRINT_SENSOR:
-                    mConfig.mCallback.onStartFingerprintNow(getRequestId());
-                    break;
-                case AuthBiometricView.Callback.ACTION_AUTHENTICATED_AND_CONFIRMED:
-                    animateAway(AuthDialogCallback.DISMISSED_BUTTON_POSITIVE);
-                    break;
-                default:
-                    Log.e(TAG, "Unhandled action: " + action);
-            }
+        public void onAuthenticated() {
+            animateAway(AuthDialogCallback.DISMISSED_BIOMETRIC_AUTHENTICATED);
+        }
+
+        @Override
+        public void onUserCanceled() {
+            sendEarlyUserCanceled();
+            animateAway(AuthDialogCallback.DISMISSED_USER_CANCELED);
+        }
+
+        @Override
+        public void onButtonNegative() {
+            animateAway(AuthDialogCallback.DISMISSED_BUTTON_NEGATIVE);
+        }
+
+        @Override
+        public void onButtonTryAgain() {
+            mFailedModalities.clear();
+            mConfig.mCallback.onTryAgainPressed(getRequestId());
+        }
+
+        @Override
+        public void onError() {
+            animateAway(AuthDialogCallback.DISMISSED_ERROR);
+        }
+
+        @Override
+        public void onUseDeviceCredential() {
+            mConfig.mCallback.onDeviceCredentialPressed(getRequestId());
+            mHandler.postDelayed(() -> {
+                addCredentialView(false /* animatePanel */, true /* animateContents */);
+            }, mConfig.mSkipAnimation ? 0 : ANIMATE_CREDENTIAL_START_DELAY_MS);
+        }
+
+        @Override
+        public void onStartDelayedFingerprintSensor() {
+            mConfig.mCallback.onStartFingerprintNow(getRequestId());
+        }
+
+        @Override
+        public void onAuthenticatedAndConfirmed() {
+            animateAway(AuthDialogCallback.DISMISSED_BUTTON_POSITIVE);
         }
     }
 
@@ -355,14 +365,10 @@
         mCredentialViewModelProvider = credentialViewModelProvider;
         mPromptViewModel = promptViewModel;
 
-        if (featureFlags.isEnabled(Flags.BIOMETRIC_BP_STRONG)) {
-            showPrompt(config, layoutInflater, promptViewModel,
-                    Utils.findFirstSensorProperties(fpProps, mConfig.mSensorIds),
-                    Utils.findFirstSensorProperties(faceProps, mConfig.mSensorIds),
-                    vibratorHelper, featureFlags);
-        } else {
-            showLegacyPrompt(config, layoutInflater, fpProps, faceProps);
-        }
+        showPrompt(config, layoutInflater, promptViewModel,
+                Utils.findFirstSensorProperties(fpProps, mConfig.mSensorIds),
+                Utils.findFirstSensorProperties(faceProps, mConfig.mSensorIds),
+                vibratorHelper, featureFlags);
 
         // TODO: De-dupe the logic with AuthCredentialPasswordView
         setOnKeyListener((v, keyCode, event) -> {
@@ -398,7 +404,8 @@
                     R.layout.biometric_prompt_layout, null, false);
             mBiometricView = BiometricViewBinder.bind(view, viewModel, mPanelController,
                     // TODO(b/201510778): This uses the wrong timeout in some cases
-                    getJankListener(view, TRANSIT, AuthDialog.ANIMATE_MEDIUM_TO_LARGE_DURATION_MS),
+                    getJankListener(view, TRANSIT,
+                            BiometricViewSizeBinder.ANIMATE_MEDIUM_TO_LARGE_DURATION_MS),
                     mBackgroundView, mBiometricCallback, mApplicationCoroutineScope,
                     vibratorHelper, featureFlags);
 
@@ -412,60 +419,6 @@
         }
     }
 
-    // TODO(b/251476085): remove entirely
-    private void showLegacyPrompt(@NonNull Config config, @NonNull LayoutInflater layoutInflater,
-            @Nullable List<FingerprintSensorPropertiesInternal> fpProps,
-            @Nullable List<FaceSensorPropertiesInternal> faceProps
-    ) {
-        // Inflate biometric view only if necessary.
-        if (Utils.isBiometricAllowed(mConfig.mPromptInfo)) {
-            final FingerprintSensorPropertiesInternal fpProperties =
-                    Utils.findFirstSensorProperties(fpProps, mConfig.mSensorIds);
-            final FaceSensorPropertiesInternal faceProperties =
-                    Utils.findFirstSensorProperties(faceProps, mConfig.mSensorIds);
-
-            if (fpProperties != null && faceProperties != null) {
-                final AuthBiometricFingerprintAndFaceView fingerprintAndFaceView =
-                        (AuthBiometricFingerprintAndFaceView) layoutInflater.inflate(
-                                R.layout.auth_biometric_fingerprint_and_face_view, null, false);
-                fingerprintAndFaceView.setSensorProperties(fpProperties);
-                fingerprintAndFaceView.setScaleFactorProvider(config.mScaleProvider);
-                fingerprintAndFaceView.updateOverrideIconLayoutParamsSize();
-                fingerprintAndFaceView.setFaceClass3(
-                        faceProperties.sensorStrength == STRENGTH_STRONG);
-                mBiometricView = fingerprintAndFaceView;
-            } else if (fpProperties != null) {
-                final AuthBiometricFingerprintView fpView =
-                        (AuthBiometricFingerprintView) layoutInflater.inflate(
-                                R.layout.auth_biometric_fingerprint_view, null, false);
-                fpView.setSensorProperties(fpProperties);
-                fpView.setScaleFactorProvider(config.mScaleProvider);
-                fpView.updateOverrideIconLayoutParamsSize();
-                mBiometricView = fpView;
-            } else if (faceProperties != null) {
-                mBiometricView = (AuthBiometricFaceView) layoutInflater.inflate(
-                        R.layout.auth_biometric_face_view, null, false);
-            } else {
-                Log.e(TAG, "No sensors found!");
-            }
-        }
-
-        // init view before showing
-        if (mBiometricView != null) {
-            final AuthBiometricView view = (AuthBiometricView) mBiometricView;
-            view.setRequireConfirmation(mConfig.mRequireConfirmation);
-            view.setPanelController(mPanelController);
-            view.setPromptInfo(mConfig.mPromptInfo);
-            view.setCallback(mBiometricCallback);
-            view.setBackgroundView(mBackgroundView);
-            view.setUserId(mConfig.mUserId);
-            view.setEffectiveUserId(mEffectiveUserId);
-            // TODO(b/201510778): This uses the wrong timeout in some cases (remove w/ above)
-            view.setJankListener(getJankListener(view, TRANSIT,
-                    AuthDialog.ANIMATE_MEDIUM_TO_LARGE_DURATION_MS));
-        }
-    }
-
     private void onBackInvoked() {
         sendEarlyUserCanceled();
         animateAway(AuthDialogCallback.DISMISSED_USER_CANCELED);
@@ -533,9 +486,6 @@
     @Override
     public void onOrientationChanged() {
         maybeUpdatePositionForUdfps(true /* invalidate */);
-        if (mBiometricView != null) {
-            mBiometricView.onOrientationChanged();
-        }
     }
 
     @Override
@@ -621,10 +571,6 @@
     }
 
     private static boolean shouldUpdatePositionForUdfps(@NonNull View view) {
-        // TODO(b/251476085): legacy view (delete when removed)
-        if (view instanceof AuthBiometricFingerprintView) {
-            return ((AuthBiometricFingerprintView) view).isUdfps();
-        }
         if (view instanceof BiometricPromptLayout) {
             // this will force the prompt to align itself on the edge of the screen
             // instead of centering (temporary workaround to prevent small implicit view
@@ -672,7 +618,6 @@
 
         if (invalidate) {
             mPanelView.invalidateOutline();
-            mBiometricView.requestLayout();
         }
 
         return true;
@@ -702,11 +647,7 @@
     }
 
     @Override
-    public void show(WindowManager wm, @Nullable Bundle savedState) {
-        if (mBiometricView != null) {
-            mBiometricView.restoreState(savedState);
-        }
-
+    public void show(WindowManager wm) {
         wm.addView(this, getLayoutParams(mWindowToken, mConfig.mPromptInfo.getTitle()));
     }
 
@@ -781,7 +722,7 @@
             if (mFailedModalities.contains(TYPE_FACE)) {
                 Log.d(TAG, "retrying failed modalities (pointer down)");
                 mFailedModalities.remove(TYPE_FACE);
-                mBiometricCallback.onAction(AuthBiometricView.Callback.ACTION_BUTTON_TRY_AGAIN);
+                mBiometricCallback.onButtonTryAgain();
             }
         } else {
             Log.e(TAG, "onPointerDown(): mBiometricView is null");
@@ -789,21 +730,6 @@
     }
 
     @Override
-    public void onSaveState(@NonNull Bundle outState) {
-        outState.putBoolean(AuthDialog.KEY_CONTAINER_GOING_AWAY,
-                mContainerState == STATE_ANIMATING_OUT);
-        // In the case where biometric and credential are both allowed, we can assume that
-        // biometric isn't showing if credential is showing since biometric is shown first.
-        outState.putBoolean(AuthDialog.KEY_BIOMETRIC_SHOWING,
-                mBiometricView != null && mCredentialView == null);
-        outState.putBoolean(AuthDialog.KEY_CREDENTIAL_SHOWING, mCredentialView != null);
-
-        if (mBiometricView != null) {
-            mBiometricView.onSaveState(outState);
-        }
-    }
-
-    @Override
     public String getOpPackageName() {
         return mConfig.mOpPackageName;
     }
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java
index d5289a4..b752c3b 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java
@@ -50,7 +50,6 @@
 import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
 import android.hardware.fingerprint.IFingerprintAuthenticatorsRegisteredCallback;
 import android.hardware.fingerprint.IUdfpsRefreshRateRequestCallback;
-import android.os.Bundle;
 import android.os.Handler;
 import android.os.RemoteException;
 import android.os.UserManager;
@@ -67,12 +66,11 @@
 import com.android.internal.jank.InteractionJankMonitor;
 import com.android.internal.os.SomeArgs;
 import com.android.internal.widget.LockPatternUtils;
-import com.android.settingslib.udfps.UdfpsOverlayParams;
-import com.android.settingslib.udfps.UdfpsUtils;
 import com.android.systemui.CoreStartable;
 import com.android.systemui.biometrics.domain.interactor.LogContextInteractor;
 import com.android.systemui.biometrics.domain.interactor.PromptCredentialInteractor;
 import com.android.systemui.biometrics.domain.interactor.PromptSelectorInteractor;
+import com.android.systemui.biometrics.shared.model.UdfpsOverlayParams;
 import com.android.systemui.biometrics.ui.viewmodel.CredentialViewModel;
 import com.android.systemui.biometrics.ui.viewmodel.PromptViewModel;
 import com.android.systemui.dagger.SysUISingleton;
@@ -88,8 +86,6 @@
 import com.android.systemui.util.concurrency.DelayableExecutor;
 import com.android.systemui.util.concurrency.Execution;
 
-import kotlin.Unit;
-
 import java.io.PrintWriter;
 import java.util.ArrayList;
 import java.util.Arrays;
@@ -103,6 +99,8 @@
 import javax.inject.Inject;
 import javax.inject.Provider;
 
+import kotlin.Unit;
+
 import kotlinx.coroutines.CoroutineScope;
 
 /**
@@ -967,7 +965,7 @@
             skipAnimation = true;
         }
 
-        showDialog(args, skipAnimation, null /* savedState */, mPromptViewModelProvider.get());
+        showDialog(args, skipAnimation, mPromptViewModelProvider.get());
     }
 
     /**
@@ -1199,7 +1197,7 @@
         return mFpEnrolledForUser.getOrDefault(userId, false);
     }
 
-    private void showDialog(SomeArgs args, boolean skipAnimation, Bundle savedState,
+    private void showDialog(SomeArgs args, boolean skipAnimation,
             @Nullable PromptViewModel viewModel) {
         mCurrentDialogArgs = args;
 
@@ -1239,7 +1237,6 @@
 
         if (DEBUG) {
             Log.d(TAG, "userId: " + userId
-                    + " savedState: " + savedState
                     + " mCurrentDialog: " + mCurrentDialog
                     + " newDialog: " + newDialog);
         }
@@ -1261,7 +1258,7 @@
         if (!promptInfo.isAllowBackgroundAuthentication() && !isOwnerInForeground()) {
             cancelIfOwnerIsNotInForeground();
         } else {
-            mCurrentDialog.show(mWindowManager, savedState);
+            mCurrentDialog.show(mWindowManager);
         }
     }
 
@@ -1283,29 +1280,12 @@
     public void onConfigurationChanged(Configuration newConfig) {
         updateSensorLocations();
 
-        // Save the state of the current dialog (buttons showing, etc)
+        // TODO(b/287311775): consider removing this to retain the UI cleanly vs re-creating
         if (mCurrentDialog != null) {
             final PromptViewModel viewModel = mCurrentDialog.getViewModel();
-            final Bundle savedState = new Bundle();
-            mCurrentDialog.onSaveState(savedState);
             mCurrentDialog.dismissWithoutCallback(false /* animate */);
             mCurrentDialog = null;
-
-            // Only show the dialog if necessary. If it was animating out, the dialog is supposed
-            // to send its pending callback immediately.
-            if (!savedState.getBoolean(AuthDialog.KEY_CONTAINER_GOING_AWAY, false)) {
-                final boolean credentialShowing =
-                        savedState.getBoolean(AuthDialog.KEY_CREDENTIAL_SHOWING);
-                if (credentialShowing) {
-                    // There may be a cleaner way to do this, rather than altering the current
-                    // authentication's parameters. This gets the job done and should be clear
-                    // enough for now.
-                    PromptInfo promptInfo = (PromptInfo) mCurrentDialogArgs.arg1;
-                    promptInfo.setAuthenticators(Authenticators.DEVICE_CREDENTIAL);
-                }
-
-                showDialog(mCurrentDialogArgs, true /* skipAnimation */, savedState, viewModel);
-            }
+            showDialog(mCurrentDialogArgs, true /* skipAnimation */, viewModel);
         }
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthDialog.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthDialog.java
index 3cfc6f2..3fd488c 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthDialog.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthDialog.java
@@ -16,59 +16,20 @@
 
 package com.android.systemui.biometrics;
 
-import android.annotation.IntDef;
-import android.annotation.NonNull;
-import android.annotation.Nullable;
 import android.hardware.biometrics.BiometricAuthenticator.Modality;
-import android.os.Bundle;
 import android.view.WindowManager;
 
 import com.android.systemui.Dumpable;
 import com.android.systemui.biometrics.ui.viewmodel.PromptViewModel;
 
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-
 /**
  * Interface for the biometric dialog UI.
  *
- * TODO(b/251476085): remove along with legacy controller once flag is removed
+ * TODO(b/287311775): remove along with legacy controller once flag is removed
  */
 @Deprecated
 public interface AuthDialog extends Dumpable {
 
-    String KEY_CONTAINER_GOING_AWAY = "container_going_away";
-    String KEY_BIOMETRIC_SHOWING = "biometric_showing";
-    String KEY_CREDENTIAL_SHOWING = "credential_showing";
-
-    String KEY_BIOMETRIC_CONFIRM_VISIBILITY = "confirm_visibility";
-    String KEY_BIOMETRIC_TRY_AGAIN_VISIBILITY = "try_agian_visibility";
-    String KEY_BIOMETRIC_STATE = "state";
-    String KEY_BIOMETRIC_INDICATOR_STRING = "indicator_string"; // error / help / hint
-    String KEY_BIOMETRIC_INDICATOR_ERROR_SHOWING = "error_is_temporary";
-    String KEY_BIOMETRIC_INDICATOR_HELP_SHOWING = "hint_is_temporary";
-    String KEY_BIOMETRIC_DIALOG_SIZE = "size";
-
-    String KEY_BIOMETRIC_SENSOR_TYPE = "sensor_type";
-    String KEY_BIOMETRIC_SENSOR_PROPS = "sensor_props";
-
-    int SIZE_UNKNOWN = 0;
-    /**
-     * Minimal UI, showing only biometric icon.
-     */
-    int SIZE_SMALL = 1;
-    /**
-     * Normal-sized biometric UI, showing title, icon, buttons, etc.
-     */
-    int SIZE_MEDIUM = 2;
-    /**
-     * Full-screen credential UI.
-     */
-    int SIZE_LARGE = 3;
-    @Retention(RetentionPolicy.SOURCE)
-    @IntDef({SIZE_UNKNOWN, SIZE_SMALL, SIZE_MEDIUM, SIZE_LARGE})
-    @interface DialogSize {}
-
     /**
      * Parameters used when laying out {@link AuthBiometricView}, its subclasses, and
      * {@link AuthPanelController}.
@@ -84,27 +45,10 @@
     }
 
     /**
-     * Animation duration, from small to medium dialog, including back panel, icon translation, etc
-     */
-    int ANIMATE_SMALL_TO_MEDIUM_DURATION_MS = 150;
-    /**
-     * Animation duration from medium to large dialog, including biometric fade out, back panel, etc
-     */
-    int ANIMATE_MEDIUM_TO_LARGE_DURATION_MS = 450;
-    /**
-     * Delay before notifying {@link AuthCredentialView} to start animating in.
-     */
-    int ANIMATE_CREDENTIAL_START_DELAY_MS = ANIMATE_MEDIUM_TO_LARGE_DURATION_MS * 2 / 3;
-    /**
-     * Animation duration when sliding in credential UI
-     */
-    int ANIMATE_CREDENTIAL_INITIAL_DURATION_MS = 150;
-
-    /**
      * Show the dialog.
      * @param wm
      */
-    void show(WindowManager wm, @Nullable Bundle savedState);
+    void show(WindowManager wm);
 
     /**
      * Dismiss the dialog without sending a callback.
@@ -146,12 +90,6 @@
     void onPointerDown();
 
     /**
-     * Save the current state.
-     * @param outState
-     */
-    void onSaveState(@NonNull Bundle outState);
-
-    /**
      * Get the client's package name
      */
     String getOpPackageName();
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthIconController.kt b/packages/SystemUI/src/com/android/systemui/biometrics/AuthIconController.kt
index f56bb88..958213a 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthIconController.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthIconController.kt
@@ -24,7 +24,7 @@
 import android.util.Log
 import com.airbnb.lottie.LottieAnimationView
 import com.airbnb.lottie.LottieCompositionFactory
-import com.android.systemui.biometrics.AuthBiometricView.BiometricState
+import com.android.systemui.biometrics.ui.binder.Spaghetti.BiometricState
 
 private const val TAG = "AuthIconController"
 
@@ -76,7 +76,7 @@
     }
 
     /** Update the icon to reflect the [newState]. */
-    fun updateState(@BiometricState lastState: Int, @BiometricState newState: Int) {
+    fun updateState(lastState: BiometricState, newState: BiometricState) {
         if (deactivated) {
             Log.w(TAG, "Ignoring updateState when deactivated: $newState")
         } else {
@@ -85,7 +85,7 @@
     }
 
     /** Call during [updateState] if the controller is not [deactivated]. */
-    abstract fun updateIcon(@BiometricState lastState: Int, @BiometricState newState: Int)
+    abstract fun updateIcon(lastState: BiometricState, newState: BiometricState)
 
     /** Called during [onAnimationEnd] if the controller is not [deactivated]. */
     open fun handleAnimationEnd(drawable: Drawable) {}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleController.kt b/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleController.kt
index ea9fe5f..141983b 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleController.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthRippleController.kt
@@ -25,14 +25,14 @@
 import android.hardware.biometrics.BiometricSourceType
 import android.util.DisplayMetrics
 import androidx.annotation.VisibleForTesting
+import com.android.app.animation.Interpolators
 import com.android.keyguard.KeyguardUpdateMonitor
 import com.android.keyguard.KeyguardUpdateMonitorCallback
 import com.android.keyguard.logging.KeyguardLogger
 import com.android.settingslib.Utils
-import com.android.settingslib.udfps.UdfpsOverlayParams
 import com.android.systemui.CoreStartable
 import com.android.systemui.R
-import com.android.app.animation.Interpolators
+import com.android.systemui.biometrics.shared.model.UdfpsOverlayParams
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.flags.FeatureFlags
 import com.android.systemui.flags.Flags
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsAnimationViewController.kt b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsAnimationViewController.kt
index 94b5fb2..66fb8ca 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsAnimationViewController.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsAnimationViewController.kt
@@ -18,14 +18,22 @@
 import android.animation.ValueAnimator
 import android.graphics.PointF
 import android.graphics.RectF
-import com.android.systemui.Dumpable
+import androidx.annotation.VisibleForTesting
+import androidx.lifecycle.Lifecycle
+import androidx.lifecycle.repeatOnLifecycle
 import com.android.app.animation.Interpolators
+import com.android.systemui.Dumpable
+import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerInteractor
 import com.android.systemui.dump.DumpManager
+import com.android.systemui.lifecycle.repeatWhenAttached
 import com.android.systemui.plugins.statusbar.StatusBarStateController
-import com.android.systemui.shade.ShadeExpansionListener
-import com.android.systemui.shade.ShadeExpansionStateManager
+import com.android.systemui.statusbar.StatusBarState.SHADE
 import com.android.systemui.statusbar.phone.SystemUIDialogManager
 import com.android.systemui.util.ViewController
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Job
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.launch
 import java.io.PrintWriter
 
 /**
@@ -41,7 +49,7 @@
 abstract class UdfpsAnimationViewController<T : UdfpsAnimationView>(
     view: T,
     protected val statusBarStateController: StatusBarStateController,
-    protected val shadeExpansionStateManager: ShadeExpansionStateManager,
+    protected val primaryBouncerInteractor: PrimaryBouncerInteractor,
     protected val dialogManager: SystemUIDialogManager,
     private val dumpManager: DumpManager
 ) : ViewController<T>(view), Dumpable {
@@ -54,14 +62,6 @@
     private var dialogAlphaAnimator: ValueAnimator? = null
     private val dialogListener = SystemUIDialogManager.Listener { runDialogAlphaAnimator() }
 
-    private val shadeExpansionListener = ShadeExpansionListener { event ->
-        // Notification shade can be expanded but not visible (fraction: 0.0), for example
-        // when a heads-up notification (HUN) is showing.
-        notificationShadeVisible = event.expanded && event.fraction > 0f
-        view.onExpansionChanged(event.fraction)
-        updatePauseAuth()
-    }
-
     /** If the notification shade is visible. */
     var notificationShadeVisible: Boolean = false
 
@@ -88,6 +88,30 @@
         view.updateAlpha()
     }
 
+    init {
+        view.repeatWhenAttached {
+            // repeatOnLifecycle CREATED (as opposed to STARTED) because the Bouncer expansion
+            // can make the view not visible; and we still want to listen for events
+            // that may make the view visible again.
+            repeatOnLifecycle(Lifecycle.State.CREATED) {
+                listenForBouncerExpansion(this)
+            }
+        }
+    }
+
+    @VisibleForTesting
+    open suspend fun listenForBouncerExpansion(scope: CoroutineScope): Job {
+        return scope.launch {
+            primaryBouncerInteractor.bouncerExpansion.map { 1f - it }.collect { expansion: Float ->
+                if (statusBarStateController.state != SHADE) {
+                    notificationShadeVisible = expansion > 0f
+                    view.onExpansionChanged(expansion)
+                    updatePauseAuth()
+                }
+            }
+        }
+    }
+
     fun runDialogAlphaAnimator() {
         val hideAffordance = dialogManager.shouldHideAffordance()
         dialogAlphaAnimator?.cancel()
@@ -108,15 +132,11 @@
     }
 
     override fun onViewAttached() {
-        val currentState =
-            shadeExpansionStateManager.addExpansionListener(shadeExpansionListener)
-        shadeExpansionListener.onPanelExpansionChanged(currentState)
         dialogManager.registerListener(dialogListener)
         dumpManager.registerDumpable(dumpTag, this)
     }
 
     override fun onViewDetached() {
-        shadeExpansionStateManager.removeExpansionListener(shadeExpansionListener)
         dialogManager.unregisterListener(dialogListener)
         dumpManager.unregisterDumpable(dumpTag)
     }
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsBpViewController.kt b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsBpViewController.kt
index 802eea3..03749a9 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsBpViewController.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsBpViewController.kt
@@ -15,9 +15,9 @@
  */
 package com.android.systemui.biometrics
 
+import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerInteractor
 import com.android.systemui.dump.DumpManager
 import com.android.systemui.plugins.statusbar.StatusBarStateController
-import com.android.systemui.shade.ShadeExpansionStateManager
 import com.android.systemui.statusbar.phone.SystemUIDialogManager
 
 /**
@@ -26,13 +26,13 @@
 class UdfpsBpViewController(
     view: UdfpsBpView,
     statusBarStateController: StatusBarStateController,
-    shadeExpansionStateManager: ShadeExpansionStateManager,
+    primaryBouncerInteractor: PrimaryBouncerInteractor,
     systemUIDialogManager: SystemUIDialogManager,
     dumpManager: DumpManager
 ) : UdfpsAnimationViewController<UdfpsBpView>(
     view,
     statusBarStateController,
-    shadeExpansionStateManager,
+    primaryBouncerInteractor,
     systemUIDialogManager,
     dumpManager
 ) {
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java
index a368703..9ffb770 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java
@@ -23,7 +23,6 @@
 import static android.hardware.biometrics.BiometricOverlayConstants.REASON_AUTH_KEYGUARD;
 import static android.hardware.biometrics.BiometricOverlayConstants.REASON_ENROLL_ENROLLING;
 import static android.hardware.biometrics.BiometricOverlayConstants.REASON_ENROLL_FIND_SENSOR;
-
 import static com.android.internal.util.Preconditions.checkNotNull;
 import static com.android.systemui.classifier.Classifier.UDFPS_AUTHENTICATION;
 import static com.android.systemui.flags.Flags.ONE_WAY_HAPTICS_API_MIGRATION;
@@ -60,6 +59,7 @@
 
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
+import androidx.annotation.OptIn;
 
 import com.android.internal.R;
 import com.android.internal.annotations.VisibleForTesting;
@@ -67,11 +67,10 @@
 import com.android.internal.util.LatencyTracker;
 import com.android.keyguard.FaceAuthApiRequestReason;
 import com.android.keyguard.KeyguardUpdateMonitor;
-import com.android.settingslib.udfps.UdfpsOverlayParams;
-import com.android.settingslib.udfps.UdfpsUtils;
 import com.android.systemui.Dumpable;
 import com.android.systemui.animation.ActivityLaunchAnimator;
 import com.android.systemui.biometrics.dagger.BiometricsBackground;
+import com.android.systemui.biometrics.shared.model.UdfpsOverlayParams;
 import com.android.systemui.biometrics.udfps.InteractionEvent;
 import com.android.systemui.biometrics.udfps.NormalizedTouchData;
 import com.android.systemui.biometrics.udfps.SinglePointerTouchProcessor;
@@ -92,7 +91,6 @@
 import com.android.systemui.log.SessionTracker;
 import com.android.systemui.plugins.FalsingManager;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
-import com.android.systemui.shade.ShadeExpansionStateManager;
 import com.android.systemui.shared.system.SysUiStatsLog;
 import com.android.systemui.statusbar.LockscreenShadeTransitionController;
 import com.android.systemui.statusbar.VibratorHelper;
@@ -106,8 +104,6 @@
 import com.android.systemui.util.settings.SecureSettings;
 import com.android.systemui.util.time.SystemClock;
 
-import kotlin.Unit;
-
 import java.io.PrintWriter;
 import java.util.ArrayList;
 import java.util.HashSet;
@@ -118,6 +114,10 @@
 import javax.inject.Inject;
 import javax.inject.Provider;
 
+import kotlin.Unit;
+
+import kotlinx.coroutines.ExperimentalCoroutinesApi;
+
 /**
  * Shows and hides the under-display fingerprint sensor (UDFPS) overlay, handles UDFPS touch events,
  * and toggles the UDFPS display mode.
@@ -149,7 +149,6 @@
     private final WindowManager mWindowManager;
     private final DelayableExecutor mFgExecutor;
     @NonNull private final Executor mBiometricExecutor;
-    @NonNull private final ShadeExpansionStateManager mShadeExpansionStateManager;
     @NonNull private final StatusBarStateController mStatusBarStateController;
     @NonNull private final KeyguardStateController mKeyguardStateController;
     @NonNull private final StatusBarKeyguardViewManager mKeyguardViewManager;
@@ -264,23 +263,44 @@
     }
 
     public class UdfpsOverlayController extends IUdfpsOverlayController.Stub {
+        @OptIn(markerClass = ExperimentalCoroutinesApi.class)
         @Override
         public void showUdfpsOverlay(long requestId, int sensorId, int reason,
                 @NonNull IUdfpsOverlayControllerCallback callback) {
             mFgExecutor.execute(() -> UdfpsController.this.showUdfpsOverlay(
-                    new UdfpsControllerOverlay(mContext, mFingerprintManager, mInflater,
-                            mWindowManager, mAccessibilityManager, mStatusBarStateController,
-                            mShadeExpansionStateManager, mKeyguardViewManager,
-                            mKeyguardUpdateMonitor, mDialogManager, mDumpManager,
-                            mLockscreenShadeTransitionController, mConfigurationController,
-                            mKeyguardStateController,
-                            mUnlockedScreenOffAnimationController,
-                            mUdfpsDisplayMode, mSecureSettings, requestId, reason, callback,
-                            (view, event, fromUdfpsView) -> onTouch(requestId, event,
-                                    fromUdfpsView), mActivityLaunchAnimator, mFeatureFlags,
-                            mPrimaryBouncerInteractor, mAlternateBouncerInteractor, mUdfpsUtils,
-                            mUdfpsKeyguardAccessibilityDelegate,
-                            mUdfpsKeyguardViewModels)));
+                    new UdfpsControllerOverlay(
+                        mContext,
+                        mFingerprintManager,
+                        mInflater,
+                        mWindowManager,
+                        mAccessibilityManager,
+                        mStatusBarStateController,
+                        mKeyguardViewManager,
+                        mKeyguardUpdateMonitor,
+                        mDialogManager,
+                        mDumpManager,
+                        mLockscreenShadeTransitionController,
+                        mConfigurationController,
+                        mKeyguardStateController,
+                        mUnlockedScreenOffAnimationController,
+                        mUdfpsDisplayMode,
+                        mSecureSettings,
+                        requestId,
+                        reason,
+                        callback,
+                        (view, event, fromUdfpsView) -> onTouch(
+                            requestId,
+                            event,
+                            fromUdfpsView
+                        ),
+                        mActivityLaunchAnimator,
+                        mFeatureFlags,
+                        mPrimaryBouncerInteractor,
+                        mAlternateBouncerInteractor,
+                        mUdfpsUtils,
+                        mUdfpsKeyguardAccessibilityDelegate,
+                        mUdfpsKeyguardViewModels
+                    )));
         }
 
         @Override
@@ -814,7 +834,6 @@
             @NonNull WindowManager windowManager,
             @NonNull StatusBarStateController statusBarStateController,
             @Main DelayableExecutor fgExecutor,
-            @NonNull ShadeExpansionStateManager shadeExpansionStateManager,
             @NonNull StatusBarKeyguardViewManager statusBarKeyguardViewManager,
             @NonNull DumpManager dumpManager,
             @NonNull KeyguardUpdateMonitor keyguardUpdateMonitor,
@@ -859,7 +878,6 @@
         mFingerprintManager = checkNotNull(fingerprintManager);
         mWindowManager = windowManager;
         mFgExecutor = fgExecutor;
-        mShadeExpansionStateManager = shadeExpansionStateManager;
         mStatusBarStateController = statusBarStateController;
         mKeyguardStateController = keyguardStateController;
         mKeyguardViewManager = statusBarKeyguardViewManager;
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsControllerOverlay.kt b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsControllerOverlay.kt
index d6ef94d..941df687 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsControllerOverlay.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsControllerOverlay.kt
@@ -46,10 +46,9 @@
 import androidx.annotation.LayoutRes
 import androidx.annotation.VisibleForTesting
 import com.android.keyguard.KeyguardUpdateMonitor
-import com.android.settingslib.udfps.UdfpsOverlayParams
-import com.android.settingslib.udfps.UdfpsUtils
 import com.android.systemui.R
 import com.android.systemui.animation.ActivityLaunchAnimator
+import com.android.systemui.biometrics.shared.model.UdfpsOverlayParams
 import com.android.systemui.biometrics.ui.controller.UdfpsKeyguardViewController
 import com.android.systemui.bouncer.domain.interactor.AlternateBouncerInteractor
 import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerInteractor
@@ -60,7 +59,6 @@
 import com.android.systemui.keyguard.ui.adapter.UdfpsKeyguardViewControllerAdapter
 import com.android.systemui.keyguard.ui.viewmodel.UdfpsKeyguardViewModels
 import com.android.systemui.plugins.statusbar.StatusBarStateController
-import com.android.systemui.shade.ShadeExpansionStateManager
 import com.android.systemui.statusbar.LockscreenShadeTransitionController
 import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager
 import com.android.systemui.statusbar.phone.SystemUIDialogManager
@@ -90,7 +88,6 @@
         private val windowManager: WindowManager,
         private val accessibilityManager: AccessibilityManager,
         private val statusBarStateController: StatusBarStateController,
-        private val shadeExpansionStateManager: ShadeExpansionStateManager,
         private val statusBarKeyguardViewManager: StatusBarKeyguardViewManager,
         private val keyguardUpdateMonitor: KeyguardUpdateMonitor,
         private val dialogManager: SystemUIDialogManager,
@@ -245,7 +242,7 @@
                         updateAccessibilityViewLocation(sensorBounds)
                     },
                     statusBarStateController,
-                    shadeExpansionStateManager,
+                    primaryBouncerInteractor,
                     dialogManager,
                     dumpManager
                 )
@@ -256,7 +253,7 @@
                     UdfpsKeyguardViewController(
                         view.addUdfpsView(R.layout.udfps_keyguard_view),
                         statusBarStateController,
-                        shadeExpansionStateManager,
+                        primaryBouncerInteractor,
                         dialogManager,
                         dumpManager,
                         alternateBouncerInteractor,
@@ -268,7 +265,6 @@
                             updateSensorLocation(sensorBounds)
                         },
                         statusBarStateController,
-                        shadeExpansionStateManager,
                         statusBarKeyguardViewManager,
                         keyguardUpdateMonitor,
                         dumpManager,
@@ -291,7 +287,7 @@
                 UdfpsBpViewController(
                     view.addUdfpsView(R.layout.udfps_bp_view),
                     statusBarStateController,
-                    shadeExpansionStateManager,
+                    primaryBouncerInteractor,
                     dialogManager,
                     dumpManager
                 )
@@ -301,7 +297,7 @@
                 UdfpsFpmEmptyViewController(
                     view.addUdfpsView(R.layout.udfps_fpm_empty_view),
                     statusBarStateController,
-                    shadeExpansionStateManager,
+                    primaryBouncerInteractor,
                     dialogManager,
                     dumpManager
                 )
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsFpmEmptyViewController.kt b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsFpmEmptyViewController.kt
index d122d64..88002e7 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsFpmEmptyViewController.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsFpmEmptyViewController.kt
@@ -15,9 +15,9 @@
  */
 package com.android.systemui.biometrics
 
+import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerInteractor
 import com.android.systemui.dump.DumpManager
 import com.android.systemui.plugins.statusbar.StatusBarStateController
-import com.android.systemui.shade.ShadeExpansionStateManager
 import com.android.systemui.statusbar.phone.SystemUIDialogManager
 
 /**
@@ -28,13 +28,13 @@
 class UdfpsFpmEmptyViewController(
     view: UdfpsFpmEmptyView,
     statusBarStateController: StatusBarStateController,
-    shadeExpansionStateManager: ShadeExpansionStateManager,
+    primaryBouncerInteractor: PrimaryBouncerInteractor,
     systemUIDialogManager: SystemUIDialogManager,
     dumpManager: DumpManager
 ) : UdfpsAnimationViewController<UdfpsFpmEmptyView>(
     view,
     statusBarStateController,
-    shadeExpansionStateManager,
+    primaryBouncerInteractor,
     systemUIDialogManager,
     dumpManager
 ) {
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerLegacy.kt b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerLegacy.kt
index e3fd3ce1..84a746c 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerLegacy.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsKeyguardViewControllerLegacy.kt
@@ -37,8 +37,6 @@
 import com.android.systemui.keyguard.ui.adapter.UdfpsKeyguardViewControllerAdapter
 import com.android.systemui.lifecycle.repeatWhenAttached
 import com.android.systemui.plugins.statusbar.StatusBarStateController
-import com.android.systemui.shade.ShadeExpansionListener
-import com.android.systemui.shade.ShadeExpansionStateManager
 import com.android.systemui.statusbar.LockscreenShadeTransitionController
 import com.android.systemui.statusbar.StatusBarState
 import com.android.systemui.statusbar.notification.stack.StackStateAnimator
@@ -55,11 +53,9 @@
 import kotlinx.coroutines.launch
 
 /** Class that coordinates non-HBM animations during keyguard authentication. */
-open class UdfpsKeyguardViewControllerLegacy
-constructor(
+open class UdfpsKeyguardViewControllerLegacy(
     private val view: UdfpsKeyguardViewLegacy,
     statusBarStateController: StatusBarStateController,
-    shadeExpansionStateManager: ShadeExpansionStateManager,
     private val keyguardViewManager: StatusBarKeyguardViewManager,
     private val keyguardUpdateMonitor: KeyguardUpdateMonitor,
     dumpManager: DumpManager,
@@ -71,14 +67,14 @@
     private val udfpsController: UdfpsController,
     private val activityLaunchAnimator: ActivityLaunchAnimator,
     featureFlags: FeatureFlags,
-    private val primaryBouncerInteractor: PrimaryBouncerInteractor,
+    primaryBouncerInteractor: PrimaryBouncerInteractor,
     private val alternateBouncerInteractor: AlternateBouncerInteractor,
     private val udfpsKeyguardAccessibilityDelegate: UdfpsKeyguardAccessibilityDelegate,
 ) :
     UdfpsAnimationViewController<UdfpsKeyguardViewLegacy>(
         view,
         statusBarStateController,
-        shadeExpansionStateManager,
+        primaryBouncerInteractor,
         systemUIDialogManager,
         dumpManager,
     ),
@@ -159,17 +155,6 @@
             }
         }
 
-    private val shadeExpansionListener = ShadeExpansionListener { (fraction) ->
-        panelExpansionFraction =
-            if (keyguardViewManager.isPrimaryBouncerInTransit) {
-                aboutToShowBouncerProgress(fraction)
-            } else {
-                fraction
-            }
-        updateAlpha()
-        updatePauseAuth()
-    }
-
     private val keyguardStateControllerCallback: KeyguardStateController.Callback =
         object : KeyguardStateController.Callback {
             override fun onUnlockedChanged() {
@@ -262,10 +247,17 @@
     }
 
     @VisibleForTesting
-    suspend fun listenForBouncerExpansion(scope: CoroutineScope): Job {
+    override suspend fun listenForBouncerExpansion(scope: CoroutineScope): Job {
         return scope.launch {
             primaryBouncerInteractor.bouncerExpansion.collect { bouncerExpansion: Float ->
                 inputBouncerExpansion = bouncerExpansion
+
+                panelExpansionFraction =
+                    if (keyguardViewManager.isPrimaryBouncerInTransit) {
+                        aboutToShowBouncerProgress(1f - bouncerExpansion)
+                    } else {
+                        1f - bouncerExpansion
+                    }
                 updateAlpha()
                 updatePauseAuth()
             }
@@ -295,8 +287,6 @@
         qsExpansion = keyguardViewManager.qsExpansion
         keyguardViewManager.addCallback(statusBarKeyguardViewManagerCallback)
         configurationController.addCallback(configurationListener)
-        val currentState = shadeExpansionStateManager.addExpansionListener(shadeExpansionListener)
-        shadeExpansionListener.onPanelExpansionChanged(currentState)
         updateScaleFactor()
         view.updatePadding()
         updateAlpha()
@@ -321,7 +311,6 @@
         keyguardViewManager.removeOccludingAppBiometricUI(occludingAppBiometricUI)
         keyguardUpdateMonitor.requestFaceAuthOnOccludingApp(false)
         configurationController.removeCallback(configurationListener)
-        shadeExpansionStateManager.removeExpansionListener(shadeExpansionListener)
         if (lockScreenShadeTransitionController.mUdfpsKeyguardViewControllerLegacy === this) {
             lockScreenShadeTransitionController.mUdfpsKeyguardViewControllerLegacy = null
         }
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsView.kt b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsView.kt
index 06dee7a..54e6215 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsView.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsView.kt
@@ -26,8 +26,8 @@
 import android.util.Log
 import android.view.MotionEvent
 import android.widget.FrameLayout
-import com.android.settingslib.udfps.UdfpsOverlayParams
 import com.android.systemui.R
+import com.android.systemui.biometrics.shared.model.UdfpsOverlayParams
 import com.android.systemui.doze.DozeReceiver
 
 private const val TAG = "UdfpsView"
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/dagger/BiometricsModule.kt b/packages/SystemUI/src/com/android/systemui/biometrics/dagger/BiometricsModule.kt
index 53dc0e3..f43285f 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/dagger/BiometricsModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/dagger/BiometricsModule.kt
@@ -16,7 +16,7 @@
 
 package com.android.systemui.biometrics.dagger
 
-import com.android.settingslib.udfps.UdfpsUtils
+import com.android.systemui.biometrics.UdfpsUtils
 import com.android.systemui.biometrics.data.repository.FacePropertyRepository
 import com.android.systemui.biometrics.data.repository.FacePropertyRepositoryImpl
 import com.android.systemui.biometrics.data.repository.FaceSettingsRepository
@@ -33,10 +33,10 @@
 import com.android.systemui.biometrics.domain.interactor.DisplayStateInteractorImpl
 import com.android.systemui.biometrics.domain.interactor.LogContextInteractor
 import com.android.systemui.biometrics.domain.interactor.LogContextInteractorImpl
-import com.android.systemui.biometrics.domain.interactor.SideFpsOverlayInteractor
-import com.android.systemui.biometrics.domain.interactor.SideFpsOverlayInteractorImpl
 import com.android.systemui.biometrics.domain.interactor.PromptSelectorInteractor
 import com.android.systemui.biometrics.domain.interactor.PromptSelectorInteractorImpl
+import com.android.systemui.biometrics.domain.interactor.SideFpsOverlayInteractor
+import com.android.systemui.biometrics.domain.interactor.SideFpsOverlayInteractorImpl
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.util.concurrency.ThreadFactory
 import dagger.Binds
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/DisplayStateInteractor.kt b/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/DisplayStateInteractor.kt
index 26b6f2a..2e734a3 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/DisplayStateInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/DisplayStateInteractor.kt
@@ -18,13 +18,16 @@
 
 import android.content.Context
 import android.content.res.Configuration
+import android.view.Display
 import com.android.systemui.biometrics.data.repository.RearDisplayStateRepository
 import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging
 import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
 import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.display.data.repository.DisplayRepository
 import com.android.systemui.unfold.compat.ScreenSizeFoldProvider
 import com.android.systemui.unfold.updates.FoldProvider
+import com.android.systemui.util.kotlin.sample
 import java.util.concurrent.Executor
 import javax.inject.Inject
 import kotlinx.coroutines.CoroutineScope
@@ -32,10 +35,14 @@
 import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.SharingStarted
 import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.filter
+import kotlinx.coroutines.flow.map
 import kotlinx.coroutines.flow.stateIn
 
 /** Aggregates display state information. */
 interface DisplayStateInteractor {
+    /** Whether the default display is currently off. */
+    val isDefaultDisplayOff: Flow<Boolean>
 
     /** Whether the device is currently in rear display mode. */
     val isInRearDisplayMode: StateFlow<Boolean>
@@ -55,6 +62,7 @@
     @Application context: Context,
     @Main mainExecutor: Executor,
     rearDisplayStateRepository: RearDisplayStateRepository,
+    displayRepository: DisplayRepository,
 ) : DisplayStateInteractor {
     private var screenSizeFoldProvider: ScreenSizeFoldProvider = ScreenSizeFoldProvider(context)
 
@@ -96,6 +104,17 @@
         screenSizeFoldProvider.onConfigurationChange(newConfig)
     }
 
+    private val defaultDisplay =
+        displayRepository.displays.map { displays ->
+            displays.firstOrNull { it.displayId == Display.DEFAULT_DISPLAY }
+        }
+
+    override val isDefaultDisplayOff =
+        displayRepository.displayChangeEvent
+            .filter { it == Display.DEFAULT_DISPLAY }
+            .sample(defaultDisplay)
+            .map { it?.state == Display.STATE_OFF }
+
     companion object {
         private const val TAG = "DisplayStateInteractor"
     }
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/PromptSelectorInteractor.kt b/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/PromptSelectorInteractor.kt
index a6ad24e..65a2c0a 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/PromptSelectorInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/PromptSelectorInteractor.kt
@@ -23,9 +23,9 @@
 import com.android.systemui.biometrics.Utils.isDeviceCredentialAllowed
 import com.android.systemui.biometrics.data.repository.FingerprintPropertyRepository
 import com.android.systemui.biometrics.data.repository.PromptRepository
-import com.android.systemui.biometrics.domain.model.BiometricModalities
 import com.android.systemui.biometrics.domain.model.BiometricOperationInfo
 import com.android.systemui.biometrics.domain.model.BiometricPromptRequest
+import com.android.systemui.biometrics.shared.model.BiometricModalities
 import com.android.systemui.biometrics.shared.model.BiometricUserInfo
 import com.android.systemui.biometrics.shared.model.FingerprintSensorType
 import com.android.systemui.biometrics.shared.model.PromptKind
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/UdfpsOverlayInteractor.kt b/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/UdfpsOverlayInteractor.kt
index 9a0792e..2a1047a 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/UdfpsOverlayInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/UdfpsOverlayInteractor.kt
@@ -18,8 +18,8 @@
 
 import android.view.MotionEvent
 import com.android.keyguard.KeyguardUpdateMonitor
-import com.android.settingslib.udfps.UdfpsOverlayParams
 import com.android.systemui.biometrics.AuthController
+import com.android.systemui.biometrics.shared.model.UdfpsOverlayParams
 import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging
 import com.android.systemui.common.coroutine.ConflatedCallbackFlow
 import com.android.systemui.dagger.SysUISingleton
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/domain/model/BiometricPromptRequest.kt b/packages/SystemUI/src/com/android/systemui/biometrics/domain/model/BiometricPromptRequest.kt
index a3ee220..8fbb250 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/domain/model/BiometricPromptRequest.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/domain/model/BiometricPromptRequest.kt
@@ -1,6 +1,7 @@
 package com.android.systemui.biometrics.domain.model
 
 import android.hardware.biometrics.PromptInfo
+import com.android.systemui.biometrics.shared.model.BiometricModalities
 import com.android.systemui.biometrics.shared.model.BiometricUserInfo
 
 /**
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/shared/model/PromptKind.kt b/packages/SystemUI/src/com/android/systemui/biometrics/shared/model/PromptKind.kt
index 416fc64..a97e2dc 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/shared/model/PromptKind.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/shared/model/PromptKind.kt
@@ -17,7 +17,6 @@
 package com.android.systemui.biometrics.shared.model
 
 import com.android.systemui.biometrics.Utils
-import com.android.systemui.biometrics.domain.model.BiometricModalities
 
 // TODO(b/251476085): this should eventually replace Utils.CredentialType
 /** Credential options for biometric prompt. Shadows [Utils.CredentialType]. */
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/udfps/SinglePointerTouchProcessor.kt b/packages/SystemUI/src/com/android/systemui/biometrics/udfps/SinglePointerTouchProcessor.kt
index eeb0f4c..83b3380 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/udfps/SinglePointerTouchProcessor.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/udfps/SinglePointerTouchProcessor.kt
@@ -21,7 +21,7 @@
 import android.view.MotionEvent
 import android.view.MotionEvent.INVALID_POINTER_ID
 import android.view.Surface
-import com.android.settingslib.udfps.UdfpsOverlayParams
+import com.android.systemui.biometrics.shared.model.UdfpsOverlayParams
 import com.android.systemui.biometrics.udfps.TouchProcessorResult.Failure
 import com.android.systemui.biometrics.udfps.TouchProcessorResult.ProcessedTouch
 import com.android.systemui.dagger.SysUISingleton
@@ -41,7 +41,6 @@
         previousPointerOnSensorId: Int,
         overlayParams: UdfpsOverlayParams,
     ): TouchProcessorResult {
-
         fun preprocess(): PreprocessedTouch {
             val touchData = List(event.pointerCount) { event.normalize(it, overlayParams) }
             val pointersOnSensor =
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/udfps/TouchProcessor.kt b/packages/SystemUI/src/com/android/systemui/biometrics/udfps/TouchProcessor.kt
index 4bf0ef6..78e1dc0 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/udfps/TouchProcessor.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/udfps/TouchProcessor.kt
@@ -17,7 +17,7 @@
 package com.android.systemui.biometrics.udfps
 
 import android.view.MotionEvent
-import com.android.settingslib.udfps.UdfpsOverlayParams
+import com.android.systemui.biometrics.shared.model.UdfpsOverlayParams
 
 /**
  * Determines whether a finger entered or left the area of the under-display fingerprint sensor
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewBinder.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewBinder.kt
index b1439fd..d616dcc 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewBinder.kt
@@ -23,7 +23,6 @@
 import android.hardware.biometrics.BiometricConstants
 import android.hardware.biometrics.BiometricPrompt
 import android.hardware.face.FaceManager
-import android.os.Bundle
 import android.text.method.ScrollingMovementMethod
 import android.util.Log
 import android.view.HapticFeedbackConstants
@@ -43,12 +42,9 @@
 import com.android.systemui.biometrics.AuthBiometricFaceIconController
 import com.android.systemui.biometrics.AuthBiometricFingerprintAndFaceIconController
 import com.android.systemui.biometrics.AuthBiometricFingerprintIconController
-import com.android.systemui.biometrics.AuthBiometricView
-import com.android.systemui.biometrics.AuthBiometricView.Callback
-import com.android.systemui.biometrics.AuthBiometricViewAdapter
 import com.android.systemui.biometrics.AuthIconController
 import com.android.systemui.biometrics.AuthPanelController
-import com.android.systemui.biometrics.domain.model.BiometricModalities
+import com.android.systemui.biometrics.shared.model.BiometricModalities
 import com.android.systemui.biometrics.shared.model.BiometricModality
 import com.android.systemui.biometrics.shared.model.PromptKind
 import com.android.systemui.biometrics.shared.model.asBiometricModality
@@ -63,7 +59,6 @@
 import com.android.systemui.statusbar.VibratorHelper
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.delay
-import kotlinx.coroutines.flow.collect
 import kotlinx.coroutines.flow.combine
 import kotlinx.coroutines.flow.first
 import kotlinx.coroutines.flow.map
@@ -83,11 +78,11 @@
         panelViewController: AuthPanelController,
         jankListener: BiometricJankListener,
         backgroundView: View,
-        legacyCallback: Callback,
+        legacyCallback: Spaghetti.Callback,
         applicationScope: CoroutineScope,
         vibratorHelper: VibratorHelper,
         featureFlags: FeatureFlags,
-    ): AuthBiometricViewAdapter {
+    ): Spaghetti {
         val accessibilityManager = view.context.getSystemService(AccessibilityManager::class.java)!!
 
         val textColorError =
@@ -141,24 +136,20 @@
             subtitleView.text = viewModel.subtitle.first()
 
             // set button listeners
-            negativeButton.setOnClickListener {
-                legacyCallback.onAction(Callback.ACTION_BUTTON_NEGATIVE)
-            }
-            cancelButton.setOnClickListener {
-                legacyCallback.onAction(Callback.ACTION_USER_CANCELED)
-            }
+            negativeButton.setOnClickListener { legacyCallback.onButtonNegative() }
+            cancelButton.setOnClickListener { legacyCallback.onUserCanceled() }
             credentialFallbackButton.setOnClickListener {
                 viewModel.onSwitchToCredential()
-                legacyCallback.onAction(Callback.ACTION_USE_DEVICE_CREDENTIAL)
+                legacyCallback.onUseDeviceCredential()
             }
             confirmationButton.setOnClickListener { viewModel.confirmAuthenticated() }
             retryButton.setOnClickListener {
                 viewModel.showAuthenticating(isRetry = true)
-                legacyCallback.onAction(Callback.ACTION_BUTTON_TRY_AGAIN)
+                legacyCallback.onButtonTryAgain()
             }
 
             // TODO(b/251476085): migrate legacy icon controllers and remove
-            var legacyState: Int = viewModel.legacyState.value
+            var legacyState = viewModel.legacyState.value
             val iconController =
                 modalities.asIconController(
                     view.context,
@@ -219,7 +210,7 @@
                         oldMode == FingerprintStartMode.Pending &&
                             newMode == FingerprintStartMode.Delayed
                     ) {
-                        legacyCallback.onAction(Callback.ACTION_START_DELAYED_FINGERPRINT_SENSOR)
+                        legacyCallback.onStartDelayedFingerprintSensor()
                     }
 
                     if (newMode.isStarted) {
@@ -246,7 +237,7 @@
                         .collect { dismissOnClick ->
                             backgroundView.setOnClickListener {
                                 if (dismissOnClick) {
-                                    legacyCallback.onAction(Callback.ACTION_USER_CANCELED)
+                                    legacyCallback.onUserCanceled()
                                 } else {
                                     Log.w(TAG, "Ignoring background click")
                                 }
@@ -360,13 +351,11 @@
 
                             launch {
                                 delay(authState.delay)
-                                legacyCallback.onAction(
-                                    if (authState.isAuthenticatedAndExplicitlyConfirmed) {
-                                        Callback.ACTION_AUTHENTICATED_AND_CONFIRMED
-                                    } else {
-                                        Callback.ACTION_AUTHENTICATED
-                                    }
-                                )
+                                if (authState.isAuthenticatedAndExplicitlyConfirmed) {
+                                    legacyCallback.onAuthenticatedAndConfirmed()
+                                } else {
+                                    legacyCallback.onAuthenticated()
+                                }
                             }
                         }
                     }
@@ -428,21 +417,50 @@
  * the view model (which will be retained) via the application scope.
  *
  * Do not reference the [view] for anything other than [asView].
- *
- * TODO(b/251476085): remove after replacing AuthContainerView
  */
-private class Spaghetti(
+@Deprecated("TODO(b/251476085): remove after replacing AuthContainerView")
+class Spaghetti(
     private val view: View,
     private val viewModel: PromptViewModel,
     private val applicationContext: Context,
     private val applicationScope: CoroutineScope,
-) : AuthBiometricViewAdapter {
+) {
+
+    @Deprecated("TODO(b/251476085): remove after replacing AuthContainerView")
+    interface Callback {
+        fun onAuthenticated()
+        fun onUserCanceled()
+        fun onButtonNegative()
+        fun onButtonTryAgain()
+        fun onError()
+        fun onUseDeviceCredential()
+        fun onStartDelayedFingerprintSensor()
+        fun onAuthenticatedAndConfirmed()
+    }
+
+    @Deprecated("TODO(b/251476085): remove after replacing AuthContainerView")
+    enum class BiometricState {
+        /** Authentication hardware idle. */
+        STATE_IDLE,
+        /** UI animating in, authentication hardware active. */
+        STATE_AUTHENTICATING_ANIMATING_IN,
+        /** UI animated in, authentication hardware active. */
+        STATE_AUTHENTICATING,
+        /** UI animated in, authentication hardware active. */
+        STATE_HELP,
+        /** Hard error, e.g. ERROR_TIMEOUT. Authentication hardware idle. */
+        STATE_ERROR,
+        /** Authenticated, waiting for user confirmation. Authentication hardware idle. */
+        STATE_PENDING_CONFIRMATION,
+        /** Authenticated, dialog animating away soon. */
+        STATE_AUTHENTICATED,
+    }
 
     private var lifecycleScope: CoroutineScope? = null
     private var modalities: BiometricModalities = BiometricModalities()
     private var legacyCallback: Callback? = null
 
-    override var legacyIconController: AuthIconController? = null
+    var legacyIconController: AuthIconController? = null
         private set
 
     // hacky way to suppress lockout errors
@@ -478,7 +496,7 @@
         )
     }
 
-    override fun onDialogAnimatedIn(fingerprintWasStarted: Boolean) {
+    fun onDialogAnimatedIn(fingerprintWasStarted: Boolean) {
         if (fingerprintWasStarted) {
             viewModel.ensureFingerprintHasStarted(isDelayed = false)
             viewModel.showAuthenticating(modalities.asDefaultHelpMessage(applicationContext))
@@ -487,7 +505,7 @@
         }
     }
 
-    override fun onAuthenticationSucceeded(@BiometricAuthenticator.Modality modality: Int) {
+    fun onAuthenticationSucceeded(@BiometricAuthenticator.Modality modality: Int) {
         applicationScope.launch {
             val authenticatedModality = modality.asBiometricModality()
             val msgId = getHelpForSuccessfulAuthentication(authenticatedModality)
@@ -511,7 +529,7 @@
             else -> null
         }
 
-    override fun onAuthenticationFailed(
+    fun onAuthenticationFailed(
         @BiometricAuthenticator.Modality modality: Int,
         failureReason: String,
     ) {
@@ -533,7 +551,7 @@
         }
     }
 
-    override fun onError(modality: Int, error: String) {
+    fun onError(modality: Int, error: String) {
         val errorModality = modality.asBiometricModality()
         if (ignoreUnsuccessfulEventsFrom(errorModality, error)) {
             return
@@ -546,11 +564,11 @@
                 authenticateAfterError = modalities.hasFingerprint,
             )
             delay(BiometricPrompt.HIDE_DIALOG_DELAY.toLong())
-            legacyCallback?.onAction(Callback.ACTION_ERROR)
+            legacyCallback?.onError()
         }
     }
 
-    override fun onHelp(modality: Int, help: String) {
+    fun onHelp(modality: Int, help: String) {
         if (ignoreUnsuccessfulEventsFrom(modality.asBiometricModality(), "")) {
             return
         }
@@ -574,36 +592,20 @@
             else -> false
         }
 
-    override fun startTransitionToCredentialUI(isError: Boolean) {
+    fun startTransitionToCredentialUI(isError: Boolean) {
         applicationScope.launch {
             viewModel.onSwitchToCredential()
-            legacyCallback?.onAction(Callback.ACTION_USE_DEVICE_CREDENTIAL)
+            legacyCallback?.onUseDeviceCredential()
         }
     }
 
-    override fun requestLayout() {
-        // nothing, for legacy view...
-    }
-
-    override fun restoreState(bundle: Bundle?) {
-        // nothing, for legacy view...
-    }
-
-    override fun onSaveState(bundle: Bundle?) {
-        // nothing, for legacy view...
-    }
-
-    override fun onOrientationChanged() {
-        // nothing, for legacy view...
-    }
-
-    override fun cancelAnimation() {
+    fun cancelAnimation() {
         view.animate()?.cancel()
     }
 
-    override fun isCoex() = modalities.hasFaceAndFingerprint
+    fun isCoex() = modalities.hasFaceAndFingerprint
 
-    override fun asView() = view
+    fun asView() = view
 }
 
 private fun BiometricModalities.asDefaultHelpMessage(context: Context): String =
@@ -638,7 +640,7 @@
     iconViewOverlay: LottieAnimationView,
 ) : AuthBiometricFingerprintAndFaceIconController(context, iconView, iconViewOverlay) {
 
-    private var state: Int? = null
+    private var state: Spaghetti.BiometricState? = null
     private val faceController = AuthBiometricFaceIconController(context, iconView)
 
     var faceMode: Boolean = true
@@ -649,11 +651,14 @@
                 faceController.deactivated = !value
                 iconView.setImageIcon(null)
                 iconViewOverlay.setImageIcon(null)
-                state?.let { updateIcon(AuthBiometricView.STATE_IDLE, it) }
+                state?.let { updateIcon(Spaghetti.BiometricState.STATE_IDLE, it) }
             }
         }
 
-    override fun updateIcon(lastState: Int, newState: Int) {
+    override fun updateIcon(
+        lastState: Spaghetti.BiometricState,
+        newState: Spaghetti.BiometricState,
+    ) {
         if (deactivated) {
             return
         }
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewSizeBinder.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewSizeBinder.kt
index 370b36b..b9af031 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewSizeBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewSizeBinder.kt
@@ -31,7 +31,6 @@
 import androidx.core.view.isGone
 import androidx.lifecycle.lifecycleScope
 import com.android.systemui.R
-import com.android.systemui.biometrics.AuthDialog
 import com.android.systemui.biometrics.AuthPanelController
 import com.android.systemui.biometrics.Utils
 import com.android.systemui.biometrics.ui.BiometricPromptLayout
@@ -47,6 +46,10 @@
 /** Helper for [BiometricViewBinder] to handle resize transitions. */
 object BiometricViewSizeBinder {
 
+    private const val ANIMATE_SMALL_TO_MEDIUM_DURATION_MS = 150
+    // TODO(b/201510778): make private when related misuse is fixed
+    const val ANIMATE_MEDIUM_TO_LARGE_DURATION_MS = 450
+
     /** Resizes [BiometricPromptLayout] and the [panelViewController] via the [PromptViewModel]. */
     fun bind(
         view: BiometricPromptLayout,
@@ -134,7 +137,7 @@
                                     )
                                 }
                                 size.isMedium && currentSize.isSmall -> {
-                                    val duration = AuthDialog.ANIMATE_SMALL_TO_MEDIUM_DURATION_MS
+                                    val duration = ANIMATE_SMALL_TO_MEDIUM_DURATION_MS
                                     panelViewController.updateForContentDimensions(
                                         width,
                                         height,
@@ -165,7 +168,7 @@
                                     )
                                 }
                                 size.isLarge -> {
-                                    val duration = AuthDialog.ANIMATE_MEDIUM_TO_LARGE_DURATION_MS
+                                    val duration = ANIMATE_MEDIUM_TO_LARGE_DURATION_MS
                                     panelViewController.setUseFullScreen(true)
                                     panelViewController.updateForContentDimensions(
                                         panelViewController.containerWidth,
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/CredentialViewBinder.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/CredentialViewBinder.kt
index 25fe619..931946a 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/CredentialViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/CredentialViewBinder.kt
@@ -9,7 +9,6 @@
 import androidx.lifecycle.repeatOnLifecycle
 import com.android.app.animation.Interpolators
 import com.android.systemui.R
-import com.android.systemui.biometrics.AuthDialog
 import com.android.systemui.biometrics.AuthPanelController
 import com.android.systemui.biometrics.ui.CredentialPasswordView
 import com.android.systemui.biometrics.ui.CredentialPatternView
@@ -22,6 +21,8 @@
 import kotlinx.coroutines.flow.onEach
 import kotlinx.coroutines.launch
 
+private const val ANIMATE_CREDENTIAL_INITIAL_DURATION_MS = 150
+
 /**
  * View binder for all credential variants of BiometricPrompt, including [CredentialPatternView] and
  * [CredentialPasswordView].
@@ -147,7 +148,7 @@
     postOnAnimation {
         animate()
             .translationY(0f)
-            .setDuration(AuthDialog.ANIMATE_CREDENTIAL_INITIAL_DURATION_MS.toLong())
+            .setDuration(ANIMATE_CREDENTIAL_INITIAL_DURATION_MS.toLong())
             .alpha(1f)
             .setInterpolator(Interpolators.LINEAR_OUT_SLOW_IN)
             .withLayer()
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/controller/UdfpsKeyguardViewController.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/controller/UdfpsKeyguardViewController.kt
index c9b1624..6f4e1a3 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/ui/controller/UdfpsKeyguardViewController.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/controller/UdfpsKeyguardViewController.kt
@@ -19,11 +19,11 @@
 import com.android.systemui.biometrics.UdfpsAnimationViewController
 import com.android.systemui.biometrics.UdfpsKeyguardView
 import com.android.systemui.bouncer.domain.interactor.AlternateBouncerInteractor
+import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerInteractor
 import com.android.systemui.dump.DumpManager
 import com.android.systemui.keyguard.ui.adapter.UdfpsKeyguardViewControllerAdapter
 import com.android.systemui.keyguard.ui.viewmodel.UdfpsKeyguardViewModels
 import com.android.systemui.plugins.statusbar.StatusBarStateController
-import com.android.systemui.shade.ShadeExpansionStateManager
 import com.android.systemui.statusbar.phone.SystemUIDialogManager
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 
@@ -32,7 +32,7 @@
 open class UdfpsKeyguardViewController(
     val view: UdfpsKeyguardView,
     statusBarStateController: StatusBarStateController,
-    shadeExpansionStateManager: ShadeExpansionStateManager,
+    primaryBouncerInteractor: PrimaryBouncerInteractor,
     systemUIDialogManager: SystemUIDialogManager,
     dumpManager: DumpManager,
     private val alternateBouncerInteractor: AlternateBouncerInteractor,
@@ -41,7 +41,7 @@
     UdfpsAnimationViewController<UdfpsKeyguardView>(
         view,
         statusBarStateController,
-        shadeExpansionStateManager,
+        primaryBouncerInteractor,
         systemUIDialogManager,
         dumpManager,
     ),
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModel.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModel.kt
index be08932..6269700 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModel.kt
@@ -19,12 +19,12 @@
 import android.util.Log
 import android.view.HapticFeedbackConstants
 import android.view.MotionEvent
-import com.android.systemui.biometrics.AuthBiometricView
 import com.android.systemui.biometrics.domain.interactor.DisplayStateInteractor
 import com.android.systemui.biometrics.domain.interactor.PromptSelectorInteractor
-import com.android.systemui.biometrics.domain.model.BiometricModalities
+import com.android.systemui.biometrics.shared.model.BiometricModalities
 import com.android.systemui.biometrics.shared.model.BiometricModality
 import com.android.systemui.biometrics.shared.model.PromptKind
+import com.android.systemui.biometrics.ui.binder.Spaghetti
 import com.android.systemui.flags.FeatureFlags
 import com.android.systemui.flags.Flags.ONE_WAY_HAPTICS_API_MIGRATION
 import com.android.systemui.statusbar.VibratorHelper
@@ -62,8 +62,8 @@
             .distinctUntilChanged()
 
     // TODO(b/251476085): remove after icon controllers are migrated - do not keep this state
-    private var _legacyState = MutableStateFlow(AuthBiometricView.STATE_IDLE)
-    val legacyState: StateFlow<Int> = _legacyState.asStateFlow()
+    private var _legacyState = MutableStateFlow(Spaghetti.BiometricState.STATE_IDLE)
+    val legacyState: StateFlow<Spaghetti.BiometricState> = _legacyState.asStateFlow()
 
     private val _isAuthenticating: MutableStateFlow<Boolean> = MutableStateFlow(false)
 
@@ -270,7 +270,7 @@
         _isAuthenticated.value = PromptAuthState(false)
         _forceMediumSize.value = true
         _message.value = PromptMessage.Error(message)
-        _legacyState.value = AuthBiometricView.STATE_ERROR
+        _legacyState.value = Spaghetti.BiometricState.STATE_ERROR
 
         if (hapticFeedback) {
             vibrator.error(failedModality)
@@ -322,13 +322,13 @@
         _forceMediumSize.value = true
         _legacyState.value =
             if (alreadyAuthenticated && isConfirmationRequired.first()) {
-                AuthBiometricView.STATE_PENDING_CONFIRMATION
+                Spaghetti.BiometricState.STATE_PENDING_CONFIRMATION
             } else if (alreadyAuthenticated && !isConfirmationRequired.first()) {
-                AuthBiometricView.STATE_AUTHENTICATED
+                Spaghetti.BiometricState.STATE_AUTHENTICATED
             } else if (clearIconError) {
-                AuthBiometricView.STATE_IDLE
+                Spaghetti.BiometricState.STATE_IDLE
             } else {
-                AuthBiometricView.STATE_HELP
+                Spaghetti.BiometricState.STATE_HELP
             }
 
         messageJob?.cancel()
@@ -353,7 +353,7 @@
         _message.value =
             if (message.isNotBlank()) PromptMessage.Help(message) else PromptMessage.Empty
         _forceMediumSize.value = true
-        _legacyState.value = AuthBiometricView.STATE_HELP
+        _legacyState.value = Spaghetti.BiometricState.STATE_HELP
 
         messageJob?.cancel()
         messageJob = launch {
@@ -373,7 +373,7 @@
         _isAuthenticating.value = true
         _isAuthenticated.value = PromptAuthState(false)
         _message.value = if (message.isBlank()) PromptMessage.Empty else PromptMessage.Help(message)
-        _legacyState.value = AuthBiometricView.STATE_AUTHENTICATING
+        _legacyState.value = Spaghetti.BiometricState.STATE_AUTHENTICATING
 
         // reset the try again button(s) after the user attempts a retry
         if (isRetry) {
@@ -406,9 +406,9 @@
         _message.value = PromptMessage.Empty
         _legacyState.value =
             if (needsUserConfirmation) {
-                AuthBiometricView.STATE_PENDING_CONFIRMATION
+                Spaghetti.BiometricState.STATE_PENDING_CONFIRMATION
             } else {
-                AuthBiometricView.STATE_AUTHENTICATED
+                Spaghetti.BiometricState.STATE_AUTHENTICATED
             }
 
         if (!needsUserConfirmation) {
@@ -449,7 +449,7 @@
 
         _isAuthenticated.value = authState.asExplicitlyConfirmed()
         _message.value = PromptMessage.Empty
-        _legacyState.value = AuthBiometricView.STATE_AUTHENTICATED
+        _legacyState.value = Spaghetti.BiometricState.STATE_AUTHENTICATED
 
         vibrator.success(authState.authenticatedModality)
 
diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/data/repository/KeyguardBouncerRepository.kt b/packages/SystemUI/src/com/android/systemui/bouncer/data/repository/KeyguardBouncerRepository.kt
index f2b4e09..c0b2153 100644
--- a/packages/SystemUI/src/com/android/systemui/bouncer/data/repository/KeyguardBouncerRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/bouncer/data/repository/KeyguardBouncerRepository.kt
@@ -28,8 +28,11 @@
 import com.android.systemui.util.time.SystemClock
 import javax.inject.Inject
 import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableSharedFlow
 import kotlinx.coroutines.flow.MutableStateFlow
 import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.asSharedFlow
 import kotlinx.coroutines.flow.asStateFlow
 import kotlinx.coroutines.flow.filterNotNull
 import kotlinx.coroutines.flow.launchIn
@@ -47,8 +50,10 @@
     val primaryBouncerShowingSoon: StateFlow<Boolean>
     val primaryBouncerStartingToHide: StateFlow<Boolean>
     val primaryBouncerStartingDisappearAnimation: StateFlow<Runnable?>
+
     /** Determines if we want to instantaneously show the primary bouncer instead of translating. */
     val primaryBouncerScrimmed: StateFlow<Boolean>
+
     /**
      * Set how much of the notification panel is showing on the screen.
      *
@@ -60,8 +65,23 @@
     val panelExpansionAmount: StateFlow<Float>
     val keyguardPosition: StateFlow<Float?>
     val isBackButtonEnabled: StateFlow<Boolean?>
-    /** Determines if user is already unlocked */
-    val keyguardAuthenticated: StateFlow<Boolean?>
+
+    /**
+     * Triggers when the user has successfully used biometrics to authenticate. True = biometrics
+     * used to authenticate is Class 3, else false. When null, biometrics haven't authenticated the
+     * device.
+     */
+    val keyguardAuthenticatedBiometrics: StateFlow<Boolean?>
+
+    /**
+     * Triggers when the given userId (Int) has successfully used primary authentication to
+     * authenticate
+     */
+    val keyguardAuthenticatedPrimaryAuth: Flow<Int>
+
+    /** Triggers when the given userId (Int) has requested the bouncer when already authenticated */
+    val userRequestedBouncerWhenAlreadyAuthenticated: Flow<Int>
+
     val showMessage: StateFlow<BouncerShowMessageModel?>
     val resourceUpdateRequests: StateFlow<Boolean>
     val alternateBouncerVisible: StateFlow<Boolean>
@@ -88,7 +108,11 @@
 
     fun setShowMessage(bouncerShowMessageModel: BouncerShowMessageModel?)
 
-    fun setKeyguardAuthenticated(keyguardAuthenticated: Boolean?)
+    fun setKeyguardAuthenticatedBiometrics(keyguardAuthenticatedBiometrics: Boolean?)
+
+    suspend fun setKeyguardAuthenticatedPrimaryAuth(userId: Int)
+
+    suspend fun setUserRequestedBouncerWhenAlreadyAuthenticated(userId: Int)
 
     fun setIsBackButtonEnabled(isBackButtonEnabled: Boolean)
 
@@ -117,9 +141,11 @@
     private val _primaryBouncerDisappearAnimation = MutableStateFlow<Runnable?>(null)
     override val primaryBouncerStartingDisappearAnimation =
         _primaryBouncerDisappearAnimation.asStateFlow()
+
     /** Determines if we want to instantaneously show the primary bouncer instead of translating. */
     private val _primaryBouncerScrimmed = MutableStateFlow(false)
     override val primaryBouncerScrimmed = _primaryBouncerScrimmed.asStateFlow()
+
     /**
      * Set how much of the notification panel is showing on the screen.
      *
@@ -134,13 +160,26 @@
     override val keyguardPosition = _keyguardPosition.asStateFlow()
     private val _isBackButtonEnabled = MutableStateFlow<Boolean?>(null)
     override val isBackButtonEnabled = _isBackButtonEnabled.asStateFlow()
-    private val _keyguardAuthenticated = MutableStateFlow<Boolean?>(null)
-    /** Determines if user is already unlocked */
-    override val keyguardAuthenticated = _keyguardAuthenticated.asStateFlow()
+
+    /** Whether the user is already unlocked by biometrics */
+    private val _keyguardAuthenticatedBiometrics = MutableStateFlow<Boolean?>(null)
+    override val keyguardAuthenticatedBiometrics = _keyguardAuthenticatedBiometrics.asStateFlow()
+
+    /** Whether the user is unlocked via a primary authentication method (pin/pattern/password). */
+    private val _keyguardAuthenticatedPrimaryAuth = MutableSharedFlow<Int>()
+    override val keyguardAuthenticatedPrimaryAuth: Flow<Int> =
+        _keyguardAuthenticatedPrimaryAuth.asSharedFlow()
+
+    /** Whether the user requested to show the bouncer when device is already authenticated */
+    private val _userRequestedBouncerWhenAlreadyAuthenticated = MutableSharedFlow<Int>()
+    override val userRequestedBouncerWhenAlreadyAuthenticated: Flow<Int> =
+        _userRequestedBouncerWhenAlreadyAuthenticated.asSharedFlow()
+
     private val _showMessage = MutableStateFlow<BouncerShowMessageModel?>(null)
     override val showMessage = _showMessage.asStateFlow()
     private val _resourceUpdateRequests = MutableStateFlow(false)
     override val resourceUpdateRequests = _resourceUpdateRequests.asStateFlow()
+
     /** Values associated with the AlternateBouncer */
     private val _alternateBouncerVisible = MutableStateFlow(false)
     override val alternateBouncerVisible = _alternateBouncerVisible.asStateFlow()
@@ -204,8 +243,16 @@
         _showMessage.value = bouncerShowMessageModel
     }
 
-    override fun setKeyguardAuthenticated(keyguardAuthenticated: Boolean?) {
-        _keyguardAuthenticated.value = keyguardAuthenticated
+    override fun setKeyguardAuthenticatedBiometrics(keyguardAuthenticatedBiometrics: Boolean?) {
+        _keyguardAuthenticatedBiometrics.value = keyguardAuthenticatedBiometrics
+    }
+
+    override suspend fun setKeyguardAuthenticatedPrimaryAuth(userId: Int) {
+        _keyguardAuthenticatedPrimaryAuth.emit(userId)
+    }
+
+    override suspend fun setUserRequestedBouncerWhenAlreadyAuthenticated(userId: Int) {
+        _userRequestedBouncerWhenAlreadyAuthenticated.emit(userId)
     }
 
     override fun setIsBackButtonEnabled(isBackButtonEnabled: Boolean) {
diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractor.kt b/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractor.kt
index 9527f32..abddb0a 100644
--- a/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractor.kt
@@ -92,6 +92,9 @@
     /** Whether the pattern should be visible for the currently-selected user. */
     val isPatternVisible: StateFlow<Boolean> = authenticationInteractor.isPatternVisible
 
+    /** The minimal length of a pattern. */
+    val minPatternLength = authenticationInteractor.minPatternLength
+
     init {
         if (flags.isEnabled()) {
             // Clear the message if moved from throttling to no-longer throttling.
@@ -204,12 +207,24 @@
                 loggingReason = "successful authentication",
             )
         } else {
-            repository.setMessage(errorMessage(authenticationInteractor.getAuthenticationMethod()))
+            showErrorMessage()
         }
 
         return isAuthenticated
     }
 
+    /**
+     * Shows the error message.
+     *
+     * Callers should use this instead of [authenticate] when they know ahead of time that an auth
+     * attempt will fail but aren't interested in the other side effects like triggering throttling.
+     * For example, if the user entered a pattern that's too short, the system can show the error
+     * message without having the attempt trigger throttling.
+     */
+    suspend fun showErrorMessage() {
+        repository.setMessage(errorMessage(authenticationInteractor.getAuthenticationMethod()))
+    }
+
     private fun promptMessage(authMethod: AuthenticationMethodModel): String {
         return when (authMethod) {
             is AuthenticationMethodModel.Pin ->
diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/PrimaryBouncerInteractor.kt b/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/PrimaryBouncerInteractor.kt
index 0e0f1f6..579f0b7 100644
--- a/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/PrimaryBouncerInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/PrimaryBouncerInteractor.kt
@@ -79,14 +79,18 @@
 ) {
     private val passiveAuthBouncerDelay =
         context.resources.getInteger(R.integer.primary_bouncer_passive_auth_delay).toLong()
+
     /** Runnable to show the primary bouncer. */
     val showRunnable = Runnable {
         repository.setPrimaryShow(true)
         repository.setPrimaryShowingSoon(false)
         primaryBouncerCallbackInteractor.dispatchVisibilityChanged(View.VISIBLE)
     }
-
-    val keyguardAuthenticated: Flow<Boolean> = repository.keyguardAuthenticated.filterNotNull()
+    val keyguardAuthenticatedPrimaryAuth: Flow<Int> = repository.keyguardAuthenticatedPrimaryAuth
+    val keyguardAuthenticatedBiometrics: Flow<Boolean> =
+        repository.keyguardAuthenticatedBiometrics.filterNotNull()
+    val userRequestedBouncerWhenAlreadyAuthenticated: Flow<Int> =
+        repository.userRequestedBouncerWhenAlreadyAuthenticated.filterNotNull()
     val isShowing: StateFlow<Boolean> = repository.primaryBouncerShow
     val startingToHide: Flow<Unit> = repository.primaryBouncerStartingToHide.filter { it }.map {}
     val isBackButtonEnabled: Flow<Boolean> = repository.isBackButtonEnabled.filterNotNull()
@@ -96,6 +100,7 @@
     val resourceUpdateRequests: Flow<Boolean> = repository.resourceUpdateRequests.filter { it }
     val keyguardPosition: Flow<Float> = repository.keyguardPosition.filterNotNull()
     val panelExpansionAmount: Flow<Float> = repository.panelExpansionAmount
+
     /** 0f = bouncer fully hidden. 1f = bouncer fully visible. */
     val bouncerExpansion: Flow<Float> =
         combine(repository.panelExpansionAmount, repository.primaryBouncerShow) {
@@ -107,6 +112,7 @@
                 0f
             }
         }
+
     /** Allow for interaction when just about fully visible */
     val isInteractable: Flow<Boolean> = bouncerExpansion.map { it > 0.9 }
     val sideFpsShowing: Flow<Boolean> = repository.sideFpsShowing
@@ -144,7 +150,7 @@
     @JvmOverloads
     fun show(isScrimmed: Boolean) {
         // Reset some states as we show the bouncer.
-        repository.setKeyguardAuthenticated(null)
+        repository.setKeyguardAuthenticatedBiometrics(null)
         repository.setPrimaryStartingToHide(false)
 
         val resumeBouncer =
@@ -268,9 +274,19 @@
         repository.setResourceUpdateRequests(true)
     }
 
-    /** Tell the bouncer that keyguard is authenticated. */
-    fun notifyKeyguardAuthenticated(strongAuth: Boolean) {
-        repository.setKeyguardAuthenticated(strongAuth)
+    /** Tell the bouncer that keyguard is authenticated with primary authentication. */
+    fun notifyKeyguardAuthenticatedPrimaryAuth(userId: Int) {
+        applicationScope.launch { repository.setKeyguardAuthenticatedPrimaryAuth(userId) }
+    }
+
+    /** Tell the bouncer that bouncer is requested when device is already authenticated */
+    fun notifyUserRequestedBouncerWhenAlreadyAuthenticated(userId: Int) {
+        applicationScope.launch { repository.setKeyguardAuthenticatedPrimaryAuth(userId) }
+    }
+
+    /** Tell the bouncer that keyguard is authenticated with biometrics. */
+    fun notifyKeyguardAuthenticatedBiometrics(strongAuth: Boolean) {
+        repository.setKeyguardAuthenticatedBiometrics(strongAuth)
     }
 
     /** Update the position of the bouncer when showing. */
@@ -280,7 +296,7 @@
 
     /** Notifies that the state change was handled. */
     fun notifyKeyguardAuthenticatedHandled() {
-        repository.setKeyguardAuthenticated(null)
+        repository.setKeyguardAuthenticatedBiometrics(null)
     }
 
     /** Notifies that the message was shown. */
diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/KeyguardBouncerViewModel.kt b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/KeyguardBouncerViewModel.kt
index 6ba8439..649ae2f 100644
--- a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/KeyguardBouncerViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/KeyguardBouncerViewModel.kt
@@ -59,7 +59,7 @@
     val bouncerShowMessage: Flow<BouncerShowMessageModel> = interactor.showMessage
 
     /** Observe whether keyguard is authenticated already. */
-    val keyguardAuthenticated: Flow<Boolean> = interactor.keyguardAuthenticated
+    val keyguardAuthenticated: Flow<Boolean> = interactor.keyguardAuthenticatedBiometrics
 
     /** Observe whether the side fps is showing. */
     val sideFpsShowing: Flow<Boolean> = interactor.sideFpsShowing
diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModel.kt b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModel.kt
index 80a41ce..d214797 100644
--- a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModel.kt
@@ -40,20 +40,21 @@
 
     /** Notifies that the UI has been shown to the user. */
     fun onShown() {
+        _password.value = ""
         interactor.resetMessage()
     }
 
     /** Notifies that the user has changed the password input. */
-    fun onPasswordInputChanged(password: String) {
-        if (this.password.value.isEmpty() && password.isNotEmpty()) {
+    fun onPasswordInputChanged(newPassword: String) {
+        if (this.password.value.isEmpty() && newPassword.isNotEmpty()) {
             interactor.clearMessage()
         }
 
-        if (password.isNotEmpty()) {
+        if (newPassword.isNotEmpty()) {
             interactor.onIntentionalUserInput()
         }
 
-        _password.value = password
+        _password.value = newPassword
     }
 
     /** Notifies that the user has pressed the key for attempting to authenticate the password. */
diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PatternBouncerViewModel.kt b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PatternBouncerViewModel.kt
index 85eaf0b..364b5e7 100644
--- a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PatternBouncerViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PatternBouncerViewModel.kt
@@ -170,7 +170,9 @@
         _selectedDots.value = linkedSetOf()
 
         applicationScope.launch {
-            if (interactor.authenticate(pattern) != true) {
+            if (pattern.size < interactor.minPatternLength) {
+                interactor.showErrorMessage()
+            } else if (interactor.authenticate(pattern) != true) {
                 showFailureAnimation()
             }
         }
diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModel.kt b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModel.kt
index ebf939b..dc5c528 100644
--- a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModel.kt
@@ -70,13 +70,7 @@
     /** Appearance of the confirm button. */
     val confirmButtonAppearance: StateFlow<ActionButtonAppearance> =
         interactor.isAutoConfirmEnabled
-            .map {
-                if (it) {
-                    ActionButtonAppearance.Hidden
-                } else {
-                    ActionButtonAppearance.Shown
-                }
-            }
+            .map { if (it) ActionButtonAppearance.Hidden else ActionButtonAppearance.Shown }
             .stateIn(
                 scope = applicationScope,
                 started = SharingStarted.Eagerly,
@@ -85,6 +79,7 @@
 
     /** Notifies that the UI has been shown to the user. */
     fun onShown() {
+        clearPinInput()
         interactor.resetMessage()
     }
 
@@ -113,7 +108,7 @@
 
     /** Notifies that the user long-pressed the backspace button. */
     fun onBackspaceButtonLongPressed() {
-        mutablePinInput.value = mutablePinInput.value.clearAll()
+        clearPinInput()
     }
 
     /** Notifies that the user clicked the "enter" button. */
@@ -121,6 +116,10 @@
         tryAuthenticate(useAutoConfirm = false)
     }
 
+    private fun clearPinInput() {
+        mutablePinInput.value = mutablePinInput.value.clearAll()
+    }
+
     private fun tryAuthenticate(useAutoConfirm: Boolean) {
         val pinCode = mutablePinInput.value.getPin()
 
@@ -133,7 +132,7 @@
 
             // TODO(b/291528545): this should not be cleared on success (at least until the view
             // is animated away).
-            mutablePinInput.value = mutablePinInput.value.clearAll()
+            clearPinInput()
         }
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/FrameworkServicesModule.java b/packages/SystemUI/src/com/android/systemui/dagger/FrameworkServicesModule.java
index c9517c2..da5e933 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/FrameworkServicesModule.java
+++ b/packages/SystemUI/src/com/android/systemui/dagger/FrameworkServicesModule.java
@@ -29,6 +29,7 @@
 import android.app.KeyguardManager;
 import android.app.NotificationManager;
 import android.app.StatsManager;
+import android.app.StatusBarManager;
 import android.app.UiModeManager;
 import android.app.WallpaperManager;
 import android.app.admin.DevicePolicyManager;
@@ -681,4 +682,10 @@
     static TextClassificationManager provideTextClassificationManager(Context context) {
         return context.getSystemService(TextClassificationManager.class);
     }
+
+    @Provides
+    @Singleton
+    static StatusBarManager provideStatusBarManager(Context context) {
+        return context.getSystemService(StatusBarManager.class);
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt b/packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt
index 7ce7ce94..f12b919 100644
--- a/packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/dagger/SystemUICoreStartableModule.kt
@@ -34,9 +34,11 @@
 import com.android.systemui.globalactions.GlobalActionsComponent
 import com.android.systemui.keyboard.KeyboardUI
 import com.android.systemui.keyboard.PhysicalKeyboardCoreStartable
-import com.android.systemui.keyguard.KeyguardViewMediator
 import com.android.systemui.keyguard.KeyguardViewConfigurator
+import com.android.systemui.keyguard.KeyguardViewMediator
 import com.android.systemui.keyguard.data.quickaffordance.MuteQuickAffordanceCoreStartable
+import com.android.systemui.keyguard.ui.binder.KeyguardDismissActionBinder
+import com.android.systemui.keyguard.ui.binder.KeyguardDismissBinder
 import com.android.systemui.log.SessionTracker
 import com.android.systemui.media.RingtonePlayer
 import com.android.systemui.media.dialog.MediaOutputSwitcherDialogUI
@@ -74,12 +76,14 @@
 /**
  * Collection of {@link CoreStartable}s that should be run on AOSP.
  */
-@Module(includes = [
-    MultiUserUtilsModule::class,
-    StartControlsStartableModule::class,
-    StartBinderLoggerModule::class,
-    WallpaperModule::class,
-])
+@Module(
+    includes = [
+        MultiUserUtilsModule::class,
+        StartControlsStartableModule::class,
+        StartBinderLoggerModule::class,
+        WallpaperModule::class,
+    ]
+)
 abstract class SystemUICoreStartableModule {
     /** Inject into AuthController.  */
     @Binds
@@ -352,4 +356,14 @@
     @IntoMap
     @ClassKey(BackActionInteractor::class)
     abstract fun bindBackActionInteractor(impl: BackActionInteractor): CoreStartable
+
+    @Binds
+    @IntoMap
+    @ClassKey(KeyguardDismissActionBinder::class)
+    abstract fun bindKeyguardDismissActionBinder(impl: KeyguardDismissActionBinder): CoreStartable
+
+    @Binds
+    @IntoMap
+    @ClassKey(KeyguardDismissBinder::class)
+    abstract fun bindKeyguardDismissBinder(impl: KeyguardDismissBinder): CoreStartable
 }
diff --git a/packages/SystemUI/src/com/android/systemui/display/data/DisplayEvent.kt b/packages/SystemUI/src/com/android/systemui/display/data/DisplayEvent.kt
new file mode 100644
index 0000000..626a68f
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/display/data/DisplayEvent.kt
@@ -0,0 +1,24 @@
+/*
+ * 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.display.data
+
+sealed interface DisplayEvent {
+    val displayId: Int
+    data class Added(override val displayId: Int) : DisplayEvent
+    data class Removed(override val displayId: Int) : DisplayEvent
+    data class Changed(override val displayId: Int) : DisplayEvent
+}
diff --git a/packages/SystemUI/src/com/android/systemui/display/data/repository/DisplayRepository.kt b/packages/SystemUI/src/com/android/systemui/display/data/repository/DisplayRepository.kt
index 3444af4..1751358 100644
--- a/packages/SystemUI/src/com/android/systemui/display/data/repository/DisplayRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/display/data/repository/DisplayRepository.kt
@@ -29,6 +29,7 @@
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.display.data.DisplayEvent
 import com.android.systemui.util.Compile
 import com.android.systemui.util.traceSection
 import javax.inject.Inject
@@ -41,6 +42,7 @@
 import kotlinx.coroutines.flow.StateFlow
 import kotlinx.coroutines.flow.combine
 import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.filter
 import kotlinx.coroutines.flow.flowOn
 import kotlinx.coroutines.flow.map
 import kotlinx.coroutines.flow.onEach
@@ -48,7 +50,13 @@
 
 /** Provides a [Flow] of [Display] as returned by [DisplayManager]. */
 interface DisplayRepository {
-    /** Provides a nullable set of displays. */
+    /** Display change event indicating a change to the given displayId has occurred. */
+    val displayChangeEvent: Flow<Int>
+
+    /**
+     * Provides a nullable set of displays. Updates when new displays have been added or removed but
+     * not when a display's info has changed.
+     */
     val displays: Flow<Set<Display>>
 
     /**
@@ -86,33 +94,36 @@
     @Application applicationScope: CoroutineScope,
     @Background backgroundCoroutineDispatcher: CoroutineDispatcher
 ) : DisplayRepository {
-
     // Displays are enabled only after receiving them in [onDisplayAdded]
-    private val enabledDisplays: StateFlow<Set<Display>> =
-        conflatedCallbackFlow {
-                val callback =
-                    object : DisplayListener {
-                        override fun onDisplayAdded(displayId: Int) {
-                            trySend(getDisplays())
-                        }
+    private val allDisplayEvents: Flow<DisplayEvent> = conflatedCallbackFlow {
+        val callback =
+            object : DisplayListener {
+                override fun onDisplayAdded(displayId: Int) {
+                    trySend(DisplayEvent.Added(displayId))
+                }
 
-                        override fun onDisplayRemoved(displayId: Int) {
-                            trySend(getDisplays())
-                        }
+                override fun onDisplayRemoved(displayId: Int) {
+                    trySend(DisplayEvent.Removed(displayId))
+                }
 
-                        override fun onDisplayChanged(displayId: Int) {
-                            trySend(getDisplays())
-                        }
-                    }
-                displayManager.registerDisplayListener(
-                    callback,
-                    backgroundHandler,
-                    EVENT_FLAG_DISPLAY_ADDED or
-                        EVENT_FLAG_DISPLAY_CHANGED or
-                        EVENT_FLAG_DISPLAY_REMOVED,
-                )
-                awaitClose { displayManager.unregisterDisplayListener(callback) }
+                override fun onDisplayChanged(displayId: Int) {
+                    trySend(DisplayEvent.Changed(displayId))
+                }
             }
+        displayManager.registerDisplayListener(
+            callback,
+            backgroundHandler,
+            EVENT_FLAG_DISPLAY_ADDED or EVENT_FLAG_DISPLAY_CHANGED or EVENT_FLAG_DISPLAY_REMOVED,
+        )
+        awaitClose { displayManager.unregisterDisplayListener(callback) }
+    }
+
+    override val displayChangeEvent: Flow<Int> =
+        allDisplayEvents.filter { it is DisplayEvent.Changed }.map { it.displayId }
+
+    private val enabledDisplays =
+        allDisplayEvents
+            .map { getDisplays() }
             .flowOn(backgroundCoroutineDispatcher)
             .stateIn(
                 applicationScope,
diff --git a/packages/SystemUI/src/com/android/systemui/dreams/touch/BouncerSwipeTouchHandler.java b/packages/SystemUI/src/com/android/systemui/dreams/touch/BouncerSwipeTouchHandler.java
index 1cd3774..4e4b79c 100644
--- a/packages/SystemUI/src/com/android/systemui/dreams/touch/BouncerSwipeTouchHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/dreams/touch/BouncerSwipeTouchHandler.java
@@ -155,9 +155,12 @@
                         return true;
                     }
 
-                    // Don't set expansion if the user doesn't have a pin/password set.
+                    // Don't set expansion if the user doesn't have a pin/password set so that no
+                    // animations are played we're not transitioning to the bouncer.
                     if (!mLockPatternUtils.isSecure(mUserTracker.getUserId())) {
-                        return true;
+                        // Return false so the gesture is not consumed, allowing the dream to wake
+                        // if it wants instead of doing nothing.
+                        return false;
                     }
 
                     // For consistency, we adopt the expansion definition found in the
diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
index bb4fcc8..7e8f682 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
+++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
@@ -122,7 +122,7 @@
     // TODO(b/292213543): Tracking Bug
     @JvmField
     val NOTIFICATION_GROUP_EXPANSION_CHANGE =
-            unreleasedFlag("notification_group_expansion_change", teamfood = true)
+            unreleasedFlag("notification_group_expansion_change")
 
     // 200 - keyguard/lockscreen
     // ** Flag retired **
@@ -268,9 +268,9 @@
     val MIGRATE_SPLIT_KEYGUARD_BOTTOM_AREA =
         unreleasedFlag("migrate_split_keyguard_bottom_area", teamfood = true)
 
-    /** Whether to listen for fingerprint authentication over keyguard occluding activities. */
-    // TODO(b/283260512): Tracking bug.
-    @JvmField val FP_LISTEN_OCCLUDING_APPS = releasedFlag("fp_listen_occluding_apps")
+    // TODO(b/297037052): Tracking bug.
+    @JvmField
+    val REMOVE_NPVC_BOTTOM_AREA_USAGE = unreleasedFlag("remove_npvc_bottom_area_usage")
 
     /** Flag meant to guard the talkback fix for the KeyguardIndicationTextView */
     // TODO(b/286563884): Tracking bug
@@ -312,11 +312,6 @@
     val KEYGUARD_WM_STATE_REFACTOR: UnreleasedFlag =
             unreleasedFlag("keyguard_wm_state_refactor")
 
-    /** Stop running face auth when the display state changes to OFF. */
-    // TODO(b/294221702): Tracking bug.
-    @JvmField val STOP_FACE_AUTH_ON_DISPLAY_OFF = resourceBooleanFlag(
-            R.bool.flag_stop_face_auth_on_display_off, "stop_face_auth_on_display_off")
-
     /** Flag to disable the face scanning animation pulsing. */
     // TODO(b/295245791): Tracking bug.
     @JvmField val STOP_PULSING_FACE_SCANNING_ANIMATION = resourceBooleanFlag(
@@ -365,6 +360,10 @@
     @JvmField
     val QS_PIPELINE_AUTO_ADD = unreleasedFlag("qs_pipeline_auto_add", teamfood = true)
 
+    // TODO(b/296357483): Tracking Bug
+    @JvmField
+    val QS_PIPELINE_NEW_TILES = unreleasedFlag("qs_pipeline_new_tiles")
+
     // TODO(b/254512383): Tracking Bug
     @JvmField
     val FULL_SCREEN_USER_SWITCHER =
@@ -389,9 +388,6 @@
     // TODO(b/265892345): Tracking Bug
     val PLUG_IN_STATUS_BAR_CHIP = releasedFlag("plug_in_status_bar_chip")
 
-    // TODO(b/280426085): Tracking Bug
-    @JvmField val NEW_BLUETOOTH_REPOSITORY = releasedFlag("new_bluetooth_repository")
-
     // TODO(b/292533677): Tracking Bug
     val WIFI_TRACKER_LIB_FOR_WIFI_ICON = releasedFlag("wifi_tracker_lib_for_wifi_icon")
 
@@ -667,8 +663,6 @@
     // TODO(b/259264861): Tracking Bug
     @JvmField val UDFPS_NEW_TOUCH_DETECTION = releasedFlag("udfps_new_touch_detection")
     @JvmField val UDFPS_ELLIPSE_DETECTION = releasedFlag("udfps_ellipse_detection")
-    // TODO(b/278622168): Tracking Bug
-    @JvmField val BIOMETRIC_BP_STRONG = releasedFlag("biometric_bp_strong")
 
     // 2300 - stylus
     @JvmField val TRACK_STYLUS_EVER_USED = releasedFlag("track_stylus_ever_used")
@@ -746,8 +740,7 @@
     // 3000 - dream
     // TODO(b/285059790) : Tracking Bug
     @JvmField
-    val LOCKSCREEN_WALLPAPER_DREAM_ENABLED =
-        unreleasedFlag(name = "enable_lockscreen_wallpaper_dream", teamfood = true)
+    val LOCKSCREEN_WALLPAPER_DREAM_ENABLED = unreleasedFlag("enable_lockscreen_wallpaper_dream")
 
     // TODO(b/283084712): Tracking Bug
     @JvmField val IMPROVED_HUN_ANIMATIONS = unreleasedFlag("improved_hun_animations")
@@ -807,4 +800,8 @@
     // TODO(b/287205379): Tracking bug
     @JvmField
     val QS_CONTAINER_GRAPH_OPTIMIZER = unreleasedFlag( "qs_container_graph_optimizer")
+
+    /** Enable showing a dialog when clicking on Quick Settings bluetooth tile. */
+    @JvmField
+    val BLUETOOTH_QS_TILE_DIALOG = unreleasedFlag("bluetooth_qs_tile_dialog")
 }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/DeviceEntryFaceAuthRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/DeviceEntryFaceAuthRepository.kt
index b82e01b..6db21b2 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/DeviceEntryFaceAuthRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/DeviceEntryFaceAuthRepository.kt
@@ -26,6 +26,7 @@
 import com.android.keyguard.FaceAuthUiEvent
 import com.android.systemui.Dumpable
 import com.android.systemui.R
+import com.android.systemui.biometrics.domain.interactor.DisplayStateInteractor
 import com.android.systemui.bouncer.domain.interactor.AlternateBouncerInteractor
 import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging
 import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
@@ -161,6 +162,7 @@
     @FaceDetectTableLog private val faceDetectLog: TableLogBuffer,
     @FaceAuthTableLog private val faceAuthLog: TableLogBuffer,
     private val keyguardTransitionInteractor: KeyguardTransitionInteractor,
+    private val displayStateInteractor: DisplayStateInteractor,
     private val featureFlags: FeatureFlags,
     dumpManager: DumpManager,
 ) : DeviceEntryFaceAuthRepository, Dumpable {
@@ -363,6 +365,16 @@
     private fun gatingConditionsForAuthAndDetect(): Array<Pair<Flow<Boolean>, String>> {
         return arrayOf(
             Pair(
+                and(
+                        displayStateInteractor.isDefaultDisplayOff,
+                        keyguardRepository.wakefulness.map { it.isAwake() },
+                    )
+                    .isFalse(),
+                // this can happen if an app is requesting for screen off, the display can
+                // turn off without wakefulness.isStartingToSleepOrAsleep calls
+                "displayIsNotOffWhileAwake",
+            ),
+            Pair(
                 biometricSettingsRepository.isFaceAuthEnrolledAndEnabled,
                 "isFaceAuthEnrolledAndEnabled"
             ),
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 d399e4c..2557e81 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
@@ -35,8 +35,10 @@
 import com.android.systemui.keyguard.WakefulnessLifecycle
 import com.android.systemui.keyguard.shared.model.BiometricUnlockModel
 import com.android.systemui.keyguard.shared.model.BiometricUnlockSource
+import com.android.systemui.keyguard.shared.model.DismissAction
 import com.android.systemui.keyguard.shared.model.DozeStateModel
 import com.android.systemui.keyguard.shared.model.DozeTransitionModel
+import com.android.systemui.keyguard.shared.model.KeyguardDone
 import com.android.systemui.keyguard.shared.model.KeyguardRootViewVisibilityState
 import com.android.systemui.keyguard.shared.model.ScreenModel
 import com.android.systemui.keyguard.shared.model.StatusBarState
@@ -53,9 +55,11 @@
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.channels.awaitClose
 import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableSharedFlow
 import kotlinx.coroutines.flow.MutableStateFlow
 import kotlinx.coroutines.flow.SharingStarted
 import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.asSharedFlow
 import kotlinx.coroutines.flow.asStateFlow
 import kotlinx.coroutines.flow.distinctUntilChanged
 import kotlinx.coroutines.flow.flowOn
@@ -187,6 +191,12 @@
     /** Receive an event for doze time tick */
     val dozeTimeTick: Flow<Long>
 
+    /** Observable for DismissAction */
+    val dismissAction: StateFlow<DismissAction>
+
+    /** Observable updated when keyguardDone should be called either now or soon. */
+    val keyguardDone: Flow<KeyguardDone>
+
     /**
      * Returns `true` if the keyguard is showing; `false` otherwise.
      *
@@ -239,6 +249,10 @@
     fun setIsActiveDreamLockscreenHosted(isLockscreenHosted: Boolean)
 
     fun dozeTimeTick()
+
+    fun setDismissAction(dismissAction: DismissAction)
+
+    suspend fun setKeyguardDone(keyguardDoneType: KeyguardDone)
 }
 
 /** Encapsulates application state for the keyguard. */
@@ -261,6 +275,19 @@
     @Application private val scope: CoroutineScope,
     private val systemClock: SystemClock,
 ) : KeyguardRepository {
+    private val _dismissAction: MutableStateFlow<DismissAction> =
+        MutableStateFlow(DismissAction.None)
+    override val dismissAction = _dismissAction.asStateFlow()
+    override fun setDismissAction(dismissAction: DismissAction) {
+        _dismissAction.value = dismissAction
+    }
+
+    private val _keyguardDone: MutableSharedFlow<KeyguardDone> = MutableSharedFlow()
+    override val keyguardDone = _keyguardDone.asSharedFlow()
+    override suspend fun setKeyguardDone(keyguardDoneType: KeyguardDone) {
+        _keyguardDone.emit(keyguardDoneType)
+    }
+
     private val _animateBottomAreaDozingTransitions = MutableStateFlow(false)
     override val animateBottomAreaDozingTransitions =
         _animateBottomAreaDozingTransitions.asStateFlow()
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/TrustRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/TrustRepository.kt
index 867675b..00036ce 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/TrustRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/TrustRepository.kt
@@ -17,6 +17,7 @@
 package com.android.systemui.keyguard.data.repository
 
 import android.app.trust.TrustManager
+import com.android.keyguard.TrustGrantFlags
 import com.android.keyguard.logging.TrustRepositoryLogger
 import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging
 import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
@@ -34,6 +35,7 @@
 import kotlinx.coroutines.flow.StateFlow
 import kotlinx.coroutines.flow.combine
 import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.filter
 import kotlinx.coroutines.flow.map
 import kotlinx.coroutines.flow.onEach
 import kotlinx.coroutines.flow.onStart
@@ -50,6 +52,9 @@
 
     /** Reports that whether trust is managed has changed for the current user. */
     val isCurrentUserTrustManaged: StateFlow<Boolean>
+
+    /** A trust agent is requesting to dismiss the keyguard from a trust change. */
+    val trustAgentRequestingToDismissKeyguard: Flow<TrustModel>
 }
 
 @SysUISingleton
@@ -78,7 +83,7 @@
                         ) {
                             logger.onTrustChanged(enabled, newlyUnlocked, userId, flags, grantMsgs)
                             trySendWithFailureLogging(
-                                TrustModel(enabled, userId),
+                                TrustModel(enabled, userId, TrustGrantFlags(flags)),
                                 TrustRepositoryLogger.TAG,
                                 "onTrustChanged"
                             )
@@ -158,6 +163,17 @@
                     initialValue = false
                 )
 
+    override val trustAgentRequestingToDismissKeyguard: Flow<TrustModel>
+        get() =
+            combine(trust, userRepository.selectedUserInfo, ::Pair)
+                .map { latestTrustModelForUser[it.second.id] }
+                .distinctUntilChanged()
+                .filter {
+                    it != null &&
+                        (it.flags.isInitiatedByUser || it.flags.dismissKeyguardRequested())
+                }
+                .map { it!! }
+
     private fun isUserTrustManaged(userId: Int) =
         trustManagedForUser[userId]?.isTrustManaged ?: false
 
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissActionInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissActionInteractor.kt
new file mode 100644
index 0000000..08d29d4
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissActionInteractor.kt
@@ -0,0 +1,118 @@
+/*
+ * 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 com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.keyguard.data.repository.KeyguardRepository
+import com.android.systemui.keyguard.shared.model.DismissAction
+import com.android.systemui.keyguard.shared.model.KeyguardDone
+import com.android.systemui.keyguard.shared.model.KeyguardState.ALTERNATE_BOUNCER
+import com.android.systemui.keyguard.shared.model.KeyguardState.GONE
+import com.android.systemui.keyguard.shared.model.KeyguardState.PRIMARY_BOUNCER
+import com.android.systemui.util.kotlin.sample
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.filter
+import kotlinx.coroutines.flow.filterNot
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.merge
+import kotlinx.coroutines.flow.stateIn
+
+/** Encapsulates business-logic for actions to run when the keyguard is dismissed. */
+@ExperimentalCoroutinesApi
+@SysUISingleton
+class KeyguardDismissActionInteractor
+@Inject
+constructor(
+    private val repository: KeyguardRepository,
+    transitionInteractor: KeyguardTransitionInteractor,
+    val dismissInteractor: KeyguardDismissInteractor,
+    @Application private val applicationScope: CoroutineScope,
+) {
+    val dismissAction: Flow<DismissAction> = repository.dismissAction
+
+    val onCancel: Flow<Runnable> = dismissAction.map { it.onCancelAction }
+
+    // TODO (b/268240415): use message in alt + primary bouncer message
+    // message to show to the user about the dismiss action, else empty string
+    val message = dismissAction.map { it.message }
+
+    /**
+     * True if the dismiss action will run an animation on the lockscreen and requires any views
+     * that would obscure this animation (ie: the primary bouncer) to immediately hide, so the
+     * animation would be visible.
+     */
+    val willAnimateDismissActionOnLockscreen: StateFlow<Boolean> =
+        dismissAction
+            .map { it.willAnimateOnLockscreen }
+            .stateIn(
+                scope = applicationScope,
+                started = SharingStarted.WhileSubscribed(),
+                initialValue = false,
+            )
+
+    private val finishedTransitionToGone: Flow<Unit> =
+        transitionInteractor.finishedKeyguardState.filter { it == GONE }.map {} // map to Unit
+    val executeDismissAction: Flow<() -> KeyguardDone> =
+        merge(
+                finishedTransitionToGone,
+                dismissInteractor.dismissKeyguardRequestWithImmediateDismissAction
+            )
+            .sample(dismissAction)
+            .filterNot { it is DismissAction.None }
+            .map { it.onDismissAction }
+    val resetDismissAction: Flow<Unit> =
+        transitionInteractor.finishedKeyguardTransitionStep
+            .filter { it.to != ALTERNATE_BOUNCER && it.to != PRIMARY_BOUNCER && it.to != GONE }
+            .sample(dismissAction)
+            .filterNot { it is DismissAction.None }
+            .map {} // map to Unit
+
+    fun runDismissAnimationOnKeyguard(): Boolean {
+        return willAnimateDismissActionOnLockscreen.value
+    }
+
+    fun runAfterKeyguardGone(runnable: Runnable) {
+        setDismissAction(
+            DismissAction.RunAfterKeyguardGone(
+                dismissAction = { runnable.run() },
+                onCancelAction = {},
+                message = "",
+                willAnimateOnLockscreen = false,
+            )
+        )
+    }
+
+    fun setDismissAction(dismissAction: DismissAction) {
+        repository.dismissAction.value.onCancelAction.run()
+        repository.setDismissAction(dismissAction)
+    }
+
+    fun handleDismissAction() {
+        repository.setDismissAction(DismissAction.None)
+    }
+
+    suspend fun setKeyguardDone(keyguardDoneTiming: KeyguardDone) {
+        dismissInteractor.setKeyguardDone(keyguardDoneTiming)
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissInteractor.kt
new file mode 100644
index 0000000..cab6928
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissInteractor.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.keyguard.domain.interactor
+
+import com.android.systemui.bouncer.domain.interactor.AlternateBouncerInteractor
+import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerInteractor
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.keyguard.data.repository.KeyguardRepository
+import com.android.systemui.keyguard.data.repository.TrustRepository
+import com.android.systemui.keyguard.shared.model.DismissAction
+import com.android.systemui.keyguard.shared.model.KeyguardDone
+import com.android.systemui.power.domain.interactor.PowerInteractor
+import com.android.systemui.user.domain.interactor.UserInteractor
+import com.android.systemui.util.kotlin.Utils.Companion.toQuad
+import com.android.systemui.util.kotlin.sample
+import javax.inject.Inject
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.filter
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.merge
+
+/** Encapsulates business logic for requesting the keyguard to dismiss/finish/done. */
+@SysUISingleton
+class KeyguardDismissInteractor
+@Inject
+constructor(
+    trustRepository: TrustRepository,
+    val keyguardRepository: KeyguardRepository,
+    val primaryBouncerInteractor: PrimaryBouncerInteractor,
+    val alternateBouncerInteractor: AlternateBouncerInteractor,
+    val powerInteractor: PowerInteractor,
+    val userInteractor: UserInteractor,
+) {
+    /*
+     * Updates when a biometric has authenticated the device and is requesting to dismiss
+     * the keyguard. When true, a class 3 biometrics has authenticated. Else, a lower class
+     * biometric strength has authenticated and is requesting to dismiss the keyguard.
+     */
+    private val biometricAuthenticatedRequestDismissKeyguard: Flow<Unit> =
+        primaryBouncerInteractor.keyguardAuthenticatedBiometrics.map {} // map to Unit
+
+    /*
+     * Updates when a trust change is requesting to dismiss the keyguard and is able to do so
+     * in the current device state.
+     */
+    private val onTrustGrantedRequestDismissKeyguard: Flow<Unit> =
+        trustRepository.trustAgentRequestingToDismissKeyguard
+            .sample(
+                combine(
+                    primaryBouncerInteractor.isShowing,
+                    alternateBouncerInteractor.isVisible,
+                    powerInteractor.isInteractive,
+                    ::Triple
+                ),
+                ::toQuad
+            )
+            .filter { (trustModel, primaryBouncerShowing, altBouncerShowing, interactive) ->
+                val bouncerShowing = primaryBouncerShowing || altBouncerShowing
+                (interactive || trustModel.flags.temporaryAndRenewable()) &&
+                    (bouncerShowing || trustModel.flags.dismissKeyguardRequested())
+            }
+            .map {} // map to Unit
+
+    /*
+     * Updates when the current user successfully has authenticated via primary authentication
+     * (pin/pattern/password).
+     */
+    private val primaryAuthenticated: Flow<Unit> =
+        primaryBouncerInteractor.keyguardAuthenticatedPrimaryAuth
+            .filter { authedUserId -> authedUserId == userInteractor.getSelectedUserId() }
+            .map {} // map to Unit
+
+    /*
+     * Updates when the current user requests the bouncer after they've already successfully
+     * authenticated (ie: from non-bypass face auth, from a trust agent that didn't immediately
+     * dismiss the keyguard, or if keyguard security is set to SWIPE or NONE).
+     */
+    private val userRequestedBouncerWhenAlreadyAuthenticated: Flow<Unit> =
+        primaryBouncerInteractor.userRequestedBouncerWhenAlreadyAuthenticated
+            .filter { authedUserId -> authedUserId == userInteractor.getSelectedUserId() }
+            .map {} // map to Unit
+
+    /** Updates when keyguardDone should be requested. */
+    val keyguardDone: Flow<KeyguardDone> = keyguardRepository.keyguardDone
+
+    /** Updates when any request to dismiss the current user's keyguard has arrived. */
+    private val dismissKeyguardRequest: Flow<DismissAction> =
+        merge(
+                biometricAuthenticatedRequestDismissKeyguard,
+                onTrustGrantedRequestDismissKeyguard,
+                primaryAuthenticated,
+                userRequestedBouncerWhenAlreadyAuthenticated,
+            )
+            .sample(keyguardRepository.dismissAction)
+
+    /**
+     * Updates when a request to dismiss the current user's keyguard has arrived and there's a
+     * dismiss action to run immediately. It's expected that the consumer will request keyguardDone
+     * with or without a deferral.
+     */
+    val dismissKeyguardRequestWithImmediateDismissAction: Flow<Unit> =
+        dismissKeyguardRequest.filter { it is DismissAction.RunImmediately }.map {} // map to Unit
+
+    /**
+     * Updates when a request to dismiss the current user's keyguard has arrived and there's isn't a
+     * dismiss action to run immediately. There may still be a dismiss action to run after the
+     * keyguard transitions to GONE.
+     */
+    val dismissKeyguardRequestWithoutImmediateDismissAction: Flow<Unit> =
+        dismissKeyguardRequest.filter { it !is DismissAction.RunImmediately }.map {} // map to Unit
+
+    suspend fun setKeyguardDone(keyguardDoneTiming: KeyguardDone) {
+        keyguardRepository.setKeyguardDone(keyguardDoneTiming)
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/OccludingAppDeviceEntryInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/OccludingAppDeviceEntryInteractor.kt
index 3ec660a..f6ad829 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/OccludingAppDeviceEntryInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/OccludingAppDeviceEntryInteractor.kt
@@ -22,8 +22,6 @@
 import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerInteractor
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Application
-import com.android.systemui.flags.FeatureFlags
-import com.android.systemui.flags.Flags
 import com.android.systemui.keyguard.data.repository.DeviceEntryFingerprintAuthRepository
 import com.android.systemui.keyguard.shared.model.ErrorFingerprintAuthenticationStatus
 import com.android.systemui.keyguard.shared.model.SuccessFingerprintAuthenticationStatus
@@ -60,7 +58,6 @@
     private val context: Context,
     activityStarter: ActivityStarter,
     powerInteractor: PowerInteractor,
-    featureFlags: FeatureFlags,
 ) {
     private val keyguardOccludedByApp: Flow<Boolean> =
         combine(
@@ -93,37 +90,35 @@
             .ifKeyguardOccludedByApp(/* elseFlow */ flowOf(null))
 
     init {
-        if (featureFlags.isEnabled(Flags.FP_LISTEN_OCCLUDING_APPS)) {
-            scope.launch {
-                // On fingerprint success when the screen is on, go to the home screen
-                fingerprintUnlockSuccessEvents.sample(powerInteractor.isInteractive).collect {
-                    if (it) {
-                        goToHomeScreen()
-                    }
-                    // don't go to the home screen if the authentication is from AOD/dozing/off
+        scope.launch {
+            // On fingerprint success when the screen is on, go to the home screen
+            fingerprintUnlockSuccessEvents.sample(powerInteractor.isInteractive).collect {
+                if (it) {
+                    goToHomeScreen()
                 }
+                // don't go to the home screen if the authentication is from AOD/dozing/off
             }
+        }
 
-            scope.launch {
-                // On device fingerprint lockout, request the bouncer with a runnable to
-                // go to the home screen. Without this, the bouncer won't proceed to the home
-                // screen.
-                fingerprintLockoutEvents.collect {
-                    activityStarter.dismissKeyguardThenExecute(
-                        object : ActivityStarter.OnDismissAction {
-                            override fun onDismiss(): Boolean {
-                                goToHomeScreen()
-                                return false
-                            }
+        scope.launch {
+            // On device fingerprint lockout, request the bouncer with a runnable to
+            // go to the home screen. Without this, the bouncer won't proceed to the home
+            // screen.
+            fingerprintLockoutEvents.collect {
+                activityStarter.dismissKeyguardThenExecute(
+                    object : ActivityStarter.OnDismissAction {
+                        override fun onDismiss(): Boolean {
+                            goToHomeScreen()
+                            return false
+                        }
 
-                            override fun willRunAnimationOnKeyguard(): Boolean {
-                                return false
-                            }
-                        },
-                        /* cancel= */ null,
-                        /* afterKeyguardGone */ false
-                    )
-                }
+                        override fun willRunAnimationOnKeyguard(): Boolean {
+                            return false
+                        }
+                    },
+                    /* cancel= */ null,
+                    /* afterKeyguardGone */ false
+                )
             }
         }
     }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/DismissAction.kt b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/DismissAction.kt
new file mode 100644
index 0000000..c8d5599
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/DismissAction.kt
@@ -0,0 +1,64 @@
+/*
+ * 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.shared.model
+
+/** DismissAction models */
+sealed interface DismissAction {
+    val onDismissAction: () -> KeyguardDone
+    val onCancelAction: Runnable
+    val message: String
+    /**
+     * True if the dismiss action will run an animation on the keyguard and requires any views that
+     * would obscure this animation (ie: the primary bouncer) to immediately hide, so the animation
+     * would be visible.
+     */
+    val willAnimateOnLockscreen: Boolean
+    val runAfterKeyguardGone: Boolean
+
+    class RunImmediately(
+        override val onDismissAction: () -> KeyguardDone,
+        override val onCancelAction: Runnable,
+        override val message: String,
+        override val willAnimateOnLockscreen: Boolean,
+    ) : DismissAction {
+        override val runAfterKeyguardGone: Boolean = false
+    }
+
+    class RunAfterKeyguardGone(
+        val dismissAction: () -> Unit,
+        override val onCancelAction: Runnable,
+        override val message: String,
+        override val willAnimateOnLockscreen: Boolean,
+    ) : DismissAction {
+        override val onDismissAction: () -> KeyguardDone = {
+            dismissAction()
+            // no-op, when this dismissAction is run after the keyguard is gone,
+            // the keyguard is already done so KeyguardDone timing is irrelevant
+            KeyguardDone.IMMEDIATE
+        }
+        override val runAfterKeyguardGone: Boolean = true
+    }
+
+    data object None : DismissAction {
+        override val onDismissAction: () -> KeyguardDone = { KeyguardDone.IMMEDIATE }
+        override val onCancelAction: Runnable = Runnable {}
+        override val message: String = ""
+        override val willAnimateOnLockscreen: Boolean = false
+        override val runAfterKeyguardGone: Boolean = false
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/KeyguardDone.kt b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/KeyguardDone.kt
new file mode 100644
index 0000000..5e8cf57
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/KeyguardDone.kt
@@ -0,0 +1,27 @@
+/*
+ * 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.shared.model
+
+/**
+ * When to send the keyguard done signal. Should it immediately be sent when the keyguard is
+ * requested to be dismissed? Or should it be sent later?
+ */
+enum class KeyguardDone {
+    IMMEDIATE, // keyguard is immediately done, immediately start transitioning away keyguard
+    LATER, // keyguard is dismissible pending the next keyguardDone call
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/TrustModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/TrustModel.kt
index cdfab1a..206b792 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/TrustModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/TrustModel.kt
@@ -16,6 +16,8 @@
 
 package com.android.systemui.keyguard.shared.model
 
+import com.android.keyguard.TrustGrantFlags
+
 sealed class TrustMessage
 
 /** Represents the trust state */
@@ -24,6 +26,7 @@
     val isTrusted: Boolean,
     /** The user, for which the trust changed. */
     val userId: Int,
+    val flags: TrustGrantFlags,
 ) : TrustMessage()
 
 /** Represents where trust agents are enabled for a particular user. */
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/WakefulnessModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/WakefulnessModel.kt
index 62f43ed..2a5beaf 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/WakefulnessModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/shared/model/WakefulnessModel.kt
@@ -36,7 +36,7 @@
 
     private fun isAsleep() = state == ASLEEP
 
-    private fun isAwake() = state == AWAKE
+    fun isAwake() = state == AWAKE
 
     fun isStartingToWakeOrAwake() = isStartingToWake() || isAwake()
 
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBlueprintViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBlueprintViewBinder.kt
index a8070c2..3dd3e07 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBlueprintViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardBlueprintViewBinder.kt
@@ -18,11 +18,13 @@
 package com.android.systemui.keyguard.ui.binder
 
 import android.os.Trace
+import android.transition.TransitionManager
 import android.util.Log
 import androidx.constraintlayout.widget.ConstraintLayout
 import androidx.constraintlayout.widget.ConstraintSet
 import androidx.lifecycle.Lifecycle
 import androidx.lifecycle.repeatOnLifecycle
+import com.android.systemui.keyguard.ui.view.layout.blueprints.transitions.BaseBlueprintTransition
 import com.android.systemui.keyguard.ui.viewmodel.KeyguardBlueprintViewModel
 import com.android.systemui.lifecycle.repeatWhenAttached
 import kotlinx.coroutines.launch
@@ -40,17 +42,29 @@
                             Trace.beginSection("KeyguardBlueprint#applyBlueprint")
                             Log.d(TAG, "applying blueprint: $blueprint")
 
-                            ConstraintSet().apply {
-                                clone(constraintLayout)
-                                val emptyLayout = ConstraintSet.Layout()
-                                knownIds.forEach { getConstraint(it).layout.copyFrom(emptyLayout) }
-                                blueprint.applyConstraints(this)
-                                // Add and remove views of sections that are not contained by the
-                                // other.
-                                blueprint?.replaceViews(prevBluePrint, constraintLayout)
-                                applyTo(constraintLayout)
+                            val cs =
+                                ConstraintSet().apply {
+                                    clone(constraintLayout)
+                                    val emptyLayout = ConstraintSet.Layout()
+                                    knownIds.forEach {
+                                        getConstraint(it).layout.copyFrom(emptyLayout)
+                                    }
+                                    blueprint.applyConstraints(this)
+                                }
+
+                            // Apply transition.
+                            if (prevBluePrint != null && prevBluePrint != blueprint) {
+                                TransitionManager.beginDelayedTransition(
+                                    constraintLayout,
+                                    BaseBlueprintTransition()
+                                )
                             }
 
+                            // Add and remove views of sections that are not contained by the
+                            // other.
+                            blueprint.replaceViews(prevBluePrint, constraintLayout)
+                            cs.applyTo(constraintLayout)
+
                             viewModel.currentBluePrint = blueprint
                             Trace.endSection()
                         }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardDismissActionBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardDismissActionBinder.kt
new file mode 100644
index 0000000..d5add61
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardDismissActionBinder.kt
@@ -0,0 +1,75 @@
+/*
+ * 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.binder
+
+import com.android.keyguard.logging.KeyguardLogger
+import com.android.systemui.CoreStartable
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.flags.FeatureFlagsClassic
+import com.android.systemui.flags.Flags
+import com.android.systemui.keyguard.domain.interactor.KeyguardDismissActionInteractor
+import com.android.systemui.log.core.LogLevel
+import com.android.systemui.util.kotlin.sample
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.launch
+
+/** Runs actions on keyguard dismissal. */
+@OptIn(ExperimentalCoroutinesApi::class)
+@SysUISingleton
+class KeyguardDismissActionBinder
+@Inject
+constructor(
+    private val interactor: KeyguardDismissActionInteractor,
+    @Application private val scope: CoroutineScope,
+    private val keyguardLogger: KeyguardLogger,
+    private val featureFlags: FeatureFlagsClassic,
+) : CoreStartable {
+
+    override fun start() {
+        if (!featureFlags.isEnabled(Flags.REFACTOR_KEYGUARD_DISMISS_INTENT)) {
+            return
+        }
+
+        scope.launch {
+            interactor.executeDismissAction.collect {
+                log("executeDismissAction")
+                interactor.setKeyguardDone(it())
+                interactor.handleDismissAction()
+            }
+        }
+
+        scope.launch {
+            interactor.resetDismissAction.sample(interactor.onCancel).collect {
+                log("resetDismissAction")
+                it.run()
+                interactor.handleDismissAction()
+            }
+        }
+
+        scope.launch { interactor.dismissAction.collect { log("updatedDismissAction=$it") } }
+    }
+
+    private fun log(message: String) {
+        keyguardLogger.log(TAG, LogLevel.DEBUG, message)
+    }
+
+    companion object {
+        private const val TAG = "KeyguardDismissAction"
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardDismissBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardDismissBinder.kt
new file mode 100644
index 0000000..f14552b
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardDismissBinder.kt
@@ -0,0 +1,83 @@
+/*
+ * 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.binder
+
+import com.android.keyguard.ViewMediatorCallback
+import com.android.keyguard.logging.KeyguardLogger
+import com.android.systemui.CoreStartable
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.flags.FeatureFlagsClassic
+import com.android.systemui.flags.Flags
+import com.android.systemui.keyguard.domain.interactor.KeyguardDismissInteractor
+import com.android.systemui.keyguard.shared.model.KeyguardDone
+import com.android.systemui.log.core.LogLevel
+import com.android.systemui.user.domain.interactor.UserInteractor
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.launch
+
+/** Handles keyguard dismissal requests. */
+@OptIn(ExperimentalCoroutinesApi::class)
+@SysUISingleton
+class KeyguardDismissBinder
+@Inject
+constructor(
+    private val interactor: KeyguardDismissInteractor,
+    private val userInteractor: UserInteractor,
+    private val viewMediatorCallback: ViewMediatorCallback,
+    @Application private val scope: CoroutineScope,
+    private val keyguardLogger: KeyguardLogger,
+    private val featureFlags: FeatureFlagsClassic,
+) : CoreStartable {
+
+    override fun start() {
+        if (!featureFlags.isEnabled(Flags.REFACTOR_KEYGUARD_DISMISS_INTENT)) {
+            return
+        }
+
+        scope.launch {
+            interactor.keyguardDone.collect { keyguardDoneTiming ->
+                when (keyguardDoneTiming) {
+                    KeyguardDone.LATER -> {
+                        log("keyguardDonePending")
+                        viewMediatorCallback.keyguardDonePending(userInteractor.getSelectedUserId())
+                    }
+                    else -> {
+                        log("keyguardDone")
+                        viewMediatorCallback.keyguardDone(userInteractor.getSelectedUserId())
+                    }
+                }
+            }
+        }
+
+        scope.launch {
+            interactor.dismissKeyguardRequestWithoutImmediateDismissAction.collect {
+                log("dismissKeyguardRequestWithoutImmediateDismissAction-keyguardDone")
+                interactor.setKeyguardDone(KeyguardDone.IMMEDIATE)
+            }
+        }
+    }
+
+    private fun log(message: String) {
+        keyguardLogger.log(TAG, LogLevel.DEBUG, message)
+    }
+
+    companion object {
+        private const val TAG = "KeyguardDismiss"
+    }
+}
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 9503f2c..4b76821 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
@@ -59,20 +59,18 @@
         val disposableHandle =
             view.repeatWhenAttached {
                 repeatOnLifecycle(Lifecycle.State.CREATED) {
-                    if (featureFlags.isEnabled(Flags.FP_LISTEN_OCCLUDING_APPS)) {
-                        launch {
-                            occludingAppDeviceEntryMessageViewModel.message.collect {
-                                biometricMessage ->
-                                if (biometricMessage?.message != null) {
-                                    chipbarCoordinator.displayView(
-                                        createChipbarInfo(
-                                            biometricMessage.message,
-                                            R.drawable.ic_lock,
-                                        )
+                    launch {
+                        occludingAppDeviceEntryMessageViewModel.message.collect { biometricMessage
+                            ->
+                            if (biometricMessage?.message != null) {
+                                chipbarCoordinator.displayView(
+                                    createChipbarInfo(
+                                        biometricMessage.message,
+                                        R.drawable.ic_lock,
                                     )
-                                } else {
-                                    chipbarCoordinator.removeView(ID, "occludingAppMsgNull")
-                                }
+                                )
+                            } else {
+                                chipbarCoordinator.removeView(ID, "occludingAppMsgNull")
                             }
                         }
                     }
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 8945318..85a2fd5 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
@@ -264,14 +264,8 @@
             KeyguardPreviewSmartspaceViewModel.getLargeClockSmartspaceTopPadding(
                 context.resources,
             )
-
         val startPadding: Int =
-            with(context.resources) {
-                getDimensionPixelSize(
-                    com.android.systemui.customization.R.dimen.clock_padding_start
-                ) + getDimensionPixelSize(R.dimen.below_clock_padding_start)
-            }
-
+            context.resources.getDimensionPixelSize(R.dimen.below_clock_padding_start)
         val endPadding: Int =
             context.resources.getDimensionPixelSize(R.dimen.below_clock_padding_end)
 
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/blueprints/transitions/BaseBlueprintTransition.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/blueprints/transitions/BaseBlueprintTransition.kt
new file mode 100644
index 0000000..42b1c10
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/blueprints/transitions/BaseBlueprintTransition.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.
+ */
+
+package com.android.systemui.keyguard.ui.view.layout.blueprints.transitions
+
+import android.animation.Animator
+import android.animation.ObjectAnimator
+import android.transition.ChangeBounds
+import android.transition.TransitionSet
+import android.transition.TransitionValues
+import android.transition.Visibility
+import android.view.View
+import android.view.ViewGroup
+
+class BaseBlueprintTransition : TransitionSet() {
+    init {
+        ordering = ORDERING_SEQUENTIAL
+        addTransition(AlphaOutVisibility())
+            .addTransition(ChangeBounds())
+            .addTransition(AlphaInVisibility())
+    }
+    class AlphaOutVisibility : Visibility() {
+        override fun onDisappear(
+            sceneRoot: ViewGroup?,
+            view: View,
+            startValues: TransitionValues?,
+            endValues: TransitionValues?
+        ): Animator {
+            return ObjectAnimator.ofFloat(view, "alpha", 0f).apply {
+                addUpdateListener { view.alpha = it.animatedValue as Float }
+                start()
+            }
+        }
+    }
+
+    class AlphaInVisibility : Visibility() {
+        override fun onAppear(
+            sceneRoot: ViewGroup?,
+            view: View,
+            startValues: TransitionValues?,
+            endValues: TransitionValues?
+        ): Animator {
+            return ObjectAnimator.ofFloat(view, "alpha", 1f).apply {
+                addUpdateListener { view.alpha = it.animatedValue as Float }
+                start()
+            }
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToGoneTransitionViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToGoneTransitionViewModel.kt
index cca96b7..0783181 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToGoneTransitionViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToGoneTransitionViewModel.kt
@@ -19,27 +19,36 @@
 import com.android.app.animation.Interpolators.EMPHASIZED_ACCELERATE
 import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerInteractor
 import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.flags.FeatureFlagsClassic
+import com.android.systemui.flags.Flags
 import com.android.systemui.keyguard.domain.interactor.FromPrimaryBouncerTransitionInteractor.Companion.TO_GONE_DURATION
+import com.android.systemui.keyguard.domain.interactor.KeyguardDismissActionInteractor
 import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor
 import com.android.systemui.keyguard.shared.model.ScrimAlpha
 import com.android.systemui.keyguard.ui.KeyguardTransitionAnimationFlow
 import com.android.systemui.statusbar.SysuiStatusBarStateController
+import dagger.Lazy
 import javax.inject.Inject
 import kotlin.time.Duration.Companion.milliseconds
+import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.flatMapLatest
 import kotlinx.coroutines.flow.map
 
 /**
  * Breaks down PRIMARY_BOUNCER->GONE transition into discrete steps for corresponding views to
  * consume.
  */
+@OptIn(ExperimentalCoroutinesApi::class)
 @SysUISingleton
 class PrimaryBouncerToGoneTransitionViewModel
 @Inject
 constructor(
-    private val interactor: KeyguardTransitionInteractor,
+    interactor: KeyguardTransitionInteractor,
     private val statusBarStateController: SysuiStatusBarStateController,
     private val primaryBouncerInteractor: PrimaryBouncerInteractor,
+    keyguardDismissActionInteractor: Lazy<KeyguardDismissActionInteractor>,
+    featureFlags: FeatureFlagsClassic,
 ) {
     private val transitionAnimation =
         KeyguardTransitionAnimationFlow(
@@ -52,11 +61,18 @@
 
     /** Bouncer container alpha */
     val bouncerAlpha: Flow<Float> =
-        transitionAnimation.createFlow(
+        if (featureFlags.isEnabled(Flags.REFACTOR_KEYGUARD_DISMISS_INTENT)) {
+            keyguardDismissActionInteractor
+                .get()
+                .willAnimateDismissActionOnLockscreen
+                .flatMapLatest { createBouncerAlphaFlow { it } }
+        } else {
+            createBouncerAlphaFlow(primaryBouncerInteractor::willRunDismissFromKeyguard)
+        }
+    private fun createBouncerAlphaFlow(willRunAnimationOnKeyguard: () -> Boolean): Flow<Float> {
+        return transitionAnimation.createFlow(
             duration = 200.milliseconds,
-            onStart = {
-                willRunDismissFromKeyguard = primaryBouncerInteractor.willRunDismissFromKeyguard()
-            },
+            onStart = { willRunDismissFromKeyguard = willRunAnimationOnKeyguard() },
             onStep = {
                 if (willRunDismissFromKeyguard) {
                     0f
@@ -65,14 +81,24 @@
                 }
             },
         )
+    }
 
     /** Lockscreen alpha */
     val lockscreenAlpha: Flow<Float> =
-        transitionAnimation.createFlow(
+        if (featureFlags.isEnabled(Flags.REFACTOR_KEYGUARD_DISMISS_INTENT)) {
+            keyguardDismissActionInteractor
+                .get()
+                .willAnimateDismissActionOnLockscreen
+                .flatMapLatest { createLockscreenAlpha { it } }
+        } else {
+            createLockscreenAlpha(primaryBouncerInteractor::willRunDismissFromKeyguard)
+        }
+    private fun createLockscreenAlpha(willRunAnimationOnKeyguard: () -> Boolean): Flow<Float> {
+        return transitionAnimation.createFlow(
             duration = 50.milliseconds,
             onStart = {
                 leaveShadeOpen = statusBarStateController.leaveOpenOnKeyguardHide()
-                willRunDismissFromKeyguard = primaryBouncerInteractor.willRunDismissFromKeyguard()
+                willRunDismissFromKeyguard = willRunAnimationOnKeyguard()
             },
             onStep = {
                 if (willRunDismissFromKeyguard || leaveShadeOpen) {
@@ -82,17 +108,26 @@
                 }
             },
         )
+    }
 
     /** Scrim alpha values */
     val scrimAlpha: Flow<ScrimAlpha> =
-        transitionAnimation
+        if (featureFlags.isEnabled(Flags.REFACTOR_KEYGUARD_DISMISS_INTENT)) {
+            keyguardDismissActionInteractor
+                .get()
+                .willAnimateDismissActionOnLockscreen
+                .flatMapLatest { createScrimAlphaFlow { it } }
+        } else {
+            createScrimAlphaFlow(primaryBouncerInteractor::willRunDismissFromKeyguard)
+        }
+    private fun createScrimAlphaFlow(willRunAnimationOnKeyguard: () -> Boolean): Flow<ScrimAlpha> {
+        return transitionAnimation
             .createFlow(
                 duration = TO_GONE_DURATION,
                 interpolator = EMPHASIZED_ACCELERATE,
                 onStart = {
                     leaveShadeOpen = statusBarStateController.leaveOpenOnKeyguardHide()
-                    willRunDismissFromKeyguard =
-                        primaryBouncerInteractor.willRunDismissFromKeyguard()
+                    willRunDismissFromKeyguard = willRunAnimationOnKeyguard()
                 },
                 onStep = { 1f - it },
             )
@@ -108,4 +143,5 @@
                     ScrimAlpha(behindAlpha = it)
                 }
             }
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaProjectionAppSelectorActivity.kt b/packages/SystemUI/src/com/android/systemui/media/MediaProjectionAppSelectorActivity.kt
index 053c9b5..60fd104 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaProjectionAppSelectorActivity.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaProjectionAppSelectorActivity.kt
@@ -29,7 +29,10 @@
 import android.os.IBinder
 import android.os.ResultReceiver
 import android.os.UserHandle
+import android.util.Log
+import android.view.View
 import android.view.ViewGroup
+import android.view.accessibility.AccessibilityEvent
 import androidx.lifecycle.Lifecycle
 import androidx.lifecycle.LifecycleOwner
 import androidx.lifecycle.LifecycleRegistry
@@ -40,6 +43,9 @@
 import com.android.internal.app.ResolverListController
 import com.android.internal.app.chooser.NotSelectableTargetInfo
 import com.android.internal.app.chooser.TargetInfo
+import com.android.internal.widget.RecyclerView
+import com.android.internal.widget.RecyclerViewAccessibilityDelegate
+import com.android.internal.widget.ResolverDrawerLayout
 import com.android.systemui.R
 import com.android.systemui.mediaprojection.appselector.MediaProjectionAppSelectorComponent
 import com.android.systemui.mediaprojection.appselector.MediaProjectionAppSelectorController
@@ -105,6 +111,10 @@
 
         super.onCreate(bundle)
         controller.init()
+        // we override AppList's AccessibilityDelegate set in ResolverActivity.onCreate because in
+        // our case this delegate must extend RecyclerViewAccessibilityDelegate, otherwise
+        // RecyclerView scrolling is broken
+        setAppListAccessibilityDelegate()
     }
 
     override fun onStart() {
@@ -277,6 +287,8 @@
         recentsViewController.createView(parent)
 
     companion object {
+        const val TAG = "MediaProjectionAppSelectorActivity"
+
         /**
          * When EXTRA_CAPTURE_REGION_RESULT_RECEIVER is passed as intent extra the activity will
          * send the [CaptureRegion] to the result receiver instead of returning media projection
@@ -313,4 +325,42 @@
             putExtra(EXTRA_SELECTED_PROFILE, selectedProfile)
         }
     }
+
+    private fun setAppListAccessibilityDelegate() {
+        val rdl = requireViewById<ResolverDrawerLayout>(com.android.internal.R.id.contentPanel)
+        for (i in 0 until mMultiProfilePagerAdapter.count) {
+            val list =
+                mMultiProfilePagerAdapter
+                    .getItem(i)
+                    .rootView
+                    .findViewById<View>(com.android.internal.R.id.resolver_list)
+            if (list == null || list !is RecyclerView) {
+                Log.wtf(TAG, "MediaProjection only supports RecyclerView")
+            } else {
+                list.accessibilityDelegate = RecyclerViewExpandingAccessibilityDelegate(rdl, list)
+            }
+        }
+    }
+
+    /**
+     * An a11y delegate propagating all a11y events to [AppListAccessibilityDelegate] so that it can
+     * expand drawer when needed. It needs to extend [RecyclerViewAccessibilityDelegate] because
+     * that superclass handles RecyclerView scrolling while using a11y services.
+     */
+    private class RecyclerViewExpandingAccessibilityDelegate(
+        rdl: ResolverDrawerLayout,
+        view: RecyclerView
+    ) : RecyclerViewAccessibilityDelegate(view) {
+
+        private val delegate = AppListAccessibilityDelegate(rdl)
+
+        override fun onRequestSendAccessibilityEvent(
+            host: ViewGroup,
+            child: View,
+            event: AccessibilityEvent
+        ): Boolean {
+            super.onRequestSendAccessibilityEvent(host, child, event)
+            return delegate.onRequestSendAccessibilityEvent(host, child, event)
+        }
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/media/MediaProjectionPermissionActivity.java b/packages/SystemUI/src/com/android/systemui/media/MediaProjectionPermissionActivity.java
index 4be572f..d403788 100644
--- a/packages/SystemUI/src/com/android/systemui/media/MediaProjectionPermissionActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/media/MediaProjectionPermissionActivity.java
@@ -29,6 +29,7 @@
 import android.app.Activity;
 import android.app.ActivityManager;
 import android.app.AlertDialog;
+import android.app.StatusBarManager;
 import android.content.Context;
 import android.content.DialogInterface;
 import android.content.Intent;
@@ -72,6 +73,7 @@
 
     private final FeatureFlags mFeatureFlags;
     private final Lazy<ScreenCaptureDevicePolicyResolver> mScreenCaptureDevicePolicyResolver;
+    private final StatusBarManager mStatusBarManager;
 
     private String mPackageName;
     private int mUid;
@@ -87,9 +89,11 @@
 
     @Inject
     public MediaProjectionPermissionActivity(FeatureFlags featureFlags,
-            Lazy<ScreenCaptureDevicePolicyResolver> screenCaptureDevicePolicyResolver) {
+            Lazy<ScreenCaptureDevicePolicyResolver> screenCaptureDevicePolicyResolver,
+            StatusBarManager statusBarManager) {
         mFeatureFlags = featureFlags;
         mScreenCaptureDevicePolicyResolver = screenCaptureDevicePolicyResolver;
+        mStatusBarManager = statusBarManager;
     }
 
     @Override
@@ -311,6 +315,8 @@
                 // WM Shell running inside.
                 mUserSelectingTask = true;
                 startActivityAsUser(intent, UserHandle.of(ActivityManager.getCurrentUser()));
+                // close shade if it's open
+                mStatusBarManager.collapsePanels();
             }
         } catch (RemoteException e) {
             Log.e(TAG, "Error granting projection permission", e);
diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputController.java b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputController.java
index bb0e9d1..7011ad9 100644
--- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputController.java
+++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputController.java
@@ -127,7 +127,7 @@
     private final ActivityStarter mActivityStarter;
     private final DialogLaunchAnimator mDialogLaunchAnimator;
     private final CommonNotifCollection mNotifCollection;
-    private final Object mMediaDevicesLock = new Object();
+    protected final Object mMediaDevicesLock = new Object();
     @VisibleForTesting
     final List<MediaDevice> mGroupMediaDevices = new CopyOnWriteArrayList<>();
     final List<MediaDevice> mCachedMediaDevices = new CopyOnWriteArrayList<>();
@@ -222,7 +222,7 @@
                 R.dimen.media_output_dialog_selectable_margin_end);
     }
 
-    void start(@NonNull Callback cb) {
+    protected void start(@NonNull Callback cb) {
         synchronized (mMediaDevicesLock) {
             mCachedMediaDevices.clear();
             mMediaItemList.clear();
@@ -256,15 +256,15 @@
         return false;
     }
 
-    boolean isRefreshing() {
+    public boolean isRefreshing() {
         return mIsRefreshing;
     }
 
-    void setRefreshing(boolean refreshing) {
+    public void setRefreshing(boolean refreshing) {
         mIsRefreshing = refreshing;
     }
 
-    void stop() {
+    protected void stop() {
         if (mMediaController != null) {
             mMediaController.unregisterCallback(mCb);
         }
@@ -551,7 +551,7 @@
         }
     }
 
-    void refreshDataSetIfNeeded() {
+    public void refreshDataSetIfNeeded() {
         if (mNeedRefresh) {
             buildMediaItems(mCachedMediaDevices);
             mCallback.onDeviceListChanged();
@@ -605,6 +605,15 @@
 
     private void buildMediaItems(List<MediaDevice> devices) {
         synchronized (mMediaDevicesLock) {
+            List<MediaItem> updatedMediaItems = buildMediaItems(mMediaItemList, devices);
+            mMediaItemList.clear();
+            mMediaItemList.addAll(updatedMediaItems);
+        }
+    }
+
+    protected List<MediaItem> buildMediaItems(List<MediaItem> oldMediaItems,
+            List<MediaDevice> devices) {
+        synchronized (mMediaDevicesLock) {
             if (!mLocalMediaManager.isPreferenceRouteListingExist()) {
                 attachRangeInfo(devices);
                 Collections.sort(devices, Comparator.naturalOrder());
@@ -616,22 +625,20 @@
             final MediaDevice connectedMediaDevice =
                     needToHandleMutingExpectedDevice ? null
                             : getCurrentConnectedMediaDevice();
-            if (mMediaItemList.isEmpty()) {
+            if (oldMediaItems.isEmpty()) {
                 if (connectedMediaDevice == null) {
                     if (DEBUG) {
                         Log.d(TAG, "No connected media device or muting expected device exist.");
                     }
-                    categorizeMediaItems(null, devices, needToHandleMutingExpectedDevice);
-                    return;
+                    return categorizeMediaItems(null, devices, needToHandleMutingExpectedDevice);
                 }
                 // selected device exist
-                categorizeMediaItems(connectedMediaDevice, devices, false);
-                return;
+                return categorizeMediaItems(connectedMediaDevice, devices, false);
             }
             // To keep the same list order
             final List<MediaDevice> targetMediaDevices = new ArrayList<>();
             final Map<Integer, MediaItem> dividerItems = new HashMap<>();
-            for (MediaItem originalMediaItem : mMediaItemList) {
+            for (MediaItem originalMediaItem : oldMediaItems) {
                 for (MediaDevice newDevice : devices) {
                     if (originalMediaItem.getMediaDevice().isPresent()
                             && TextUtils.equals(originalMediaItem.getMediaDevice().get().getId(),
@@ -642,7 +649,7 @@
                 }
                 if (originalMediaItem.getMediaItemType()
                         == MediaItem.MediaItemType.TYPE_GROUP_DIVIDER) {
-                    dividerItems.put(mMediaItemList.indexOf(originalMediaItem), originalMediaItem);
+                    dividerItems.put(oldMediaItems.indexOf(originalMediaItem), originalMediaItem);
                 }
             }
             if (targetMediaDevices.size() != devices.size()) {
@@ -651,16 +658,18 @@
             }
             List<MediaItem> finalMediaItems = targetMediaDevices.stream().map(
                     MediaItem::new).collect(Collectors.toList());
-            dividerItems.forEach((key, item) -> {
-                finalMediaItems.add(key, item);
-            });
+            dividerItems.forEach(finalMediaItems::add);
             attachConnectNewDeviceItemIfNeeded(finalMediaItems);
-            mMediaItemList.clear();
-            mMediaItemList.addAll(finalMediaItems);
+            return finalMediaItems;
         }
     }
 
-    private void categorizeMediaItems(MediaDevice connectedMediaDevice, List<MediaDevice> devices,
+    /**
+     * Initial categorization of current devices, will not be called for updates to the devices
+     * list.
+     */
+    private List<MediaItem> categorizeMediaItems(MediaDevice connectedMediaDevice,
+            List<MediaDevice> devices,
             boolean needToHandleMutingExpectedDevice) {
         synchronized (mMediaDevicesLock) {
             List<MediaItem> finalMediaItems = new ArrayList<>();
@@ -691,8 +700,7 @@
                 }
             }
             attachConnectNewDeviceItemIfNeeded(finalMediaItems);
-            mMediaItemList.clear();
-            mMediaItemList.addAll(finalMediaItems);
+            return finalMediaItems;
         }
     }
 
@@ -765,7 +773,7 @@
         mGroupMediaDevices.clear();
     }
 
-    void connectDevice(MediaDevice device) {
+    protected void connectDevice(MediaDevice device) {
         mMetricLogger.updateOutputEndPoints(getCurrentConnectedMediaDevice(), device);
 
         ThreadUtils.postOnBackgroundThread(() -> {
@@ -777,7 +785,7 @@
         return mMediaItemList;
     }
 
-    MediaDevice getCurrentConnectedMediaDevice() {
+    public MediaDevice getCurrentConnectedMediaDevice() {
         return mLocalMediaManager.getCurrentConnectedDevice();
     }
 
@@ -794,7 +802,7 @@
         return mLocalMediaManager.getSelectableMediaDevice();
     }
 
-    List<MediaDevice> getSelectedMediaDevice() {
+    public List<MediaDevice> getSelectedMediaDevice() {
         return mLocalMediaManager.getSelectedMediaDevice();
     }
 
@@ -859,7 +867,7 @@
                 UserHandle.of(UserHandle.myUserId()));
     }
 
-    boolean isAnyDeviceTransferring() {
+    public boolean isAnyDeviceTransferring() {
         synchronized (mMediaDevicesLock) {
             for (MediaItem mediaItem : mMediaItemList) {
                 if (mediaItem.getMediaDevice().isPresent()
@@ -976,7 +984,7 @@
         broadcast.setBroadcastCode(broadcastCode.getBytes(StandardCharsets.UTF_8));
     }
 
-    void setTemporaryAllowListExceptionIfNeeded(MediaDevice targetDevice) {
+    protected void setTemporaryAllowListExceptionIfNeeded(MediaDevice targetDevice) {
         if (mPowerExemptionManager == null || mPackageName == null) {
             Log.w(TAG, "powerExemptionManager or package name is null");
             return;
@@ -1221,7 +1229,7 @@
         }
     };
 
-    interface Callback {
+    public interface Callback {
         /**
          * Override to handle the media content updating.
          */
diff --git a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputDialogFactory.kt b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputDialogFactory.kt
index af65937..2b38edb 100644
--- a/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputDialogFactory.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/dialog/MediaOutputDialogFactory.kt
@@ -38,7 +38,7 @@
 /**
  * Factory to create [MediaOutputDialog] objects.
  */
-class MediaOutputDialogFactory @Inject constructor(
+open class MediaOutputDialogFactory @Inject constructor(
     private val context: Context,
     private val mediaSessionManager: MediaSessionManager,
     private val lbm: LocalBluetoothManager?,
@@ -60,7 +60,7 @@
     }
 
     /** Creates a [MediaOutputDialog] for the given package. */
-    fun create(packageName: String, aboveStatusBar: Boolean, view: View? = null) {
+    open fun create(packageName: String, aboveStatusBar: Boolean, view: View? = null) {
         // Dismiss the previous dialog, if any.
         mediaOutputDialog?.dismiss()
 
@@ -89,7 +89,7 @@
     }
 
     /** dismiss [MediaOutputDialog] if exist. */
-    fun dismiss() {
+    open fun dismiss() {
         mediaOutputDialog?.dismiss()
         mediaOutputDialog = null
     }
diff --git a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java
index 555269d..62b22c5 100644
--- a/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java
+++ b/packages/SystemUI/src/com/android/systemui/navigationbar/NavigationBar.java
@@ -133,7 +133,6 @@
 import com.android.systemui.settings.DisplayTracker;
 import com.android.systemui.settings.UserContextProvider;
 import com.android.systemui.settings.UserTracker;
-import com.android.systemui.shade.ShadeController;
 import com.android.systemui.shade.ShadeViewController;
 import com.android.systemui.shared.navigationbar.RegionSamplingHelper;
 import com.android.systemui.shared.recents.utilities.Utilities;
@@ -156,6 +155,7 @@
 import com.android.systemui.statusbar.phone.LightBarController;
 import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager;
 import com.android.systemui.statusbar.policy.DeviceProvisionedController;
+import com.android.systemui.statusbar.policy.KeyguardStateController;
 import com.android.systemui.util.DeviceConfigProxy;
 import com.android.systemui.util.ViewController;
 import com.android.wm.shell.back.BackAnimation;
@@ -200,7 +200,7 @@
     private final StatusBarKeyguardViewManager mStatusBarKeyguardViewManager;
     private final SysUiState mSysUiFlagsContainer;
     private final Lazy<Optional<CentralSurfaces>> mCentralSurfacesOptionalLazy;
-    private final ShadeController mShadeController;
+    private final KeyguardStateController mKeyguardStateController;
     private final ShadeViewController mShadeViewController;
     private final NotificationRemoteInputManager mNotificationRemoteInputManager;
     private final OverviewProxyService mOverviewProxyService;
@@ -262,7 +262,7 @@
     @VisibleForTesting
     public int mDisplayId;
     private boolean mIsOnDefaultDisplay;
-    public boolean mHomeBlockedThisTouch;
+    private boolean mHomeBlockedThisTouch;
 
     /**
      * When user is QuickSwitching between apps of different orientations, we'll draw a fake
@@ -531,7 +531,6 @@
     @Inject
     NavigationBar(
             NavigationBarView navigationBarView,
-            ShadeController shadeController,
             NavigationBarFrame navigationBarFrame,
             @Nullable Bundle savedState,
             @DisplayId Context context,
@@ -550,6 +549,7 @@
             Optional<Pip> pipOptional,
             Optional<Recents> recentsOptional,
             Lazy<Optional<CentralSurfaces>> centralSurfacesOptionalLazy,
+            KeyguardStateController keyguardStateController,
             ShadeViewController shadeViewController,
             NotificationRemoteInputManager notificationRemoteInputManager,
             NotificationShadeDepthController notificationShadeDepthController,
@@ -585,7 +585,7 @@
         mStatusBarKeyguardViewManager = statusBarKeyguardViewManager;
         mSysUiFlagsContainer = sysUiFlagsContainer;
         mCentralSurfacesOptionalLazy = centralSurfacesOptionalLazy;
-        mShadeController = shadeController;
+        mKeyguardStateController = keyguardStateController;
         mShadeViewController = shadeViewController;
         mNotificationRemoteInputManager = notificationRemoteInputManager;
         mOverviewProxyService = overviewProxyService;
@@ -1326,8 +1326,7 @@
                 mHomeBlockedThisTouch = false;
                 if (mTelecomManagerOptional.isPresent()
                         && mTelecomManagerOptional.get().isRinging()) {
-                    if (centralSurfacesOptional.map(CentralSurfaces::isKeyguardShowing)
-                            .orElse(false)) {
+                    if (mKeyguardStateController.isShowing()) {
                         Log.i(TAG, "Ignoring HOME; there's a ringing incoming call. " +
                                 "No heads up");
                         mHomeBlockedThisTouch = true;
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/BluetoothTile.java b/packages/SystemUI/src/com/android/systemui/qs/tiles/BluetoothTile.java
index 30218a6..26912f8 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/BluetoothTile.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/BluetoothTile.java
@@ -226,7 +226,7 @@
                 listenToMetadata(device);
             } else {
                 stopListeningToStaleDeviceMetadata();
-                batteryLevel = device.getBatteryLevel();
+                batteryLevel = device.getMinBatteryLevelWithMemberDevices();
             }
 
             if (batteryLevel > BluetoothDevice.BATTERY_LEVEL_UNKNOWN) {
diff --git a/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyRecentsImpl.java b/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyRecentsImpl.java
index 4b22edc..21c5ae8 100644
--- a/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyRecentsImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/recents/OverviewProxyRecentsImpl.java
@@ -16,7 +16,6 @@
 
 package com.android.systemui.recents;
 
-import android.annotation.Nullable;
 import android.content.Context;
 import android.os.Handler;
 import android.os.RemoteException;
@@ -25,11 +24,7 @@
 import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.plugins.ActivityStarter;
 import com.android.systemui.shared.recents.IOverviewProxy;
-import com.android.systemui.statusbar.phone.CentralSurfaces;
-
-import dagger.Lazy;
-
-import java.util.Optional;
+import com.android.systemui.statusbar.policy.KeyguardStateController;
 
 import javax.inject.Inject;
 
@@ -40,20 +35,20 @@
 public class OverviewProxyRecentsImpl implements RecentsImplementation {
 
     private final static String TAG = "OverviewProxyRecentsImpl";
-    @Nullable
-    private final Lazy<Optional<CentralSurfaces>> mCentralSurfacesOptionalLazy;
-
     private Handler mHandler;
     private final OverviewProxyService mOverviewProxyService;
     private final ActivityStarter mActivityStarter;
+    private final KeyguardStateController mKeyguardStateController;
 
     @SuppressWarnings("OptionalUsedAsFieldOrParameterType")
     @Inject
-    public OverviewProxyRecentsImpl(Lazy<Optional<CentralSurfaces>> centralSurfacesOptionalLazy,
-            OverviewProxyService overviewProxyService, ActivityStarter activityStarter) {
-        mCentralSurfacesOptionalLazy = centralSurfacesOptionalLazy;
+    public OverviewProxyRecentsImpl(
+            OverviewProxyService overviewProxyService,
+            ActivityStarter activityStarter,
+            KeyguardStateController keyguardStateController) {
         mOverviewProxyService = overviewProxyService;
         mActivityStarter = activityStarter;
+        mKeyguardStateController = keyguardStateController;
     }
 
     @Override
@@ -101,9 +96,7 @@
                 }
             };
             // Preload only if device for current user is unlocked
-            final Optional<CentralSurfaces> centralSurfacesOptional =
-                    mCentralSurfacesOptionalLazy.get();
-            if (centralSurfacesOptional.map(CentralSurfaces::isKeyguardShowing).orElse(false)) {
+            if (mKeyguardStateController.isShowing()) {
                 mActivityStarter.executeRunnableDismissingKeyguard(
                         () -> mHandler.post(toggleRecents), null, true /* dismissShade */,
                         false /* afterKeyguardGone */,
diff --git a/packages/SystemUI/src/com/android/systemui/scene/shared/flag/SceneContainerFlags.kt b/packages/SystemUI/src/com/android/systemui/scene/shared/flag/SceneContainerFlags.kt
index 83fb723..291d273 100644
--- a/packages/SystemUI/src/com/android/systemui/scene/shared/flag/SceneContainerFlags.kt
+++ b/packages/SystemUI/src/com/android/systemui/scene/shared/flag/SceneContainerFlags.kt
@@ -69,7 +69,9 @@
             listOf(ComposeMustBeAvailable(), CompileTimeFlagMustBeEnabled())
 
     override fun isEnabled(): Boolean {
-        return requirements.all { it.isMet() }
+        // SCENE_CONTAINER_ENABLED is an explicit static flag check that helps with downstream
+        // optimizations, e.g., unused code stripping. Do not remove!
+        return Flags.SCENE_CONTAINER_ENABLED && requirements.all { it.isMet() }
     }
 
     override fun requirementDescription(): String {
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
index ca5d7b3..49ce832 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
@@ -166,7 +166,6 @@
 import com.android.systemui.plugins.statusbar.StatusBarStateController.StateListener;
 import com.android.systemui.shade.data.repository.ShadeRepository;
 import com.android.systemui.shade.transition.ShadeTransitionController;
-import com.android.systemui.shared.system.InteractionJankMonitorWrapper;
 import com.android.systemui.shared.system.QuickStepContract;
 import com.android.systemui.statusbar.CommandQueue;
 import com.android.systemui.statusbar.GestureRecorder;
@@ -372,7 +371,6 @@
     /** The current squish amount for the predictive back animation */
     private float mCurrentBackProgress = 0.0f;
     private boolean mTracking;
-    private boolean mIsTrackingExpansionFromStatusBar;
     private boolean mHintAnimationRunning;
     @Deprecated
     private KeyguardBottomAreaView mKeyguardBottomArea;
@@ -2849,7 +2847,6 @@
 
     private void onTrackingStopped(boolean expand) {
         mTracking = false;
-        maybeStopTrackingExpansionFromStatusBar(expand);
 
         updateExpansionAndVisibility();
         if (expand) {
@@ -4250,42 +4247,6 @@
     }
 
     @Override
-    public void startTrackingExpansionFromStatusBar() {
-        mIsTrackingExpansionFromStatusBar = true;
-        InteractionJankMonitorWrapper.begin(
-                mView, InteractionJankMonitorWrapper.CUJ_SHADE_EXPAND_FROM_STATUS_BAR);
-    }
-
-    /**
-     * Stops tracking an expansion that originated from the status bar (if we had started tracking
-     * it).
-     *
-     * @param expand the expand boolean passed to {@link #onTrackingStopped(boolean)}.
-     */
-    private void maybeStopTrackingExpansionFromStatusBar(boolean expand) {
-        if (!mIsTrackingExpansionFromStatusBar) {
-            return;
-        }
-        mIsTrackingExpansionFromStatusBar = false;
-
-        // Determine whether the shade actually expanded due to the status bar touch:
-        // - If the user just taps on the status bar, then #isExpanded is false but
-        // #onTrackingStopped is called with `true`.
-        // - If the user drags down on the status bar but doesn't drag down far enough, then
-        // #onTrackingStopped is called with `false` but #isExpanded is true.
-        // So, we need *both* #onTrackingStopped called with `true` *and* #isExpanded to be true in
-        // order to confirm that the shade successfully opened.
-        boolean shadeExpansionFromStatusBarSucceeded = expand && isExpanded();
-        if (shadeExpansionFromStatusBarSucceeded) {
-            InteractionJankMonitorWrapper.end(
-                    InteractionJankMonitorWrapper.CUJ_SHADE_EXPAND_FROM_STATUS_BAR);
-        } else {
-            InteractionJankMonitorWrapper.cancel(
-                    InteractionJankMonitorWrapper.CUJ_SHADE_EXPAND_FROM_STATUS_BAR);
-        }
-    }
-
-    @Override
     public void updateTouchableRegion() {
         //A layout will ensure that onComputeInternalInsets will be called and after that we can
         // resize the layout. Make sure that the window stays small for one frame until the
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java
index 0f85c76..880ba92 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowViewController.java
@@ -37,6 +37,7 @@
 import com.android.keyguard.dagger.KeyguardBouncerComponent;
 import com.android.systemui.Dumpable;
 import com.android.systemui.R;
+import com.android.systemui.animation.ActivityLaunchAnimator;
 import com.android.systemui.back.domain.interactor.BackActionInteractor;
 import com.android.systemui.bouncer.domain.interactor.BouncerMessageInteractor;
 import com.android.systemui.bouncer.ui.binder.KeyguardBouncerViewBinder;
@@ -560,7 +561,9 @@
     void setExpandAnimationRunning(boolean running) {
         if (mExpandAnimationRunning != running) {
             // TODO(b/288507023): Remove this log.
-            Log.d(TAG, "Setting mExpandAnimationRunning=" + running);
+            if (ActivityLaunchAnimator.DEBUG_LAUNCH_ANIMATION) {
+                Log.d(TAG, "Setting mExpandAnimationRunning=" + running);
+            }
             if (running) {
                 mLaunchAnimationTimeout = mClock.uptimeMillis() + 5000;
             }
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeController.java b/packages/SystemUI/src/com/android/systemui/shade/ShadeController.java
index 02f337a..447a15d 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ShadeController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeController.java
@@ -33,6 +33,8 @@
  * {@link com.android.systemui.keyguard.KeyguardViewMediator} and others.
  */
 public interface ShadeController extends CoreStartable {
+    /** True if the shade UI is enabled on this particular Android variant and false otherwise. */
+    boolean isShadeEnabled();
 
     /** Make our window larger and the shade expanded */
     void instantExpandShade();
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeControllerEmptyImpl.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeControllerEmptyImpl.kt
index 5f95bca..82959ee 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ShadeControllerEmptyImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeControllerEmptyImpl.kt
@@ -23,6 +23,7 @@
 /** Empty implementation of ShadeController for variants of Android without shades. */
 @SysUISingleton
 open class ShadeControllerEmptyImpl @Inject constructor() : ShadeController {
+    override fun isShadeEnabled() = false
     override fun start() {}
     override fun instantExpandShade() {}
     override fun instantCollapseShade() {}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeControllerImpl.java b/packages/SystemUI/src/com/android/systemui/shade/ShadeControllerImpl.java
index 9a3e4e5..367449b 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ShadeControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeControllerImpl.java
@@ -114,6 +114,11 @@
     }
 
     @Override
+    public boolean isShadeEnabled() {
+        return true;
+    }
+
+    @Override
     public void instantExpandShade() {
         // Make our window larger and the panel expanded.
         makeExpandedVisible(true /* force */);
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeViewController.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeViewController.kt
index 1121834..cdbea81 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ShadeViewController.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeViewController.kt
@@ -248,9 +248,6 @@
     /** Sends an external (e.g. Status Bar) touch event to the Shade touch handler. */
     fun handleExternalTouch(event: MotionEvent): Boolean
 
-    /** Starts tracking a shade expansion gesture that originated from the status bar. */
-    fun startTrackingExpansionFromStatusBar()
-
     /**
      * Performs haptic feedback from a view with a haptic feedback constant.
      *
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeViewControllerEmptyImpl.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeViewControllerEmptyImpl.kt
index 6a2bef2..1893756 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ShadeViewControllerEmptyImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeViewControllerEmptyImpl.kt
@@ -86,7 +86,6 @@
     override fun handleExternalTouch(event: MotionEvent): Boolean {
         return false
     }
-    override fun startTrackingExpansionFromStatusBar() {}
     override fun performHapticFeedback(constant: Int) {}
 
     override val shadeHeadsUpTracker = ShadeHeadsUpTrackerEmptyImpl()
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeViewProviderModule.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeViewProviderModule.kt
index f1e75b1..3f7512a 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ShadeViewProviderModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeViewProviderModule.kt
@@ -76,7 +76,7 @@
             scenesProvider: Provider<Set<@JvmSuppressWildcards Scene>>,
             layoutInsetController: NotificationInsetsController,
         ): WindowRootView {
-            return if (Flags.SCENE_CONTAINER_ENABLED && sceneContainerFlags.isEnabled()) {
+            return if (sceneContainerFlags.isEnabled()) {
                 val sceneWindowRootView =
                     layoutInflater.inflate(R.layout.scene_window_root, null) as SceneWindowRootView
                 sceneWindowRootView.init(
diff --git a/packages/SystemUI/src/com/android/systemui/shade/data/repository/ShadeRepository.kt b/packages/SystemUI/src/com/android/systemui/shade/data/repository/ShadeRepository.kt
index 509921f..947259a 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/data/repository/ShadeRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/data/repository/ShadeRepository.kt
@@ -42,7 +42,7 @@
 
     /**
      * The amount the lockscreen shade has dragged down by the user, [0-1]. 0 means fully collapsed,
-     * 1 means fully expanded.
+     * 1 means fully expanded. Value resets to 0 when the user finishes dragging.
      */
     val lockscreenShadeExpansion: StateFlow<Float>
 
diff --git a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractor.kt b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractor.kt
index 3b19411..95a072c 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/domain/interactor/ShadeInteractor.kt
@@ -20,6 +20,10 @@
 import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.keyguard.data.repository.KeyguardRepository
 import com.android.systemui.keyguard.shared.model.StatusBarState
+import com.android.systemui.scene.domain.interactor.SceneInteractor
+import com.android.systemui.scene.shared.flag.SceneContainerFlags
+import com.android.systemui.scene.shared.model.ObservableTransitionState
+import com.android.systemui.scene.shared.model.SceneKey
 import com.android.systemui.shade.data.repository.ShadeRepository
 import com.android.systemui.statusbar.disableflags.data.repository.DisableFlagsRepository
 import com.android.systemui.statusbar.notification.stack.domain.interactor.SharedNotificationContainerInteractor
@@ -28,22 +32,29 @@
 import com.android.systemui.user.domain.interactor.UserInteractor
 import com.android.systemui.util.kotlin.pairwise
 import javax.inject.Inject
+import javax.inject.Provider
 import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.SharingStarted
 import kotlinx.coroutines.flow.StateFlow
 import kotlinx.coroutines.flow.combine
 import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.flatMapLatest
+import kotlinx.coroutines.flow.flowOf
 import kotlinx.coroutines.flow.map
 import kotlinx.coroutines.flow.stateIn
 
 /** Business logic for shade interactions. */
+@OptIn(ExperimentalCoroutinesApi::class)
 @SysUISingleton
 class ShadeInteractor
 @Inject
 constructor(
     @Application scope: CoroutineScope,
     disableFlagsRepository: DisableFlagsRepository,
+    sceneContainerFlags: SceneContainerFlags,
+    sceneInteractorProvider: Provider<SceneInteractor>,
     keyguardRepository: KeyguardRepository,
     userSetupRepository: UserSetupRepository,
     deviceProvisionedController: DeviceProvisionedController,
@@ -68,28 +79,45 @@
 
     /** The amount [0-1] that the shade has been opened */
     val shadeExpansion: Flow<Float> =
-        combine(
-            repository.lockscreenShadeExpansion,
-            keyguardRepository.statusBarState,
-            repository.legacyShadeExpansion,
-            repository.qsExpansion,
-            splitShadeEnabled
-        ) { dragDownAmount, statusBarState, legacyShadeExpansion, qsExpansion, splitShadeEnabled ->
-            when (statusBarState) {
-                // legacyShadeExpansion is 1 instead of 0 when QS is expanded
-                StatusBarState.SHADE ->
-                    if (!splitShadeEnabled && qsExpansion > 0f) 0f else legacyShadeExpansion
-                StatusBarState.KEYGUARD -> dragDownAmount
-                // This is required, as shadeExpansion gets reset to 0f even with the shade open
-                StatusBarState.SHADE_LOCKED -> 1f
-            }
+        if (sceneContainerFlags.isEnabled()) {
+            sceneBasedExpansion(sceneInteractorProvider.get(), SceneKey.Shade)
+        } else {
+            combine(
+                    repository.lockscreenShadeExpansion,
+                    keyguardRepository.statusBarState,
+                    repository.legacyShadeExpansion,
+                    repository.qsExpansion,
+                    splitShadeEnabled
+                ) {
+                    lockscreenShadeExpansion,
+                    statusBarState,
+                    legacyShadeExpansion,
+                    qsExpansion,
+                    splitShadeEnabled ->
+                    when (statusBarState) {
+                        // legacyShadeExpansion is 1 instead of 0 when QS is expanded
+                        StatusBarState.SHADE ->
+                            if (!splitShadeEnabled && qsExpansion > 0f) 0f else legacyShadeExpansion
+                        StatusBarState.KEYGUARD -> lockscreenShadeExpansion
+                        // dragDownAmount, which drives lockscreenShadeExpansion resets to 0f when
+                        // the pointer is lifted and the lockscreen shade is fully expanded
+                        StatusBarState.SHADE_LOCKED -> 1f
+                    }
+                }
+                .distinctUntilChanged()
         }
 
     /**
      * The amount [0-1] QS has been opened. Normal shade with notifications (QQS) visible will
      * report 0f.
      */
-    val qsExpansion: StateFlow<Float> = repository.qsExpansion
+    val qsExpansion: StateFlow<Float> =
+        if (sceneContainerFlags.isEnabled()) {
+            sceneBasedExpansion(sceneInteractorProvider.get(), SceneKey.QuickSettings)
+                .stateIn(scope, SharingStarted.Eagerly, 0f)
+        } else {
+            repository.qsExpansion
+        }
 
     /** The amount [0-1] either QS or the shade has been opened */
     val anyExpansion: StateFlow<Float> =
@@ -119,4 +147,26 @@
                 disableFlags.isQuickSettingsEnabled() &&
                 !isDozing
         }
+
+    fun sceneBasedExpansion(sceneInteractor: SceneInteractor, sceneKey: SceneKey) =
+        sceneInteractor.transitionState
+            .flatMapLatest { state ->
+                when (state) {
+                    is ObservableTransitionState.Idle ->
+                        if (state.scene == sceneKey) {
+                            flowOf(1f)
+                        } else {
+                            flowOf(0f)
+                        }
+                    is ObservableTransitionState.Transition ->
+                        if (state.toScene == sceneKey) {
+                            state.progress
+                        } else if (state.fromScene == sceneKey) {
+                            state.progress.map { progress -> 1 - progress }
+                        } else {
+                            flowOf(0f)
+                        }
+                }
+            }
+            .distinctUntilChanged()
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationLaunchAnimatorController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationLaunchAnimatorController.kt
index 0aedbf3..c62546f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationLaunchAnimatorController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/NotificationLaunchAnimatorController.kt
@@ -140,8 +140,9 @@
     }
 
     override fun onIntentStarted(willAnimate: Boolean) {
-        // TODO(b/288507023): Remove this log.
-        Log.d(TAG, "onIntentStarted(willAnimate=$willAnimate)")
+        if (ActivityLaunchAnimator.DEBUG_LAUNCH_ANIMATION) {
+            Log.d(TAG, "onIntentStarted(willAnimate=$willAnimate)")
+        }
         notificationExpansionRepository.setIsExpandAnimationRunning(willAnimate)
         notificationEntry.isExpandAnimationRunning = willAnimate
 
@@ -172,8 +173,9 @@
     }
 
     override fun onLaunchAnimationCancelled(newKeyguardOccludedState: Boolean?) {
-        // TODO(b/288507023): Remove this log.
-        Log.d(TAG, "onLaunchAnimationCancelled()")
+        if (ActivityLaunchAnimator.DEBUG_LAUNCH_ANIMATION) {
+            Log.d(TAG, "onLaunchAnimationCancelled()")
+        }
 
         // TODO(b/184121838): Should we call InteractionJankMonitor.cancel if the animation started
         // here?
@@ -191,8 +193,9 @@
     }
 
     override fun onLaunchAnimationEnd(isExpandingFullyAbove: Boolean) {
-        // TODO(b/288507023): Remove this log.
-        Log.d(TAG, "onLaunchAnimationEnd()")
+        if (ActivityLaunchAnimator.DEBUG_LAUNCH_ANIMATION) {
+            Log.d(TAG, "onLaunchAnimationEnd()")
+        }
         jankMonitor.end(InteractionJankMonitor.CUJ_NOTIFICATION_APP_START)
 
         notification.isExpandAnimationRunning = false
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/data/repository/NotificationExpansionRepository.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/data/repository/NotificationExpansionRepository.kt
index 8754c4a..6f0a97a 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/data/repository/NotificationExpansionRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/data/repository/NotificationExpansionRepository.kt
@@ -17,6 +17,7 @@
 package com.android.systemui.statusbar.notification.data.repository
 
 import android.util.Log
+import com.android.systemui.animation.ActivityLaunchAnimator
 import com.android.systemui.dagger.SysUISingleton
 import javax.inject.Inject
 import kotlinx.coroutines.flow.Flow
@@ -40,8 +41,9 @@
 
     /** Sets whether the notification expansion animation is currently running. */
     fun setIsExpandAnimationRunning(running: Boolean) {
-        // TODO(b/288507023): Remove this log.
-        Log.d(TAG, "setIsExpandAnimationRunning(running=$running)")
+        if (ActivityLaunchAnimator.DEBUG_LAUNCH_ANIMATION) {
+            Log.d(TAG, "setIsExpandAnimationRunning(running=$running)")
+        }
         _isExpandAnimationRunning.value = running
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotifInflationErrorManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotifInflationErrorManager.java
index 51eb9f7..c24e9dc 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotifInflationErrorManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/NotifInflationErrorManager.java
@@ -36,7 +36,7 @@
 @SysUISingleton
 public class NotifInflationErrorManager {
 
-    Set<NotificationEntry> mErroredNotifs = new ArraySet<>();
+    Set<String> mErroredNotifs = new ArraySet<>();
     List<NotifInflationErrorListener> mListeners = new ArrayList<>();
 
     @Inject
@@ -48,7 +48,7 @@
      * @param e the exception encountered while inflating
      */
     public void setInflationError(NotificationEntry entry, Exception e) {
-        mErroredNotifs.add(entry);
+        mErroredNotifs.add(entry.getKey());
         for (int i = 0; i < mListeners.size(); i++) {
             mListeners.get(i).onNotifInflationError(entry, e);
         }
@@ -58,8 +58,8 @@
      * Notification inflated successfully and is no longer errored out.
      */
     public void clearInflationError(NotificationEntry entry) {
-        if (mErroredNotifs.contains(entry)) {
-            mErroredNotifs.remove(entry);
+        if (mErroredNotifs.contains(entry.getKey())) {
+            mErroredNotifs.remove(entry.getKey());
             for (int i = 0; i < mListeners.size(); i++) {
                 mListeners.get(i).onNotifInflationErrorCleared(entry);
             }
@@ -70,7 +70,7 @@
      * Whether or not the notification encountered an exception while inflating.
      */
     public boolean hasInflationError(@NonNull NotificationEntry entry) {
-        return mErroredNotifs.contains(entry);
+        return mErroredNotifs.contains(entry.getKey());
     }
 
     /**
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
index d8f513c..93b5ff7 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
@@ -1659,8 +1659,9 @@
 
     @VisibleForTesting
     void onKeyguardTransitionChanged(TransitionStep transitionStep) {
-        boolean isTransitionToAod = transitionStep.getFrom().equals(KeyguardState.GONE)
-                && transitionStep.getTo().equals(KeyguardState.AOD);
+        boolean isTransitionToAod = transitionStep.getTo().equals(KeyguardState.AOD)
+                && (transitionStep.getFrom().equals(KeyguardState.GONE)
+                || transitionStep.getFrom().equals(KeyguardState.OCCLUDED));
         if (mIsInTransitionToAod != isTransitionToAod) {
             mIsInTransitionToAod = isTransitionToAod;
             updateShowEmptyShadeView();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java
index 0031b574..3b9afa1 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java
@@ -17,8 +17,6 @@
 package com.android.systemui.statusbar.phone;
 
 import static android.app.StatusBarManager.SESSION_KEYGUARD;
-
-import static com.android.systemui.flags.Flags.FP_LISTEN_OCCLUDING_APPS;
 import static com.android.systemui.flags.Flags.ONE_WAY_HAPTICS_API_MIGRATION;
 import static com.android.systemui.keyguard.WakefulnessLifecycle.UNKNOWN_LAST_WAKE_TIME;
 
@@ -702,8 +700,7 @@
         }
 
         final boolean screenOff = !mUpdateMonitor.isDeviceInteractive();
-        if (!mVibratorHelper.hasVibrator() && (screenOff || (mUpdateMonitor.isDreaming()
-                && !mFeatureFlags.isEnabled(FP_LISTEN_OCCLUDING_APPS)))) {
+        if (!mVibratorHelper.hasVibrator() && screenOff) {
             mLogger.d("wakeup device on authentication failure (device doesn't have a vibrator)");
             startWakeAndUnlock(MODE_ONLY_WAKE);
         } else if (biometricSourceType == BiometricSourceType.FINGERPRINT
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfaces.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfaces.java
index 6b7048a..cfa481e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfaces.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfaces.java
@@ -291,8 +291,6 @@
     @VisibleForTesting
     void updateScrimController();
 
-    boolean isKeyguardShowing();
-
     boolean shouldIgnoreTouch();
 
     boolean isDeviceInteractive();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesEmptyImpl.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesEmptyImpl.kt
index 25647268..ff380db 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesEmptyImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesEmptyImpl.kt
@@ -79,7 +79,6 @@
     override fun setTransitionToFullShadeProgress(transitionToFullShadeProgress: Float) {}
     override fun setPrimaryBouncerHiddenFraction(expansion: Float) {}
     override fun updateScrimController() {}
-    override fun isKeyguardShowing() = false
     override fun shouldIgnoreTouch() = false
     override fun isDeviceInteractive() = false
     override fun awakenDreams() {}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
index 93bc36c..490c469 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesImpl.java
@@ -24,11 +24,9 @@
 import static android.view.WindowInsetsController.APPEARANCE_LOW_PROFILE_BARS;
 import static android.view.WindowInsetsController.APPEARANCE_OPAQUE_STATUS_BARS;
 import static android.view.WindowInsetsController.APPEARANCE_SEMI_TRANSPARENT_STATUS_BARS;
-
 import static androidx.core.view.ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_AUTO;
 import static androidx.core.view.ViewCompat.IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS;
 import static androidx.lifecycle.Lifecycle.State.RESUMED;
-
 import static com.android.systemui.Dependency.TIME_TICK_HANDLER_NAME;
 import static com.android.systemui.charging.WirelessChargingAnimation.UNKNOWN_BATTERY_LEVEL;
 import static com.android.systemui.statusbar.NotificationLockscreenUserManager.PERMISSION_SELF;
@@ -1433,7 +1431,7 @@
         // - QS is expanded and we're swiping - swiping up now will hide QS, not dismiss the
         //   keyguard.
         // - Shade is in QQS over keyguard - swiping up should take us back to keyguard
-        if (!isKeyguardShowing()
+        if (!mKeyguardStateController.isShowing()
                 || mStatusBarKeyguardViewManager.primaryBouncerIsOrWillBeShowing()
                 || mKeyguardStateController.isOccluded()
                 || !mKeyguardStateController.canDismissLockScreen()
@@ -2873,7 +2871,8 @@
         if (mDevicePolicyManager.getCameraDisabled(null,
                 mLockscreenUserManager.getCurrentUserId())) {
             return false;
-        } else if (isKeyguardShowing() && mStatusBarKeyguardViewManager.isSecure()) {
+        } else if (mKeyguardStateController.isShowing()
+                && mStatusBarKeyguardViewManager.isSecure()) {
             // Check if the admin has disabled the camera specifically for the keyguard
             return (mDevicePolicyManager.getKeyguardDisabledFeatures(null,
                     mLockscreenUserManager.getCurrentUserId())
@@ -2989,12 +2988,6 @@
 
         Trace.endSection();
     }
-
-    @Override
-    public boolean isKeyguardShowing() {
-        return mKeyguardStateController.isShowing();
-    }
-
     @Override
     public boolean shouldIgnoreTouch() {
         return (mStatusBarStateController.isDozing()
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewController.kt b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewController.kt
index 23b0ee0..dd471aa 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewController.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/PhoneStatusBarViewController.kt
@@ -206,7 +206,6 @@
                     shadeLogger.logMotionEvent(event, "top edge touch ignored")
                     return true
                 }
-                shadeViewController.startTrackingExpansionFromStatusBar()
             }
             return shadeViewController.handleExternalTouch(event)
         }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
index 27b8406..3afbbfd 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
@@ -17,7 +17,6 @@
 package com.android.systemui.statusbar.phone;
 
 import static android.view.WindowInsets.Type.navigationBars;
-
 import static com.android.systemui.bouncer.shared.constants.KeyguardBouncerConstants.EXPANSION_HIDDEN;
 import static com.android.systemui.plugins.ActivityStarter.OnDismissAction;
 import static com.android.systemui.statusbar.phone.BiometricUnlockController.MODE_WAKE_AND_UNLOCK;
@@ -67,9 +66,12 @@
 import com.android.systemui.dreams.DreamOverlayStateController;
 import com.android.systemui.flags.FeatureFlags;
 import com.android.systemui.flags.Flags;
+import com.android.systemui.keyguard.domain.interactor.KeyguardDismissActionInteractor;
 import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor;
 import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor;
 import com.android.systemui.keyguard.domain.interactor.WindowManagerLockscreenVisibilityInteractor;
+import com.android.systemui.keyguard.shared.model.DismissAction;
+import com.android.systemui.keyguard.shared.model.KeyguardDone;
 import com.android.systemui.navigationbar.NavigationBarView;
 import com.android.systemui.navigationbar.NavigationModeController;
 import com.android.systemui.navigationbar.TaskbarDelegate;
@@ -104,6 +106,7 @@
 import javax.inject.Inject;
 
 import kotlinx.coroutines.CoroutineDispatcher;
+import kotlinx.coroutines.ExperimentalCoroutinesApi;
 
 /**
  * Manages creating, showing, hiding and resetting the keyguard within the status bar. Calls back
@@ -111,7 +114,7 @@
  * which is in turn, reported to this class by the current
  * {@link com.android.keyguard.KeyguardViewController}.
  */
-@SysUISingleton
+@ExperimentalCoroutinesApi @SysUISingleton
 public class StatusBarKeyguardViewManager implements RemoteInputController.Callback,
         StatusBarStateController.StateListener, ConfigurationController.ConfigurationListener,
         ShadeExpansionListener, NavigationModeController.ModeChangedListener,
@@ -338,6 +341,7 @@
         }
     };
     private Lazy<WindowManagerLockscreenVisibilityInteractor> mWmLockscreenVisibilityInteractor;
+    private Lazy<KeyguardDismissActionInteractor> mKeyguardDismissActionInteractor;
 
     @Inject
     public StatusBarKeyguardViewManager(
@@ -367,7 +371,8 @@
             ActivityStarter activityStarter,
             KeyguardTransitionInteractor keyguardTransitionInteractor,
             @Main CoroutineDispatcher mainDispatcher,
-            Lazy<WindowManagerLockscreenVisibilityInteractor> wmLockscreenVisibilityInteractor
+            Lazy<WindowManagerLockscreenVisibilityInteractor> wmLockscreenVisibilityInteractor,
+            Lazy<KeyguardDismissActionInteractor> keyguardDismissActionInteractorLazy
     ) {
         mContext = context;
         mViewMediatorCallback = callback;
@@ -400,6 +405,7 @@
         mKeyguardTransitionInteractor = keyguardTransitionInteractor;
         mMainDispatcher = mainDispatcher;
         mWmLockscreenVisibilityInteractor = wmLockscreenVisibilityInteractor;
+        mKeyguardDismissActionInteractor = keyguardDismissActionInteractorLazy;
     }
 
     KeyguardTransitionInteractor mKeyguardTransitionInteractor;
@@ -692,6 +698,45 @@
 
     public void dismissWithAction(OnDismissAction r, Runnable cancelAction,
             boolean afterKeyguardGone, String message) {
+        if (mFlags.isEnabled(Flags.REFACTOR_KEYGUARD_DISMISS_INTENT)) {
+            if (r == null) {
+                return;
+            }
+            Trace.beginSection("StatusBarKeyguardViewManager#interactorDismissWithAction");
+            if (afterKeyguardGone) {
+                mKeyguardDismissActionInteractor.get().setDismissAction(
+                        new DismissAction.RunAfterKeyguardGone(
+                                () -> {
+                                    r.onDismiss();
+                                    return null;
+                                },
+                                (cancelAction != null) ? cancelAction : () -> {},
+                                message == null ? "" : message,
+                                r.willRunAnimationOnKeyguard()
+                        )
+                );
+            } else {
+                mKeyguardDismissActionInteractor.get().setDismissAction(
+                        new DismissAction.RunImmediately(
+                                () -> {
+                                    if (r.onDismiss()) {
+                                        return KeyguardDone.LATER;
+                                    } else {
+                                        return KeyguardDone.IMMEDIATE;
+                                    }
+                                },
+                                (cancelAction != null) ? cancelAction : () -> {},
+                                message == null ? "" : message,
+                                r.willRunAnimationOnKeyguard()
+                        )
+                );
+            }
+
+            showBouncer(true);
+            Trace.endSection();
+            return;
+        }
+
         if (mKeyguardStateController.isShowing()) {
             try {
                 Trace.beginSection("StatusBarKeyguardViewManager#dismissWithAction");
@@ -705,9 +750,12 @@
                     return;
                 }
 
-                mAfterKeyguardGoneAction = r;
-                mKeyguardGoneCancelAction = cancelAction;
-                mDismissActionWillAnimateOnKeyguard = r != null && r.willRunAnimationOnKeyguard();
+                if (!mFlags.isEnabled(Flags.REFACTOR_KEYGUARD_DISMISS_INTENT)) {
+                    mAfterKeyguardGoneAction = r;
+                    mKeyguardGoneCancelAction = cancelAction;
+                    mDismissActionWillAnimateOnKeyguard = r != null
+                            && r.willRunAnimationOnKeyguard();
+                }
 
                 // If there is an alternate auth interceptor (like the UDFPS), show that one
                 // instead of the bouncer.
@@ -755,6 +803,12 @@
      * Adds a {@param runnable} to be executed after Keyguard is gone.
      */
     public void addAfterKeyguardGoneRunnable(Runnable runnable) {
+        if (mFlags.isEnabled(Flags.REFACTOR_KEYGUARD_DISMISS_INTENT)) {
+            if (runnable != null) {
+                mKeyguardDismissActionInteractor.get().runAfterKeyguardGone(runnable);
+            }
+            return;
+        }
         mAfterKeyguardGoneRunnables.add(runnable);
     }
 
@@ -936,7 +990,11 @@
             // We update the state (which will show the keyguard) only if an animation will run on
             // the keyguard. If there is no animation, we wait before updating the state so that we
             // go directly from bouncer to launcher/app.
-            if (mDismissActionWillAnimateOnKeyguard) {
+            if (mFlags.isEnabled(Flags.REFACTOR_KEYGUARD_DISMISS_INTENT)) {
+                if (mKeyguardDismissActionInteractor.get().runDismissAnimationOnKeyguard()) {
+                    updateStates();
+                }
+            } else if (mDismissActionWillAnimateOnKeyguard) {
                 updateStates();
             }
         } else if (finishRunnable != null) {
@@ -1059,6 +1117,9 @@
     }
 
     private void executeAfterKeyguardGoneAction() {
+        if (mFlags.isEnabled(Flags.REFACTOR_KEYGUARD_DISMISS_INTENT)) {
+            return;
+        }
         if (mAfterKeyguardGoneAction != null) {
             mAfterKeyguardGoneAction.onDismiss();
             mAfterKeyguardGoneAction = null;
@@ -1351,10 +1412,10 @@
 
     /**
      * Notifies that the user has authenticated by other means than using the bouncer, for example,
-     * fingerprint.
+     * fingerprint and the keyguard should immediately dismiss.
      */
     public void notifyKeyguardAuthenticated(boolean strongAuth) {
-        mPrimaryBouncerInteractor.notifyKeyguardAuthenticated(strongAuth);
+        mPrimaryBouncerInteractor.notifyKeyguardAuthenticatedBiometrics(strongAuth);
 
         if (mAlternateBouncerInteractor.isVisibleState()) {
             hideAlternateBouncer(false);
@@ -1442,6 +1503,8 @@
         pw.println("  isBouncerShowing(): " + isBouncerShowing());
         pw.println("  bouncerIsOrWillBeShowing(): " + primaryBouncerIsOrWillBeShowing());
         pw.println("  Registered KeyguardViewManagerCallbacks:");
+        pw.println(" refactorKeyguardDismissIntent enabled:"
+                + mFlags.isEnabled(Flags.REFACTOR_KEYGUARD_DISMISS_INTENT));
         for (KeyguardViewManagerCallback callback : mCallbacks) {
             pw.println("      " + callback);
         }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java
index ecc996c..53662f4 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java
@@ -53,8 +53,8 @@
 import com.android.systemui.statusbar.notification.collection.NotificationEntry;
 import com.android.systemui.statusbar.notification.collection.render.NotifShadeEventSource;
 import com.android.systemui.statusbar.notification.domain.interactor.NotificationsInteractor;
-import com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProvider;
 import com.android.systemui.statusbar.notification.interruption.NotificationInterruptSuppressor;
+import com.android.systemui.statusbar.notification.interruption.VisualInterruptionDecisionProvider;
 import com.android.systemui.statusbar.notification.row.ExpandableNotificationRow;
 import com.android.systemui.statusbar.notification.row.NotificationGutsManager;
 import com.android.systemui.statusbar.notification.row.NotificationGutsManager.OnSettingsClickListener;
@@ -116,7 +116,7 @@
             NotificationMediaManager notificationMediaManager,
             NotificationGutsManager notificationGutsManager,
             InitController initController,
-            NotificationInterruptStateProvider notificationInterruptStateProvider,
+            VisualInterruptionDecisionProvider visualInterruptionDecisionProvider,
             NotificationRemoteInputManager remoteInputManager,
             NotificationRemoteInputManager.Callback remoteInputManagerCallback,
             NotificationListContainer notificationListContainer) {
@@ -162,7 +162,7 @@
         initController.addPostInitTask(() -> {
             mNotifShadeEventSource.setShadeEmptiedCallback(this::maybeClosePanelForShadeEmptied);
             mNotifShadeEventSource.setNotifRemovedByUserCallback(this::maybeEndAmbientPulse);
-            notificationInterruptStateProvider.addSuppressor(mInterruptSuppressor);
+            visualInterruptionDecisionProvider.addLegacySuppressor(mInterruptSuppressor);
             mLockscreenUserManager.setUpWithPresenter(this);
             mGutsManager.setUpWithPresenter(
                     this, mNotifListContainer, mOnSettingsClickListener);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BluetoothControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BluetoothControllerImpl.java
index 9b0daca..945cc6b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/BluetoothControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/BluetoothControllerImpl.java
@@ -38,8 +38,6 @@
 import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.dump.DumpManager;
-import com.android.systemui.flags.FeatureFlags;
-import com.android.systemui.flags.Flags;
 import com.android.systemui.settings.UserTracker;
 import com.android.systemui.statusbar.policy.bluetooth.BluetoothRepository;
 import com.android.systemui.statusbar.policy.bluetooth.ConnectionStatusModel;
@@ -64,7 +62,6 @@
         CachedBluetoothDevice.Callback, LocalBluetoothProfileManager.ServiceListener {
     private static final String TAG = "BluetoothController";
 
-    private final FeatureFlags mFeatureFlags;
     private final DumpManager mDumpManager;
     private final BluetoothLogger mLogger;
     private final BluetoothRepository mBluetoothRepository;
@@ -89,7 +86,6 @@
     @Inject
     public BluetoothControllerImpl(
             Context context,
-            FeatureFlags featureFlags,
             UserTracker userTracker,
             DumpManager dumpManager,
             BluetoothLogger logger,
@@ -97,7 +93,6 @@
             @Main Looper mainLooper,
             @Nullable LocalBluetoothManager localBluetoothManager,
             @Nullable BluetoothAdapter bluetoothAdapter) {
-        mFeatureFlags = featureFlags;
         mDumpManager = dumpManager;
         mLogger = logger;
         mBluetoothRepository = bluetoothRepository;
@@ -252,37 +247,8 @@
     }
 
     private void updateConnected() {
-        if (mFeatureFlags.isEnabled(Flags.NEW_BLUETOOTH_REPOSITORY)) {
-            mBluetoothRepository.fetchConnectionStatusInBackground(
-                    getDevices(), this::onConnectionStatusFetched);
-        } else {
-            updateConnectedOld();
-        }
-    }
-
-    /** Used only if {@link Flags.NEW_BLUETOOTH_REPOSITORY} is *not* enabled. */
-    private void updateConnectedOld() {
-        // Make sure our connection state is up to date.
-        int state = mLocalBluetoothManager.getBluetoothAdapter().getConnectionState();
-        List<CachedBluetoothDevice> newList = new ArrayList<>();
-        // If any of the devices are in a higher state than the adapter, move the adapter into
-        // that state.
-        for (CachedBluetoothDevice device : getDevices()) {
-            int maxDeviceState = device.getMaxConnectionState();
-            if (maxDeviceState > state) {
-                state = maxDeviceState;
-            }
-            if (device.isConnected()) {
-                newList.add(device);
-            }
-        }
-
-        if (newList.isEmpty() && state == BluetoothAdapter.STATE_CONNECTED) {
-            // If somehow we think we are connected, but have no connected devices, we aren't
-            // connected.
-            state = BluetoothAdapter.STATE_DISCONNECTED;
-        }
-        onConnectionStatusFetched(new ConnectionStatusModel(state, newList));
+        mBluetoothRepository.fetchConnectionStatusInBackground(
+                getDevices(), this::onConnectionStatusFetched);
     }
 
     private void onConnectionStatusFetched(ConnectionStatusModel status) {
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPinViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPinViewControllerTest.kt
index 61acacd..33d4097 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPinViewControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardPinViewControllerTest.kt
@@ -113,6 +113,7 @@
         // For posture tests:
         `when`(mockKeyguardPinView.buttons).thenReturn(arrayOf())
         `when`(lockPatternUtils.getPinLength(anyInt())).thenReturn(6)
+        `when`(featureFlags.isEnabled(Flags.LOCKSCREEN_ENABLE_LANDSCAPE)).thenReturn(false)
 
         objectKeyguardPINView =
             View.inflate(mContext, R.layout.keyguard_pin_view, null)
@@ -122,6 +123,7 @@
     private fun constructPinViewController(
         mKeyguardPinView: KeyguardPINView
     ): KeyguardPinViewController {
+        mKeyguardPinView.setIsLockScreenLandscapeEnabled(false)
         return KeyguardPinViewController(
             mKeyguardPinView,
             keyguardUpdateMonitor,
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.kt
index 5da919b..7c1861e 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.kt
@@ -43,6 +43,7 @@
 import com.android.systemui.biometrics.FaceAuthAccessibilityDelegate
 import com.android.systemui.biometrics.SideFpsController
 import com.android.systemui.biometrics.SideFpsUiRequestSource
+import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerInteractor
 import com.android.systemui.bouncer.shared.constants.KeyguardBouncerConstants
 import com.android.systemui.classifier.FalsingA11yDelegate
 import com.android.systemui.classifier.FalsingCollector
@@ -72,6 +73,7 @@
 import com.android.systemui.util.mockito.whenever
 import com.android.systemui.util.settings.GlobalSettings
 import com.google.common.truth.Truth
+import dagger.Lazy
 import java.util.Optional
 import junit.framework.Assert
 import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -154,6 +156,7 @@
     private lateinit var sceneInteractor: SceneInteractor
     private lateinit var keyguardTransitionInteractor: KeyguardTransitionInteractor
     private lateinit var authenticationInteractor: AuthenticationInteractor
+    @Mock private lateinit var primaryBouncerInteractor: Lazy<PrimaryBouncerInteractor>
     private lateinit var sceneTransitionStateFlow: MutableStateFlow<ObservableTransitionState>
 
     private lateinit var underTest: KeyguardSecurityContainerController
@@ -193,6 +196,7 @@
         featureFlags.set(Flags.REVAMPED_BOUNCER_MESSAGES, true)
         featureFlags.set(Flags.BOUNCER_USER_SWITCHER, false)
         featureFlags.set(Flags.KEYGUARD_WM_STATE_REFACTOR, false)
+        featureFlags.set(Flags.REFACTOR_KEYGUARD_DISMISS_INTENT, false)
 
         keyguardPasswordViewController =
             KeyguardPasswordViewController(
@@ -257,7 +261,8 @@
                 userInteractor,
                 deviceProvisionedController,
                 faceAuthAccessibilityDelegate,
-                keyguardTransitionInteractor
+                keyguardTransitionInteractor,
+                primaryBouncerInteractor,
             ) {
                 authenticationInteractor
             }
@@ -381,6 +386,36 @@
     }
 
     @Test
+    fun showSecurityScreen_oneHandedMode_flagEnabled_oneHandedMode_simpin() {
+        testableResources.addOverride(R.bool.can_use_one_handed_bouncer, true)
+        setupGetSecurityView(SecurityMode.SimPin)
+        verify(view)
+            .initMode(
+                eq(KeyguardSecurityContainer.MODE_ONE_HANDED),
+                eq(globalSettings),
+                eq(falsingManager),
+                eq(userSwitcherController),
+                any(),
+                eq(falsingA11yDelegate)
+            )
+    }
+
+    @Test
+    fun showSecurityScreen_oneHandedMode_flagEnabled_oneHandedMode_simpuk() {
+        testableResources.addOverride(R.bool.can_use_one_handed_bouncer, true)
+        setupGetSecurityView(SecurityMode.SimPuk)
+        verify(view)
+            .initMode(
+                eq(KeyguardSecurityContainer.MODE_ONE_HANDED),
+                eq(globalSettings),
+                eq(falsingManager),
+                eq(userSwitcherController),
+                any(),
+                eq(falsingA11yDelegate)
+            )
+    }
+
+    @Test
     fun showSecurityScreen_twoHandedMode_flagEnabled_noOneHandedMode() {
         testableResources.addOverride(R.bool.can_use_one_handed_bouncer, true)
         setupGetSecurityView(SecurityMode.Password)
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
index 0cd82f0..562d3a5 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/KeyguardUpdateMonitorTest.java
@@ -29,7 +29,6 @@
 import static android.hardware.fingerprint.FingerprintSensorProperties.TYPE_UDFPS_OPTICAL;
 import static android.telephony.SubscriptionManager.DATA_ROAMING_DISABLE;
 import static android.telephony.SubscriptionManager.NAME_SOURCE_CARRIER_ID;
-
 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_BOOT;
 import static com.android.internal.widget.LockPatternUtils.StrongAuthTracker.STRONG_AUTH_REQUIRED_AFTER_USER_LOCKDOWN;
@@ -39,16 +38,11 @@
 import static com.android.keyguard.KeyguardUpdateMonitor.DEFAULT_CANCEL_SIGNAL_TIMEOUT;
 import static com.android.keyguard.KeyguardUpdateMonitor.HAL_POWER_PRESS_TIMEOUT;
 import static com.android.keyguard.KeyguardUpdateMonitor.getCurrentUser;
-import static com.android.systemui.flags.Flags.FP_LISTEN_OCCLUDING_APPS;
-import static com.android.systemui.flags.Flags.STOP_FACE_AUTH_ON_DISPLAY_OFF;
 import static com.android.systemui.statusbar.policy.DevicePostureController.DEVICE_POSTURE_CLOSED;
 import static com.android.systemui.statusbar.policy.DevicePostureController.DEVICE_POSTURE_OPENED;
 import static com.android.systemui.statusbar.policy.DevicePostureController.DEVICE_POSTURE_UNKNOWN;
-
 import static com.google.common.truth.Truth.assertThat;
-
 import static junit.framework.Assert.assertEquals;
-
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyBoolean;
 import static org.mockito.ArgumentMatchers.anyInt;
@@ -145,7 +139,6 @@
 import com.android.systemui.biometrics.FingerprintInteractiveToAuthProvider;
 import com.android.systemui.broadcast.BroadcastDispatcher;
 import com.android.systemui.dump.DumpManager;
-import com.android.systemui.flags.FakeFeatureFlags;
 import com.android.systemui.log.SessionTracker;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.settings.FakeDisplayTracker;
@@ -299,7 +292,6 @@
     private final Executor mBackgroundExecutor = Runnable::run;
     private final Executor mMainExecutor = Runnable::run;
     private TestableLooper mTestableLooper;
-    private FakeFeatureFlags mFeatureFlags;
     private Handler mHandler;
     private TestableKeyguardUpdateMonitor mKeyguardUpdateMonitor;
     private MockitoSession mMockitoSession;
@@ -354,9 +346,6 @@
 
         mTestableLooper = TestableLooper.get(this);
         allowTestableLooperAsMainThread();
-        mFeatureFlags = new FakeFeatureFlags();
-        mFeatureFlags.set(FP_LISTEN_OCCLUDING_APPS, false);
-        mFeatureFlags.set(STOP_FACE_AUTH_ON_DISPLAY_OFF, false);
 
         when(mSecureSettings.getUriFor(anyString())).thenReturn(mURI);
 
@@ -1593,8 +1582,6 @@
     @Test
     public void listenForFingerprint_whenOccludingAppPkgOnAllowlist()
             throws RemoteException {
-        mFeatureFlags.set(FP_LISTEN_OCCLUDING_APPS, true);
-
         // GIVEN keyguard isn't visible (app occluding)
         mKeyguardUpdateMonitor.dispatchStartedWakingUp(PowerManager.WAKE_REASON_POWER_BUTTON);
         mKeyguardUpdateMonitor.setKeyguardShowing(true, true);
@@ -1616,8 +1603,6 @@
     @Test
     public void doNotListenForFingerprint_whenOccludingAppPkgNotOnAllowlist()
             throws RemoteException {
-        mFeatureFlags.set(FP_LISTEN_OCCLUDING_APPS, true);
-
         // GIVEN keyguard isn't visible (app occluding)
         mKeyguardUpdateMonitor.dispatchStartedWakingUp(PowerManager.WAKE_REASON_POWER_BUTTON);
         mKeyguardUpdateMonitor.setKeyguardShowing(true, true);
@@ -2979,11 +2964,6 @@
     }
 
     @Test
-    public void stopFaceAuthOnDisplayOffFlagNotEnabled_doNotRegisterForDisplayCallback() {
-        assertThat(mDisplayTracker.getDisplayCallbacks().size()).isEqualTo(0);
-    }
-
-    @Test
     public void onDisplayOn_nothingHappens() throws RemoteException {
         // GIVEN
         keyguardIsVisible();
@@ -3325,7 +3305,6 @@
         clearInvocations(mFingerprintManager);
         clearInvocations(mBiometricManager);
         clearInvocations(mStatusBarStateController);
-        mFeatureFlags.set(STOP_FACE_AUTH_ON_DISPLAY_OFF, true);
         mKeyguardUpdateMonitor = new TestableKeyguardUpdateMonitor(mContext);
         setupBiometrics(mKeyguardUpdateMonitor);
         assertThat(mDisplayTracker.getDisplayCallbacks().size()).isEqualTo(1);
@@ -3407,7 +3386,7 @@
                     mDreamManager, mDevicePolicyManager, mSensorPrivacyManager, mTelephonyManager,
                     mPackageManager, mFaceManager, mFingerprintManager, mBiometricManager,
                     mFaceWakeUpTriggersConfig, mDevicePostureController,
-                    Optional.of(mInteractiveToAuthProvider), mFeatureFlags,
+                    Optional.of(mInteractiveToAuthProvider),
                     mTaskStackChangeListeners, mActivityTaskManager, mDisplayTracker);
             setStrongAuthTracker(KeyguardUpdateMonitorTest.this.mStrongAuthTracker);
         }
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/LockIconViewControllerTest.java b/packages/SystemUI/tests/src/com/android/keyguard/LockIconViewControllerTest.java
index 979fc83..a2dc776 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/LockIconViewControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/keyguard/LockIconViewControllerTest.java
@@ -17,11 +17,9 @@
 package com.android.keyguard;
 
 import static android.hardware.biometrics.BiometricAuthenticator.TYPE_FINGERPRINT;
-
 import static com.android.keyguard.LockIconView.ICON_LOCK;
 import static com.android.keyguard.LockIconView.ICON_UNLOCK;
 import static com.android.systemui.flags.Flags.ONE_WAY_HAPTICS_API_MIGRATION;
-
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.Mockito.anyBoolean;
 import static org.mockito.Mockito.anyInt;
@@ -40,8 +38,8 @@
 
 import androidx.test.filters.SmallTest;
 
-import com.android.settingslib.udfps.UdfpsOverlayParams;
 import com.android.systemui.biometrics.UdfpsController;
+import com.android.systemui.biometrics.shared.model.UdfpsOverlayParams;
 import com.android.systemui.doze.util.BurnInHelperKt;
 
 import org.junit.Test;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/SystemActionsTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/SystemActionsTest.java
index 576f689..b478d5c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/SystemActionsTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/SystemActionsTest.java
@@ -17,7 +17,6 @@
 package com.android.systemui.accessibility;
 
 import static com.google.common.truth.Truth.assertThat;
-
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.Mockito.doAnswer;
@@ -41,9 +40,7 @@
 import com.android.systemui.shade.ShadeController;
 import com.android.systemui.shade.ShadeViewController;
 import com.android.systemui.statusbar.NotificationShadeWindowController;
-import com.android.systemui.statusbar.phone.CentralSurfaces;
-
-import dagger.Lazy;
+import com.android.systemui.statusbar.policy.KeyguardStateController;
 
 import org.junit.Before;
 import org.junit.Test;
@@ -64,12 +61,12 @@
     @Mock
     private NotificationShadeWindowController mNotificationShadeController;
     @Mock
+    private KeyguardStateController mKeyguardStateController;
+    @Mock
     private ShadeController mShadeController;
     @Mock
     private ShadeViewController mShadeViewController;
     @Mock
-    private Lazy<Optional<CentralSurfaces>> mCentralSurfacesOptionalLazy;
-    @Mock
     private Optional<Recents> mRecentsOptional;
     @Mock
     private TelecomManager mTelecomManager;
@@ -84,9 +81,15 @@
         MockitoAnnotations.initMocks(this);
         mContext.addMockSystemService(TelecomManager.class, mTelecomManager);
         mContext.addMockSystemService(InputManager.class, mInputManager);
-        mSystemActions = new SystemActions(mContext, mUserTracker, mNotificationShadeController,
-                mShadeController, () -> mShadeViewController, mCentralSurfacesOptionalLazy,
-                mRecentsOptional, mDisplayTracker);
+        mSystemActions = new SystemActions(
+                mContext,
+                mUserTracker,
+                mNotificationShadeController,
+                mKeyguardStateController,
+                mShadeController,
+                () -> mShadeViewController,
+                mRecentsOptional,
+                mDisplayTracker);
     }
 
     @Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthBiometricFingerprintAndFaceViewTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthBiometricFingerprintAndFaceViewTest.kt
deleted file mode 100644
index a93af7d..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthBiometricFingerprintAndFaceViewTest.kt
+++ /dev/null
@@ -1,164 +0,0 @@
-/*
- * Copyright (C) 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.systemui.biometrics
-
-import android.hardware.biometrics.BiometricAuthenticator.TYPE_FACE
-import android.hardware.biometrics.BiometricAuthenticator.TYPE_FINGERPRINT
-import android.hardware.biometrics.BiometricConstants
-import android.hardware.face.FaceManager
-import android.testing.TestableLooper
-import android.testing.TestableLooper.RunWithLooper
-import android.view.View
-import androidx.test.ext.junit.runners.AndroidJUnit4
-import androidx.test.filters.SmallTest
-import com.android.systemui.R
-import com.android.systemui.RoboPilotTest
-import com.android.systemui.SysuiTestCase
-import com.google.common.truth.Truth.assertThat
-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.never
-import org.mockito.Mockito.verify
-import org.mockito.Mockito.times
-import org.mockito.junit.MockitoJUnit
-
-
-@RunWith(AndroidJUnit4::class)
-@RunWithLooper(setAsMainLooper = true)
-@SmallTest
-@RoboPilotTest
-class AuthBiometricFingerprintAndFaceViewTest : SysuiTestCase() {
-
-    @JvmField
-    @Rule
-    var mockitoRule = MockitoJUnit.rule()
-
-    @Mock
-    private lateinit var callback: AuthBiometricView.Callback
-
-    @Mock
-    private lateinit var panelController: AuthPanelController
-
-    private lateinit var biometricView: AuthBiometricFingerprintAndFaceView
-
-    @Before
-    fun setup() {
-        biometricView = R.layout.auth_biometric_fingerprint_and_face_view
-                .asTestAuthBiometricView(mContext, callback, panelController)
-        waitForIdleSync()
-    }
-
-    @After
-    fun tearDown() {
-        biometricView.destroyDialog()
-    }
-
-    @Test
-    fun fingerprintSuccessDoesNotRequireExplicitConfirmation() {
-        biometricView.onDialogAnimatedIn(fingerprintWasStarted = true)
-        biometricView.onAuthenticationSucceeded(TYPE_FINGERPRINT)
-        TestableLooper.get(this).moveTimeForward(1000)
-        waitForIdleSync()
-
-        assertThat(biometricView.isAuthenticated).isTrue()
-        verify(callback).onAction(AuthBiometricView.Callback.ACTION_AUTHENTICATED)
-    }
-
-    @Test
-    fun faceSuccessRequiresExplicitConfirmation() {
-        biometricView.onDialogAnimatedIn(fingerprintWasStarted = true)
-        biometricView.onAuthenticationSucceeded(TYPE_FACE)
-        waitForIdleSync()
-
-        assertThat(biometricView.isAuthenticated).isFalse()
-        assertThat(biometricView.isAuthenticating).isFalse()
-        assertThat(biometricView.mConfirmButton.visibility).isEqualTo(View.GONE)
-        verify(callback, never()).onAction(AuthBiometricView.Callback.ACTION_AUTHENTICATED)
-
-        // icon acts as confirm button
-        biometricView.mIconView.performClick()
-        TestableLooper.get(this).moveTimeForward(1000)
-        waitForIdleSync()
-
-        assertThat(biometricView.isAuthenticated).isTrue()
-        verify(callback).onAction(AuthBiometricView.Callback.ACTION_AUTHENTICATED_AND_CONFIRMED)
-    }
-
-    @Test
-    fun ignoresFaceErrors_faceIsNotClass3_notLockoutError() {
-        biometricView.onDialogAnimatedIn(fingerprintWasStarted = true)
-        biometricView.onError(TYPE_FACE, "not a face")
-        waitForIdleSync()
-
-        assertThat(biometricView.isAuthenticating).isTrue()
-        verify(callback, never()).onAction(AuthBiometricView.Callback.ACTION_ERROR)
-
-        biometricView.onError(TYPE_FINGERPRINT, "that's a nope")
-        TestableLooper.get(this).moveTimeForward(1000)
-        waitForIdleSync()
-
-        verify(callback).onAction(AuthBiometricView.Callback.ACTION_ERROR)
-    }
-
-    @Test
-    fun doNotIgnoresFaceErrors_faceIsClass3_notLockoutError() {
-        biometricView.isFaceClass3 = true
-        biometricView.onDialogAnimatedIn(fingerprintWasStarted = true)
-        biometricView.onError(TYPE_FACE, "not a face")
-        waitForIdleSync()
-
-        assertThat(biometricView.isAuthenticating).isTrue()
-        verify(callback, never()).onAction(AuthBiometricView.Callback.ACTION_ERROR)
-
-        biometricView.onError(TYPE_FINGERPRINT, "that's a nope")
-        TestableLooper.get(this).moveTimeForward(1000)
-        waitForIdleSync()
-
-        verify(callback).onAction(AuthBiometricView.Callback.ACTION_ERROR)
-    }
-
-    @Test
-    fun doNotIgnoresFaceErrors_faceIsClass3_lockoutError() {
-        biometricView.isFaceClass3 = true
-        biometricView.onDialogAnimatedIn(fingerprintWasStarted = true)
-        biometricView.onError(
-            TYPE_FACE,
-            FaceManager.getErrorString(
-                biometricView.context,
-                BiometricConstants.BIOMETRIC_ERROR_LOCKOUT_PERMANENT,
-                0 /*vendorCode */
-            )
-        )
-        waitForIdleSync()
-
-        assertThat(biometricView.isAuthenticating).isTrue()
-        verify(callback).onAction(AuthBiometricView.Callback.ACTION_ERROR)
-
-        biometricView.onError(TYPE_FINGERPRINT, "that's a nope")
-        TestableLooper.get(this).moveTimeForward(1000)
-        waitForIdleSync()
-
-        verify(callback, times(2)).onAction(AuthBiometricView.Callback.ACTION_ERROR)
-    }
-
-
-    override fun waitForIdleSync() = TestableLooper.get(this).processAllMessages()
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthBiometricFingerprintIconControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthBiometricFingerprintIconControllerTest.kt
index cac618b..52bf350 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthBiometricFingerprintIconControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthBiometricFingerprintIconControllerTest.kt
@@ -27,6 +27,7 @@
 import com.airbnb.lottie.LottieAnimationView
 import com.android.systemui.R
 import com.android.systemui.SysuiTestCase
+import com.android.systemui.biometrics.ui.binder.Spaghetti.BiometricState
 import com.google.common.truth.Truth.assertThat
 import org.junit.Before
 import org.junit.Rule
@@ -63,7 +64,7 @@
         setupFingerprintSensorProperties(FingerprintSensorProperties.TYPE_POWER_BUTTON)
         controller = AuthBiometricFingerprintIconController(context, iconView, iconViewOverlay)
 
-        assertThat(controller.getIconContentDescription(AuthBiometricView.STATE_AUTHENTICATING))
+        assertThat(controller.getIconContentDescription(BiometricState.STATE_AUTHENTICATING))
             .isEqualTo(
                 context.resources.getString(
                     R.string.security_settings_sfps_enroll_find_sensor_message
@@ -76,7 +77,7 @@
         setupFingerprintSensorProperties(FingerprintSensorProperties.TYPE_UDFPS_OPTICAL)
         controller = AuthBiometricFingerprintIconController(context, iconView, iconViewOverlay)
 
-        assertThat(controller.getIconContentDescription(AuthBiometricView.STATE_AUTHENTICATING))
+        assertThat(controller.getIconContentDescription(BiometricState.STATE_AUTHENTICATING))
             .isEqualTo(context.resources.getString(R.string.fingerprint_dialog_touch_sensor))
     }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthBiometricFingerprintViewTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthBiometricFingerprintViewTest.kt
deleted file mode 100644
index 8e5d96b..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthBiometricFingerprintViewTest.kt
+++ /dev/null
@@ -1,283 +0,0 @@
-/*
- * Copyright (C) 2022 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package com.android.systemui.biometrics
-
-import android.hardware.biometrics.BiometricAuthenticator
-import android.os.Bundle
-import androidx.test.ext.junit.runners.AndroidJUnit4
-import android.testing.TestableLooper
-import android.testing.TestableLooper.RunWithLooper
-import android.view.View
-import androidx.test.filters.SmallTest
-import com.android.systemui.R
-import com.android.systemui.RoboPilotTest
-import com.android.systemui.SysuiTestCase
-import com.google.common.truth.Truth.assertThat
-import org.junit.After
-import org.junit.Before
-import org.junit.Rule
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.mockito.ArgumentMatchers
-import org.mockito.ArgumentMatchers.eq
-import org.mockito.Mock
-import org.mockito.Mockito.never
-import org.mockito.Mockito.verify
-import org.mockito.junit.MockitoJUnit
-
-@RunWith(AndroidJUnit4::class)
-@RunWithLooper(setAsMainLooper = true)
-@SmallTest
-@RoboPilotTest
-class AuthBiometricFingerprintViewTest : SysuiTestCase() {
-
-    @JvmField
-    @Rule
-    val mockitoRule = MockitoJUnit.rule()
-
-    @Mock
-    private lateinit var callback: AuthBiometricView.Callback
-
-    @Mock
-    private lateinit var panelController: AuthPanelController
-
-    private lateinit var biometricView: AuthBiometricView
-
-    private fun createView(allowDeviceCredential: Boolean = false): AuthBiometricFingerprintView {
-        val view: AuthBiometricFingerprintView =
-                R.layout.auth_biometric_fingerprint_view.asTestAuthBiometricView(
-                mContext, callback, panelController, allowDeviceCredential = allowDeviceCredential
-        )
-        waitForIdleSync()
-        return view
-    }
-
-    @Before
-    fun setup() {
-        biometricView = createView()
-    }
-
-    @After
-    fun tearDown() {
-        biometricView.destroyDialog()
-    }
-
-    @Test
-    fun testOnAuthenticationSucceeded_noConfirmationRequired_sendsActionAuthenticated() {
-        biometricView.onAuthenticationSucceeded(BiometricAuthenticator.TYPE_FINGERPRINT)
-        TestableLooper.get(this).moveTimeForward(1000)
-        waitForIdleSync()
-
-        assertThat(biometricView.isAuthenticated).isTrue()
-        verify(callback).onAction(AuthBiometricView.Callback.ACTION_AUTHENTICATED)
-    }
-
-    @Test
-    fun testOnAuthenticationSucceeded_confirmationRequired_updatesDialogContents() {
-        biometricView.setRequireConfirmation(true)
-        biometricView.onAuthenticationSucceeded(BiometricAuthenticator.TYPE_FINGERPRINT)
-        TestableLooper.get(this).moveTimeForward(1000)
-        waitForIdleSync()
-
-        // TODO: this should be tested in the subclasses
-        if (biometricView.supportsRequireConfirmation()) {
-            verify(callback, never()).onAction(ArgumentMatchers.anyInt())
-            assertThat(biometricView.mNegativeButton.visibility).isEqualTo(View.GONE)
-            assertThat(biometricView.mCancelButton.visibility).isEqualTo(View.VISIBLE)
-            assertThat(biometricView.mCancelButton.isEnabled).isTrue()
-            assertThat(biometricView.mConfirmButton.isEnabled).isTrue()
-            assertThat(biometricView.mIndicatorView.text)
-                    .isEqualTo(mContext.getText(R.string.biometric_dialog_tap_confirm))
-            assertThat(biometricView.mIndicatorView.visibility).isEqualTo(View.VISIBLE)
-        } else {
-            assertThat(biometricView.isAuthenticated).isTrue()
-            verify(callback).onAction(eq(AuthBiometricView.Callback.ACTION_AUTHENTICATED))
-        }
-    }
-
-    @Test
-    fun testPositiveButton_sendsActionAuthenticated() {
-        biometricView.mConfirmButton.performClick()
-        TestableLooper.get(this).moveTimeForward(1000)
-        waitForIdleSync()
-
-        verify(callback).onAction(AuthBiometricView.Callback.ACTION_AUTHENTICATED)
-        assertThat(biometricView.isAuthenticated).isTrue()
-    }
-
-    @Test
-    fun testNegativeButton_beforeAuthentication_sendsActionButtonNegative() {
-        biometricView.onDialogAnimatedIn(fingerprintWasStarted = true)
-        biometricView.mNegativeButton.performClick()
-        TestableLooper.get(this).moveTimeForward(1000)
-        waitForIdleSync()
-
-        verify(callback).onAction(AuthBiometricView.Callback.ACTION_BUTTON_NEGATIVE)
-    }
-
-    @Test
-    fun testCancelButton_whenPendingConfirmation_sendsActionUserCanceled() {
-        biometricView.setRequireConfirmation(true)
-        biometricView.onAuthenticationSucceeded(BiometricAuthenticator.TYPE_FINGERPRINT)
-
-        assertThat(biometricView.mNegativeButton.visibility).isEqualTo(View.GONE)
-        biometricView.mCancelButton.performClick()
-        TestableLooper.get(this).moveTimeForward(1000)
-        waitForIdleSync()
-
-        verify(callback).onAction(AuthBiometricView.Callback.ACTION_USER_CANCELED)
-    }
-
-    @Test
-    fun testTryAgainButton_sendsActionTryAgain() {
-        biometricView.mTryAgainButton.performClick()
-        TestableLooper.get(this).moveTimeForward(1000)
-        waitForIdleSync()
-
-        verify(callback).onAction(AuthBiometricView.Callback.ACTION_BUTTON_TRY_AGAIN)
-        assertThat(biometricView.mTryAgainButton.visibility).isEqualTo(View.GONE)
-        assertThat(biometricView.isAuthenticating).isTrue()
-    }
-
-    @Test
-    fun testOnErrorSendsActionError() {
-        biometricView.onError(BiometricAuthenticator.TYPE_FACE, "testError")
-        TestableLooper.get(this).moveTimeForward(1000)
-        waitForIdleSync()
-
-        verify(callback).onAction(eq(AuthBiometricView.Callback.ACTION_ERROR))
-    }
-
-    @Test
-    fun testOnErrorShowsMessage() {
-        // prevent error state from instantly returning to authenticating in the test
-        biometricView.mAnimationDurationHideDialog = 10_000
-
-        val message = "another error"
-        biometricView.onError(BiometricAuthenticator.TYPE_FACE, message)
-        TestableLooper.get(this).moveTimeForward(1000)
-        waitForIdleSync()
-
-        assertThat(biometricView.isAuthenticating).isFalse()
-        assertThat(biometricView.isAuthenticated).isFalse()
-        assertThat(biometricView.mIndicatorView.visibility).isEqualTo(View.VISIBLE)
-        assertThat(biometricView.mIndicatorView.text).isEqualTo(message)
-    }
-
-    @Test
-    fun testBackgroundClicked_sendsActionUserCanceled() {
-        val view = View(mContext)
-        biometricView.setBackgroundView(view)
-        view.performClick()
-
-        verify(callback).onAction(eq(AuthBiometricView.Callback.ACTION_USER_CANCELED))
-    }
-
-    @Test
-    fun testBackgroundClicked_afterAuthenticated_neverSendsUserCanceled() {
-        val view = View(mContext)
-        biometricView.setBackgroundView(view)
-        biometricView.onAuthenticationSucceeded(BiometricAuthenticator.TYPE_FINGERPRINT)
-        waitForIdleSync()
-        view.performClick()
-
-        verify(callback, never())
-                .onAction(eq(AuthBiometricView.Callback.ACTION_USER_CANCELED))
-    }
-
-    @Test
-    fun testBackgroundClicked_whenSmallDialog_neverSendsUserCanceled() {
-        biometricView.mLayoutParams = AuthDialog.LayoutParams(0, 0)
-        biometricView.updateSize(AuthDialog.SIZE_SMALL)
-        val view = View(mContext)
-        biometricView.setBackgroundView(view)
-        view.performClick()
-
-        verify(callback, never()).onAction(eq(AuthBiometricView.Callback.ACTION_USER_CANCELED))
-    }
-
-    @Test
-    fun testIgnoresUselessHelp() {
-        biometricView.mAnimationDurationHideDialog = 10_000
-        biometricView.onDialogAnimatedIn(fingerprintWasStarted = true)
-        waitForIdleSync()
-
-        assertThat(biometricView.isAuthenticating).isTrue()
-
-        val helpText = biometricView.mIndicatorView.text
-        biometricView.onHelp(BiometricAuthenticator.TYPE_FINGERPRINT, "")
-        waitForIdleSync()
-
-        // text should not change
-        assertThat(biometricView.mIndicatorView.text).isEqualTo(helpText)
-        verify(callback, never()).onAction(eq(AuthBiometricView.Callback.ACTION_ERROR))
-    }
-
-    @Test
-    fun testRestoresState() {
-        val requireConfirmation = true
-        biometricView.mAnimationDurationHideDialog = 10_000
-        val failureMessage = "testFailureMessage"
-        biometricView.setRequireConfirmation(requireConfirmation)
-        biometricView.onAuthenticationFailed(BiometricAuthenticator.TYPE_FACE, failureMessage)
-        waitForIdleSync()
-
-        val state = Bundle()
-        biometricView.onSaveState(state)
-        assertThat(biometricView.mTryAgainButton.visibility).isEqualTo(View.GONE)
-        assertThat(state.getInt(AuthDialog.KEY_BIOMETRIC_TRY_AGAIN_VISIBILITY))
-                .isEqualTo(View.GONE)
-        assertThat(state.getInt(AuthDialog.KEY_BIOMETRIC_STATE))
-                .isEqualTo(AuthBiometricView.STATE_ERROR)
-        assertThat(biometricView.mIndicatorView.visibility).isEqualTo(View.VISIBLE)
-        assertThat(state.getBoolean(AuthDialog.KEY_BIOMETRIC_INDICATOR_ERROR_SHOWING)).isTrue()
-        assertThat(biometricView.mIndicatorView.text).isEqualTo(failureMessage)
-        assertThat(state.getString(AuthDialog.KEY_BIOMETRIC_INDICATOR_STRING))
-                .isEqualTo(failureMessage)
-
-        // TODO: Test dialog size. Should move requireConfirmation to buildBiometricPromptBundle
-
-        // Create new dialog and restore the previous state into it
-        biometricView.destroyDialog()
-        biometricView = createView()
-        biometricView.restoreState(state)
-        biometricView.mAnimationDurationHideDialog = 10_000
-        biometricView.setRequireConfirmation(requireConfirmation)
-        waitForIdleSync()
-
-        assertThat(biometricView.mTryAgainButton.visibility).isEqualTo(View.GONE)
-        assertThat(biometricView.mIndicatorView.visibility).isEqualTo(View.VISIBLE)
-
-        // TODO: Test restored text. Currently cannot test this, since it gets restored only after
-        // dialog size is known.
-    }
-
-    @Test
-    fun testCredentialButton_whenDeviceCredentialAllowed() {
-        biometricView.destroyDialog()
-        biometricView = createView(allowDeviceCredential = true)
-
-        assertThat(biometricView.mUseCredentialButton.visibility).isEqualTo(View.VISIBLE)
-        assertThat(biometricView.mNegativeButton.visibility).isEqualTo(View.GONE)
-
-        biometricView.mUseCredentialButton.performClick()
-        waitForIdleSync()
-
-        verify(callback).onAction(AuthBiometricView.Callback.ACTION_USE_DEVICE_CREDENTIAL)
-    }
-
-    override fun waitForIdleSync() = TestableLooper.get(this).processAllMessages()
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.kt
index 9584d88..7775a05 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.kt
@@ -42,12 +42,14 @@
 import com.android.systemui.biometrics.data.repository.FakeFingerprintPropertyRepository
 import com.android.systemui.biometrics.data.repository.FakePromptRepository
 import com.android.systemui.biometrics.data.repository.FakeRearDisplayStateRepository
+import com.android.systemui.biometrics.domain.interactor.DisplayStateInteractor
 import com.android.systemui.biometrics.domain.interactor.DisplayStateInteractorImpl
 import com.android.systemui.biometrics.domain.interactor.FakeCredentialInteractor
 import com.android.systemui.biometrics.domain.interactor.PromptCredentialInteractor
 import com.android.systemui.biometrics.domain.interactor.PromptSelectorInteractorImpl
 import com.android.systemui.biometrics.ui.viewmodel.CredentialViewModel
 import com.android.systemui.biometrics.ui.viewmodel.PromptViewModel
+import com.android.systemui.display.data.repository.FakeDisplayRepository
 import com.android.systemui.flags.FakeFeatureFlags
 import com.android.systemui.flags.Flags
 import com.android.systemui.keyguard.WakefulnessLifecycle
@@ -103,9 +105,6 @@
     @Mock
     lateinit var vibrator: VibratorHelper
 
-    // TODO(b/278622168): remove with flag
-    open val useNewBiometricPrompt = false
-
     private val testScope = TestScope(StandardTestDispatcher())
     private val fakeExecutor = FakeExecutor(FakeSystemClock())
     private val biometricPromptRepository = FakePromptRepository()
@@ -125,13 +124,8 @@
         )
     }
 
-    private val displayStateInteractor = DisplayStateInteractorImpl(
-        testScope.backgroundScope,
-        mContext,
-        fakeExecutor,
-        rearDisplayStateRepository
-    )
-
+    private lateinit var displayRepository: FakeDisplayRepository
+    private lateinit var displayStateInteractor: DisplayStateInteractor
 
     private val credentialViewModel = CredentialViewModel(mContext, bpCredentialInteractor)
 
@@ -139,8 +133,17 @@
 
     @Before
     fun setup() {
-        featureFlags.set(Flags.BIOMETRIC_BP_STRONG, useNewBiometricPrompt)
+        displayRepository = FakeDisplayRepository()
         featureFlags.set(Flags.ONE_WAY_HAPTICS_API_MIGRATION, false)
+
+        displayStateInteractor =
+            DisplayStateInteractorImpl(
+                    testScope.backgroundScope,
+                    mContext,
+                    fakeExecutor,
+                    rearDisplayStateRepository,
+                    displayRepository,
+            )
     }
 
     @After
@@ -228,9 +231,7 @@
     @Test
     fun testActionCancel_panelInteractionDetectorDisable() {
         val container = initializeFingerprintContainer()
-        container.mBiometricCallback.onAction(
-                AuthBiometricView.Callback.ACTION_USER_CANCELED
-        )
+        container.mBiometricCallback.onUserCanceled()
         waitForIdleSync()
         verify(panelInteractionDetector).disable()
     }
@@ -239,9 +240,7 @@
     @Test
     fun testActionAuthenticated_sendsDismissedAuthenticated() {
         val container = initializeFingerprintContainer()
-        container.mBiometricCallback.onAction(
-            AuthBiometricView.Callback.ACTION_AUTHENTICATED
-        )
+        container.mBiometricCallback.onAuthenticated()
         waitForIdleSync()
 
         verify(callback).onDismissed(
@@ -255,9 +254,7 @@
     @Test
     fun testActionUserCanceled_sendsDismissedUserCanceled() {
         val container = initializeFingerprintContainer()
-        container.mBiometricCallback.onAction(
-            AuthBiometricView.Callback.ACTION_USER_CANCELED
-        )
+        container.mBiometricCallback.onUserCanceled()
         waitForIdleSync()
 
         verify(callback).onSystemEvent(
@@ -275,9 +272,7 @@
     @Test
     fun testActionButtonNegative_sendsDismissedButtonNegative() {
         val container = initializeFingerprintContainer()
-        container.mBiometricCallback.onAction(
-            AuthBiometricView.Callback.ACTION_BUTTON_NEGATIVE
-        )
+        container.mBiometricCallback.onButtonNegative()
         waitForIdleSync()
 
         verify(callback).onDismissed(
@@ -293,9 +288,7 @@
         val container = initializeFingerprintContainer(
             authenticators = BiometricManager.Authenticators.BIOMETRIC_WEAK
         )
-        container.mBiometricCallback.onAction(
-            AuthBiometricView.Callback.ACTION_BUTTON_TRY_AGAIN
-        )
+        container.mBiometricCallback.onButtonTryAgain()
         waitForIdleSync()
 
         verify(callback).onTryAgainPressed(authContainer?.requestId ?: 0L)
@@ -304,9 +297,7 @@
     @Test
     fun testActionError_sendsDismissedError() {
         val container = initializeFingerprintContainer()
-        container.mBiometricCallback.onAction(
-            AuthBiometricView.Callback.ACTION_ERROR
-        )
+        container.mBiometricCallback.onError()
         waitForIdleSync()
 
         verify(callback).onDismissed(
@@ -324,9 +315,7 @@
             authenticators = BiometricManager.Authenticators.BIOMETRIC_WEAK or
                     BiometricManager.Authenticators.DEVICE_CREDENTIAL
         )
-        container.mBiometricCallback.onAction(
-            AuthBiometricView.Callback.ACTION_USE_DEVICE_CREDENTIAL
-        )
+        container.mBiometricCallback.onUseDeviceCredential()
         waitForIdleSync()
 
         verify(callback).onDeviceCredentialPressed(authContainer?.requestId ?: 0L)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest2.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest2.kt
deleted file mode 100644
index b56d055..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest2.kt
+++ /dev/null
@@ -1,30 +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.biometrics
-
-import android.testing.TestableLooper
-import androidx.test.ext.junit.runners.AndroidJUnit4
-import androidx.test.filters.SmallTest
-import org.junit.runner.RunWith
-
-// TODO(b/278622168): remove with flag
-@RunWith(AndroidJUnit4::class)
-@TestableLooper.RunWithLooper(setAsMainLooper = true)
-@SmallTest
-class AuthContainerViewTest2 : AuthContainerViewTest() {
-    override val useNewBiometricPrompt = true
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthControllerTest.java
index 6d71dd5..d0b3833 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthControllerTest.java
@@ -17,22 +17,17 @@
 package com.android.systemui.biometrics;
 
 import static android.hardware.biometrics.BiometricAuthenticator.TYPE_FINGERPRINT;
-import static android.hardware.biometrics.BiometricManager.Authenticators;
-
 import static com.google.common.truth.Truth.assertThat;
-
 import static junit.framework.Assert.assertEquals;
 import static junit.framework.Assert.assertFalse;
 import static junit.framework.Assert.assertNotSame;
 import static junit.framework.Assert.assertNull;
 import static junit.framework.Assert.assertTrue;
-
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.ArgumentMatchers.anyLong;
 import static org.mockito.ArgumentMatchers.anyString;
 import static org.mockito.ArgumentMatchers.eq;
-import static org.mockito.Mockito.doAnswer;
 import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.never;
@@ -48,7 +43,6 @@
 import android.content.Context;
 import android.content.Intent;
 import android.content.pm.PackageManager;
-import android.content.res.Configuration;
 import android.content.res.Resources;
 import android.graphics.Point;
 import android.hardware.biometrics.BiometricAuthenticator;
@@ -69,7 +63,6 @@
 import android.hardware.fingerprint.FingerprintSensorProperties;
 import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
 import android.hardware.fingerprint.IFingerprintAuthenticatorsRegisteredCallback;
-import android.os.Bundle;
 import android.os.Handler;
 import android.os.RemoteException;
 import android.os.UserManager;
@@ -86,7 +79,6 @@
 import com.android.internal.R;
 import com.android.internal.jank.InteractionJankMonitor;
 import com.android.internal.widget.LockPatternUtils;
-import com.android.settingslib.udfps.UdfpsUtils;
 import com.android.systemui.RoboPilotTest;
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.biometrics.domain.interactor.LogContextInteractor;
@@ -95,7 +87,6 @@
 import com.android.systemui.biometrics.ui.viewmodel.CredentialViewModel;
 import com.android.systemui.biometrics.ui.viewmodel.PromptViewModel;
 import com.android.systemui.flags.FakeFeatureFlags;
-import com.android.systemui.flags.Flags;
 import com.android.systemui.keyguard.WakefulnessLifecycle;
 import com.android.systemui.statusbar.CommandQueue;
 import com.android.systemui.statusbar.VibratorHelper;
@@ -207,10 +198,6 @@
 
     @Before
     public void setup() throws RemoteException {
-        // TODO(b/278622168): remove with flag
-        // AuthController simply passes this through to AuthContainerView (does not impact test)
-        mFeatureFlags.set(Flags.BIOMETRIC_BP_STRONG, false);
-
         mContextSpy = spy(mContext);
         mExecution = new FakeExecution();
         mTestableLooper = TestableLooper.get(this);
@@ -463,7 +450,7 @@
     @Test
     public void testShowInvoked_whenSystemRequested() {
         showDialog(new int[] {1} /* sensorIds */, false /* credentialAllowed */);
-        verify(mDialog1).show(any(), any());
+        verify(mDialog1).show(any());
     }
 
     @Test
@@ -664,7 +651,7 @@
         // 2) Client cancels authentication
 
         showDialog(new int[0] /* sensorIds */, true /* credentialAllowed */);
-        verify(mDialog1).show(any(), any());
+        verify(mDialog1).show(any());
 
         final byte[] credentialAttestation = generateRandomHAT();
 
@@ -680,7 +667,7 @@
     @Test
     public void testShowNewDialog_beforeOldDialogDismissed_SkipsAnimations() {
         showDialog(new int[] {1} /* sensorIds */, false /* credentialAllowed */);
-        verify(mDialog1).show(any(), any());
+        verify(mDialog1).show(any());
 
         showDialog(new int[] {1} /* sensorIds */, false /* credentialAllowed */);
 
@@ -688,59 +675,7 @@
         verify(mDialog1).dismissWithoutCallback(eq(false) /* animate */);
 
         // Second dialog should be shown without animation
-        verify(mDialog2).show(any(), any());
-    }
-
-    @Test
-    public void testConfigurationPersists_whenOnConfigurationChanged() {
-        showDialog(new int[] {1} /* sensorIds */, false /* credentialAllowed */);
-        verify(mDialog1).show(any(), any());
-
-        // Return that the UI is in "showing" state
-        doAnswer(invocation -> {
-            Object[] args = invocation.getArguments();
-            Bundle savedState = (Bundle) args[0];
-            savedState.putBoolean(AuthDialog.KEY_CONTAINER_GOING_AWAY, false);
-            return null; // onSaveState returns void
-        }).when(mDialog1).onSaveState(any());
-
-        mAuthController.onConfigurationChanged(new Configuration());
-
-        ArgumentCaptor<Bundle> captor = ArgumentCaptor.forClass(Bundle.class);
-        verify(mDialog1).onSaveState(captor.capture());
-
-        // Old dialog doesn't animate
-        verify(mDialog1).dismissWithoutCallback(eq(false /* animate */));
-
-        // Saved state is restored into new dialog
-        ArgumentCaptor<Bundle> captor2 = ArgumentCaptor.forClass(Bundle.class);
-        verify(mDialog2).show(any(), captor2.capture());
-
-        // TODO: This should check all values we want to save/restore
-        assertEquals(captor.getValue(), captor2.getValue());
-    }
-
-    @Test
-    public void testConfigurationPersists_whenBiometricFallbackToCredential() {
-        showDialog(new int[] {1} /* sensorIds */, true /* credentialAllowed */);
-        verify(mDialog1).show(any(), any());
-
-        // Pretend that the UI is now showing device credential UI.
-        doAnswer(invocation -> {
-            Object[] args = invocation.getArguments();
-            Bundle savedState = (Bundle) args[0];
-            savedState.putBoolean(AuthDialog.KEY_CONTAINER_GOING_AWAY, false);
-            savedState.putBoolean(AuthDialog.KEY_CREDENTIAL_SHOWING, true);
-            return null; // onSaveState returns void
-        }).when(mDialog1).onSaveState(any());
-
-        mAuthController.onConfigurationChanged(new Configuration());
-
-        // Check that the new dialog was initialized to the credential UI.
-        ArgumentCaptor<Bundle> captor = ArgumentCaptor.forClass(Bundle.class);
-        verify(mDialog2).show(any(), captor.capture());
-        assertEquals(Authenticators.DEVICE_CREDENTIAL,
-                mAuthController.mLastBiometricPromptInfo.getAuthenticators());
+        verify(mDialog2).show(any());
     }
 
     @Test
@@ -1010,7 +945,7 @@
                 REQUEST_ID);
 
         assertNull(mAuthController.mCurrentDialog);
-        verify(mDialog1, never()).show(any(), any());
+        verify(mDialog1, never()).show(any());
     }
 
     private void showDialog(int[] sensorIds, boolean credentialAllowed) {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthDialogPanelInteractionDetectorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthDialogPanelInteractionDetectorTest.kt
index 9751fad..2bb3785 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthDialogPanelInteractionDetectorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthDialogPanelInteractionDetectorTest.kt
@@ -28,6 +28,8 @@
 import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
 import com.android.systemui.keyguard.domain.interactor.KeyguardInteractorFactory
 import com.android.systemui.plugins.ActivityStarter
+import com.android.systemui.scene.SceneTestUtils
+import com.android.systemui.scene.shared.flag.FakeSceneContainerFlags
 import com.android.systemui.shade.data.repository.FakeShadeRepository
 import com.android.systemui.shade.domain.interactor.ShadeInteractor
 import com.android.systemui.statusbar.disableflags.data.repository.FakeDisableFlagsRepository
@@ -42,8 +44,6 @@
 import com.android.systemui.user.domain.interactor.RefreshUsersScheduler
 import com.android.systemui.user.domain.interactor.UserInteractor
 import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.test.StandardTestDispatcher
-import kotlinx.coroutines.test.TestScope
 import kotlinx.coroutines.test.runCurrent
 import kotlinx.coroutines.test.runTest
 import org.junit.Before
@@ -56,12 +56,15 @@
 @SmallTest
 @OptIn(ExperimentalCoroutinesApi::class)
 class AuthDialogPanelInteractionDetectorTest : SysuiTestCase() {
+    private val utils = SceneTestUtils(this)
+    private val testScope = utils.testScope
+    private val testDispatcher = utils.testDispatcher
     private val disableFlagsRepository = FakeDisableFlagsRepository()
     private val featureFlags = FakeFeatureFlags()
     private val keyguardRepository = FakeKeyguardRepository()
     private val shadeRepository = FakeShadeRepository()
-    private val testDispatcher = StandardTestDispatcher()
-    private val testScope = TestScope(testDispatcher)
+    private val sceneContainerFlags = FakeSceneContainerFlags()
+    private val sceneInteractor = utils.sceneInteractor()
     private val userSetupRepository = FakeUserSetupRepository()
     private val userRepository = FakeUserRepository()
     private val configurationRepository = FakeConfigurationRepository()
@@ -126,6 +129,8 @@
             ShadeInteractor(
                 testScope.backgroundScope,
                 disableFlagsRepository,
+                sceneContainerFlags,
+                { sceneInteractor },
                 keyguardRepository,
                 userSetupRepository,
                 deviceProvisionedController,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/BiometricTestExtensions.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/BiometricTestExtensions.kt
index 94244cd..9f24a9f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/BiometricTestExtensions.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/BiometricTestExtensions.kt
@@ -16,8 +16,6 @@
 
 package com.android.systemui.biometrics
 
-import android.annotation.IdRes
-import android.content.Context
 import android.hardware.biometrics.BiometricManager.Authenticators
 import android.hardware.biometrics.ComponentInfoInternal
 import android.hardware.biometrics.PromptInfo
@@ -27,57 +25,6 @@
 import android.hardware.face.FaceSensorPropertiesInternal
 import android.hardware.fingerprint.FingerprintSensorProperties
 import android.hardware.fingerprint.FingerprintSensorPropertiesInternal
-import android.os.Bundle
-import android.testing.ViewUtils
-import android.view.LayoutInflater
-
-/**
- * Inflate the given BiometricPrompt layout and initialize it with test parameters.
- *
- * This attaches the view so be sure to call [destroyDialog] at the end of the test.
- */
-@IdRes
-internal fun <T : AuthBiometricView> Int.asTestAuthBiometricView(
-    context: Context,
-    callback: AuthBiometricView.Callback,
-    panelController: AuthPanelController,
-    allowDeviceCredential: Boolean = false,
-    savedState: Bundle? = null,
-    hideDelay: Int = 0
-): T {
-    val view = LayoutInflater.from(context).inflate(this, null, false) as T
-    view.mAnimationDurationLong = 0
-    view.mAnimationDurationShort = 0
-    view.mAnimationDurationHideDialog = hideDelay
-    view.setPromptInfo(buildPromptInfo(allowDeviceCredential))
-    view.setCallback(callback)
-    view.restoreState(savedState)
-    view.setPanelController(panelController)
-
-    ViewUtils.attachView(view)
-
-    return view
-}
-
-private fun buildPromptInfo(allowDeviceCredential: Boolean): PromptInfo {
-    val promptInfo = PromptInfo()
-    promptInfo.title = "Title"
-    var authenticators = Authenticators.BIOMETRIC_WEAK
-    if (allowDeviceCredential) {
-        authenticators = authenticators or Authenticators.DEVICE_CREDENTIAL
-    } else {
-        promptInfo.negativeButtonText = "Negative"
-    }
-    promptInfo.authenticators = authenticators
-    return promptInfo
-}
-
-/** Detach the view, if needed. */
-internal fun AuthBiometricView?.destroyDialog() {
-    if (this != null && isAttachedToWindow) {
-        ViewUtils.detachView(this)
-    }
-}
 
 /** Create [FingerprintSensorPropertiesInternal] for a test. */
 internal fun fingerprintSensorPropertiesInternal(
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/SideFpsControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/SideFpsControllerTest.kt
index 994db46..3ebc2d6 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/SideFpsControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/SideFpsControllerTest.kt
@@ -61,6 +61,7 @@
 import com.android.systemui.biometrics.domain.interactor.DisplayStateInteractorImpl
 import com.android.systemui.bouncer.data.repository.FakeKeyguardBouncerRepository
 import com.android.systemui.bouncer.domain.interactor.AlternateBouncerInteractor
+import com.android.systemui.display.data.repository.FakeDisplayRepository
 import com.android.systemui.dump.DumpManager
 import com.android.systemui.keyguard.data.repository.FakeBiometricSettingsRepository
 import com.android.systemui.plugins.statusbar.StatusBarStateController
@@ -115,12 +116,13 @@
     @Captor lateinit var overlayCaptor: ArgumentCaptor<View>
     @Captor lateinit var overlayViewParamsCaptor: ArgumentCaptor<WindowManager.LayoutParams>
 
+    private lateinit var displayRepository: FakeDisplayRepository
+    private lateinit var rearDisplayStateRepository: FakeRearDisplayStateRepository
     private lateinit var keyguardBouncerRepository: FakeKeyguardBouncerRepository
     private lateinit var alternateBouncerInteractor: AlternateBouncerInteractor
     private lateinit var displayStateInteractor: DisplayStateInteractor
 
     private val executor = FakeExecutor(FakeSystemClock())
-    private val rearDisplayStateRepository = FakeRearDisplayStateRepository()
     private val testScope = TestScope(StandardTestDispatcher())
 
     private lateinit var overlayController: ISidefpsController
@@ -142,6 +144,8 @@
 
     @Before
     fun setup() {
+        displayRepository = FakeDisplayRepository()
+        rearDisplayStateRepository = FakeRearDisplayStateRepository()
         keyguardBouncerRepository = FakeKeyguardBouncerRepository()
         alternateBouncerInteractor =
             AlternateBouncerInteractor(
@@ -157,7 +161,8 @@
                 testScope.backgroundScope,
                 context,
                 executor,
-                rearDisplayStateRepository
+                rearDisplayStateRepository,
+                displayRepository,
             )
 
         context.addMockSystemService(DisplayManager::class.java, displayManager)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsBpViewControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsBpViewControllerTest.kt
index 7de78a6..469f65a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsBpViewControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsBpViewControllerTest.kt
@@ -21,6 +21,7 @@
 import androidx.test.filters.SmallTest
 import com.android.systemui.RoboPilotTest
 import com.android.systemui.SysuiTestCase
+import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerInteractor
 import com.android.systemui.dump.DumpManager
 import com.android.systemui.plugins.statusbar.StatusBarStateController
 import com.android.systemui.shade.ShadeExpansionStateManager
@@ -44,6 +45,7 @@
     @Mock lateinit var udfpsBpView: UdfpsBpView
     @Mock lateinit var statusBarStateController: StatusBarStateController
     @Mock lateinit var shadeExpansionStateManager: ShadeExpansionStateManager
+    @Mock lateinit var primaryBouncerInteractor: PrimaryBouncerInteractor
     @Mock lateinit var systemUIDialogManager: SystemUIDialogManager
     @Mock lateinit var dumpManager: DumpManager
 
@@ -55,12 +57,13 @@
             UdfpsBpViewController(
                 udfpsBpView,
                 statusBarStateController,
-                shadeExpansionStateManager,
+                primaryBouncerInteractor,
                 systemUIDialogManager,
                 dumpManager
             )
     }
 
+    @TestableLooper.RunWithLooper(setAsMainLooper = true)
     @Test
     fun testShouldNeverPauseAuth() {
         assertFalse(udfpsBpViewController.shouldPauseAuth())
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerOverlayTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerOverlayTest.kt
index 0e0d0e3..c735419 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerOverlayTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerOverlayTest.kt
@@ -37,12 +37,11 @@
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import com.android.keyguard.KeyguardUpdateMonitor
-import com.android.settingslib.udfps.UdfpsOverlayParams
-import com.android.settingslib.udfps.UdfpsUtils
 import com.android.systemui.R
 import com.android.systemui.RoboPilotTest
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.animation.ActivityLaunchAnimator
+import com.android.systemui.biometrics.shared.model.UdfpsOverlayParams
 import com.android.systemui.bouncer.domain.interactor.AlternateBouncerInteractor
 import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerInteractor
 import com.android.systemui.dump.DumpManager
@@ -50,7 +49,6 @@
 import com.android.systemui.flags.Flags
 import com.android.systemui.keyguard.ui.viewmodel.UdfpsKeyguardViewModels
 import com.android.systemui.plugins.statusbar.StatusBarStateController
-import com.android.systemui.shade.ShadeExpansionStateManager
 import com.android.systemui.statusbar.LockscreenShadeTransitionController
 import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager
 import com.android.systemui.statusbar.phone.SystemUIDialogManager
@@ -97,7 +95,6 @@
     @Mock private lateinit var windowManager: WindowManager
     @Mock private lateinit var accessibilityManager: AccessibilityManager
     @Mock private lateinit var statusBarStateController: StatusBarStateController
-    @Mock private lateinit var shadeExpansionStateManager: ShadeExpansionStateManager
     @Mock private lateinit var statusBarKeyguardViewManager: StatusBarKeyguardViewManager
     @Mock private lateinit var keyguardUpdateMonitor: KeyguardUpdateMonitor
     @Mock private lateinit var dialogManager: SystemUIDialogManager
@@ -145,13 +142,32 @@
         block: () -> Unit
     ) {
         controllerOverlay = UdfpsControllerOverlay(
-            context, fingerprintManager, inflater, windowManager, accessibilityManager,
-            statusBarStateController, shadeExpansionStateManager, statusBarKeyguardViewManager,
-            keyguardUpdateMonitor, dialogManager, dumpManager, transitionController,
-            configurationController, keyguardStateController, unlockedScreenOffAnimationController,
-            udfpsDisplayMode, secureSettings, REQUEST_ID, reason,
-            controllerCallback, onTouch, activityLaunchAnimator, featureFlags,
-            primaryBouncerInteractor, alternateBouncerInteractor, isDebuggable, udfpsUtils,
+            context,
+            fingerprintManager,
+            inflater,
+            windowManager,
+            accessibilityManager,
+            statusBarStateController,
+            statusBarKeyguardViewManager,
+            keyguardUpdateMonitor,
+            dialogManager,
+            dumpManager,
+            transitionController,
+            configurationController,
+            keyguardStateController,
+            unlockedScreenOffAnimationController,
+            udfpsDisplayMode,
+            secureSettings,
+            REQUEST_ID,
+            reason,
+            controllerCallback,
+            onTouch,
+            activityLaunchAnimator,
+            featureFlags,
+            primaryBouncerInteractor,
+            alternateBouncerInteractor,
+            isDebuggable,
+            udfpsUtils,
             udfpsKeyguardAccessibilityDelegate,
             udfpsKeyguardViewModels,
         )
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java
index e01b5af..b6bc7af 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerTest.java
@@ -20,15 +20,12 @@
 import static android.view.MotionEvent.ACTION_DOWN;
 import static android.view.MotionEvent.ACTION_MOVE;
 import static android.view.MotionEvent.ACTION_UP;
-
 import static com.android.internal.util.FunctionalUtils.ThrowingConsumer;
 import static com.android.systemui.classifier.Classifier.UDFPS_AUTHENTICATION;
 import static com.android.systemui.flags.Flags.ONE_WAY_HAPTICS_API_MIGRATION;
-
 import static junit.framework.Assert.assertEquals;
 import static junit.framework.Assert.assertFalse;
 import static junit.framework.Assert.assertTrue;
-
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyBoolean;
 import static org.mockito.ArgumentMatchers.anyFloat;
@@ -77,12 +74,11 @@
 import com.android.internal.logging.InstanceIdSequence;
 import com.android.internal.util.LatencyTracker;
 import com.android.keyguard.KeyguardUpdateMonitor;
-import com.android.settingslib.udfps.UdfpsOverlayParams;
-import com.android.settingslib.udfps.UdfpsUtils;
 import com.android.systemui.R;
 import com.android.systemui.RoboPilotTest;
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.animation.ActivityLaunchAnimator;
+import com.android.systemui.biometrics.shared.model.UdfpsOverlayParams;
 import com.android.systemui.biometrics.udfps.InteractionEvent;
 import com.android.systemui.biometrics.udfps.NormalizedTouchData;
 import com.android.systemui.biometrics.udfps.SinglePointerTouchProcessor;
@@ -98,7 +94,6 @@
 import com.android.systemui.log.SessionTracker;
 import com.android.systemui.plugins.FalsingManager;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
-import com.android.systemui.shade.ShadeExpansionStateManager;
 import com.android.systemui.statusbar.LockscreenShadeTransitionController;
 import com.android.systemui.statusbar.VibratorHelper;
 import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager;
@@ -314,19 +309,48 @@
                         (Provider<AlternateUdfpsTouchProvider>) () -> mAlternateTouchProvider)
                         : Optional.empty();
 
-        mUdfpsController = new UdfpsController(mContext, new FakeExecution(), mLayoutInflater,
-                mFingerprintManager, mWindowManager, mStatusBarStateController, mFgExecutor,
-                new ShadeExpansionStateManager(), mStatusBarKeyguardViewManager, mDumpManager,
-                mKeyguardUpdateMonitor, mFeatureFlags, mFalsingManager, mPowerManager,
-                mAccessibilityManager, mLockscreenShadeTransitionController, mScreenLifecycle,
-                mVibrator, mUdfpsHapticsSimulator, mUdfpsShell, mKeyguardStateController,
-                mDisplayManager, mHandler, mConfigurationController, mSystemClock,
-                mUnlockedScreenOffAnimationController, mSystemUIDialogManager, mLatencyTracker,
-                mActivityLaunchAnimator, alternateTouchProvider, mBiometricExecutor,
-                mPrimaryBouncerInteractor, mSinglePointerTouchProcessor, mSessionTracker,
-                mAlternateBouncerInteractor, mSecureSettings, mInputManager, mUdfpsUtils,
+        mUdfpsController = new UdfpsController(
+                mContext,
+                new FakeExecution(),
+                mLayoutInflater,
+                mFingerprintManager,
+                mWindowManager,
+                mStatusBarStateController,
+                mFgExecutor,
+                mStatusBarKeyguardViewManager,
+                mDumpManager,
+                mKeyguardUpdateMonitor,
+                mFeatureFlags,
+                mFalsingManager,
+                mPowerManager,
+                mAccessibilityManager,
+                mLockscreenShadeTransitionController,
+                mScreenLifecycle,
+                mVibrator,
+                mUdfpsHapticsSimulator,
+                mUdfpsShell,
+                mKeyguardStateController,
+                mDisplayManager,
+                mHandler,
+                mConfigurationController,
+                mSystemClock,
+                mUnlockedScreenOffAnimationController,
+                mSystemUIDialogManager,
+                mLatencyTracker,
+                mActivityLaunchAnimator,
+                alternateTouchProvider,
+                mBiometricExecutor,
+                mPrimaryBouncerInteractor,
+                mSinglePointerTouchProcessor,
+                mSessionTracker,
+                mAlternateBouncerInteractor,
+                mSecureSettings,
+                mInputManager,
+                mUdfpsUtils,
                 mock(KeyguardFaceAuthInteractor.class),
-                mUdfpsKeyguardAccessibilityDelegate, mUdfpsKeyguardViewModels);
+                mUdfpsKeyguardAccessibilityDelegate,
+                mUdfpsKeyguardViewModels
+        );
         verify(mFingerprintManager).setUdfpsOverlayController(mOverlayCaptor.capture());
         mOverlayController = mOverlayCaptor.getValue();
         verify(mScreenLifecycle).addObserver(mScreenObserverCaptor.capture());
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewLegacyControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewLegacyControllerBaseTest.java
index 032753a..3276e66 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewLegacyControllerBaseTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewLegacyControllerBaseTest.java
@@ -18,7 +18,6 @@
 
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyInt;
-import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
@@ -27,15 +26,14 @@
 import com.android.keyguard.KeyguardUpdateMonitor;
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.animation.ActivityLaunchAnimator;
+import com.android.systemui.bouncer.domain.interactor.AlternateBouncerInteractor;
+import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerInteractor;
 import com.android.systemui.dump.DumpManager;
 import com.android.systemui.flags.FakeFeatureFlags;
 import com.android.systemui.flags.Flags;
 import com.android.systemui.keyguard.KeyguardViewMediator;
-import com.android.systemui.bouncer.domain.interactor.AlternateBouncerInteractor;
-import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerInteractor;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
 import com.android.systemui.shade.ShadeExpansionChangeEvent;
-import com.android.systemui.shade.ShadeExpansionListener;
 import com.android.systemui.shade.ShadeExpansionStateManager;
 import com.android.systemui.statusbar.LockscreenShadeTransitionController;
 import com.android.systemui.statusbar.phone.StatusBarKeyguardViewManager;
@@ -51,8 +49,6 @@
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
 
-import java.util.List;
-
 public class UdfpsKeyguardViewLegacyControllerBaseTest extends SysuiTestCase {
     // Dependencies
     protected @Mock UdfpsKeyguardViewLegacy mView;
@@ -83,9 +79,6 @@
     private @Captor ArgumentCaptor<StatusBarStateController.StateListener> mStateListenerCaptor;
     protected StatusBarStateController.StateListener mStatusBarStateListener;
 
-    private @Captor ArgumentCaptor<ShadeExpansionListener> mExpansionListenerCaptor;
-    protected List<ShadeExpansionListener> mExpansionListeners;
-
     private @Captor ArgumentCaptor<KeyguardStateController.Callback>
             mKeyguardStateControllerCallbackCaptor;
     protected KeyguardStateController.Callback mKeyguardStateControllerCallback;
@@ -116,23 +109,6 @@
         mStatusBarStateListener = mStateListenerCaptor.getValue();
     }
 
-    protected void captureStatusBarExpansionListeners() {
-        verify(mShadeExpansionStateManager, times(2))
-                .addExpansionListener(mExpansionListenerCaptor.capture());
-        // first (index=0) is from super class, UdfpsAnimationViewController.
-        // second (index=1) is from UdfpsKeyguardViewController
-        mExpansionListeners = mExpansionListenerCaptor.getAllValues();
-    }
-
-    protected void updateStatusBarExpansion(float fraction, boolean expanded) {
-        ShadeExpansionChangeEvent event =
-                new ShadeExpansionChangeEvent(
-                        fraction, expanded, /* tracking= */ false, /* dragDownPxAmount= */ 0f);
-        for (ShadeExpansionListener listener : mExpansionListeners) {
-            listener.onPanelExpansionChanged(event);
-        }
-    }
-
     protected void captureKeyguardStateControllerCallback() {
         verify(mKeyguardStateController).addCallback(
                 mKeyguardStateControllerCallbackCaptor.capture());
@@ -155,7 +131,6 @@
         UdfpsKeyguardViewControllerLegacy controller = new UdfpsKeyguardViewControllerLegacy(
                 mView,
                 mStatusBarStateController,
-                mShadeExpansionStateManager,
                 mStatusBarKeyguardViewManager,
                 mKeyguardUpdateMonitor,
                 mDumpManager,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewLegacyControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewLegacyControllerTest.java
index d24290f..8508f45 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewLegacyControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewLegacyControllerTest.java
@@ -21,9 +21,7 @@
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.Mockito.atLeast;
-import static org.mockito.Mockito.atLeastOnce;
 import static org.mockito.Mockito.never;
-import static org.mockito.Mockito.reset;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
@@ -34,7 +32,6 @@
 import androidx.test.filters.SmallTest;
 
 import com.android.systemui.RoboPilotTest;
-import com.android.systemui.shade.ShadeExpansionListener;
 import com.android.systemui.statusbar.StatusBarState;
 
 import org.junit.Test;
@@ -66,12 +63,6 @@
     }
 
     @Test
-    public void testRegistersExpansionChangedListenerOnAttached() {
-        mController.onViewAttached();
-        captureStatusBarExpansionListeners();
-    }
-
-    @Test
     public void testRegistersStatusBarStateListenersOnAttached() {
         mController.onViewAttached();
         captureStatusBarStateListeners();
@@ -98,14 +89,10 @@
     public void testListenersUnregisteredOnDetached() {
         mController.onViewAttached();
         captureStatusBarStateListeners();
-        captureStatusBarExpansionListeners();
         captureKeyguardStateControllerCallback();
         mController.onViewDetached();
 
         verify(mStatusBarStateController).removeCallback(mStatusBarStateListener);
-        for (ShadeExpansionListener listener : mExpansionListeners) {
-            verify(mShadeExpansionStateManager).removeExpansionListener(listener);
-        }
         verify(mKeyguardStateController).removeCallback(mKeyguardStateControllerCallback);
     }
 
@@ -134,23 +121,6 @@
     }
 
     @Test
-    public void testFadeFromDialogSuggestedAlpha() {
-        // GIVEN view is attached and status bar expansion is 1f
-        mController.onViewAttached();
-        captureStatusBarStateListeners();
-        captureStatusBarExpansionListeners();
-        updateStatusBarExpansion(1f, true);
-        reset(mView);
-
-        // WHEN dialog suggested alpha is .6f
-        when(mView.getDialogSuggestedAlpha()).thenReturn(.6f);
-        sendStatusBarStateChanged(StatusBarState.KEYGUARD);
-
-        // THEN alpha is updated based on dialog suggested alpha
-        verify(mView).setUnpausedAlpha((int) (.6f * 255));
-    }
-
-    @Test
     public void testShouldNotPauseAuthOnKeyguard() {
         mController.onViewAttached();
         captureStatusBarStateListeners();
@@ -250,72 +220,6 @@
     }
 
     @Test
-    public void testFadeInWithStatusBarExpansion() {
-        // GIVEN view is attached
-        mController.onViewAttached();
-        captureStatusBarExpansionListeners();
-        captureKeyguardStateControllerCallback();
-        reset(mView);
-
-        // WHEN status bar expansion is 0
-        updateStatusBarExpansion(0, true);
-
-        // THEN alpha is 0
-        verify(mView).setUnpausedAlpha(0);
-    }
-
-    @Test
-    public void testTransitionToFullShadeProgress() {
-        // GIVEN view is attached and status bar expansion is 1f
-        mController.onViewAttached();
-        captureStatusBarExpansionListeners();
-        updateStatusBarExpansion(1f, true);
-        reset(mView);
-        when(mView.getDialogSuggestedAlpha()).thenReturn(1f);
-
-        // WHEN we're transitioning to the full shade
-        float transitionProgress = .6f;
-        mController.setTransitionToFullShadeProgress(transitionProgress);
-
-        // THEN alpha is between 0 and 255
-        verify(mView).setUnpausedAlpha((int) ((1f - transitionProgress) * 255));
-    }
-
-    @Test
-    public void testUpdatePanelExpansion_pauseAuth() {
-        // GIVEN view is attached + on the keyguard
-        mController.onViewAttached();
-        captureStatusBarStateListeners();
-        captureStatusBarExpansionListeners();
-        sendStatusBarStateChanged(StatusBarState.KEYGUARD);
-        reset(mView);
-
-        // WHEN panelViewExpansion changes to hide
-        when(mView.getUnpausedAlpha()).thenReturn(0);
-        updateStatusBarExpansion(0f, false);
-
-        // THEN pause auth is updated to PAUSE
-        verify(mView, atLeastOnce()).setPauseAuth(true);
-    }
-
-    @Test
-    public void testUpdatePanelExpansion_unpauseAuth() {
-        // GIVEN view is attached + on the keyguard + panel expansion is 0f
-        mController.onViewAttached();
-        captureStatusBarStateListeners();
-        captureStatusBarExpansionListeners();
-        sendStatusBarStateChanged(StatusBarState.KEYGUARD);
-        reset(mView);
-
-        // WHEN panelViewExpansion changes to expanded
-        when(mView.getUnpausedAlpha()).thenReturn(255);
-        updateStatusBarExpansion(1f, true);
-
-        // THEN pause auth is updated to NOT pause
-        verify(mView, atLeastOnce()).setPauseAuth(false);
-    }
-
-    @Test
     // TODO(b/259264861): Tracking Bug
     public void testUdfpsExpandedOverlayOn() {
         // GIVEN view is attached and useExpandedOverlay is true
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewLegacyControllerWithCoroutinesTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewLegacyControllerWithCoroutinesTest.kt
index 8dfeb3b..1885f64 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewLegacyControllerWithCoroutinesTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsKeyguardViewLegacyControllerWithCoroutinesTest.kt
@@ -39,8 +39,10 @@
 import com.android.systemui.plugins.statusbar.StatusBarStateController
 import com.android.systemui.statusbar.StatusBarState
 import com.android.systemui.statusbar.policy.KeyguardStateController
+import com.android.systemui.util.mockito.whenever
 import com.android.systemui.util.time.FakeSystemClock
 import com.android.systemui.util.time.SystemClock
+import kotlinx.coroutines.test.StandardTestDispatcher
 import kotlinx.coroutines.test.TestScope
 import kotlinx.coroutines.test.runCurrent
 import kotlinx.coroutines.test.runTest
@@ -51,6 +53,7 @@
 import org.junit.runner.RunWith
 import org.mockito.ArgumentMatchers.any
 import org.mockito.Mock
+import org.mockito.Mockito
 import org.mockito.Mockito.mock
 import org.mockito.Mockito.verify
 import org.mockito.MockitoAnnotations
@@ -62,15 +65,15 @@
 @kotlinx.coroutines.ExperimentalCoroutinesApi
 class UdfpsKeyguardViewLegacyControllerWithCoroutinesTest :
     UdfpsKeyguardViewLegacyControllerBaseTest() {
-    lateinit var keyguardBouncerRepository: KeyguardBouncerRepository
-    @Mock private lateinit var bouncerLogger: TableLogBuffer
+    private val testDispatcher = StandardTestDispatcher()
+    private val testScope = TestScope(testDispatcher)
 
-    private lateinit var testScope: TestScope
+    private lateinit var keyguardBouncerRepository: KeyguardBouncerRepository
+
+    @Mock private lateinit var bouncerLogger: TableLogBuffer
 
     @Before
     override fun setUp() {
-        testScope = TestScope()
-
         allowTestableLooperAsMainThread() // repeatWhenAttached requires the main thread
         MockitoAnnotations.initMocks(this)
         keyguardBouncerRepository =
@@ -82,7 +85,7 @@
         super.setUp()
     }
 
-    override fun createUdfpsKeyguardViewController(): UdfpsKeyguardViewControllerLegacy? {
+    override fun createUdfpsKeyguardViewController(): UdfpsKeyguardViewControllerLegacy {
         mPrimaryBouncerInteractor =
             PrimaryBouncerInteractor(
                 keyguardBouncerRepository,
@@ -115,6 +118,70 @@
     }
 
     @Test
+    fun bouncerExpansionChange_fadeIn() =
+        testScope.runTest {
+            // GIVEN view is attached
+            mController.onViewAttached()
+            captureKeyguardStateControllerCallback()
+            Mockito.reset(mView)
+
+            // WHEN status bar expansion is 0
+            val job = mController.listenForBouncerExpansion(this)
+            keyguardBouncerRepository.setPrimaryShow(true)
+            keyguardBouncerRepository.setPanelExpansion(KeyguardBouncerConstants.EXPANSION_VISIBLE)
+            runCurrent()
+
+            // THEN alpha is 0
+            verify(mView).unpausedAlpha = 0
+
+            job.cancel()
+        }
+
+    @Test
+    fun bouncerExpansionChange_pauseAuth() =
+        testScope.runTest {
+            // GIVEN view is attached + on the keyguard
+            mController.onViewAttached()
+            captureStatusBarStateListeners()
+            sendStatusBarStateChanged(StatusBarState.KEYGUARD)
+            Mockito.reset(mView)
+
+            // WHEN panelViewExpansion changes to hide
+            whenever(mView.unpausedAlpha).thenReturn(0)
+            val job = mController.listenForBouncerExpansion(this)
+            keyguardBouncerRepository.setPrimaryShow(true)
+            keyguardBouncerRepository.setPanelExpansion(KeyguardBouncerConstants.EXPANSION_VISIBLE)
+            runCurrent()
+
+            // THEN pause auth is updated to PAUSE
+            verify(mView, Mockito.atLeastOnce()).setPauseAuth(true)
+
+            job.cancel()
+        }
+
+    @Test
+    fun bouncerExpansionChange_unpauseAuth() =
+        testScope.runTest {
+            // GIVEN view is attached + on the keyguard + panel expansion is 0f
+            mController.onViewAttached()
+            captureStatusBarStateListeners()
+            sendStatusBarStateChanged(StatusBarState.KEYGUARD)
+            Mockito.reset(mView)
+
+            // WHEN panelViewExpansion changes to expanded
+            whenever(mView.unpausedAlpha).thenReturn(255)
+            val job = mController.listenForBouncerExpansion(this)
+            keyguardBouncerRepository.setPrimaryShow(true)
+            keyguardBouncerRepository.setPanelExpansion(KeyguardBouncerConstants.EXPANSION_HIDDEN)
+            runCurrent()
+
+            // THEN pause auth is updated to NOT pause
+            verify(mView, Mockito.atLeastOnce()).setPauseAuth(false)
+
+            job.cancel()
+        }
+
+    @Test
     fun shadeLocked_showAlternateBouncer_unpauseAuth() =
         testScope.runTest {
             // GIVEN view is attached + on the SHADE_LOCKED (udfps view not showing)
@@ -154,4 +221,48 @@
 
             job.cancel()
         }
+
+    @Test
+    fun fadeFromDialogSuggestedAlpha() =
+        testScope.runTest {
+            // GIVEN view is attached and status bar expansion is 1f
+            mController.onViewAttached()
+            captureStatusBarStateListeners()
+            val job = mController.listenForBouncerExpansion(this)
+            keyguardBouncerRepository.setPrimaryShow(true)
+            keyguardBouncerRepository.setPanelExpansion(KeyguardBouncerConstants.EXPANSION_HIDDEN)
+            runCurrent()
+            Mockito.reset(mView)
+
+            // WHEN dialog suggested alpha is .6f
+            whenever(mView.dialogSuggestedAlpha).thenReturn(.6f)
+            sendStatusBarStateChanged(StatusBarState.KEYGUARD)
+
+            // THEN alpha is updated based on dialog suggested alpha
+            verify(mView).unpausedAlpha = (.6f * 255).toInt()
+
+            job.cancel()
+        }
+
+    @Test
+    fun transitionToFullShadeProgress() =
+        testScope.runTest {
+            // GIVEN view is attached and status bar expansion is 1f
+            mController.onViewAttached()
+            val job = mController.listenForBouncerExpansion(this)
+            keyguardBouncerRepository.setPrimaryShow(true)
+            keyguardBouncerRepository.setPanelExpansion(KeyguardBouncerConstants.EXPANSION_HIDDEN)
+            runCurrent()
+            Mockito.reset(mView)
+            whenever(mView.dialogSuggestedAlpha).thenReturn(1f)
+
+            // WHEN we're transitioning to the full shade
+            val transitionProgress = .6f
+            mController.setTransitionToFullShadeProgress(transitionProgress)
+
+            // THEN alpha is between 0 and 255
+            verify(mView).unpausedAlpha = ((1f - transitionProgress) * 255).toInt()
+
+            job.cancel()
+        }
 }
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/udfps/UdfpsUtilsTest.java b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsUtilsTest.java
similarity index 84%
rename from packages/SettingsLib/tests/robotests/src/com/android/settingslib/udfps/UdfpsUtilsTest.java
rename to packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsUtilsTest.java
index f4f0ef9..2aeba9a 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/udfps/UdfpsUtilsTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsUtilsTest.java
@@ -14,40 +14,45 @@
  * limitations under the License.
  */
 
-package com.android.settingslib.udfps;
+package com.android.systemui.biometrics;
 
 import static com.google.common.truth.Truth.assertThat;
 
-import android.content.Context;
+import android.content.res.Resources;
 import android.graphics.Rect;
 import android.view.Surface;
 
-import com.android.settingslib.R;
+import androidx.test.filters.SmallTest;
+
+import com.android.systemui.SysuiTestCase;
+import com.android.systemui.biometrics.shared.model.UdfpsOverlayParams;
+import com.android.systemui.shared.biometrics.R;
 
 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.junit.MockitoJUnit;
 import org.mockito.junit.MockitoRule;
-import org.robolectric.RobolectricTestRunner;
-import org.robolectric.RuntimeEnvironment;
 
-
-@RunWith(RobolectricTestRunner.class)
-public class UdfpsUtilsTest {
+@RunWith(JUnit4.class)
+@SmallTest
+public class UdfpsUtilsTest extends SysuiTestCase {
     @Rule
     public final MockitoRule rule = MockitoJUnit.rule();
-
-    private Context mContext;
     private String[] mTouchHints;
     private UdfpsUtils mUdfpsUtils;
 
     @Before
     public void setUp() {
-        mContext = RuntimeEnvironment.application;
-        mTouchHints = mContext.getResources().getStringArray(
-                R.array.udfps_accessibility_touch_hints);
+        Resources resources = mContext.getResources();
+        mTouchHints = new String[]{
+                resources.getString(R.string.udfps_accessibility_touch_hints_left),
+                resources.getString(R.string.udfps_accessibility_touch_hints_down),
+                resources.getString(R.string.udfps_accessibility_touch_hints_right),
+                resources.getString(R.string.udfps_accessibility_touch_hints_up),
+        };
         mUdfpsUtils = new UdfpsUtils();
     }
 
@@ -86,7 +91,7 @@
 
 
     @Test
-    public void testTouchOutsideAreaNoRotation90Degrees() {
+    public void testTouchOutsideAreaRotation90Degrees() {
         int rotation = Surface.ROTATION_90;
         // touch at 0 degrees -> 90 degrees
         assertThat(
@@ -120,7 +125,7 @@
 
 
     @Test
-    public void testTouchOutsideAreaNoRotation270Degrees() {
+    public void testTouchOutsideAreaRotation270Degrees() {
         int rotation = Surface.ROTATION_270;
         // touch at 0 degrees -> 270 degrees
         assertThat(
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsViewTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsViewTest.kt
index d11c965..6d4588d 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsViewTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsViewTest.kt
@@ -19,16 +19,16 @@
 import android.graphics.PointF
 import android.graphics.RectF
 import android.hardware.biometrics.SensorLocationInternal
-import androidx.test.ext.junit.runners.AndroidJUnit4
 import android.testing.TestableLooper
 import android.testing.ViewUtils
 import android.view.LayoutInflater
 import android.view.Surface
+import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
-import com.android.settingslib.udfps.UdfpsOverlayParams
 import com.android.systemui.R
 import com.android.systemui.RoboPilotTest
 import com.android.systemui.SysuiTestCase
+import com.android.systemui.biometrics.shared.model.UdfpsOverlayParams
 import com.android.systemui.util.mockito.any
 import com.android.systemui.util.mockito.mock
 import com.android.systemui.util.mockito.withArgCaptor
@@ -42,8 +42,8 @@
 import org.mockito.Mockito.never
 import org.mockito.Mockito.nullable
 import org.mockito.Mockito.verify
-import org.mockito.Mockito.`when` as whenever
 import org.mockito.junit.MockitoJUnit
+import org.mockito.Mockito.`when` as whenever
 
 private const val SENSOR_X = 50
 private const val SENSOR_Y = 250
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/DisplayStateInteractorImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/DisplayStateInteractorImplTest.kt
index 2217c5c..524f254 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/DisplayStateInteractorImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/DisplayStateInteractorImplTest.kt
@@ -1,9 +1,12 @@
 package com.android.systemui.biometrics.domain.interactor
 
+import android.view.Display
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.biometrics.data.repository.FakeRearDisplayStateRepository
 import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.display.data.repository.FakeDisplayRepository
+import com.android.systemui.display.data.repository.display
 import com.android.systemui.unfold.compat.ScreenSizeFoldProvider
 import com.android.systemui.unfold.updates.FoldProvider
 import com.android.systemui.util.concurrency.FakeExecutor
@@ -34,19 +37,23 @@
 
     private val fakeExecutor = FakeExecutor(FakeSystemClock())
     private val testScope = TestScope(StandardTestDispatcher())
-    private val rearDisplayStateRepository = FakeRearDisplayStateRepository()
+    private lateinit var rearDisplayStateRepository: FakeRearDisplayStateRepository
+    private lateinit var displayRepository: FakeDisplayRepository
 
     @Mock private lateinit var screenSizeFoldProvider: ScreenSizeFoldProvider
     private lateinit var interactor: DisplayStateInteractorImpl
 
     @Before
     fun setup() {
+        rearDisplayStateRepository = FakeRearDisplayStateRepository()
+        displayRepository = FakeDisplayRepository()
         interactor =
             DisplayStateInteractorImpl(
                 testScope.backgroundScope,
                 mContext,
                 fakeExecutor,
-                rearDisplayStateRepository
+                rearDisplayStateRepository,
+                displayRepository,
             )
         interactor.setScreenSizeFoldProvider(screenSizeFoldProvider)
     }
@@ -76,6 +83,21 @@
             callback.onFoldUpdated(isFolded = false)
             assertThat(isFolded()).isFalse()
         }
+
+    @Test
+    fun isDefaultDisplayOffChanges() =
+        testScope.runTest {
+            val isDefaultDisplayOff by collectLastValue(interactor.isDefaultDisplayOff)
+            runCurrent()
+
+            displayRepository.emit(setOf(display(0, 0, Display.DEFAULT_DISPLAY, Display.STATE_OFF)))
+            displayRepository.emitDisplayChangeEvent(Display.DEFAULT_DISPLAY)
+            assertThat(isDefaultDisplayOff).isTrue()
+
+            displayRepository.emit(setOf(display(0, 0, Display.DEFAULT_DISPLAY, Display.STATE_ON)))
+            displayRepository.emitDisplayChangeEvent(Display.DEFAULT_DISPLAY)
+            assertThat(isDefaultDisplayOff).isFalse()
+        }
 }
 
 private fun FoldProvider.captureCallback() =
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/PromptSelectorInteractorImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/PromptSelectorInteractorImplTest.kt
index 4d5e1b7..f15b738 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/PromptSelectorInteractorImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/PromptSelectorInteractorImplTest.kt
@@ -25,9 +25,9 @@
 import com.android.systemui.biometrics.Utils
 import com.android.systemui.biometrics.data.repository.FakeFingerprintPropertyRepository
 import com.android.systemui.biometrics.data.repository.FakePromptRepository
-import com.android.systemui.biometrics.domain.model.BiometricModalities
 import com.android.systemui.biometrics.faceSensorPropertiesInternal
 import com.android.systemui.biometrics.fingerprintSensorPropertiesInternal
+import com.android.systemui.biometrics.shared.model.BiometricModalities
 import com.android.systemui.biometrics.shared.model.PromptKind
 import com.android.systemui.coroutines.collectLastValue
 import com.android.systemui.util.mockito.any
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/UdfpsOverlayInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/UdfpsOverlayInteractorTest.kt
index 9431d86..6b9c34b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/UdfpsOverlayInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/UdfpsOverlayInteractorTest.kt
@@ -20,9 +20,9 @@
 import android.test.suitebuilder.annotation.SmallTest
 import android.view.MotionEvent
 import android.view.Surface
-import com.android.settingslib.udfps.UdfpsOverlayParams
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.biometrics.AuthController
+import com.android.systemui.biometrics.shared.model.UdfpsOverlayParams
 import com.android.systemui.coroutines.collectLastValue
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.test.StandardTestDispatcher
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/model/BiometricPromptRequestTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/model/BiometricPromptRequestTest.kt
index be0276a..9e3c576 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/model/BiometricPromptRequestTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/model/BiometricPromptRequestTest.kt
@@ -4,6 +4,7 @@
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.biometrics.fingerprintSensorPropertiesInternal
 import com.android.systemui.biometrics.promptInfo
+import com.android.systemui.biometrics.shared.model.BiometricModalities
 import com.android.systemui.biometrics.shared.model.BiometricUserInfo
 import com.google.common.truth.Truth.assertThat
 import org.junit.Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/model/BiometricModalitiesTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/shared/model/BiometricModalitiesTest.kt
similarity index 98%
rename from packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/model/BiometricModalitiesTest.kt
rename to packages/SystemUI/tests/src/com/android/systemui/biometrics/shared/model/BiometricModalitiesTest.kt
index 526b833..22e3e7f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/model/BiometricModalitiesTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/shared/model/BiometricModalitiesTest.kt
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.systemui.biometrics.domain.model
+package com.android.systemui.biometrics.shared.model
 
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/udfps/SinglePointerTouchProcessorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/udfps/SinglePointerTouchProcessorTest.kt
index ec2c1bc..99c2c40 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/udfps/SinglePointerTouchProcessorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/udfps/SinglePointerTouchProcessorTest.kt
@@ -23,8 +23,8 @@
 import android.view.Surface
 import android.view.Surface.Rotation
 import androidx.test.filters.SmallTest
-import com.android.settingslib.udfps.UdfpsOverlayParams
 import com.android.systemui.SysuiTestCase
+import com.android.systemui.biometrics.shared.model.UdfpsOverlayParams
 import com.google.common.truth.Truth.assertThat
 import org.junit.Test
 import org.junit.runner.RunWith
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/PromptFingerprintIconViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/PromptFingerprintIconViewModelTest.kt
index 7697c09..b3964b6 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/PromptFingerprintIconViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/PromptFingerprintIconViewModelTest.kt
@@ -14,6 +14,7 @@
 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.display.data.repository.FakeDisplayRepository
 import com.android.systemui.util.concurrency.FakeExecutor
 import com.android.systemui.util.time.FakeSystemClock
 import com.google.common.truth.Truth.assertThat
@@ -39,9 +40,10 @@
 
     @Mock private lateinit var lockPatternUtils: LockPatternUtils
 
-    private val fingerprintRepository = FakeFingerprintPropertyRepository()
-    private val promptRepository = FakePromptRepository()
-    private val rearDisplayStateRepository = FakeRearDisplayStateRepository()
+    private lateinit var displayRepository: FakeDisplayRepository
+    private lateinit var fingerprintRepository: FakeFingerprintPropertyRepository
+    private lateinit var promptRepository: FakePromptRepository
+    private lateinit var rearDisplayStateRepository: FakeRearDisplayStateRepository
 
     private val testScope = TestScope(StandardTestDispatcher())
     private val fakeExecutor = FakeExecutor(FakeSystemClock())
@@ -52,6 +54,11 @@
 
     @Before
     fun setup() {
+        displayRepository = FakeDisplayRepository()
+        fingerprintRepository = FakeFingerprintPropertyRepository()
+        promptRepository = FakePromptRepository()
+        rearDisplayStateRepository = FakeRearDisplayStateRepository()
+
         promptSelectorInteractor =
             PromptSelectorInteractorImpl(fingerprintRepository, promptRepository, lockPatternUtils)
         displayStateInteractor =
@@ -59,7 +66,8 @@
                 testScope.backgroundScope,
                 mContext,
                 fakeExecutor,
-                rearDisplayStateRepository
+                rearDisplayStateRepository,
+                displayRepository,
             )
         viewModel = PromptFingerprintIconViewModel(displayStateInteractor, promptSelectorInteractor)
     }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModelTest.kt
index 0ed46da..5834e31 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModelTest.kt
@@ -24,20 +24,22 @@
 import androidx.test.filters.SmallTest
 import com.android.internal.widget.LockPatternUtils
 import com.android.systemui.SysuiTestCase
-import com.android.systemui.biometrics.AuthBiometricView
 import com.android.systemui.biometrics.data.repository.FakeFingerprintPropertyRepository
 import com.android.systemui.biometrics.data.repository.FakePromptRepository
 import com.android.systemui.biometrics.data.repository.FakeRearDisplayStateRepository
+import com.android.systemui.biometrics.domain.interactor.DisplayStateInteractor
 import com.android.systemui.biometrics.domain.interactor.DisplayStateInteractorImpl
 import com.android.systemui.biometrics.domain.interactor.PromptSelectorInteractor
 import com.android.systemui.biometrics.domain.interactor.PromptSelectorInteractorImpl
-import com.android.systemui.biometrics.domain.model.BiometricModalities
 import com.android.systemui.biometrics.extractAuthenticatorTypes
 import com.android.systemui.biometrics.faceSensorPropertiesInternal
 import com.android.systemui.biometrics.fingerprintSensorPropertiesInternal
+import com.android.systemui.biometrics.shared.model.BiometricModalities
 import com.android.systemui.biometrics.shared.model.BiometricModality
+import com.android.systemui.biometrics.ui.binder.Spaghetti.BiometricState
 import com.android.systemui.coroutines.collectLastValue
 import com.android.systemui.coroutines.collectValues
+import com.android.systemui.display.data.repository.FakeDisplayRepository
 import com.android.systemui.flags.FakeFeatureFlags
 import com.android.systemui.flags.Flags.ONE_WAY_HAPTICS_API_MIGRATION
 import com.android.systemui.statusbar.VibratorHelper
@@ -77,17 +79,12 @@
 
     private val fakeExecutor = FakeExecutor(FakeSystemClock())
     private val testScope = TestScope()
-    private val fingerprintRepository = FakeFingerprintPropertyRepository()
-    private val promptRepository = FakePromptRepository()
-    private val rearDisplayStateRepository = FakeRearDisplayStateRepository()
 
-    private val displayStateInteractor =
-        DisplayStateInteractorImpl(
-            testScope.backgroundScope,
-            mContext,
-            fakeExecutor,
-            rearDisplayStateRepository
-        )
+    private lateinit var fingerprintRepository: FakeFingerprintPropertyRepository
+    private lateinit var promptRepository: FakePromptRepository
+    private lateinit var rearDisplayStateRepository: FakeRearDisplayStateRepository
+    private lateinit var displayRepository: FakeDisplayRepository
+    private lateinit var displayStateInteractor: DisplayStateInteractor
 
     private lateinit var selector: PromptSelectorInteractor
     private lateinit var viewModel: PromptViewModel
@@ -95,6 +92,18 @@
 
     @Before
     fun setup() {
+        fingerprintRepository = FakeFingerprintPropertyRepository()
+        promptRepository = FakePromptRepository()
+        rearDisplayStateRepository = FakeRearDisplayStateRepository()
+        displayRepository = FakeDisplayRepository()
+        displayStateInteractor =
+            DisplayStateInteractorImpl(
+                testScope.backgroundScope,
+                mContext,
+                fakeExecutor,
+                rearDisplayStateRepository,
+                displayRepository,
+            )
         selector =
             PromptSelectorInteractorImpl(fingerprintRepository, promptRepository, lockPatternUtils)
         selector.resetPrompt()
@@ -123,7 +132,7 @@
             }
             assertThat(message).isEqualTo(PromptMessage.Empty)
             assertThat(size).isEqualTo(expectedSize)
-            assertThat(legacyState).isEqualTo(AuthBiometricView.STATE_IDLE)
+            assertThat(legacyState).isEqualTo(BiometricState.STATE_IDLE)
 
             val startMessage = "here we go"
             viewModel.showAuthenticating(startMessage, isRetry = false)
@@ -133,7 +142,7 @@
             assertThat(authenticated?.isNotAuthenticated).isTrue()
             assertThat(size).isEqualTo(expectedSize)
             assertButtonsVisible(negative = expectedSize != PromptSize.SMALL)
-            assertThat(legacyState).isEqualTo(AuthBiometricView.STATE_AUTHENTICATING)
+            assertThat(legacyState).isEqualTo(BiometricState.STATE_AUTHENTICATING)
         }
 
     @Test
@@ -211,7 +220,7 @@
         assertThat(authenticating).isTrue()
         assertThat(authenticated?.isNotAuthenticated).isTrue()
         assertThat(size).isEqualTo(if (authWithSmallPrompt) PromptSize.SMALL else PromptSize.MEDIUM)
-        assertThat(legacyState).isEqualTo(AuthBiometricView.STATE_AUTHENTICATING)
+        assertThat(legacyState).isEqualTo(BiometricState.STATE_AUTHENTICATING)
         assertButtonsVisible(negative = !authWithSmallPrompt)
 
         val delay = 1000L
@@ -231,9 +240,9 @@
         assertThat(legacyState)
             .isEqualTo(
                 if (expectConfirmation) {
-                    AuthBiometricView.STATE_PENDING_CONFIRMATION
+                    BiometricState.STATE_PENDING_CONFIRMATION
                 } else {
-                    AuthBiometricView.STATE_AUTHENTICATED
+                    BiometricState.STATE_AUTHENTICATED
                 }
             )
         assertButtonsVisible(
@@ -302,7 +311,7 @@
         assertThat(size).isEqualTo(PromptSize.MEDIUM)
         assertThat(message).isEqualTo(PromptMessage.Error(errorMessage))
         assertThat(messageVisible).isTrue()
-        assertThat(legacyState).isEqualTo(AuthBiometricView.STATE_ERROR)
+        assertThat(legacyState).isEqualTo(BiometricState.STATE_ERROR)
 
         // temporary error should disappear after a delay
         errorJob.join()
@@ -317,11 +326,11 @@
         assertThat(legacyState)
             .isEqualTo(
                 if (restart) {
-                    AuthBiometricView.STATE_AUTHENTICATING
+                    BiometricState.STATE_AUTHENTICATING
                 } else if (clearIconError) {
-                    AuthBiometricView.STATE_IDLE
+                    BiometricState.STATE_IDLE
                 } else {
-                    AuthBiometricView.STATE_HELP
+                    BiometricState.STATE_HELP
                 }
             )
 
@@ -496,7 +505,7 @@
 
         assertThat(authenticating).isFalse()
         assertThat(authenticated?.isAuthenticated).isTrue()
-        assertThat(legacyState).isEqualTo(AuthBiometricView.STATE_AUTHENTICATED)
+        assertThat(legacyState).isEqualTo(BiometricState.STATE_AUTHENTICATED)
         assertThat(canTryAgain).isFalse()
     }
 
@@ -522,7 +531,7 @@
         assertThat(authenticated?.isAuthenticated).isTrue()
 
         if (testCase.isFaceOnly && expectConfirmation) {
-            assertThat(legacyState).isEqualTo(AuthBiometricView.STATE_PENDING_CONFIRMATION)
+            assertThat(legacyState).isEqualTo(BiometricState.STATE_PENDING_CONFIRMATION)
 
             assertThat(size).isEqualTo(PromptSize.MEDIUM)
             assertButtonsVisible(
@@ -534,7 +543,7 @@
             assertThat(message).isEqualTo(PromptMessage.Empty)
             assertButtonsVisible()
         } else {
-            assertThat(legacyState).isEqualTo(AuthBiometricView.STATE_AUTHENTICATED)
+            assertThat(legacyState).isEqualTo(BiometricState.STATE_AUTHENTICATED)
         }
     }
 
@@ -571,7 +580,7 @@
 
         assertThat(authenticating).isFalse()
         assertThat(authenticated?.isAuthenticated).isTrue()
-        assertThat(legacyState).isEqualTo(AuthBiometricView.STATE_AUTHENTICATED)
+        assertThat(legacyState).isEqualTo(BiometricState.STATE_AUTHENTICATED)
         assertThat(canTryAgain).isFalse()
     }
 
@@ -605,7 +614,7 @@
         viewModel.showHelp(helpMessage)
 
         assertThat(size).isEqualTo(PromptSize.MEDIUM)
-        assertThat(legacyState).isEqualTo(AuthBiometricView.STATE_HELP)
+        assertThat(legacyState).isEqualTo(BiometricState.STATE_HELP)
         assertThat(message).isEqualTo(PromptMessage.Help(helpMessage))
         assertThat(messageVisible).isTrue()
 
@@ -633,9 +642,9 @@
 
         assertThat(size).isEqualTo(PromptSize.MEDIUM)
         if (confirmationRequired == true) {
-            assertThat(legacyState).isEqualTo(AuthBiometricView.STATE_PENDING_CONFIRMATION)
+            assertThat(legacyState).isEqualTo(BiometricState.STATE_PENDING_CONFIRMATION)
         } else {
-            assertThat(legacyState).isEqualTo(AuthBiometricView.STATE_AUTHENTICATED)
+            assertThat(legacyState).isEqualTo(BiometricState.STATE_AUTHENTICATED)
         }
         assertThat(message).isEqualTo(PromptMessage.Help(helpMessage))
         assertThat(messageVisible).isTrue()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bouncer/domain/interactor/PrimaryBouncerInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bouncer/domain/interactor/PrimaryBouncerInteractorTest.kt
index f892453..420fdba 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/bouncer/domain/interactor/PrimaryBouncerInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/bouncer/domain/interactor/PrimaryBouncerInteractorTest.kt
@@ -115,7 +115,7 @@
     @Test
     fun testShow_isScrimmed() {
         underTest.show(true)
-        verify(repository).setKeyguardAuthenticated(null)
+        verify(repository).setKeyguardAuthenticatedBiometrics(null)
         verify(repository).setPrimaryStartingToHide(false)
         verify(repository).setPrimaryScrimmed(true)
         verify(repository).setPanelExpansion(EXPANSION_VISIBLE)
@@ -222,8 +222,8 @@
 
     @Test
     fun testNotifyKeyguardAuthenticated() {
-        underTest.notifyKeyguardAuthenticated(true)
-        verify(repository).setKeyguardAuthenticated(true)
+        underTest.notifyKeyguardAuthenticatedBiometrics(true)
+        verify(repository).setKeyguardAuthenticatedBiometrics(true)
     }
 
     @Test
@@ -241,7 +241,7 @@
     @Test
     fun testNotifyKeyguardAuthenticatedHandled() {
         underTest.notifyKeyguardAuthenticatedHandled()
-        verify(repository).setKeyguardAuthenticated(null)
+        verify(repository).setKeyguardAuthenticatedBiometrics(null)
     }
 
     @Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModelTest.kt
index 4380af8..12090e5 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModelTest.kt
@@ -185,6 +185,41 @@
             assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Gone))
         }
 
+    @Test
+    fun onShown_againAfterSceneChange_resetsPassword() =
+        testScope.runTest {
+            val currentScene by collectLastValue(sceneInteractor.desiredScene)
+            val password by collectLastValue(underTest.password)
+            utils.authenticationRepository.setAuthenticationMethod(
+                AuthenticationMethodModel.Password
+            )
+            utils.authenticationRepository.setUnlocked(false)
+            sceneInteractor.changeScene(SceneModel(SceneKey.Bouncer), "reason")
+            sceneInteractor.onSceneChanged(SceneModel(SceneKey.Bouncer), "reason")
+            assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
+            underTest.onShown()
+
+            // The user types a password.
+            underTest.onPasswordInputChanged("password")
+            assertThat(password).isEqualTo("password")
+
+            // The user doesn't confirm the password, but navigates back to the lockscreen instead.
+            sceneInteractor.changeScene(SceneModel(SceneKey.Lockscreen), "reason")
+            sceneInteractor.onSceneChanged(SceneModel(SceneKey.Lockscreen), "reason")
+            assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Lockscreen))
+
+            // The user navigates to the bouncer again.
+            sceneInteractor.changeScene(SceneModel(SceneKey.Bouncer), "reason")
+            sceneInteractor.onSceneChanged(SceneModel(SceneKey.Bouncer), "reason")
+            assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
+
+            underTest.onShown()
+
+            // Ensure the previously-entered password is not shown.
+            assertThat(password).isEmpty()
+            assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
+        }
+
     companion object {
         private const val ENTER_YOUR_PASSWORD = "Enter your password"
         private const val WRONG_PASSWORD = "Wrong password"
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/PatternBouncerViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/PatternBouncerViewModelTest.kt
index ea2cad2..3c5212a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/PatternBouncerViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/PatternBouncerViewModelTest.kt
@@ -203,6 +203,49 @@
         }
 
     @Test
+    fun onDragEnd_whenPatternTooShort() =
+        testScope.runTest {
+            val currentScene by collectLastValue(sceneInteractor.desiredScene)
+            val message by collectLastValue(bouncerViewModel.message)
+            val selectedDots by collectLastValue(underTest.selectedDots)
+            val currentDot by collectLastValue(underTest.currentDot)
+            val throttlingDialogMessage by
+                collectLastValue(bouncerViewModel.throttlingDialogMessage)
+            utils.authenticationRepository.setAuthenticationMethod(
+                AuthenticationMethodModel.Pattern
+            )
+            utils.authenticationRepository.setUnlocked(false)
+            sceneInteractor.changeScene(SceneModel(SceneKey.Bouncer), "reason")
+            sceneInteractor.onSceneChanged(SceneModel(SceneKey.Bouncer), "reason")
+            assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
+            underTest.onShown()
+
+            // Enter a pattern that's too short more than enough times that would normally trigger
+            // throttling if the pattern were not too short and wrong:
+            val attempts = FakeAuthenticationRepository.MAX_FAILED_AUTH_TRIES_BEFORE_THROTTLING + 1
+            repeat(attempts) { attempt ->
+                underTest.onDragStart()
+                CORRECT_PATTERN.subList(
+                        0,
+                        authenticationInteractor.minPatternLength - 1,
+                    )
+                    .forEach { coordinate ->
+                        underTest.onDrag(
+                            xPx = 30f * coordinate.x + 15,
+                            yPx = 30f * coordinate.y + 15,
+                            containerSizePx = 90,
+                            verticalOffsetPx = 0f,
+                        )
+                    }
+
+                underTest.onDragEnd()
+
+                assertWithMessage("Attempt #$attempt").that(message?.text).isEqualTo(WRONG_PATTERN)
+                assertWithMessage("Attempt #$attempt").that(throttlingDialogMessage).isNull()
+            }
+        }
+
+    @Test
     fun onDragEnd_correctAfterWrong() =
         testScope.runTest {
             val currentScene by collectLastValue(sceneInteractor.desiredScene)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModelTest.kt
index 531f86a..a684221 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModelTest.kt
@@ -314,6 +314,42 @@
         }
 
     @Test
+    fun onShown_againAfterSceneChange_resetsPin() =
+        testScope.runTest {
+            val currentScene by collectLastValue(sceneInteractor.desiredScene)
+            val pin by collectLastValue(underTest.pinInput.map { it.getPin() })
+            utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
+            utils.authenticationRepository.setUnlocked(false)
+            sceneInteractor.changeScene(SceneModel(SceneKey.Bouncer), "reason")
+            sceneInteractor.onSceneChanged(SceneModel(SceneKey.Bouncer), "reason")
+
+            assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
+            underTest.onShown()
+
+            // The user types a PIN.
+            FakeAuthenticationRepository.DEFAULT_PIN.forEach { digit ->
+                underTest.onPinButtonClicked(digit)
+            }
+            assertThat(pin).isNotEmpty()
+
+            // The user doesn't confirm the PIN, but navigates back to the lockscreen instead.
+            sceneInteractor.changeScene(SceneModel(SceneKey.Lockscreen), "reason")
+            sceneInteractor.onSceneChanged(SceneModel(SceneKey.Lockscreen), "reason")
+            assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Lockscreen))
+
+            // The user navigates to the bouncer again.
+            sceneInteractor.changeScene(SceneModel(SceneKey.Bouncer), "reason")
+            sceneInteractor.onSceneChanged(SceneModel(SceneKey.Bouncer), "reason")
+            assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
+
+            underTest.onShown()
+
+            // Ensure the previously-entered PIN is not shown.
+            assertThat(pin).isEmpty()
+            assertThat(currentScene).isEqualTo(SceneModel(SceneKey.Bouncer))
+        }
+
+    @Test
     fun backspaceButtonAppearance_withoutAutoConfirm_alwaysShown() =
         testScope.runTest {
             val backspaceButtonAppearance by collectLastValue(underTest.backspaceButtonAppearance)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/dreams/touch/BouncerSwipeTouchHandlerTest.java b/packages/SystemUI/tests/src/com/android/systemui/dreams/touch/BouncerSwipeTouchHandlerTest.java
index 90076fd..ffcaeee 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/dreams/touch/BouncerSwipeTouchHandlerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/dreams/touch/BouncerSwipeTouchHandlerTest.java
@@ -17,7 +17,6 @@
 package com.android.systemui.dreams.touch;
 
 import static com.google.common.truth.Truth.assertThat;
-
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyFloat;
 import static org.mockito.ArgumentMatchers.eq;
@@ -295,7 +294,8 @@
     }
 
     /**
-     * Makes sure the expansion amount is proportional to (1 - scroll).
+     * Verifies that swiping up when the lock pattern is not secure does not consume the scroll
+     * gesture or expand.
      */
     @Test
     public void testSwipeUp_keyguardNotSecure_doesNotExpand() {
@@ -314,8 +314,10 @@
                 0, SCREEN_HEIGHT_PX - distanceY, 0);
 
         reset(mScrimController);
+
+        // Scroll gesture is not consumed.
         assertThat(gestureListener.onScroll(event1, event2, 0, distanceY))
-                .isTrue();
+                .isFalse();
         // We should not expand since the keyguard is not secure
         verify(mScrimController, never()).expand(any());
     }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/DeviceEntryFaceAuthRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/DeviceEntryFaceAuthRepositoryTest.kt
index ec15416..f234582 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/DeviceEntryFaceAuthRepositoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/data/repository/DeviceEntryFaceAuthRepositoryTest.kt
@@ -29,6 +29,7 @@
 import android.hardware.face.FaceSensorProperties
 import android.hardware.face.FaceSensorPropertiesInternal
 import android.os.CancellationSignal
+import android.view.Display
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import com.android.internal.logging.InstanceId.fakeInstanceId
@@ -41,11 +42,16 @@
 import com.android.systemui.RoboPilotTest
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.biometrics.data.repository.FakeFacePropertyRepository
+import com.android.systemui.biometrics.data.repository.FakeRearDisplayStateRepository
+import com.android.systemui.biometrics.domain.interactor.DisplayStateInteractor
+import com.android.systemui.biometrics.domain.interactor.DisplayStateInteractorImpl
 import com.android.systemui.bouncer.data.repository.FakeKeyguardBouncerRepository
 import com.android.systemui.bouncer.domain.interactor.AlternateBouncerInteractor
 import com.android.systemui.coroutines.FlowValue
 import com.android.systemui.coroutines.collectLastValue
 import com.android.systemui.coroutines.collectValues
+import com.android.systemui.display.data.repository.FakeDisplayRepository
+import com.android.systemui.display.data.repository.display
 import com.android.systemui.dump.DumpManager
 import com.android.systemui.dump.logcatLogBuffer
 import com.android.systemui.flags.FakeFeatureFlags
@@ -73,6 +79,7 @@
 import com.android.systemui.statusbar.phone.KeyguardBypassController
 import com.android.systemui.user.data.model.SelectionStatus
 import com.android.systemui.user.data.repository.FakeUserRepository
+import com.android.systemui.util.concurrency.FakeExecutor
 import com.android.systemui.util.mockito.KotlinArgumentCaptor
 import com.android.systemui.util.mockito.captureMany
 import com.android.systemui.util.mockito.mock
@@ -151,7 +158,9 @@
     private lateinit var keyguardRepository: FakeKeyguardRepository
     private lateinit var keyguardInteractor: KeyguardInteractor
     private lateinit var alternateBouncerInteractor: AlternateBouncerInteractor
+    private lateinit var displayStateInteractor: DisplayStateInteractor
     private lateinit var bouncerRepository: FakeKeyguardBouncerRepository
+    private lateinit var displayRepository: FakeDisplayRepository
     private lateinit var fakeCommandQueue: FakeCommandQueue
     private lateinit var featureFlags: FakeFeatureFlags
     private lateinit var fakeFacePropertyRepository: FakeFacePropertyRepository
@@ -162,9 +171,10 @@
     @Before
     fun setup() {
         MockitoAnnotations.initMocks(this)
+        testDispatcher = StandardTestDispatcher()
+        testScope = TestScope(testDispatcher)
         fakeUserRepository = FakeUserRepository()
         fakeUserRepository.setUserInfos(listOf(primaryUser, secondaryUser))
-        testDispatcher = StandardTestDispatcher()
         biometricSettingsRepository = FakeBiometricSettingsRepository()
         deviceEntryFingerprintAuthRepository = FakeDeviceEntryFingerprintAuthRepository()
         trustRepository = FakeTrustRepository()
@@ -192,9 +202,18 @@
                 keyguardUpdateMonitor = keyguardUpdateMonitor,
             )
 
+        displayRepository = FakeDisplayRepository()
+        displayStateInteractor =
+            DisplayStateInteractorImpl(
+                applicationScope = testScope.backgroundScope,
+                context = context,
+                mainExecutor = FakeExecutor(FakeSystemClock()),
+                rearDisplayStateRepository = FakeRearDisplayStateRepository(),
+                displayRepository = displayRepository,
+            )
+
         bypassStateChangedListener =
             KotlinArgumentCaptor(KeyguardBypassController.OnBypassStateChangedListener::class.java)
-        testScope = TestScope(testDispatcher)
         whenever(sessionTracker.getSessionId(SESSION_KEYGUARD)).thenReturn(keyguardSessionId)
         whenever(faceManager.sensorPropertiesInternal)
             .thenReturn(listOf(createFaceSensorProperties(supportsFaceDetection = true)))
@@ -253,6 +272,7 @@
             faceDetectBuffer,
             faceAuthBuffer,
             keyguardTransitionInteractor,
+            displayStateInteractor,
             featureFlags,
             dumpManager,
         )
@@ -683,6 +703,44 @@
         }
 
     @Test
+    fun authenticateCanRunWhenDisplayIsOffAndWakingUp() =
+        testScope.runTest {
+            initCollectors()
+            allPreconditionsToRunFaceAuthAreTrue()
+
+            displayRepository.emit(setOf(display(0, 0, Display.DEFAULT_DISPLAY, Display.STATE_OFF)))
+            displayRepository.emitDisplayChangeEvent(Display.DEFAULT_DISPLAY)
+            keyguardRepository.setWakefulnessModel(
+                WakefulnessModel(
+                    WakefulnessState.STARTING_TO_WAKE,
+                    lastWakeReason = WakeSleepReason.POWER_BUTTON,
+                    lastSleepReason = WakeSleepReason.POWER_BUTTON
+                )
+            )
+
+            assertThat(canFaceAuthRun()).isTrue()
+        }
+
+    @Test
+    fun authenticateDoesNotRunWhenDisplayIsOffAndAwake() =
+        testScope.runTest {
+            testGatingCheckForFaceAuth {
+                keyguardRepository.setWakefulnessModel(
+                    WakefulnessModel(
+                        WakefulnessState.AWAKE,
+                        lastWakeReason = WakeSleepReason.POWER_BUTTON,
+                        lastSleepReason = WakeSleepReason.POWER_BUTTON
+                    )
+                )
+
+                displayRepository.emit(
+                    setOf(display(0, 0, Display.DEFAULT_DISPLAY, Display.STATE_OFF))
+                )
+                displayRepository.emitDisplayChangeEvent(Display.DEFAULT_DISPLAY)
+            }
+        }
+
+    @Test
     fun everythingWorksWithFaceAuthRefactorFlagDisabled() =
         testScope.runTest {
             featureFlags.set(FACE_AUTH_REFACTOR, false)
@@ -1017,7 +1075,9 @@
             faceAuthenticateIsCalled()
         }
 
-    private suspend fun TestScope.testGatingCheckForFaceAuth(gatingCheckModifier: () -> Unit) {
+    private suspend fun TestScope.testGatingCheckForFaceAuth(
+        gatingCheckModifier: suspend () -> Unit
+    ) {
         initCollectors()
         allPreconditionsToRunFaceAuthAreTrue()
 
@@ -1108,6 +1168,8 @@
         faceLockoutResetCallback.value.onLockoutReset(0)
         bouncerRepository.setAlternateVisible(true)
         keyguardRepository.setKeyguardShowing(true)
+        displayRepository.emit(setOf(display(0, 0, Display.DEFAULT_DISPLAY, Display.STATE_ON)))
+        displayRepository.emitDisplayChangeEvent(Display.DEFAULT_DISPLAY)
         runCurrent()
     }
 
@@ -1120,6 +1182,7 @@
         authenticated = collectLastValue(underTest.isAuthenticated)
         bypassEnabled = collectLastValue(underTest.isBypassEnabled)
         fakeUserRepository.setSelectedUserInfo(primaryUser)
+        runCurrent()
     }
 
     private fun successResult() = FaceManager.AuthenticationResult(null, null, primaryUserId, false)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissActionInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissActionInteractorTest.kt
new file mode 100644
index 0000000..181cc88
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissActionInteractorTest.kt
@@ -0,0 +1,265 @@
+/*
+ * 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 androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.RoboPilotTest
+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.DismissAction
+import com.android.systemui.keyguard.shared.model.KeyguardDone
+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.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+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.MockitoAnnotations
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+@RoboPilotTest
+@RunWith(AndroidJUnit4::class)
+class KeyguardDismissActionInteractorTest : SysuiTestCase() {
+    private lateinit var keyguardRepository: FakeKeyguardRepository
+    private lateinit var transitionRepository: FakeKeyguardTransitionRepository
+
+    private lateinit var dispatcher: TestDispatcher
+    private lateinit var testScope: TestScope
+
+    private lateinit var dismissInteractorWithDependencies:
+        KeyguardDismissInteractorFactory.WithDependencies
+    private lateinit var underTest: KeyguardDismissActionInteractor
+
+    @Before
+    fun setUp() {
+        MockitoAnnotations.initMocks(this)
+        dispatcher = StandardTestDispatcher()
+        testScope = TestScope(dispatcher)
+
+        dismissInteractorWithDependencies =
+            KeyguardDismissInteractorFactory.create(
+                context = context,
+                testScope = testScope,
+                broadcastDispatcher = fakeBroadcastDispatcher,
+                dispatcher = dispatcher,
+            )
+        keyguardRepository = dismissInteractorWithDependencies.keyguardRepository
+        transitionRepository = FakeKeyguardTransitionRepository()
+
+        underTest =
+            KeyguardDismissActionInteractor(
+                keyguardRepository,
+                KeyguardTransitionInteractorFactory.create(
+                        scope = testScope.backgroundScope,
+                        repository = transitionRepository,
+                    )
+                    .keyguardTransitionInteractor,
+                dismissInteractorWithDependencies.interactor,
+                testScope.backgroundScope,
+            )
+    }
+
+    @Test
+    fun updateDismissAction_onRepoChange() =
+        testScope.runTest {
+            val dismissAction by collectLastValue(underTest.dismissAction)
+
+            val newDismissAction =
+                DismissAction.RunImmediately(
+                    onDismissAction = { KeyguardDone.IMMEDIATE },
+                    onCancelAction = {},
+                    message = "",
+                    willAnimateOnLockscreen = true,
+                )
+            keyguardRepository.setDismissAction(newDismissAction)
+            assertThat(dismissAction).isEqualTo(newDismissAction)
+        }
+
+    @Test
+    fun messageUpdate() =
+        testScope.runTest {
+            val message by collectLastValue(underTest.message)
+            keyguardRepository.setDismissAction(
+                DismissAction.RunImmediately(
+                    onDismissAction = { KeyguardDone.IMMEDIATE },
+                    onCancelAction = {},
+                    message = "message",
+                    willAnimateOnLockscreen = true,
+                )
+            )
+            assertThat(message).isEqualTo("message")
+        }
+
+    @Test
+    fun runDismissAnimationOnKeyguard_defaultStateFalse() =
+        testScope.runTest { assertThat(underTest.runDismissAnimationOnKeyguard()).isFalse() }
+
+    @Test
+    fun runDismissAnimationOnKeyguardUpdates() =
+        testScope.runTest {
+            val animate by collectLastValue(underTest.willAnimateDismissActionOnLockscreen)
+            keyguardRepository.setDismissAction(
+                DismissAction.RunImmediately(
+                    onDismissAction = { KeyguardDone.IMMEDIATE },
+                    onCancelAction = {},
+                    message = "message",
+                    willAnimateOnLockscreen = true,
+                )
+            )
+            assertThat(animate).isEqualTo(true)
+
+            keyguardRepository.setDismissAction(
+                DismissAction.RunImmediately(
+                    onDismissAction = { KeyguardDone.IMMEDIATE },
+                    onCancelAction = {},
+                    message = "message",
+                    willAnimateOnLockscreen = false,
+                )
+            )
+            assertThat(animate).isEqualTo(false)
+        }
+
+    @Test
+    fun executeDismissAction_dismissKeyguardRequestWithImmediateDismissAction_biometricAuthed() =
+        testScope.runTest {
+            val executeDismissAction by collectLastValue(underTest.executeDismissAction)
+
+            val onDismissAction = { KeyguardDone.IMMEDIATE }
+            keyguardRepository.setDismissAction(
+                DismissAction.RunImmediately(
+                    onDismissAction = onDismissAction,
+                    onCancelAction = {},
+                    message = "message",
+                    willAnimateOnLockscreen = true,
+                )
+            )
+            dismissInteractorWithDependencies.bouncerRepository.setKeyguardAuthenticatedBiometrics(
+                true
+            )
+            assertThat(executeDismissAction).isEqualTo(onDismissAction)
+        }
+
+    @Test
+    fun executeDismissAction_dismissKeyguardRequestWithoutImmediateDismissAction() =
+        testScope.runTest {
+            val executeDismissAction by collectLastValue(underTest.executeDismissAction)
+
+            // WHEN a keyguard action will run after the keyguard is gone
+            val onDismissAction = {}
+            keyguardRepository.setDismissAction(
+                DismissAction.RunAfterKeyguardGone(
+                    dismissAction = onDismissAction,
+                    onCancelAction = {},
+                    message = "message",
+                    willAnimateOnLockscreen = true,
+                )
+            )
+            assertThat(executeDismissAction).isNull()
+
+            // WHEN the keyguard is GONE
+            transitionRepository.sendTransitionStep(
+                TransitionStep(to = KeyguardState.GONE, transitionState = TransitionState.FINISHED)
+            )
+            assertThat(executeDismissAction).isNotNull()
+        }
+
+    @Test
+    fun resetDismissAction() =
+        testScope.runTest {
+            val resetDismissAction by collectLastValue(underTest.resetDismissAction)
+
+            keyguardRepository.setDismissAction(
+                DismissAction.RunAfterKeyguardGone(
+                    dismissAction = {},
+                    onCancelAction = {},
+                    message = "message",
+                    willAnimateOnLockscreen = true,
+                )
+            )
+            transitionRepository.sendTransitionStep(
+                TransitionStep(
+                    to = KeyguardState.AOD,
+                    transitionState = TransitionState.FINISHED,
+                )
+            )
+            assertThat(resetDismissAction).isEqualTo(Unit)
+        }
+
+    @Test
+    fun setDismissAction_callsCancelRunnableOnPreviousDismissAction() =
+        testScope.runTest {
+            val dismissAction by collectLastValue(underTest.dismissAction)
+            var previousDismissActionCancelCalled = false
+            keyguardRepository.setDismissAction(
+                DismissAction.RunImmediately(
+                    onDismissAction = { KeyguardDone.IMMEDIATE },
+                    onCancelAction = { previousDismissActionCancelCalled = true },
+                    message = "",
+                    willAnimateOnLockscreen = true,
+                )
+            )
+
+            val newDismissAction =
+                DismissAction.RunImmediately(
+                    onDismissAction = { KeyguardDone.IMMEDIATE },
+                    onCancelAction = {},
+                    message = "",
+                    willAnimateOnLockscreen = true,
+                )
+            underTest.setDismissAction(newDismissAction)
+
+            // THEN previous dismiss action got its onCancel called
+            assertThat(previousDismissActionCancelCalled).isTrue()
+
+            // THEN dismiss action is updated
+            assertThat(dismissAction).isEqualTo(newDismissAction)
+        }
+
+    @Test
+    fun handleDismissAction() =
+        testScope.runTest {
+            val dismissAction by collectLastValue(underTest.dismissAction)
+            underTest.handleDismissAction()
+            assertThat(dismissAction).isEqualTo(DismissAction.None)
+        }
+
+    @Test
+    fun setKeyguardDone() =
+        testScope.runTest {
+            val keyguardDoneTiming by
+                collectLastValue(dismissInteractorWithDependencies.interactor.keyguardDone)
+            runCurrent()
+
+            underTest.setKeyguardDone(KeyguardDone.LATER)
+            assertThat(keyguardDoneTiming).isEqualTo(KeyguardDone.LATER)
+
+            underTest.setKeyguardDone(KeyguardDone.IMMEDIATE)
+            assertThat(keyguardDoneTiming).isEqualTo(KeyguardDone.IMMEDIATE)
+        }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissInteractorTest.kt
new file mode 100644
index 0000000..c407b14
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissInteractorTest.kt
@@ -0,0 +1,208 @@
+/*
+ * 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.content.pm.UserInfo
+import android.service.trust.TrustAgentService
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.keyguard.TrustGrantFlags
+import com.android.systemui.RoboPilotTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.keyguard.shared.model.DismissAction
+import com.android.systemui.keyguard.shared.model.KeyguardDone
+import com.android.systemui.keyguard.shared.model.TrustModel
+import com.google.common.truth.Truth.assertThat
+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.MockitoAnnotations
+
+@SmallTest
+@RoboPilotTest
+@RunWith(AndroidJUnit4::class)
+class KeyguardDismissInteractorTest : SysuiTestCase() {
+    private lateinit var dispatcher: TestDispatcher
+    private lateinit var testScope: TestScope
+
+    private lateinit var underTestDependencies: KeyguardDismissInteractorFactory.WithDependencies
+    private lateinit var underTest: KeyguardDismissInteractor
+    private val userInfo = UserInfo(0, "", 0)
+
+    @Before
+    fun setUp() {
+        MockitoAnnotations.initMocks(this)
+        dispatcher = StandardTestDispatcher()
+        testScope = TestScope(dispatcher)
+
+        underTestDependencies =
+            KeyguardDismissInteractorFactory.create(
+                context = context,
+                testScope = testScope,
+                broadcastDispatcher = fakeBroadcastDispatcher,
+                dispatcher = dispatcher,
+            )
+        underTest = underTestDependencies.interactor
+        underTestDependencies.userRepository.setUserInfos(listOf(userInfo))
+    }
+
+    @Test
+    fun biometricAuthenticatedRequestDismissKeyguard_noDismissAction() =
+        testScope.runTest {
+            val dismissKeyguardRequestWithoutImmediateDismissAction by
+                collectLastValue(underTest.dismissKeyguardRequestWithoutImmediateDismissAction)
+
+            underTestDependencies.bouncerRepository.setKeyguardAuthenticatedBiometrics(null)
+            assertThat(dismissKeyguardRequestWithoutImmediateDismissAction).isNull()
+
+            underTestDependencies.bouncerRepository.setKeyguardAuthenticatedBiometrics(true)
+            assertThat(dismissKeyguardRequestWithoutImmediateDismissAction).isEqualTo(Unit)
+        }
+
+    @Test
+    fun onTrustGrantedRequestDismissKeyguard_noDismissAction() =
+        testScope.runTest {
+            val dismissKeyguardRequestWithoutImmediateDismissAction by
+                collectLastValue(underTest.dismissKeyguardRequestWithoutImmediateDismissAction)
+            underTestDependencies.trustRepository.setRequestDismissKeyguard(
+                TrustModel(
+                    true,
+                    0,
+                    TrustGrantFlags(0),
+                )
+            )
+            assertThat(dismissKeyguardRequestWithoutImmediateDismissAction).isNull()
+
+            underTestDependencies.powerRepository.setInteractive(true)
+            underTestDependencies.trustRepository.setRequestDismissKeyguard(
+                TrustModel(
+                    true,
+                    0,
+                    TrustGrantFlags(TrustAgentService.FLAG_GRANT_TRUST_DISMISS_KEYGUARD),
+                )
+            )
+            assertThat(dismissKeyguardRequestWithoutImmediateDismissAction).isEqualTo(Unit)
+        }
+
+    @Test
+    fun primaryAuthenticated_noDismissAction() =
+        testScope.runTest {
+            val dismissKeyguardRequestWithoutImmediateDismissAction by
+                collectLastValue(underTest.dismissKeyguardRequestWithoutImmediateDismissAction)
+            underTestDependencies.userRepository.setSelectedUserInfo(userInfo)
+            runCurrent()
+
+            // authenticated different user
+            underTestDependencies.bouncerRepository.setKeyguardAuthenticatedPrimaryAuth(22)
+            assertThat(dismissKeyguardRequestWithoutImmediateDismissAction).isNull()
+
+            // authenticated correct user
+            underTestDependencies.bouncerRepository.setKeyguardAuthenticatedPrimaryAuth(userInfo.id)
+            assertThat(dismissKeyguardRequestWithoutImmediateDismissAction).isEqualTo(Unit)
+        }
+
+    @Test
+    fun userRequestedBouncerWhenAlreadyAuthenticated_noDismissAction() =
+        testScope.runTest {
+            val dismissKeyguardRequestWithoutImmediateDismissAction by
+                collectLastValue(underTest.dismissKeyguardRequestWithoutImmediateDismissAction)
+            underTestDependencies.userRepository.setSelectedUserInfo(userInfo)
+            runCurrent()
+
+            // requested from different user
+            underTestDependencies.bouncerRepository.setUserRequestedBouncerWhenAlreadyAuthenticated(
+                22
+            )
+            assertThat(dismissKeyguardRequestWithoutImmediateDismissAction).isNull()
+
+            // requested from correct user
+            underTestDependencies.bouncerRepository.setUserRequestedBouncerWhenAlreadyAuthenticated(
+                userInfo.id
+            )
+            assertThat(dismissKeyguardRequestWithoutImmediateDismissAction).isEqualTo(Unit)
+        }
+
+    @Test
+    fun keyguardDone() =
+        testScope.runTest {
+            val keyguardDone by collectLastValue(underTest.keyguardDone)
+            assertThat(keyguardDone).isNull()
+
+            underTest.setKeyguardDone(KeyguardDone.IMMEDIATE)
+            assertThat(keyguardDone).isEqualTo(KeyguardDone.IMMEDIATE)
+
+            underTest.setKeyguardDone(KeyguardDone.LATER)
+            assertThat(keyguardDone).isEqualTo(KeyguardDone.LATER)
+        }
+
+    @Test
+    fun userRequestedBouncerWhenAlreadyAuthenticated_dismissActionRunImmediately() =
+        testScope.runTest {
+            val dismissKeyguardRequestWithoutImmediateDismissAction by
+                collectLastValue(underTest.dismissKeyguardRequestWithoutImmediateDismissAction)
+            val dismissKeyguardRequestWithImmediateDismissAction by
+                collectLastValue(underTest.dismissKeyguardRequestWithImmediateDismissAction)
+            underTestDependencies.userRepository.setSelectedUserInfo(userInfo)
+            runCurrent()
+
+            underTestDependencies.keyguardRepository.setDismissAction(
+                DismissAction.RunImmediately(
+                    onDismissAction = { KeyguardDone.IMMEDIATE },
+                    onCancelAction = {},
+                    message = "",
+                    willAnimateOnLockscreen = true,
+                )
+            )
+            underTestDependencies.bouncerRepository.setUserRequestedBouncerWhenAlreadyAuthenticated(
+                userInfo.id
+            )
+            assertThat(dismissKeyguardRequestWithoutImmediateDismissAction).isNull()
+            assertThat(dismissKeyguardRequestWithImmediateDismissAction).isEqualTo(Unit)
+        }
+
+    @Test
+    fun userRequestedBouncerWhenAlreadyAuthenticated_dismissActionRunAfterKeyguardGone() =
+        testScope.runTest {
+            val dismissKeyguardRequestWithImmediateWithoutDismissAction by
+                collectLastValue(underTest.dismissKeyguardRequestWithoutImmediateDismissAction)
+            val dismissKeyguardRequestWithImmediateDismissAction by
+                collectLastValue(underTest.dismissKeyguardRequestWithImmediateDismissAction)
+            underTestDependencies.userRepository.setSelectedUserInfo(userInfo)
+            runCurrent()
+
+            underTestDependencies.keyguardRepository.setDismissAction(
+                DismissAction.RunAfterKeyguardGone(
+                    dismissAction = {},
+                    onCancelAction = {},
+                    message = "",
+                    willAnimateOnLockscreen = true,
+                )
+            )
+            underTestDependencies.bouncerRepository.setUserRequestedBouncerWhenAlreadyAuthenticated(
+                userInfo.id
+            )
+            assertThat(dismissKeyguardRequestWithImmediateDismissAction).isNull()
+            assertThat(dismissKeyguardRequestWithImmediateWithoutDismissAction).isEqualTo(Unit)
+        }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/OccludingAppDeviceEntryInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/OccludingAppDeviceEntryInteractorTest.kt
index bdcb9ab..b81a3d4 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/OccludingAppDeviceEntryInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/OccludingAppDeviceEntryInteractorTest.kt
@@ -159,7 +159,6 @@
                     screenOffAnimationController = mock(),
                     statusBarStateController = mock(),
                 ),
-                FakeFeatureFlags().apply { set(Flags.FP_LISTEN_OCCLUDING_APPS, true) },
             )
     }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToGoneTransitionViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToGoneTransitionViewModelTest.kt
index 904662e..da372ea 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToGoneTransitionViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/PrimaryBouncerToGoneTransitionViewModelTest.kt
@@ -22,7 +22,10 @@
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerInteractor
 import com.android.systemui.coroutines.collectValues
+import com.android.systemui.flags.FakeFeatureFlags
+import com.android.systemui.flags.Flags
 import com.android.systemui.keyguard.data.repository.FakeKeyguardTransitionRepository
+import com.android.systemui.keyguard.domain.interactor.KeyguardDismissActionInteractor
 import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractorFactory
 import com.android.systemui.keyguard.shared.model.KeyguardState
 import com.android.systemui.keyguard.shared.model.ScrimAlpha
@@ -32,6 +35,7 @@
 import com.android.systemui.util.mockito.whenever
 import com.google.common.collect.Range
 import com.google.common.truth.Truth.assertThat
+import dagger.Lazy
 import kotlinx.coroutines.test.TestScope
 import kotlinx.coroutines.test.UnconfinedTestDispatcher
 import kotlinx.coroutines.test.runTest
@@ -47,13 +51,21 @@
 class PrimaryBouncerToGoneTransitionViewModelTest : SysuiTestCase() {
     private lateinit var underTest: PrimaryBouncerToGoneTransitionViewModel
     private lateinit var repository: FakeKeyguardTransitionRepository
+    private lateinit var featureFlags: FakeFeatureFlags
     @Mock private lateinit var statusBarStateController: SysuiStatusBarStateController
     @Mock private lateinit var primaryBouncerInteractor: PrimaryBouncerInteractor
+    @Mock
+    private lateinit var keyguardDismissActionInteractor: Lazy<KeyguardDismissActionInteractor>
 
     @Before
     fun setUp() {
         MockitoAnnotations.initMocks(this)
         repository = FakeKeyguardTransitionRepository()
+        val featureFlags =
+            FakeFeatureFlags().apply {
+                set(Flags.REFACTOR_KEYGUARD_DISMISS_INTENT, false)
+                set(Flags.UDFPS_NEW_TOUCH_DETECTION, true)
+            }
         val interactor =
             KeyguardTransitionInteractorFactory.create(
                     scope = TestScope().backgroundScope,
@@ -64,7 +76,9 @@
             PrimaryBouncerToGoneTransitionViewModel(
                 interactor,
                 statusBarStateController,
-                primaryBouncerInteractor
+                primaryBouncerInteractor,
+                keyguardDismissActionInteractor,
+                featureFlags,
             )
 
         whenever(primaryBouncerInteractor.willRunDismissFromKeyguard()).thenReturn(false)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarTest.java b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarTest.java
index cbfad56..48a36cb 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/navigationbar/NavigationBarTest.java
@@ -30,6 +30,8 @@
 import static com.android.systemui.navigationbar.NavigationBar.NavBarActionEvent.NAVBAR_ASSIST_LONGPRESS;
 import static com.android.systemui.shared.system.QuickStepContract.SYSUI_STATE_SCREEN_PINNING;
 
+import static com.google.common.truth.Truth.assertThat;
+
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
@@ -93,7 +95,6 @@
 import com.android.systemui.settings.UserContextProvider;
 import com.android.systemui.settings.UserTracker;
 import com.android.systemui.shade.NotificationShadeWindowView;
-import com.android.systemui.shade.ShadeController;
 import com.android.systemui.shade.ShadeViewController;
 import com.android.systemui.shared.rotation.RotationButtonController;
 import com.android.systemui.shared.system.TaskStackChangeListeners;
@@ -330,6 +331,58 @@
     }
 
     @Test
+    public void onHomeTouch_isRinging_keyguardShowing_touchBlocked() {
+        when(mTelecomManager.isRinging()).thenReturn(true);
+        when(mKeyguardStateController.isShowing()).thenReturn(true);
+
+        boolean result = mNavigationBar.onHomeTouch(
+                mNavigationBar.getView(),
+                    MotionEvent.obtain(
+                    /*downTime=*/SystemClock.uptimeMillis(),
+                    /*eventTime=*/SystemClock.uptimeMillis(),
+                    /*action=*/MotionEvent.ACTION_DOWN,
+                    0, 0, 0));
+
+        assertThat(result).isTrue();
+
+        // Verify subsequent touches are also blocked
+        boolean nextTouchEvent = mNavigationBar.onHomeTouch(
+                mNavigationBar.getView(),
+                MotionEvent.obtain(
+                        /*downTime=*/SystemClock.uptimeMillis(),
+                        /*eventTime=*/SystemClock.uptimeMillis(),
+                        /*action=*/MotionEvent.ACTION_MOVE,
+                        0, 0, 0));
+        assertThat(nextTouchEvent).isTrue();
+    }
+
+    @Test
+    public void onHomeTouch_isRinging_keyguardNotShowing_touchNotBlocked() {
+        when(mTelecomManager.isRinging()).thenReturn(true);
+        when(mKeyguardStateController.isShowing()).thenReturn(false);
+
+        boolean result = mNavigationBar.onHomeTouch(
+                mNavigationBar.getView(),
+                MotionEvent.obtain(
+                        /*downTime=*/SystemClock.uptimeMillis(),
+                        /*eventTime=*/SystemClock.uptimeMillis(),
+                        /*action=*/MotionEvent.ACTION_DOWN,
+                        0, 0, 0));
+
+        assertThat(result).isFalse();
+
+        // Verify subsequent touches are also not blocked
+        boolean nextTouchEvent = mNavigationBar.onHomeTouch(
+                mNavigationBar.getView(),
+                MotionEvent.obtain(
+                        /*downTime=*/SystemClock.uptimeMillis(),
+                        /*eventTime=*/SystemClock.uptimeMillis(),
+                        /*action=*/MotionEvent.ACTION_MOVE,
+                        0, 0, 0));
+        assertThat(nextTouchEvent).isFalse();
+    }
+
+    @Test
     public void testRegisteredWithUserTracker() {
         mNavigationBar.init();
         mNavigationBar.onViewAttached();
@@ -468,7 +521,6 @@
         when(deviceProvisionedController.isDeviceProvisioned()).thenReturn(true);
         return spy(new NavigationBar(
                 mNavigationBarView,
-                mock(ShadeController.class),
                 mNavigationBarFrame,
                 null,
                 context,
@@ -487,6 +539,7 @@
                 Optional.of(mock(Pip.class)),
                 Optional.of(mock(Recents.class)),
                 () -> Optional.of(mCentralSurfaces),
+                mKeyguardStateController,
                 mock(ShadeViewController.class),
                 mock(NotificationRemoteInputManager.class),
                 mock(NotificationShadeDepthController.class),
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/data/repository/AutoAddSettingsRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/data/repository/AutoAddSettingsRepositoryTest.kt
index 77b3e69f..9386d71 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/data/repository/AutoAddSettingsRepositoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/data/repository/AutoAddSettingsRepositoryTest.kt
@@ -17,8 +17,9 @@
 package com.android.systemui.qs.pipeline.data.repository
 
 import android.provider.Settings
-import android.testing.AndroidTestingRunner
+import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
+import com.android.systemui.RoboPilotTest
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.coroutines.collectLastValue
 import com.android.systemui.qs.pipeline.shared.TileSpec
@@ -34,7 +35,8 @@
 
 @OptIn(ExperimentalCoroutinesApi::class)
 @SmallTest
-@RunWith(AndroidTestingRunner::class)
+@RoboPilotTest
+@RunWith(AndroidJUnit4::class)
 class AutoAddSettingsRepositoryTest : SysuiTestCase() {
     private val secureSettings = FakeSettings()
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/data/repository/CustomTileAddedSharedPreferencesRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/data/repository/CustomTileAddedSharedPreferencesRepositoryTest.kt
index d7ab903..30f5811 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/data/repository/CustomTileAddedSharedPreferencesRepositoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/data/repository/CustomTileAddedSharedPreferencesRepositoryTest.kt
@@ -18,8 +18,9 @@
 
 import android.content.ComponentName
 import android.content.SharedPreferences
-import android.testing.AndroidTestingRunner
+import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
+import com.android.systemui.RoboPilotTest
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.settings.UserFileManager
 import com.android.systemui.util.FakeSharedPreferences
@@ -29,7 +30,8 @@
 import org.junit.runner.RunWith
 
 @SmallTest
-@RunWith(AndroidTestingRunner::class)
+@RoboPilotTest
+@RunWith(AndroidJUnit4::class)
 class CustomTileAddedSharedPreferencesRepositoryTest : SysuiTestCase() {
 
     private lateinit var underTest: CustomTileAddedSharedPrefsRepository
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/data/repository/InstalledTilesComponentRepositoryImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/data/repository/InstalledTilesComponentRepositoryImplTest.kt
index dc0fae5..995de66 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/data/repository/InstalledTilesComponentRepositoryImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/data/repository/InstalledTilesComponentRepositoryImplTest.kt
@@ -29,8 +29,10 @@
 import android.content.pm.ServiceInfo
 import android.os.UserHandle
 import android.service.quicksettings.TileService
-import android.testing.AndroidTestingRunner
+import android.testing.TestableLooper
+import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
+import com.android.systemui.RoboPilotTest
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.coroutines.collectLastValue
 import com.android.systemui.util.mockito.any
@@ -60,7 +62,9 @@
 import org.mockito.MockitoAnnotations
 
 @SmallTest
-@RunWith(AndroidTestingRunner::class)
+@RoboPilotTest
+@RunWith(AndroidJUnit4::class)
+@TestableLooper.RunWithLooper
 @OptIn(ExperimentalCoroutinesApi::class)
 class InstalledTilesComponentRepositoryImplTest : SysuiTestCase() {
     private val testDispatcher = StandardTestDispatcher()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/data/repository/TileSpecSettingsRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/data/repository/TileSpecSettingsRepositoryTest.kt
index 72c31b1..aef5faf 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/data/repository/TileSpecSettingsRepositoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/data/repository/TileSpecSettingsRepositoryTest.kt
@@ -17,9 +17,10 @@
 package com.android.systemui.qs.pipeline.data.repository
 
 import android.provider.Settings
-import android.testing.AndroidTestingRunner
+import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import com.android.systemui.R
+import com.android.systemui.RoboPilotTest
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.coroutines.collectLastValue
 import com.android.systemui.qs.QSHost
@@ -41,7 +42,8 @@
 import org.mockito.MockitoAnnotations
 
 @SmallTest
-@RunWith(AndroidTestingRunner::class)
+@RoboPilotTest
+@RunWith(AndroidJUnit4::class)
 @OptIn(ExperimentalCoroutinesApi::class)
 class TileSpecSettingsRepositoryTest : SysuiTestCase() {
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/domain/autoaddable/AutoAddableSettingListTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/domain/autoaddable/AutoAddableSettingListTest.kt
index 817ac61..6e579d4 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/domain/autoaddable/AutoAddableSettingListTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/domain/autoaddable/AutoAddableSettingListTest.kt
@@ -17,9 +17,10 @@
 package com.android.systemui.qs.pipeline.domain.autoaddable
 
 import android.content.ComponentName
-import android.testing.AndroidTestingRunner
+import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import com.android.systemui.R
+import com.android.systemui.RoboPilotTest
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.qs.pipeline.shared.TileSpec
 import com.android.systemui.util.mockito.mock
@@ -28,7 +29,8 @@
 import org.junit.runner.RunWith
 
 @SmallTest
-@RunWith(AndroidTestingRunner::class)
+@RoboPilotTest
+@RunWith(AndroidJUnit4::class)
 class AutoAddableSettingListTest : SysuiTestCase() {
 
     private val factory =
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/domain/autoaddable/AutoAddableSettingTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/domain/autoaddable/AutoAddableSettingTest.kt
index 36c3c9d..7c6dd24 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/domain/autoaddable/AutoAddableSettingTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/domain/autoaddable/AutoAddableSettingTest.kt
@@ -16,8 +16,9 @@
 
 package com.android.systemui.qs.pipeline.domain.autoaddable
 
-import android.testing.AndroidTestingRunner
+import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
+import com.android.systemui.RoboPilotTest
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.coroutines.collectLastValue
 import com.android.systemui.coroutines.collectValues
@@ -35,7 +36,8 @@
 
 @OptIn(ExperimentalCoroutinesApi::class)
 @SmallTest
-@RunWith(AndroidTestingRunner::class)
+@RoboPilotTest
+@RunWith(AndroidJUnit4::class)
 class AutoAddableSettingTest : SysuiTestCase() {
 
     private val testDispatcher = StandardTestDispatcher()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/domain/autoaddable/CallbackControllerAutoAddableTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/domain/autoaddable/CallbackControllerAutoAddableTest.kt
index afb43c7..469eee3 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/domain/autoaddable/CallbackControllerAutoAddableTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/domain/autoaddable/CallbackControllerAutoAddableTest.kt
@@ -16,8 +16,9 @@
 
 package com.android.systemui.qs.pipeline.domain.autoaddable
 
-import android.testing.AndroidTestingRunner
+import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
+import com.android.systemui.RoboPilotTest
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.coroutines.collectLastValue
 import com.android.systemui.qs.pipeline.domain.model.AutoAddSignal
@@ -35,7 +36,8 @@
 
 @OptIn(ExperimentalCoroutinesApi::class)
 @SmallTest
-@RunWith(AndroidTestingRunner::class)
+@RoboPilotTest
+@RunWith(AndroidJUnit4::class)
 class CallbackControllerAutoAddableTest : SysuiTestCase() {
 
     @Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/domain/autoaddable/CastAutoAddableTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/domain/autoaddable/CastAutoAddableTest.kt
index a357dad..b6eaa39 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/domain/autoaddable/CastAutoAddableTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/domain/autoaddable/CastAutoAddableTest.kt
@@ -16,8 +16,9 @@
 
 package com.android.systemui.qs.pipeline.domain.autoaddable
 
-import android.testing.AndroidTestingRunner
+import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
+import com.android.systemui.RoboPilotTest
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.coroutines.collectLastValue
 import com.android.systemui.qs.pipeline.domain.model.AutoAddSignal
@@ -41,7 +42,8 @@
 
 @OptIn(ExperimentalCoroutinesApi::class)
 @SmallTest
-@RunWith(AndroidTestingRunner::class)
+@RoboPilotTest
+@RunWith(AndroidJUnit4::class)
 class CastAutoAddableTest : SysuiTestCase() {
 
     @Mock private lateinit var castController: CastController
@@ -128,6 +130,6 @@
     }
 
     companion object {
-        private val SPEC = TileSpec.create(CastTile.TILE_SPEC)
+        private val SPEC by lazy { TileSpec.create(CastTile.TILE_SPEC) }
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/domain/autoaddable/DataSaverAutoAddableTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/domain/autoaddable/DataSaverAutoAddableTest.kt
index 098ffc3..a755fbb 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/domain/autoaddable/DataSaverAutoAddableTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/domain/autoaddable/DataSaverAutoAddableTest.kt
@@ -16,8 +16,9 @@
 
 package com.android.systemui.qs.pipeline.domain.autoaddable
 
-import android.testing.AndroidTestingRunner
+import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
+import com.android.systemui.RoboPilotTest
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.coroutines.collectLastValue
 import com.android.systemui.qs.pipeline.domain.model.AutoAddSignal
@@ -40,7 +41,8 @@
 
 @OptIn(ExperimentalCoroutinesApi::class)
 @SmallTest
-@RunWith(AndroidTestingRunner::class)
+@RoboPilotTest
+@RunWith(AndroidJUnit4::class)
 class DataSaverAutoAddableTest : SysuiTestCase() {
 
     @Mock private lateinit var dataSaverController: DataSaverController
@@ -80,6 +82,6 @@
     }
 
     companion object {
-        private val SPEC = TileSpec.create(DataSaverTile.TILE_SPEC)
+        private val SPEC by lazy { TileSpec.create(DataSaverTile.TILE_SPEC) }
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/domain/autoaddable/DeviceControlsAutoAddableTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/domain/autoaddable/DeviceControlsAutoAddableTest.kt
index a2e3538..daacca51 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/domain/autoaddable/DeviceControlsAutoAddableTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/domain/autoaddable/DeviceControlsAutoAddableTest.kt
@@ -16,8 +16,9 @@
 
 package com.android.systemui.qs.pipeline.domain.autoaddable
 
-import android.testing.AndroidTestingRunner
+import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
+import com.android.systemui.RoboPilotTest
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.coroutines.collectLastValue
 import com.android.systemui.qs.pipeline.domain.model.AutoAddSignal
@@ -43,7 +44,8 @@
 
 @OptIn(ExperimentalCoroutinesApi::class)
 @SmallTest
-@RunWith(AndroidTestingRunner::class)
+@RoboPilotTest
+@RunWith(AndroidJUnit4::class)
 class DeviceControlsAutoAddableTest : SysuiTestCase() {
 
     @Mock private lateinit var deviceControlsController: DeviceControlsController
@@ -110,6 +112,6 @@
     }
 
     companion object {
-        private val SPEC = TileSpec.create(DeviceControlsTile.TILE_SPEC)
+        private val SPEC by lazy { TileSpec.create(DeviceControlsTile.TILE_SPEC) }
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/domain/autoaddable/HotspotAutoAddableTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/domain/autoaddable/HotspotAutoAddableTest.kt
index ee96b47..4b5f7f6 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/domain/autoaddable/HotspotAutoAddableTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/domain/autoaddable/HotspotAutoAddableTest.kt
@@ -16,8 +16,9 @@
 
 package com.android.systemui.qs.pipeline.domain.autoaddable
 
-import android.testing.AndroidTestingRunner
+import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
+import com.android.systemui.RoboPilotTest
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.coroutines.collectLastValue
 import com.android.systemui.qs.pipeline.domain.model.AutoAddSignal
@@ -40,7 +41,8 @@
 
 @OptIn(ExperimentalCoroutinesApi::class)
 @SmallTest
-@RunWith(AndroidTestingRunner::class)
+@RoboPilotTest
+@RunWith(AndroidJUnit4::class)
 class HotspotAutoAddableTest : SysuiTestCase() {
 
     @Mock private lateinit var hotspotController: HotspotController
@@ -78,6 +80,6 @@
     }
 
     companion object {
-        private val SPEC = TileSpec.create(HotspotTile.TILE_SPEC)
+        private val SPEC by lazy { TileSpec.create(HotspotTile.TILE_SPEC) }
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/domain/autoaddable/NightDisplayAutoAddableTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/domain/autoaddable/NightDisplayAutoAddableTest.kt
index e03072a..32d9db2 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/domain/autoaddable/NightDisplayAutoAddableTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/domain/autoaddable/NightDisplayAutoAddableTest.kt
@@ -17,8 +17,9 @@
 package com.android.systemui.qs.pipeline.domain.autoaddable
 
 import android.hardware.display.NightDisplayListener
-import android.testing.AndroidTestingRunner
+import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
+import com.android.systemui.RoboPilotTest
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.coroutines.collectLastValue
 import com.android.systemui.dagger.NightDisplayListenerModule
@@ -49,7 +50,8 @@
 
 @OptIn(ExperimentalCoroutinesApi::class)
 @SmallTest
-@RunWith(AndroidTestingRunner::class)
+@RoboPilotTest
+@RunWith(AndroidJUnit4::class)
 class NightDisplayAutoAddableTest : SysuiTestCase() {
 
     @Mock(answer = Answers.RETURNS_SELF)
@@ -121,6 +123,6 @@
     }
 
     companion object {
-        private val SPEC = TileSpec.create(NightDisplayTile.TILE_SPEC)
+        private val SPEC by lazy { TileSpec.create(NightDisplayTile.TILE_SPEC) }
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/domain/autoaddable/ReduceBrightColorsAutoAddableTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/domain/autoaddable/ReduceBrightColorsAutoAddableTest.kt
index 7b4a55e..fb513a6 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/domain/autoaddable/ReduceBrightColorsAutoAddableTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/domain/autoaddable/ReduceBrightColorsAutoAddableTest.kt
@@ -16,8 +16,9 @@
 
 package com.android.systemui.qs.pipeline.domain.autoaddable
 
-import android.testing.AndroidTestingRunner
+import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
+import com.android.systemui.RoboPilotTest
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.coroutines.collectLastValue
 import com.android.systemui.qs.ReduceBrightColorsController
@@ -43,7 +44,8 @@
 
 @OptIn(ExperimentalCoroutinesApi::class)
 @SmallTest
-@RunWith(AndroidTestingRunner::class)
+@RoboPilotTest
+@RunWith(AndroidJUnit4::class)
 class ReduceBrightColorsAutoAddableTest : SysuiTestCase() {
 
     @Mock private lateinit var reduceBrightColorsController: ReduceBrightColorsController
@@ -103,6 +105,6 @@
     }
 
     companion object {
-        private val SPEC = TileSpec.create(ReduceBrightColorsTile.TILE_SPEC)
+        private val SPEC by lazy { TileSpec.create(ReduceBrightColorsTile.TILE_SPEC) }
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/domain/autoaddable/SafetyCenterAutoAddableTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/domain/autoaddable/SafetyCenterAutoAddableTest.kt
index fb35a3a..8036cb4 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/domain/autoaddable/SafetyCenterAutoAddableTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/domain/autoaddable/SafetyCenterAutoAddableTest.kt
@@ -18,9 +18,10 @@
 
 import android.content.ComponentName
 import android.content.pm.PackageManager
-import android.testing.AndroidTestingRunner
+import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import com.android.systemui.R
+import com.android.systemui.RoboPilotTest
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.coroutines.collectLastValue
 import com.android.systemui.coroutines.collectValues
@@ -51,7 +52,8 @@
 
 @OptIn(ExperimentalCoroutinesApi::class)
 @SmallTest
-@RunWith(AndroidTestingRunner::class)
+@RoboPilotTest
+@RunWith(AndroidJUnit4::class)
 class SafetyCenterAutoAddableTest : SysuiTestCase() {
     private val testDispatcher = StandardTestDispatcher()
     private val testScope = TestScope(testDispatcher)
@@ -155,9 +157,10 @@
     companion object {
         private const val SAFETY_TILE_CLASS_NAME = "cls"
         private const val PERMISSION_CONTROLLER_PACKAGE_NAME = "pkg"
-        private val SPEC =
+        private val SPEC by lazy {
             TileSpec.create(
                 ComponentName(PERMISSION_CONTROLLER_PACKAGE_NAME, SAFETY_TILE_CLASS_NAME)
             )
+        }
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/domain/autoaddable/WalletAutoAddableTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/domain/autoaddable/WalletAutoAddableTest.kt
index 6b250f4..1c8cb54 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/domain/autoaddable/WalletAutoAddableTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/domain/autoaddable/WalletAutoAddableTest.kt
@@ -16,8 +16,9 @@
 
 package com.android.systemui.qs.pipeline.domain.autoaddable
 
-import android.testing.AndroidTestingRunner
+import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
+import com.android.systemui.RoboPilotTest
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.coroutines.collectLastValue
 import com.android.systemui.qs.pipeline.domain.model.AutoAddSignal
@@ -37,7 +38,8 @@
 
 @OptIn(ExperimentalCoroutinesApi::class)
 @SmallTest
-@RunWith(AndroidTestingRunner::class)
+@RoboPilotTest
+@RunWith(AndroidJUnit4::class)
 class WalletAutoAddableTest : SysuiTestCase() {
 
     @Mock private lateinit var walletController: WalletController
@@ -76,6 +78,6 @@
     }
 
     companion object {
-        private val SPEC = TileSpec.create(QuickAccessWalletTile.TILE_SPEC)
+        private val SPEC by lazy { TileSpec.create(QuickAccessWalletTile.TILE_SPEC) }
     }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/domain/autoaddable/WorkTileAutoAddableTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/domain/autoaddable/WorkTileAutoAddableTest.kt
index e9f7c8ab..de1d29fd 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/domain/autoaddable/WorkTileAutoAddableTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/domain/autoaddable/WorkTileAutoAddableTest.kt
@@ -21,8 +21,9 @@
 import android.content.pm.UserInfo.FLAG_MANAGED_PROFILE
 import android.content.pm.UserInfo.FLAG_PRIMARY
 import android.content.pm.UserInfo.FLAG_PROFILE
-import android.testing.AndroidTestingRunner
+import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
+import com.android.systemui.RoboPilotTest
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.coroutines.collectLastValue
 import com.android.systemui.qs.pipeline.domain.model.AutoAddSignal
@@ -40,7 +41,8 @@
 
 @OptIn(ExperimentalCoroutinesApi::class)
 @SmallTest
-@RunWith(AndroidTestingRunner::class)
+@RoboPilotTest
+@RunWith(AndroidJUnit4::class)
 class WorkTileAutoAddableTest : SysuiTestCase() {
 
     private lateinit var userTracker: FakeUserTracker
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/domain/interactor/AutoAddInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/domain/interactor/AutoAddInteractorTest.kt
index f924b35..bb18115 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/domain/interactor/AutoAddInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/domain/interactor/AutoAddInteractorTest.kt
@@ -16,8 +16,9 @@
 
 package com.android.systemui.qs.pipeline.domain.interactor
 
-import android.testing.AndroidTestingRunner
+import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
+import com.android.systemui.RoboPilotTest
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.coroutines.collectLastValue
 import com.android.systemui.dump.DumpManager
@@ -46,7 +47,8 @@
 import org.mockito.MockitoAnnotations
 
 @SmallTest
-@RunWith(AndroidTestingRunner::class)
+@RoboPilotTest
+@RunWith(AndroidJUnit4::class)
 @OptIn(ExperimentalCoroutinesApi::class)
 class AutoAddInteractorTest : SysuiTestCase() {
     private val testScope = TestScope()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/domain/interactor/CurrentTilesInteractorImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/domain/interactor/CurrentTilesInteractorImplTest.kt
index 54a9360..dc1b9c4 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/domain/interactor/CurrentTilesInteractorImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/domain/interactor/CurrentTilesInteractorImplTest.kt
@@ -22,8 +22,9 @@
 import android.content.pm.UserInfo
 import android.os.UserHandle
 import android.service.quicksettings.Tile
-import android.testing.AndroidTestingRunner
+import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
+import com.android.systemui.RoboPilotTest
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.coroutines.collectLastValue
 import com.android.systemui.dump.nano.SystemUIProtoDump
@@ -69,7 +70,8 @@
 import org.mockito.MockitoAnnotations
 
 @SmallTest
-@RunWith(AndroidTestingRunner::class)
+@RoboPilotTest
+@RunWith(AndroidJUnit4::class)
 @OptIn(ExperimentalCoroutinesApi::class)
 class CurrentTilesInteractorImplTest : SysuiTestCase() {
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/domain/interactor/PanelInteractorImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/domain/interactor/PanelInteractorImplTest.kt
index 6556cfd..151b256 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/domain/interactor/PanelInteractorImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/domain/interactor/PanelInteractorImplTest.kt
@@ -15,8 +15,9 @@
  */
 package com.android.systemui.qs.pipeline.domain.interactor
 
-import android.testing.AndroidTestingRunner
+import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
+import com.android.systemui.RoboPilotTest
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.shade.ShadeController
 import org.junit.Before
@@ -26,7 +27,8 @@
 import org.mockito.Mockito.verify
 import org.mockito.MockitoAnnotations
 
-@RunWith(AndroidTestingRunner::class)
+@RoboPilotTest
+@RunWith(AndroidJUnit4::class)
 @SmallTest
 class PanelInteractorImplTest : SysuiTestCase() {
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/shared/TileSpecTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/shared/TileSpecTest.kt
index d880172..34c4c98 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/shared/TileSpecTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/pipeline/shared/TileSpecTest.kt
@@ -17,15 +17,17 @@
 package com.android.systemui.qs.pipeline.shared
 
 import android.content.ComponentName
-import android.testing.AndroidTestingRunner
+import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
+import com.android.systemui.RoboPilotTest
 import com.android.systemui.SysuiTestCase
 import com.google.common.truth.Truth.assertThat
 import org.junit.Test
 import org.junit.runner.RunWith
 
 @SmallTest
-@RunWith(AndroidTestingRunner::class)
+@RoboPilotTest
+@RunWith(AndroidJUnit4::class)
 class TileSpecTest : SysuiTestCase() {
 
     @Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/BluetoothTileTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/BluetoothTileTest.kt
index 5e7f68c..e5c55d8 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/BluetoothTileTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/BluetoothTileTest.kt
@@ -168,7 +168,7 @@
         val btDevice = mock<BluetoothDevice>()
         whenever(cachedDevice2.device).thenReturn(btDevice)
         whenever(btDevice.getMetadata(BluetoothDevice.METADATA_MAIN_BATTERY)).thenReturn(null)
-        whenever(cachedDevice2.batteryLevel).thenReturn(25)
+        whenever(cachedDevice2.minBatteryLevelWithMemberDevices).thenReturn(25)
         addConnectedDevice(cachedDevice2)
 
         tile.handleUpdateState(state, /* arg= */ null)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerBaseTest.java
index e42a7a6..46b636b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerBaseTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerBaseTest.java
@@ -34,7 +34,6 @@
 import com.android.internal.logging.UiEventLogger;
 import com.android.keyguard.KeyguardStatusView;
 import com.android.keyguard.KeyguardUpdateMonitor;
-import com.android.keyguard.TestScopeProvider;
 import com.android.systemui.R;
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.common.ui.data.repository.FakeConfigurationRepository;
@@ -48,6 +47,8 @@
 import com.android.systemui.plugins.FalsingManager;
 import com.android.systemui.plugins.qs.QS;
 import com.android.systemui.qs.QSFragment;
+import com.android.systemui.scene.SceneTestUtils;
+import com.android.systemui.scene.shared.flag.FakeSceneContainerFlags;
 import com.android.systemui.screenrecord.RecordingController;
 import com.android.systemui.shade.data.repository.FakeShadeRepository;
 import com.android.systemui.shade.domain.interactor.ShadeInteractor;
@@ -99,7 +100,8 @@
 
     protected QuickSettingsController mQsController;
 
-    protected TestScope mTestScope = TestScopeProvider.getTestScope();
+    protected SceneTestUtils mUtils = new SceneTestUtils(this);
+    protected TestScope mTestScope = mUtils.getTestScope();
 
     @Mock
     protected Resources mResources;
@@ -172,6 +174,8 @@
                 new ShadeInteractor(
                         mTestScope.getBackgroundScope(),
                         mDisableFlagsRepository,
+                        new FakeSceneContainerFlags(),
+                        () -> mUtils.sceneInteractor(),
                         mKeyguardRepository,
                         new FakeUserSetupRepository(),
                         mDeviceProvisionedController,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/data/repository/ShadeInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shade/data/repository/ShadeInteractorTest.kt
index ba5ecce..dd6ab73 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/data/repository/ShadeInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/data/repository/ShadeInteractorTest.kt
@@ -35,6 +35,10 @@
 import com.android.systemui.keyguard.domain.interactor.KeyguardInteractorFactory
 import com.android.systemui.keyguard.shared.model.StatusBarState
 import com.android.systemui.plugins.ActivityStarter
+import com.android.systemui.scene.SceneTestUtils
+import com.android.systemui.scene.shared.flag.FakeSceneContainerFlags
+import com.android.systemui.scene.shared.model.ObservableTransitionState
+import com.android.systemui.scene.shared.model.SceneKey
 import com.android.systemui.shade.domain.interactor.ShadeInteractor
 import com.android.systemui.statusbar.disableflags.data.model.DisableFlagsModel
 import com.android.systemui.statusbar.disableflags.data.repository.FakeDisableFlagsRepository
@@ -52,9 +56,8 @@
 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.runBlocking
-import kotlinx.coroutines.test.StandardTestDispatcher
-import kotlinx.coroutines.test.TestScope
 import kotlinx.coroutines.test.runCurrent
 import kotlinx.coroutines.test.runTest
 import org.junit.Before
@@ -67,9 +70,11 @@
 class ShadeInteractorTest : SysuiTestCase() {
     private lateinit var underTest: ShadeInteractor
 
-    private val testDispatcher = StandardTestDispatcher()
-    private val testScope = TestScope(testDispatcher)
+    private val utils = SceneTestUtils(this)
+    private val testScope = utils.testScope
     private val featureFlags = FakeFeatureFlags()
+    private val sceneContainerFlags = FakeSceneContainerFlags()
+    private val sceneInteractor = utils.sceneInteractor()
     private val userSetupRepository = FakeUserSetupRepository()
     private val userRepository = FakeUserRepository()
     private val disableFlagsRepository = FakeDisableFlagsRepository()
@@ -103,7 +108,7 @@
         val refreshUsersScheduler =
             RefreshUsersScheduler(
                 applicationScope = testScope.backgroundScope,
-                mainDispatcher = testDispatcher,
+                mainDispatcher = utils.testDispatcher,
                 repository = userRepository,
             )
 
@@ -141,7 +146,7 @@
                     ),
                 broadcastDispatcher = fakeBroadcastDispatcher,
                 keyguardUpdateMonitor = keyguardUpdateMonitor,
-                backgroundDispatcher = testDispatcher,
+                backgroundDispatcher = utils.testDispatcher,
                 activityManager = activityManager,
                 refreshUsersScheduler = refreshUsersScheduler,
                 guestUserInteractor = guestInteractor,
@@ -151,6 +156,8 @@
             ShadeInteractor(
                 testScope.backgroundScope,
                 disableFlagsRepository,
+                sceneContainerFlags,
+                { sceneInteractor },
                 keyguardRepository,
                 userSetupRepository,
                 deviceProvisionedController,
@@ -557,4 +564,146 @@
             // THEN anyExpanding is false
             assertThat(actual).isFalse()
         }
+
+    @Test
+    fun lockscreenShadeExpansion_idle_onScene() =
+        testScope.runTest() {
+            // GIVEN an expansion flow based on transitions to and from a scene
+            val key = SceneKey.Shade
+            val expansion = underTest.sceneBasedExpansion(sceneInteractor, key)
+            val expansionAmount by collectLastValue(expansion)
+
+            // WHEN transition state is idle on the scene
+            val transitionState =
+                MutableStateFlow<ObservableTransitionState>(ObservableTransitionState.Idle(key))
+            sceneInteractor.setTransitionState(transitionState)
+
+            // THEN expansion is 1
+            assertThat(expansionAmount).isEqualTo(1f)
+        }
+
+    @Test
+    fun lockscreenShadeExpansion_idle_onDifferentScene() =
+        testScope.runTest() {
+            // GIVEN an expansion flow based on transitions to and from a scene
+            val expansion = underTest.sceneBasedExpansion(sceneInteractor, SceneKey.Shade)
+            val expansionAmount by collectLastValue(expansion)
+
+            // WHEN transition state is idle on a different scene
+            val transitionState =
+                MutableStateFlow<ObservableTransitionState>(
+                    ObservableTransitionState.Idle(SceneKey.Lockscreen)
+                )
+            sceneInteractor.setTransitionState(transitionState)
+
+            // THEN expansion is 0
+            assertThat(expansionAmount).isEqualTo(0f)
+        }
+
+    @Test
+    fun lockscreenShadeExpansion_transitioning_toScene() =
+        testScope.runTest() {
+            // GIVEN an expansion flow based on transitions to and from a scene
+            val key = SceneKey.QuickSettings
+            val expansion = underTest.sceneBasedExpansion(sceneInteractor, key)
+            val expansionAmount by collectLastValue(expansion)
+
+            // WHEN transition state is starting to move to the scene
+            val progress = MutableStateFlow(0f)
+            val transitionState =
+                MutableStateFlow<ObservableTransitionState>(
+                    ObservableTransitionState.Transition(
+                        fromScene = SceneKey.Lockscreen,
+                        toScene = key,
+                        progress = progress,
+                    )
+                )
+            sceneInteractor.setTransitionState(transitionState)
+
+            // THEN expansion is 0
+            assertThat(expansionAmount).isEqualTo(0f)
+
+            // WHEN transition state is partially to the scene
+            progress.value = .4f
+
+            // THEN expansion matches the progress
+            assertThat(expansionAmount).isEqualTo(.4f)
+
+            // WHEN transition completes
+            progress.value = 1f
+
+            // THEN expansion is 1
+            assertThat(expansionAmount).isEqualTo(1f)
+        }
+
+    @Test
+    fun lockscreenShadeExpansion_transitioning_fromScene() =
+        testScope.runTest() {
+            // GIVEN an expansion flow based on transitions to and from a scene
+            val key = SceneKey.QuickSettings
+            val expansion = underTest.sceneBasedExpansion(sceneInteractor, key)
+            val expansionAmount by collectLastValue(expansion)
+
+            // WHEN transition state is starting to move to the scene
+            val progress = MutableStateFlow(0f)
+            val transitionState =
+                MutableStateFlow<ObservableTransitionState>(
+                    ObservableTransitionState.Transition(
+                        fromScene = key,
+                        toScene = SceneKey.Lockscreen,
+                        progress = progress,
+                    )
+                )
+            sceneInteractor.setTransitionState(transitionState)
+
+            // THEN expansion is 1
+            assertThat(expansionAmount).isEqualTo(1f)
+
+            // WHEN transition state is partially to the scene
+            progress.value = .4f
+
+            // THEN expansion reflects the progress
+            assertThat(expansionAmount).isEqualTo(.6f)
+
+            // WHEN transition completes
+            progress.value = 1f
+
+            // THEN expansion is 0
+            assertThat(expansionAmount).isEqualTo(0f)
+        }
+
+    @Test
+    fun lockscreenShadeExpansion_transitioning_toAndFromDifferentScenes() =
+        testScope.runTest() {
+            // GIVEN an expansion flow based on transitions to and from a scene
+            val expansion = underTest.sceneBasedExpansion(sceneInteractor, SceneKey.QuickSettings)
+            val expansionAmount by collectLastValue(expansion)
+
+            // WHEN transition state is starting to between different scenes
+            val progress = MutableStateFlow(0f)
+            val transitionState =
+                MutableStateFlow<ObservableTransitionState>(
+                    ObservableTransitionState.Transition(
+                        fromScene = SceneKey.Lockscreen,
+                        toScene = SceneKey.Shade,
+                        progress = progress,
+                    )
+                )
+            sceneInteractor.setTransitionState(transitionState)
+
+            // THEN expansion is 0
+            assertThat(expansionAmount).isEqualTo(0f)
+
+            // WHEN transition state is partially complete
+            progress.value = .4f
+
+            // THEN expansion is still 0
+            assertThat(expansionAmount).isEqualTo(0f)
+
+            // WHEN transition completes
+            progress.value = 1f
+
+            // THEN expansion is still 0
+            assertThat(expansionAmount).isEqualTo(0f)
+        }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/LockscreenShadeTransitionControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/LockscreenShadeTransitionControllerTest.kt
index 6f75880..2446234 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/LockscreenShadeTransitionControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/LockscreenShadeTransitionControllerTest.kt
@@ -20,6 +20,8 @@
 import com.android.systemui.plugins.qs.QS
 import com.android.systemui.power.data.repository.FakePowerRepository
 import com.android.systemui.power.domain.interactor.PowerInteractor
+import com.android.systemui.scene.SceneTestUtils
+import com.android.systemui.scene.shared.flag.FakeSceneContainerFlags
 import com.android.systemui.shade.ShadeViewController
 import com.android.systemui.shade.data.repository.FakeShadeRepository
 import com.android.systemui.shade.domain.interactor.ShadeInteractor
@@ -39,8 +41,6 @@
 import com.android.systemui.statusbar.policy.FakeConfigurationController
 import com.android.systemui.util.mockito.mock
 import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.test.StandardTestDispatcher
-import kotlinx.coroutines.test.TestScope
 import kotlinx.coroutines.test.runCurrent
 import org.junit.After
 import org.junit.Assert.assertFalse
@@ -74,8 +74,8 @@
 @RunWith(AndroidTestingRunner::class)
 @OptIn(ExperimentalCoroutinesApi::class)
 class LockscreenShadeTransitionControllerTest : SysuiTestCase() {
-
-    private val testScope = TestScope(StandardTestDispatcher())
+    private val utils = SceneTestUtils(this)
+    private val testScope = utils.testScope
 
     lateinit var transitionController: LockscreenShadeTransitionController
     lateinit var row: ExpandableNotificationRow
@@ -102,6 +102,8 @@
     @Mock lateinit var qsTransitionController: LockscreenShadeQsTransitionController
     @Mock lateinit var activityStarter: ActivityStarter
     @Mock lateinit var transitionControllerCallback: LockscreenShadeTransitionController.Callback
+    private val sceneContainerFlags = FakeSceneContainerFlags()
+    private val sceneInteractor = utils.sceneInteractor()
     private val disableFlagsRepository = FakeDisableFlagsRepository()
     private val keyguardRepository = FakeKeyguardRepository()
     private val configurationRepository = FakeConfigurationRepository()
@@ -113,6 +115,8 @@
         ShadeInteractor(
             testScope.backgroundScope,
             disableFlagsRepository,
+            sceneContainerFlags,
+            { sceneInteractor },
             keyguardRepository,
             userSetupRepository = FakeUserSetupRepository(),
             deviceProvisionedController = mock(),
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerBaseTest.java
index 2e5afa4..98315d0c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerBaseTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerBaseTest.java
@@ -18,6 +18,8 @@
 
 import static android.telephony.AccessNetworkConstants.TRANSPORT_TYPE_WWAN;
 import static android.telephony.NetworkRegistrationInfo.DOMAIN_PS;
+import static android.telephony.NetworkRegistrationInfo.REGISTRATION_STATE_DENIED;
+import static android.telephony.NetworkRegistrationInfo.REGISTRATION_STATE_HOME;
 
 import static junit.framework.Assert.assertEquals;
 import static junit.framework.Assert.assertNotNull;
@@ -439,11 +441,29 @@
 
     public void setVoiceRegState(int voiceRegState) {
         when(mServiceState.getState()).thenReturn(voiceRegState);
+        when(mServiceState.getVoiceRegState()).thenReturn(voiceRegState);
         updateServiceState();
     }
 
-    public void setDataRegState(int dataRegState) {
-        when(mServiceState.getDataRegistrationState()).thenReturn(dataRegState);
+    public void setDataRegInService(boolean inService) {
+        // mFakeRegInfo#isInService()
+        // Utils#isInService uses NetworkRegistrationInfo#isInService(). Since we can't
+        // mock the answer here, just set the bit based on what the caller wants
+        NetworkRegistrationInfo.Builder builder = new NetworkRegistrationInfo.Builder()
+                .setTransportType(TRANSPORT_TYPE_WWAN)
+                .setDomain(DOMAIN_PS)
+                .setAccessNetworkTechnology(TelephonyManager.NETWORK_TYPE_LTE);
+
+        if (inService) {
+            builder.setRegistrationState(REGISTRATION_STATE_HOME);
+        } else {
+            builder.setRegistrationState(REGISTRATION_STATE_DENIED);
+        }
+
+        NetworkRegistrationInfo fakeRegInfo = builder.build();
+        when(mServiceState.getNetworkRegistrationInfo(DOMAIN_PS, TRANSPORT_TYPE_WWAN))
+                .thenReturn(fakeRegInfo);
+
         updateServiceState();
     }
 
@@ -658,3 +678,4 @@
         assertEquals("Data network name", expected, mNetworkController.getMobileDataNetworkName());
     }
 }
+
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerDataTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerDataTest.java
index d5689dc..f667b83 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerDataTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/connectivity/NetworkControllerDataTest.java
@@ -340,7 +340,7 @@
     public void testIsDataInService_notInService_false() {
         setupDefaultSignal();
         setVoiceRegState(ServiceState.STATE_OUT_OF_SERVICE);
-        setDataRegState(ServiceState.STATE_OUT_OF_SERVICE);
+        setDataRegInService(false);
         assertFalse(mNetworkController.isMobileDataNetworkInService());
     }
 
@@ -408,3 +408,4 @@
                 false);
     }
 }
+
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotifInflationErrorManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotifInflationErrorManagerTest.kt
new file mode 100644
index 0000000..e38adeb
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/row/NotifInflationErrorManagerTest.kt
@@ -0,0 +1,97 @@
+/*
+ * 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.statusbar.notification.row
+
+import android.testing.AndroidTestingRunner
+import android.testing.TestableLooper
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.statusbar.notification.collection.NotificationEntry
+import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder
+import com.android.systemui.statusbar.notification.row.NotifInflationErrorManager.NotifInflationErrorListener
+import com.android.systemui.util.mockito.any
+import com.google.common.truth.Truth.assertThat
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mockito.mock
+import org.mockito.Mockito.never
+import org.mockito.Mockito.verify
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+@TestableLooper.RunWithLooper
+class NotifInflationErrorManagerTest : SysuiTestCase() {
+    private lateinit var manager: NotifInflationErrorManager
+
+    private val listener1 = mock(NotifInflationErrorListener::class.java)
+    private val listener2 = mock(NotifInflationErrorListener::class.java)
+
+    private val foo: NotificationEntry = NotificationEntryBuilder().setPkg("foo").build()
+    private val bar: NotificationEntry = NotificationEntryBuilder().setPkg("bar").build()
+    private val baz: NotificationEntry = NotificationEntryBuilder().setPkg("baz").build()
+
+    private val fooException = Exception("foo")
+    private val barException = Exception("bar")
+
+    @Before
+    fun setUp() {
+        // Reset manager instance before each test.
+        manager = NotifInflationErrorManager()
+    }
+
+    @Test
+    fun testTracksInflationErrors() {
+        manager.setInflationError(foo, fooException)
+        manager.setInflationError(bar, barException)
+
+        assertThat(manager.hasInflationError(foo)).isTrue()
+        assertThat(manager.hasInflationError(bar)).isTrue()
+        assertThat(manager.hasInflationError(baz)).isFalse()
+
+        manager.clearInflationError(bar)
+
+        assertThat(manager.hasInflationError(bar)).isFalse()
+    }
+
+    @Test
+    fun testNotifiesListeners() {
+        manager.addInflationErrorListener(listener1)
+        manager.setInflationError(foo, fooException)
+
+        verify(listener1).onNotifInflationError(foo, fooException)
+
+        manager.addInflationErrorListener(listener2)
+        manager.setInflationError(bar, barException)
+
+        verify(listener1).onNotifInflationError(bar, barException)
+        verify(listener2).onNotifInflationError(bar, barException)
+
+        manager.clearInflationError(foo)
+
+        verify(listener1).onNotifInflationErrorCleared(foo)
+        verify(listener2).onNotifInflationErrorCleared(foo)
+    }
+
+    @Test
+    fun testClearUnknownEntry() {
+        manager.addInflationErrorListener(listener1)
+        manager.clearInflationError(foo)
+
+        verify(listener1, never()).onNotifInflationErrorCleared(any())
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java
index 6f431be..79cf932 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutControllerTest.java
@@ -628,6 +628,16 @@
         verify(mNotificationStackScrollLayout).updateEmptyShadeView(eq(false), anyBoolean());
     }
 
+    @Test
+    public void updateEmptyShadeView_onKeyguardOccludedTransitionToAod_hidesView() {
+        initController(/* viewIsAttached= */ true);
+        mController.onKeyguardTransitionChanged(
+                new TransitionStep(
+                        /* from= */ KeyguardState.OCCLUDED,
+                        /* to= */ KeyguardState.AOD));
+        verify(mNotificationStackScrollLayout).updateEmptyShadeView(eq(false), anyBoolean());
+    }
+
     private LogMaker logMatcher(int category, int type) {
         return argThat(new LogMatcher(category, type));
     }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelTest.kt
index ff68eb7..521069f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModelTest.kt
@@ -33,6 +33,8 @@
 import com.android.systemui.keyguard.shared.model.StatusBarState
 import com.android.systemui.keyguard.shared.model.TransitionState
 import com.android.systemui.keyguard.shared.model.TransitionStep
+import com.android.systemui.scene.SceneTestUtils
+import com.android.systemui.scene.shared.flag.FakeSceneContainerFlags
 import com.android.systemui.shade.data.repository.FakeShadeRepository
 import com.android.systemui.shade.domain.interactor.ShadeInteractor
 import com.android.systemui.statusbar.disableflags.data.repository.FakeDisableFlagsRepository
@@ -46,8 +48,6 @@
 import com.android.systemui.util.mockito.mock
 import com.android.systemui.util.mockito.whenever
 import com.google.common.truth.Truth.assertThat
-import kotlinx.coroutines.test.StandardTestDispatcher
-import kotlinx.coroutines.test.TestScope
 import kotlinx.coroutines.test.runCurrent
 import kotlinx.coroutines.test.runTest
 import org.junit.Before
@@ -59,12 +59,16 @@
 @SmallTest
 @RunWith(AndroidJUnit4::class)
 class SharedNotificationContainerViewModelTest : SysuiTestCase() {
-    private val testScope = TestScope(StandardTestDispatcher())
+    private val utils = SceneTestUtils(this)
+
+    private val testScope = utils.testScope
 
     private val disableFlagsRepository = FakeDisableFlagsRepository()
     private val userSetupRepository = FakeUserSetupRepository()
     private val shadeRepository = FakeShadeRepository()
     private val keyguardRepository = FakeKeyguardRepository()
+    private val sceneContainerFlags = FakeSceneContainerFlags()
+    private val sceneInteractor = utils.sceneInteractor()
 
     private lateinit var configurationRepository: FakeConfigurationRepository
     private lateinit var sharedNotificationContainerInteractor:
@@ -107,6 +111,8 @@
             ShadeInteractor(
                 testScope.backgroundScope,
                 disableFlagsRepository,
+                sceneContainerFlags,
+                { sceneInteractor },
                 keyguardRepository,
                 userSetupRepository,
                 deviceProvisionedController,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/BiometricsUnlockControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/BiometricsUnlockControllerTest.java
index 5107ecc..20e732a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/BiometricsUnlockControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/BiometricsUnlockControllerTest.java
@@ -16,12 +16,9 @@
 
 package com.android.systemui.statusbar.phone;
 
-import static com.android.systemui.flags.Flags.FP_LISTEN_OCCLUDING_APPS;
 import static com.android.systemui.flags.Flags.ONE_WAY_HAPTICS_API_MIGRATION;
 import static com.android.systemui.statusbar.phone.BiometricUnlockController.MODE_WAKE_AND_UNLOCK;
-
 import static com.google.common.truth.Truth.assertThat;
-
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyBoolean;
 import static org.mockito.ArgumentMatchers.anyInt;
@@ -133,7 +130,6 @@
     public void setUp() {
         MockitoAnnotations.initMocks(this);
         mFeatureFlags = new FakeFeatureFlags();
-        mFeatureFlags.set(FP_LISTEN_OCCLUDING_APPS, false);
         mFeatureFlags.set(ONE_WAY_HAPTICS_API_MIGRATION, false);
         when(mKeyguardStateController.isShowing()).thenReturn(true);
         when(mUpdateMonitor.isDeviceInteractive()).thenReturn(true);
@@ -439,24 +435,6 @@
 
     @Test
     public void onFPFailureNoHaptics_notInteractive_showLockScreen() {
-        mFeatureFlags.set(FP_LISTEN_OCCLUDING_APPS, true);
-
-        // GIVEN no vibrator and device is not interactive
-        when(mVibratorHelper.hasVibrator()).thenReturn(false);
-        when(mUpdateMonitor.isDeviceInteractive()).thenReturn(false);
-        when(mUpdateMonitor.isDreaming()).thenReturn(false);
-
-        // WHEN FP fails
-        mBiometricUnlockController.onBiometricAuthFailed(BiometricSourceType.FINGERPRINT);
-
-        // THEN wakeup the device
-        verify(mPowerManager).wakeUp(anyLong(), anyInt(), anyString());
-    }
-
-    @Test
-    public void onFPFailureNoHaptics_notInteractive_showLockScreen_doNotListenOccludingApps() {
-        mFeatureFlags.set(FP_LISTEN_OCCLUDING_APPS, false);
-
         // GIVEN no vibrator and device is not interactive
         when(mVibratorHelper.hasVibrator()).thenReturn(false);
         when(mUpdateMonitor.isDeviceInteractive()).thenReturn(false);
@@ -471,8 +449,6 @@
 
     @Test
     public void onFPFailureNoHaptics_dreaming_showLockScreen() {
-        mFeatureFlags.set(FP_LISTEN_OCCLUDING_APPS, true);
-
         // GIVEN no vibrator and device is dreaming
         when(mVibratorHelper.hasVibrator()).thenReturn(false);
         when(mUpdateMonitor.isDeviceInteractive()).thenReturn(true);
@@ -486,22 +462,6 @@
     }
 
     @Test
-    public void onFPFailureNoHaptics_dreaming_showLockScreen_doNotListeOccludingApps() {
-        mFeatureFlags.set(FP_LISTEN_OCCLUDING_APPS, false);
-
-        // GIVEN no vibrator and device is dreaming
-        when(mVibratorHelper.hasVibrator()).thenReturn(false);
-        when(mUpdateMonitor.isDeviceInteractive()).thenReturn(true);
-        when(mUpdateMonitor.isDreaming()).thenReturn(true);
-
-        // WHEN FP fails
-        mBiometricUnlockController.onBiometricAuthFailed(BiometricSourceType.FINGERPRINT);
-
-        // THEN wakeup the device
-        verify(mPowerManager).wakeUp(anyLong(), anyInt(), anyString());
-    }
-
-    @Test
     public void onSideFingerprintSuccess_recentPowerButtonPress_noHaptic() {
         // GIVEN side fingerprint enrolled, last wake reason was power button
         when(mAuthController.isSfpsEnrolled(anyInt())).thenReturn(true);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java
index 0da7360..798c3f9 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManagerTest.java
@@ -18,7 +18,6 @@
 
 import static com.android.systemui.bouncer.shared.constants.KeyguardBouncerConstants.EXPANSION_HIDDEN;
 import static com.android.systemui.bouncer.shared.constants.KeyguardBouncerConstants.EXPANSION_VISIBLE;
-
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
 import static org.mockito.ArgumentMatchers.any;
@@ -34,7 +33,6 @@
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
-
 import static kotlinx.coroutines.test.TestCoroutineDispatchersKt.StandardTestDispatcher;
 
 import android.service.trust.TrustAgentService;
@@ -73,8 +71,9 @@
 import com.android.systemui.bouncer.ui.BouncerViewDelegate;
 import com.android.systemui.dock.DockManager;
 import com.android.systemui.dreams.DreamOverlayStateController;
-import com.android.systemui.flags.FeatureFlags;
+import com.android.systemui.flags.FakeFeatureFlags;
 import com.android.systemui.flags.Flags;
+import com.android.systemui.keyguard.domain.interactor.KeyguardDismissActionInteractor;
 import com.android.systemui.keyguard.domain.interactor.KeyguardTransitionInteractor;
 import com.android.systemui.keyguard.domain.interactor.WindowManagerLockscreenVisibilityInteractor;
 import com.android.systemui.navigationbar.NavigationModeController;
@@ -131,7 +130,7 @@
     @Mock private SysUIUnfoldComponent mSysUiUnfoldComponent;
     @Mock private DreamOverlayStateController mDreamOverlayStateController;
     @Mock private LatencyTracker mLatencyTracker;
-    @Mock private FeatureFlags mFeatureFlags;
+    private FakeFeatureFlags mFeatureFlags;
     @Mock private KeyguardSecurityModel mKeyguardSecurityModel;
     @Mock private PrimaryBouncerCallbackInteractor mPrimaryBouncerCallbackInteractor;
     @Mock private PrimaryBouncerInteractor mPrimaryBouncerInteractor;
@@ -171,9 +170,11 @@
                 .thenReturn(mKeyguardMessageAreaController);
         when(mBouncerView.getDelegate()).thenReturn(mBouncerViewDelegate);
         when(mBouncerViewDelegate.getBackCallback()).thenReturn(mBouncerViewDelegateBackCallback);
-        when(mFeatureFlags
-                .isEnabled(Flags.WM_ENABLE_PREDICTIVE_BACK_BOUNCER_ANIM))
-                .thenReturn(true);
+        mFeatureFlags = new FakeFeatureFlags();
+        mFeatureFlags.set(Flags.WM_ENABLE_PREDICTIVE_BACK_BOUNCER_ANIM, true);
+        mFeatureFlags.set(Flags.REFACTOR_KEYGUARD_DISMISS_INTENT, false);
+        mFeatureFlags.set(Flags.UDFPS_NEW_TOUCH_DETECTION, true);
+        mFeatureFlags.set(Flags.KEYGUARD_WM_STATE_REFACTOR, false);
 
         when(mNotificationShadeWindowController.getWindowRootView())
                 .thenReturn(mNotificationShadeWindowView);
@@ -208,7 +209,8 @@
                         mActivityStarter,
                         mock(KeyguardTransitionInteractor.class),
                         StandardTestDispatcher(null, null),
-                        () -> mock(WindowManagerLockscreenVisibilityInteractor.class)) {
+                        () -> mock(WindowManagerLockscreenVisibilityInteractor.class),
+                        () -> mock(KeyguardDismissActionInteractor.class)) {
                     @Override
                     public ViewRootImpl getViewRootImpl() {
                         return mViewRootImpl;
@@ -711,7 +713,8 @@
                         mActivityStarter,
                         mock(KeyguardTransitionInteractor.class),
                         StandardTestDispatcher(null, null),
-                        () -> mock(WindowManagerLockscreenVisibilityInteractor.class)) {
+                        () -> mock(WindowManagerLockscreenVisibilityInteractor.class),
+                        () -> mock(KeyguardDismissActionInteractor.class)) {
                     @Override
                     public ViewRootImpl getViewRootImpl() {
                         return mViewRootImpl;
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenterTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenterTest.java
index 34c4ac1..233f407 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenterTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenterTest.java
@@ -15,7 +15,6 @@
 package com.android.systemui.statusbar.phone;
 
 import static android.view.Display.DEFAULT_DISPLAY;
-
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
 import static org.mockito.Mockito.mock;
@@ -54,8 +53,8 @@
 import com.android.systemui.statusbar.notification.collection.NotificationEntryBuilder;
 import com.android.systemui.statusbar.notification.collection.render.NotifShadeEventSource;
 import com.android.systemui.statusbar.notification.domain.interactor.NotificationsInteractor;
-import com.android.systemui.statusbar.notification.interruption.NotificationInterruptStateProvider;
 import com.android.systemui.statusbar.notification.interruption.NotificationInterruptSuppressor;
+import com.android.systemui.statusbar.notification.interruption.VisualInterruptionDecisionProvider;
 import com.android.systemui.statusbar.notification.row.NotificationGutsManager;
 import com.android.systemui.statusbar.notification.stack.NotificationListContainer;
 import com.android.systemui.statusbar.notification.stack.NotificationStackScrollLayout;
@@ -72,8 +71,8 @@
 @RunWithLooper()
 public class StatusBarNotificationPresenterTest extends SysuiTestCase {
     private StatusBarNotificationPresenter mStatusBarNotificationPresenter;
-    private final NotificationInterruptStateProvider mNotificationInterruptStateProvider =
-            mock(NotificationInterruptStateProvider.class);
+    private final VisualInterruptionDecisionProvider mVisualInterruptionDecisionProvider =
+            mock(VisualInterruptionDecisionProvider.class);
     private NotificationInterruptSuppressor mInterruptSuppressor;
     private CommandQueue mCommandQueue;
     private FakeMetricsLogger mMetricsLogger;
@@ -125,14 +124,14 @@
                 mock(NotificationMediaManager.class),
                 mock(NotificationGutsManager.class),
                 mInitController,
-                mNotificationInterruptStateProvider,
+                mVisualInterruptionDecisionProvider,
                 mock(NotificationRemoteInputManager.class),
                 mock(NotificationRemoteInputManager.Callback.class),
                 mock(NotificationListContainer.class));
         mInitController.executePostInitTasks();
         ArgumentCaptor<NotificationInterruptSuppressor> suppressorCaptor =
                 ArgumentCaptor.forClass(NotificationInterruptSuppressor.class);
-        verify(mNotificationInterruptStateProvider).addSuppressor(suppressorCaptor.capture());
+        verify(mVisualInterruptionDecisionProvider).addLegacySuppressor(suppressorCaptor.capture());
         mInterruptSuppressor = suppressorCaptor.getValue();
     }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryTest.kt
index 3af960b..8ef82c9 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/mobile/data/repository/prod/MobileConnectionRepositoryTest.kt
@@ -19,8 +19,13 @@
 import android.content.BroadcastReceiver
 import android.content.Context
 import android.content.Intent
+import android.telephony.AccessNetworkConstants.TRANSPORT_TYPE_WLAN
+import android.telephony.AccessNetworkConstants.TRANSPORT_TYPE_WWAN
 import android.telephony.CarrierConfigManager.KEY_INFLATE_SIGNAL_STRENGTH_BOOL
 import android.telephony.NetworkRegistrationInfo
+import android.telephony.NetworkRegistrationInfo.DOMAIN_PS
+import android.telephony.NetworkRegistrationInfo.REGISTRATION_STATE_DENIED
+import android.telephony.NetworkRegistrationInfo.REGISTRATION_STATE_HOME
 import android.telephony.ServiceState
 import android.telephony.ServiceState.STATE_IN_SERVICE
 import android.telephony.ServiceState.STATE_OUT_OF_SERVICE
@@ -80,7 +85,6 @@
 import com.android.systemui.statusbar.pipeline.shared.data.model.toMobileDataActivityModel
 import com.android.systemui.util.mockito.any
 import com.android.systemui.util.mockito.argumentCaptor
-import com.android.systemui.util.mockito.mock
 import com.android.systemui.util.mockito.whenever
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -801,11 +805,18 @@
             var latest: Boolean? = null
             val job = underTest.isInService.onEach { latest = it }.launchIn(this)
 
+            val nriInService =
+                NetworkRegistrationInfo.Builder()
+                    .setDomain(DOMAIN_PS)
+                    .setTransportType(TRANSPORT_TYPE_WWAN)
+                    .setRegistrationState(REGISTRATION_STATE_HOME)
+                    .build()
+
             getTelephonyCallbackForType<ServiceStateListener>()
                 .onServiceStateChanged(
                     ServiceState().also {
                         it.voiceRegState = STATE_IN_SERVICE
-                        it.dataRegState = STATE_IN_SERVICE
+                        it.addNetworkRegistrationInfo(nriInService)
                     }
                 )
 
@@ -814,17 +825,23 @@
             getTelephonyCallbackForType<ServiceStateListener>()
                 .onServiceStateChanged(
                     ServiceState().also {
-                        it.dataRegState = STATE_IN_SERVICE
                         it.voiceRegState = STATE_OUT_OF_SERVICE
+                        it.addNetworkRegistrationInfo(nriInService)
                     }
                 )
             assertThat(latest).isTrue()
 
+            val nriNotInService =
+                NetworkRegistrationInfo.Builder()
+                    .setDomain(DOMAIN_PS)
+                    .setTransportType(TRANSPORT_TYPE_WWAN)
+                    .setRegistrationState(REGISTRATION_STATE_DENIED)
+                    .build()
             getTelephonyCallbackForType<ServiceStateListener>()
                 .onServiceStateChanged(
                     ServiceState().also {
                         it.voiceRegState = STATE_OUT_OF_SERVICE
-                        it.dataRegState = STATE_OUT_OF_SERVICE
+                        it.addNetworkRegistrationInfo(nriNotInService)
                     }
                 )
             assertThat(latest).isFalse()
@@ -838,18 +855,17 @@
             var latest: Boolean? = null
             val job = underTest.isInService.onEach { latest = it }.launchIn(this)
 
-            // Mock the service state here so we can make it specifically IWLAN
-            val serviceState: ServiceState = mock()
-            whenever(serviceState.state).thenReturn(STATE_OUT_OF_SERVICE)
-            whenever(serviceState.dataRegistrationState).thenReturn(STATE_IN_SERVICE)
-
-            // See [com.android.settingslib.Utils.isInService] for more info. This is one way to
-            // make the network look like IWLAN
-            val networkRegWlan: NetworkRegistrationInfo = mock()
-            whenever(serviceState.getNetworkRegistrationInfo(any(), any()))
-                .thenReturn(networkRegWlan)
-            whenever(networkRegWlan.registrationState)
-                .thenReturn(NetworkRegistrationInfo.REGISTRATION_STATE_HOME)
+            val iwlanData =
+                NetworkRegistrationInfo.Builder()
+                    .setDomain(DOMAIN_PS)
+                    .setTransportType(TRANSPORT_TYPE_WLAN)
+                    .setRegistrationState(REGISTRATION_STATE_HOME)
+                    .build()
+            val serviceState =
+                ServiceState().also {
+                    it.voiceRegState = STATE_OUT_OF_SERVICE
+                    it.addNetworkRegistrationInfo(iwlanData)
+                }
 
             getTelephonyCallbackForType<ServiceStateListener>().onServiceStateChanged(serviceState)
             assertThat(latest).isFalse()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BluetoothControllerImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BluetoothControllerImplTest.java
index 243f881..e761635 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BluetoothControllerImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/policy/BluetoothControllerImplTest.java
@@ -46,8 +46,6 @@
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.bluetooth.BluetoothLogger;
 import com.android.systemui.dump.DumpManager;
-import com.android.systemui.flags.FakeFeatureFlags;
-import com.android.systemui.flags.Flags;
 import com.android.systemui.settings.UserTracker;
 import com.android.systemui.statusbar.policy.bluetooth.BluetoothRepository;
 import com.android.systemui.statusbar.policy.bluetooth.FakeBluetoothRepository;
@@ -75,8 +73,6 @@
     private DumpManager mMockDumpManager;
     private BluetoothControllerImpl mBluetoothControllerImpl;
     private BluetoothAdapter mMockAdapter;
-    private final FakeFeatureFlags mFakeFeatureFlags = new FakeFeatureFlags();
-
     private List<CachedBluetoothDevice> mDevices;
 
     @Before
@@ -98,11 +94,9 @@
 
         BluetoothRepository bluetoothRepository =
                 new FakeBluetoothRepository(mMockBluetoothManager);
-        mFakeFeatureFlags.set(Flags.NEW_BLUETOOTH_REPOSITORY, true);
 
         mBluetoothControllerImpl = new BluetoothControllerImpl(
                 mContext,
-                mFakeFeatureFlags,
                 mUserTracker,
                 mMockDumpManager,
                 mock(BluetoothLogger.class),
@@ -111,27 +105,8 @@
                 mMockBluetoothManager,
                 mMockAdapter);
     }
-
     @Test
-    public void testNoConnectionWithDevices_repoFlagOff() {
-        mFakeFeatureFlags.set(Flags.NEW_BLUETOOTH_REPOSITORY, false);
-
-        CachedBluetoothDevice device = mock(CachedBluetoothDevice.class);
-        when(device.isConnected()).thenReturn(true);
-        when(device.getMaxConnectionState()).thenReturn(BluetoothProfile.STATE_CONNECTED);
-        mDevices.add(device);
-        when(mMockLocalAdapter.getConnectionState())
-                .thenReturn(BluetoothAdapter.STATE_DISCONNECTED);
-
-        mBluetoothControllerImpl.onConnectionStateChanged(null,
-                BluetoothAdapter.STATE_DISCONNECTED);
-        assertTrue(mBluetoothControllerImpl.isBluetoothConnected());
-    }
-
-    @Test
-    public void testNoConnectionWithDevices_repoFlagOn() {
-        mFakeFeatureFlags.set(Flags.NEW_BLUETOOTH_REPOSITORY, true);
-
+    public void testNoConnectionWithDevices() {
         CachedBluetoothDevice device = mock(CachedBluetoothDevice.class);
         when(device.isConnected()).thenReturn(true);
         when(device.getMaxConnectionState()).thenReturn(BluetoothProfile.STATE_CONNECTED);
@@ -147,9 +122,7 @@
     }
 
     @Test
-    public void testOnServiceConnected_updatesConnectionState_repoFlagOff() {
-        mFakeFeatureFlags.set(Flags.NEW_BLUETOOTH_REPOSITORY, false);
-
+    public void testOnServiceConnected_updatesConnectionState() {
         when(mMockLocalAdapter.getConnectionState()).thenReturn(BluetoothAdapter.STATE_CONNECTING);
 
         mBluetoothControllerImpl.onServiceConnected();
@@ -159,41 +132,7 @@
     }
 
     @Test
-    public void testOnServiceConnected_updatesConnectionState_repoFlagOn() {
-        mFakeFeatureFlags.set(Flags.NEW_BLUETOOTH_REPOSITORY, true);
-
-        when(mMockLocalAdapter.getConnectionState()).thenReturn(BluetoothAdapter.STATE_CONNECTING);
-
-        mBluetoothControllerImpl.onServiceConnected();
-
-        assertTrue(mBluetoothControllerImpl.isBluetoothConnecting());
-        assertFalse(mBluetoothControllerImpl.isBluetoothConnected());
-    }
-
-    @Test
-    public void getConnectedDevices_onlyReturnsConnected_repoFlagOff() {
-        mFakeFeatureFlags.set(Flags.NEW_BLUETOOTH_REPOSITORY, false);
-
-        CachedBluetoothDevice device1Disconnected = mock(CachedBluetoothDevice.class);
-        when(device1Disconnected.isConnected()).thenReturn(false);
-        mDevices.add(device1Disconnected);
-
-        CachedBluetoothDevice device2Connected = mock(CachedBluetoothDevice.class);
-        when(device2Connected.isConnected()).thenReturn(true);
-        mDevices.add(device2Connected);
-
-        mBluetoothControllerImpl.onDeviceAdded(device1Disconnected);
-        mBluetoothControllerImpl.onDeviceAdded(device2Connected);
-
-        assertThat(mBluetoothControllerImpl.getConnectedDevices()).hasSize(1);
-        assertThat(mBluetoothControllerImpl.getConnectedDevices().get(0))
-                .isEqualTo(device2Connected);
-    }
-
-    @Test
-    public void getConnectedDevices_onlyReturnsConnected_repoFlagOn() {
-        mFakeFeatureFlags.set(Flags.NEW_BLUETOOTH_REPOSITORY, true);
-
+    public void getConnectedDevices_onlyReturnsConnected() {
         CachedBluetoothDevice device1Disconnected = mock(CachedBluetoothDevice.class);
         when(device1Disconnected.isConnected()).thenReturn(false);
         mDevices.add(device1Disconnected);
@@ -235,31 +174,7 @@
     }
 
     @Test
-    public void testOnACLConnectionStateChange_updatesBluetoothStateOnConnection_repoFlagOff() {
-        mFakeFeatureFlags.set(Flags.NEW_BLUETOOTH_REPOSITORY, false);
-
-        BluetoothController.Callback callback = mock(BluetoothController.Callback.class);
-        mBluetoothControllerImpl.addCallback(callback);
-
-        assertFalse(mBluetoothControllerImpl.isBluetoothConnected());
-        CachedBluetoothDevice device = mock(CachedBluetoothDevice.class);
-        mDevices.add(device);
-        when(device.isConnected()).thenReturn(true);
-        when(device.getMaxConnectionState()).thenReturn(BluetoothProfile.STATE_CONNECTED);
-        reset(callback);
-        mBluetoothControllerImpl.onAclConnectionStateChanged(device,
-                BluetoothProfile.STATE_CONNECTED);
-
-        mTestableLooper.processAllMessages();
-
-        assertTrue(mBluetoothControllerImpl.isBluetoothConnected());
-        verify(callback, atLeastOnce()).onBluetoothStateChange(anyBoolean());
-    }
-
-    @Test
-    public void testOnACLConnectionStateChange_updatesBluetoothStateOnConnection_repoFlagOn() {
-        mFakeFeatureFlags.set(Flags.NEW_BLUETOOTH_REPOSITORY, true);
-
+    public void testOnACLConnectionStateChange_updatesBluetoothStateOnConnection() {
         BluetoothController.Callback callback = mock(BluetoothController.Callback.class);
         mBluetoothControllerImpl.addCallback(callback);
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
index 1b623a3..7595a54 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
@@ -134,6 +134,7 @@
 import com.android.wm.shell.bubbles.Bubble;
 import com.android.wm.shell.bubbles.BubbleData;
 import com.android.wm.shell.bubbles.BubbleDataRepository;
+import com.android.wm.shell.bubbles.BubbleEducationController;
 import com.android.wm.shell.bubbles.BubbleEntry;
 import com.android.wm.shell.bubbles.BubbleLogger;
 import com.android.wm.shell.bubbles.BubbleOverflow;
@@ -277,6 +278,8 @@
     @Mock
     private BubbleLogger mBubbleLogger;
     @Mock
+    private BubbleEducationController mEducationController;
+    @Mock
     private TaskStackListenerImpl mTaskStackListener;
     @Mock
     private KeyguardStateController mKeyguardStateController;
@@ -369,7 +372,8 @@
 
         mPositioner = new TestableBubblePositioner(mContext, mWindowManager);
         mPositioner.setMaxBubbles(5);
-        mBubbleData = new BubbleData(mContext, mBubbleLogger, mPositioner, syncExecutor);
+        mBubbleData = new BubbleData(mContext, mBubbleLogger, mPositioner, mEducationController,
+                syncExecutor);
 
         when(mUserManager.getProfiles(ActivityManager.getCurrentUser())).thenReturn(
                 Collections.singletonList(mock(UserInfo.class)));
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/authentication/data/repository/FakeAuthenticationRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/authentication/data/repository/FakeAuthenticationRepository.kt
index 7c98df6..f2e4528 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/authentication/data/repository/FakeAuthenticationRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/authentication/data/repository/FakeAuthenticationRepository.kt
@@ -51,6 +51,8 @@
     override val authenticationMethod: StateFlow<AuthenticationMethodModel> =
         _authenticationMethod.asStateFlow()
 
+    override val minPatternLength: Int = 4
+
     private var isLockscreenEnabled = true
     private var failedAttemptCount = 0
     private var throttlingEndTimestamp = 0L
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/bouncer/data/repository/FakeKeyguardBouncerRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/bouncer/data/repository/FakeKeyguardBouncerRepository.kt
index 0847c85..b45c198 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/bouncer/data/repository/FakeKeyguardBouncerRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/bouncer/data/repository/FakeKeyguardBouncerRepository.kt
@@ -2,11 +2,14 @@
 
 import com.android.systemui.bouncer.shared.constants.KeyguardBouncerConstants
 import com.android.systemui.bouncer.shared.model.BouncerShowMessageModel
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableSharedFlow
 import kotlinx.coroutines.flow.MutableStateFlow
 import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.asSharedFlow
 import kotlinx.coroutines.flow.asStateFlow
 
-/** Fake implementation of [KeyguardRepository] */
+/** Fake implementation of [KeyguardBouncerRepository] */
 class FakeKeyguardBouncerRepository : KeyguardBouncerRepository {
     private val _primaryBouncerShow = MutableStateFlow(false)
     override val primaryBouncerShow = _primaryBouncerShow.asStateFlow()
@@ -26,7 +29,13 @@
     private val _isBackButtonEnabled = MutableStateFlow<Boolean?>(null)
     override val isBackButtonEnabled = _isBackButtonEnabled.asStateFlow()
     private val _keyguardAuthenticated = MutableStateFlow<Boolean?>(null)
-    override val keyguardAuthenticated = _keyguardAuthenticated.asStateFlow()
+    override val keyguardAuthenticatedBiometrics = _keyguardAuthenticated.asStateFlow()
+    private val _keyguardAuthenticatedPrimaryAuth = MutableSharedFlow<Int>()
+    override val keyguardAuthenticatedPrimaryAuth: Flow<Int> =
+        _keyguardAuthenticatedPrimaryAuth.asSharedFlow()
+    private val _userRequestedBouncerWhenAlreadyAuthenticated = MutableSharedFlow<Int>()
+    override val userRequestedBouncerWhenAlreadyAuthenticated: Flow<Int> =
+        _userRequestedBouncerWhenAlreadyAuthenticated.asSharedFlow()
     private val _showMessage = MutableStateFlow<BouncerShowMessageModel?>(null)
     override val showMessage = _showMessage.asStateFlow()
     private val _resourceUpdateRequests = MutableStateFlow(false)
@@ -83,10 +92,18 @@
         _showMessage.value = bouncerShowMessageModel
     }
 
-    override fun setKeyguardAuthenticated(keyguardAuthenticated: Boolean?) {
+    override fun setKeyguardAuthenticatedBiometrics(keyguardAuthenticated: Boolean?) {
         _keyguardAuthenticated.value = keyguardAuthenticated
     }
 
+    override suspend fun setKeyguardAuthenticatedPrimaryAuth(userId: Int) {
+        _keyguardAuthenticatedPrimaryAuth.emit(userId)
+    }
+
+    override suspend fun setUserRequestedBouncerWhenAlreadyAuthenticated(userId: Int) {
+        _userRequestedBouncerWhenAlreadyAuthenticated.emit(userId)
+    }
+
     override fun setIsBackButtonEnabled(isBackButtonEnabled: Boolean) {
         _isBackButtonEnabled.value = isBackButtonEnabled
     }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/display/data/repository/FakeDisplayRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/display/data/repository/FakeDisplayRepository.kt
index 2ac625d..5cd09d8 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/display/data/repository/FakeDisplayRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/display/data/repository/FakeDisplayRepository.kt
@@ -22,11 +22,19 @@
 import org.mockito.Mockito.`when` as whenever
 
 /** Creates a mock display. */
-fun display(type: Int, flags: Int = 0, id: Int = 0): Display {
+fun display(
+    type: Int,
+    flags: Int = 0,
+    id: Int = 0,
+    state: Int? = null,
+): Display {
     return mock {
         whenever(this.displayId).thenReturn(id)
         whenever(this.type).thenReturn(type)
         whenever(this.flags).thenReturn(flags)
+        if (state != null) {
+            whenever(this.state).thenReturn(state)
+        }
     }
 }
 
@@ -35,7 +43,7 @@
     mock<DisplayRepository.PendingDisplay> { whenever(this.id).thenReturn(id) }
 
 /** Fake [DisplayRepository] implementation for testing. */
-class FakeDisplayRepository : DisplayRepository {
+class FakeDisplayRepository() : DisplayRepository {
     private val flow = MutableSharedFlow<Set<Display>>()
     private val pendingDisplayFlow = MutableSharedFlow<DisplayRepository.PendingDisplay?>()
 
@@ -50,4 +58,8 @@
 
     override val pendingDisplay: Flow<DisplayRepository.PendingDisplay?>
         get() = pendingDisplayFlow
+
+    private val _displayChangeEvent = MutableSharedFlow<Int>()
+    override val displayChangeEvent: Flow<Int> = _displayChangeEvent
+    suspend fun emitDisplayChangeEvent(displayId: Int) = _displayChangeEvent.emit(displayId)
 }
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 cc0c943..dae8644 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
@@ -21,7 +21,9 @@
 import com.android.systemui.common.shared.model.Position
 import com.android.systemui.keyguard.shared.model.BiometricUnlockModel
 import com.android.systemui.keyguard.shared.model.BiometricUnlockSource
+import com.android.systemui.keyguard.shared.model.DismissAction
 import com.android.systemui.keyguard.shared.model.DozeTransitionModel
+import com.android.systemui.keyguard.shared.model.KeyguardDone
 import com.android.systemui.keyguard.shared.model.KeyguardRootViewVisibilityState
 import com.android.systemui.keyguard.shared.model.ScreenModel
 import com.android.systemui.keyguard.shared.model.ScreenState
@@ -30,12 +32,18 @@
 import com.android.systemui.keyguard.shared.model.WakefulnessModel
 import com.android.systemui.keyguard.shared.model.WakefulnessState
 import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableSharedFlow
 import kotlinx.coroutines.flow.MutableStateFlow
 import kotlinx.coroutines.flow.StateFlow
 import kotlinx.coroutines.flow.asStateFlow
 
 /** Fake implementation of [KeyguardRepository] */
 class FakeKeyguardRepository : KeyguardRepository {
+    private val _deferKeyguardDone: MutableSharedFlow<KeyguardDone> = MutableSharedFlow()
+    override val keyguardDone: Flow<KeyguardDone> = _deferKeyguardDone
+
+    private val _dismissAction = MutableStateFlow<DismissAction>(DismissAction.None)
+    override val dismissAction: StateFlow<DismissAction> = _dismissAction
 
     private val _animateBottomAreaDozingTransitions = MutableStateFlow(false)
     override val animateBottomAreaDozingTransitions: StateFlow<Boolean> =
@@ -175,6 +183,14 @@
         _dozeTimeTick.value = _dozeTimeTick.value + 1
     }
 
+    override fun setDismissAction(dismissAction: DismissAction) {
+        _dismissAction.value = dismissAction
+    }
+
+    override suspend fun setKeyguardDone(timing: KeyguardDone) {
+        _deferKeyguardDone.emit(timing)
+    }
+
     fun dozeTimeTick(millis: Long) {
         _dozeTimeTick.value = millis
     }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeTrustRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeTrustRepository.kt
index 817e1db..9d98f94 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeTrustRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeTrustRepository.kt
@@ -17,6 +17,8 @@
 
 package com.android.systemui.keyguard.data.repository
 
+import com.android.keyguard.TrustGrantFlags
+import com.android.systemui.keyguard.shared.model.TrustModel
 import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.MutableStateFlow
 import kotlinx.coroutines.flow.StateFlow
@@ -35,6 +37,9 @@
     override val isCurrentUserTrustManaged: StateFlow<Boolean>
         get() = _isCurrentUserTrustManaged
 
+    private val _requestDismissKeyguard = MutableStateFlow(TrustModel(false, 0, TrustGrantFlags(0)))
+    override val trustAgentRequestingToDismissKeyguard: Flow<TrustModel> = _requestDismissKeyguard
+
     fun setCurrentUserTrusted(trust: Boolean) {
         _isCurrentUserTrusted.value = trust
     }
@@ -46,4 +51,8 @@
     fun setCurrentUserActiveUnlockAvailable(available: Boolean) {
         _isCurrentUserActiveUnlockAvailable.value = available
     }
+
+    fun setRequestDismissKeyguard(trustModel: TrustModel) {
+        _requestDismissKeyguard.value = trustModel
+    }
 }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissInteractorFactory.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissInteractorFactory.kt
new file mode 100644
index 0000000..6dd41f4
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/domain/interactor/KeyguardDismissInteractorFactory.kt
@@ -0,0 +1,171 @@
+/*
+ * 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.app.ActivityManager
+import android.content.Context
+import android.os.Handler
+import android.os.UserManager
+import com.android.internal.logging.UiEventLogger
+import com.android.keyguard.KeyguardSecurityModel
+import com.android.keyguard.KeyguardUpdateMonitor
+import com.android.systemui.bouncer.data.repository.FakeKeyguardBouncerRepository
+import com.android.systemui.bouncer.domain.interactor.AlternateBouncerInteractor
+import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerCallbackInteractor
+import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerInteractor
+import com.android.systemui.bouncer.ui.BouncerView
+import com.android.systemui.broadcast.FakeBroadcastDispatcher
+import com.android.systemui.classifier.FalsingCollector
+import com.android.systemui.flags.FakeFeatureFlagsClassic
+import com.android.systemui.flags.Flags
+import com.android.systemui.keyguard.DismissCallbackRegistry
+import com.android.systemui.keyguard.data.repository.FakeBiometricSettingsRepository
+import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
+import com.android.systemui.keyguard.data.repository.FakeTrustRepository
+import com.android.systemui.plugins.ActivityStarter
+import com.android.systemui.plugins.statusbar.StatusBarStateController
+import com.android.systemui.power.data.repository.FakePowerRepository
+import com.android.systemui.power.domain.interactor.PowerInteractor
+import com.android.systemui.statusbar.phone.ScreenOffAnimationController
+import com.android.systemui.statusbar.policy.KeyguardStateController
+import com.android.systemui.telephony.data.repository.FakeTelephonyRepository
+import com.android.systemui.telephony.domain.interactor.TelephonyInteractor
+import com.android.systemui.user.data.repository.FakeUserRepository
+import com.android.systemui.user.domain.interactor.GuestUserInteractor
+import com.android.systemui.user.domain.interactor.HeadlessSystemUserMode
+import com.android.systemui.user.domain.interactor.RefreshUsersScheduler
+import com.android.systemui.user.domain.interactor.UserInteractor
+import com.android.systemui.util.time.FakeSystemClock
+import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.test.TestScope
+import org.mockito.Mockito.mock
+
+/**
+ * Helper to create a new KeyguardDismissInteractor in a way that doesn't require modifying many
+ * tests whenever we add a constructor param.
+ */
+object KeyguardDismissInteractorFactory {
+    @JvmOverloads
+    @JvmStatic
+    fun create(
+        context: Context,
+        testScope: TestScope,
+        broadcastDispatcher: FakeBroadcastDispatcher,
+        dispatcher: CoroutineDispatcher,
+        trustRepository: FakeTrustRepository = FakeTrustRepository(),
+        keyguardRepository: FakeKeyguardRepository = FakeKeyguardRepository(),
+        bouncerRepository: FakeKeyguardBouncerRepository = FakeKeyguardBouncerRepository(),
+        keyguardUpdateMonitor: KeyguardUpdateMonitor = mock(KeyguardUpdateMonitor::class.java),
+        featureFlags: FakeFeatureFlagsClassic =
+            FakeFeatureFlagsClassic().apply {
+                set(Flags.DELAY_BOUNCER, true)
+                set(Flags.REFACTOR_KEYGUARD_DISMISS_INTENT, true)
+                set(Flags.FULL_SCREEN_USER_SWITCHER, false)
+            },
+        powerRepository: FakePowerRepository = FakePowerRepository(),
+        userRepository: FakeUserRepository = FakeUserRepository(),
+    ): WithDependencies {
+        val primaryBouncerInteractor =
+            PrimaryBouncerInteractor(
+                bouncerRepository,
+                mock(BouncerView::class.java),
+                mock(Handler::class.java),
+                mock(KeyguardStateController::class.java),
+                mock(KeyguardSecurityModel::class.java),
+                mock(PrimaryBouncerCallbackInteractor::class.java),
+                mock(FalsingCollector::class.java),
+                mock(DismissCallbackRegistry::class.java),
+                context,
+                keyguardUpdateMonitor,
+                trustRepository,
+                featureFlags,
+                testScope.backgroundScope,
+            )
+        val alternateBouncerInteractor =
+            AlternateBouncerInteractor(
+                mock(StatusBarStateController::class.java),
+                mock(KeyguardStateController::class.java),
+                bouncerRepository,
+                FakeBiometricSettingsRepository(),
+                FakeSystemClock(),
+                keyguardUpdateMonitor,
+            )
+        val powerInteractor =
+            PowerInteractor(
+                powerRepository,
+                keyguardRepository,
+                mock(FalsingCollector::class.java),
+                mock(ScreenOffAnimationController::class.java),
+                mock(StatusBarStateController::class.java),
+            )
+        val userInteractor =
+            UserInteractor(
+                applicationContext = context,
+                repository = userRepository,
+                mock(ActivityStarter::class.java),
+                keyguardInteractor =
+                    KeyguardInteractorFactory.create(
+                            repository = keyguardRepository,
+                            bouncerRepository = bouncerRepository,
+                            featureFlags = featureFlags,
+                        )
+                        .keyguardInteractor,
+                featureFlags = featureFlags,
+                manager = mock(UserManager::class.java),
+                headlessSystemUserMode = mock(HeadlessSystemUserMode::class.java),
+                applicationScope = testScope.backgroundScope,
+                telephonyInteractor =
+                    TelephonyInteractor(
+                        repository = FakeTelephonyRepository(),
+                    ),
+                broadcastDispatcher = broadcastDispatcher,
+                keyguardUpdateMonitor = keyguardUpdateMonitor,
+                backgroundDispatcher = dispatcher,
+                activityManager = mock(ActivityManager::class.java),
+                refreshUsersScheduler = mock(RefreshUsersScheduler::class.java),
+                guestUserInteractor = mock(GuestUserInteractor::class.java),
+                uiEventLogger = mock(UiEventLogger::class.java),
+            )
+        return WithDependencies(
+            trustRepository = trustRepository,
+            keyguardRepository = keyguardRepository,
+            bouncerRepository = bouncerRepository,
+            keyguardUpdateMonitor = keyguardUpdateMonitor,
+            powerRepository = powerRepository,
+            userRepository = userRepository,
+            interactor =
+                KeyguardDismissInteractor(
+                    trustRepository,
+                    keyguardRepository,
+                    primaryBouncerInteractor,
+                    alternateBouncerInteractor,
+                    powerInteractor,
+                    userInteractor,
+                ),
+        )
+    }
+
+    data class WithDependencies(
+        val trustRepository: FakeTrustRepository,
+        val keyguardRepository: FakeKeyguardRepository,
+        val bouncerRepository: FakeKeyguardBouncerRepository,
+        val keyguardUpdateMonitor: KeyguardUpdateMonitor,
+        val powerRepository: FakePowerRepository,
+        val userRepository: FakeUserRepository,
+        val interactor: KeyguardDismissInteractor,
+    )
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/pipeline/data/repository/FakeTileSpecRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/pipeline/data/repository/FakeTileSpecRepository.kt
index 2865710..aa8dbe1 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/pipeline/data/repository/FakeTileSpecRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/pipeline/data/repository/FakeTileSpecRepository.kt
@@ -16,7 +16,6 @@
 
 package com.android.systemui.qs.pipeline.data.repository
 
-import android.util.Log
 import com.android.systemui.qs.pipeline.data.repository.TileSpecRepository.Companion.POSITION_AT_END
 import com.android.systemui.qs.pipeline.shared.TileSpec
 import kotlinx.coroutines.flow.Flow
@@ -28,7 +27,7 @@
     private val tilesPerUser = mutableMapOf<Int, MutableStateFlow<List<TileSpec>>>()
 
     override fun tilesSpecs(userId: Int): Flow<List<TileSpec>> {
-        return getFlow(userId).asStateFlow().also { Log.d("Fabian", "Retrieving flow for $userId") }
+        return getFlow(userId).asStateFlow()
     }
 
     override suspend fun addTile(userId: Int, tile: TileSpec, position: Int) {
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/scene/SceneTestUtils.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/scene/SceneTestUtils.kt
index 9dea0a0..2d79e0f 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/scene/SceneTestUtils.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/scene/SceneTestUtils.kt
@@ -127,6 +127,7 @@
         )
     }
 
+    @JvmOverloads
     fun sceneInteractor(
         repository: SceneContainerRepository = fakeSceneContainerRepository()
     ): SceneInteractor {
diff --git a/services/autofill/java/com/android/server/autofill/ui/SaveUi.java b/services/autofill/java/com/android/server/autofill/ui/SaveUi.java
index 70382f1..4488d86 100644
--- a/services/autofill/java/com/android/server/autofill/ui/SaveUi.java
+++ b/services/autofill/java/com/android/server/autofill/ui/SaveUi.java
@@ -443,7 +443,8 @@
                     }
                     final BatchUpdates batchUpdates = pair.second;
                     // First apply the updates...
-                    final RemoteViews templateUpdates = batchUpdates.getUpdates();
+                    final RemoteViews templateUpdates =
+                            Helper.sanitizeRemoteView(batchUpdates.getUpdates());
                     if (templateUpdates != null) {
                         if (sDebug) Slog.d(TAG, "Applying template updates for batch update #" + i);
                         templateUpdates.reapply(context, customSubtitleView);
diff --git a/services/backup/java/com/android/server/backup/TransportManager.java b/services/backup/java/com/android/server/backup/TransportManager.java
index 05327dc..a792db0 100644
--- a/services/backup/java/com/android/server/backup/TransportManager.java
+++ b/services/backup/java/com/android/server/backup/TransportManager.java
@@ -738,6 +738,9 @@
         try {
             String transportName = transport.name();
             String transportDirName = transport.transportDirName();
+            if (transportName == null || transportDirName == null) {
+                return BackupManager.ERROR_TRANSPORT_INVALID;
+            }
             registerTransport(transportComponent, transport);
             // If registerTransport() hasn't thrown...
             Slog.d(TAG, addUserIdToLogMessage(mUserId, "Transport " + transportString
diff --git a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
index 1ce7d96..330e35b 100644
--- a/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
+++ b/services/companion/java/com/android/server/companion/CompanionDeviceManagerService.java
@@ -74,6 +74,7 @@
 import android.companion.IOnMessageReceivedListener;
 import android.companion.IOnTransportsChangedListener;
 import android.companion.ISystemDataTransferCallback;
+import android.companion.datatransfer.PermissionSyncRequest;
 import android.companion.utils.FeatureUtils;
 import android.content.ComponentName;
 import android.content.Context;
@@ -873,6 +874,24 @@
         }
 
         @Override
+        public void enablePermissionsSync(int associationId) {
+            getAssociationWithCallerChecks(associationId);
+            mSystemDataTransferProcessor.enablePermissionsSync(associationId);
+        }
+
+        @Override
+        public void disablePermissionsSync(int associationId) {
+            getAssociationWithCallerChecks(associationId);
+            mSystemDataTransferProcessor.disablePermissionsSync(associationId);
+        }
+
+        @Override
+        public PermissionSyncRequest getPermissionSyncRequest(int associationId) {
+            getAssociationWithCallerChecks(associationId);
+            return mSystemDataTransferProcessor.getPermissionSyncRequest(associationId);
+        }
+
+        @Override
         @EnforcePermission(MANAGE_COMPANION_DEVICES)
         public void enableSecureTransport(boolean enabled) {
             enableSecureTransport_enforcePermission();
@@ -1041,7 +1060,7 @@
                 String[] args, ShellCallback callback, ResultReceiver resultReceiver)
                 throws RemoteException {
             new CompanionDeviceShellCommand(CompanionDeviceManagerService.this, mAssociationStore,
-                    mDevicePresenceMonitor, mTransportManager, mSystemDataTransferRequestStore,
+                    mDevicePresenceMonitor, mTransportManager, mSystemDataTransferProcessor,
                     mAssociationRequestsProcessor)
                     .exec(this, in, out, err, args, callback, resultReceiver);
         }
diff --git a/services/companion/java/com/android/server/companion/CompanionDeviceShellCommand.java b/services/companion/java/com/android/server/companion/CompanionDeviceShellCommand.java
index d368b86..1f62613 100644
--- a/services/companion/java/com/android/server/companion/CompanionDeviceShellCommand.java
+++ b/services/companion/java/com/android/server/companion/CompanionDeviceShellCommand.java
@@ -27,7 +27,7 @@
 import android.os.ShellCommand;
 import android.util.proto.ProtoOutputStream;
 
-import com.android.server.companion.datatransfer.SystemDataTransferRequestStore;
+import com.android.server.companion.datatransfer.SystemDataTransferProcessor;
 import com.android.server.companion.datatransfer.contextsync.BitmapUtils;
 import com.android.server.companion.datatransfer.contextsync.CrossDeviceSyncController;
 import com.android.server.companion.presence.CompanionDevicePresenceMonitor;
@@ -44,20 +44,20 @@
     private final CompanionDevicePresenceMonitor mDevicePresenceMonitor;
     private final CompanionTransportManager mTransportManager;
 
-    private final SystemDataTransferRequestStore mSystemDataTransferRequestStore;
+    private final SystemDataTransferProcessor mSystemDataTransferProcessor;
     private final AssociationRequestsProcessor mAssociationRequestsProcessor;
 
     CompanionDeviceShellCommand(CompanionDeviceManagerService service,
             AssociationStoreImpl associationStore,
             CompanionDevicePresenceMonitor devicePresenceMonitor,
             CompanionTransportManager transportManager,
-            SystemDataTransferRequestStore systemDataTransferRequestStore,
+            SystemDataTransferProcessor systemDataTransferProcessor,
             AssociationRequestsProcessor associationRequestsProcessor) {
         mService = service;
         mAssociationStore = associationStore;
         mDevicePresenceMonitor = devicePresenceMonitor;
         mTransportManager = transportManager;
-        mSystemDataTransferRequestStore = systemDataTransferRequestStore;
+        mSystemDataTransferProcessor = systemDataTransferProcessor;
         mAssociationRequestsProcessor = associationRequestsProcessor;
     }
 
@@ -261,16 +261,47 @@
                     break;
                 }
 
-                case "allow-permission-sync": {
-                    int userId = getNextIntArgRequired();
+                case "get-perm-sync-state": {
                     associationId = getNextIntArgRequired();
-                    boolean enabled = getNextBooleanArgRequired();
-                    PermissionSyncRequest request = new PermissionSyncRequest(associationId);
-                    request.setUserId(userId);
-                    request.setUserConsented(enabled);
-                    mSystemDataTransferRequestStore.writeRequest(userId, request);
+                    PermissionSyncRequest request =
+                            mSystemDataTransferProcessor.getPermissionSyncRequest(associationId);
+                    out.println((request == null ? "null" : request.isUserConsented()));
+                    break;
                 }
-                break;
+
+                case "remove-perm-sync-state": {
+                    associationId = getNextIntArgRequired();
+                    PermissionSyncRequest request =
+                            mSystemDataTransferProcessor.getPermissionSyncRequest(associationId);
+                    out.print((request == null ? "null" : request.isUserConsented()));
+                    mSystemDataTransferProcessor.removePermissionSyncRequest(associationId);
+                    request = mSystemDataTransferProcessor.getPermissionSyncRequest(associationId);
+                    // should print " -> null"
+                    out.println(" -> " + (request == null ? "null" : request.isUserConsented()));
+                    break;
+                }
+
+                case "enable-perm-sync": {
+                    associationId = getNextIntArgRequired();
+                    PermissionSyncRequest request =
+                            mSystemDataTransferProcessor.getPermissionSyncRequest(associationId);
+                    out.print((request == null ? "null" : request.isUserConsented()));
+                    mSystemDataTransferProcessor.enablePermissionsSync(associationId);
+                    request = mSystemDataTransferProcessor.getPermissionSyncRequest(associationId);
+                    out.println(" -> " + request.isUserConsented()); // should print " -> true"
+                    break;
+                }
+
+                case "disable-perm-sync": {
+                    associationId = getNextIntArgRequired();
+                    PermissionSyncRequest request =
+                            mSystemDataTransferProcessor.getPermissionSyncRequest(associationId);
+                    out.print((request == null ? "null" : request.isUserConsented()));
+                    mSystemDataTransferProcessor.disablePermissionsSync(associationId);
+                    request = mSystemDataTransferProcessor.getPermissionSyncRequest(associationId);
+                    out.println(" -> " + request.isUserConsented()); // should print " -> false"
+                    break;
+                }
 
                 default:
                     return handleDefaultCommands(cmd);
@@ -346,5 +377,14 @@
 
         pw.println("  create-emulated-transport <ASSOCIATION_ID>");
         pw.println("      Create an EmulatedTransport for testing purposes only");
+
+        pw.println("  enable-perm-sync <ASSOCIATION_ID>");
+        pw.println("      Enable perm sync for the association.");
+        pw.println("  disable-perm-sync <ASSOCIATION_ID>");
+        pw.println("      Disable perm sync for the association.");
+        pw.println("  get-perm-sync-state <ASSOCIATION_ID>");
+        pw.println("      Get perm sync state for the association.");
+        pw.println("  remove-perm-sync-state <ASSOCIATION_ID>");
+        pw.println("      Remove perm sync state for the association.");
     }
 }
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 13f41ed..42b5a8b 100644
--- a/services/companion/java/com/android/server/companion/datatransfer/SystemDataTransferProcessor.java
+++ b/services/companion/java/com/android/server/companion/datatransfer/SystemDataTransferProcessor.java
@@ -139,6 +139,13 @@
             @UserIdInt int userId, int associationId) {
         if (PackageUtils.isPackageAllowlisted(mContext, mPackageManager, packageName)) {
             Slog.i(LOG_TAG, "User consent Intent should be skipped. Returning null.");
+            // Auto enable perm sync for the whitelisted packages, but don't override user decision
+            PermissionSyncRequest request = getPermissionSyncRequest(associationId);
+            if (request == null) {
+                PermissionSyncRequest newRequest = new PermissionSyncRequest(associationId);
+                newRequest.setUserConsented(true);
+                mSystemDataTransferRequestStore.writeRequest(userId, newRequest);
+            }
             return null;
         }
 
@@ -185,29 +192,17 @@
         final AssociationInfo association = resolveAssociation(packageName, userId, associationId);
 
         // Check if the request has been consented by the user.
-        if (PackageUtils.isPackageAllowlisted(mContext, mPackageManager, packageName)) {
-            Slog.i(LOG_TAG, "Skip user consent check due to the same OEM package.");
-        } else {
-            List<SystemDataTransferRequest> storedRequests =
-                    mSystemDataTransferRequestStore.readRequestsByAssociationId(userId,
-                            associationId);
-            boolean hasConsented = false;
-            for (SystemDataTransferRequest storedRequest : storedRequests) {
-                if (storedRequest instanceof PermissionSyncRequest
-                        && storedRequest.isUserConsented()) {
-                    hasConsented = true;
-                    break;
-                }
+        PermissionSyncRequest request = getPermissionSyncRequest(associationId);
+        if (request == null || !request.isUserConsented()) {
+            String message =
+                    "User " + userId + " hasn't consented permission sync for associationId ["
+                            + associationId + ".";
+            Slog.e(LOG_TAG, message);
+            try {
+                callback.onError(message);
+            } catch (RemoteException ignored) {
             }
-            if (!hasConsented) {
-                String message = "User " + userId + " hasn't consented permission sync.";
-                Slog.e(LOG_TAG, message);
-                try {
-                    callback.onError(message);
-                } catch (RemoteException ignored) {
-                }
-                return;
-            }
+            return;
         }
 
         // Start permission sync
@@ -225,6 +220,50 @@
         }
     }
 
+    /**
+     * Enable perm sync for the association
+     */
+    public void enablePermissionsSync(int associationId) {
+        int userId = mAssociationStore.getAssociationById(associationId).getUserId();
+        PermissionSyncRequest request = new PermissionSyncRequest(associationId);
+        request.setUserConsented(true);
+        mSystemDataTransferRequestStore.writeRequest(userId, request);
+    }
+
+    /**
+     * Disable perm sync for the association
+     */
+    public void disablePermissionsSync(int associationId) {
+        int userId = mAssociationStore.getAssociationById(associationId).getUserId();
+        PermissionSyncRequest request = new PermissionSyncRequest(associationId);
+        request.setUserConsented(false);
+        mSystemDataTransferRequestStore.writeRequest(userId, request);
+    }
+
+    /**
+     * Get perm sync request for the association.
+     */
+    @Nullable
+    public PermissionSyncRequest getPermissionSyncRequest(int associationId) {
+        int userId = mAssociationStore.getAssociationById(associationId).getUserId();
+        List<SystemDataTransferRequest> requests =
+                mSystemDataTransferRequestStore.readRequestsByAssociationId(userId, associationId);
+        for (SystemDataTransferRequest request : requests) {
+            if (request instanceof PermissionSyncRequest) {
+                return (PermissionSyncRequest) request;
+            }
+        }
+        return null;
+    }
+
+    /**
+     * Remove perm sync request for the association.
+     */
+    public void removePermissionSyncRequest(int associationId) {
+        int userId = mAssociationStore.getAssociationById(associationId).getUserId();
+        mSystemDataTransferRequestStore.removeRequestsByAssociationId(userId, associationId);
+    }
+
     private void onReceivePermissionRestore(byte[] message) {
         Slog.i(LOG_TAG, "Applying permissions.");
         // Start applying permissions
diff --git a/services/companion/java/com/android/server/companion/datatransfer/SystemDataTransferRequestStore.java b/services/companion/java/com/android/server/companion/datatransfer/SystemDataTransferRequestStore.java
index 720cefa..9f489e8 100644
--- a/services/companion/java/com/android/server/companion/datatransfer/SystemDataTransferRequestStore.java
+++ b/services/companion/java/com/android/server/companion/datatransfer/SystemDataTransferRequestStore.java
@@ -104,7 +104,7 @@
     }
 
     @NonNull
-    List<SystemDataTransferRequest> readRequestsByAssociationId(@UserIdInt int userId,
+    public List<SystemDataTransferRequest> readRequestsByAssociationId(@UserIdInt int userId,
             int associationId) {
         List<SystemDataTransferRequest> cachedRequests;
         synchronized (mLock) {
diff --git a/services/core/Android.bp b/services/core/Android.bp
index b941aaf..d9c2694 100644
--- a/services/core/Android.bp
+++ b/services/core/Android.bp
@@ -192,8 +192,8 @@
         "apache-commons-math",
         "power_optimization_flags_lib",
         "notification_flags_lib",
-        "pm_flags_lib",
         "camera_platform_flags_core_java_lib",
+        "biometrics_flags_lib",
     ],
     javac_shard_size: 50,
     javacflags: [
diff --git a/services/core/java/com/android/server/am/OomAdjuster.java b/services/core/java/com/android/server/am/OomAdjuster.java
index 87077a6..a451f36 100644
--- a/services/core/java/com/android/server/am/OomAdjuster.java
+++ b/services/core/java/com/android/server/am/OomAdjuster.java
@@ -159,7 +159,6 @@
 import com.android.internal.annotations.CompositeRWLock;
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
-import com.android.server.LocalServices;
 import com.android.server.ServiceThread;
 import com.android.server.am.PlatformCompatCache.CachedCompatChangeId;
 import com.android.server.wm.ActivityServiceConnectionsHolder;
@@ -317,11 +316,6 @@
     static final long USE_SHORT_FGS_USAGE_INTERACTION_TIME = 183972877L;
 
     /**
-     * For some direct access we need to power manager.
-     */
-    PowerManagerInternal mLocalPowerManager;
-
-    /**
      * Service for optimizing resource usage from background apps.
      */
     CachedAppOptimizer mCachedAppOptimizer;
@@ -431,7 +425,6 @@
         mProcLock = service.mProcLock;
         mActiveUids = activeUids;
 
-        mLocalPowerManager = LocalServices.getService(PowerManagerInternal.class);
         mConstants = mService.mConstants;
         mCachedAppOptimizer = new CachedAppOptimizer(mService);
         mCacheOomRanker = new CacheOomRanker(service);
@@ -1480,8 +1473,8 @@
         becameIdle.clear();
 
         // Update from any uid changes.
-        if (mLocalPowerManager != null) {
-            mLocalPowerManager.startUidChanges();
+        if (mService.mLocalPowerManager != null) {
+            mService.mLocalPowerManager.startUidChanges();
         }
         for (int i = activeUids.size() - 1; i >= 0; i--) {
             final UidRecord uidRec = activeUids.valueAt(i);
@@ -1575,8 +1568,8 @@
             }
             mService.mInternal.deletePendingTopUid(uidRec.getUid(), nowElapsed);
         }
-        if (mLocalPowerManager != null) {
-            mLocalPowerManager.finishUidChanges();
+        if (mService.mLocalPowerManager != null) {
+            mService.mLocalPowerManager.finishUidChanges();
         }
 
         int size = becameIdle.size();
@@ -3613,8 +3606,8 @@
         final long nowElapsed = SystemClock.elapsedRealtime();
         final long maxBgTime = nowElapsed - mConstants.BACKGROUND_SETTLE_TIME;
         long nextTime = 0;
-        if (mLocalPowerManager != null) {
-            mLocalPowerManager.startUidChanges();
+        if (mService.mLocalPowerManager != null) {
+            mService.mLocalPowerManager.startUidChanges();
         }
         for (int i = N - 1; i >= 0; i--) {
             final UidRecord uidRec = mActiveUids.valueAt(i);
@@ -3634,8 +3627,8 @@
                 }
             }
         }
-        if (mLocalPowerManager != null) {
-            mLocalPowerManager.finishUidChanges();
+        if (mService.mLocalPowerManager != null) {
+            mService.mLocalPowerManager.finishUidChanges();
         }
         // Also check if there are any apps in cached and background restricted mode,
         // if so, kill it if it's been there long enough, or kick off a msg to check
diff --git a/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java b/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java
index b79ba7ba..6d2fc0d 100644
--- a/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java
+++ b/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java
@@ -115,28 +115,47 @@
     };
 
     // All the aconfig flags under the listed DeviceConfig scopes will be synced to native level.
+    // The list is sorted.
     @VisibleForTesting
     static final String[] sDeviceConfigAconfigScopes = new String[] {
-        "biometrics_framework",
-        "core_experiments_team_internal",
-        "camera_platform",
-        "power",
-        "vibrator",
-        "haptics",
-        "text",
+        "android_core_networking",
+        "angle",
         "arc_next",
-        "test_suites",
-        "hardware_backed_security_mainline",
-        "threadnetwork",
-        "media_solutions",
-        "responsible_apis",
-        "rust",
-        "pixel_biometrics",
+        "bluetooth",
+        "biometrics_framework",
+        "biometrics_integration",
+        "camera_platform",
+        "car_framework",
         "car_perception",
         "car_security",
         "car_telemetry",
-        "car_framework",
-        "android-nfc",
+        "codec_fwk",
+        "companion",
+        "context_hub",
+        "core_experiments_team_internal",
+        "haptics",
+        "hardware_backed_security_mainline",
+        "media_audio",
+        "media_solutions",
+        "nfc",
+        "pixel_system_sw_touch",
+        "pixel_watch",
+        "platform_security",
+        "power",
+        "preload_safety",
+        "responsible_apis",
+        "rust",
+        "system_performance",
+        "test_suites",
+        "text",
+        "threadnetwork",
+        "tv_system_ui",
+        "vibrator",
+        "wear_frameworks",
+        "wear_system_health",
+        "wear_systems",
+        "window_surfaces",
+        "windowing_frontend"
     };
 
     private final String[] mGlobalSettings;
diff --git a/services/core/java/com/android/server/am/UserController.java b/services/core/java/com/android/server/am/UserController.java
index 2770833..de4ad20 100644
--- a/services/core/java/com/android/server/am/UserController.java
+++ b/services/core/java/com/android/server/am/UserController.java
@@ -2212,6 +2212,7 @@
                     final IRemoteCallback callback = new IRemoteCallback.Stub() {
                         @Override
                         public void sendResult(Bundle data) throws RemoteException {
+                            asyncTraceEnd("onUserSwitching-" + name, newUserId);
                             synchronized (mLock) {
                                 long delayForObserver = SystemClock.elapsedRealtime()
                                         - dispatchStartedTimeForObserver;
@@ -2229,8 +2230,6 @@
                                             + " ms after dispatchUserSwitch.");
                                 }
 
-                                TimingsTraceAndSlog t2 = new TimingsTraceAndSlog(TAG);
-                                t2.traceBegin("onUserSwitchingReply-" + name);
                                 curWaitingUserSwitchCallbacks.remove(name);
                                 // Continue switching if all callbacks have been notified and
                                 // user switching session is still valid
@@ -2239,13 +2238,11 @@
                                         == mCurWaitingUserSwitchCallbacks)) {
                                     sendContinueUserSwitchLU(uss, oldUserId, newUserId);
                                 }
-                                t2.traceEnd();
                             }
                         }
                     };
-                    t.traceBegin("onUserSwitching-" + name);
+                    asyncTraceBegin("onUserSwitching-" + name, newUserId);
                     mUserSwitchObservers.getBroadcastItem(i).onUserSwitching(newUserId, callback);
-                    t.traceEnd();
                 } catch (RemoteException e) {
                     // Ignore
                 }
diff --git a/services/core/java/com/android/server/biometrics/Android.bp b/services/core/java/com/android/server/biometrics/Android.bp
new file mode 100644
index 0000000..6cbe4ad
--- /dev/null
+++ b/services/core/java/com/android/server/biometrics/Android.bp
@@ -0,0 +1,12 @@
+aconfig_declarations {
+    name: "biometrics_flags",
+    package: "com.android.server.biometrics",
+    srcs: [
+        "biometrics.aconfig",
+    ],
+}
+
+java_aconfig_library {
+    name: "biometrics_flags_lib",
+    aconfig_declarations: "biometrics_flags",
+}
diff --git a/services/core/java/com/android/server/biometrics/biometrics.aconfig b/services/core/java/com/android/server/biometrics/biometrics.aconfig
new file mode 100644
index 0000000..b537e0e
--- /dev/null
+++ b/services/core/java/com/android/server/biometrics/biometrics.aconfig
@@ -0,0 +1,8 @@
+package: "com.android.server.biometrics"
+
+flag {
+  name: "face_vhal_feature"
+  namespace: "biometrics_framework"
+  description: "This flag controls tunscany virtual HAL feature"
+  bug: "294254230"
+}
diff --git a/services/core/java/com/android/server/biometrics/sensors/face/FaceService.java b/services/core/java/com/android/server/biometrics/sensors/face/FaceService.java
index 6f26e7b..5084b60 100644
--- a/services/core/java/com/android/server/biometrics/sensors/face/FaceService.java
+++ b/services/core/java/com/android/server/biometrics/sensors/face/FaceService.java
@@ -58,6 +58,7 @@
 import com.android.internal.util.DumpUtils;
 import com.android.internal.widget.LockPatternUtils;
 import com.android.server.SystemService;
+import com.android.server.biometrics.Flags;
 import com.android.server.biometrics.Utils;
 import com.android.server.biometrics.log.BiometricContext;
 import com.android.server.biometrics.sensors.BiometricStateCallback;
@@ -67,6 +68,8 @@
 import com.android.server.biometrics.sensors.face.aidl.FaceProvider;
 import com.android.server.biometrics.sensors.face.hidl.Face10;
 
+import com.google.android.collect.Lists;
+
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
 import java.util.ArrayList;
@@ -639,15 +642,24 @@
             provider.second.scheduleGetFeature(provider.first, token, userId, feature,
                     new ClientMonitorCallbackConverter(receiver), opPackageName);
         }
-
-        private List<ServiceProvider> getAidlProviders() {
+        @NonNull
+        private List<ServiceProvider> getHidlProviders(
+                @NonNull List<FaceSensorPropertiesInternal> hidlSensors) {
             final List<ServiceProvider> providers = new ArrayList<>();
 
-            final String[] instances = ServiceManager.getDeclaredInstances(IFace.DESCRIPTOR);
-            if (instances == null || instances.length == 0) {
-                return providers;
+            for (FaceSensorPropertiesInternal hidlSensor : hidlSensors) {
+                providers.add(
+                        Face10.newInstance(getContext(), mBiometricStateCallback,
+                                hidlSensor, mLockoutResetDispatcher));
             }
 
+            return providers;
+        }
+
+        @NonNull
+        private List<ServiceProvider> getAidlProviders(@NonNull List<String> instances) {
+            final List<ServiceProvider> providers = new ArrayList<>();
+
             for (String instance : instances) {
                 final String fqName = IFace.DESCRIPTOR + "/" + instance;
                 final IFace face = IFace.Stub.asInterface(
@@ -676,17 +688,55 @@
             super.registerAuthenticators_enforcePermission();
 
             mRegistry.registerAll(() -> {
-                final List<ServiceProvider> providers = new ArrayList<>();
-                for (FaceSensorPropertiesInternal hidlSensor : hidlSensors) {
-                    providers.add(
-                            Face10.newInstance(getContext(), mBiometricStateCallback,
-                                    hidlSensor, mLockoutResetDispatcher));
+                List<String> aidlSensors = new ArrayList<>();
+                final String[] instances = ServiceManager.getDeclaredInstances(IFace.DESCRIPTOR);
+                if (instances != null) {
+                    aidlSensors.addAll(Lists.newArrayList(instances));
                 }
-                providers.addAll(getAidlProviders());
+
+                final Pair<List<FaceSensorPropertiesInternal>, List<String>>
+                        filteredInstances = filterAvailableHalInstances(hidlSensors, aidlSensors);
+
+                final List<ServiceProvider> providers = new ArrayList<>();
+                providers.addAll(getHidlProviders(filteredInstances.first));
+                providers.addAll(getAidlProviders(filteredInstances.second));
                 return providers;
             });
         }
 
+        private Pair<List<FaceSensorPropertiesInternal>, List<String>>
+                filterAvailableHalInstances(
+                @NonNull List<FaceSensorPropertiesInternal> hidlInstances,
+                @NonNull List<String> aidlInstances) {
+            if ((hidlInstances.size() + aidlInstances.size()) <= 1) {
+                return new Pair(hidlInstances, aidlInstances);
+            }
+
+            if (Flags.faceVhalFeature()) {
+                Slog.i(TAG, "Face VHAL feature is on");
+            } else {
+                Slog.i(TAG, "Face VHAL feature is off");
+            }
+
+            final int virtualAt = aidlInstances.indexOf("virtual");
+            if (Flags.faceVhalFeature() && Utils.isVirtualEnabled(getContext())) {
+                if (virtualAt != -1) {
+                    //only virtual instance should be returned
+                    return new Pair(new ArrayList<>(), List.of(aidlInstances.get(virtualAt)));
+                } else {
+                    Slog.e(TAG, "Could not find virtual interface while it is enabled");
+                    return new Pair(hidlInstances, aidlInstances);
+                }
+            } else {
+                //remove virtual instance
+                aidlInstances = new ArrayList<>(aidlInstances);
+                if (virtualAt != -1) {
+                    aidlInstances.remove(virtualAt);
+                }
+                return new Pair(hidlInstances, aidlInstances);
+            }
+        }
+
         @Override
         public void addAuthenticatorsRegisteredCallback(
                 IFaceAuthenticatorsRegisteredCallback callback) {
@@ -749,7 +799,7 @@
 
     void syncEnrollmentsNow() {
         Utils.checkPermissionOrShell(getContext(), MANAGE_FACE);
-        if (Utils.isVirtualEnabled(getContext())) {
+        if (Flags.faceVhalFeature() && Utils.isVirtualEnabled(getContext())) {
             Slog.i(TAG, "Sync virtual enrollments");
             final int userId = ActivityManager.getCurrentUser();
             for (ServiceProvider provider : mRegistry.getProviders()) {
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintDetectClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintDetectClient.java
index 51a9385..4502e5d 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintDetectClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintDetectClient.java
@@ -128,7 +128,11 @@
         vibrateSuccess();
 
         try {
-            getListener().onDetected(getSensorId(), getTargetUserId(), mIsStrongBiometric);
+            if (getListener() != null) {
+                getListener().onDetected(getSensorId(), getTargetUserId(), mIsStrongBiometric);
+            } else {
+                Slog.e(TAG, "Listener is null!");
+            }
             mCallback.onClientFinished(this, true /* success */);
         } catch (RemoteException e) {
             Slog.e(TAG, "Remote exception when sending onDetected", e);
diff --git a/services/core/java/com/android/server/content/SyncManager.java b/services/core/java/com/android/server/content/SyncManager.java
index e072eeb..8736a53 100644
--- a/services/core/java/com/android/server/content/SyncManager.java
+++ b/services/core/java/com/android/server/content/SyncManager.java
@@ -3046,7 +3046,7 @@
 
     public static void sendMessage(Message message) {
         final SyncManager instance = getInstance();
-        if (instance != null) {
+        if (instance != null && instance.mSyncHandler != null) {
             instance.mSyncHandler.sendMessage(message);
         }
     }
diff --git a/services/core/java/com/android/server/dreams/DreamManagerService.java b/services/core/java/com/android/server/dreams/DreamManagerService.java
index d88fe8a..d2aff25 100644
--- a/services/core/java/com/android/server/dreams/DreamManagerService.java
+++ b/services/core/java/com/android/server/dreams/DreamManagerService.java
@@ -324,7 +324,8 @@
             pw.println("mWhenToDream=" + mWhenToDream);
             pw.println("mKeepDreamingWhenUnpluggingDefault=" + mKeepDreamingWhenUnpluggingDefault);
             pw.println("getDozeComponent()=" + getDozeComponent());
-            pw.println("mDreamOverlayServiceName=" + mDreamOverlayServiceName.flattenToString());
+            pw.println("mDreamOverlayServiceName="
+                    + ComponentName.flattenToShortString(mDreamOverlayServiceName));
             pw.println();
 
             DumpUtils.dumpAsync(mHandler, (pw1, prefix) -> mController.dump(pw1), pw, "", 200);
diff --git a/services/core/java/com/android/server/input/GestureMonitorSpyWindow.java b/services/core/java/com/android/server/input/GestureMonitorSpyWindow.java
index 2ede56d..a2c8748 100644
--- a/services/core/java/com/android/server/input/GestureMonitorSpyWindow.java
+++ b/services/core/java/com/android/server/input/GestureMonitorSpyWindow.java
@@ -62,10 +62,10 @@
         mWindowHandle.ownerUid = uid;
         mWindowHandle.scaleFactor = 1.0f;
         mWindowHandle.replaceTouchableRegionWithCrop(null /* use this surface's bounds */);
-        mWindowHandle.inputConfig =
-                InputConfig.NOT_FOCUSABLE | InputConfig.SPY | InputConfig.TRUSTED_OVERLAY;
+        mWindowHandle.inputConfig = InputConfig.NOT_FOCUSABLE | InputConfig.SPY;
 
         final SurfaceControl.Transaction t = new SurfaceControl.Transaction();
+        mWindowHandle.setTrustedOverlay(t, mInputSurface, true);
         t.setInputWindowInfo(mInputSurface, mWindowHandle);
         t.setLayer(mInputSurface, InputManagerService.INPUT_OVERLAY_LAYER_GESTURE_MONITOR);
         t.setPosition(mInputSurface, 0, 0);
diff --git a/services/core/java/com/android/server/inputmethod/HandwritingEventReceiverSurface.java b/services/core/java/com/android/server/inputmethod/HandwritingEventReceiverSurface.java
index 7726f40..dbbbed3 100644
--- a/services/core/java/com/android/server/inputmethod/HandwritingEventReceiverSurface.java
+++ b/services/core/java/com/android/server/inputmethod/HandwritingEventReceiverSurface.java
@@ -57,13 +57,13 @@
                 InputConfig.NOT_FOCUSABLE
                         | InputConfig.NOT_TOUCHABLE
                         | InputConfig.SPY
-                        | InputConfig.INTERCEPTS_STYLUS
-                        | InputConfig.TRUSTED_OVERLAY;
+                        | InputConfig.INTERCEPTS_STYLUS;
 
         // Configure the surface to receive stylus events across the entire display.
         mWindowHandle.replaceTouchableRegionWithCrop(null /* use this surface's bounds */);
 
         final SurfaceControl.Transaction t = new SurfaceControl.Transaction();
+        mWindowHandle.setTrustedOverlay(t, mInputSurface, true);
         t.setInputWindowInfo(mInputSurface, mWindowHandle);
         t.setLayer(mInputSurface, InputManagerService.INPUT_OVERLAY_LAYER_HANDWRITING_SURFACE);
         t.setPosition(mInputSurface, 0, 0);
diff --git a/services/core/java/com/android/server/inputmethod/HandwritingModeController.java b/services/core/java/com/android/server/inputmethod/HandwritingModeController.java
index 26df162..749d6b0 100644
--- a/services/core/java/com/android/server/inputmethod/HandwritingModeController.java
+++ b/services/core/java/com/android/server/inputmethod/HandwritingModeController.java
@@ -224,6 +224,7 @@
     @Nullable
     HandwritingSession startHandwritingSession(
             int requestId, int imePid, int imeUid, IBinder focusedWindowToken) {
+        clearPendingHandwritingDelegation();
         if (mHandwritingSurface == null) {
             Slog.e(TAG, "Cannot start handwriting session: Handwriting was not initialized.");
             return null;
diff --git a/services/core/java/com/android/server/inputmethod/ImeTrackerService.java b/services/core/java/com/android/server/inputmethod/ImeTrackerService.java
index 2efb0be..b090334 100644
--- a/services/core/java/com/android/server/inputmethod/ImeTrackerService.java
+++ b/services/core/java/com/android/server/inputmethod/ImeTrackerService.java
@@ -259,19 +259,16 @@
                     .withZone(ZoneId.systemDefault());
 
             pw.print(prefix);
-            pw.println("ImeTrackerService#History.mLiveEntries: "
-                    + mLiveEntries.size() + " elements");
+            pw.println("mLiveEntries: " + mLiveEntries.size() + " elements");
 
             for (final var entry: mLiveEntries.values()) {
-                dumpEntry(entry, pw, prefix, formatter);
+                dumpEntry(entry, pw, prefix + "  ", formatter);
             }
-
             pw.print(prefix);
-            pw.println("ImeTrackerService#History.mEntries: "
-                    + mEntries.size() + " elements");
+            pw.println("mEntries: " + mEntries.size() + " elements");
 
             for (final var entry: mEntries) {
-                dumpEntry(entry, pw, prefix, formatter);
+                dumpEntry(entry, pw, prefix + "  ", formatter);
             }
         }
 
@@ -279,22 +276,22 @@
         private void dumpEntry(@NonNull Entry entry, @NonNull PrintWriter pw,
                 @NonNull String prefix, @NonNull DateTimeFormatter formatter) {
             pw.print(prefix);
-            pw.print(" #" + entry.mSequenceNumber);
+            pw.print("#" + entry.mSequenceNumber);
             pw.print(" " + ImeTracker.Debug.typeToString(entry.mType));
             pw.print(" - " + ImeTracker.Debug.statusToString(entry.mStatus));
             pw.print(" - " + entry.mTag);
             pw.println(" (" + entry.mDuration + "ms):");
 
             pw.print(prefix);
-            pw.print("   startTime=" + formatter.format(Instant.ofEpochMilli(entry.mStartTime)));
+            pw.print("  startTime=" + formatter.format(Instant.ofEpochMilli(entry.mStartTime)));
             pw.println(" " + ImeTracker.Debug.originToString(entry.mOrigin));
 
             pw.print(prefix);
-            pw.print("   reason=" + InputMethodDebug.softInputDisplayReasonToString(entry.mReason));
+            pw.print("  reason=" + InputMethodDebug.softInputDisplayReasonToString(entry.mReason));
             pw.println(" " + ImeTracker.Debug.phaseToString(entry.mPhase));
 
             pw.print(prefix);
-            pw.println("   requestWindowName=" + entry.mRequestWindowName);
+            pw.println("  requestWindowName=" + entry.mRequestWindowName);
         }
 
         /** A history entry. */
diff --git a/services/core/java/com/android/server/inputmethod/ImeVisibilityStateComputer.java b/services/core/java/com/android/server/inputmethod/ImeVisibilityStateComputer.java
index 2e0274b..0dd48ae 100644
--- a/services/core/java/com/android/server/inputmethod/ImeVisibilityStateComputer.java
+++ b/services/core/java/com/android/server/inputmethod/ImeVisibilityStateComputer.java
@@ -588,12 +588,12 @@
         proto.write(INPUT_SHOWN, mInputShown);
     }
 
-    void dump(PrintWriter pw) {
+    void dump(@NonNull PrintWriter pw, @NonNull String prefix) {
         final Printer p = new PrintWriterPrinter(pw);
-        p.println(" mRequestedShowExplicitly=" + mRequestedShowExplicitly
+        p.println(prefix + "mRequestedShowExplicitly=" + mRequestedShowExplicitly
                 + " mShowForced=" + mShowForced);
-        p.println("  mImeHiddenByDisplayPolicy=" + mPolicy.isImeHiddenByDisplayPolicy());
-        p.println("  mInputShown=" + mInputShown);
+        p.println(prefix + "mImeHiddenByDisplayPolicy=" + mPolicy.isImeHiddenByDisplayPolicy());
+        p.println(prefix + "mInputShown=" + mInputShown);
     }
 
     /**
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodBindingController.java b/services/core/java/com/android/server/inputmethod/InputMethodBindingController.java
index 20c7029..2fe2271 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodBindingController.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodBindingController.java
@@ -70,7 +70,7 @@
     @NonNull private final WindowManagerInternal mWindowManagerInternal;
 
     @GuardedBy("ImfLock.class") private long mLastBindTime;
-    @GuardedBy("ImfLock.class") private boolean mHasConnection;
+    @GuardedBy("ImfLock.class") private boolean mHasMainConnection;
     @GuardedBy("ImfLock.class") @Nullable private String mCurId;
     @GuardedBy("ImfLock.class") @Nullable private String mSelectedMethodId;
     @GuardedBy("ImfLock.class") @Nullable private Intent mCurIntent;
@@ -137,8 +137,8 @@
      * a service (whether or not we have gotten its IBinder back yet).
      */
     @GuardedBy("ImfLock.class")
-    boolean hasConnection() {
-        return mHasConnection;
+    boolean hasMainConnection() {
+        return mHasMainConnection;
     }
 
     /**
@@ -369,7 +369,7 @@
             unbindVisibleConnection();
         }
 
-        if (hasConnection()) {
+        if (hasMainConnection()) {
             unbindMainConnection();
         }
 
@@ -464,7 +464,7 @@
     @GuardedBy("ImfLock.class")
     private void unbindMainConnection() {
         mContext.unbindService(mMainConnection);
-        mHasConnection = false;
+        mHasMainConnection = false;
     }
 
     @GuardedBy("ImfLock.class")
@@ -485,8 +485,9 @@
 
     @GuardedBy("ImfLock.class")
     private boolean bindCurrentInputMethodServiceMainConnection() {
-        mHasConnection = bindCurrentInputMethodService(mMainConnection, mImeConnectionBindFlags);
-        return mHasConnection;
+        mHasMainConnection = bindCurrentInputMethodService(mMainConnection,
+                mImeConnectionBindFlags);
+        return mHasMainConnection;
     }
 
     /**
@@ -499,7 +500,7 @@
     void setCurrentMethodVisible() {
         if (mCurMethod != null) {
             if (DEBUG) Slog.d(TAG, "setCurrentMethodVisible: mCurToken=" + mCurToken);
-            if (hasConnection() && !isVisibleBound()) {
+            if (hasMainConnection() && !isVisibleBound()) {
                 mVisibleBound = bindCurrentInputMethodService(mVisibleConnection,
                         IME_VISIBLE_BIND_FLAGS);
             }
@@ -507,7 +508,7 @@
         }
 
         // No IME is currently connected. Reestablish the main connection.
-        if (!hasConnection()) {
+        if (!hasMainConnection()) {
             if (DEBUG) {
                 Slog.d(TAG, "Cannot show input: no IME bound. Rebinding.");
             }
@@ -528,7 +529,7 @@
             bindCurrentInputMethodServiceMainConnection();
         } else {
             if (DEBUG) {
-                Slog.d(TAG, "Can't show input: connection = " + mHasConnection + ", time = "
+                Slog.d(TAG, "Can't show input: connection = " + mHasMainConnection + ", time = "
                         + (TIME_TO_RECONNECT - bindingDuration));
             }
         }
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
index 131eec3..699e9c8 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
@@ -358,12 +358,12 @@
 
         @Override
         public String toString() {
-            return "SessionState{uid " + mClient.mUid + " pid " + mClient.mPid
-                    + " method " + Integer.toHexString(
+            return "SessionState{uid=" + mClient.mUid + " pid=" + mClient.mPid
+                    + " method=" + Integer.toHexString(
                     IInputMethodInvoker.getBinderIdentityHashCode(mMethod))
-                    + " session " + Integer.toHexString(
+                    + " session=" + Integer.toHexString(
                     System.identityHashCode(mSession))
-                    + " channel " + mChannel
+                    + " channel=" + mChannel
                     + "}";
         }
 
@@ -388,9 +388,9 @@
 
         @Override
         public String toString() {
-            return "AccessibilitySessionState{uid " + mClient.mUid + " pid " + mClient.mPid
-                    + " id " + Integer.toHexString(mId)
-                    + " session " + Integer.toHexString(
+            return "AccessibilitySessionState{uid=" + mClient.mUid + " pid=" + mClient.mPid
+                    + " id=" + Integer.toHexString(mId)
+                    + " session=" + Integer.toHexString(
                     System.identityHashCode(mSession))
                     + "}";
         }
@@ -603,7 +603,7 @@
      */
     @GuardedBy("ImfLock.class")
     private boolean hasConnectionLocked() {
-        return mBindingController.hasConnection();
+        return mBindingController.hasMainConnection();
     }
 
     /** The token tracking the current IME request or {@code null} otherwise. */
@@ -884,47 +884,47 @@
                     continue;
                 }
                 pw.print(prefix);
-                pw.println("SoftInputShowHideHistory #" + entry.mSequenceNumber + ":");
+                pw.println("SoftInputShowHide #" + entry.mSequenceNumber + ":");
 
                 pw.print(prefix);
-                pw.println(" time=" + formatter.format(Instant.ofEpochMilli(entry.mWallTime))
+                pw.println("  time=" + formatter.format(Instant.ofEpochMilli(entry.mWallTime))
                         + " (timestamp=" + entry.mTimestamp + ")");
 
                 pw.print(prefix);
-                pw.print(" reason=" + InputMethodDebug.softInputDisplayReasonToString(
+                pw.print("  reason=" + InputMethodDebug.softInputDisplayReasonToString(
                         entry.mReason));
                 pw.println(" inFullscreenMode=" + entry.mInFullscreenMode);
 
                 pw.print(prefix);
-                pw.println(" requestClient=" + entry.mClientState);
+                pw.println("  requestClient=" + entry.mClientState);
 
                 pw.print(prefix);
-                pw.println(" focusedWindowName=" + entry.mFocusedWindowName);
+                pw.println("  focusedWindowName=" + entry.mFocusedWindowName);
 
                 pw.print(prefix);
-                pw.println(" requestWindowName=" + entry.mRequestWindowName);
+                pw.println("  requestWindowName=" + entry.mRequestWindowName);
 
                 pw.print(prefix);
-                pw.println(" imeControlTargetName=" + entry.mImeControlTargetName);
+                pw.println("  imeControlTargetName=" + entry.mImeControlTargetName);
 
                 pw.print(prefix);
-                pw.println(" imeTargetNameFromWm=" + entry.mImeTargetNameFromWm);
+                pw.println("  imeTargetNameFromWm=" + entry.mImeTargetNameFromWm);
 
                 pw.print(prefix);
-                pw.println(" imeSurfaceParentName=" + entry.mImeSurfaceParentName);
+                pw.println("  imeSurfaceParentName=" + entry.mImeSurfaceParentName);
 
                 pw.print(prefix);
-                pw.print(" editorInfo: ");
+                pw.print("  editorInfo:");
                 if (entry.mEditorInfo != null) {
                     pw.print(" inputType=" + entry.mEditorInfo.inputType);
                     pw.print(" privateImeOptions=" + entry.mEditorInfo.privateImeOptions);
                     pw.println(" fieldId (viewId)=" + entry.mEditorInfo.fieldId);
                 } else {
-                    pw.println("null");
+                    pw.println(" null");
                 }
 
                 pw.print(prefix);
-                pw.println(" focusedWindowSoftInputMode=" + InputMethodDebug.softInputModeToString(
+                pw.println("  focusedWindowSoftInputMode=" + InputMethodDebug.softInputModeToString(
                         entry.mFocusedWindowSoftInputMode));
             }
         }
@@ -1052,30 +1052,30 @@
                 pw.println("StartInput #" + entry.mSequenceNumber + ":");
 
                 pw.print(prefix);
-                pw.println(" time=" + formatter.format(Instant.ofEpochMilli(entry.mWallTime))
+                pw.println("  time=" + formatter.format(Instant.ofEpochMilli(entry.mWallTime))
                         + " (timestamp=" + entry.mTimestamp + ")"
                         + " reason="
                         + InputMethodDebug.startInputReasonToString(entry.mStartInputReason)
                         + " restarting=" + entry.mRestarting);
 
                 pw.print(prefix);
-                pw.print(" imeToken=" + entry.mImeTokenString + " [" + entry.mImeId + "]");
+                pw.print("  imeToken=" + entry.mImeTokenString + " [" + entry.mImeId + "]");
                 pw.print(" imeUserId=" + entry.mImeUserId);
                 pw.println(" imeDisplayId=" + entry.mImeDisplayId);
 
                 pw.print(prefix);
-                pw.println(" targetWin=" + entry.mTargetWindowString
+                pw.println("  targetWin=" + entry.mTargetWindowString
                         + " [" + entry.mEditorInfo.packageName + "]"
                         + " targetUserId=" + entry.mTargetUserId
                         + " targetDisplayId=" + entry.mTargetDisplayId
                         + " clientBindSeq=" + entry.mClientBindSequenceNumber);
 
                 pw.print(prefix);
-                pw.println(" softInputMode=" + InputMethodDebug.softInputModeToString(
+                pw.println("  softInputMode=" + InputMethodDebug.softInputModeToString(
                         entry.mTargetWindowSoftInputMode));
 
                 pw.print(prefix);
-                pw.println(" inputType=0x" + Integer.toHexString(entry.mEditorInfo.inputType)
+                pw.println("  inputType=0x" + Integer.toHexString(entry.mEditorInfo.inputType)
                         + " imeOptions=0x" + Integer.toHexString(entry.mEditorInfo.imeOptions)
                         + " fieldId=0x" + Integer.toHexString(entry.mEditorInfo.fieldId)
                         + " fieldName=" + entry.mEditorInfo.fieldName
@@ -3369,13 +3369,19 @@
     @BinderThread
     @Override
     public void startStylusHandwriting(IInputMethodClient client) {
+        startStylusHandwriting(client, false /* usesDelegation */);
+    }
+
+    private void startStylusHandwriting(IInputMethodClient client, boolean usesDelegation) {
         Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "IMMS.startStylusHandwriting");
         try {
             ImeTracing.getInstance().triggerManagerServiceDump(
                     "InputMethodManagerService#startStylusHandwriting");
             int uid = Binder.getCallingUid();
             synchronized (ImfLock.class) {
-                mHwController.clearPendingHandwritingDelegation();
+                if (!usesDelegation) {
+                    mHwController.clearPendingHandwritingDelegation();
+                }
                 if (!canInteractWithImeLocked(uid, client, "startStylusHandwriting",
                         null /* statsToken */)) {
                     return;
@@ -3457,7 +3463,7 @@
             return false;
         }
 
-        startStylusHandwriting(client);
+        startStylusHandwriting(client, true /* usesDelegation */);
         return true;
     }
 
@@ -5946,11 +5952,11 @@
                 p.println("  InputMethod #" + i + ":");
                 info.dump(p, "    ");
             }
-            p.println("  Clients:");
+            p.println("  ClientStates:");
             final int numClients = mClients.size();
             for (int i = 0; i < numClients; ++i) {
                 final ClientState ci = mClients.valueAt(i);
-                p.println("  Client " + ci + ":");
+                p.println("  " + ci + ":");
                 p.println("    client=" + ci.mClient);
                 p.println("    fallbackInputConnection=" + ci.mFallbackInputConnection);
                 p.println("    sessionRequested=" + ci.mSessionRequested);
@@ -5977,7 +5983,7 @@
             method = getCurMethodLocked();
             p.println("  mCurMethod=" + getCurMethodLocked());
             p.println("  mEnabledSession=" + mEnabledSession);
-            mVisibilityStateComputer.dump(pw);
+            mVisibilityStateComputer.dump(pw, "  ");
             p.println("  mInFullscreenMode=" + mInFullscreenMode);
             p.println("  mSystemReady=" + mSystemReady + " mInteractive=" + mIsInteractive);
             p.println("  ENABLE_HIDE_IME_CAPTION_BAR="
@@ -5991,13 +5997,13 @@
             mSettings.dumpLocked(p, "    ");
 
             p.println("  mStartInputHistory:");
-            mStartInputHistory.dump(pw, "   ");
+            mStartInputHistory.dump(pw, "    ");
 
             p.println("  mSoftInputShowHideHistory:");
-            mSoftInputShowHideHistory.dump(pw, "   ");
+            mSoftInputShowHideHistory.dump(pw, "    ");
 
             p.println("  mImeTrackerService#History:");
-            mImeTrackerService.dump(pw, "   ");
+            mImeTrackerService.dump(pw, "    ");
         }
 
         // Exit here for critical dump, as remaining sections require IPCs to other processes.
diff --git a/services/core/java/com/android/server/media/AudioPlayerStateMonitor.java b/services/core/java/com/android/server/media/AudioPlayerStateMonitor.java
index 4873467..7cf6eae 100644
--- a/services/core/java/com/android/server/media/AudioPlayerStateMonitor.java
+++ b/services/core/java/com/android/server/media/AudioPlayerStateMonitor.java
@@ -154,6 +154,23 @@
     }
 
     /**
+     * Returns whether the given uid corresponds to the last process to audio or not.
+     *
+     * <p> This is useful for handling the cases where the foreground app is playing media without
+     * a media session. Then, volume events should affect the local music stream rather than other
+     * media sessions.
+     *
+     * @return {@code true} if the given uid corresponds to the last process to audio or
+     * {@code false} otherwise.
+     */
+    public boolean hasUidPlayedAudioLast(int uid) {
+        synchronized (mLock) {
+            return !mSortedAudioPlaybackClientUids.isEmpty()
+                    && uid == mSortedAudioPlaybackClientUids.get(0);
+        }
+    }
+
+    /**
      * Returns if the audio playback is active for the uid.
      */
     public boolean isPlaybackActive(int uid) {
@@ -234,7 +251,7 @@
                     }
                 }
 
-                // Update mSortedAuioPlaybackClientUids.
+                // Update mSortedAudioPlaybackClientUids.
                 for (int i = 0; i < activeAudioPlaybackConfigs.size(); ++i) {
                     AudioPlaybackConfiguration config = activeAudioPlaybackConfigs.valueAt(i);
                     final int uid = config.getClientUid();
diff --git a/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java b/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java
index 5b87069..4892c22 100644
--- a/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java
+++ b/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java
@@ -22,7 +22,6 @@
 import static android.media.MediaRoute2ProviderService.REASON_UNKNOWN_ERROR;
 import static android.media.MediaRouter2Utils.getOriginalId;
 import static android.media.MediaRouter2Utils.getProviderId;
-
 import static com.android.internal.util.function.pooled.PooledLambda.obtainMessage;
 import static com.android.server.media.MediaFeatureFlagManager.FEATURE_SCANNING_MINIMUM_PACKAGE_IMPORTANCE;
 
@@ -214,17 +213,16 @@
     @NonNull
     public List<MediaRoute2Info> getSystemRoutes() {
         final int uid = Binder.getCallingUid();
+        final int pid = Binder.getCallingPid();
         final int userId = UserHandle.getUserHandleForUid(uid).getIdentifier();
-        final boolean hasModifyAudioRoutingPermission = mContext.checkCallingOrSelfPermission(
-                android.Manifest.permission.MODIFY_AUDIO_ROUTING)
-                == PackageManager.PERMISSION_GRANTED;
+        final boolean hasSystemRoutingPermission = checkCallerHasSystemRoutingPermissions(pid, uid);
 
         final long token = Binder.clearCallingIdentity();
         try {
             Collection<MediaRoute2Info> systemRoutes;
             synchronized (mLock) {
                 UserRecord userRecord = getOrCreateUserRecordLocked(userId);
-                if (hasModifyAudioRoutingPermission) {
+                if (hasSystemRoutingPermission) {
                     MediaRoute2ProviderInfo providerInfo =
                             userRecord.mHandler.mSystemProvider.getProviderInfo();
                     if (providerInfo != null) {
@@ -255,9 +253,8 @@
         final boolean hasConfigureWifiDisplayPermission = mContext.checkCallingOrSelfPermission(
                 android.Manifest.permission.CONFIGURE_WIFI_DISPLAY)
                 == PackageManager.PERMISSION_GRANTED;
-        final boolean hasModifyAudioRoutingPermission = mContext.checkCallingOrSelfPermission(
-                android.Manifest.permission.MODIFY_AUDIO_ROUTING)
-                == PackageManager.PERMISSION_GRANTED;
+        final boolean hasModifyAudioRoutingPermission =
+                checkCallerHasModifyAudioRoutingPermission(pid, uid);
 
         final long token = Binder.clearCallingIdentity();
         try {
@@ -666,17 +663,17 @@
     public RoutingSessionInfo getSystemSessionInfo(
             @Nullable String packageName, boolean setDeviceRouteSelected) {
         final int uid = Binder.getCallingUid();
+        final int pid = Binder.getCallingPid();
         final int userId = UserHandle.getUserHandleForUid(uid).getIdentifier();
-        final boolean hasModifyAudioRoutingPermission = mContext.checkCallingOrSelfPermission(
-                android.Manifest.permission.MODIFY_AUDIO_ROUTING)
-                == PackageManager.PERMISSION_GRANTED;
+        final boolean hasSystemRoutingPermissions =
+                checkCallerHasSystemRoutingPermissions(pid, uid);
 
         final long token = Binder.clearCallingIdentity();
         try {
             synchronized (mLock) {
                 UserRecord userRecord = getOrCreateUserRecordLocked(userId);
                 List<RoutingSessionInfo> sessionInfos;
-                if (hasModifyAudioRoutingPermission) {
+                if (hasSystemRoutingPermissions) {
                     if (setDeviceRouteSelected) {
                         // Return a fake system session that shows the device route as selected and
                         // available bluetooth routes as transferable.
@@ -707,6 +704,25 @@
         }
     }
 
+    private boolean checkCallerHasSystemRoutingPermissions(int pid, int uid) {
+        return checkCallerHasModifyAudioRoutingPermission(pid, uid);
+    }
+
+    private boolean checkCallerHasModifyAudioRoutingPermission(int pid, int uid) {
+        return mContext.checkPermission(Manifest.permission.MODIFY_AUDIO_ROUTING, pid, uid)
+                == PackageManager.PERMISSION_GRANTED;
+    }
+
+    private boolean checkCallerHasBluetoothPermissions(int pid, int uid) {
+        boolean hasBluetoothRoutingPermission = true;
+        for (String permission : BLUETOOTH_PERMISSIONS_FOR_SYSTEM_ROUTING) {
+            hasBluetoothRoutingPermission &=
+                    mContext.checkPermission(permission, pid, uid)
+                            == PackageManager.PERMISSION_GRANTED;
+        }
+        return hasBluetoothRoutingPermission;
+    }
+
     // End of methods that implements operations for both MediaRouter2 and MediaRouter2Manager.
 
     public void dump(@NonNull PrintWriter pw, @NonNull String prefix) {
@@ -1587,20 +1603,11 @@
             mPid = pid;
             mHasConfigureWifiDisplayPermission = hasConfigureWifiDisplayPermission;
             mHasModifyAudioRoutingPermission = hasModifyAudioRoutingPermission;
-            mHasBluetoothRoutingPermission = new AtomicBoolean(fetchBluetoothPermission());
+            mHasBluetoothRoutingPermission =
+                    new AtomicBoolean(checkCallerHasBluetoothPermissions(mPid, mUid));
             mRouterId = mNextRouterOrManagerId.getAndIncrement();
         }
 
-        private boolean fetchBluetoothPermission() {
-            boolean hasBluetoothRoutingPermission = true;
-            for (String permission : BLUETOOTH_PERMISSIONS_FOR_SYSTEM_ROUTING) {
-                hasBluetoothRoutingPermission &=
-                        mContext.checkPermission(permission, mPid, mUid)
-                                == PackageManager.PERMISSION_GRANTED;
-            }
-            return hasBluetoothRoutingPermission;
-        }
-
         /**
          * Returns whether the corresponding router has permission to query and control system
          * routes.
@@ -1611,7 +1618,7 @@
 
         public void maybeUpdateSystemRoutingPermissionLocked() {
             boolean oldSystemRoutingPermissionValue = hasSystemRoutingPermission();
-            mHasBluetoothRoutingPermission.set(fetchBluetoothPermission());
+            mHasBluetoothRoutingPermission.set(checkCallerHasBluetoothPermissions(mPid, mUid));
             boolean newSystemRoutingPermissionValue = hasSystemRoutingPermission();
             if (oldSystemRoutingPermissionValue != newSystemRoutingPermissionValue) {
                 Map<String, MediaRoute2Info> routesToReport =
@@ -2093,34 +2100,36 @@
             if (!hasAddedOrModifiedRoutes && !hasRemovedRoutes) {
                 return;
             }
-            List<RouterRecord> routerRecordsWithModifyAudioRoutingPermission =
-                    getRouterRecords(true);
-            List<RouterRecord> routerRecordsWithoutModifyAudioRoutingPermission =
-                    getRouterRecords(false);
+            List<RouterRecord> routerRecordsWithSystemRoutingPermission =
+                    getRouterRecords(/* hasSystemRoutingPermission= */ true);
+            List<RouterRecord> routerRecordsWithoutSystemRoutingPermission =
+                    getRouterRecords(/* hasSystemRoutingPermission= */ false);
             List<IMediaRouter2Manager> managers = getManagers();
 
             // Managers receive all provider updates with all routes.
             notifyRoutesUpdatedToManagers(
                     managers, new ArrayList<>(mLastNotifiedRoutesToPrivilegedRouters.values()));
 
-            // Routers with modify audio permission (usually system routers) receive all provider
-            // updates with all routes.
+            // Routers with system routing access (either via {@link MODIFY_AUDIO_ROUTING} or
+            // {@link BLUETOOTH_CONNECT} + {@link BLUETOOTH_SCAN}) receive all provider updates
+            // with all routes.
             notifyRoutesUpdatedToRouterRecords(
-                    routerRecordsWithModifyAudioRoutingPermission,
+                    routerRecordsWithSystemRoutingPermission,
                     new ArrayList<>(mLastNotifiedRoutesToPrivilegedRouters.values()));
 
             if (!isSystemProvider) {
                 // Regular routers receive updates from all non-system providers with all non-system
                 // routes.
                 notifyRoutesUpdatedToRouterRecords(
-                        routerRecordsWithoutModifyAudioRoutingPermission,
+                        routerRecordsWithoutSystemRoutingPermission,
                         new ArrayList<>(mLastNotifiedRoutesToNonPrivilegedRouters.values()));
             } else if (hasAddedOrModifiedRoutes) {
-                // On system provider updates, regular routers receive the updated default route.
-                // This is the only system route they should receive.
+                // On system provider updates, routers without system routing access
+                // receive the updated default route. This is the only system route they should
+                // receive.
                 mLastNotifiedRoutesToNonPrivilegedRouters.put(defaultRoute.getId(), defaultRoute);
                 notifyRoutesUpdatedToRouterRecords(
-                        routerRecordsWithoutModifyAudioRoutingPermission,
+                        routerRecordsWithoutSystemRoutingPermission,
                         new ArrayList<>(mLastNotifiedRoutesToNonPrivilegedRouters.values()));
             }
         }
@@ -2526,7 +2535,7 @@
             }
         }
 
-        private List<RouterRecord> getRouterRecords(boolean hasModifyAudioRoutingPermission) {
+        private List<RouterRecord> getRouterRecords(boolean hasSystemRoutingPermission) {
             MediaRouter2ServiceImpl service = mServiceRef.get();
             List<RouterRecord> routerRecords = new ArrayList<>();
             if (service == null) {
@@ -2534,7 +2543,7 @@
             }
             synchronized (service.mLock) {
                 for (RouterRecord routerRecord : mUserRecord.mRouterRecords) {
-                    if (hasModifyAudioRoutingPermission
+                    if (hasSystemRoutingPermission
                             == routerRecord.hasSystemRoutingPermission()) {
                         routerRecords.add(routerRecord);
                     }
diff --git a/services/core/java/com/android/server/media/MediaSessionService.java b/services/core/java/com/android/server/media/MediaSessionService.java
index f4c9518..803ab28 100644
--- a/services/core/java/com/android/server/media/MediaSessionService.java
+++ b/services/core/java/com/android/server/media/MediaSessionService.java
@@ -19,7 +19,6 @@
 import static android.Manifest.permission.INTERACT_ACROSS_USERS_FULL;
 import static android.os.UserHandle.ALL;
 import static android.os.UserHandle.CURRENT;
-
 import static com.android.server.media.MediaKeyDispatcher.KEY_EVENT_LONG_PRESS;
 import static com.android.server.media.MediaKeyDispatcher.isDoubleTapOverridden;
 import static com.android.server.media.MediaKeyDispatcher.isLongPressOverridden;
@@ -85,6 +84,7 @@
 
 import com.android.internal.R;
 import com.android.internal.annotations.GuardedBy;
+import com.android.media.flags.Flags;
 import com.android.server.LocalManagerRegistry;
 import com.android.server.LocalServices;
 import com.android.server.SystemService;
@@ -2192,6 +2192,21 @@
                     isValidLocalStreamType(suggestedStream)
                             && AudioSystem.isStreamActive(suggestedStream, 0);
 
+            if (session != null && session.getUid() != uid
+                    && mAudioPlayerStateMonitor.hasUidPlayedAudioLast(uid)) {
+                if (Flags.adjustVolumeForForegroundAppPlayingAudioWithoutMediaSession()) {
+                    // The app in the foreground has been the last app to play media locally.
+                    // Therefore, We ignore the chosen session so that volume events affect the
+                    // local music stream instead. See b/275185436 for details.
+                    Log.d(TAG, "Ignoring session=" + session + " and adjusting suggestedStream="
+                            + suggestedStream + " instead");
+                    session = null;
+                } else {
+                    Log.d(TAG, "Session=" + session + " will not be not ignored and will receive"
+                            + " the volume adjustment event");
+                }
+            }
+
             if (session == null || preferSuggestedStream) {
                 if (DEBUG_KEY_EVENT) {
                     Log.d(TAG, "Adjusting suggestedStream=" + suggestedStream + " by " + direction
diff --git a/services/core/java/com/android/server/notification/OWNERS b/services/core/java/com/android/server/notification/OWNERS
index 6c4dd6d..72c4529 100644
--- a/services/core/java/com/android/server/notification/OWNERS
+++ b/services/core/java/com/android/server/notification/OWNERS
@@ -1,4 +1,4 @@
-# Bug component: 34005
+# Bug component: 1305560
 
 juliacr@google.com
 yurilin@google.com
diff --git a/services/core/java/com/android/server/pm/Android.bp b/services/core/java/com/android/server/pm/Android.bp
deleted file mode 100644
index 89c0124..0000000
--- a/services/core/java/com/android/server/pm/Android.bp
+++ /dev/null
@@ -1,12 +0,0 @@
-aconfig_declarations {
-    name: "pm_flags",
-    package: "com.android.server.pm",
-    srcs: [
-        "*.aconfig",
-    ],
-}
-
-java_aconfig_library {
-    name: "pm_flags_lib",
-    aconfig_declarations: "pm_flags",
-}
diff --git a/services/core/java/com/android/server/pm/ComputerEngine.java b/services/core/java/com/android/server/pm/ComputerEngine.java
index 69a6c13..ffa2af1 100644
--- a/services/core/java/com/android/server/pm/ComputerEngine.java
+++ b/services/core/java/com/android/server/pm/ComputerEngine.java
@@ -58,6 +58,7 @@
 import static com.android.server.pm.PackageManagerService.EMPTY_INT_ARRAY;
 import static com.android.server.pm.PackageManagerService.HIDE_EPHEMERAL_APIS;
 import static com.android.server.pm.PackageManagerService.TAG;
+import static com.android.server.pm.PackageManagerServiceUtils.compareSignatureArrays;
 import static com.android.server.pm.PackageManagerServiceUtils.compareSignatures;
 import static com.android.server.pm.PackageManagerServiceUtils.isSystemOrRootOrShell;
 import static com.android.server.pm.resolution.ComponentResolver.RESOLVE_PRIORITY_SORTER;
@@ -4215,8 +4216,7 @@
         if (p2SigningDetails == null) {
             return PackageManager.SIGNATURE_SECOND_NOT_SIGNED;
         }
-        int result = compareSignatures(p1SigningDetails.getSignatures(),
-                p2SigningDetails.getSignatures());
+        int result = compareSignatures(p1SigningDetails, p2SigningDetails);
         if (result == PackageManager.SIGNATURE_MATCH) {
             return result;
         }
@@ -4231,7 +4231,7 @@
             Signature[] p2Signatures = p2SigningDetails.hasPastSigningCertificates()
                     ? new Signature[]{p2SigningDetails.getPastSigningCertificates()[0]}
                     : p2SigningDetails.getSignatures();
-            result = compareSignatures(p1Signatures, p2Signatures);
+            result = compareSignatureArrays(p1Signatures, p2Signatures);
         }
         return result;
     }
diff --git a/services/core/java/com/android/server/pm/IPackageManagerBase.java b/services/core/java/com/android/server/pm/IPackageManagerBase.java
index 8f7b721..76203ac 100644
--- a/services/core/java/com/android/server/pm/IPackageManagerBase.java
+++ b/services/core/java/com/android/server/pm/IPackageManagerBase.java
@@ -30,7 +30,6 @@
 import android.content.IntentFilter;
 import android.content.pm.ActivityInfo;
 import android.content.pm.ApplicationInfo;
-import android.content.pm.IPackageArchiverService;
 import android.content.pm.IPackageDeleteObserver;
 import android.content.pm.IPackageDeleteObserver2;
 import android.content.pm.IPackageInstaller;
@@ -100,9 +99,6 @@
     private final PackageInstallerService mInstallerService;
 
     @NonNull
-    private final PackageArchiverService mPackageArchiverService;
-
-    @NonNull
     private final PackageProperty mPackageProperty;
 
     @NonNull
@@ -131,8 +127,7 @@
             @Nullable ComponentName instantAppResolverSettingsComponent,
             @NonNull String requiredSupplementalProcessPackage,
             @Nullable String servicesExtensionPackageName,
-            @Nullable String sharedSystemSharedLibraryPackageName,
-            @NonNull PackageArchiverService packageArchiverService) {
+            @Nullable String sharedSystemSharedLibraryPackageName) {
         mService = service;
         mContext = context;
         mDexOptHelper = dexOptHelper;
@@ -148,7 +143,6 @@
         mRequiredSupplementalProcessPackage = requiredSupplementalProcessPackage;
         mServicesExtensionPackageName = servicesExtensionPackageName;
         mSharedSystemSharedLibraryPackageName = sharedSystemSharedLibraryPackageName;
-        mPackageArchiverService = packageArchiverService;
     }
 
     protected Computer snapshot() {
@@ -622,12 +616,6 @@
 
     @Override
     @Deprecated
-    public final IPackageArchiverService getPackageArchiverService() {
-        return mPackageArchiverService;
-    }
-
-    @Override
-    @Deprecated
     public final void getPackageSizeInfo(final String packageName, int userId,
             final IPackageStatsObserver observer) {
         throw new UnsupportedOperationException(
diff --git a/services/core/java/com/android/server/pm/InstallPackageHelper.java b/services/core/java/com/android/server/pm/InstallPackageHelper.java
index d668146..468b3a7 100644
--- a/services/core/java/com/android/server/pm/InstallPackageHelper.java
+++ b/services/core/java/com/android/server/pm/InstallPackageHelper.java
@@ -4728,8 +4728,7 @@
                 synchronized (mPm.mLock) {
                     platformPkgSetting = mPm.mSettings.getPackageLPr("android");
                 }
-                if (!comparePackageSignatures(platformPkgSetting,
-                        pkg.getSigningDetails().getSignatures())) {
+                if (!comparePackageSignatures(platformPkgSetting, pkg.getSigningDetails())) {
                     throw PackageManagerException.ofInternalError("Overlay "
                             + pkg.getPackageName()
                             + " must target Q or later, "
@@ -4751,8 +4750,7 @@
                     targetPkgSetting = mPm.mSettings.getPackageLPr(pkg.getOverlayTarget());
                 }
                 if (targetPkgSetting != null) {
-                    if (!comparePackageSignatures(targetPkgSetting,
-                            pkg.getSigningDetails().getSignatures())) {
+                    if (!comparePackageSignatures(targetPkgSetting, pkg.getSigningDetails())) {
                         // check reference signature
                         if (mPm.mOverlayConfigSignaturePackage == null) {
                             throw PackageManagerException.ofInternalError("Overlay "
@@ -4767,8 +4765,7 @@
                             refPkgSetting = mPm.mSettings.getPackageLPr(
                                     mPm.mOverlayConfigSignaturePackage);
                         }
-                        if (!comparePackageSignatures(refPkgSetting,
-                                pkg.getSigningDetails().getSignatures())) {
+                        if (!comparePackageSignatures(refPkgSetting, pkg.getSigningDetails())) {
                             throw PackageManagerException.ofInternalError("Overlay "
                                     + pkg.getPackageName() + " signed with a different "
                                     + "certificate than both the reference package and "
@@ -4799,8 +4796,7 @@
                 synchronized (mPm.mLock) {
                     platformPkgSetting = mPm.mSettings.getPackageLPr("android");
                 }
-                if (!comparePackageSignatures(platformPkgSetting,
-                        pkg.getSigningDetails().getSignatures())) {
+                if (!comparePackageSignatures(platformPkgSetting, pkg.getSigningDetails())) {
                     throw PackageManagerException.ofInternalError("Apps that share a user with a "
                             + "privileged app must themselves be marked as privileged. "
                             + pkg.getPackageName() + " shares privileged user "
@@ -4839,10 +4835,8 @@
                     // to allowlist their privileged permissions just like other
                     // priv-apps.
                     PackageSetting platformPkgSetting = mPm.mSettings.getPackageLPr("android");
-                    if ((compareSignatures(
-                            platformPkgSetting.getSigningDetails().getSignatures(),
-                            pkg.getSigningDetails().getSignatures())
-                            != PackageManager.SIGNATURE_MATCH)) {
+                    if ((compareSignatures(platformPkgSetting.getSigningDetails(),
+                            pkg.getSigningDetails()) != PackageManager.SIGNATURE_MATCH)) {
                         scanFlags |= SCAN_AS_PRIVILEGED;
                     }
                 }
diff --git a/services/core/java/com/android/server/pm/PackageArchiverService.java b/services/core/java/com/android/server/pm/PackageArchiver.java
similarity index 95%
rename from services/core/java/com/android/server/pm/PackageArchiverService.java
rename to services/core/java/com/android/server/pm/PackageArchiver.java
index e052407..64cdca3 100644
--- a/services/core/java/com/android/server/pm/PackageArchiverService.java
+++ b/services/core/java/com/android/server/pm/PackageArchiver.java
@@ -31,10 +31,8 @@
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentSender;
-import android.content.pm.IPackageArchiverService;
 import android.content.pm.LauncherActivityInfo;
 import android.content.pm.LauncherApps;
-import android.content.pm.PackageArchiver;
 import android.content.pm.PackageInstaller;
 import android.content.pm.PackageManager;
 import android.content.pm.VersionedPackage;
@@ -74,7 +72,7 @@
  * while the data directory is kept. Archived apps are included in the list of launcher apps where
  * tapping them re-installs the full app.
  */
-public class PackageArchiverService extends IPackageArchiverService.Stub {
+public class PackageArchiver {
 
     private static final String TAG = "PackageArchiverService";
 
@@ -93,13 +91,12 @@
     @Nullable
     private LauncherApps mLauncherApps;
 
-    public PackageArchiverService(Context context, PackageManagerService mPm) {
+    PackageArchiver(Context context, PackageManagerService mPm) {
         this.mContext = context;
         this.mPm = mPm;
     }
 
-    @Override
-    public void requestArchive(
+    void requestArchive(
             @NonNull String packageName,
             @NonNull String callerPackageName,
             @NonNull IntentSender intentSender,
@@ -112,10 +109,11 @@
         Computer snapshot = mPm.snapshotComputer();
         int userId = userHandle.getIdentifier();
         int binderUid = Binder.getCallingUid();
-        int providedUid = snapshot.getPackageUid(callerPackageName, 0, userId);
+        if (!PackageManagerServiceUtils.isRootOrShell(binderUid)) {
+            verifyCaller(snapshot.getPackageUid(callerPackageName, 0, userId), binderUid);
+        }
         snapshot.enforceCrossUserPermission(binderUid, userId, true, true,
                 "archiveApp");
-        verifyCaller(providedUid, binderUid);
         CompletableFuture<ArchiveState> archiveStateFuture;
         try {
             archiveStateFuture = createArchiveState(packageName, userId);
@@ -233,8 +231,7 @@
         return true;
     }
 
-    @Override
-    public void requestUnarchive(
+    void requestUnarchive(
             @NonNull String packageName,
             @NonNull String callerPackageName,
             @NonNull UserHandle userHandle) {
@@ -245,10 +242,11 @@
         Computer snapshot = mPm.snapshotComputer();
         int userId = userHandle.getIdentifier();
         int binderUid = Binder.getCallingUid();
-        int providedUid = snapshot.getPackageUid(callerPackageName, 0, userId);
+        if (!PackageManagerServiceUtils.isRootOrShell(binderUid)) {
+            verifyCaller(snapshot.getPackageUid(callerPackageName, 0, userId), binderUid);
+        }
         snapshot.enforceCrossUserPermission(binderUid, userId, true, true,
                 "unarchiveApp");
-        verifyCaller(providedUid, binderUid);
         PackageStateInternal ps;
         try {
             ps = getPackageState(packageName, snapshot, binderUid, userId);
@@ -290,8 +288,8 @@
         int userId = userHandle.getIdentifier();
         Intent unarchiveIntent = new Intent(Intent.ACTION_UNARCHIVE_PACKAGE);
         unarchiveIntent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
-        unarchiveIntent.putExtra(PackageArchiver.EXTRA_UNARCHIVE_PACKAGE_NAME, packageName);
-        unarchiveIntent.putExtra(PackageArchiver.EXTRA_UNARCHIVE_ALL_USERS,
+        unarchiveIntent.putExtra(PackageInstaller.EXTRA_UNARCHIVE_PACKAGE_NAME, packageName);
+        unarchiveIntent.putExtra(PackageInstaller.EXTRA_UNARCHIVE_ALL_USERS,
                 userId == UserHandle.USER_ALL);
         unarchiveIntent.setPackage(installerPackage);
 
diff --git a/services/core/java/com/android/server/pm/PackageInstallerService.java b/services/core/java/com/android/server/pm/PackageInstallerService.java
index fabef76..95b565d 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerService.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerService.java
@@ -18,9 +18,7 @@
 
 import static android.app.admin.DevicePolicyResources.Strings.Core.PACKAGE_DELETED_BY_DO;
 import static android.os.Process.INVALID_UID;
-
 import static com.android.server.pm.PackageManagerService.SHELL_PACKAGE_NAME;
-
 import static org.xmlpull.v1.XmlPullParser.END_DOCUMENT;
 import static org.xmlpull.v1.XmlPullParser.START_TAG;
 
@@ -182,6 +180,8 @@
             Manifest.permission.USE_FULL_SCREEN_INTENT
     );
 
+    final PackageArchiver mPackageArchiver;
+
     private final Context mContext;
     private final PackageManagerService mPm;
     private final ApexManager mApexManager;
@@ -301,6 +301,7 @@
                 apexParserSupplier, mInstallThread.getLooper());
         mGentleUpdateHelper = new GentleUpdateHelper(
                 context, mInstallThread.getLooper(), new AppStateHelper(context));
+        mPackageArchiver = new PackageArchiver(mContext, mPm);
 
         LocalServices.getService(SystemServiceManager.class).startService(
                 new Lifecycle(context, this));
@@ -1504,6 +1505,24 @@
         mSilentUpdatePolicy.setSilentUpdatesThrottleTime(throttleTimeInSeconds);
     }
 
+    @Override
+    public void requestArchive(
+            @NonNull String packageName,
+            @NonNull String callerPackageName,
+            @NonNull IntentSender intentSender,
+            @NonNull UserHandle userHandle) {
+        mPackageArchiver.requestArchive(packageName, callerPackageName, intentSender,
+                userHandle);
+    }
+
+    @Override
+    public void requestUnarchive(
+            @NonNull String packageName,
+            @NonNull String callerPackageName,
+            @NonNull UserHandle userHandle) {
+        mPackageArchiver.requestUnarchive(packageName, callerPackageName, userHandle);
+    }
+
     private static int getSessionCount(SparseArray<PackageInstallerSession> sessions,
             int installerUid) {
         int count = 0;
diff --git a/services/core/java/com/android/server/pm/PackageInstallerSession.java b/services/core/java/com/android/server/pm/PackageInstallerSession.java
index 0dd4111ad..9e0a83c 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerSession.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerSession.java
@@ -3396,7 +3396,7 @@
             }
 
             if (!isInstalledByAdb(getInstallSource().mInitiatingPackageName)
-                    && !mPm.mArchiverService.verifySupportsUnarchival(
+                    && !mPm.mInstallerService.mPackageArchiver.verifySupportsUnarchival(
                     getInstallSource().mInstallerPackageName)) {
                 throw new PackageManagerException(
                         PackageManager.INSTALL_FAILED_SESSION_INVALID,
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index e3ff6f6b..d23dcbc 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -84,6 +84,7 @@
 import android.content.pm.DataLoaderType;
 import android.content.pm.FallbackCategoryProvider;
 import android.content.pm.FeatureInfo;
+import android.content.pm.Flags;
 import android.content.pm.IDexModuleRegisterCallback;
 import android.content.pm.IOnChecksumsReadyListener;
 import android.content.pm.IPackageDataObserver;
@@ -799,9 +800,6 @@
     final SparseArray<VerifyingSession> mPendingEnableRollback = new SparseArray<>();
 
     final PackageInstallerService mInstallerService;
-
-    final PackageArchiverService mArchiverService;
-
     final ArtManagerService mArtManagerService;
 
     // TODO(b/260124949): Remove these.
@@ -1630,8 +1628,7 @@
                 (i, pm) -> new CrossProfileIntentFilterHelper(i.getSettings(),
                         i.getUserManagerService(), i.getLock(), i.getUserManagerInternal(),
                         context),
-                (i, pm) -> new UpdateOwnershipHelper(),
-                (i, pm) -> new PackageArchiverService(i.getContext(), pm));
+                (i, pm) -> new UpdateOwnershipHelper());
 
         if (Build.VERSION.SDK_INT <= 0) {
             Slog.w(TAG, "**** ro.build.version.sdk not set!");
@@ -1776,7 +1773,6 @@
         mFactoryTest = testParams.factoryTest;
         mIncrementalManager = testParams.incrementalManager;
         mInstallerService = testParams.installerService;
-        mArchiverService = testParams.archiverService;
         mInstantAppRegistry = testParams.instantAppRegistry;
         mChangedPackagesTracker = testParams.changedPackagesTracker;
         mInstantAppResolverConnection = testParams.instantAppResolverConnection;
@@ -2356,7 +2352,6 @@
             });
 
             mInstallerService = mInjector.getPackageInstallerService();
-            mArchiverService = mInjector.getPackageArchiverService();
             final ComponentName instantAppResolverComponent = getInstantAppResolver(computer);
             if (instantAppResolverComponent != null) {
                 if (DEBUG_INSTANT) {
@@ -4621,7 +4616,7 @@
                     mDomainVerificationConnection, mInstallerService, mPackageProperty,
                     mResolveComponentName, mInstantAppResolverSettingsComponent,
                     mRequiredSdkSandboxPackage, mServicesExtensionPackageName,
-                    mSharedSystemSharedLibraryPackageName, mArchiverService);
+                    mSharedSystemSharedLibraryPackageName);
         }
 
         @Override
@@ -5926,15 +5921,15 @@
                     }
                 }
 
-                Signature[] callerSignature;
+                SigningDetails callerSigningDetails;
                 final int appId = UserHandle.getAppId(callingUid);
                 Pair<PackageStateInternal, SharedUserApi> either =
                         snapshot.getPackageOrSharedUser(appId);
                 if (either != null) {
                     if (either.first != null) {
-                        callerSignature = either.first.getSigningDetails().getSignatures();
+                        callerSigningDetails = either.first.getSigningDetails();
                     } else {
-                        callerSignature = either.second.getSigningDetails().getSignatures();
+                        callerSigningDetails = either.second.getSigningDetails();
                     }
                 } else {
                     throw new SecurityException("Unknown calling UID: " + callingUid);
@@ -5943,8 +5938,8 @@
                 // Verify: can't set installerPackageName to a package that is
                 // not signed with the same cert as the caller.
                 if (installerPackageState != null) {
-                    if (compareSignatures(callerSignature,
-                            installerPackageState.getSigningDetails().getSignatures())
+                    if (compareSignatures(callerSigningDetails,
+                            installerPackageState.getSigningDetails())
                             != PackageManager.SIGNATURE_MATCH) {
                         throw new SecurityException(
                                 "Caller does not have same cert as new installer package "
@@ -5960,8 +5955,8 @@
                         ? null : snapshot.getPackageStateInternal(targetInstallerPackageName);
 
                 if (targetInstallerPkgSetting != null) {
-                    if (compareSignatures(callerSignature,
-                            targetInstallerPkgSetting.getSigningDetails().getSignatures())
+                    if (compareSignatures(callerSigningDetails,
+                            targetInstallerPkgSetting.getSigningDetails())
                             != PackageManager.SIGNATURE_MATCH) {
                         throw new SecurityException(
                                 "Caller does not have same cert as old installer package "
diff --git a/services/core/java/com/android/server/pm/PackageManagerServiceInjector.java b/services/core/java/com/android/server/pm/PackageManagerServiceInjector.java
index 9495279..0c2e082 100644
--- a/services/core/java/com/android/server/pm/PackageManagerServiceInjector.java
+++ b/services/core/java/com/android/server/pm/PackageManagerServiceInjector.java
@@ -127,8 +127,7 @@
             mPreparingPackageParserProducer;
     private final Singleton<PackageInstallerService>
             mPackageInstallerServiceProducer;
-    private final Singleton<PackageArchiverService>
-            mPackageArchiverServiceProducer;
+
     private final ProducerWithArgument<InstantAppResolverConnection, ComponentName>
             mInstantAppResolverConnectionProducer;
     private final Singleton<LegacyPermissionManagerInternal>
@@ -187,8 +186,7 @@
             Producer<IBackupManager> iBackupManager,
             Producer<SharedLibrariesImpl> sharedLibrariesProducer,
             Producer<CrossProfileIntentFilterHelper> crossProfileIntentFilterHelperProducer,
-            Producer<UpdateOwnershipHelper> updateOwnershipHelperProducer,
-            Producer<PackageArchiverService> packageArchiverServiceProducer) {
+            Producer<UpdateOwnershipHelper> updateOwnershipHelperProducer) {
         mContext = context;
         mLock = lock;
         mInstaller = installer;
@@ -244,7 +242,6 @@
         mCrossProfileIntentFilterHelperProducer = new Singleton<>(
                 crossProfileIntentFilterHelperProducer);
         mUpdateOwnershipHelperProducer = new Singleton<>(updateOwnershipHelperProducer);
-        mPackageArchiverServiceProducer = new Singleton<>(packageArchiverServiceProducer);
     }
 
     /**
@@ -391,10 +388,6 @@
         return mPackageInstallerServiceProducer.get(this, mPackageManager);
     }
 
-    public PackageArchiverService getPackageArchiverService() {
-        return mPackageArchiverServiceProducer.get(this, mPackageManager);
-    }
-
     public InstantAppResolverConnection getInstantAppResolverConnection(
             ComponentName instantAppResolverComponent) {
         return mInstantAppResolverConnectionProducer.produce(
diff --git a/services/core/java/com/android/server/pm/PackageManagerServiceTestParams.java b/services/core/java/com/android/server/pm/PackageManagerServiceTestParams.java
index b91ce4b..ca57209 100644
--- a/services/core/java/com/android/server/pm/PackageManagerServiceTestParams.java
+++ b/services/core/java/com/android/server/pm/PackageManagerServiceTestParams.java
@@ -60,7 +60,6 @@
     public @Nullable String incidentReportApproverPackage;
     public IncrementalManager incrementalManager;
     public PackageInstallerService installerService;
-    public PackageArchiverService archiverService;
     public InstantAppRegistry instantAppRegistry;
     public ChangedPackagesTracker changedPackagesTracker = new ChangedPackagesTracker();
     public InstantAppResolverConnection instantAppResolverConnection;
diff --git a/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java b/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java
index 2028231..1679987 100644
--- a/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java
+++ b/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java
@@ -403,7 +403,11 @@
      * <br />
      * {@link PackageManager#SIGNATURE_NO_MATCH}: if the two signature sets differ.
      */
-    public static int compareSignatures(Signature[] s1, Signature[] s2) {
+    public static int compareSignatures(SigningDetails sd1, SigningDetails sd2) {
+        return compareSignatureArrays(sd1.getSignatures(), sd2.getSignatures());
+    }
+
+    static int compareSignatureArrays(Signature[] s1, Signature[] s2) {
         if (s1 == null) {
             return s2 == null
                     ? PackageManager.SIGNATURE_NEITHER_SIGNED
@@ -445,10 +449,10 @@
      * set or if the signing details of the package are unknown.
      */
     public static boolean comparePackageSignatures(PackageSetting pkgSetting,
-            Signature[] signatures) {
+            SigningDetails otherSigningDetails) {
         final SigningDetails signingDetails = pkgSetting.getSigningDetails();
         return signingDetails == SigningDetails.UNKNOWN
-                || compareSignatures(signingDetails.getSignatures(), signatures)
+                || compareSignatures(signingDetails, otherSigningDetails)
                 == PackageManager.SIGNATURE_MATCH;
     }
 
diff --git a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
index a0c9cd1..8d82085 100644
--- a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
+++ b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
@@ -26,7 +26,6 @@
 import static android.content.pm.PackageManager.RESTRICTION_HIDE_FROM_SUGGESTIONS;
 import static android.content.pm.PackageManager.RESTRICTION_HIDE_NOTIFICATIONS;
 import static android.content.pm.PackageManager.RESTRICTION_NONE;
-
 import static com.android.server.LocalManagerRegistry.ManagerNotFoundException;
 import static com.android.server.pm.PackageManagerService.PLATFORM_PACKAGE_NAME;
 
@@ -334,6 +333,8 @@
                     return runRenameUser();
                 case "set-user-restriction":
                     return runSetUserRestriction();
+                case "get-user-restriction":
+                    return runGetUserRestriction();
                 case "supports-multiple-users":
                     return runSupportsMultipleUsers();
                 case "get-max-users":
@@ -379,6 +380,10 @@
                     return runWaitForHandler(/* forBackgroundHandler= */ false);
                 case "wait-for-background-handler":
                     return runWaitForHandler(/* forBackgroundHandler= */ true);
+                case "archive":
+                    return runArchive();
+                case "request-unarchive":
+                    return runUnarchive();
                 default: {
                     if (ART_SERVICE_COMMANDS.contains(cmd)) {
                         if (DexOptHelper.useArtService()) {
@@ -3405,6 +3410,51 @@
         return 0;
     }
 
+    private int runGetUserRestriction() throws RemoteException {
+        final PrintWriter pw = getOutPrintWriter();
+        int userId = UserHandle.USER_SYSTEM;
+        boolean getAllRestrictions = false;
+
+        String opt;
+        while ((opt = getNextOption()) != null) {
+            switch (opt) {
+                case "--user":
+                    userId = UserHandle.parseUserArg(getNextArgRequired());
+                    break;
+                case "--all":
+                    getAllRestrictions = true;
+                    if (getNextArg() != null) {
+                        throw new IllegalArgumentException("Argument unexpected after \"--all\"");
+                    }
+                    break;
+                default:
+                    throw new IllegalArgumentException("Unknown option " + opt);
+            }
+        }
+
+        final int translatedUserId =
+                translateUserId(userId, UserHandle.USER_NULL, "runGetUserRestriction");
+        final IUserManager um = IUserManager.Stub.asInterface(
+                ServiceManager.getService(Context.USER_SERVICE));
+
+        if (getAllRestrictions) {
+            final Bundle restrictions = um.getUserRestrictions(translatedUserId);
+            pw.println("All restrictions:");
+            pw.println(restrictions.toString());
+        } else {
+            String restriction = getNextArg();
+            if (restriction == null) {
+                throw new IllegalArgumentException("No restriction key specified");
+            }
+            String unexpectedArgument = getNextArg();
+            if (unexpectedArgument != null) {
+                throw new IllegalArgumentException("Argument unexpected after restriction key");
+            }
+            pw.println(um.hasUserRestriction(restriction, translatedUserId));
+        }
+        return 0;
+    }
+
     public int runSupportsMultipleUsers() {
         getOutPrintWriter().println("Is multiuser supported: "
                 + UserManager.supportsMultipleUsers());
@@ -4485,6 +4535,105 @@
         }
     }
 
+    private int runArchive() throws RemoteException {
+        final PrintWriter pw = getOutPrintWriter();
+        int userId = UserHandle.USER_ALL;
+
+        String opt;
+        while ((opt = getNextOption()) != null) {
+            if (opt.equals("--user")) {
+                userId = UserHandle.parseUserArg(getNextArgRequired());
+                if (userId != UserHandle.USER_ALL && userId != UserHandle.USER_CURRENT) {
+                    UserManagerInternal umi =
+                            LocalServices.getService(UserManagerInternal.class);
+                    UserInfo userInfo = umi.getUserInfo(userId);
+                    if (userInfo == null) {
+                        pw.println("Failure [user " + userId + " doesn't exist]");
+                        return 1;
+                    }
+                }
+            } else {
+                pw.println("Error: Unknown option: " + opt);
+                return 1;
+            }
+        }
+
+        final String packageName = getNextArg();
+        if (packageName == null) {
+            pw.println("Error: package name not specified");
+            return 1;
+        }
+
+        final int translatedUserId =
+                translateUserId(userId, UserHandle.USER_SYSTEM, "runArchive");
+        final LocalIntentReceiver receiver = new LocalIntentReceiver();
+
+        try {
+            mInterface.getPackageInstaller().requestArchive(packageName,
+                    /* callerPackageName= */ "", receiver.getIntentSender(),
+                    new UserHandle(translatedUserId));
+        } catch (Exception e) {
+            pw.println("Failure [" + e.getMessage() + "]");
+            return 1;
+        }
+
+        final Intent result = receiver.getResult();
+        final int status = result.getIntExtra(PackageInstaller.EXTRA_STATUS,
+                PackageInstaller.STATUS_FAILURE);
+        if (status == PackageInstaller.STATUS_SUCCESS) {
+            pw.println("Success");
+            return 0;
+        } else {
+            pw.println("Failure ["
+                    + result.getStringExtra(PackageInstaller.EXTRA_STATUS_MESSAGE) + "]");
+            return 1;
+        }
+    }
+
+    private int runUnarchive() throws RemoteException {
+        final PrintWriter pw = getOutPrintWriter();
+        int userId = UserHandle.USER_ALL;
+
+        String opt;
+        while ((opt = getNextOption()) != null) {
+            if (opt.equals("--user")) {
+                userId = UserHandle.parseUserArg(getNextArgRequired());
+                if (userId != UserHandle.USER_ALL && userId != UserHandle.USER_CURRENT) {
+                    UserManagerInternal umi =
+                            LocalServices.getService(UserManagerInternal.class);
+                    UserInfo userInfo = umi.getUserInfo(userId);
+                    if (userInfo == null) {
+                        pw.println("Failure [user " + userId + " doesn't exist]");
+                        return 1;
+                    }
+                }
+            } else {
+                pw.println("Error: Unknown option: " + opt);
+                return 1;
+            }
+        }
+
+        final String packageName = getNextArg();
+        if (packageName == null) {
+            pw.println("Error: package name not specified");
+            return 1;
+        }
+
+        final int translatedUserId =
+                translateUserId(userId, UserHandle.USER_SYSTEM, "runArchive");
+
+        try {
+            mInterface.getPackageInstaller().requestUnarchive(packageName,
+                    /* callerPackageName= */ "", new UserHandle(translatedUserId));
+        } catch (Exception e) {
+            pw.println("Failure [" + e.getMessage() + "]");
+            return 1;
+        }
+
+        pw.println("Success");
+        return 0;
+    }
+
     @Override
     public void onHelp() {
         final PrintWriter pw = getOutPrintWriter();
@@ -4788,6 +4937,12 @@
         pw.println("");
         pw.println("  set-user-restriction [--user USER_ID] RESTRICTION VALUE");
         pw.println("");
+        pw.println("  get-user-restriction [--user USER_ID] [--all] RESTRICTION_KEY");
+        pw.println("    Display the value of restriction for the given restriction key if the");
+        pw.println("    given user is valid.");
+        pw.println("      --all: display all restrictions for the given user");
+        pw.println("          This option is used without restriction key");
+        pw.println("");
         pw.println("  get-max-users");
         pw.println("");
         pw.println("  get-max-running-users");
@@ -4851,6 +5006,16 @@
         pw.println("      --timeout: wait for a given number of milliseconds. If the handler(s)");
         pw.println("        fail to finish before the timeout, the command returns error.");
         pw.println("");
+        pw.println("  archive [--user USER_ID] PACKAGE ");
+        pw.println("    During the archival process, the apps APKs and cache are removed from the");
+        pw.println("    device while the user data is kept. Options are:");
+        pw.println("      --user: archive the app from the given user.");
+        pw.println("");
+        pw.println("  request-unarchive [--user USER_ID] PACKAGE ");
+        pw.println("    Requests to unarchive a currently archived package by sending a request");
+        pw.println("    to unarchive an app to the responsible installer. Options are:");
+        pw.println("      --user: request unarchival of the app from the given user.");
+        pw.println("");
         if (DexOptHelper.useArtService()) {
             printArtServiceHelp();
         } else {
diff --git a/services/core/java/com/android/server/pm/SELinuxMMAC.java b/services/core/java/com/android/server/pm/SELinuxMMAC.java
index a8cdef4..cf5aa7b 100644
--- a/services/core/java/com/android/server/pm/SELinuxMMAC.java
+++ b/services/core/java/com/android/server/pm/SELinuxMMAC.java
@@ -605,7 +605,7 @@
         // Check for exact signature matches across all certs.
         Signature[] certs = mCerts.toArray(new Signature[0]);
         if (pkg.getSigningDetails() != SigningDetails.UNKNOWN
-                && !Signature.areExactMatch(certs, pkg.getSigningDetails().getSignatures())) {
+                && !Signature.areExactMatch(pkg.getSigningDetails(), certs)) {
 
             // certs aren't exact match, but the package may have rotated from the known system cert
             if (certs.length > 1 || !pkg.getSigningDetails().hasCertificate(certs[0])) {
diff --git a/services/core/java/com/android/server/pm/ScanPackageUtils.java b/services/core/java/com/android/server/pm/ScanPackageUtils.java
index f4dca3f..0cac790 100644
--- a/services/core/java/com/android/server/pm/ScanPackageUtils.java
+++ b/services/core/java/com/android/server/pm/ScanPackageUtils.java
@@ -911,8 +911,8 @@
         parsedPackage.setSignedWithPlatformKey(
                 (PLATFORM_PACKAGE_NAME.equals(parsedPackage.getPackageName())
                         || (platformPkg != null && compareSignatures(
-                        platformPkg.getSigningDetails().getSignatures(),
-                        parsedPackage.getSigningDetails().getSignatures()
+                        platformPkg.getSigningDetails(),
+                        parsedPackage.getSigningDetails()
                 ) == PackageManager.SIGNATURE_MATCH))
         );
 
diff --git a/services/core/java/com/android/server/pm/flags.aconfig b/services/core/java/com/android/server/pm/flags.aconfig
deleted file mode 100644
index e584801..0000000
--- a/services/core/java/com/android/server/pm/flags.aconfig
+++ /dev/null
@@ -1,9 +0,0 @@
-package: "com.android.server.pm"
-
-flag {
-    name: "quarantined_enabled"
-    namespace: "package_manager_service"
-    description: "Feature flag for Quarantined state"
-    bug: "269127435"
-}
-
diff --git a/services/core/java/com/android/server/pm/pkg/SuspendParams.java b/services/core/java/com/android/server/pm/pkg/SuspendParams.java
index 4e08106..86391c9 100644
--- a/services/core/java/com/android/server/pm/pkg/SuspendParams.java
+++ b/services/core/java/com/android/server/pm/pkg/SuspendParams.java
@@ -17,6 +17,7 @@
 package com.android.server.pm.pkg;
 
 import android.annotation.Nullable;
+import android.content.pm.Flags;
 import android.content.pm.SuspendDialogInfo;
 import android.os.BaseBundle;
 import android.os.PersistableBundle;
@@ -24,7 +25,6 @@
 
 import com.android.modules.utils.TypedXmlPullParser;
 import com.android.modules.utils.TypedXmlSerializer;
-import com.android.server.pm.Flags;
 
 import org.xmlpull.v1.XmlPullParser;
 import org.xmlpull.v1.XmlPullParserException;
diff --git a/services/core/java/com/android/server/pm/pkg/parsing/ParsingPackageUtils.java b/services/core/java/com/android/server/pm/pkg/parsing/ParsingPackageUtils.java
index 81c2f07..812e228 100644
--- a/services/core/java/com/android/server/pm/pkg/parsing/ParsingPackageUtils.java
+++ b/services/core/java/com/android/server/pm/pkg/parsing/ParsingPackageUtils.java
@@ -3267,8 +3267,7 @@
         if (existingSigningDetails == SigningDetails.UNKNOWN) {
             return verified;
         } else {
-            if (!Signature.areExactMatch(existingSigningDetails.getSignatures(),
-                    verified.getResult().getSignatures())) {
+            if (!Signature.areExactMatch(existingSigningDetails, verified.getResult())) {
                 return input.error(INSTALL_PARSE_FAILED_INCONSISTENT_CERTIFICATES,
                         baseCodePath + " has mismatched certificates");
             }
diff --git a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
index ddc0519..cfc7031 100644
--- a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
+++ b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
@@ -326,9 +326,6 @@
                             if (DEBUG) {
                                 Slog.d(TAG, "publish system wallpaper changed!");
                             }
-                            if (localSync != null) {
-                                localSync.complete();
-                            }
                             notifyWallpaperChanged(wallpaper);
                         }
                     };
@@ -336,7 +333,7 @@
                     // If this was the system wallpaper, rebind...
                     bindWallpaperComponentLocked(mImageWallpaper, true, false, wallpaper,
                             callback);
-                    notifyColorsWhich |= FLAG_SYSTEM;
+                    notifyColorsWhich |= wallpaper.mWhich;
                 }
 
                 if (lockWallpaperChanged) {
@@ -350,9 +347,6 @@
                             if (DEBUG) {
                                 Slog.d(TAG, "publish lock wallpaper changed!");
                             }
-                            if (localSync != null) {
-                                localSync.complete();
-                            }
                             notifyWallpaperChanged(wallpaper);
                         }
                     };
@@ -377,9 +371,8 @@
                 }
 
                 saveSettingsLocked(wallpaper.userId);
-                // Notify the client immediately if only lockscreen wallpaper changed.
-                if (lockWallpaperChanged && !sysWallpaperChanged) {
-                    notifyWallpaperChanged(wallpaper);
+                if ((sysWallpaperChanged || lockWallpaperChanged) && localSync != null) {
+                    localSync.complete();
                 }
             }
 
@@ -1426,7 +1419,6 @@
                             lockWp.connection.mWallpaper = lockWp;
                             mOriginalSystem.mWhich = FLAG_LOCK;
                             updateEngineFlags(mOriginalSystem);
-                            notifyWallpaperColorsChanged(lockWp, FLAG_LOCK);
                         } else {
                             // Failed rename, use current system wp for both
                             if (DEBUG) {
@@ -1446,7 +1438,6 @@
                         updateEngineFlags(mOriginalSystem);
                         mLockWallpaperMap.put(mNewWallpaper.userId, mOriginalSystem);
                         mLastLockWallpaper = mOriginalSystem;
-                        notifyWallpaperColorsChanged(mOriginalSystem, FLAG_LOCK);
                     }
                 } else if (mNewWallpaper.mWhich == FLAG_LOCK) {
                     // New wp is lock only, so old system+lock is now system only
@@ -1460,10 +1451,7 @@
                     }
                 }
             }
-
-            synchronized (mLock) {
-                saveSettingsLocked(mNewWallpaper.userId);
-            }
+            saveSettingsLocked(mNewWallpaper.userId);
 
             if (DEBUG) {
                 Slog.v(TAG, "--- wallpaper changed --");
@@ -3340,7 +3328,6 @@
                         if (DEBUG) {
                             Slog.d(TAG, "publish system wallpaper changed!");
                         }
-                        liveSync.complete();
                     }
                 };
 
@@ -3396,6 +3383,7 @@
                         }
                         mLockWallpaperMap.remove(newWallpaper.userId);
                     }
+                    if (liveSync != null) liveSync.complete();
                 }
             } finally {
                 Binder.restoreCallingIdentity(ident);
@@ -3514,6 +3502,11 @@
         }
         // Has the component changed?
         if (!force && changingToSame(componentName, wallpaper)) {
+            try {
+                if (reply != null) reply.sendResult(null);
+            } catch (RemoteException e) {
+                Slog.e(TAG, "Failed to send callback", e);
+            }
             return true;
         }
 
diff --git a/services/core/java/com/android/server/wm/ActivityClientController.java b/services/core/java/com/android/server/wm/ActivityClientController.java
index b823e73..4c9ec9d 100644
--- a/services/core/java/com/android/server/wm/ActivityClientController.java
+++ b/services/core/java/com/android/server/wm/ActivityClientController.java
@@ -492,6 +492,8 @@
                 final boolean res;
                 final boolean finishWithRootActivity =
                         finishTask == Activity.FINISH_TASK_WITH_ROOT_ACTIVITY;
+                mTaskSupervisor.getBackgroundActivityLaunchController()
+                        .onActivityRequestedFinishing(r);
                 if (finishTask == Activity.FINISH_TASK_WITH_ACTIVITY
                         || (finishWithRootActivity && r == rootR)) {
                     // If requested, remove the task that is associated to this activity only if it
@@ -1018,7 +1020,7 @@
         try {
             final ClientTransaction transaction = ClientTransaction.obtain(
                     r.app.getThread(), r.token);
-            transaction.addCallback(EnterPipRequestedItem.obtain());
+            transaction.addCallback(EnterPipRequestedItem.obtain(r.token));
             mService.getLifecycleManager().scheduleTransaction(transaction);
             return true;
         } catch (Exception e) {
@@ -1040,7 +1042,7 @@
         try {
             final ClientTransaction transaction = ClientTransaction.obtain(
                     r.app.getThread(), r.token);
-            transaction.addCallback(PipStateTransactionItem.obtain(pipState));
+            transaction.addCallback(PipStateTransactionItem.obtain(r.token, pipState));
             mService.getLifecycleManager().scheduleTransaction(transaction);
         } catch (Exception e) {
             Slog.w(TAG, "Failed to send pip state transaction item: "
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index 582536b..dca2b6f 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -324,7 +324,6 @@
 import android.util.EventLog;
 import android.util.Log;
 import android.util.MergedConfiguration;
-import android.util.Pair;
 import android.util.Slog;
 import android.util.TimeUtils;
 import android.util.proto.ProtoOutputStream;
@@ -725,9 +724,9 @@
     private final boolean mIsUserAlwaysVisible;
 
     /** Allow activity launches which would otherwise be blocked by
-     * {@link ActivityTransitionSecurityController#checkActivityAllowedToStart}
+     * {@link BackgroundActivityStartController#checkActivityAllowedToStart}
      */
-    private boolean mAllowCrossUidActivitySwitchFromBelow;
+    boolean mAllowCrossUidActivitySwitchFromBelow;
 
     /** Have we been asked to have this token keep the screen frozen? */
     private boolean mFreezingScreen;
@@ -1444,7 +1443,7 @@
                     config);
 
             mAtmService.getLifecycleManager().scheduleTransaction(app.getThread(), token,
-                    MoveToDisplayItem.obtain(displayId, config));
+                    MoveToDisplayItem.obtain(token, displayId, config));
         } catch (RemoteException e) {
             // If process died, whatever.
         }
@@ -1461,7 +1460,7 @@
                     + "config: %s", this, config);
 
             mAtmService.getLifecycleManager().scheduleTransaction(app.getThread(), token,
-                    ActivityConfigurationChangeItem.obtain(config));
+                    ActivityConfigurationChangeItem.obtain(token, config));
         } catch (RemoteException e) {
             // If process died, whatever.
         }
@@ -1482,7 +1481,7 @@
                     this, onTop);
 
             mAtmService.getLifecycleManager().scheduleTransaction(app.getThread(), token,
-                    TopResumedActivityChangeItem.obtain(onTop));
+                    TopResumedActivityChangeItem.obtain(token, onTop));
         } catch (RemoteException e) {
             // If process died, whatever.
             Slog.w(TAG, "Failed to send top-resumed=" + onTop + " to " + this, e);
@@ -2729,7 +2728,7 @@
         try {
             mTransferringSplashScreenState = TRANSFER_SPLASH_SCREEN_ATTACH_TO_CLIENT;
             mAtmService.getLifecycleManager().scheduleTransaction(app.getThread(), token,
-                    TransferSplashScreenViewStateItem.obtain(parcelable,
+                    TransferSplashScreenViewStateItem.obtain(token, parcelable,
                             windowAnimationLeash));
             scheduleTransferSplashScreenTimeout();
         } catch (Exception e) {
@@ -3896,7 +3895,7 @@
             try {
                 if (DEBUG_SWITCH) Slog.i(TAG_SWITCH, "Destroying: " + this);
                 mAtmService.getLifecycleManager().scheduleTransaction(app.getThread(), token,
-                        DestroyActivityItem.obtain(finishing, configChangeFlags));
+                        DestroyActivityItem.obtain(token, finishing, configChangeFlags));
             } catch (Exception e) {
                 // We can just ignore exceptions here...  if the process has crashed, our death
                 // notification will clean things up.
@@ -4804,7 +4803,7 @@
                 final ArrayList<ResultInfo> list = new ArrayList<ResultInfo>();
                 list.add(new ResultInfo(resultWho, requestCode, resultCode, data));
                 mAtmService.getLifecycleManager().scheduleTransaction(app.getThread(), token,
-                        ActivityResultItem.obtain(list));
+                        ActivityResultItem.obtain(token, list));
                 return;
             } catch (Exception e) {
                 Slog.w(TAG, "Exception thrown sending result to " + this, e);
@@ -4817,7 +4816,7 @@
             final ClientTransaction transaction = ClientTransaction.obtain(app.getThread(), token);
             // Build result to be returned immediately.
             transaction.addCallback(ActivityResultItem.obtain(
-                    List.of(new ResultInfo(resultWho, requestCode, resultCode, data))));
+                    token, List.of(new ResultInfo(resultWho, requestCode, resultCode, data))));
             // When the activity result is delivered, the activity will transition to RESUMED.
             // Since the activity is only resumed so the result can be immediately delivered,
             // return it to its original lifecycle state.
@@ -4858,13 +4857,13 @@
     private ActivityLifecycleItem getLifecycleItemForCurrentStateForResult() {
         switch (mState) {
             case STARTED:
-                return StartActivityItem.obtain(null);
+                return StartActivityItem.obtain(token, null);
             case PAUSING:
             case PAUSED:
-                return PauseActivityItem.obtain();
+                return PauseActivityItem.obtain(token);
             case STOPPING:
             case STOPPED:
-                return StopActivityItem.obtain(configChangeFlags);
+                return StopActivityItem.obtain(token, configChangeFlags);
             default:
                 // Do not send a result immediately if the activity is in state INITIALIZING,
                 // RESTARTING_PROCESS, FINISHING, DESTROYING, or DESTROYED.
@@ -4910,7 +4909,7 @@
                 // so only if activity is currently RESUMED. Otherwise, client may have extra
                 // life-cycle calls to RESUMED (and PAUSED later).
                 mAtmService.getLifecycleManager().scheduleTransaction(app.getThread(), token,
-                        NewIntentItem.obtain(ar, mState == RESUMED));
+                        NewIntentItem.obtain(token, ar, mState == RESUMED));
                 unsent = false;
             } catch (RemoteException e) {
                 Slog.w(TAG, "Exception thrown sending new intent to " + this, e);
@@ -6145,7 +6144,7 @@
                     shortComponentName, "userLeaving=false", "make-active");
             try {
                 mAtmService.getLifecycleManager().scheduleTransaction(app.getThread(), token,
-                        PauseActivityItem.obtain(finishing, false /* userLeaving */,
+                        PauseActivityItem.obtain(token, finishing, false /* userLeaving */,
                                 configChangeFlags, false /* dontReport */, mAutoEnteringPip));
             } catch (Exception e) {
                 Slog.w(TAG, "Exception thrown sending pause: " + intent.getComponent(), e);
@@ -6158,7 +6157,7 @@
 
             try {
                 mAtmService.getLifecycleManager().scheduleTransaction(app.getThread(), token,
-                        StartActivityItem.obtain(takeOptions()));
+                        StartActivityItem.obtain(token, takeOptions()));
             } catch (Exception e) {
                 Slog.w(TAG, "Exception thrown sending start: " + intent.getComponent(), e);
             }
@@ -6456,7 +6455,7 @@
             EventLogTags.writeWmStopActivity(
                     mUserId, System.identityHashCode(this), shortComponentName);
             mAtmService.getLifecycleManager().scheduleTransaction(app.getThread(), token,
-                    StopActivityItem.obtain(configChangeFlags));
+                    StopActivityItem.obtain(token, configChangeFlags));
 
             mAtmService.mH.postDelayed(mStopTimeoutRunnable, STOP_TIMEOUT);
         } catch (Exception e) {
@@ -9871,17 +9870,17 @@
                     (andResume ? "RESUMED" : "PAUSED"), this, Debug.getCallers(6));
             forceNewConfig = false;
             startRelaunching();
-            final ClientTransactionItem callbackItem = ActivityRelaunchItem.obtain(pendingResults,
-                    pendingNewIntents, configChangeFlags,
+            final ClientTransactionItem callbackItem = ActivityRelaunchItem.obtain(token,
+                    pendingResults, pendingNewIntents, configChangeFlags,
                     new MergedConfiguration(getProcessGlobalConfiguration(),
                             getMergedOverrideConfiguration()),
                     preserveWindow);
             final ActivityLifecycleItem lifecycleItem;
             if (andResume) {
-                lifecycleItem = ResumeActivityItem.obtain(isTransitionForward(),
+                lifecycleItem = ResumeActivityItem.obtain(token, isTransitionForward(),
                         shouldSendCompatFakeFocus());
             } else {
-                lifecycleItem = PauseActivityItem.obtain();
+                lifecycleItem = PauseActivityItem.obtain(token);
             }
             final ClientTransaction transaction = ClientTransaction.obtain(app.getThread(), token);
             transaction.addCallback(callbackItem);
@@ -9978,7 +9977,7 @@
         // {@link ActivityTaskManagerService.activityStopped}).
         try {
             mAtmService.getLifecycleManager().scheduleTransaction(app.getThread(), token,
-                    StopActivityItem.obtain(0 /* configChanges */));
+                    StopActivityItem.obtain(token, 0 /* configChanges */));
         } catch (RemoteException e) {
             Slog.w(TAG, "Exception thrown during restart " + this, e);
         }
@@ -10191,50 +10190,6 @@
         mAllowCrossUidActivitySwitchFromBelow = allowed;
     }
 
-    /**
-     * Determines if a source is allowed to add or remove activities from the task,
-     * if the current ActivityRecord is above it in the stack
-     *
-     * A transition is blocked ({@code false} returned) if all of the following are met:
-     * <pre>
-     * 1. The source activity and the current activity record belong to different apps
-     * (i.e, have different UIDs).
-     * 2. Both the source activity and the current activity target U+
-     * 3. The current activity has not set
-     * {@link ActivityRecord#setAllowCrossUidActivitySwitchFromBelow(boolean)} to {@code true}
-     * </pre>
-     *
-     * Returns a pair where the elements mean:
-     * <pre>
-     * First: {@code false} if we should actually block the transition (takes into consideration
-     * feature flag and targetSdk).
-     * Second: {@code false} if we should warn about the transition via toasts. This happens if
-     * the transition would be blocked in case both the app was targeting U+ and the feature was
-     * enabled.
-     * </pre>
-     *
-     * @param sourceUid The source (s) activity performing the state change
-     */
-    Pair<Boolean, Boolean> allowCrossUidActivitySwitchFromBelow(int sourceUid) {
-        int myUid = info.applicationInfo.uid;
-        if (sourceUid == myUid) {
-            return new Pair<>(true, true);
-        }
-
-        // If mAllowCrossUidActivitySwitchFromBelow is set, honor it.
-        if (mAllowCrossUidActivitySwitchFromBelow) {
-            return new Pair<>(true, true);
-        }
-
-        // If it is not set, default to true if both records target ≥ U, false otherwise
-        // TODO(b/258792202) Replace with CompatChanges and replace Pair with boolean once feature
-        // flag is removed
-        boolean restrictActivitySwitch =
-                ActivitySecurityModelFeatureFlags.shouldRestrictActivitySwitch(myUid)
-                    && ActivitySecurityModelFeatureFlags.shouldRestrictActivitySwitch(sourceUid);
-        return new Pair<>(!restrictActivitySwitch, false);
-    }
-
     boolean getTurnScreenOnFlag() {
         return mTurnScreenOn || containsTurnScreenOnWindow();
     }
diff --git a/services/core/java/com/android/server/wm/ActivitySecurityModelFeatureFlags.java b/services/core/java/com/android/server/wm/ActivitySecurityModelFeatureFlags.java
index 19d8129..f1a2159 100644
--- a/services/core/java/com/android/server/wm/ActivitySecurityModelFeatureFlags.java
+++ b/services/core/java/com/android/server/wm/ActivitySecurityModelFeatureFlags.java
@@ -43,7 +43,7 @@
     static final String DOC_LINK = "go/android-asm";
 
     /** Used to determine which version of the ASM logic was used in logs while we iterate */
-    static final int ASM_VERSION = 7;
+    static final int ASM_VERSION = 8;
 
     private static final String NAMESPACE = NAMESPACE_WINDOW_MANAGER;
     private static final String KEY_ASM_PREFIX = "ActivitySecurity__";
@@ -53,7 +53,7 @@
     private static final String KEY_ASM_EXEMPTED_PACKAGES = KEY_ASM_PREFIX
             + "asm_exempted_packages";
     private static final int VALUE_DISABLE = 0;
-    private static final int VALUE_ENABLE_FOR_U = 1;
+    private static final int VALUE_ENABLE_FOR_V = 1;
     private static final int VALUE_ENABLE_FOR_ALL = 2;
 
     private static final int DEFAULT_VALUE = VALUE_DISABLE;
@@ -84,7 +84,7 @@
 
     private static boolean flagEnabledForUid(int flag, int uid) {
         boolean flagEnabled = flag == VALUE_ENABLE_FOR_ALL
-                || (flag == VALUE_ENABLE_FOR_U
+                || (flag == VALUE_ENABLE_FOR_V
                     && CompatChanges.isChangeEnabled(ASM_RESTRICTIONS, uid));
 
         if (flagEnabled) {
diff --git a/services/core/java/com/android/server/wm/ActivityStartController.java b/services/core/java/com/android/server/wm/ActivityStartController.java
index a6e5040..c39b266 100644
--- a/services/core/java/com/android/server/wm/ActivityStartController.java
+++ b/services/core/java/com/android/server/wm/ActivityStartController.java
@@ -98,8 +98,6 @@
     /** Whether an {@link ActivityStarter} is currently executing (starting an Activity). */
     private boolean mInExecution = false;
 
-    private final BackgroundActivityStartController mBalController;
-
     /**
      * TODO(b/64750076): Capture information necessary for dump and
      * {@link #postStartActivityProcessingForLastStarter} rather than keeping the entire object
@@ -122,7 +120,6 @@
         mFactory.setController(this);
         mPendingRemoteAnimationRegistry = new PendingRemoteAnimationRegistry(service.mGlobalLock,
                 service.mH);
-        mBalController = new BackgroundActivityStartController(mService, mSupervisor);
     }
 
     /**
@@ -666,8 +663,4 @@
             pw.println("(nothing)");
         }
     }
-
-    BackgroundActivityStartController getBackgroundActivityLaunchController() {
-        return mBalController;
-    }
 }
diff --git a/services/core/java/com/android/server/wm/ActivityStarter.java b/services/core/java/com/android/server/wm/ActivityStarter.java
index 458d1e8..27315bb 100644
--- a/services/core/java/com/android/server/wm/ActivityStarter.java
+++ b/services/core/java/com/android/server/wm/ActivityStarter.java
@@ -74,16 +74,8 @@
 import static com.android.server.wm.ActivityTaskSupervisor.DEFER_RESUME;
 import static com.android.server.wm.ActivityTaskSupervisor.ON_TOP;
 import static com.android.server.wm.ActivityTaskSupervisor.PRESERVE_WINDOWS;
-import static com.android.server.wm.ActivityTaskSupervisor.getApplicationLabel;
-import static com.android.server.wm.BackgroundActivityStartController.BAL_ALLOW_ALLOWLISTED_COMPONENT;
-import static com.android.server.wm.BackgroundActivityStartController.BAL_ALLOW_ALLOWLISTED_UID;
 import static com.android.server.wm.BackgroundActivityStartController.BAL_ALLOW_DEFAULT;
-import static com.android.server.wm.BackgroundActivityStartController.BAL_ALLOW_PENDING_INTENT;
-import static com.android.server.wm.BackgroundActivityStartController.BAL_ALLOW_PERMISSION;
-import static com.android.server.wm.BackgroundActivityStartController.BAL_ALLOW_SAW_PERMISSION;
-import static com.android.server.wm.BackgroundActivityStartController.BAL_ALLOW_VISIBLE_WINDOW;
 import static com.android.server.wm.BackgroundActivityStartController.BAL_BLOCK;
-import static com.android.server.wm.BackgroundActivityStartController.balCodeToString;
 import static com.android.server.wm.LaunchParamsController.LaunchParamsModifier.PHASE_BOUNDS;
 import static com.android.server.wm.LaunchParamsController.LaunchParamsModifier.PHASE_DISPLAY;
 import static com.android.server.wm.Task.REPARENT_MOVE_ROOT_TASK_TO_FRONT;
@@ -126,18 +118,14 @@
 import android.os.UserManager;
 import android.service.voice.IVoiceInteractionSession;
 import android.text.TextUtils;
-import android.util.Pair;
 import android.util.Pools.SynchronizedPool;
 import android.util.Slog;
-import android.widget.Toast;
 import android.window.RemoteTransition;
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.app.HeavyWeightSwitcherActivity;
 import com.android.internal.app.IVoiceInteractor;
 import com.android.internal.protolog.common.ProtoLog;
-import com.android.internal.util.FrameworkStatsLog;
-import com.android.server.UiThread;
 import com.android.server.am.PendingIntentRecord;
 import com.android.server.pm.InstantAppResolver;
 import com.android.server.power.ShutdownCheckPoints;
@@ -151,10 +139,6 @@
 import java.io.PrintWriter;
 import java.text.DateFormat;
 import java.util.Date;
-import java.util.StringJoiner;
-import java.util.function.Consumer;
-import java.util.function.Function;
-import java.util.function.Predicate;
 
 /**
  * Controller for interpreting how and then launching an activity.
@@ -188,7 +172,7 @@
      * Feature flag for go/activity-security rules
      */
     @ChangeId
-    @EnabledSince(targetSdkVersion = Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+    @EnabledSince(targetSdkVersion = Build.VERSION_CODES.VANILLA_ICE_CREAM)
     static final long ASM_RESTRICTIONS = 230590090L;
 
     private final ActivityTaskManagerService mService;
@@ -1114,7 +1098,7 @@
                 Trace.traceBegin(Trace.TRACE_TAG_WINDOW_MANAGER,
                         "shouldAbortBackgroundActivityStart");
                 BackgroundActivityStartController balController =
-                        mController.getBackgroundActivityLaunchController();
+                        mSupervisor.getBackgroundActivityLaunchController();
                 balCode =
                         balController.checkBackgroundActivityStart(
                                 callingUid,
@@ -1841,6 +1825,9 @@
                     sourceRecord, "launch-into-pip");
         }
 
+        mSupervisor.getBackgroundActivityLaunchController()
+                .onNewActivityLaunched(mStartActivity);
+
         return START_SUCCESS;
     }
 
@@ -1973,7 +1960,9 @@
             }
         }
 
-        if (!checkActivitySecurityModel(r, newTask, targetTask)) {
+        if (!mSupervisor.getBackgroundActivityLaunchController().checkActivityAllowedToStart(
+                mSourceRecord, r, newTask, targetTask, mLaunchFlags, mBalCode, mCallingUid,
+                mRealCallingUid)) {
             return START_ABORTED;
         }
 
@@ -1981,226 +1970,6 @@
     }
 
     /**
-     * TODO(b/263368846): Shift to BackgroundActivityStartController once class is ready
-     * Log activity starts which violate one of the following rules of the
-     * activity security model (ASM):
-     * See go/activity-security for rationale behind the rules.
-     * 1. Within a task, only an activity matching a top UID of the task can start activities
-     * 2. Only activities within a foreground task, which match a top UID of the task, can
-     * create a new task or bring an existing one into the foreground
-     */
-    private boolean checkActivitySecurityModel(ActivityRecord r, boolean newTask, Task targetTask) {
-        // BAL Exception allowed in all cases
-        if (mBalCode == BAL_ALLOW_ALLOWLISTED_UID) {
-            return true;
-        }
-
-        // Intents with FLAG_ACTIVITY_NEW_TASK will always be considered as creating a new task
-        // even if the intent is delivered to an existing task.
-        boolean taskToFront = newTask
-                || (mLaunchFlags & FLAG_ACTIVITY_NEW_TASK) == FLAG_ACTIVITY_NEW_TASK;
-
-        // BAL exception only allowed for new tasks
-        if (taskToFront) {
-            if (mBalCode == BAL_ALLOW_ALLOWLISTED_COMPONENT
-                    || mBalCode == BAL_ALLOW_PERMISSION
-                    || mBalCode == BAL_ALLOW_PENDING_INTENT
-                    || mBalCode == BAL_ALLOW_SAW_PERMISSION
-                    || mBalCode == BAL_ALLOW_VISIBLE_WINDOW) {
-                return true;
-            }
-        }
-
-        Pair<Boolean, Boolean> pair = null;
-        if (mSourceRecord != null) {
-            boolean passesAsmChecks = true;
-            Task sourceTask = mSourceRecord.getTask();
-
-            // Allow launching into a new task (or a task matching the launched activity's
-            // affinity) only if the current task is foreground or mutating its own task.
-            // The latter can happen eg. if caller uses NEW_TASK flag and the activity being
-            // launched matches affinity of source task.
-            if (taskToFront) {
-                passesAsmChecks = sourceTask != null
-                        && (sourceTask.isVisible() || sourceTask == targetTask);
-            }
-
-            if (passesAsmChecks) {
-                Task taskToCheck = taskToFront ? sourceTask : targetTask;
-                pair = ActivityTaskSupervisor
-                        .doesTopActivityMatchingUidExistForAsm(taskToCheck, mSourceRecord.getUid(),
-                                mSourceRecord);
-            }
-        } else if (!taskToFront) {
-            // We don't have a sourceRecord, and we're launching into an existing task.
-            // Allow if callingUid is top of stack.
-            pair = ActivityTaskSupervisor
-                    .doesTopActivityMatchingUidExistForAsm(targetTask, mCallingUid,
-                            /*sourceRecord*/null);
-        }
-
-        boolean shouldBlockActivityStart = true;
-        if (pair != null) {
-            // We block if feature flag is enabled
-            shouldBlockActivityStart = !pair.first;
-            // Used for logging/toasts. Would we block if target sdk was U and feature was
-            // enabled? If so, we can't return here but we also might not block at the end
-            boolean wouldBlockActivityStartIgnoringFlags = !pair.second;
-
-            if (!wouldBlockActivityStartIgnoringFlags) {
-                return true;
-            }
-        }
-
-        // ASM rules have failed. Log why
-        return logAsmFailureAndCheckFeatureEnabled(r, newTask, targetTask, shouldBlockActivityStart,
-                taskToFront);
-    }
-
-    private boolean logAsmFailureAndCheckFeatureEnabled(ActivityRecord r, boolean newTask,
-            Task targetTask, boolean shouldBlockActivityStart, boolean taskToFront) {
-        // ASM rules have failed. Log why
-        ActivityRecord targetTopActivity = targetTask == null ? null
-                : targetTask.getActivity(ar -> !ar.finishing && !ar.isAlwaysOnTop());
-
-        int action = newTask || mSourceRecord == null
-                ? FrameworkStatsLog.ACTIVITY_ACTION_BLOCKED__ACTION__ACTIVITY_START_NEW_TASK
-                : (mSourceRecord.getTask().equals(targetTask)
-                        ? FrameworkStatsLog.ACTIVITY_ACTION_BLOCKED__ACTION__ACTIVITY_START_SAME_TASK
-                        :  FrameworkStatsLog.ACTIVITY_ACTION_BLOCKED__ACTION__ACTIVITY_START_DIFFERENT_TASK);
-
-        boolean blockActivityStartAndFeatureEnabled = ActivitySecurityModelFeatureFlags
-                .shouldRestrictActivitySwitch(mCallingUid)
-                && shouldBlockActivityStart;
-
-        String asmDebugInfo = getDebugInfoForActivitySecurity("Launch", r, targetTask,
-                targetTopActivity, blockActivityStartAndFeatureEnabled, /*taskToFront*/taskToFront);
-
-        FrameworkStatsLog.write(FrameworkStatsLog.ACTIVITY_ACTION_BLOCKED,
-                /* caller_uid */
-                mSourceRecord != null ? mSourceRecord.getUid() : mCallingUid,
-                /* caller_activity_class_name */
-                mSourceRecord != null ? mSourceRecord.info.name : null,
-                /* target_task_top_activity_uid */
-                targetTopActivity != null ? targetTopActivity.getUid() : -1,
-                /* target_task_top_activity_class_name */
-                targetTopActivity != null ? targetTopActivity.info.name : null,
-                /* target_task_is_different */
-                newTask || mSourceRecord == null || targetTask == null
-                        || !targetTask.equals(mSourceRecord.getTask()),
-                /* target_activity_uid */
-                r.getUid(),
-                /* target_activity_class_name */
-                r.info.name,
-                /* target_intent_action */
-                r.intent.getAction(),
-                /* target_intent_flags */
-                mLaunchFlags,
-                /* action */
-                action,
-                /* version */
-                ActivitySecurityModelFeatureFlags.ASM_VERSION,
-                /* multi_window - we have our source not in the target task, but both are visible */
-                targetTask != null && mSourceRecord != null
-                        && !targetTask.equals(mSourceRecord.getTask()) && targetTask.isVisible(),
-                /* bal_code */
-                mBalCode,
-                /* task_stack */
-                asmDebugInfo
-        );
-
-        String launchedFromPackageName = r.launchedFromPackage;
-        if (ActivitySecurityModelFeatureFlags.shouldShowToast(mCallingUid)) {
-            String toastText = ActivitySecurityModelFeatureFlags.DOC_LINK
-                    + (blockActivityStartAndFeatureEnabled ? " blocked " : " would block ")
-                    + getApplicationLabel(mService.mContext.getPackageManager(),
-                    launchedFromPackageName);
-            UiThread.getHandler().post(() -> Toast.makeText(mService.mContext,
-                    toastText, Toast.LENGTH_LONG).show());
-
-            Slog.i(TAG, asmDebugInfo);
-        }
-
-        if (blockActivityStartAndFeatureEnabled) {
-            Slog.e(TAG, "[ASM] Abort Launching r: " + r
-                    + " as source: "
-                    + (mSourceRecord != null ? mSourceRecord : launchedFromPackageName)
-                    + " is in background. New task: " + newTask
-                    + ". Top activity: " + targetTopActivity
-                    + ". BAL Code: " + balCodeToString(mBalCode));
-
-            return false;
-        }
-
-        return true;
-    }
-
-    /** Only called when an activity launch may be blocked, which should happen very rarely */
-    private String getDebugInfoForActivitySecurity(String action, ActivityRecord r, Task targetTask,
-            ActivityRecord targetTopActivity, boolean blockActivityStartAndFeatureEnabled,
-            boolean taskToFront) {
-        final String prefix = "[ASM] ";
-        Function<ActivityRecord, String> recordToString = (ar) -> {
-            if (ar == null) {
-                return null;
-            }
-            return (ar == mSourceRecord ? " [source]=> "
-                    : ar == targetTopActivity ? " [ top  ]=> "
-                            : ar == r ? " [target]=> "
-                                    : "         => ")
-                    + ar
-                    + " :: visible=" + ar.isVisible()
-                    + ", finishing=" + ar.isFinishing()
-                    + ", alwaysOnTop=" + ar.isAlwaysOnTop()
-                    + ", taskFragment=" + ar.getTaskFragment();
-        };
-
-        StringJoiner joiner = new StringJoiner("\n");
-        joiner.add(prefix + "------ Activity Security " + action + " Debug Logging Start ------");
-        joiner.add(prefix + "Block Enabled: " + blockActivityStartAndFeatureEnabled);
-        joiner.add(prefix + "ASM Version: " + ActivitySecurityModelFeatureFlags.ASM_VERSION);
-
-        boolean targetTaskMatchesSourceTask = targetTask != null
-                && mSourceRecord != null && mSourceRecord.getTask() == targetTask;
-
-        if (mSourceRecord == null) {
-            joiner.add(prefix + "Source Package: " + r.launchedFromPackage);
-            String realCallingPackage = mService.mContext.getPackageManager().getNameForUid(
-                    mRealCallingUid);
-            joiner.add(prefix + "Real Calling Uid Package: " + realCallingPackage);
-        } else {
-            joiner.add(prefix + "Source Record: " + recordToString.apply(mSourceRecord));
-            if (targetTaskMatchesSourceTask) {
-                joiner.add(prefix + "Source/Target Task: " + mSourceRecord.getTask());
-                joiner.add(prefix + "Source/Target Task Stack: ");
-            } else {
-                joiner.add(prefix + "Source Task: " + mSourceRecord.getTask());
-                joiner.add(prefix + "Source Task Stack: ");
-            }
-            mSourceRecord.getTask().forAllActivities((Consumer<ActivityRecord>)
-                    ar -> joiner.add(prefix + recordToString.apply(ar)));
-        }
-
-        joiner.add(prefix + "Target Task Top: " + recordToString.apply(targetTopActivity));
-        if (!targetTaskMatchesSourceTask) {
-            joiner.add(prefix + "Target Task: " + targetTask);
-            if (targetTask != null) {
-                joiner.add(prefix + "Target Task Stack: ");
-                targetTask.forAllActivities((Consumer<ActivityRecord>)
-                        ar -> joiner.add(prefix + recordToString.apply(ar)));
-            }
-        }
-
-        joiner.add(prefix + "Target Record: " + recordToString.apply(r));
-        joiner.add(prefix + "Intent: " + mIntent);
-        joiner.add(prefix + "TaskToFront: " + taskToFront);
-        joiner.add(prefix + "BalCode: " + balCodeToString(mBalCode));
-
-        joiner.add(prefix + "------ Activity Security " + action + " Debug Logging End ------");
-        return joiner.toString();
-    }
-
-    /**
      * Returns whether embedding of {@code starting} is allowed.
      *
      * @param taskFragment the TaskFragment for embedding.
@@ -2284,8 +2053,9 @@
                 reusedTask != null ? reusedTask.getTopNonFinishingActivity() : null, intentGrants);
 
         if (mAddingToTask) {
-            clearTopIfNeeded(targetTask, mCallingUid, mRealCallingUid, mStartActivity.getUid(),
-                    mLaunchFlags);
+            mSupervisor.getBackgroundActivityLaunchController().clearTopIfNeeded(targetTask,
+                    mSourceRecord, mStartActivity, mCallingUid, mRealCallingUid, mLaunchFlags,
+                    mBalCode);
             return START_SUCCESS;
         }
 
@@ -2318,65 +2088,6 @@
     }
 
     /**
-     * If the top activity uid does not match the launching or launched activity, and the launch was
-     * not requested from the top uid, we want to clear out all non matching activities to prevent
-     * the top activity being sandwiched.
-     *
-     * Both creator and sender UID are considered for the launching activity.
-     */
-    private void clearTopIfNeeded(@NonNull Task targetTask, int callingUid, int realCallingUid,
-            int startingUid, int launchFlags) {
-        if ((launchFlags & FLAG_ACTIVITY_NEW_TASK) != FLAG_ACTIVITY_NEW_TASK
-                || mBalCode == BAL_ALLOW_ALLOWLISTED_UID) {
-            // Launch is from the same task, (a top or privileged UID), or is directly privileged.
-            return;
-        }
-
-        Predicate<ActivityRecord> isLaunchingOrLaunched = ar ->
-                ar.isUid(startingUid) || ar.isUid(callingUid) || ar.isUid(realCallingUid);
-
-        // Return early if we know for sure we won't need to clear any activities by just checking
-        // the top activity.
-        ActivityRecord targetTaskTop = targetTask.getTopMostActivity();
-        if (targetTaskTop == null || isLaunchingOrLaunched.test(targetTaskTop)) {
-            return;
-        }
-
-        // Find the first activity which matches a safe UID and is not finishing. Clear everything
-        // above it
-        boolean shouldBlockActivityStart = ActivitySecurityModelFeatureFlags
-                .shouldRestrictActivitySwitch(callingUid);
-        int[] finishCount = new int[0];
-        if (shouldBlockActivityStart) {
-            ActivityRecord activity = targetTask.getActivity(isLaunchingOrLaunched);
-            if (activity == null) {
-                // mStartActivity is not in task, so clear everything
-                activity = mStartActivity;
-            }
-
-            finishCount = new int[1];
-            targetTask.performClearTop(activity, launchFlags, finishCount);
-            if (finishCount[0] > 0) {
-                Slog.w(TAG, "Cleared top n: " + finishCount[0] + " activities from task t: "
-                        + targetTask + " not matching top uid: " + callingUid);
-            }
-        }
-
-        if (ActivitySecurityModelFeatureFlags.shouldShowToast(callingUid)
-                && (!shouldBlockActivityStart || finishCount[0] > 0)) {
-            UiThread.getHandler().post(() -> Toast.makeText(mService.mContext,
-                    (shouldBlockActivityStart
-                            ? "Top activities cleared by "
-                            : "Top activities would be cleared by ")
-                            + ActivitySecurityModelFeatureFlags.DOC_LINK,
-                    Toast.LENGTH_LONG).show());
-
-            getDebugInfoForActivitySecurity("Clear Top", mStartActivity, targetTask, targetTaskTop,
-                    shouldBlockActivityStart, /* taskToFront */ true);
-        }
-    }
-
-    /**
      * Check if the activity being launched is the same as the one currently at the top and it
      * should only be launched once.
      */
diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
index 4aea70c..6999c6a 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
@@ -2220,7 +2220,7 @@
             callerApp = getProcessController(appThread);
         }
         final BackgroundActivityStartController balController =
-                getActivityStartController().getBackgroundActivityLaunchController();
+                mTaskSupervisor.getBackgroundActivityLaunchController();
         if (balController.shouldAbortBackgroundActivityStart(
                 callingUid,
                 callingPid,
diff --git a/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java b/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
index 6eb9ed69..e523119 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
@@ -134,12 +134,10 @@
 import android.provider.MediaStore;
 import android.util.ArrayMap;
 import android.util.MergedConfiguration;
-import android.util.Pair;
 import android.util.Slog;
 import android.util.SparseArray;
 import android.util.SparseIntArray;
 import android.view.Display;
-import android.widget.Toast;
 
 import com.android.internal.R;
 import com.android.internal.annotations.GuardedBy;
@@ -147,10 +145,8 @@
 import com.android.internal.content.ReferrerIntent;
 import com.android.internal.protolog.common.ProtoLog;
 import com.android.internal.util.ArrayUtils;
-import com.android.internal.util.FrameworkStatsLog;
 import com.android.internal.util.function.pooled.PooledLambda;
 import com.android.server.LocalServices;
-import com.android.server.UiThread;
 import com.android.server.am.ActivityManagerService;
 import com.android.server.am.HostingRecord;
 import com.android.server.am.UserState;
@@ -256,6 +252,9 @@
     final ActivityTaskManagerService mService;
     RootWindowContainer mRootWindowContainer;
 
+    /** Helper class for checking if an activity transition meets security rules */
+    BackgroundActivityStartController mBalController;
+
     /** The historial list of recent tasks including inactive tasks */
     RecentTasks mRecentTasks;
 
@@ -466,6 +465,8 @@
         mLaunchParamsPersister = new LaunchParamsPersister(mPersisterQueue, this);
         mLaunchParamsController = new LaunchParamsController(mService, mLaunchParamsPersister);
         mLaunchParamsController.registerDefaultModifiers(this);
+
+        mBalController = new BackgroundActivityStartController(mService, this);
     }
 
     void onSystemReady() {
@@ -928,8 +929,8 @@
                 final IBinder fragmentToken = r.getTaskFragment().getFragmentToken();
 
                 final int deviceId = getDeviceIdForDisplayId(r.getDisplayId());
-                clientTransaction.addCallback(LaunchActivityItem.obtain(new Intent(r.intent),
-                        System.identityHashCode(r), r.info,
+                clientTransaction.addCallback(LaunchActivityItem.obtain(r.token,
+                        new Intent(r.intent), System.identityHashCode(r), r.info,
                         // TODO: Have this take the merged configuration instead of separate global
                         // and override configs.
                         mergedConfiguration.getGlobalConfiguration(),
@@ -943,10 +944,10 @@
                 // Set desired final state.
                 final ActivityLifecycleItem lifecycleItem;
                 if (andResume) {
-                    lifecycleItem = ResumeActivityItem.obtain(isTransitionForward,
+                    lifecycleItem = ResumeActivityItem.obtain(r.token, isTransitionForward,
                             r.shouldSendCompatFakeFocus());
                 } else {
-                    lifecycleItem = PauseActivityItem.obtain();
+                    lifecycleItem = PauseActivityItem.obtain(r.token);
                 }
                 clientTransaction.setLifecycleStateRequest(lifecycleItem);
 
@@ -1284,6 +1285,10 @@
         return mAppOpsManager;
     }
 
+    BackgroundActivityStartController getBackgroundActivityLaunchController() {
+        return mBalController;
+    }
+
     private int getComponentRestrictionForCallingPackage(ActivityInfo activityInfo,
             String callingPackage, @Nullable String callingFeatureId, int callingPid,
             int callingUid, boolean ignoreTargetSecurity) {
@@ -1700,172 +1705,12 @@
             if (task.isPersistable) {
                 mService.notifyTaskPersisterLocked(null, true);
             }
-            checkActivitySecurityForTaskClear(callingUid, task, callerActivityClassName);
+            mBalController
+                    .checkActivityAllowedToClearTask(task, callingUid, callerActivityClassName);
         } finally {
             task.mInRemoveTask = false;
         }
     }
-
-    // TODO(b/263368846) Move to live with the rest of the ASM logic.
-    /**
-     * Returns home if the passed in callingUid is not top of the stack, rather than returning to
-     * previous task.
-     */
-    private void checkActivitySecurityForTaskClear(int callingUid, Task task,
-            String callerActivityClassName) {
-        // We may have already checked that the callingUid has additional clearTask privileges, and
-        // cleared the calling identify. If so, we infer we do not need further restrictions here.
-        if (callingUid == SYSTEM_UID || !task.isVisible() || task.inMultiWindowMode()) {
-            return;
-        }
-
-        TaskDisplayArea displayArea = task.getTaskDisplayArea();
-        if (displayArea == null) {
-            // If there is no associated display area, we can not return home.
-            return;
-        }
-
-        Pair<Boolean, Boolean> pair = doesTopActivityMatchingUidExistForAsm(task, callingUid, null);
-        boolean shouldBlockActivitySwitchIfFeatureEnabled = !pair.first;
-        boolean wouldBlockActivitySwitchIgnoringFlags = !pair.second;
-
-        if (!wouldBlockActivitySwitchIgnoringFlags) {
-            return;
-        }
-
-        ActivityRecord topActivity = task.getActivity(ar -> !ar.finishing && !ar.isAlwaysOnTop());
-        FrameworkStatsLog.write(FrameworkStatsLog.ACTIVITY_ACTION_BLOCKED,
-                /* caller_uid */
-                callingUid,
-                /* caller_activity_class_name */
-                callerActivityClassName,
-                /* target_task_top_activity_uid */
-                topActivity == null ? -1 : topActivity.getUid(),
-                /* target_task_top_activity_class_name */
-                topActivity == null ? null : topActivity.info.name,
-                /* target_task_is_different */
-                false,
-                /* target_activity_uid */
-                -1,
-                /* target_activity_class_name */
-                null,
-                /* target_intent_action */
-                null,
-                /* target_intent_flags */
-                0,
-                /* action */
-                FrameworkStatsLog.ACTIVITY_ACTION_BLOCKED__ACTION__FINISH_TASK,
-                /* version */
-                ActivitySecurityModelFeatureFlags.ASM_VERSION,
-                /* multi_window */
-                false,
-                /* bal_code */
-                -1,
-                /* task_stack */
-                null
-        );
-
-        boolean restrictActivitySwitch = ActivitySecurityModelFeatureFlags
-                .shouldRestrictActivitySwitch(callingUid)
-                && shouldBlockActivitySwitchIfFeatureEnabled;
-
-        PackageManager pm = mService.mContext.getPackageManager();
-        String callingPackage = pm.getNameForUid(callingUid);
-        final CharSequence callingLabel;
-        if (callingPackage == null) {
-            callingPackage = String.valueOf(callingUid);
-            callingLabel = callingPackage;
-        } else {
-            callingLabel = getApplicationLabel(pm, callingPackage);
-        }
-
-        if (ActivitySecurityModelFeatureFlags.shouldShowToast(callingUid)) {
-            UiThread.getHandler().post(() -> Toast.makeText(mService.mContext,
-                    (ActivitySecurityModelFeatureFlags.DOC_LINK
-                            + (restrictActivitySwitch ? " returned home due to "
-                                    : " would return home due to ")
-                            + callingLabel), Toast.LENGTH_LONG).show());
-        }
-
-        // If the activity switch should be restricted, return home rather than the
-        // previously top task, to prevent users from being confused which app they're
-        // viewing
-        if (restrictActivitySwitch) {
-            Slog.w(TAG, "[ASM] Return to home as source: " + callingPackage
-                    + " is not on top of task t: " + task);
-            displayArea.moveHomeActivityToTop("taskRemoved");
-        } else {
-            Slog.i(TAG, "[ASM] Would return to home as source: " + callingPackage
-                    + " is not on top of task t: " + task);
-        }
-    }
-
-    /**
-     *  For the purpose of ASM, ‘Top UID” for a task is defined as an activity UID
-     *  1. Which is top of the stack in z-order
-     *      a. Excluding any activities with the flag ‘isAlwaysOnTop’ and
-     *      b. Excluding any activities which are `finishing`
-     *  2. Or top of an adjacent task fragment to (1)
-     *
-     *  The 'sourceRecord' can be considered top even if it is 'finishing'
-     *
-     * @return A pair where the first value is the return value matching the checks above, and the
-     * second value is the return value disregarding the feature flag or target api levels. Use the
-     * first value for blocking launches - the second value is only used to determine if a toast
-     * should be displayed, and will be used alongside a feature flag in {@link ActivityStarter}.
-     */
-    // TODO(b/263368846) Shift to BackgroundActivityStartController once class is ready
-    @Nullable
-    static Pair<Boolean, Boolean> doesTopActivityMatchingUidExistForAsm(@Nullable Task task,
-            int uid, @Nullable ActivityRecord sourceRecord) {
-        // If the source is visible, consider it 'top'.
-        if (sourceRecord != null && sourceRecord.isVisible()) {
-            return new Pair<>(true, true);
-        }
-
-        // Always allow actual top activity to clear task
-        ActivityRecord topActivity = task.getTopMostActivity();
-        if (topActivity != null && topActivity.isUid(uid)) {
-            return new Pair<>(true, true);
-        }
-
-        // Consider the source activity, whether or not it is finishing. Do not consider any other
-        // finishing activity.
-        Predicate<ActivityRecord> topOfStackPredicate = (ar) -> ar.equals(sourceRecord)
-                || (!ar.finishing && !ar.isAlwaysOnTop());
-
-        // Check top of stack (or the first task fragment for embedding).
-        topActivity = task.getActivity(topOfStackPredicate);
-        if (topActivity == null) {
-            return new Pair<>(false, false);
-        }
-
-        Pair<Boolean, Boolean> pair = topActivity.allowCrossUidActivitySwitchFromBelow(uid);
-        if (pair.first) {
-            return new Pair<>(true, pair.second);
-        }
-
-        // Even if the top activity is not a match, we may be in an embedded activity scenario with
-        // an adjacent task fragment. Get the second fragment.
-        TaskFragment taskFragment = topActivity.getTaskFragment();
-        if (taskFragment == null) {
-            return new Pair<>(false, false);
-        }
-
-        TaskFragment adjacentTaskFragment = taskFragment.getAdjacentTaskFragment();
-        if (adjacentTaskFragment == null) {
-            return new Pair<>(false, false);
-        }
-
-        // Check the second fragment.
-        topActivity = adjacentTaskFragment.getActivity(topOfStackPredicate);
-        if (topActivity == null) {
-            return new Pair<>(false, false);
-        }
-
-        return topActivity.allowCrossUidActivitySwitchFromBelow(uid);
-    }
-
     static CharSequence getApplicationLabel(PackageManager pm, String packageName) {
         try {
             ApplicationInfo launchedFromPackageInfo = pm.getApplicationInfo(
diff --git a/services/core/java/com/android/server/wm/AppTaskImpl.java b/services/core/java/com/android/server/wm/AppTaskImpl.java
index de38a20..761b0a8 100644
--- a/services/core/java/com/android/server/wm/AppTaskImpl.java
+++ b/services/core/java/com/android/server/wm/AppTaskImpl.java
@@ -122,8 +122,7 @@
                     callerApp = mService.getProcessController(appThread);
                 }
                 final BackgroundActivityStartController balController =
-                        mService.getActivityStartController()
-                                .getBackgroundActivityLaunchController();
+                        mService.mTaskSupervisor.getBackgroundActivityLaunchController();
                 if (balController.shouldAbortBackgroundActivityStart(
                         callingUid,
                         callingPid,
diff --git a/services/core/java/com/android/server/wm/BackgroundActivityStartController.java b/services/core/java/com/android/server/wm/BackgroundActivityStartController.java
index 188f4d0..4bafccd 100644
--- a/services/core/java/com/android/server/wm/BackgroundActivityStartController.java
+++ b/services/core/java/com/android/server/wm/BackgroundActivityStartController.java
@@ -17,7 +17,9 @@
 package com.android.server.wm;
 
 import static android.Manifest.permission.START_ACTIVITIES_FROM_BACKGROUND;
+import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
 import static android.content.pm.PackageManager.PERMISSION_GRANTED;
+import static android.os.Process.SYSTEM_UID;
 import static android.provider.DeviceConfig.NAMESPACE_WINDOW_MANAGER;
 
 import static com.android.internal.util.Preconditions.checkState;
@@ -26,10 +28,13 @@
 import static com.android.server.wm.ActivityTaskManagerDebugConfig.TAG_WITH_CLASS_NAME;
 import static com.android.server.wm.ActivityTaskManagerService.APP_SWITCH_ALLOW;
 import static com.android.server.wm.ActivityTaskManagerService.APP_SWITCH_FG_ONLY;
+import static com.android.server.wm.ActivityTaskSupervisor.getApplicationLabel;
+import static com.android.server.wm.PendingRemoteAnimationRegistry.TIMEOUT_MS;
 
 import static java.lang.annotation.RetentionPolicy.SOURCE;
 
 import android.annotation.IntDef;
+import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.app.ActivityManager;
 import android.app.ActivityOptions;
@@ -45,12 +50,19 @@
 import android.util.ArraySet;
 import android.util.DebugUtils;
 import android.util.Slog;
+import android.widget.Toast;
 
-
+import com.android.internal.annotations.GuardedBy;
 import com.android.internal.util.FrameworkStatsLog;
+import com.android.server.UiThread;
 import com.android.server.am.PendingIntentRecord;
 
 import java.lang.annotation.Retention;
+import java.util.HashMap;
+import java.util.StringJoiner;
+import java.util.function.Consumer;
+import java.util.function.Function;
+import java.util.function.Predicate;
 
 /**
  * Helper class to check permissions for starting Activities.
@@ -65,6 +77,9 @@
     public static final String VERDICT_WOULD_BE_ALLOWED_IF_SENDER_GRANTS_BAL =
             "Activity start would be allowed if the sender granted BAL privileges";
 
+    private static final long ASM_GRACEPERIOD_TIMEOUT_MS = TIMEOUT_MS;
+    private static final int ASM_GRACEPERIOD_MAX_REPEATS = 5;
+
     private final ActivityTaskManagerService mService;
     private final ActivityTaskSupervisor mSupervisor;
 
@@ -148,6 +163,11 @@
         }
     }
 
+    @GuardedBy("mService.mGlobalLock")
+    private HashMap<Integer, FinishedActivityEntry> mTaskIdToFinishedActivity = new HashMap<>();
+    @GuardedBy("mService.mGlobalLock")
+    private FinishedActivityEntry mTopFinishedActivity = null;
+
     BackgroundActivityStartController(
             final ActivityTaskManagerService service, final ActivityTaskSupervisor supervisor) {
         mService = service;
@@ -575,6 +595,501 @@
         return BAL_BLOCK;
     }
 
+    /**
+     * Log activity starts which violate one of the following rules of the
+     * activity security model (ASM):
+     * See go/activity-security for rationale behind the rules.
+     * 1. Within a task, only an activity matching a top UID of the task can start activities
+     * 2. Only activities within a foreground task, which match a top UID of the task, can
+     * create a new task or bring an existing one into the foreground
+     */
+    boolean checkActivityAllowedToStart(@Nullable ActivityRecord sourceRecord,
+            @NonNull ActivityRecord targetRecord, boolean newTask, @NonNull Task targetTask,
+            int launchFlags, int balCode, int callingUid, int realCallingUid) {
+        // BAL Exception allowed in all cases
+        if (balCode == BAL_ALLOW_ALLOWLISTED_UID) {
+            return true;
+        }
+
+        // Intents with FLAG_ACTIVITY_NEW_TASK will always be considered as creating a new task
+        // even if the intent is delivered to an existing task.
+        boolean taskToFront = newTask
+                || (launchFlags & FLAG_ACTIVITY_NEW_TASK) == FLAG_ACTIVITY_NEW_TASK;
+
+        // BAL exception only allowed for new tasks
+        if (taskToFront) {
+            if (balCode == BAL_ALLOW_ALLOWLISTED_COMPONENT
+                    || balCode == BAL_ALLOW_PERMISSION
+                    || balCode == BAL_ALLOW_PENDING_INTENT
+                    || balCode == BAL_ALLOW_SAW_PERMISSION
+                    || balCode == BAL_ALLOW_VISIBLE_WINDOW) {
+                return true;
+            }
+        }
+
+        if (balCode == BAL_ALLOW_GRACE_PERIOD) {
+            if (taskToFront && mTopFinishedActivity != null
+                    && mTopFinishedActivity.mUid == callingUid) {
+                return true;
+            } else if (!taskToFront) {
+                FinishedActivityEntry finishedEntry =
+                        mTaskIdToFinishedActivity.get(targetTask.mTaskId);
+                if (finishedEntry != null && finishedEntry.mUid == callingUid) {
+                    return true;
+                }
+            }
+        }
+
+        BlockActivityStart bas = null;
+        if (sourceRecord != null) {
+            boolean passesAsmChecks = true;
+            Task sourceTask = sourceRecord.getTask();
+
+            // Allow launching into a new task (or a task matching the launched activity's
+            // affinity) only if the current task is foreground or mutating its own task.
+            // The latter can happen eg. if caller uses NEW_TASK flag and the activity being
+            // launched matches affinity of source task.
+            if (taskToFront) {
+                passesAsmChecks = sourceTask != null
+                        && (sourceTask.isVisible() || sourceTask == targetTask);
+            }
+
+            if (passesAsmChecks) {
+                Task taskToCheck = taskToFront ? sourceTask : targetTask;
+                bas = isTopActivityMatchingUidAbsentForAsm(taskToCheck, sourceRecord.getUid(),
+                        sourceRecord);
+            }
+        } else if (!taskToFront) {
+            // We don't have a sourceRecord, and we're launching into an existing task.
+            // Allow if callingUid is top of stack.
+            bas = isTopActivityMatchingUidAbsentForAsm(targetTask, callingUid,
+                    /*sourceRecord*/null);
+        }
+
+        if (bas != null && !bas.mWouldBlockActivityStartIgnoringFlag) {
+            return true;
+        }
+
+        // ASM rules have failed. Log why
+        return logAsmFailureAndCheckFeatureEnabled(sourceRecord, callingUid, realCallingUid,
+                newTask, targetTask, targetRecord, balCode, launchFlags, bas, taskToFront);
+    }
+
+    private boolean logAsmFailureAndCheckFeatureEnabled(ActivityRecord sourceRecord, int callingUid,
+            int realCallingUid, boolean newTask, Task targetTask, ActivityRecord targetRecord,
+            @BalCode int balCode, int launchFlags, BlockActivityStart bas, boolean taskToFront) {
+
+        ActivityRecord targetTopActivity = targetTask == null ? null
+                : targetTask.getActivity(ar -> !ar.finishing && !ar.isAlwaysOnTop());
+
+        int action = newTask || sourceRecord == null
+                ? FrameworkStatsLog.ACTIVITY_ACTION_BLOCKED__ACTION__ACTIVITY_START_NEW_TASK
+                : (sourceRecord.getTask().equals(targetTask)
+                ? FrameworkStatsLog.ACTIVITY_ACTION_BLOCKED__ACTION__ACTIVITY_START_SAME_TASK
+                : FrameworkStatsLog.ACTIVITY_ACTION_BLOCKED__ACTION__ACTIVITY_START_DIFFERENT_TASK);
+
+        boolean blockActivityStartAndFeatureEnabled = ActivitySecurityModelFeatureFlags
+                .shouldRestrictActivitySwitch(callingUid)
+                && (bas == null || bas.mBlockActivityStartIfFlagEnabled);
+
+        String asmDebugInfo = getDebugInfoForActivitySecurity("Launch", sourceRecord,
+                targetRecord, targetTask, targetTopActivity, realCallingUid, balCode,
+                blockActivityStartAndFeatureEnabled, taskToFront);
+
+        FrameworkStatsLog.write(FrameworkStatsLog.ACTIVITY_ACTION_BLOCKED,
+                /* caller_uid */
+                sourceRecord != null ? sourceRecord.getUid() : callingUid,
+                /* caller_activity_class_name */
+                sourceRecord != null ? sourceRecord.info.name : null,
+                /* target_task_top_activity_uid */
+                targetTopActivity != null ? targetTopActivity.getUid() : -1,
+                /* target_task_top_activity_class_name */
+                targetTopActivity != null ? targetTopActivity.info.name : null,
+                /* target_task_is_different */
+                newTask || sourceRecord == null || targetTask == null
+                        || !targetTask.equals(sourceRecord.getTask()),
+                /* target_activity_uid */
+                targetRecord.getUid(),
+                /* target_activity_class_name */
+                targetRecord.info.name,
+                /* target_intent_action */
+                targetRecord.intent.getAction(),
+                /* target_intent_flags */
+                launchFlags,
+                /* action */
+                action,
+                /* version */
+                ActivitySecurityModelFeatureFlags.ASM_VERSION,
+                /* multi_window - we have our source not in the target task, but both are visible */
+                targetTask != null && sourceRecord != null
+                        && !targetTask.equals(sourceRecord.getTask()) && targetTask.isVisible(),
+                /* bal_code */
+                balCode,
+                /* debug_info */
+                asmDebugInfo
+        );
+
+        String launchedFromPackageName = targetRecord.launchedFromPackage;
+        if (ActivitySecurityModelFeatureFlags.shouldShowToast(callingUid)) {
+            String toastText = ActivitySecurityModelFeatureFlags.DOC_LINK
+                    + (blockActivityStartAndFeatureEnabled ? " blocked " : " would block ")
+                    + getApplicationLabel(mService.mContext.getPackageManager(),
+                    launchedFromPackageName);
+            UiThread.getHandler().post(() -> Toast.makeText(mService.mContext,
+                    toastText, Toast.LENGTH_LONG).show());
+
+            Slog.i(TAG, asmDebugInfo);
+        }
+
+        if (blockActivityStartAndFeatureEnabled) {
+            Slog.e(TAG, "[ASM] Abort Launching r: " + targetRecord
+                    + " as source: "
+                    + (sourceRecord != null ? sourceRecord : launchedFromPackageName)
+                    + " is in background. New task: " + newTask
+                    + ". Top activity: " + targetTopActivity
+                    + ". BAL Code: " + balCodeToString(balCode));
+
+            return false;
+        }
+
+        return true;
+    }
+
+    /**
+     * If the top activity uid does not match the launching or launched activity, and the launch was
+     * not requested from the top uid, we want to clear out all non matching activities to prevent
+     * the top activity being sandwiched.
+     * Both creator and sender UID are considered for the launching activity.
+     */
+    void clearTopIfNeeded(@NonNull Task targetTask, @Nullable ActivityRecord sourceRecord,
+            @NonNull ActivityRecord targetRecord, int callingUid, int realCallingUid,
+            int launchFlags, @BalCode int balCode) {
+        if ((launchFlags & FLAG_ACTIVITY_NEW_TASK) != FLAG_ACTIVITY_NEW_TASK
+                || balCode == BAL_ALLOW_ALLOWLISTED_UID) {
+            // Launch is from the same task, (a top or privileged UID), or is directly privileged.
+            return;
+        }
+
+        int startingUid = targetRecord.getUid();
+        Predicate<ActivityRecord> isLaunchingOrLaunched = ar ->
+                ar.isUid(startingUid) || ar.isUid(callingUid) || ar.isUid(realCallingUid);
+
+        // Return early if we know for sure we won't need to clear any activities by just checking
+        // the top activity.
+        ActivityRecord targetTaskTop = targetTask.getTopMostActivity();
+        if (targetTaskTop == null || isLaunchingOrLaunched.test(targetTaskTop)) {
+            return;
+        }
+
+        // Find the first activity which matches a safe UID and is not finishing. Clear everything
+        // above it
+        boolean shouldBlockActivityStart = ActivitySecurityModelFeatureFlags
+                .shouldRestrictActivitySwitch(callingUid);
+        int[] finishCount = new int[0];
+        if (shouldBlockActivityStart) {
+            ActivityRecord activity = targetTask.getActivity(isLaunchingOrLaunched);
+            if (activity == null) {
+                // mStartActivity is not in task, so clear everything
+                activity = targetRecord;
+            }
+
+            finishCount = new int[1];
+            targetTask.performClearTop(activity, launchFlags, finishCount);
+            if (finishCount[0] > 0) {
+                Slog.w(TAG, "Cleared top n: " + finishCount[0] + " activities from task t: "
+                        + targetTask + " not matching top uid: " + callingUid);
+            }
+        }
+
+        if (ActivitySecurityModelFeatureFlags.shouldShowToast(callingUid)
+                && (!shouldBlockActivityStart || finishCount[0] > 0)) {
+            UiThread.getHandler().post(() -> Toast.makeText(mService.mContext,
+                    (shouldBlockActivityStart
+                            ? "Top activities cleared by "
+                            : "Top activities would be cleared by ")
+                            + ActivitySecurityModelFeatureFlags.DOC_LINK,
+                    Toast.LENGTH_LONG).show());
+
+            Slog.i(TAG, getDebugInfoForActivitySecurity("Clear Top", sourceRecord, targetRecord,
+                    targetTask, targetTaskTop, realCallingUid, balCode, shouldBlockActivityStart,
+                    /* taskToFront */ true));
+        }
+    }
+
+    /**
+     * Returns home if the passed in callingUid is not top of the stack, rather than returning to
+     * previous task.
+     */
+    void checkActivityAllowedToClearTask(@NonNull Task task, int callingUid,
+            @NonNull String callerActivityClassName) {
+        // We may have already checked that the callingUid has additional clearTask privileges, and
+        // cleared the calling identify. If so, we infer we do not need further restrictions here.
+        if (callingUid == SYSTEM_UID || !task.isVisible() || task.inMultiWindowMode()) {
+            return;
+        }
+
+        TaskDisplayArea displayArea = task.getTaskDisplayArea();
+        if (displayArea == null) {
+            // If there is no associated display area, we can not return home.
+            return;
+        }
+
+        BlockActivityStart bas = isTopActivityMatchingUidAbsentForAsm(task, callingUid, null);
+        if (!bas.mWouldBlockActivityStartIgnoringFlag) {
+            return;
+        }
+
+        ActivityRecord topActivity = task.getActivity(ar -> !ar.finishing && !ar.isAlwaysOnTop());
+        FrameworkStatsLog.write(FrameworkStatsLog.ACTIVITY_ACTION_BLOCKED,
+                /* caller_uid */
+                callingUid,
+                /* caller_activity_class_name */
+                callerActivityClassName,
+                /* target_task_top_activity_uid */
+                topActivity == null ? -1 : topActivity.getUid(),
+                /* target_task_top_activity_class_name */
+                topActivity == null ? null : topActivity.info.name,
+                /* target_task_is_different */
+                false,
+                /* target_activity_uid */
+                -1,
+                /* target_activity_class_name */
+                null,
+                /* target_intent_action */
+                null,
+                /* target_intent_flags */
+                0,
+                /* action */
+                FrameworkStatsLog.ACTIVITY_ACTION_BLOCKED__ACTION__FINISH_TASK,
+                /* version */
+                ActivitySecurityModelFeatureFlags.ASM_VERSION,
+                /* multi_window */
+                false,
+                /* bal_code */
+                -1,
+                /* debug_info */
+                null
+        );
+
+        boolean restrictActivitySwitch = ActivitySecurityModelFeatureFlags
+                .shouldRestrictActivitySwitch(callingUid)
+                && bas.mBlockActivityStartIfFlagEnabled;
+
+        PackageManager pm = mService.mContext.getPackageManager();
+        String callingPackage = pm.getNameForUid(callingUid);
+        final CharSequence callingLabel;
+        if (callingPackage == null) {
+            callingPackage = String.valueOf(callingUid);
+            callingLabel = callingPackage;
+        } else {
+            callingLabel = getApplicationLabel(pm, callingPackage);
+        }
+
+        if (ActivitySecurityModelFeatureFlags.shouldShowToast(callingUid)) {
+            UiThread.getHandler().post(() -> Toast.makeText(mService.mContext,
+                    (ActivitySecurityModelFeatureFlags.DOC_LINK
+                            + (restrictActivitySwitch ? " returned home due to "
+                            : " would return home due to ")
+                            + callingLabel), Toast.LENGTH_LONG).show());
+        }
+
+        // If the activity switch should be restricted, return home rather than the
+        // previously top task, to prevent users from being confused which app they're
+        // viewing
+        if (restrictActivitySwitch) {
+            Slog.w(TAG, "[ASM] Return to home as source: " + callingPackage
+                    + " is not on top of task t: " + task);
+            displayArea.moveHomeActivityToTop("taskRemoved");
+        } else {
+            Slog.i(TAG, "[ASM] Would return to home as source: " + callingPackage
+                    + " is not on top of task t: " + task);
+        }
+    }
+
+    /**
+     * For the purpose of ASM, ‘Top UID” for a task is defined as an activity UID
+     * 1. Which is top of the stack in z-order
+     * a. Excluding any activities with the flag ‘isAlwaysOnTop’ and
+     * b. Excluding any activities which are `finishing`
+     * 2. Or top of an adjacent task fragment to (1)
+     * <p>
+     * The 'sourceRecord' can be considered top even if it is 'finishing'
+     *
+     * Returns a class where the elements are:
+     * <pre>
+     * shouldBlockActivityStart: {@code true} if we should actually block the transition (takes into
+     * consideration feature flag and targetSdk).
+     * wouldBlockActivityStartIgnoringFlags: {@code true} if we should warn about the transition via
+     * toasts. This happens if the transition would be blocked in case both the app was targeting V+
+     * and the feature was enabled.
+     * </pre>
+     */
+    private BlockActivityStart isTopActivityMatchingUidAbsentForAsm(@NonNull Task task,
+            int uid, @Nullable ActivityRecord sourceRecord) {
+        // If the source is visible, consider it 'top'.
+        if (sourceRecord != null && sourceRecord.isVisible()) {
+            return new BlockActivityStart(false, false);
+        }
+
+        // Always allow actual top activity to clear task
+        ActivityRecord topActivity = task.getTopMostActivity();
+        if (topActivity != null && topActivity.isUid(uid)) {
+            return new BlockActivityStart(false, false);
+        }
+
+        // Consider the source activity, whether or not it is finishing. Do not consider any other
+        // finishing activity.
+        Predicate<ActivityRecord> topOfStackPredicate = (ar) -> ar.equals(sourceRecord)
+                || (!ar.finishing && !ar.isAlwaysOnTop());
+
+        // Check top of stack (or the first task fragment for embedding).
+        topActivity = task.getActivity(topOfStackPredicate);
+        if (topActivity == null) {
+            return new BlockActivityStart(true, true);
+        }
+
+        BlockActivityStart pair = blockCrossUidActivitySwitchFromBelow(topActivity, uid);
+        if (!pair.mBlockActivityStartIfFlagEnabled) {
+            return pair;
+        }
+
+        // Even if the top activity is not a match, we may be in an embedded activity scenario with
+        // an adjacent task fragment. Get the second fragment.
+        TaskFragment taskFragment = topActivity.getTaskFragment();
+        if (taskFragment == null) {
+            return pair;
+        }
+
+        TaskFragment adjacentTaskFragment = taskFragment.getAdjacentTaskFragment();
+        if (adjacentTaskFragment == null) {
+            return pair;
+        }
+
+        // Check the second fragment.
+        topActivity = adjacentTaskFragment.getActivity(topOfStackPredicate);
+        if (topActivity == null) {
+            return new BlockActivityStart(true, true);
+        }
+
+        return blockCrossUidActivitySwitchFromBelow(topActivity, uid);
+    }
+
+    /**
+     * Determines if a source is allowed to add or remove activities from the task,
+     * if the current ActivityRecord is above it in the stack
+     *
+     * A transition is blocked ({@code false} returned) if all of the following are met:
+     * <pre>
+     * 1. The source activity and the current activity record belong to different apps
+     * (i.e, have different UIDs).
+     * 2. Both the source activity and the current activity target U+
+     * 3. The current activity has not set
+     * {@link ActivityRecord#setAllowCrossUidActivitySwitchFromBelow(boolean)} to {@code true}
+     * </pre>
+     *
+     * Returns a class where the elements are:
+     * <pre>
+     * shouldBlockActivityStart: {@code true} if we should actually block the transition (takes into
+     * consideration feature flag and targetSdk).
+     * wouldBlockActivityStartIgnoringFlags: {@code true} if we should warn about the transition via
+     * toasts. This happens if the transition would be blocked in case both the app was targeting V+
+     * and the feature was enabled.
+     * </pre>
+     *
+     * @param sourceUid The source (s) activity performing the state change
+     */
+    private BlockActivityStart blockCrossUidActivitySwitchFromBelow(ActivityRecord ar,
+            int sourceUid) {
+        if (ar.isUid(sourceUid)) {
+            return new BlockActivityStart(false, false);
+        }
+
+        // If mAllowCrossUidActivitySwitchFromBelow is set, honor it.
+        if (ar.mAllowCrossUidActivitySwitchFromBelow) {
+            return new BlockActivityStart(false, false);
+        }
+
+        // At this point, we would block if the feature is launched and both apps were V+
+        // Since we have a feature flag, we need to check that too
+        // TODO(b/258792202) Replace with CompatChanges and replace Pair with boolean once feature
+        // flag is removed
+        boolean restrictActivitySwitch =
+                ActivitySecurityModelFeatureFlags.shouldRestrictActivitySwitch(ar.getUid())
+                        && ActivitySecurityModelFeatureFlags
+                        .shouldRestrictActivitySwitch(sourceUid);
+        return new BackgroundActivityStartController
+                .BlockActivityStart(restrictActivitySwitch, true);
+    }
+
+    /**
+     * Only called when an activity launch may be blocked, which should happen very rarely
+     */
+    private String getDebugInfoForActivitySecurity(@NonNull String action,
+            @Nullable ActivityRecord sourceRecord, @NonNull ActivityRecord targetRecord,
+            @Nullable Task targetTask, @Nullable ActivityRecord targetTopActivity,
+            int realCallingUid, @BalCode int balCode,
+            boolean blockActivityStartAndFeatureEnabled, boolean taskToFront) {
+        final String prefix = "[ASM] ";
+        Function<ActivityRecord, String> recordToString = (ar) -> {
+            if (ar == null) {
+                return null;
+            }
+            return (ar == sourceRecord ? " [source]=> "
+                    : ar == targetTopActivity ? " [ top  ]=> "
+                    : ar == targetRecord ? " [target]=> "
+                    : "         => ")
+                    + ar
+                    + " :: visible=" + ar.isVisible()
+                    + ", finishing=" + ar.isFinishing()
+                    + ", alwaysOnTop=" + ar.isAlwaysOnTop()
+                    + ", taskFragment=" + ar.getTaskFragment();
+        };
+
+        StringJoiner joiner = new StringJoiner("\n");
+        joiner.add(prefix + "------ Activity Security " + action + " Debug Logging Start ------");
+        joiner.add(prefix + "Block Enabled: " + blockActivityStartAndFeatureEnabled);
+        joiner.add(prefix + "ASM Version: " + ActivitySecurityModelFeatureFlags.ASM_VERSION);
+
+        boolean targetTaskMatchesSourceTask = targetTask != null
+                && sourceRecord != null && sourceRecord.getTask() == targetTask;
+
+        if (sourceRecord == null) {
+            joiner.add(prefix + "Source Package: " + targetRecord.launchedFromPackage);
+            String realCallingPackage = mService.mContext.getPackageManager().getNameForUid(
+                    realCallingUid);
+            joiner.add(prefix + "Real Calling Uid Package: " + realCallingPackage);
+        } else {
+            joiner.add(prefix + "Source Record: " + recordToString.apply(sourceRecord));
+            if (targetTaskMatchesSourceTask) {
+                joiner.add(prefix + "Source/Target Task: " + sourceRecord.getTask());
+                joiner.add(prefix + "Source/Target Task Stack: ");
+            } else {
+                joiner.add(prefix + "Source Task: " + sourceRecord.getTask());
+                joiner.add(prefix + "Source Task Stack: ");
+            }
+            sourceRecord.getTask().forAllActivities((Consumer<ActivityRecord>)
+                    ar -> joiner.add(prefix + recordToString.apply(ar)));
+        }
+
+        joiner.add(prefix + "Target Task Top: " + recordToString.apply(targetTopActivity));
+        if (!targetTaskMatchesSourceTask) {
+            joiner.add(prefix + "Target Task: " + targetTask);
+            if (targetTask != null) {
+                joiner.add(prefix + "Target Task Stack: ");
+                targetTask.forAllActivities((Consumer<ActivityRecord>)
+                        ar -> joiner.add(prefix + recordToString.apply(ar)));
+            }
+        }
+
+        joiner.add(prefix + "Target Record: " + recordToString.apply(targetRecord));
+        joiner.add(prefix + "Intent: " + targetRecord.intent);
+        joiner.add(prefix + "TaskToFront: " + taskToFront);
+        joiner.add(prefix + "BalCode: " + balCodeToString(balCode));
+
+        joiner.add(prefix + "------ Activity Security " + action + " Debug Logging End ------");
+        return joiner.toString();
+    }
+
     static @BalCode int logStartAllowedAndReturnCode(@BalCode int code, boolean background,
             int callingUid, int realCallingUid, Intent intent, int pid, String msg) {
         return logStartAllowedAndReturnCode(code, background, callingUid, realCallingUid, intent,
@@ -653,4 +1168,91 @@
                     realCallingUid);
         }
     }
+
+    /**
+     * Called whenever an activity finishes. Stores the record, so it can be used by ASM grace
+     * period checks.
+     */
+    void onActivityRequestedFinishing(@NonNull ActivityRecord finishActivity) {
+        // We only update the entry if the passed in activity
+        // 1. Has been chained less than a set max AND
+        // 2. Is visible or top
+        FinishedActivityEntry entry =
+                mTaskIdToFinishedActivity.get(finishActivity.getTask().mTaskId);
+        if (entry != null && finishActivity.isUid(entry.mUid)
+                && entry.mLaunchCount > ASM_GRACEPERIOD_MAX_REPEATS) {
+            return;
+        }
+
+        if (!finishActivity.mVisibleRequested
+                && finishActivity != finishActivity.getTask().getTopMostActivity()) {
+            return;
+        }
+
+        FinishedActivityEntry newEntry = new FinishedActivityEntry(finishActivity);
+        mTaskIdToFinishedActivity.put(finishActivity.getTask().mTaskId, newEntry);
+        if (finishActivity.getTask().mVisibleRequested) {
+            mTopFinishedActivity = newEntry;
+        }
+    }
+
+    /**
+     * Called whenever an activity starts. Updates the record so the activity is no longer
+     * considered for ASM grace period checks
+     */
+    void onNewActivityLaunched(ActivityRecord activityStarted) {
+        if (activityStarted.getTask() == null) {
+            return;
+        }
+
+        if (activityStarted.getTask().mVisibleRequested) {
+            mTopFinishedActivity = null;
+        }
+
+        FinishedActivityEntry entry =
+                mTaskIdToFinishedActivity.get(activityStarted.getTask().mTaskId);
+        if (entry != null && activityStarted.getTask().isTaskId(entry.mTaskId)) {
+            mTaskIdToFinishedActivity.remove(entry.mTaskId);
+        }
+    }
+
+    static class BlockActivityStart {
+        // We should block if feature flag is enabled
+        private final boolean mBlockActivityStartIfFlagEnabled;
+        // Used for logging/toasts. Would we block if target sdk was V and feature was
+        // enabled?
+        private final boolean mWouldBlockActivityStartIgnoringFlag;
+
+        BlockActivityStart(boolean shouldBlockActivityStart,
+                boolean wouldBlockActivityStartIgnoringFlags) {
+            this.mBlockActivityStartIfFlagEnabled = shouldBlockActivityStart;
+            this.mWouldBlockActivityStartIgnoringFlag = wouldBlockActivityStartIgnoringFlags;
+        }
+    }
+
+    private class FinishedActivityEntry {
+        int mUid;
+        int mTaskId;
+        int mLaunchCount;
+
+        FinishedActivityEntry(ActivityRecord ar) {
+            FinishedActivityEntry entry = mTaskIdToFinishedActivity.get(ar.getTask().mTaskId);
+            int taskId = ar.getTask().mTaskId;
+            this.mUid = ar.getUid();
+            this.mTaskId = taskId;
+            this.mLaunchCount = entry == null || !ar.isUid(entry.mUid) ? 1 : entry.mLaunchCount + 1;
+
+            mService.mH.postDelayed(() -> {
+                synchronized (mService.mGlobalLock) {
+                    if (mTaskIdToFinishedActivity.get(taskId) == this) {
+                        mTaskIdToFinishedActivity.remove(taskId);
+                    }
+
+                    if (mTopFinishedActivity == this) {
+                        mTopFinishedActivity = null;
+                    }
+                }
+            }, ASM_GRACEPERIOD_TIMEOUT_MS);
+        }
+    }
 }
diff --git a/services/core/java/com/android/server/wm/CompatModePackages.java b/services/core/java/com/android/server/wm/CompatModePackages.java
index e906b18..2439159 100644
--- a/services/core/java/com/android/server/wm/CompatModePackages.java
+++ b/services/core/java/com/android/server/wm/CompatModePackages.java
@@ -451,7 +451,7 @@
         final float appScale = compatScale != null
                 ? compatScale.mScaleFactor
                 : getCompatScale(ai.packageName, ai.uid, /* checkProvider= */ false);
-        final float densityScale = compatScale != null ? compatScale.mDensityScaleFactor : 1f;
+        final float densityScale = compatScale != null ? compatScale.mDensityScaleFactor : appScale;
         final Configuration config = mService.getGlobalConfiguration();
         return new CompatibilityInfo(ai, config.screenLayout, config.smallestScreenWidthDp,
                 forceCompat, appScale, densityScale);
diff --git a/services/core/java/com/android/server/wm/ContentRecorder.java b/services/core/java/com/android/server/wm/ContentRecorder.java
index f0e4149..7cd07d6 100644
--- a/services/core/java/com/android/server/wm/ContentRecorder.java
+++ b/services/core/java/com/android/server/wm/ContentRecorder.java
@@ -151,6 +151,20 @@
                 return;
             }
 
+            // TODO(b/297514518) Do not start capture if the app is in PIP, the bounds are
+            //  inaccurate.
+            if (mContentRecordingSession.getContentToRecord() == RECORD_CONTENT_TASK) {
+                final Task capturedTask = mRecordedWindowContainer.asTask();
+                if (capturedTask.inPinnedWindowingMode()) {
+                    ProtoLog.v(WM_DEBUG_CONTENT_RECORDING,
+                            "Content Recording: Display %d was already recording, but "
+                                    + "pause capture since the task is in PIP",
+                            mDisplayContent.getDisplayId());
+                    pauseRecording();
+                    return;
+                }
+            }
+
             ProtoLog.v(WM_DEBUG_CONTENT_RECORDING,
                     "Content Recording: Display %d was already recording, so apply "
                             + "transformations if necessary",
@@ -292,6 +306,17 @@
             return;
         }
 
+        // TODO(b/297514518) Do not start capture if the app is in PIP, the bounds are inaccurate.
+        if (mContentRecordingSession.getContentToRecord() == RECORD_CONTENT_TASK) {
+            if (mRecordedWindowContainer.asTask().inPinnedWindowingMode()) {
+                ProtoLog.v(WM_DEBUG_CONTENT_RECORDING,
+                        "Content Recording: Display %d should start recording, but "
+                                + "don't yet since the task is in PIP",
+                        mDisplayContent.getDisplayId());
+                return;
+            }
+        }
+
         final Point surfaceSize = fetchSurfaceSizeIfPresent();
         if (surfaceSize == null) {
             ProtoLog.v(WM_DEBUG_CONTENT_RECORDING,
@@ -305,9 +330,6 @@
                         + "state %d",
                 mDisplayContent.getDisplayId(), mDisplayContent.getDisplayInfo().state);
 
-        // TODO(b/274790702): Do not start recording if waiting for consent - for now,
-        //  go ahead.
-
         // Create a mirrored hierarchy for the SurfaceControl of the DisplayArea to capture.
         mRecordedSurface = SurfaceControl.mirrorSurface(
                 mRecordedWindowContainer.getSurfaceControl());
diff --git a/services/core/java/com/android/server/wm/DisplayRotationCompatPolicy.java b/services/core/java/com/android/server/wm/DisplayRotationCompatPolicy.java
index f96f99d..c4ed0dd 100644
--- a/services/core/java/com/android/server/wm/DisplayRotationCompatPolicy.java
+++ b/services/core/java/com/android/server/wm/DisplayRotationCompatPolicy.java
@@ -228,9 +228,9 @@
                             + "activityRecord=%s", activity);
             final ClientTransaction transaction = ClientTransaction.obtain(
                     activity.app.getThread(), activity.token);
-            transaction.addCallback(
-                    RefreshCallbackItem.obtain(cycleThroughStop ? ON_STOP : ON_PAUSE));
-            transaction.setLifecycleStateRequest(ResumeActivityItem.obtain(
+            transaction.addCallback(RefreshCallbackItem.obtain(activity.token,
+                            cycleThroughStop ? ON_STOP : ON_PAUSE));
+            transaction.setLifecycleStateRequest(ResumeActivityItem.obtain(activity.token,
                     /* isForward */ false, /* shouldSendCompatFakeFocus */ false));
             activity.mAtmService.getLifecycleManager().scheduleTransaction(transaction);
             mHandler.postDelayed(
diff --git a/services/core/java/com/android/server/wm/DragState.java b/services/core/java/com/android/server/wm/DragState.java
index 0f1a105..8519e4d 100644
--- a/services/core/java/com/android/server/wm/DragState.java
+++ b/services/core/java/com/android/server/wm/DragState.java
@@ -21,7 +21,6 @@
 import static android.content.ClipDescription.MIMETYPE_APPLICATION_TASK;
 import static android.os.InputConstants.DEFAULT_DISPATCHING_TIMEOUT_MILLIS;
 import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_INTERCEPT_GLOBAL_DRAG_AND_DROP;
-
 import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_ORIENTATION;
 import static com.android.internal.protolog.ProtoLogGroup.WM_SHOW_TRANSACTIONS;
 import static com.android.server.wm.DragDropController.MSG_ANIMATION_END;
@@ -33,7 +32,6 @@
 import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
 import static com.android.server.wm.WindowManagerService.MY_PID;
 import static com.android.server.wm.WindowManagerService.MY_UID;
-
 import static java.util.concurrent.CompletableFuture.completedFuture;
 
 import android.animation.Animator;
@@ -48,7 +46,6 @@
 import android.os.Binder;
 import android.os.Build;
 import android.os.IBinder;
-import android.os.InputConfig;
 import android.os.RemoteException;
 import android.os.UserHandle;
 import android.os.UserManager;
@@ -186,8 +183,13 @@
         // Crop the input surface to the display size.
         mTmpClipRect.set(0, 0, mDisplaySize.x, mDisplaySize.y);
 
+        // Make trusted overlay to not block any touches while D&D ongoing and allowing
+        // touches to pass through to windows underneath. This allows user to interact with the
+        // UI to navigate while dragging.
+        h.setTrustedOverlay(mTransaction, mInputSurface, true);
         mTransaction.show(mInputSurface)
                 .setInputWindowInfo(mInputSurface, h)
+                .setTrustedOverlay(mInputSurface, true)
                 .setLayer(mInputSurface, Integer.MAX_VALUE)
                 .setCrop(mInputSurface, mTmpClipRect);
 
@@ -377,11 +379,6 @@
             mDragWindowHandle.ownerUid = MY_UID;
             mDragWindowHandle.scaleFactor = 1.0f;
 
-            // InputConfig.TRUSTED_OVERLAY: To not block any touches while D&D ongoing and allowing
-            // touches to pass through to windows underneath. This allows user to interact with the
-            // UI to navigate while dragging.
-            mDragWindowHandle.inputConfig = InputConfig.TRUSTED_OVERLAY;
-
             // The drag window cannot receive new touches.
             mDragWindowHandle.touchableRegion.setEmpty();
 
diff --git a/services/core/java/com/android/server/wm/InputConsumerImpl.java b/services/core/java/com/android/server/wm/InputConsumerImpl.java
index 39622c1..c21930d 100644
--- a/services/core/java/com/android/server/wm/InputConsumerImpl.java
+++ b/services/core/java/com/android/server/wm/InputConsumerImpl.java
@@ -74,7 +74,7 @@
         mWindowHandle.ownerPid = WindowManagerService.MY_PID;
         mWindowHandle.ownerUid = WindowManagerService.MY_UID;
         mWindowHandle.scaleFactor = 1.0f;
-        mWindowHandle.inputConfig = InputConfig.NOT_FOCUSABLE | InputConfig.TRUSTED_OVERLAY;
+        mWindowHandle.inputConfig = InputConfig.NOT_FOCUSABLE;
 
         mInputSurface = mService.makeSurfaceBuilder(
                         mService.mRoot.getDisplayContent(displayId).getSession())
@@ -129,12 +129,14 @@
 
     void show(SurfaceControl.Transaction t, WindowContainer w) {
         t.show(mInputSurface);
+        mWindowHandle.setTrustedOverlay(t, mInputSurface, true);
         t.setInputWindowInfo(mInputSurface, mWindowHandle);
         t.setRelativeLayer(mInputSurface, w.getSurfaceControl(), 1);
     }
 
     void show(SurfaceControl.Transaction t, int layer) {
         t.show(mInputSurface);
+        mWindowHandle.setTrustedOverlay(t, mInputSurface, true);
         t.setInputWindowInfo(mInputSurface, mWindowHandle);
         t.setLayer(mInputSurface, layer);
     }
diff --git a/services/core/java/com/android/server/wm/InputMonitor.java b/services/core/java/com/android/server/wm/InputMonitor.java
index 825d38b..f77da62 100644
--- a/services/core/java/com/android/server/wm/InputMonitor.java
+++ b/services/core/java/com/android/server/wm/InputMonitor.java
@@ -40,12 +40,10 @@
 import static android.view.WindowManager.LayoutParams.TYPE_STATUS_BAR_ADDITIONAL;
 import static android.view.WindowManager.LayoutParams.TYPE_VOICE_INTERACTION;
 import static android.view.WindowManager.LayoutParams.TYPE_WALLPAPER;
-
 import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_FOCUS_LIGHT;
 import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_INPUT;
 import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
 import static com.android.server.wm.WindowManagerService.LOGTAG_INPUT_FOCUS;
-
 import static java.lang.Integer.MAX_VALUE;
 
 import android.annotation.Nullable;
@@ -732,7 +730,7 @@
                 new InputWindowHandle(null /* inputApplicationHandle */, displayId));
         inputWindowHandle.setName(name);
         inputWindowHandle.setLayoutParamsType(TYPE_SECURE_SYSTEM_OVERLAY);
-        inputWindowHandle.setTrustedOverlay(true);
+        inputWindowHandle.setTrustedOverlay(t, sc, true);
         populateOverlayInputInfo(inputWindowHandle);
         setInputWindowInfoIfNeeded(t, sc, inputWindowHandle);
     }
diff --git a/services/core/java/com/android/server/wm/InputWindowHandleWrapper.java b/services/core/java/com/android/server/wm/InputWindowHandleWrapper.java
index 64b7a60..90d81bd 100644
--- a/services/core/java/com/android/server/wm/InputWindowHandleWrapper.java
+++ b/services/core/java/com/android/server/wm/InputWindowHandleWrapper.java
@@ -195,6 +195,11 @@
         mChanged = true;
     }
 
+    void setTrustedOverlay(SurfaceControl.Transaction t, SurfaceControl sc,
+            boolean trustedOverlay) {
+        mHandle.setTrustedOverlay(t, sc, trustedOverlay);
+    }
+
     void setOwnerPid(int pid) {
         if (mHandle.ownerPid == pid) {
             return;
diff --git a/services/core/java/com/android/server/wm/LetterboxConfiguration.java b/services/core/java/com/android/server/wm/LetterboxConfiguration.java
index 45cf10b..579fd14 100644
--- a/services/core/java/com/android/server/wm/LetterboxConfiguration.java
+++ b/services/core/java/com/android/server/wm/LetterboxConfiguration.java
@@ -29,6 +29,7 @@
 
 import com.android.internal.R;
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.window.flags.FeatureFlags;
 
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
@@ -90,13 +91,6 @@
             "enable_app_compat_user_aspect_ratio_fullscreen";
     private static final boolean DEFAULT_VALUE_ENABLE_USER_ASPECT_RATIO_FULLSCREEN = true;
 
-    // Whether the letterbox wallpaper style is enabled by default
-    private static final String KEY_ENABLE_LETTERBOX_BACKGROUND_WALLPAPER =
-            "enable_letterbox_background_wallpaper";
-
-    // TODO(b/290048978): Enable wallpaper as default letterbox background.
-    private static final boolean DEFAULT_VALUE_ENABLE_LETTERBOX_BACKGROUND_WALLPAPER = false;
-
     /**
      * Override of aspect ratio for fixed orientation letterboxing that is set via ADB with
      * set-fixed-orientation-letterbox-aspect-ratio or via {@link
@@ -185,6 +179,9 @@
     @NonNull
     private final LetterboxConfigurationPersister mLetterboxConfigurationPersister;
 
+    @NonNull
+    private final FeatureFlags mFeatureFlags;
+
     // Aspect ratio of letterbox for fixed orientation, values <=
     // MIN_FIXED_ORIENTATION_LETTERBOX_ASPECT_RATIO will be ignored.
     private float mFixedOrientationLetterboxAspectRatio;
@@ -301,7 +298,8 @@
     // Flags dynamically updated with {@link android.provider.DeviceConfig}.
     @NonNull private final SynchedDeviceConfig mDeviceConfig;
 
-    LetterboxConfiguration(@NonNull final Context systemUiContext) {
+    LetterboxConfiguration(@NonNull final Context systemUiContext,
+                           @NonNull FeatureFlags featureFags) {
         this(systemUiContext, new LetterboxConfigurationPersister(
                 () -> readLetterboxHorizontalReachabilityPositionFromConfig(
                         systemUiContext, /* forBookMode */ false),
@@ -310,14 +308,16 @@
                 () -> readLetterboxHorizontalReachabilityPositionFromConfig(
                         systemUiContext, /* forBookMode */ true),
                 () -> readLetterboxVerticalReachabilityPositionFromConfig(
-                        systemUiContext, /* forTabletopMode */ true)));
+                        systemUiContext, /* forTabletopMode */ true)),
+                featureFags);
     }
 
     @VisibleForTesting
     LetterboxConfiguration(@NonNull final Context systemUiContext,
-            @NonNull final LetterboxConfigurationPersister letterboxConfigurationPersister) {
+            @NonNull final LetterboxConfigurationPersister letterboxConfigurationPersister,
+            @NonNull FeatureFlags featureFags) {
         mContext = systemUiContext;
-
+        mFeatureFlags = featureFags;
         mFixedOrientationLetterboxAspectRatio = mContext.getResources().getFloat(
                 R.dimen.config_fixedOrientationLetterboxAspectRatio);
         mLetterboxBackgroundType = readLetterboxBackgroundTypeFromConfig(mContext);
@@ -385,8 +385,6 @@
                         DEFAULT_VALUE_ENABLE_USER_ASPECT_RATIO_SETTINGS,
                         mContext.getResources().getBoolean(
                                 R.bool.config_appCompatUserAppAspectRatioSettingsIsEnabled))
-                .addDeviceConfigEntry(KEY_ENABLE_LETTERBOX_BACKGROUND_WALLPAPER,
-                        DEFAULT_VALUE_ENABLE_LETTERBOX_BACKGROUND_WALLPAPER, /* enabled */ true)
                 .addDeviceConfigEntry(KEY_ENABLE_USER_ASPECT_RATIO_FULLSCREEN,
                         DEFAULT_VALUE_ENABLE_USER_ASPECT_RATIO_FULLSCREEN,
                         mContext.getResources().getBoolean(
@@ -544,8 +542,7 @@
     }
 
     /**
-     * Resets letterbox background type value depending on the
-     * {@link #KEY_ENABLE_LETTERBOX_BACKGROUND_WALLPAPER} built time and runtime flags.
+     * Resets letterbox background type value depending on the built time and runtime flags.
      *
      * <p>If enabled, the letterbox background type value is set toZ
      * {@link #LETTERBOX_BACKGROUND_WALLPAPER}. When disabled the letterbox background type value
@@ -555,12 +552,11 @@
         mLetterboxBackgroundTypeOverride = LETTERBOX_BACKGROUND_OVERRIDE_UNSET;
     }
 
-    // Returns KEY_ENABLE_LETTERBOX_BACKGROUND_WALLPAPER if the DeviceConfig flag is enabled
-    // or the value in com.android.internal.R.integer.config_letterboxBackgroundType if the flag
-    // is disabled.
+    // Returns LETTERBOX_BACKGROUND_WALLPAPER if the flag is enabled or the value in
+    // com.android.internal.R.integer.config_letterboxBackgroundType if the flag is disabled.
     @LetterboxBackgroundType
     private int getDefaultLetterboxBackgroundType() {
-        return mDeviceConfig.getFlagValue(KEY_ENABLE_LETTERBOX_BACKGROUND_WALLPAPER)
+        return mFeatureFlags.letterboxBackgroundWallpaperFlag()
                 ? LETTERBOX_BACKGROUND_WALLPAPER : mLetterboxBackgroundType;
     }
 
diff --git a/services/core/java/com/android/server/wm/OWNERS b/services/core/java/com/android/server/wm/OWNERS
index 26abe51..458786f 100644
--- a/services/core/java/com/android/server/wm/OWNERS
+++ b/services/core/java/com/android/server/wm/OWNERS
@@ -18,4 +18,4 @@
 yunfanc@google.com
 
 per-file BackgroundActivityStartController.java = set noparent
-per-file BackgroundActivityStartController.java = brufino@google.com, ogunwale@google.com, louischang@google.com, lus@google.com
+per-file BackgroundActivityStartController.java = brufino@google.com, ogunwale@google.com, louischang@google.com, lus@google.com
\ No newline at end of file
diff --git a/services/core/java/com/android/server/wm/PendingRemoteAnimationRegistry.java b/services/core/java/com/android/server/wm/PendingRemoteAnimationRegistry.java
index 073bbbb..aa04aec 100644
--- a/services/core/java/com/android/server/wm/PendingRemoteAnimationRegistry.java
+++ b/services/core/java/com/android/server/wm/PendingRemoteAnimationRegistry.java
@@ -30,7 +30,7 @@
  */
 class PendingRemoteAnimationRegistry {
 
-    private static final long TIMEOUT_MS = 3000;
+    static final long TIMEOUT_MS = 3000;
 
     private final ArrayMap<String, Entry> mEntries = new ArrayMap<>();
     private final Handler mHandler;
diff --git a/services/core/java/com/android/server/wm/SafeActivityOptions.java b/services/core/java/com/android/server/wm/SafeActivityOptions.java
index fe3094e..6418148 100644
--- a/services/core/java/com/android/server/wm/SafeActivityOptions.java
+++ b/services/core/java/com/android/server/wm/SafeActivityOptions.java
@@ -148,7 +148,8 @@
                 .setPendingIntentBackgroundActivityStartMode(
                         options.getPendingIntentBackgroundActivityStartMode())
                 .setPendingIntentCreatorBackgroundActivityStartMode(
-                        options.getPendingIntentCreatorBackgroundActivityStartMode());
+                        options.getPendingIntentCreatorBackgroundActivityStartMode())
+                .setRemoteTransition(options.getRemoteTransition());
     }
 
     /**
diff --git a/services/core/java/com/android/server/wm/TaskFragment.java b/services/core/java/com/android/server/wm/TaskFragment.java
index 6dc896a..57f44cb 100644
--- a/services/core/java/com/android/server/wm/TaskFragment.java
+++ b/services/core/java/com/android/server/wm/TaskFragment.java
@@ -1466,13 +1466,13 @@
                         if (DEBUG_RESULTS) {
                             Slog.v(TAG_RESULTS, "Delivering results to " + next + ": " + a);
                         }
-                        transaction.addCallback(ActivityResultItem.obtain(a));
+                        transaction.addCallback(ActivityResultItem.obtain(next.token, a));
                     }
                 }
 
                 if (next.newIntents != null) {
                     transaction.addCallback(
-                            NewIntentItem.obtain(next.newIntents, true /* resume */));
+                            NewIntentItem.obtain(next.token, next.newIntents, true /* resume */));
                 }
 
                 // Well the app will no longer be stopped.
@@ -1486,7 +1486,7 @@
                 next.app.setPendingUiCleanAndForceProcessStateUpTo(mAtmService.mTopProcessState);
                 next.abortAndClearOptionsAnimation();
                 transaction.setLifecycleStateRequest(
-                        ResumeActivityItem.obtain(next.app.getReportedProcState(),
+                        ResumeActivityItem.obtain(next.token, next.app.getReportedProcState(),
                                 dc.isNextTransitionForward(), next.shouldSendCompatFakeFocus()));
                 mAtmService.getLifecycleManager().scheduleTransaction(transaction);
 
@@ -1726,7 +1726,7 @@
                     prev.shortComponentName, "userLeaving=" + userLeaving, reason);
 
             mAtmService.getLifecycleManager().scheduleTransaction(prev.app.getThread(),
-                    prev.token, PauseActivityItem.obtain(prev.finishing, userLeaving,
+                    prev.token, PauseActivityItem.obtain(prev.token, prev.finishing, userLeaving,
                             prev.configChangeFlags, pauseImmediately, autoEnteringPip));
         } catch (Exception e) {
             // Ignore exception, if process died other code will cleanup.
diff --git a/services/core/java/com/android/server/wm/TransitionController.java b/services/core/java/com/android/server/wm/TransitionController.java
index e4b9571..0d78701 100644
--- a/services/core/java/com/android/server/wm/TransitionController.java
+++ b/services/core/java/com/android/server/wm/TransitionController.java
@@ -1147,7 +1147,7 @@
             Transition.asyncTraceBegin("animating", 0x41bfaf1 /* hashcode of TAG */);
         } else if (!animatingState && mAnimatingState) {
             t.setEarlyWakeupEnd();
-            mAtm.mWindowManager.requestTraversal();
+            mAtm.mWindowManager.scheduleAnimationLocked();
             mSnapshotController.setPause(false);
             mAnimatingState = false;
             Transition.asyncTraceEnd(0x41bfaf1 /* hashcode of TAG */);
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index 439b719..e720446 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -97,7 +97,6 @@
 import static android.view.displayhash.DisplayHashResultCallback.DISPLAY_HASH_ERROR_MISSING_WINDOW;
 import static android.view.displayhash.DisplayHashResultCallback.DISPLAY_HASH_ERROR_NOT_VISIBLE_ON_SCREEN;
 import static android.window.WindowProviderService.isWindowProviderService;
-
 import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_ADD_REMOVE;
 import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_ANIM;
 import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_BOOT;
@@ -334,6 +333,7 @@
 import com.android.server.policy.WindowManagerPolicy.ScreenOffListener;
 import com.android.server.power.ShutdownThread;
 import com.android.server.utils.PriorityDump;
+import com.android.window.flags.FeatureFlagsImpl;
 
 import dalvik.annotation.optimization.NeverCompile;
 
@@ -1175,7 +1175,8 @@
 
         mLetterboxConfiguration = new LetterboxConfiguration(
                 // Using SysUI context to have access to Material colors extracted from Wallpaper.
-                ActivityThread.currentActivityThread().getSystemUiContext());
+                ActivityThread.currentActivityThread().getSystemUiContext(),
+                new FeatureFlagsImpl());
 
         mInputManager = inputManager; // Must be before createDisplayContentLocked.
         mDisplayManagerInternal = LocalServices.getService(DisplayManagerInternal.class);
@@ -8868,11 +8869,6 @@
             h.inputConfig |= InputConfig.NOT_FOCUSABLE;
         }
 
-        //  Check private trusted overlay flag to set trustedOverlay field of input window handle.
-        if ((privateFlags & PRIVATE_FLAG_TRUSTED_OVERLAY) != 0) {
-            h.inputConfig |= InputConfig.TRUSTED_OVERLAY;
-        }
-
         h.dispatchingTimeoutMillis = DEFAULT_DISPATCHING_TIMEOUT_MILLIS;
         h.ownerUid = callingUid;
         h.ownerPid = callingPid;
@@ -8892,6 +8888,8 @@
         }
 
         final SurfaceControl.Transaction t = mTransactionFactory.get();
+        //  Check private trusted overlay flag to set trustedOverlay field of input window handle.
+        h.setTrustedOverlay(t, surface, (privateFlags & PRIVATE_FLAG_TRUSTED_OVERLAY) != 0);
         t.setInputWindowInfo(surface, h);
         t.apply();
         t.close();
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index b12cc0b..822082b 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -28,6 +28,7 @@
 import static android.os.InputConstants.DEFAULT_DISPATCHING_TIMEOUT_MILLIS;
 import static android.os.PowerManager.DRAW_WAKE_LOCK;
 import static android.os.Trace.TRACE_TAG_WINDOW_MANAGER;
+import static android.view.InputWindowHandle.USE_SURFACE_TRUSTED_OVERLAY;
 import static android.view.SurfaceControl.Transaction;
 import static android.view.SurfaceControl.getGlobalTransaction;
 import static android.view.ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_CONTENT;
@@ -98,7 +99,6 @@
 import static android.view.WindowManagerGlobal.RELAYOUT_RES_FIRST_TIME;
 import static android.view.WindowManagerPolicyConstants.TYPE_LAYER_MULTIPLIER;
 import static android.view.WindowManagerPolicyConstants.TYPE_LAYER_OFFSET;
-
 import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_ADD_REMOVE;
 import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_ANIM;
 import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_APP_TRANSITIONS;
@@ -1112,7 +1112,9 @@
         mInputWindowHandle.setName(getName());
         mInputWindowHandle.setPackageName(mAttrs.packageName);
         mInputWindowHandle.setLayoutParamsType(mAttrs.type);
-        mInputWindowHandle.setTrustedOverlay(shouldWindowHandleBeTrusted(s));
+        if (!USE_SURFACE_TRUSTED_OVERLAY) {
+            mInputWindowHandle.setTrustedOverlay(isWindowTrustedOverlay());
+        }
         if (DEBUG) {
             Slog.v(TAG, "Window " + this + " client=" + c.asBinder()
                             + " token=" + token + " (" + mAttrs.token + ")" + " params=" + a);
@@ -1193,12 +1195,12 @@
                 : service.mAtmService.getProcessController(s.mPid, s.mUid);
     }
 
-    boolean shouldWindowHandleBeTrusted(Session s) {
+    private boolean isWindowTrustedOverlay() {
         return InputMonitor.isTrustedOverlay(mAttrs.type)
                 || ((mAttrs.privateFlags & PRIVATE_FLAG_TRUSTED_OVERLAY) != 0
-                        && s.mCanAddInternalSystemWindow)
+                        && mSession.mCanAddInternalSystemWindow)
                 || ((mAttrs.privateFlags & PRIVATE_FLAG_SYSTEM_APPLICATION_OVERLAY) != 0
-                        && s.mCanCreateSystemApplicationOverlay);
+                        && mSession.mCanCreateSystemApplicationOverlay);
     }
 
     int getTouchOcclusionMode() {
@@ -5192,6 +5194,9 @@
             updateFrameRateSelectionPriorityIfNeeded();
             updateScaleIfNeeded();
             mWinAnimator.prepareSurfaceLocked(getSyncTransaction());
+            if (USE_SURFACE_TRUSTED_OVERLAY) {
+                getSyncTransaction().setTrustedOverlay(mSurfaceControl, isWindowTrustedOverlay());
+            }
         }
         super.prepareSurfaces();
     }
@@ -5944,7 +5949,13 @@
     }
 
     boolean isTrustedOverlay() {
-        return mInputWindowHandle.isTrustedOverlay();
+        if (USE_SURFACE_TRUSTED_OVERLAY) {
+            WindowState parentWindow = getParentWindow();
+            return isWindowTrustedOverlay() || (parentWindow != null
+                    && parentWindow.isWindowTrustedOverlay());
+        } else {
+            return mInputWindowHandle.isTrustedOverlay();
+        }
     }
 
     public boolean receiveFocusFromTapOutside() {
diff --git a/services/devicepolicy/Android.bp b/services/devicepolicy/Android.bp
index d4d17ec..41706f0 100644
--- a/services/devicepolicy/Android.bp
+++ b/services/devicepolicy/Android.bp
@@ -24,4 +24,7 @@
         "app-compat-annotations",
         "service-permission.stubs.system_server",
     ],
+    static_libs: [
+        "device_policy_aconfig_flags_lib",
+    ],
 }
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyEngine.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyEngine.java
index 9c1d765..21b1291 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyEngine.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyEngine.java
@@ -624,6 +624,27 @@
     }
 
     /**
+     * Retrieves the global policy set by the admin for the provided {@code policyDefinition}
+     * if one was set, otherwise returns {@code null}.
+     */
+    @Nullable
+    <V> V getGlobalPolicySetByAdmin(
+            @NonNull PolicyDefinition<V> policyDefinition,
+            @NonNull EnforcingAdmin enforcingAdmin) {
+        Objects.requireNonNull(policyDefinition);
+        Objects.requireNonNull(enforcingAdmin);
+
+        synchronized (mLock) {
+            if (!hasGlobalPolicyLocked(policyDefinition)) {
+                return null;
+            }
+            PolicyValue<V> value = getGlobalPolicyStateLocked(policyDefinition)
+                    .getPoliciesSetByAdmins().get(enforcingAdmin);
+            return value == null ? null : value.getValue();
+        }
+    }
+
+    /**
      * Retrieves the values set for the provided {@code policyDefinition} by each admin.
      */
     @NonNull
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
index 438a9d6..50dc061 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/DevicePolicyManagerService.java
@@ -491,6 +491,7 @@
 import com.android.server.SystemService;
 import com.android.server.SystemServiceManager;
 import com.android.server.devicepolicy.ActiveAdmin.TrustAgentInfo;
+import com.android.server.devicepolicy.flags.FlagUtils;
 import com.android.server.inputmethod.InputMethodManagerInternal;
 import com.android.server.net.NetworkPolicyManagerInternal;
 import com.android.server.pm.DefaultCrossProfileIntentFilter;
@@ -3432,6 +3433,9 @@
             }
 
             revertTransferOwnershipIfNecessaryLocked();
+            if (!FlagUtils.isPolicyEngineMigrationV2Enabled()) {
+                updateUsbDataSignal(mContext, isUsbDataSignalingEnabledInternalLocked());
+            }
         }
 
         // In case flag value has changed, we apply it during boot to avoid doing it concurrently
@@ -9121,9 +9125,15 @@
                         UserManager.DISALLOW_CAMERA);
         if (who != null) {
             EnforcingAdmin admin = getEnforcingAdminForCaller(who, callerPackageName);
-            return Boolean.TRUE.equals(
-                    mDevicePolicyEngine.getLocalPolicySetByAdmin(
-                            policy, admin, affectedUserId));
+            Boolean value = null;
+            if (isDeviceOwner(caller)) {
+                value = mDevicePolicyEngine.getGlobalPolicySetByAdmin(policy, admin);
+            } else {
+                value = mDevicePolicyEngine.getLocalPolicySetByAdmin(
+                        policy, admin, affectedUserId);
+            }
+            return Boolean.TRUE.equals(value);
+
         } else {
             return Boolean.TRUE.equals(
                     mDevicePolicyEngine.getResolvedPolicy(policy, affectedUserId));
@@ -21575,17 +21585,35 @@
         Objects.requireNonNull(packageName, "Admin package name must be provided");
         final CallerIdentity caller = getCallerIdentity(packageName);
 
-        synchronized (getLockObject()) {
-            EnforcingAdmin enforcingAdmin = enforcePermissionAndGetEnforcingAdmin(
-                    /* admin= */ null, MANAGE_DEVICE_POLICY_USB_DATA_SIGNALLING,
-                    caller.getPackageName(),
-                    caller.getUserId());
+        if (!FlagUtils.isPolicyEngineMigrationV2Enabled()) {
+            Preconditions.checkCallAuthorization(
+                    isDefaultDeviceOwner(caller) || isProfileOwnerOfOrganizationOwnedDevice(caller),
+                    "USB data signaling can only be controlled by a device owner or "
+                            + "a profile owner on an organization-owned device.");
             Preconditions.checkState(canUsbDataSignalingBeDisabled(),
                     "USB data signaling cannot be disabled.");
-            mDevicePolicyEngine.setGlobalPolicy(
-                    PolicyDefinition.USB_DATA_SIGNALING,
-                    enforcingAdmin,
-                    new BooleanPolicyValue(enabled));
+        }
+
+        synchronized (getLockObject()) {
+            if (FlagUtils.isPolicyEngineMigrationV2Enabled()) {
+                EnforcingAdmin enforcingAdmin = enforcePermissionAndGetEnforcingAdmin(
+                        /* admin= */ null, MANAGE_DEVICE_POLICY_USB_DATA_SIGNALLING,
+                        caller.getPackageName(),
+                        caller.getUserId());
+                Preconditions.checkState(canUsbDataSignalingBeDisabled(),
+                        "USB data signaling cannot be disabled.");
+                mDevicePolicyEngine.setGlobalPolicy(
+                        PolicyDefinition.USB_DATA_SIGNALING,
+                        enforcingAdmin,
+                        new BooleanPolicyValue(enabled));
+            } else {
+                ActiveAdmin admin = getProfileOwnerOrDeviceOwnerLocked(caller.getUserId());
+                if (admin.mUsbDataSignalingEnabled != enabled) {
+                    admin.mUsbDataSignalingEnabled = enabled;
+                    saveSettingsLocked(caller.getUserId());
+                    updateUsbDataSignal(mContext, isUsbDataSignalingEnabledInternalLocked());
+                }
+            }
         }
         DevicePolicyEventLogger
                 .createEvent(DevicePolicyEnums.SET_USB_DATA_SIGNALING)
@@ -21607,10 +21635,24 @@
     @Override
     public boolean isUsbDataSignalingEnabled(String packageName) {
         final CallerIdentity caller = getCallerIdentity(packageName);
-        Boolean enabled = mDevicePolicyEngine.getResolvedPolicy(
-                PolicyDefinition.USB_DATA_SIGNALING,
-                caller.getUserId());
-        return enabled == null || enabled;
+        if (FlagUtils.isPolicyEngineMigrationV2Enabled()) {
+            Boolean enabled = mDevicePolicyEngine.getResolvedPolicy(
+                    PolicyDefinition.USB_DATA_SIGNALING,
+                    caller.getUserId());
+            return enabled == null || enabled;
+        } else {
+            synchronized (getLockObject()) {
+                // If the caller is an admin, return the policy set by itself. Otherwise
+                // return the device-wide policy.
+                if (isDefaultDeviceOwner(caller) || isProfileOwnerOfOrganizationOwnedDevice(
+                        caller)) {
+                    return getProfileOwnerOrDeviceOwnerLocked(
+                            caller.getUserId()).mUsbDataSignalingEnabled;
+                } else {
+                    return isUsbDataSignalingEnabledInternalLocked();
+                }
+            }
+        }
     }
 
     private boolean isUsbDataSignalingEnabledInternalLocked() {
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/PolicyState.java b/services/devicepolicy/java/com/android/server/devicepolicy/PolicyState.java
index 599c4a7..22464d5 100644
--- a/services/devicepolicy/java/com/android/server/devicepolicy/PolicyState.java
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/PolicyState.java
@@ -260,11 +260,14 @@
                                 break;
                         }
                     }
-                    if (admin != null) {
+                    if (admin != null && value != null) {
                         policiesSetByAdmins.put(admin, value);
                     } else {
-                        Slogf.wtf(TAG, "Error Parsing TAG_ADMIN_POLICY_ENTRY, EnforcingAdmin "
-                                + "is null");
+                        Slogf.wtf(TAG, "Error Parsing TAG_ADMIN_POLICY_ENTRY for "
+                                + (policyDefinition == null ? "unknown policy" : "policy with "
+                                + "definition " + policyDefinition) + ", EnforcingAdmin is: "
+                                + (admin == null ? "null" : admin) + ", value is : "
+                                + (value == null ? "null" : value));
                     }
                     break;
                 case TAG_POLICY_DEFINITION_ENTRY:
@@ -283,7 +286,9 @@
                     }
                     currentResolvedPolicy = policyDefinition.readPolicyValueFromXml(parser);
                     if (currentResolvedPolicy == null) {
-                        Slogf.wtf(TAG, "Error Parsing TAG_RESOLVED_VALUE_ENTRY, "
+                        Slogf.wtf(TAG, "Error Parsing TAG_RESOLVED_VALUE_ENTRY for "
+                                + (policyDefinition == null ? "unknown policy" : "policy with "
+                                + "definition " + policyDefinition) + ", "
                                 + "currentResolvedPolicy is null");
                     }
                     break;
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/flags/Android.bp b/services/devicepolicy/java/com/android/server/devicepolicy/flags/Android.bp
new file mode 100644
index 0000000..1a45782
--- /dev/null
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/flags/Android.bp
@@ -0,0 +1,16 @@
+package {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+aconfig_declarations {
+    name: "device_policy_aconfig_flags",
+    package: "com.android.server.devicepolicy.flags",
+    srcs: [
+        "flags.aconfig",
+    ],
+}
+
+java_aconfig_library {
+    name: "device_policy_aconfig_flags_lib",
+    aconfig_declarations: "device_policy_aconfig_flags",
+}
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/flags/FlagUtils.java b/services/devicepolicy/java/com/android/server/devicepolicy/flags/FlagUtils.java
new file mode 100644
index 0000000..9fe3749
--- /dev/null
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/flags/FlagUtils.java
@@ -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.server.devicepolicy.flags;
+
+import static com.android.server.devicepolicy.flags.Flags.policyEngineMigrationV2Enabled;
+
+import android.os.Binder;
+
+public final class FlagUtils {
+    private FlagUtils(){}
+
+    public static boolean isPolicyEngineMigrationV2Enabled() {
+        return Binder.withCleanCallingIdentity(() -> {
+            return policyEngineMigrationV2Enabled();
+        });
+    }
+}
diff --git a/services/devicepolicy/java/com/android/server/devicepolicy/flags/flags.aconfig b/services/devicepolicy/java/com/android/server/devicepolicy/flags/flags.aconfig
new file mode 100644
index 0000000..00702a9
--- /dev/null
+++ b/services/devicepolicy/java/com/android/server/devicepolicy/flags/flags.aconfig
@@ -0,0 +1,8 @@
+package: "com.android.server.devicepolicy.flags"
+
+flag {
+  name: "policy_engine_migration_v2_enabled"
+  namespace: "enterprise"
+  description: "V2 of the policy engine migrations for Android V"
+  bug: "289520697"
+}
\ No newline at end of file
diff --git a/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodBindingControllerTest.java b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodBindingControllerTest.java
index e87a34e..798e1ae 100644
--- a/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodBindingControllerTest.java
+++ b/services/tests/InputMethodSystemServerTests/src/com/android/server/inputmethod/InputMethodBindingControllerTest.java
@@ -158,7 +158,7 @@
         assertThat(result.result).isEqualTo(InputBindResult.ResultCode.SUCCESS_WAITING_IME_BINDING);
         assertThat(result.id).isEqualTo(info.getId());
         synchronized (ImfLock.class) {
-            assertThat(mBindingController.hasConnection()).isTrue();
+            assertThat(mBindingController.hasMainConnection()).isTrue();
             assertThat(mBindingController.getCurId()).isEqualTo(info.getId());
             assertThat(mBindingController.getCurToken()).isNotNull();
         }
@@ -202,7 +202,7 @@
 
         synchronized (ImfLock.class) {
             // Unbind both main connection and visible connection
-            assertThat(mBindingController.hasConnection()).isFalse();
+            assertThat(mBindingController.hasMainConnection()).isFalse();
             assertThat(mBindingController.isVisibleBound()).isFalse();
             verify(mContext, times(2)).unbindService(any(ServiceConnection.class));
             assertThat(mBindingController.getCurToken()).isNull();
diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/PackageArchiverServiceTest.java b/services/tests/mockingservicestests/src/com/android/server/pm/PackageArchiverTest.java
similarity index 92%
rename from services/tests/mockingservicestests/src/com/android/server/pm/PackageArchiverServiceTest.java
rename to services/tests/mockingservicestests/src/com/android/server/pm/PackageArchiverTest.java
index 60b28d3..d988063 100644
--- a/services/tests/mockingservicestests/src/com/android/server/pm/PackageArchiverServiceTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/pm/PackageArchiverTest.java
@@ -38,7 +38,6 @@
 import android.content.pm.ActivityInfo;
 import android.content.pm.LauncherActivityInfo;
 import android.content.pm.LauncherApps;
-import android.content.pm.PackageArchiver;
 import android.content.pm.PackageInstaller;
 import android.content.pm.PackageManager;
 import android.content.pm.VersionedPackage;
@@ -75,7 +74,7 @@
 @SmallTest
 @Presubmit
 @RunWith(AndroidJUnit4.class)
-public class PackageArchiverServiceTest {
+public class PackageArchiverTest {
 
     private static final String PACKAGE = "com.example";
     private static final String CALLER_PACKAGE = "com.caller";
@@ -119,7 +118,7 @@
 
     private PackageSetting mPackageSetting;
 
-    private PackageArchiverService mArchiveService;
+    private PackageArchiver mArchiveManager;
 
     @Before
     public void setUp() throws Exception {
@@ -160,8 +159,8 @@
                 mock(Resources.class));
         when(mIcon.compress(eq(Bitmap.CompressFormat.PNG), eq(100), any())).thenReturn(true);
 
-        mArchiveService = spy(new PackageArchiverService(mContext, pm));
-        doReturn(ICON_PATH).when(mArchiveService).storeIcon(eq(PACKAGE),
+        mArchiveManager = spy(new PackageArchiver(mContext, pm));
+        doReturn(ICON_PATH).when(mArchiveManager).storeIcon(eq(PACKAGE),
                 any(LauncherActivityInfo.class), eq(mUserId));
     }
 
@@ -169,7 +168,7 @@
     public void archiveApp_callerPackageNameIncorrect() {
         Exception e = assertThrows(
                 SecurityException.class,
-                () -> mArchiveService.requestArchive(PACKAGE, "different", mIntentSender,
+                () -> mArchiveManager.requestArchive(PACKAGE, "different", mIntentSender,
                         UserHandle.CURRENT));
         assertThat(e).hasMessageThat().isEqualTo(
                 String.format(
@@ -186,7 +185,7 @@
 
         Exception e = assertThrows(
                 ParcelableException.class,
-                () -> mArchiveService.requestArchive(PACKAGE, CALLER_PACKAGE, mIntentSender,
+                () -> mArchiveManager.requestArchive(PACKAGE, CALLER_PACKAGE, mIntentSender,
                         UserHandle.CURRENT));
         assertThat(e.getCause()).isInstanceOf(PackageManager.NameNotFoundException.class);
         assertThat(e.getCause()).hasMessageThat().isEqualTo(
@@ -197,7 +196,7 @@
     public void archiveApp_packageNotInstalledForUser() throws IntentSender.SendIntentException {
         mPackageSetting.modifyUserState(UserHandle.CURRENT.getIdentifier()).setInstalled(false);
 
-        mArchiveService.requestArchive(PACKAGE, CALLER_PACKAGE, mIntentSender, UserHandle.CURRENT);
+        mArchiveManager.requestArchive(PACKAGE, CALLER_PACKAGE, mIntentSender, UserHandle.CURRENT);
         rule.mocks().getHandler().flush();
 
         ArgumentCaptor<Intent> intentCaptor = ArgumentCaptor.forClass(Intent.class);
@@ -226,7 +225,7 @@
 
         Exception e = assertThrows(
                 ParcelableException.class,
-                () -> mArchiveService.requestArchive(PACKAGE, CALLER_PACKAGE, mIntentSender,
+                () -> mArchiveManager.requestArchive(PACKAGE, CALLER_PACKAGE, mIntentSender,
                         UserHandle.CURRENT));
         assertThat(e.getCause()).isInstanceOf(PackageManager.NameNotFoundException.class);
         assertThat(e.getCause()).hasMessageThat().isEqualTo("No installer found");
@@ -239,7 +238,7 @@
 
         Exception e = assertThrows(
                 ParcelableException.class,
-                () -> mArchiveService.requestArchive(PACKAGE, CALLER_PACKAGE, mIntentSender,
+                () -> mArchiveManager.requestArchive(PACKAGE, CALLER_PACKAGE, mIntentSender,
                         UserHandle.CURRENT));
         assertThat(e.getCause()).isInstanceOf(PackageManager.NameNotFoundException.class);
         assertThat(e.getCause()).hasMessageThat().isEqualTo(
@@ -249,10 +248,10 @@
     @Test
     public void archiveApp_storeIconFails() throws IntentSender.SendIntentException, IOException {
         IOException e = new IOException("IO");
-        doThrow(e).when(mArchiveService).storeIcon(eq(PACKAGE),
+        doThrow(e).when(mArchiveManager).storeIcon(eq(PACKAGE),
                 any(LauncherActivityInfo.class), eq(mUserId));
 
-        mArchiveService.requestArchive(PACKAGE, CALLER_PACKAGE, mIntentSender, UserHandle.CURRENT);
+        mArchiveManager.requestArchive(PACKAGE, CALLER_PACKAGE, mIntentSender, UserHandle.CURRENT);
         rule.mocks().getHandler().flush();
 
         ArgumentCaptor<Intent> intentCaptor = ArgumentCaptor.forClass(Intent.class);
@@ -268,7 +267,7 @@
 
     @Test
     public void archiveApp_success() {
-        mArchiveService.requestArchive(PACKAGE, CALLER_PACKAGE, mIntentSender, UserHandle.CURRENT);
+        mArchiveManager.requestArchive(PACKAGE, CALLER_PACKAGE, mIntentSender, UserHandle.CURRENT);
         rule.mocks().getHandler().flush();
 
         verify(mInstallerService).uninstall(
@@ -286,7 +285,7 @@
 
         Exception e = assertThrows(
                 SecurityException.class,
-                () -> mArchiveService.requestUnarchive(PACKAGE, "different",
+                () -> mArchiveManager.requestUnarchive(PACKAGE, "different",
                         UserHandle.CURRENT));
         assertThat(e).hasMessageThat().isEqualTo(
                 String.format(
@@ -304,7 +303,7 @@
 
         Exception e = assertThrows(
                 ParcelableException.class,
-                () -> mArchiveService.requestUnarchive(PACKAGE, CALLER_PACKAGE,
+                () -> mArchiveManager.requestUnarchive(PACKAGE, CALLER_PACKAGE,
                         UserHandle.CURRENT));
         assertThat(e.getCause()).isInstanceOf(PackageManager.NameNotFoundException.class);
         assertThat(e.getCause()).hasMessageThat().isEqualTo(
@@ -315,7 +314,7 @@
     public void unarchiveApp_notArchived() {
         Exception e = assertThrows(
                 ParcelableException.class,
-                () -> mArchiveService.requestUnarchive(PACKAGE, CALLER_PACKAGE,
+                () -> mArchiveManager.requestUnarchive(PACKAGE, CALLER_PACKAGE,
                         UserHandle.CURRENT));
         assertThat(e.getCause()).isInstanceOf(PackageManager.NameNotFoundException.class);
         assertThat(e.getCause()).hasMessageThat().isEqualTo(
@@ -338,7 +337,7 @@
 
         Exception e = assertThrows(
                 ParcelableException.class,
-                () -> mArchiveService.requestUnarchive(PACKAGE, CALLER_PACKAGE,
+                () -> mArchiveManager.requestUnarchive(PACKAGE, CALLER_PACKAGE,
                         UserHandle.CURRENT));
         assertThat(e.getCause()).isInstanceOf(PackageManager.NameNotFoundException.class);
         assertThat(e.getCause()).hasMessageThat().isEqualTo(
@@ -349,7 +348,7 @@
     public void unarchiveApp_success() {
         mUserState.setArchiveState(createArchiveState()).setInstalled(false);
 
-        mArchiveService.requestUnarchive(PACKAGE, CALLER_PACKAGE, UserHandle.CURRENT);
+        mArchiveManager.requestUnarchive(PACKAGE, CALLER_PACKAGE, UserHandle.CURRENT);
         rule.mocks().getHandler().flush();
 
         ArgumentCaptor<Intent> intentCaptor = ArgumentCaptor.forClass(Intent.class);
@@ -366,10 +365,10 @@
                 /* initialExtras= */ isNull());
         Intent intent = intentCaptor.getValue();
         assertThat(intent.getFlags() & FLAG_RECEIVER_FOREGROUND).isNotEqualTo(0);
-        assertThat(intent.getStringExtra(PackageArchiver.EXTRA_UNARCHIVE_PACKAGE_NAME)).isEqualTo(
+        assertThat(intent.getStringExtra(PackageInstaller.EXTRA_UNARCHIVE_PACKAGE_NAME)).isEqualTo(
                 PACKAGE);
         assertThat(
-                intent.getBooleanExtra(PackageArchiver.EXTRA_UNARCHIVE_ALL_USERS, true)).isFalse();
+                intent.getBooleanExtra(PackageInstaller.EXTRA_UNARCHIVE_ALL_USERS, true)).isFalse();
         assertThat(intent.getPackage()).isEqualTo(INSTALLER_PACKAGE);
     }
 
diff --git a/services/tests/servicestests/src/com/android/server/accessibility/ProxyManagerTest.java b/services/tests/servicestests/src/com/android/server/accessibility/ProxyManagerTest.java
index 035bef6..3808f30 100644
--- a/services/tests/servicestests/src/com/android/server/accessibility/ProxyManagerTest.java
+++ b/services/tests/servicestests/src/com/android/server/accessibility/ProxyManagerTest.java
@@ -47,7 +47,9 @@
 import android.os.IBinder;
 import android.os.RemoteCallbackList;
 import android.os.RemoteException;
-import android.platform.test.flag.junit.SetFlagsRule;
+import android.platform.test.annotations.RequiresFlagsEnabled;
+import android.platform.test.flag.junit.CheckFlagsRule;
+import android.platform.test.flag.junit.DeviceFlagsValueProvider;
 import android.util.ArraySet;
 import android.view.KeyEvent;
 import android.view.MotionEvent;
@@ -90,8 +92,7 @@
     private static final int STREAMED_CALLING_UID = 9876;
 
     @Rule
-    public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
-
+    public final CheckFlagsRule mCheckFlagsRule = DeviceFlagsValueProvider.createCheckFlagsRule();
 
     @Mock private Context mMockContext;
     @Mock private AccessibilitySecurityPolicy mMockSecurityPolicy;
@@ -116,8 +117,6 @@
         MockitoAnnotations.initMocks(this);
         final Resources resources = InstrumentationRegistry.getContext().getResources();
 
-        mSetFlagsRule.enableFlags(Flags.FLAG_PROXY_USE_APPS_ON_VIRTUAL_DEVICE_LISTENER);
-
         mFocusStrokeWidthDefaultValue =
                 resources.getDimensionPixelSize(R.dimen.accessibility_focus_highlight_stroke_width);
         mFocusColorDefaultValue = resources.getColor(R.color.accessibility_focus_highlight_color);
@@ -230,6 +229,7 @@
      * app changes to the proxy device.
      */
     @Test
+    @RequiresFlagsEnabled(Flags.FLAG_PROXY_USE_APPS_ON_VIRTUAL_DEVICE_LISTENER)
     public void testUpdateProxyOfRunningAppsChange_changedUidIsStreamedApp_propagatesChange() {
         final VirtualDeviceManagerInternal localVdm =
                 Mockito.mock(VirtualDeviceManagerInternal.class);
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintDetectClientTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintDetectClientTest.java
index 20d5f93..78d3a9d 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintDetectClientTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintDetectClientTest.java
@@ -19,6 +19,7 @@
 import static com.google.common.truth.Truth.assertThat;
 
 import static org.mockito.ArgumentMatchers.anyBoolean;
+import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.any;
 import static org.mockito.Mockito.inOrder;
 import static org.mockito.Mockito.never;
@@ -147,6 +148,24 @@
         verify(mBiometricContext).unsubscribe(same(mOperationContextCaptor.getValue()));
     }
 
+    @Test
+    public void testWhenListenerIsNull() {
+        final AidlSession aidl = new AidlSession(0, mHal, USER_ID, mHalSessionCallback);
+        final FingerprintDetectClient client =  new FingerprintDetectClient(mContext, () -> aidl,
+                mToken, 6 /* requestId */, null /* listener */,
+                new FingerprintAuthenticateOptions.Builder()
+                        .setUserId(2)
+                        .setSensorId(1)
+                        .setOpPackageName("a-test")
+                        .build(),
+                mBiometricLogger, mBiometricContext,
+                mUdfpsOverlayController, true /* isStrongBiometric */);
+        client.start(mCallback);
+        client.onInteractionDetected();
+
+        verify(mCallback).onClientFinished(eq(client), anyBoolean());
+    }
+
     private FingerprintDetectClient createClient() throws RemoteException {
         return createClient(200 /* version */);
     }
diff --git a/services/tests/wmtests/AndroidManifest.xml b/services/tests/wmtests/AndroidManifest.xml
index 42e3383..d6a3877 100644
--- a/services/tests/wmtests/AndroidManifest.xml
+++ b/services/tests/wmtests/AndroidManifest.xml
@@ -47,6 +47,8 @@
     <uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>
     <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
     <uses-permission android:name="android.permission.MANAGE_MEDIA_PROJECTION"/>
+    <uses-permission android:name="android.permission.INTERNAL_SYSTEM_WINDOW"/>
+
 
     <!-- TODO: Remove largeHeap hack when memory leak is fixed (b/123984854) -->
     <application android:debuggable="true"
@@ -104,6 +106,11 @@
             android:showWhenLocked="true"
             android:turnScreenOn="true" />
 
+        <activity android:name="android.app.Activity"
+            android:exported="true"
+            android:showWhenLocked="true"
+            android:turnScreenOn="true" />
+
         <activity
             android:name="androidx.test.core.app.InstrumentationActivityInvoker$EmptyActivity"
             android:exported="true">
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
index 31682bc..ae58700 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
@@ -517,7 +517,7 @@
 
         // The configuration change is still sent to the activity, even if it doesn't relaunch.
         final ActivityConfigurationChangeItem expected =
-                ActivityConfigurationChangeItem.obtain(newConfig);
+                ActivityConfigurationChangeItem.obtain(activity.token, newConfig);
         verify(mAtm.getLifecycleManager()).scheduleTransaction(
                 eq(activity.app.getThread()), eq(activity.token), eq(expected));
     }
@@ -597,7 +597,7 @@
         activity.setRequestedOrientation(requestedOrientation);
 
         final ActivityConfigurationChangeItem expected =
-                ActivityConfigurationChangeItem.obtain(newConfig);
+                ActivityConfigurationChangeItem.obtain(activity.token, newConfig);
         verify(mAtm.getLifecycleManager()).scheduleTransaction(eq(activity.app.getThread()),
                 eq(activity.token), eq(expected));
 
@@ -815,7 +815,7 @@
                     false /* preserveWindow */, true /* ignoreStopState */);
 
             final ActivityConfigurationChangeItem expected =
-                    ActivityConfigurationChangeItem.obtain(newConfig);
+                    ActivityConfigurationChangeItem.obtain(activity.token, newConfig);
             verify(mAtm.getLifecycleManager()).scheduleTransaction(
                     eq(activity.app.getThread()), eq(activity.token), eq(expected));
         } finally {
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 ae87e38..e2bb115 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityStarterTests.java
@@ -174,7 +174,7 @@
         mController = mock(ActivityStartController.class);
         BackgroundActivityStartController balController =
                 new BackgroundActivityStartController(mAtm, mSupervisor);
-        doReturn(balController).when(mController).getBackgroundActivityLaunchController();
+        doReturn(balController).when(mAtm.mTaskSupervisor).getBackgroundActivityLaunchController();
         mActivityMetricsLogger = mock(ActivityMetricsLogger.class);
         clearInvocations(mActivityMetricsLogger);
         mAppOpsManager = mAtm.getAppOpsManager();
diff --git a/services/tests/wmtests/src/com/android/server/wm/ContentRecorderTests.java b/services/tests/wmtests/src/com/android/server/wm/ContentRecorderTests.java
index 622e81e..ecd84e1 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ContentRecorderTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ContentRecorderTests.java
@@ -16,6 +16,9 @@
 
 package com.android.server.wm;
 
+import static android.app.WindowConfiguration.WINDOWING_MODE_FULLSCREEN;
+import static android.app.WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW;
+import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
 import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_FULL_SENSOR;
 import static android.content.res.Configuration.ORIENTATION_LANDSCAPE;
 import static android.content.res.Configuration.ORIENTATION_PORTRAIT;
@@ -361,6 +364,39 @@
     }
 
     @Test
+    public void testTaskWindowingModeChanged_pip_stopsRecording() {
+        // WHEN a recording is ongoing.
+        mTask.setWindowingMode(WINDOWING_MODE_FULLSCREEN);
+        mContentRecorder.setContentRecordingSession(mTaskSession);
+        mContentRecorder.updateRecording();
+        assertThat(mContentRecorder.isCurrentlyRecording()).isTrue();
+
+        // WHEN a configuration change arrives, and the task is now pinned.
+        mTask.setWindowingMode(WINDOWING_MODE_PINNED);
+        Configuration configuration = mTask.getConfiguration();
+        mTask.onConfigurationChanged(configuration);
+
+        // THEN recording is paused.
+        assertThat(mContentRecorder.isCurrentlyRecording()).isFalse();
+    }
+
+    @Test
+    public void testTaskWindowingModeChanged_fullscreen_startsRecording() {
+        // WHEN a recording is ongoing.
+        mTask.setWindowingMode(WINDOWING_MODE_PINNED);
+        mContentRecorder.setContentRecordingSession(mTaskSession);
+        mContentRecorder.updateRecording();
+        assertThat(mContentRecorder.isCurrentlyRecording()).isFalse();
+
+        // WHEN the task is now fullscreen.
+        mTask.setWindowingMode(WINDOWING_MODE_FULLSCREEN);
+        mContentRecorder.updateRecording();
+
+        // THEN recording is started.
+        assertThat(mContentRecorder.isCurrentlyRecording()).isTrue();
+    }
+
+    @Test
     public void testStartRecording_notifiesCallback_taskSession() {
         // WHEN a recording is ongoing.
         mContentRecorder.setContentRecordingSession(mTaskSession);
@@ -385,6 +421,45 @@
     }
 
     @Test
+    public void testStartRecording_taskInPIP_recordingNotStarted() {
+        // GIVEN a task is in PIP.
+        mContentRecorder.setContentRecordingSession(mTaskSession);
+        mTask.setWindowingMode(WINDOWING_MODE_PINNED);
+
+        // WHEN a recording tries to start.
+        mContentRecorder.updateRecording();
+
+        // THEN recording does not start.
+        assertThat(mContentRecorder.isCurrentlyRecording()).isFalse();
+    }
+
+    @Test
+    public void testStartRecording_taskInSplit_recordingStarted() {
+        // GIVEN a task is in PIP.
+        mContentRecorder.setContentRecordingSession(mTaskSession);
+        mTask.setWindowingMode(WINDOWING_MODE_MULTI_WINDOW);
+
+        // WHEN a recording tries to start.
+        mContentRecorder.updateRecording();
+
+        // THEN recording does not start.
+        assertThat(mContentRecorder.isCurrentlyRecording()).isTrue();
+    }
+
+    @Test
+    public void testStartRecording_taskInFullscreen_recordingStarted() {
+        // GIVEN a task is in PIP.
+        mContentRecorder.setContentRecordingSession(mTaskSession);
+        mTask.setWindowingMode(WINDOWING_MODE_FULLSCREEN);
+
+        // WHEN a recording tries to start.
+        mContentRecorder.updateRecording();
+
+        // THEN recording does not start.
+        assertThat(mContentRecorder.isCurrentlyRecording()).isTrue();
+    }
+
+    @Test
     public void testOnVisibleRequestedChanged_notifiesCallback() {
         // WHEN a recording is ongoing.
         mContentRecorder.setContentRecordingSession(mTaskSession);
diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayRotationCompatPolicyTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayRotationCompatPolicyTests.java
index 769a309..1b44c01 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayRotationCompatPolicyTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayRotationCompatPolicyTests.java
@@ -573,8 +573,9 @@
 
         final ClientTransaction transaction = ClientTransaction.obtain(
                 mActivity.app.getThread(), mActivity.token);
-        transaction.addCallback(RefreshCallbackItem.obtain(cycleThroughStop ? ON_STOP : ON_PAUSE));
-        transaction.setLifecycleStateRequest(ResumeActivityItem.obtain(
+        transaction.addCallback(RefreshCallbackItem.obtain(mActivity.token,
+                cycleThroughStop ? ON_STOP : ON_PAUSE));
+        transaction.setLifecycleStateRequest(ResumeActivityItem.obtain(mActivity.token,
                 /* isForward */ false, /* shouldSendCompatFakeFocus */ false));
 
         verify(mActivity.mAtmService.getLifecycleManager(), times(refreshRequested ? 1 : 0))
diff --git a/services/tests/wmtests/src/com/android/server/wm/LetterboxConfigurationTest.java b/services/tests/wmtests/src/com/android/server/wm/LetterboxConfigurationTest.java
index 80e169d..be3f01e 100644
--- a/services/tests/wmtests/src/com/android/server/wm/LetterboxConfigurationTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/LetterboxConfigurationTest.java
@@ -17,14 +17,15 @@
 package com.android.server.wm;
 
 import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
-
+import static com.android.server.wm.LetterboxConfiguration.LETTERBOX_BACKGROUND_SOLID_COLOR;
+import static com.android.server.wm.LetterboxConfiguration.LETTERBOX_BACKGROUND_WALLPAPER;
 import static com.android.server.wm.LetterboxConfiguration.LETTERBOX_HORIZONTAL_REACHABILITY_POSITION_CENTER;
 import static com.android.server.wm.LetterboxConfiguration.LETTERBOX_HORIZONTAL_REACHABILITY_POSITION_LEFT;
 import static com.android.server.wm.LetterboxConfiguration.LETTERBOX_HORIZONTAL_REACHABILITY_POSITION_RIGHT;
 import static com.android.server.wm.LetterboxConfiguration.LETTERBOX_VERTICAL_REACHABILITY_POSITION_BOTTOM;
 import static com.android.server.wm.LetterboxConfiguration.LETTERBOX_VERTICAL_REACHABILITY_POSITION_CENTER;
 import static com.android.server.wm.LetterboxConfiguration.LETTERBOX_VERTICAL_REACHABILITY_POSITION_TOP;
-
+import static junit.framework.Assert.assertEquals;
 import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.mock;
@@ -37,6 +38,8 @@
 
 import androidx.test.filters.SmallTest;
 
+import com.android.window.flags.FakeFeatureFlagsImpl;
+
 import org.junit.Before;
 import org.junit.Test;
 
@@ -57,12 +60,16 @@
     private LetterboxConfiguration mLetterboxConfiguration;
     private LetterboxConfigurationPersister mLetterboxConfigurationPersister;
 
+    private MutableFakeFeatureFlagsImpl mMutableFakeFeatureFlags;
+
+
     @Before
     public void setUp() throws Exception {
         mContext = getInstrumentation().getTargetContext();
+        mMutableFakeFeatureFlags = new MutableFakeFeatureFlagsImpl();
         mLetterboxConfigurationPersister = mock(LetterboxConfigurationPersister.class);
         mLetterboxConfiguration = new LetterboxConfiguration(mContext,
-                mLetterboxConfigurationPersister);
+                mLetterboxConfigurationPersister, mMutableFakeFeatureFlags);
     }
 
     @Test
@@ -92,6 +99,22 @@
     }
 
     @Test
+    public void test_whenFlagEnabled_wallpaperIsDefaultBackground() {
+        mMutableFakeFeatureFlags.setLetterboxBackgroundWallpaperFlag(true);
+        assertEquals(LETTERBOX_BACKGROUND_WALLPAPER,
+                mLetterboxConfiguration.getLetterboxBackgroundType());
+        assertEquals(1, mMutableFakeFeatureFlags.getInvocationCount());
+    }
+
+    @Test
+    public void test_whenFlagDisabled_solidColorIsDefaultBackground() {
+        mMutableFakeFeatureFlags.setLetterboxBackgroundWallpaperFlag(false);
+        assertEquals(LETTERBOX_BACKGROUND_SOLID_COLOR,
+                mLetterboxConfiguration.getLetterboxBackgroundType());
+        assertEquals(1, mMutableFakeFeatureFlags.getInvocationCount());
+    }
+
+    @Test
     public void test_whenMovedHorizontally_updatePositionAccordingly() {
         // Starting from center
         assertForHorizontalMove(
@@ -288,4 +311,23 @@
                 false /* forTabletopMode */,
                 LETTERBOX_VERTICAL_REACHABILITY_POSITION_TOP);
     }
+
+    private static class MutableFakeFeatureFlagsImpl extends FakeFeatureFlagsImpl {
+        private boolean mLetterboxBackgroundWallpaperFlag;
+        private int mInvocationCount;
+
+        public void setLetterboxBackgroundWallpaperFlag(boolean letterboxBackgroundWallpaperFlag) {
+            mLetterboxBackgroundWallpaperFlag = letterboxBackgroundWallpaperFlag;
+        }
+
+        @Override
+        public boolean letterboxBackgroundWallpaperFlag() {
+            mInvocationCount++;
+            return mLetterboxBackgroundWallpaperFlag;
+        }
+
+        int getInvocationCount() {
+            return mInvocationCount;
+        }
+    }
 }
diff --git a/services/tests/wmtests/src/com/android/server/wm/TrustedOverlayTests.java b/services/tests/wmtests/src/com/android/server/wm/TrustedOverlayTests.java
new file mode 100644
index 0000000..e8a847c
--- /dev/null
+++ b/services/tests/wmtests/src/com/android/server/wm/TrustedOverlayTests.java
@@ -0,0 +1,149 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.wm;
+
+import static android.view.InputWindowHandle.USE_SURFACE_TRUSTED_OVERLAY;
+import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_TRUSTED_OVERLAY;
+import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_PANEL;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assume.assumeFalse;
+import static org.junit.Assume.assumeTrue;
+
+import android.app.Activity;
+import android.app.Instrumentation;
+import android.os.IBinder;
+import android.platform.test.annotations.Presubmit;
+import android.server.wm.BuildUtils;
+import android.server.wm.CtsWindowInfoUtils;
+import android.view.View;
+import android.view.ViewTreeObserver;
+import android.view.WindowManager;
+
+import androidx.test.platform.app.InstrumentationRegistry;
+import androidx.test.rule.ActivityTestRule;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TestName;
+
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
+@Presubmit
+public class TrustedOverlayTests {
+    private static final String TAG = "TrustedOverlayTests";
+    private static final long TIMEOUT_S = 5L * BuildUtils.HW_TIMEOUT_MULTIPLIER;
+
+    @Rule
+    public TestName mName = new TestName();
+
+    private final ActivityTestRule<Activity> mActivityRule = new ActivityTestRule<>(
+            Activity.class);
+
+    private Instrumentation mInstrumentation;
+    private Activity mActivity;
+
+    @Before
+    public void setup() {
+        mInstrumentation = InstrumentationRegistry.getInstrumentation();
+        mActivity = mActivityRule.launchActivity(null);
+    }
+
+    @Test
+    public void setTrustedOverlayInputWindow() throws InterruptedException {
+        assumeFalse(USE_SURFACE_TRUSTED_OVERLAY);
+        testTrustedOverlayChildHelper(false);
+    }
+
+    @Test
+    public void setTrustedOverlayChildLayer() throws InterruptedException {
+        assumeTrue(USE_SURFACE_TRUSTED_OVERLAY);
+        testTrustedOverlayChildHelper(true);
+    }
+
+    private void testTrustedOverlayChildHelper(boolean expectTrusted) throws InterruptedException {
+        IBinder[] tokens = new IBinder[2];
+        CountDownLatch hostTokenReady = new CountDownLatch(1);
+        mInstrumentation.runOnMainSync(() -> {
+            mActivity.getWindow().addPrivateFlags(PRIVATE_FLAG_TRUSTED_OVERLAY);
+            View rootView = mActivity.getWindow().getDecorView();
+            if (rootView.isAttachedToWindow()) {
+                tokens[0] = rootView.getWindowToken();
+                hostTokenReady.countDown();
+            } else {
+                rootView.getViewTreeObserver().addOnWindowAttachListener(
+                        new ViewTreeObserver.OnWindowAttachListener() {
+                            @Override
+                            public void onWindowAttached() {
+                                tokens[0] = rootView.getWindowToken();
+                                hostTokenReady.countDown();
+                            }
+
+                            @Override
+                            public void onWindowDetached() {
+                            }
+                        });
+            }
+        });
+
+        assertTrue("Failed to wait for host to get added",
+                hostTokenReady.await(TIMEOUT_S, TimeUnit.SECONDS));
+
+        mInstrumentation.runOnMainSync(() -> {
+            WindowManager wm = mActivity.getSystemService(WindowManager.class);
+
+            View childView = new View(mActivity) {
+                @Override
+                protected void onAttachedToWindow() {
+                    super.onAttachedToWindow();
+                    tokens[1] = getWindowToken();
+                }
+            };
+            WindowManager.LayoutParams params = new WindowManager.LayoutParams();
+            params.token = tokens[0];
+            params.type = TYPE_APPLICATION_PANEL;
+            wm.addView(childView, params);
+        });
+
+        boolean[] foundTrusted = new boolean[2];
+
+        CtsWindowInfoUtils.waitForWindowInfos(
+                windowInfos -> {
+                    for (var windowInfo : windowInfos) {
+                        if (windowInfo.windowToken == tokens[0]
+                                && windowInfo.isTrustedOverlay) {
+                            foundTrusted[0] = true;
+                        } else if (windowInfo.windowToken == tokens[1]
+                                && windowInfo.isTrustedOverlay) {
+                            foundTrusted[1] = true;
+                        }
+                    }
+                    return foundTrusted[0] && foundTrusted[1];
+                }, TIMEOUT_S, TimeUnit.SECONDS);
+
+        if (!foundTrusted[0] || !foundTrusted[1]) {
+            CtsWindowInfoUtils.dumpWindowsOnScreen(TAG, mName.getMethodName());
+        }
+
+        assertEquals("Failed to find parent window or was not marked trusted", expectTrusted,
+                foundTrusted[0]);
+        assertEquals("Failed to find child window or was not marked trusted", expectTrusted,
+                foundTrusted[1]);
+    }
+}
diff --git a/services/wallpapereffectsgeneration/java/com/android/server/wallpapereffectsgeneration/WallpaperEffectsGenerationPerUserService.java b/services/wallpapereffectsgeneration/java/com/android/server/wallpapereffectsgeneration/WallpaperEffectsGenerationPerUserService.java
index 3870dfd..4f99f14 100644
--- a/services/wallpapereffectsgeneration/java/com/android/server/wallpapereffectsgeneration/WallpaperEffectsGenerationPerUserService.java
+++ b/services/wallpapereffectsgeneration/java/com/android/server/wallpapereffectsgeneration/WallpaperEffectsGenerationPerUserService.java
@@ -95,21 +95,29 @@
         String newTaskId = cinematicEffectRequest.getTaskId();
         // Previous request is still being processed.
         if (mCinematicEffectListenerWrapper != null) {
+            CinematicEffectResponse cinematicEffectResponse;
             if (mCinematicEffectListenerWrapper.mTaskId.equals(newTaskId)) {
-                invokeCinematicListenerAndCleanup(
-                        new CinematicEffectResponse.Builder(
-                                CinematicEffectResponse.CINEMATIC_EFFECT_STATUS_PENDING, newTaskId)
-                                .build()
-                );
+                cinematicEffectResponse =  new CinematicEffectResponse.Builder(
+                        CinematicEffectResponse.CINEMATIC_EFFECT_STATUS_PENDING, newTaskId)
+                        .build();
             } else {
-                invokeCinematicListenerAndCleanup(
-                        new CinematicEffectResponse.Builder(
-                                CinematicEffectResponse.CINEMATIC_EFFECT_STATUS_TOO_MANY_REQUESTS,
-                                newTaskId).build()
-                );
+                cinematicEffectResponse =  new CinematicEffectResponse.Builder(
+                        CinematicEffectResponse.CINEMATIC_EFFECT_STATUS_TOO_MANY_REQUESTS,
+                        newTaskId)
+                        .build();
             }
-            return;
+            try {
+                cinematicEffectListener.onCinematicEffectGenerated(cinematicEffectResponse);
+                return;
+            } catch (RemoteException e) {
+                if (isDebug()) {
+                    Slog.w(TAG, "RemoteException invoking cinematic effect listener for task["
+                            + mCinematicEffectListenerWrapper.mTaskId + "]");
+                }
+                return;
+            }
         }
+
         RemoteWallpaperEffectsGenerationService remoteService = ensureRemoteServiceLocked();
         if (remoteService != null) {
             remoteService.executeOnResolvedService(
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/ImeShownOnAppStartHelper.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/ImeShownOnAppStartHelper.kt
index 1a65611..e106f65 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/ImeShownOnAppStartHelper.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/ImeShownOnAppStartHelper.kt
@@ -18,8 +18,6 @@
 
 import android.app.Instrumentation
 import android.tools.common.Rotation
-import android.tools.common.traces.Condition
-import android.tools.common.traces.DeviceStateDump
 import android.tools.common.traces.component.ComponentNameMatcher
 import android.tools.common.traces.component.IComponentMatcher
 import android.tools.device.helpers.FIND_TIMEOUT
@@ -54,14 +52,14 @@
         launchedAppComponentMatcherOverride: IComponentMatcher?,
         action: String?,
         stringExtras: Map<String, String>,
-        waitConditions: Array<Condition<DeviceStateDump>>
+        waitConditionsBuilder: WindowManagerStateHelper.StateSyncBuilder
     ) {
         super.launchViaIntent(
             wmHelper,
             launchedAppComponentMatcherOverride,
             action,
             stringExtras,
-            waitConditions
+            waitConditionsBuilder
         )
         waitIMEShown(wmHelper)
     }
diff --git a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/PipAppHelper.kt b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/PipAppHelper.kt
index 82d2ae0..c30786f 100644
--- a/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/PipAppHelper.kt
+++ b/tests/FlickerTests/src/com/android/server/wm/flicker/helpers/PipAppHelper.kt
@@ -244,12 +244,16 @@
         action: String? = null,
         stringExtras: Map<String, String>
     ) {
-        launchViaIntentAndWaitShown(
+        launchViaIntent(
             wmHelper,
             launchedAppComponentMatcherOverride,
             action,
             stringExtras,
-            waitConditions = arrayOf(ConditionsFactory.hasPipWindow())
+            waitConditionsBuilder = wmHelper
+                .StateSyncBuilder()
+                .add(ConditionsFactory.isWMStateComplete())
+                .withAppTransitionIdle()
+                .add(ConditionsFactory.hasPipWindow())
         )
 
         wmHelper
@@ -261,7 +265,7 @@
 
     /** Expand the PIP window back to full screen via intent and wait until the app is visible */
     fun exitPipToFullScreenViaIntent(wmHelper: WindowManagerStateHelper) =
-        launchViaIntentAndWaitShown(wmHelper)
+        launchViaIntent(wmHelper)
 
     fun clickEnterPipButton(wmHelper: WindowManagerStateHelper) {
         clickObject(ENTER_PIP_BUTTON_ID)