Merge "[flexiglass] Moves hardcoded enabledness check into SceneContainerFlags." into main
diff --git a/AconfigFlags.bp b/AconfigFlags.bp
index 6bd6c93..525ae80 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,9 +29,11 @@
         ":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}",
     ],
     // Add aconfig-annotations-lib as a dependency for the optimization
     libs: ["aconfig-annotations-lib"],
@@ -58,6 +61,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",
@@ -214,3 +224,28 @@
     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",
+    package: "com.android.media.flags",
+    srcs: ["media/java/android/media/flags/media_better_together.aconfig"],
+}
+
+java_aconfig_library {
+    name: "com.android.media.flags.bettertogether-aconfig-java",
+    aconfig_declarations: "com.android.media.flags.bettertogether-aconfig",
+    defaults: ["framework-minus-apex-aconfig-java-defaults"],
+}
diff --git a/OWNERS b/OWNERS
index 6c25324..4e5c7d8 100644
--- a/OWNERS
+++ b/OWNERS
@@ -28,7 +28,7 @@
 # Support bulk translation updates
 per-file */res*/values*/*.xml = byi@google.com, delphij@google.com
 
-per-file **.bp,**.mk = hansson@google.com, joeo@google.com
+per-file **.bp,**.mk = hansson@google.com, joeo@google.com, lamontjones@google.com
 per-file TestProtoLibraries.bp = file:platform/platform_testing:/libraries/health/OWNERS
 per-file TestProtoLibraries.bp = file:platform/tools/tradefederation:/OWNERS
 
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/apex/jobscheduler/service/java/com/android/server/tare/Agent.java b/apex/jobscheduler/service/java/com/android/server/tare/Agent.java
index dcc324d..5c60562 100644
--- a/apex/jobscheduler/service/java/com/android/server/tare/Agent.java
+++ b/apex/jobscheduler/service/java/com/android/server/tare/Agent.java
@@ -75,7 +75,7 @@
     private static final String ALARM_TAG_AFFORDABILITY_CHECK = "*tare.affordability_check*";
 
     private final Object mLock;
-    private final Handler mHandler;
+    private final AgentHandler mHandler;
     private final Analyst mAnalyst;
     private final InternalResourceService mIrs;
     private final Scribe mScribe;
@@ -992,6 +992,7 @@
     void tearDownLocked() {
         mCurrentOngoingEvents.clear();
         mBalanceThresholdAlarmQueue.removeAllAlarms();
+        mHandler.removeAllMessages();
     }
 
     @VisibleForTesting
@@ -1290,6 +1291,11 @@
                 break;
             }
         }
+
+        void removeAllMessages() {
+            removeMessages(MSG_CHECK_ALL_AFFORDABILITY);
+            removeMessages(MSG_CHECK_INDIVIDUAL_AFFORDABILITY);
+        }
     }
 
     @GuardedBy("mLock")
diff --git a/core/api/current.txt b/core/api/current.txt
index d92c693..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);
   }
@@ -23946,10 +23951,12 @@
     method @Nullable public android.media.MediaRouter2.RoutingController getController(@NonNull String);
     method @NonNull public java.util.List<android.media.MediaRouter2.RoutingController> getControllers();
     method @NonNull public static android.media.MediaRouter2 getInstance(@NonNull android.content.Context);
+    method @FlaggedApi(FLAG_ENABLE_RLP_CALLBACKS_IN_MEDIA_ROUTER2) @Nullable public android.media.RouteListingPreference getRouteListingPreference();
     method @NonNull public java.util.List<android.media.MediaRoute2Info> getRoutes();
     method @NonNull public android.media.MediaRouter2.RoutingController getSystemController();
     method public void registerControllerCallback(@NonNull java.util.concurrent.Executor, @NonNull android.media.MediaRouter2.ControllerCallback);
     method public void registerRouteCallback(@NonNull java.util.concurrent.Executor, @NonNull android.media.MediaRouter2.RouteCallback, @NonNull android.media.RouteDiscoveryPreference);
+    method @FlaggedApi(FLAG_ENABLE_RLP_CALLBACKS_IN_MEDIA_ROUTER2) public void registerRouteListingPreferenceCallback(@NonNull java.util.concurrent.Executor, @NonNull android.media.MediaRouter2.RouteListingPreferenceCallback);
     method public void registerTransferCallback(@NonNull java.util.concurrent.Executor, @NonNull android.media.MediaRouter2.TransferCallback);
     method public void setOnGetControllerHintsListener(@Nullable android.media.MediaRouter2.OnGetControllerHintsListener);
     method public void setRouteListingPreference(@Nullable android.media.RouteListingPreference);
@@ -23958,6 +23965,7 @@
     method public void transferTo(@NonNull android.media.MediaRoute2Info);
     method public void unregisterControllerCallback(@NonNull android.media.MediaRouter2.ControllerCallback);
     method public void unregisterRouteCallback(@NonNull android.media.MediaRouter2.RouteCallback);
+    method @FlaggedApi(FLAG_ENABLE_RLP_CALLBACKS_IN_MEDIA_ROUTER2) public void unregisterRouteListingPreferenceCallback(@NonNull android.media.MediaRouter2.RouteListingPreferenceCallback);
     method public void unregisterTransferCallback(@NonNull android.media.MediaRouter2.TransferCallback);
   }
 
@@ -23978,6 +23986,11 @@
     method public void onRoutesUpdated(@NonNull java.util.List<android.media.MediaRoute2Info>);
   }
 
+  @FlaggedApi(FLAG_ENABLE_RLP_CALLBACKS_IN_MEDIA_ROUTER2) public abstract static class MediaRouter2.RouteListingPreferenceCallback {
+    ctor @FlaggedApi(FLAG_ENABLE_RLP_CALLBACKS_IN_MEDIA_ROUTER2) public MediaRouter2.RouteListingPreferenceCallback();
+    method @FlaggedApi(FLAG_ENABLE_RLP_CALLBACKS_IN_MEDIA_ROUTER2) public void onRouteListingPreferenceChanged(@Nullable android.media.RouteListingPreference);
+  }
+
   public class MediaRouter2.RoutingController {
     method public void deselectRoute(@NonNull android.media.MediaRoute2Info);
     method @Nullable public android.os.Bundle getControlHints();
@@ -46573,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);
@@ -46588,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);
@@ -46601,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;
   }
 
@@ -46786,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);
@@ -46795,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);
@@ -46823,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);
@@ -46840,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
@@ -46880,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);
@@ -46898,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 {
@@ -47875,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);
   }
@@ -59871,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();
@@ -60014,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 a5e69ab..fcfa41a6 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -3192,6 +3192,7 @@
 
   public static class VirtualDeviceManager.VirtualDevice implements java.lang.AutoCloseable {
     method public void addActivityListener(@NonNull java.util.concurrent.Executor, @NonNull android.companion.virtual.VirtualDeviceManager.ActivityListener);
+    method @FlaggedApi(Flags.FLAG_DYNAMIC_POLICY) @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) public void addActivityPolicyExemption(@NonNull android.content.ComponentName);
     method public void addSoundEffectListener(@NonNull java.util.concurrent.Executor, @NonNull android.companion.virtual.VirtualDeviceManager.SoundEffectListener);
     method @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) public void close();
     method @NonNull public android.content.Context createContext();
@@ -3212,6 +3213,7 @@
     method public void launchPendingIntent(int, @NonNull android.app.PendingIntent, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.IntConsumer);
     method @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) public void registerIntentInterceptor(@NonNull android.content.IntentFilter, @NonNull java.util.concurrent.Executor, @NonNull android.companion.virtual.VirtualDeviceManager.IntentInterceptorCallback);
     method public void removeActivityListener(@NonNull android.companion.virtual.VirtualDeviceManager.ActivityListener);
+    method @FlaggedApi(Flags.FLAG_DYNAMIC_POLICY) @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) public void removeActivityPolicyExemption(@NonNull android.content.ComponentName);
     method public void removeSoundEffectListener(@NonNull android.companion.virtual.VirtualDeviceManager.SoundEffectListener);
     method @FlaggedApi(Flags.FLAG_DYNAMIC_POLICY) @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) public void setDevicePolicy(int, int);
     method @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE) public void setShowPointerIcon(boolean);
@@ -3243,6 +3245,7 @@
     field public static final int LOCK_STATE_DEFAULT = 0; // 0x0
     field public static final int NAVIGATION_POLICY_DEFAULT_ALLOWED = 0; // 0x0
     field public static final int NAVIGATION_POLICY_DEFAULT_BLOCKED = 1; // 0x1
+    field @FlaggedApi(Flags.FLAG_DYNAMIC_POLICY) public static final int POLICY_TYPE_ACTIVITY = 3; // 0x3
     field public static final int POLICY_TYPE_AUDIO = 1; // 0x1
     field public static final int POLICY_TYPE_RECENTS = 2; // 0x2
     field public static final int POLICY_TYPE_SENSORS = 0; // 0x0
@@ -3795,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;
   }
@@ -3809,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";
@@ -3819,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
@@ -3903,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[]);
@@ -3929,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);
@@ -3979,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/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 39589fa..f28b4b4 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -36,7 +36,6 @@
 import static android.window.ConfigurationHelper.freeTextLayoutCachesIfNeeded;
 import static android.window.ConfigurationHelper.isDifferentDisplay;
 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;
 
@@ -1286,8 +1285,13 @@
         }
 
         private void updateCompatOverrideScale(CompatibilityInfo info) {
-            CompatibilityInfo.setOverrideInvertedScale(
-                    info.hasOverrideScaling() ? info.applicationInvertedScale : 1f);
+            if (info.hasOverrideScaling()) {
+                CompatibilityInfo.setOverrideInvertedScale(info.applicationInvertedScale,
+                        info.applicationDensityInvertedScale);
+            } else {
+                CompatibilityInfo.setOverrideInvertedScale(/* invertScale */ 1f,
+                        /* densityInvertScale */1f);
+            }
         }
 
         public final void runIsolatedEntryPoint(String entryPoint, String[] entryPointArgs) {
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/companion/virtual/IVirtualDevice.aidl b/core/java/android/companion/virtual/IVirtualDevice.aidl
index c58561d..bf00a5a 100644
--- a/core/java/android/companion/virtual/IVirtualDevice.aidl
+++ b/core/java/android/companion/virtual/IVirtualDevice.aidl
@@ -23,6 +23,7 @@
 import android.companion.virtual.sensor.VirtualSensor;
 import android.companion.virtual.sensor.VirtualSensorConfig;
 import android.companion.virtual.sensor.VirtualSensorEvent;
+import android.content.ComponentName;
 import android.content.IntentFilter;
 import android.graphics.Point;
 import android.graphics.PointF;
@@ -86,6 +87,18 @@
     void setDevicePolicy(int policyType, int devicePolicy);
 
     /**
+     * Adds an exemption to the default activity launch policy.
+     */
+    @EnforcePermission("CREATE_VIRTUAL_DEVICE")
+    void addActivityPolicyExemption(in ComponentName exemption);
+
+    /**
+     * Removes an exemption to the default activity launch policy.
+     */
+    @EnforcePermission("CREATE_VIRTUAL_DEVICE")
+    void removeActivityPolicyExemption(in ComponentName exemption);
+
+    /**
      * Notifies that an audio session being started.
      */
     @EnforcePermission("CREATE_VIRTUAL_DEVICE")
diff --git a/core/java/android/companion/virtual/VirtualDeviceInternal.java b/core/java/android/companion/virtual/VirtualDeviceInternal.java
index 2e5c0f7..7bf2e91 100644
--- a/core/java/android/companion/virtual/VirtualDeviceInternal.java
+++ b/core/java/android/companion/virtual/VirtualDeviceInternal.java
@@ -247,6 +247,22 @@
         }
     }
 
+    void addActivityPolicyExemption(@NonNull ComponentName componentName) {
+        try {
+            mVirtualDevice.addActivityPolicyExemption(componentName);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    void removeActivityPolicyExemption(@NonNull ComponentName componentName) {
+        try {
+            mVirtualDevice.removeActivityPolicyExemption(componentName);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
     @NonNull
     VirtualDpad createVirtualDpad(@NonNull VirtualDpadConfig config) {
         try {
diff --git a/core/java/android/companion/virtual/VirtualDeviceManager.java b/core/java/android/companion/virtual/VirtualDeviceManager.java
index 7b81031..d338d17 100644
--- a/core/java/android/companion/virtual/VirtualDeviceManager.java
+++ b/core/java/android/companion/virtual/VirtualDeviceManager.java
@@ -62,7 +62,6 @@
 import android.view.Surface;
 
 import com.android.internal.annotations.GuardedBy;
-import com.android.internal.util.AnnotationValidations;
 
 import java.lang.annotation.ElementType;
 import java.lang.annotation.Retention;
@@ -624,19 +623,62 @@
          * @param devicePolicy the value of the policy, i.e. how to interpret the device behavior.
          *
          * @see VirtualDeviceParams#POLICY_TYPE_RECENTS
+         * @see VirtualDeviceParams#POLICY_TYPE_ACTIVITY
          */
         @FlaggedApi(Flags.FLAG_DYNAMIC_POLICY)
         @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)
         public void setDevicePolicy(@VirtualDeviceParams.DynamicPolicyType int policyType,
                 @VirtualDeviceParams.DevicePolicy int devicePolicy) {
-            AnnotationValidations.validate(
-                    VirtualDeviceParams.DynamicPolicyType.class, null, policyType);
-            AnnotationValidations.validate(
-                    VirtualDeviceParams.DevicePolicy.class, null, devicePolicy);
             mVirtualDeviceInternal.setDevicePolicy(policyType, devicePolicy);
         }
 
         /**
+         * Specifies a component name to be exempt from the current activity launch policy.
+         *
+         * <p>If the current {@link VirtualDeviceParams#POLICY_TYPE_ACTIVIY} allows activity
+         * launches by default, (i.e. it is {@link VirtualDeviceParams#DEVICE_POLICY_DEFAULT},
+         * then the specified component will be blocked from launching.
+         * If the current {@link VirtualDeviceParams#POLICY_TYPE_ACTIVITY} blocks activity
+         * launches by default, (i.e. it is {@link VirtualDeviceParams#DEVICE_POLICY_CUSTOM}, then
+         * the specified component will be allowed to launch.</p>
+         *
+         * <p>Note that changing the activity launch policy will not affect current set of exempt
+         * components and it needs to be updated separately.</p>
+         *
+         * @see #removeActivityPolicyExemption
+         * @see #setDevicePolicy
+         */
+        @FlaggedApi(Flags.FLAG_DYNAMIC_POLICY)
+        @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)
+        public void addActivityPolicyExemption(@NonNull ComponentName componentName) {
+            mVirtualDeviceInternal.addActivityPolicyExemption(
+                    Objects.requireNonNull(componentName));
+        }
+
+        /**
+         * Makes the specified component name to adhere to the default activity launch policy.
+         *
+         * <p>If the current {@link VirtualDeviceParams#POLICY_TYPE_ACTIVIY} allows activity
+         * launches by default, (i.e. it is {@link VirtualDeviceParams#DEVICE_POLICY_DEFAULT},
+         * then the specified component will be allowed to launch.
+         * If the current {@link VirtualDeviceParams#POLICY_TYPE_ACTIVITY} blocks activity
+         * launches by default, (i.e. it is {@link VirtualDeviceParams#DEVICE_POLICY_CUSTOM}, then
+         * the specified component will be blocked from launching.</p>
+         *
+         * <p>Note that changing the activity launch policy will not affect current set of exempt
+         * components and it needs to be updated separately.</p>
+         *
+         * @see #addActivityPolicyExemption
+         * @see #setDevicePolicy
+         */
+        @FlaggedApi(Flags.FLAG_DYNAMIC_POLICY)
+        @RequiresPermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)
+        public void removeActivityPolicyExemption(@NonNull ComponentName componentName) {
+            mVirtualDeviceInternal.removeActivityPolicyExemption(
+                    Objects.requireNonNull(componentName));
+        }
+
+        /**
          * Creates a virtual dpad.
          *
          * @param config the configurations of the virtual dpad.
diff --git a/core/java/android/companion/virtual/VirtualDeviceParams.java b/core/java/android/companion/virtual/VirtualDeviceParams.java
index 51df257..b4c740ec 100644
--- a/core/java/android/companion/virtual/VirtualDeviceParams.java
+++ b/core/java/android/companion/virtual/VirtualDeviceParams.java
@@ -22,12 +22,14 @@
 import static java.util.concurrent.TimeUnit.MICROSECONDS;
 
 import android.annotation.CallbackExecutor;
+import android.annotation.FlaggedApi;
 import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.RequiresPermission;
 import android.annotation.SuppressLint;
 import android.annotation.SystemApi;
+import android.companion.virtual.flags.Flags;
 import android.companion.virtual.sensor.IVirtualSensorCallback;
 import android.companion.virtual.sensor.VirtualSensor;
 import android.companion.virtual.sensor.VirtualSensorCallback;
@@ -144,7 +146,7 @@
      * @hide
      */
     @IntDef(prefix = "POLICY_TYPE_", value = {POLICY_TYPE_SENSORS, POLICY_TYPE_AUDIO,
-            POLICY_TYPE_RECENTS})
+            POLICY_TYPE_RECENTS, POLICY_TYPE_ACTIVITY})
     @Retention(RetentionPolicy.SOURCE)
     @Target({ElementType.TYPE_PARAMETER, ElementType.TYPE_USE})
     public @interface PolicyType {}
@@ -155,7 +157,7 @@
      * @see VirtualDeviceManager.VirtualDevice#setDevicePolicy
      * @hide
      */
-    @IntDef(prefix = "POLICY_TYPE_", value = {POLICY_TYPE_RECENTS})
+    @IntDef(prefix = "POLICY_TYPE_", value = {POLICY_TYPE_RECENTS, POLICY_TYPE_ACTIVITY})
     @Retention(RetentionPolicy.SOURCE)
     @Target({ElementType.TYPE_PARAMETER, ElementType.TYPE_USE})
     public @interface DynamicPolicyType {}
@@ -195,19 +197,35 @@
      *     <li>{@link #DEVICE_POLICY_DEFAULT}: Activities launched on VirtualDisplays owned by this
      *     device will appear in the host device recents.
      *     <li>{@link #DEVICE_POLICY_CUSTOM}: Activities launched on VirtualDisplays owned by this
-     *      *     device will not appear in recents.
+     *     device will not appear in recents.
      * </ul>
      */
     public static final int POLICY_TYPE_RECENTS = 2;
 
+    /**
+     * Tells the activity manager what the default launch behavior for activities on this device is.
+     *
+     * <ul>
+     *     <li>{@link #DEVICE_POLICY_DEFAULT}: Activities are allowed to be launched on displays
+     *     owned by this device, unless explicitly blocked by the device.
+     *     <li>{@link #DEVICE_POLICY_CUSTOM}: Activities are blocked from launching on displays
+     *     owned by this device, unless explicitly allowed by the device.
+     * </ul>
+     *
+     * @see VirtualDeviceManager.VirtualDevice#addActivityPolicyExemption
+     * @see VirtualDeviceManager.VirtualDevice#removeActivityPolicyExemption
+     */
+    @FlaggedApi(Flags.FLAG_DYNAMIC_POLICY)
+    public static final int POLICY_TYPE_ACTIVITY = 3;
+
     private final int mLockState;
     @NonNull private final ArraySet<UserHandle> mUsersWithMatchingAccounts;
     @NavigationPolicy
     private final int mDefaultNavigationPolicy;
-    @NonNull private final ArraySet<ComponentName> mCrossTaskNavigationExceptions;
+    @NonNull private final ArraySet<ComponentName> mCrossTaskNavigationExemptions;
     @ActivityPolicy
     private final int mDefaultActivityPolicy;
-    @NonNull private final ArraySet<ComponentName> mActivityPolicyExceptions;
+    @NonNull private final ArraySet<ComponentName> mActivityPolicyExemptions;
     @Nullable private final String mName;
     // Mapping of @PolicyType to @DevicePolicy
     @NonNull private final SparseIntArray mDevicePolicies;
@@ -220,9 +238,9 @@
             @LockState int lockState,
             @NonNull Set<UserHandle> usersWithMatchingAccounts,
             @NavigationPolicy int defaultNavigationPolicy,
-            @NonNull Set<ComponentName> crossTaskNavigationExceptions,
+            @NonNull Set<ComponentName> crossTaskNavigationExemptions,
             @ActivityPolicy int defaultActivityPolicy,
-            @NonNull Set<ComponentName> activityPolicyExceptions,
+            @NonNull Set<ComponentName> activityPolicyExemptions,
             @Nullable String name,
             @NonNull SparseIntArray devicePolicies,
             @NonNull List<VirtualSensorConfig> virtualSensorConfigs,
@@ -233,11 +251,11 @@
         mUsersWithMatchingAccounts =
                 new ArraySet<>(Objects.requireNonNull(usersWithMatchingAccounts));
         mDefaultNavigationPolicy = defaultNavigationPolicy;
-        mCrossTaskNavigationExceptions =
-                new ArraySet<>(Objects.requireNonNull(crossTaskNavigationExceptions));
+        mCrossTaskNavigationExemptions =
+                new ArraySet<>(Objects.requireNonNull(crossTaskNavigationExemptions));
         mDefaultActivityPolicy = defaultActivityPolicy;
-        mActivityPolicyExceptions =
-                new ArraySet<>(Objects.requireNonNull(activityPolicyExceptions));
+        mActivityPolicyExemptions =
+                new ArraySet<>(Objects.requireNonNull(activityPolicyExemptions));
         mName = name;
         mDevicePolicies = Objects.requireNonNull(devicePolicies);
         mVirtualSensorConfigs = Objects.requireNonNull(virtualSensorConfigs);
@@ -251,9 +269,9 @@
         mLockState = parcel.readInt();
         mUsersWithMatchingAccounts = (ArraySet<UserHandle>) parcel.readArraySet(null);
         mDefaultNavigationPolicy = parcel.readInt();
-        mCrossTaskNavigationExceptions = (ArraySet<ComponentName>) parcel.readArraySet(null);
+        mCrossTaskNavigationExemptions = (ArraySet<ComponentName>) parcel.readArraySet(null);
         mDefaultActivityPolicy = parcel.readInt();
-        mActivityPolicyExceptions = (ArraySet<ComponentName>) parcel.readArraySet(null);
+        mActivityPolicyExemptions = (ArraySet<ComponentName>) parcel.readArraySet(null);
         mName = parcel.readString8();
         mDevicePolicies = parcel.readSparseIntArray();
         mVirtualSensorConfigs = new ArrayList<>();
@@ -295,7 +313,7 @@
     public Set<ComponentName> getAllowedCrossTaskNavigations() {
         return mDefaultNavigationPolicy == NAVIGATION_POLICY_DEFAULT_ALLOWED
                 ? Collections.emptySet()
-                : Collections.unmodifiableSet(mCrossTaskNavigationExceptions);
+                : Collections.unmodifiableSet(mCrossTaskNavigationExemptions);
     }
 
     /**
@@ -310,7 +328,7 @@
     public Set<ComponentName> getBlockedCrossTaskNavigations() {
         return mDefaultNavigationPolicy == NAVIGATION_POLICY_DEFAULT_BLOCKED
                 ? Collections.emptySet()
-                : Collections.unmodifiableSet(mCrossTaskNavigationExceptions);
+                : Collections.unmodifiableSet(mCrossTaskNavigationExemptions);
     }
 
     /**
@@ -336,7 +354,7 @@
     public Set<ComponentName> getAllowedActivities() {
         return mDefaultActivityPolicy == ACTIVITY_POLICY_DEFAULT_ALLOWED
                 ? Collections.emptySet()
-                : Collections.unmodifiableSet(mActivityPolicyExceptions);
+                : Collections.unmodifiableSet(mActivityPolicyExemptions);
     }
 
     /**
@@ -349,7 +367,7 @@
     public Set<ComponentName> getBlockedActivities() {
         return mDefaultActivityPolicy == ACTIVITY_POLICY_DEFAULT_BLOCKED
                 ? Collections.emptySet()
-                : Collections.unmodifiableSet(mActivityPolicyExceptions);
+                : Collections.unmodifiableSet(mActivityPolicyExemptions);
     }
 
     /**
@@ -440,9 +458,9 @@
         dest.writeInt(mLockState);
         dest.writeArraySet(mUsersWithMatchingAccounts);
         dest.writeInt(mDefaultNavigationPolicy);
-        dest.writeArraySet(mCrossTaskNavigationExceptions);
+        dest.writeArraySet(mCrossTaskNavigationExemptions);
         dest.writeInt(mDefaultActivityPolicy);
-        dest.writeArraySet(mActivityPolicyExceptions);
+        dest.writeArraySet(mActivityPolicyExemptions);
         dest.writeString8(mName);
         dest.writeSparseIntArray(mDevicePolicies);
         dest.writeTypedList(mVirtualSensorConfigs);
@@ -476,9 +494,9 @@
         return mLockState == that.mLockState
                 && mUsersWithMatchingAccounts.equals(that.mUsersWithMatchingAccounts)
                 && Objects.equals(
-                        mCrossTaskNavigationExceptions, that.mCrossTaskNavigationExceptions)
+                        mCrossTaskNavigationExemptions, that.mCrossTaskNavigationExemptions)
                 && mDefaultNavigationPolicy == that.mDefaultNavigationPolicy
-                && Objects.equals(mActivityPolicyExceptions, that.mActivityPolicyExceptions)
+                && Objects.equals(mActivityPolicyExemptions, that.mActivityPolicyExemptions)
                 && mDefaultActivityPolicy == that.mDefaultActivityPolicy
                 && Objects.equals(mName, that.mName)
                 && mAudioPlaybackSessionId == that.mAudioPlaybackSessionId
@@ -488,8 +506,8 @@
     @Override
     public int hashCode() {
         int hashCode = Objects.hash(
-                mLockState, mUsersWithMatchingAccounts, mCrossTaskNavigationExceptions,
-                mDefaultNavigationPolicy, mActivityPolicyExceptions, mDefaultActivityPolicy, mName,
+                mLockState, mUsersWithMatchingAccounts, mCrossTaskNavigationExemptions,
+                mDefaultNavigationPolicy, mActivityPolicyExemptions, mDefaultActivityPolicy, mName,
                 mDevicePolicies, mAudioPlaybackSessionId, mAudioRecordingSessionId);
         for (int i = 0; i < mDevicePolicies.size(); i++) {
             hashCode = 31 * hashCode + mDevicePolicies.keyAt(i);
@@ -505,9 +523,9 @@
                 + " mLockState=" + mLockState
                 + " mUsersWithMatchingAccounts=" + mUsersWithMatchingAccounts
                 + " mDefaultNavigationPolicy=" + mDefaultNavigationPolicy
-                + " mCrossTaskNavigationExceptions=" + mCrossTaskNavigationExceptions
+                + " mCrossTaskNavigationExemptions=" + mCrossTaskNavigationExemptions
                 + " mDefaultActivityPolicy=" + mDefaultActivityPolicy
-                + " mActivityPolicyExceptions=" + mActivityPolicyExceptions
+                + " mActivityPolicyExemptions=" + mActivityPolicyExemptions
                 + " mName=" + mName
                 + " mDevicePolicies=" + mDevicePolicies
                 + " mAudioPlaybackSessionId=" + mAudioPlaybackSessionId
@@ -524,9 +542,9 @@
         pw.println(prefix + "mLockState=" + mLockState);
         pw.println(prefix + "mUsersWithMatchingAccounts=" + mUsersWithMatchingAccounts);
         pw.println(prefix + "mDefaultNavigationPolicy=" + mDefaultNavigationPolicy);
-        pw.println(prefix + "mCrossTaskNavigationExceptions=" + mCrossTaskNavigationExceptions);
+        pw.println(prefix + "mCrossTaskNavigationExemptions=" + mCrossTaskNavigationExemptions);
         pw.println(prefix + "mDefaultActivityPolicy=" + mDefaultActivityPolicy);
-        pw.println(prefix + "mActivityPolicyExceptions=" + mActivityPolicyExceptions);
+        pw.println(prefix + "mActivityPolicyExemptions=" + mActivityPolicyExemptions);
         pw.println(prefix + "mDevicePolicies=" + mDevicePolicies);
         pw.println(prefix + "mVirtualSensorConfigs=" + mVirtualSensorConfigs);
         pw.println(prefix + "mAudioPlaybackSessionId=" + mAudioPlaybackSessionId);
@@ -552,11 +570,11 @@
 
         private @LockState int mLockState = LOCK_STATE_DEFAULT;
         @NonNull private Set<UserHandle> mUsersWithMatchingAccounts = Collections.emptySet();
-        @NonNull private Set<ComponentName> mCrossTaskNavigationExceptions = Collections.emptySet();
+        @NonNull private Set<ComponentName> mCrossTaskNavigationExemptions = Collections.emptySet();
         @NavigationPolicy
         private int mDefaultNavigationPolicy = NAVIGATION_POLICY_DEFAULT_ALLOWED;
         private boolean mDefaultNavigationPolicyConfigured = false;
-        @NonNull private Set<ComponentName> mActivityPolicyExceptions = Collections.emptySet();
+        @NonNull private Set<ComponentName> mActivityPolicyExemptions = Collections.emptySet();
         @ActivityPolicy
         private int mDefaultActivityPolicy = ACTIVITY_POLICY_DEFAULT_ALLOWED;
         private boolean mDefaultActivityPolicyConfigured = false;
@@ -700,7 +718,7 @@
             }
             mDefaultNavigationPolicy = NAVIGATION_POLICY_DEFAULT_BLOCKED;
             mDefaultNavigationPolicyConfigured = true;
-            mCrossTaskNavigationExceptions = Objects.requireNonNull(allowedCrossTaskNavigations);
+            mCrossTaskNavigationExemptions = Objects.requireNonNull(allowedCrossTaskNavigations);
             return this;
         }
 
@@ -731,7 +749,7 @@
             }
             mDefaultNavigationPolicy = NAVIGATION_POLICY_DEFAULT_ALLOWED;
             mDefaultNavigationPolicyConfigured = true;
-            mCrossTaskNavigationExceptions = Objects.requireNonNull(blockedCrossTaskNavigations);
+            mCrossTaskNavigationExemptions = Objects.requireNonNull(blockedCrossTaskNavigations);
             return this;
         }
 
@@ -757,7 +775,7 @@
             }
             mDefaultActivityPolicy = ACTIVITY_POLICY_DEFAULT_BLOCKED;
             mDefaultActivityPolicyConfigured = true;
-            mActivityPolicyExceptions = Objects.requireNonNull(allowedActivities);
+            mActivityPolicyExemptions = Objects.requireNonNull(allowedActivities);
             return this;
         }
 
@@ -783,7 +801,7 @@
             }
             mDefaultActivityPolicy = ACTIVITY_POLICY_DEFAULT_ALLOWED;
             mDefaultActivityPolicyConfigured = true;
-            mActivityPolicyExceptions = Objects.requireNonNull(blockedActivities);
+            mActivityPolicyExemptions = Objects.requireNonNull(blockedActivities);
             return this;
         }
 
@@ -956,6 +974,35 @@
                         mVirtualSensorDirectChannelCallback);
             }
 
+            if (Flags.dynamicPolicy()) {
+                switch (mDevicePolicies.get(POLICY_TYPE_ACTIVITY, -1)) {
+                    case DEVICE_POLICY_DEFAULT:
+                        if (mDefaultActivityPolicyConfigured
+                                && mDefaultActivityPolicy == ACTIVITY_POLICY_DEFAULT_BLOCKED) {
+                            throw new IllegalArgumentException(
+                                    "DEVICE_POLICY_DEFAULT is explicitly configured for "
+                                            + "POLICY_TYPE_ACTIVITY, which is exclusive with "
+                                            + "setAllowedActivities.");
+                        }
+                        break;
+                    case DEVICE_POLICY_CUSTOM:
+                        if (mDefaultActivityPolicyConfigured
+                                && mDefaultActivityPolicy == ACTIVITY_POLICY_DEFAULT_ALLOWED) {
+                            throw new IllegalArgumentException(
+                                    "DEVICE_POLICY_CUSTOM is explicitly configured for "
+                                            + "POLICY_TYPE_ACTIVITY, which is exclusive with "
+                                            + "setBlockedActivities.");
+                        }
+                        break;
+                    default:
+                        if (mDefaultActivityPolicyConfigured
+                                && mDefaultActivityPolicy == ACTIVITY_POLICY_DEFAULT_BLOCKED) {
+                            mDevicePolicies.put(POLICY_TYPE_ACTIVITY, DEVICE_POLICY_CUSTOM);
+                        }
+                        break;
+                }
+            }
+
             if ((mAudioPlaybackSessionId != AUDIO_SESSION_ID_GENERATE
                     || mAudioRecordingSessionId != AUDIO_SESSION_ID_GENERATE)
                     && mDevicePolicies.get(POLICY_TYPE_AUDIO, DEVICE_POLICY_DEFAULT)
@@ -964,7 +1011,7 @@
                         + "required for configuration of device-specific audio session ids.");
             }
 
-            SparseArray<Set<String>> sensorNameByType = new SparseArray();
+            SparseArray<Set<String>> sensorNameByType = new SparseArray<>();
             for (int i = 0; i < mVirtualSensorConfigs.size(); ++i) {
                 VirtualSensorConfig config = mVirtualSensorConfigs.get(i);
                 Set<String> sensorNames = sensorNameByType.get(config.getType(), new ArraySet<>());
@@ -979,9 +1026,9 @@
                     mLockState,
                     mUsersWithMatchingAccounts,
                     mDefaultNavigationPolicy,
-                    mCrossTaskNavigationExceptions,
+                    mCrossTaskNavigationExemptions,
                     mDefaultActivityPolicy,
-                    mActivityPolicyExceptions,
+                    mActivityPolicyExemptions,
                     mName,
                     mDevicePolicies,
                     mVirtualSensorConfigs,
diff --git a/core/java/android/companion/virtual/flags.aconfig b/core/java/android/companion/virtual/flags.aconfig
index 3a3ab24..ee36f18 100644
--- a/core/java/android/companion/virtual/flags.aconfig
+++ b/core/java/android/companion/virtual/flags.aconfig
@@ -27,4 +27,3 @@
     description: "Enable Virtual Camera"
     bug: "270352264"
 }
-
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/content/res/CompatibilityInfo.java b/core/java/android/content/res/CompatibilityInfo.java
index 08ba5b6..f929c1f 100644
--- a/core/java/android/content/res/CompatibilityInfo.java
+++ b/core/java/android/content/res/CompatibilityInfo.java
@@ -100,7 +100,7 @@
      * The effective screen density we have selected for this application.
      */
     public final int applicationDensity;
-    
+
     /**
      * Application's scale.
      */
@@ -112,9 +112,27 @@
      */
     public final float applicationInvertedScale;
 
+    /**
+     * Application's density scale.
+     *
+     * <p>In most cases this is equal to {@link #applicationScale}, but in some cases e.g.
+     * Automotive the requirement is to just scale the density and keep the resolution the same.
+     * This is used for artificially making apps look zoomed in to compensate for the user distance
+     * from the screen.
+     */
+    public final float applicationDensityScale;
+
+    /**
+     * Application's density inverted scale.
+     */
+    public final float applicationDensityInvertedScale;
+
     /** The process level override inverted scale. See {@link #HAS_OVERRIDE_SCALING}. */
     private static float sOverrideInvertedScale = 1f;
 
+    /** The process level override inverted density scale. See {@link #HAS_OVERRIDE_SCALING}. */
+    private static float sOverrideDensityInvertScale = 1f;
+
     @UnsupportedAppUsage
     @Deprecated
     public CompatibilityInfo(ApplicationInfo appInfo, int screenLayout, int sw,
@@ -123,17 +141,24 @@
     }
 
     public CompatibilityInfo(ApplicationInfo appInfo, int screenLayout, int sw,
-            boolean forceCompat, float overrideScale) {
+            boolean forceCompat, float scaleFactor) {
+        this(appInfo, screenLayout, sw, forceCompat, scaleFactor, scaleFactor);
+    }
+
+    public CompatibilityInfo(ApplicationInfo appInfo, int screenLayout, int sw,
+            boolean forceCompat, float scaleFactor, float densityScaleFactor) {
         int compatFlags = 0;
 
         if (appInfo.targetSdkVersion < VERSION_CODES.O) {
             compatFlags |= NEEDS_COMPAT_RES;
         }
-        if (overrideScale != 1.0f) {
-            applicationScale = overrideScale;
-            applicationInvertedScale = 1.0f / overrideScale;
+        if (scaleFactor != 1f || densityScaleFactor != 1f) {
+            applicationScale = scaleFactor;
+            applicationInvertedScale = 1f / scaleFactor;
+            applicationDensityScale = densityScaleFactor;
+            applicationDensityInvertedScale = 1f / densityScaleFactor;
             applicationDensity = (int) ((DisplayMetrics.DENSITY_DEVICE_STABLE
-                    * applicationInvertedScale) + .5f);
+                    * applicationDensityInvertedScale) + .5f);
             mCompatibilityFlags = NEVER_NEEDS_COMPAT | HAS_OVERRIDE_SCALING;
             // Override scale has the highest priority. So ignore other compatibility attributes.
             return;
@@ -181,7 +206,8 @@
             applicationDensity = DisplayMetrics.DENSITY_DEVICE;
             applicationScale = 1.0f;
             applicationInvertedScale = 1.0f;
-
+            applicationDensityScale = 1.0f;
+            applicationDensityInvertedScale = 1.0f;
         } else {
             /**
              * Has the application said that its UI is expandable?  Based on the
@@ -271,11 +297,16 @@
                 applicationDensity = DisplayMetrics.DENSITY_DEVICE;
                 applicationScale = 1.0f;
                 applicationInvertedScale = 1.0f;
+                applicationDensityScale = 1.0f;
+                applicationDensityInvertedScale = 1.0f;
             } else {
                 applicationDensity = DisplayMetrics.DENSITY_DEFAULT;
                 applicationScale = DisplayMetrics.DENSITY_DEVICE
                         / (float) DisplayMetrics.DENSITY_DEFAULT;
                 applicationInvertedScale = 1.0f / applicationScale;
+                applicationDensityScale = DisplayMetrics.DENSITY_DEVICE
+                        / (float) DisplayMetrics.DENSITY_DEFAULT;
+                applicationDensityInvertedScale = 1f / applicationDensityScale;
                 compatFlags |= SCALING_REQUIRED;
             }
         }
@@ -289,6 +320,8 @@
         applicationDensity = dens;
         applicationScale = scale;
         applicationInvertedScale = invertedScale;
+        applicationDensityScale = (float) DisplayMetrics.DENSITY_DEVICE_STABLE / dens;
+        applicationDensityInvertedScale = 1f / applicationDensityScale;
     }
 
     @UnsupportedAppUsage
@@ -528,7 +561,8 @@
     /** Applies the compatibility adjustment to the display metrics. */
     public void applyDisplayMetricsIfNeeded(DisplayMetrics inoutDm, boolean applyToSize) {
         if (hasOverrideScale()) {
-            scaleDisplayMetrics(sOverrideInvertedScale, inoutDm, applyToSize);
+            scaleDisplayMetrics(sOverrideInvertedScale, sOverrideDensityInvertScale, inoutDm,
+                    applyToSize);
             return;
         }
         if (!equals(DEFAULT_COMPATIBILITY_INFO)) {
@@ -548,15 +582,17 @@
         }
 
         if (isScalingRequired()) {
-            scaleDisplayMetrics(applicationInvertedScale, inoutDm, true /* applyToSize */);
+            scaleDisplayMetrics(applicationInvertedScale, applicationDensityInvertedScale, inoutDm,
+                    true /* applyToSize */);
         }
     }
 
     /** Scales the density of the given display metrics. */
-    private static void scaleDisplayMetrics(float invertedRatio, DisplayMetrics inoutDm,
-            boolean applyToSize) {
-        inoutDm.density = inoutDm.noncompatDensity * invertedRatio;
-        inoutDm.densityDpi = (int) ((inoutDm.noncompatDensityDpi * invertedRatio) + .5f);
+    private static void scaleDisplayMetrics(float invertScale, float densityInvertScale,
+            DisplayMetrics inoutDm, boolean applyToSize) {
+        inoutDm.density = inoutDm.noncompatDensity * densityInvertScale;
+        inoutDm.densityDpi = (int) ((inoutDm.noncompatDensityDpi
+                * densityInvertScale) + .5f);
         // Note: since this is changing the scaledDensity, you might think we also need to change
         // inoutDm.fontScaleConverter to accurately calculate non-linear font scaling. But we're not
         // going to do that, for a couple of reasons (see b/265695259 for details):
@@ -570,12 +606,12 @@
         //    b. Sometime later by WindowManager in onResume or other windowing events. In this case
         //       the DisplayMetrics object is never used by the app/resources, so it's ok if
         //       fontScaleConverter is null because it's not being used to scale fonts anyway.
-        inoutDm.scaledDensity = inoutDm.noncompatScaledDensity * invertedRatio;
-        inoutDm.xdpi = inoutDm.noncompatXdpi * invertedRatio;
-        inoutDm.ydpi = inoutDm.noncompatYdpi * invertedRatio;
+        inoutDm.scaledDensity = inoutDm.noncompatScaledDensity * densityInvertScale;
+        inoutDm.xdpi = inoutDm.noncompatXdpi * densityInvertScale;
+        inoutDm.ydpi = inoutDm.noncompatYdpi * densityInvertScale;
         if (applyToSize) {
-            inoutDm.widthPixels = (int) (inoutDm.widthPixels * invertedRatio + 0.5f);
-            inoutDm.heightPixels = (int) (inoutDm.heightPixels * invertedRatio + 0.5f);
+            inoutDm.widthPixels = (int) (inoutDm.widthPixels * invertScale + 0.5f);
+            inoutDm.heightPixels = (int) (inoutDm.heightPixels * invertScale + 0.5f);
         }
     }
 
@@ -594,38 +630,55 @@
         }
         inoutConfig.densityDpi = displayDensity;
         if (isScalingRequired()) {
-            scaleConfiguration(applicationInvertedScale, inoutConfig);
+            scaleConfiguration(applicationInvertedScale, applicationDensityInvertedScale,
+                    inoutConfig);
         }
     }
 
     /** Scales the density and bounds of the given configuration. */
-    public static void scaleConfiguration(float invertedRatio, Configuration inoutConfig) {
-        inoutConfig.densityDpi = (int) ((inoutConfig.densityDpi * invertedRatio) + .5f);
-        inoutConfig.windowConfiguration.scale(invertedRatio);
+    public static void scaleConfiguration(float invertScale, Configuration inoutConfig) {
+        scaleConfiguration(invertScale, invertScale, inoutConfig);
+    }
+
+    /** Scales the density and bounds of the given configuration. */
+    public static void scaleConfiguration(float invertScale, float densityInvertScale,
+            Configuration inoutConfig) {
+        inoutConfig.densityDpi = (int) ((inoutConfig.densityDpi
+                * densityInvertScale) + .5f);
+        inoutConfig.windowConfiguration.scale(invertScale);
     }
 
     /** @see #sOverrideInvertedScale */
     public static void applyOverrideScaleIfNeeded(Configuration config) {
         if (!hasOverrideScale()) return;
-        scaleConfiguration(sOverrideInvertedScale, config);
+        scaleConfiguration(sOverrideInvertedScale, sOverrideDensityInvertScale, config);
     }
 
     /** @see #sOverrideInvertedScale */
     public static void applyOverrideScaleIfNeeded(MergedConfiguration mergedConfig) {
         if (!hasOverrideScale()) return;
-        scaleConfiguration(sOverrideInvertedScale, mergedConfig.getGlobalConfiguration());
-        scaleConfiguration(sOverrideInvertedScale, mergedConfig.getOverrideConfiguration());
-        scaleConfiguration(sOverrideInvertedScale, mergedConfig.getMergedConfiguration());
+        scaleConfiguration(sOverrideInvertedScale, sOverrideDensityInvertScale,
+                mergedConfig.getGlobalConfiguration());
+        scaleConfiguration(sOverrideInvertedScale, sOverrideDensityInvertScale,
+                mergedConfig.getOverrideConfiguration());
+        scaleConfiguration(sOverrideInvertedScale, sOverrideDensityInvertScale,
+                mergedConfig.getMergedConfiguration());
     }
 
     /** Returns {@code true} if this process is in a environment with override scale. */
     private static boolean hasOverrideScale() {
-        return sOverrideInvertedScale != 1f;
+        return sOverrideInvertedScale != 1f || sOverrideDensityInvertScale != 1f;
     }
 
     /** @see #sOverrideInvertedScale */
-    public static void setOverrideInvertedScale(float invertedRatio) {
-        sOverrideInvertedScale = invertedRatio;
+    public static void setOverrideInvertedScale(float invertScale) {
+        setOverrideInvertedScale(invertScale, invertScale);
+    }
+
+    /** @see #sOverrideInvertedScale */
+    public static void setOverrideInvertedScale(float invertScale, float densityInvertScale) {
+        sOverrideInvertedScale = invertScale;
+        sOverrideDensityInvertScale = densityInvertScale;
     }
 
     /** @see #sOverrideInvertedScale */
@@ -633,6 +686,11 @@
         return sOverrideInvertedScale;
     }
 
+    /** @see #sOverrideDensityInvertScale */
+    public static float getOverrideDensityInvertedScale() {
+        return sOverrideDensityInvertScale;
+    }
+
     /**
      * Compute the frame Rect for applications runs under compatibility mode.
      *
@@ -693,6 +751,8 @@
             if (applicationDensity != oc.applicationDensity) return false;
             if (applicationScale != oc.applicationScale) return false;
             if (applicationInvertedScale != oc.applicationInvertedScale) return false;
+            if (applicationDensityScale != oc.applicationDensityScale) return false;
+            if (applicationDensityInvertedScale != oc.applicationDensityInvertedScale) return false;
             return true;
         } catch (ClassCastException e) {
             return false;
@@ -713,6 +773,8 @@
         if (hasOverrideScaling()) {
             sb.append(" overrideInvScale=");
             sb.append(applicationInvertedScale);
+            sb.append(" overrideDensityInvScale=");
+            sb.append(applicationDensityInvertedScale);
         }
         if (!supportsScreen()) {
             sb.append(" resizing");
@@ -734,6 +796,8 @@
         result = 31 * result + applicationDensity;
         result = 31 * result + Float.floatToIntBits(applicationScale);
         result = 31 * result + Float.floatToIntBits(applicationInvertedScale);
+        result = 31 * result + Float.floatToIntBits(applicationDensityScale);
+        result = 31 * result + Float.floatToIntBits(applicationDensityInvertedScale);
         return result;
     }
 
@@ -748,6 +812,8 @@
         dest.writeInt(applicationDensity);
         dest.writeFloat(applicationScale);
         dest.writeFloat(applicationInvertedScale);
+        dest.writeFloat(applicationDensityScale);
+        dest.writeFloat(applicationDensityInvertedScale);
     }
 
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P, trackingBug = 115609023)
@@ -769,5 +835,61 @@
         applicationDensity = source.readInt();
         applicationScale = source.readFloat();
         applicationInvertedScale = source.readFloat();
+        applicationDensityScale = source.readFloat();
+        applicationDensityInvertedScale = source.readFloat();
+    }
+
+    /**
+     * A data class for holding scale factor for width, height, and density.
+     */
+    public static final class CompatScale {
+
+        public final float mScaleFactor;
+        public final float mDensityScaleFactor;
+
+        public CompatScale(float scaleFactor) {
+            this(scaleFactor, scaleFactor);
+        }
+
+        public CompatScale(float scaleFactor, float densityScaleFactor) {
+            mScaleFactor = scaleFactor;
+            mDensityScaleFactor = densityScaleFactor;
+        }
+
+        @Override
+        public boolean equals(@Nullable Object o) {
+            if (this == o) {
+                return true;
+            }
+            if (!(o instanceof CompatScale)) {
+                return false;
+            }
+            try {
+                CompatScale oc = (CompatScale) o;
+                if (mScaleFactor != oc.mScaleFactor) return false;
+                if (mDensityScaleFactor != oc.mDensityScaleFactor) return false;
+                return true;
+            } catch (ClassCastException e) {
+                return false;
+            }
+        }
+
+        @Override
+        public String toString() {
+            StringBuilder sb = new StringBuilder(128);
+            sb.append("mScaleFactor= ");
+            sb.append(mScaleFactor);
+            sb.append(" mDensityScaleFactor= ");
+            sb.append(mDensityScaleFactor);
+            return sb.toString();
+        }
+
+        @Override
+        public int hashCode() {
+            int result = 17;
+            result = 31 * result + Float.floatToIntBits(mScaleFactor);
+            result = 31 * result + Float.floatToIntBits(mDensityScaleFactor);
+            return result;
+        }
     }
 }
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/security/flags.aconfig b/core/java/android/security/flags.aconfig
index b27dac2..b6c2b83 100644
--- a/core/java/android/security/flags.aconfig
+++ b/core/java/android/security/flags.aconfig
@@ -6,3 +6,10 @@
     description: "Feature flag for fs-verity API"
     bug: "285185747"
 }
+
+flag {
+    name: "fix_unlocked_device_required_keys"
+    namespace: "hardware_backed_security"
+    description: "Fix bugs in behavior of UnlockedDeviceRequired keystore keys"
+    bug: "296464083"
+}
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/FeatureFlagUtils.java b/core/java/android/util/FeatureFlagUtils.java
index 12527e9..0ed275c 100644
--- a/core/java/android/util/FeatureFlagUtils.java
+++ b/core/java/android/util/FeatureFlagUtils.java
@@ -208,6 +208,14 @@
     public static final String SETTINGS_REMOTE_DEVICE_CREDENTIAL_VALIDATION =
             "settings_remote_device_credential_validation";
 
+    // TODO(b/295516544): Remove this when trunk stable feature flag is available.
+    /**
+     * Flag to enable / disable the Private Space Settings. It's disabled by default.
+     * @hide
+     */
+    public static final String SETTINGS_PRIVATE_SPACE_SETTINGS =
+            "settings_private_space_settings";
+
 
     private static final Map<String, String> DEFAULT_FLAGS;
 
@@ -256,6 +264,7 @@
         DEFAULT_FLAGS.put(SETTINGS_BIOMETRICS2_FINGERPRINT_SETTINGS, "false");
         // TODO: b/298454866 Replace with Trunk Stable Feature Flag
         DEFAULT_FLAGS.put(SETTINGS_REMOTEAUTH_ENROLLMENT_SETTINGS, "false");
+        DEFAULT_FLAGS.put(SETTINGS_PRIVATE_SPACE_SETTINGS, "false");
     }
 
     private static final Set<String> PERSISTENT_FLAGS;
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/WindowManager.java b/core/java/android/view/WindowManager.java
index b8385c6..e64274e 100644
--- a/core/java/android/view/WindowManager.java
+++ b/core/java/android/view/WindowManager.java
@@ -3139,15 +3139,6 @@
         public static final int PRIVATE_FLAG_DISABLE_WALLPAPER_TOUCH_EVENTS = 1 << 10;
 
         /**
-         * Flag to force the status bar window to be visible all the time. If the bar is hidden when
-         * this flag is set it will be shown again.
-         * This can only be set by {@link LayoutParams#TYPE_STATUS_BAR}.
-         *
-         * {@hide}
-         */
-        public static final int PRIVATE_FLAG_FORCE_SHOW_STATUS_BAR = 1 << 11;
-
-        /**
          * Flag to indicate that the window frame should be the requested frame adding the display
          * cutout frame. This will only be applied if a specific size smaller than the parent frame
          * is given, and the window is covering the display cutout. The extended frame will not be
@@ -3238,15 +3229,6 @@
         public static final int PRIVATE_FLAG_NOT_MAGNIFIABLE = 1 << 22;
 
         /**
-         * Flag to indicate that the status bar window is in a state such that it forces showing
-         * the navigation bar unless the navigation bar window is explicitly set to
-         * {@link View#GONE}.
-         * It only takes effects if this is set by {@link LayoutParams#TYPE_STATUS_BAR}.
-         * @hide
-         */
-        public static final int PRIVATE_FLAG_STATUS_FORCE_SHOW_NAVIGATION = 1 << 23;
-
-        /**
          * Flag to indicate that the window is color space agnostic, and the color can be
          * interpreted to any color space.
          * @hide
@@ -3334,7 +3316,6 @@
                 PRIVATE_FLAG_SYSTEM_ERROR,
                 PRIVATE_FLAG_OPTIMIZE_MEASURE,
                 PRIVATE_FLAG_DISABLE_WALLPAPER_TOUCH_EVENTS,
-                PRIVATE_FLAG_FORCE_SHOW_STATUS_BAR,
                 PRIVATE_FLAG_LAYOUT_SIZE_EXTENDED_BY_CUTOUT,
                 PRIVATE_FLAG_FORCE_DECOR_VIEW_VISIBILITY,
                 PRIVATE_FLAG_LAYOUT_CHILD_WINDOW_IN_PARENT_FRAME,
@@ -3345,7 +3326,6 @@
                 PRIVATE_FLAG_IS_ROUNDED_CORNERS_OVERLAY,
                 PRIVATE_FLAG_EXCLUDE_FROM_SCREEN_MAGNIFICATION,
                 PRIVATE_FLAG_NOT_MAGNIFIABLE,
-                PRIVATE_FLAG_STATUS_FORCE_SHOW_NAVIGATION,
                 PRIVATE_FLAG_COLOR_SPACE_AGNOSTIC,
                 PRIVATE_FLAG_USE_BLAST,
                 PRIVATE_FLAG_APPEARANCE_CONTROLLED,
@@ -3401,10 +3381,6 @@
                         equals = PRIVATE_FLAG_DISABLE_WALLPAPER_TOUCH_EVENTS,
                         name = "DISABLE_WALLPAPER_TOUCH_EVENTS"),
                 @ViewDebug.FlagToString(
-                        mask = PRIVATE_FLAG_FORCE_SHOW_STATUS_BAR,
-                        equals = PRIVATE_FLAG_FORCE_SHOW_STATUS_BAR,
-                        name = "FORCE_STATUS_BAR_VISIBLE"),
-                @ViewDebug.FlagToString(
                         mask = PRIVATE_FLAG_LAYOUT_SIZE_EXTENDED_BY_CUTOUT,
                         equals = PRIVATE_FLAG_LAYOUT_SIZE_EXTENDED_BY_CUTOUT,
                         name = "LAYOUT_SIZE_EXTENDED_BY_CUTOUT"),
@@ -3445,10 +3421,6 @@
                         equals = PRIVATE_FLAG_NOT_MAGNIFIABLE,
                         name = "NOT_MAGNIFIABLE"),
                 @ViewDebug.FlagToString(
-                        mask = PRIVATE_FLAG_STATUS_FORCE_SHOW_NAVIGATION,
-                        equals = PRIVATE_FLAG_STATUS_FORCE_SHOW_NAVIGATION,
-                        name = "STATUS_FORCE_SHOW_NAVIGATION"),
-                @ViewDebug.FlagToString(
                         mask = PRIVATE_FLAG_COLOR_SPACE_AGNOSTIC,
                         equals = PRIVATE_FLAG_COLOR_SPACE_AGNOSTIC,
                         name = "COLOR_SPACE_AGNOSTIC"),
@@ -4412,6 +4384,16 @@
         public InsetsFrameProvider[] providedInsets;
 
         /**
+         * Specifies which {@link InsetsType}s should be forcibly shown. The types shown by this
+         * method won't affect the app's layout. This field only takes effects if the caller has
+         * {@link android.Manifest.permission#STATUS_BAR_SERVICE} or the caller has the same uid as
+         * the recents component.
+         *
+         * @hide
+         */
+        public @InsetsType int forciblyShownTypes;
+
+        /**
          * {@link LayoutParams} to be applied to the window when layout with a assigned rotation.
          * This will make layout during rotation change smoothly.
          *
@@ -4869,6 +4851,7 @@
             out.writeInt(mBlurBehindRadius);
             out.writeBoolean(mWallpaperTouchEventsEnabled);
             out.writeTypedArray(providedInsets, 0 /* parcelableFlags */);
+            out.writeInt(forciblyShownTypes);
             checkNonRecursiveParams();
             out.writeTypedArray(paramsForRotation, 0 /* parcelableFlags */);
             out.writeInt(mDisplayFlags);
@@ -4940,6 +4923,7 @@
             mBlurBehindRadius = in.readInt();
             mWallpaperTouchEventsEnabled = in.readBoolean();
             providedInsets = in.createTypedArray(InsetsFrameProvider.CREATOR);
+            forciblyShownTypes = in.readInt();
             paramsForRotation = in.createTypedArray(LayoutParams.CREATOR);
             mDisplayFlags = in.readInt();
         }
@@ -5245,6 +5229,11 @@
                 changes |= LAYOUT_CHANGED;
             }
 
+            if (forciblyShownTypes != o.forciblyShownTypes) {
+                forciblyShownTypes = o.forciblyShownTypes;
+                changes |= PRIVATE_FLAGS_CHANGED;
+            }
+
             if (paramsForRotation != o.paramsForRotation) {
                 if ((changes & LAYOUT_CHANGED) == 0) {
                     if (paramsForRotation != null && o.paramsForRotation != null
@@ -5482,6 +5471,11 @@
                     sb.append(prefix).append("    ").append(providedInsets[i]);
                 }
             }
+            if (forciblyShownTypes != 0) {
+                sb.append(System.lineSeparator());
+                sb.append(prefix).append("  forciblyShownTypes=").append(
+                        WindowInsets.Type.toString(forciblyShownTypes));
+            }
             if (paramsForRotation != null && paramsForRotation.length != 0) {
                 sb.append(System.lineSeparator());
                 sb.append(prefix).append("  paramsForRotation:");
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/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/util/FileRotator.java b/core/java/com/android/internal/util/FileRotator.java
index 5bc48c5..c9d9926 100644
--- a/core/java/com/android/internal/util/FileRotator.java
+++ b/core/java/com/android/internal/util/FileRotator.java
@@ -19,6 +19,9 @@
 import android.annotation.NonNull;
 import android.os.FileUtils;
 import android.util.Log;
+import android.util.Pair;
+
+import libcore.io.IoUtils;
 
 import java.io.BufferedInputStream;
 import java.io.BufferedOutputStream;
@@ -28,12 +31,12 @@
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.OutputStream;
+import java.util.Comparator;
 import java.util.Objects;
+import java.util.TreeSet;
 import java.util.zip.ZipEntry;
 import java.util.zip.ZipOutputStream;
 
-import libcore.io.IoUtils;
-
 /**
  * Utility that rotates files over time, similar to {@code logrotate}. There is
  * a single "active" file, which is periodically rotated into historical files,
@@ -302,17 +305,24 @@
     public void readMatching(Reader reader, long matchStartMillis, long matchEndMillis)
             throws IOException {
         final FileInfo info = new FileInfo(mPrefix);
+        final TreeSet<Pair<Long, String>> readSet = new TreeSet<>(
+                Comparator.comparingLong(o -> o.first));
         for (String name : mBasePath.list()) {
             if (!info.parse(name)) continue;
 
-            // read file when it overlaps
+            // Add file to set when it overlaps.
             if (info.startMillis <= matchEndMillis && matchStartMillis <= info.endMillis) {
-                if (LOGD) Log.d(TAG, "reading matching " + name);
-
-                final File file = new File(mBasePath, name);
-                readFile(file, reader);
+                readSet.add(new Pair(info.startMillis, name));
             }
         }
+
+        // Read files in ascending order of start timestamp.
+        for (Pair<Long, String> pair : readSet) {
+            final String name = pair.second;
+            if (LOGD) Log.d(TAG, "reading matching " + name);
+            final File file = new File(mBasePath, name);
+            readFile(file, reader);
+        }
     }
 
     /**
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/config.xml b/core/res/res/values/config.xml
index 7d2690e..e7764d8 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -5717,13 +5717,13 @@
 
     <!-- Blur radius for the Option 3 in R.integer.config_letterboxBackgroundType. Values < 0 are
         ignored and 0 is used. -->
-    <dimen name="config_letterboxBackgroundWallpaperBlurRadius">24dp</dimen>
+    <dimen name="config_letterboxBackgroundWallpaperBlurRadius">38dp</dimen>
 
     <!-- Alpha of a black translucent scrim showed over wallpaper letterbox background when
         the Option 3 is selected for R.integer.config_letterboxBackgroundType.
         Values < 0 or >= 1 are ignored and 0.0 (transparent) is used instead. -->
     <item name="config_letterboxBackgroundWallaperDarkScrimAlpha" format="float" type="dimen">
-        0.75
+        0.54
     </item>
 
     <!-- Corners appearance of the letterbox background.
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/utiltests/src/com/android/internal/util/FileRotatorTest.java b/core/tests/utiltests/src/com/android/internal/util/FileRotatorTest.java
index 95272132..73e47e16 100644
--- a/core/tests/utiltests/src/com/android/internal/util/FileRotatorTest.java
+++ b/core/tests/utiltests/src/com/android/internal/util/FileRotatorTest.java
@@ -366,6 +366,16 @@
         assertReadAll(rotate, "bar");
     }
 
+    public void testReadSorted() throws Exception {
+        write("rotator.1024-2048", "2");
+        write("rotator.2048-4096", "3");
+        write("rotator.512-1024", "1");
+
+        final FileRotator rotate = new FileRotator(
+                mBasePath, PREFIX, SECOND_IN_MILLIS, SECOND_IN_MILLIS);
+        assertReadAll(rotate, "1", "2", "3");
+    }
+
     public void testFileSystemInaccessible() throws Exception {
         File inaccessibleDir = null;
         String dirPath = getContext().getFilesDir() + File.separator + "inaccessible";
@@ -422,16 +432,7 @@
         }
 
         public void assertRead(String... expected) {
-            assertEquals(expected.length, mActual.size());
-
-            final ArrayList<String> actualCopy = new ArrayList<String>(mActual);
-            for (String value : expected) {
-                if (!actualCopy.remove(value)) {
-                    final String expectedString = Arrays.toString(expected);
-                    final String actualString = Arrays.toString(mActual.toArray());
-                    fail("expected: " + expectedString + " but was: " + actualString);
-                }
-            }
+            assertEquals(Arrays.asList(expected), mActual);
         }
     }
 }
diff --git a/data/etc/services.core.protolog.json b/data/etc/services.core.protolog.json
index 6057852..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",
@@ -3067,6 +3079,12 @@
       "group": "WM_DEBUG_REMOTE_ANIMATIONS",
       "at": "com\/android\/server\/wm\/RemoteAnimationController.java"
     },
+    "643263584": {
+      "message": "Content Recording: Apply transformations of shift %d x %d, scale %f, crop %d x %d for display %d",
+      "level": "VERBOSE",
+      "group": "WM_DEBUG_CONTENT_RECORDING",
+      "at": "com\/android\/server\/wm\/ContentRecorder.java"
+    },
     "644675193": {
       "message": "Real start recents",
       "level": "DEBUG",
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/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/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/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/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/DesktopTasksController.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/DesktopTasksController.kt
index b918c83..40c519e 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) }
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/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/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/pipeline/skia/SkiaPipeline.cpp b/libs/hwui/pipeline/skia/SkiaPipeline.cpp
index 6679f8f..e0f1f6e 100644
--- a/libs/hwui/pipeline/skia/SkiaPipeline.cpp
+++ b/libs/hwui/pipeline/skia/SkiaPipeline.cpp
@@ -18,6 +18,7 @@
 
 #include <include/android/SkSurfaceAndroid.h>
 #include <include/gpu/ganesh/SkSurfaceGanesh.h>
+#include <include/encode/SkPngEncoder.h>
 #include <SkCanvas.h>
 #include <SkColor.h>
 #include <SkColorSpace.h>
@@ -440,6 +441,13 @@
                 procs.fTypefaceProc = [](SkTypeface* tf, void* ctx){
                     return tf->serialize(SkTypeface::SerializeBehavior::kDoIncludeData);
                 };
+                procs.fImageProc = [](SkImage* img, void* ctx) -> sk_sp<SkData> {
+                    GrDirectContext* dCtx = static_cast<GrDirectContext*>(ctx);
+                    return SkPngEncoder::Encode(dCtx,
+                                                img,
+                                                SkPngEncoder::Options{});
+                };
+                procs.fImageCtx = mRenderThread.getGrContext();
                 auto data = picture->serialize(&procs);
                 savePictureAsync(data, mCapturedFile);
                 mCaptureSequence = 0;
diff --git a/media/java/android/media/MediaRouter2.java b/media/java/android/media/MediaRouter2.java
index d6921c8..8c63580 100644
--- a/media/java/android/media/MediaRouter2.java
+++ b/media/java/android/media/MediaRouter2.java
@@ -17,9 +17,11 @@
 package android.media;
 
 import static com.android.internal.util.function.pooled.PooledLambda.obtainMessage;
+import static com.android.media.flags.Flags.FLAG_ENABLE_RLP_CALLBACKS_IN_MEDIA_ROUTER2;
 
 import android.Manifest;
 import android.annotation.CallbackExecutor;
+import android.annotation.FlaggedApi;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.RequiresPermission;
@@ -370,14 +372,14 @@
     }
 
     /**
-     * Registers callback to be invoked when the {@link RouteListingPreference} of the target
-     * router changes.
+     * Registers the given callback to be invoked when the {@link RouteListingPreference} of the
+     * target router changes.
      *
-     * <p>Calls using a previously registered callback will overwrite the callback record.
+     * <p>Calls using a previously registered callback will overwrite the previous executor.
      *
      * @see #setRouteListingPreference(RouteListingPreference)
-     * @hide
      */
+    @FlaggedApi(FLAG_ENABLE_RLP_CALLBACKS_IN_MEDIA_ROUTER2)
     public void registerRouteListingPreferenceCallback(
             @NonNull @CallbackExecutor Executor executor,
             @NonNull RouteListingPreferenceCallback routeListingPreferenceCallback) {
@@ -393,9 +395,8 @@
 
     /**
      * Unregisters the given callback to not receive {@link RouteListingPreference} change events.
-     *
-     * @hide
      */
+    @FlaggedApi(FLAG_ENABLE_RLP_CALLBACKS_IN_MEDIA_ROUTER2)
     public void unregisterRouteListingPreferenceCallback(
             @NonNull RouteListingPreferenceCallback callback) {
         Objects.requireNonNull(callback, "callback must not be null");
@@ -462,9 +463,12 @@
     /**
      * Returns the current {@link RouteListingPreference} of the target router.
      *
+     * <p>If this instance was created using {@link #getInstance(Context, String)}, then it returns
+     * the last {@link RouteListingPreference} set by the process this router was created for.
+     *
      * @see #setRouteListingPreference(RouteListingPreference)
-     * @hide
      */
+    @FlaggedApi(FLAG_ENABLE_RLP_CALLBACKS_IN_MEDIA_ROUTER2)
     @Nullable
     public RouteListingPreference getRouteListingPreference() {
         synchronized (mLock) {
@@ -1201,9 +1205,19 @@
         public void onPreferredFeaturesChanged(@NonNull List<String> preferredFeatures) {}
     }
 
-    /** @hide */
+    /** Callback for receiving events related to {@link RouteListingPreference}. */
+    @FlaggedApi(FLAG_ENABLE_RLP_CALLBACKS_IN_MEDIA_ROUTER2)
     public abstract static class RouteListingPreferenceCallback {
-        /** @hide */
+
+        @FlaggedApi(FLAG_ENABLE_RLP_CALLBACKS_IN_MEDIA_ROUTER2)
+        public RouteListingPreferenceCallback() {}
+
+        /**
+         * Called when the {@link RouteListingPreference} changes.
+         *
+         * @see #getRouteListingPreference
+         */
+        @FlaggedApi(FLAG_ENABLE_RLP_CALLBACKS_IN_MEDIA_ROUTER2)
         public void onRouteListingPreferenceChanged(@Nullable RouteListingPreference preference) {}
     }
 
diff --git a/media/java/android/media/flags/media_better_together.aconfig b/media/java/android/media/flags/media_better_together.aconfig
new file mode 100644
index 0000000..17962ee
--- /dev/null
+++ b/media/java/android/media/flags/media_better_together.aconfig
@@ -0,0 +1,8 @@
+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"
+}
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/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/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/CredentialManager/wear/res/values/strings.xml b/packages/CredentialManager/wear/res/values/strings.xml
new file mode 100644
index 0000000..10ea918
--- /dev/null
+++ b/packages/CredentialManager/wear/res/values/strings.xml
@@ -0,0 +1,21 @@
+<?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">
+  <!-- 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/Spa/gallery/src/com/android/settingslib/spa/gallery/GallerySpaEnvironment.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/GallerySpaEnvironment.kt
index 4a252a9..471f3b9 100644
--- a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/GallerySpaEnvironment.kt
+++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/GallerySpaEnvironment.kt
@@ -22,6 +22,7 @@
 import com.android.settingslib.spa.framework.common.SpaEnvironment
 import com.android.settingslib.spa.framework.common.createSettingsPage
 import com.android.settingslib.spa.gallery.button.ActionButtonPageProvider
+import com.android.settingslib.spa.gallery.chart.ChartPageProvider
 import com.android.settingslib.spa.gallery.dialog.AlertDialogPageProvider
 import com.android.settingslib.spa.gallery.editor.EditorMainPageProvider
 import com.android.settingslib.spa.gallery.editor.SettingsExposedDropdownMenuBoxPageProvider
@@ -32,7 +33,6 @@
 import com.android.settingslib.spa.gallery.itemList.OperateListPageProvider
 import com.android.settingslib.spa.gallery.editor.SettingsOutlinedTextFieldPageProvider
 import com.android.settingslib.spa.gallery.page.ArgumentPageProvider
-import com.android.settingslib.spa.gallery.page.ChartPageProvider
 import com.android.settingslib.spa.gallery.page.FooterPageProvider
 import com.android.settingslib.spa.gallery.page.IllustrationPageProvider
 import com.android.settingslib.spa.gallery.page.LoadingBarPageProvider
diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/chart/BarChartEntry.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/chart/BarChartEntry.kt
new file mode 100644
index 0000000..bf7a8e1
--- /dev/null
+++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/chart/BarChartEntry.kt
@@ -0,0 +1,55 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.spa.gallery.chart
+
+import com.android.settingslib.spa.framework.common.SettingsEntryBuilder
+import com.android.settingslib.spa.framework.common.SettingsPage
+import com.android.settingslib.spa.widget.chart.BarChart
+import com.android.settingslib.spa.widget.chart.BarChartData
+import com.android.settingslib.spa.widget.chart.BarChartModel
+import com.android.settingslib.spa.widget.chart.ColorPalette
+import com.android.settingslib.spa.widget.preference.Preference
+import com.android.settingslib.spa.widget.preference.PreferenceModel
+import com.github.mikephil.charting.formatter.IAxisValueFormatter
+
+fun createBarChartEntry(owner: SettingsPage) = SettingsEntryBuilder.create("Bar Chart", owner)
+    .setUiLayoutFn {
+        Preference(object : PreferenceModel {
+            override val title = "Bar Chart"
+        })
+        BarChart(
+            barChartModel = object : BarChartModel {
+                override val chartDataList = listOf(
+                    BarChartData(x = 0f, y = listOf(12f, 2f)),
+                    BarChartData(x = 1f, y = listOf(5f, 1f)),
+                    BarChartData(x = 2f, y = listOf(21f, 2f)),
+                    BarChartData(x = 3f, y = listOf(5f, 1f)),
+                    BarChartData(x = 4f, y = listOf(10f, 0f)),
+                    BarChartData(x = 5f, y = listOf(9f, 1f)),
+                    BarChartData(x = 6f, y = listOf(1f, 1f)),
+                )
+                override val colors = listOf(ColorPalette.green, ColorPalette.yellow)
+                override val xValueFormatter = IAxisValueFormatter { value, _ ->
+                    "4/${value.toInt() + 1}"
+                }
+                override val yValueFormatter = IAxisValueFormatter { value, _ ->
+                    "${value.toInt()}m"
+                }
+                override val yAxisMaxValue = 30f
+            }
+        )
+    }.build()
diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/ChartPage.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/chart/ChartPageProvider.kt
similarity index 73%
rename from packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/ChartPage.kt
rename to packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/chart/ChartPageProvider.kt
index 69c4705..7a6ae2c 100644
--- a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/page/ChartPage.kt
+++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/chart/ChartPageProvider.kt
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2022 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.settingslib.spa.gallery.page
+package com.android.settingslib.spa.gallery.chart
 
 import android.os.Bundle
 import androidx.compose.runtime.Composable
@@ -25,9 +25,6 @@
 import com.android.settingslib.spa.framework.common.createSettingsPage
 import com.android.settingslib.spa.framework.compose.navigator
 import com.android.settingslib.spa.framework.theme.SettingsTheme
-import com.android.settingslib.spa.widget.chart.BarChart
-import com.android.settingslib.spa.widget.chart.BarChartData
-import com.android.settingslib.spa.widget.chart.BarChartModel
 import com.android.settingslib.spa.widget.chart.LineChart
 import com.android.settingslib.spa.widget.chart.LineChartData
 import com.android.settingslib.spa.widget.chart.LineChartModel
@@ -83,36 +80,7 @@
                     )
                 }.build()
         )
-        entryList.add(
-            SettingsEntryBuilder.create("Bar Chart", owner)
-                .setUiLayoutFn {
-                    Preference(object : PreferenceModel {
-                        override val title = "Bar Chart"
-                    })
-                    BarChart(
-                        barChartModel = object : BarChartModel {
-                            override val chartDataList = listOf(
-                                BarChartData(x = 0f, y = 12f),
-                                BarChartData(x = 1f, y = 5f),
-                                BarChartData(x = 2f, y = 21f),
-                                BarChartData(x = 3f, y = 5f),
-                                BarChartData(x = 4f, y = 10f),
-                                BarChartData(x = 5f, y = 9f),
-                                BarChartData(x = 6f, y = 1f),
-                            )
-                            override val xValueFormatter =
-                                IAxisValueFormatter { value, _ ->
-                                    "${WeekDay.values()[value.toInt()]}"
-                                }
-                            override val yValueFormatter =
-                                IAxisValueFormatter { value, _ ->
-                                    "${value.toInt()}m"
-                                }
-                            override val yAxisMaxValue = 30f
-                        }
-                    )
-                }.build()
-        )
+        entryList.add(createBarChartEntry(owner))
         entryList.add(
             SettingsEntryBuilder.create("Pie Chart", owner)
                 .setUiLayoutFn {
diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/home/HomePageProvider.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/home/HomePageProvider.kt
index bb311a5..6cac220 100644
--- a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/home/HomePageProvider.kt
+++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/home/HomePageProvider.kt
@@ -28,12 +28,12 @@
 import com.android.settingslib.spa.gallery.R
 import com.android.settingslib.spa.gallery.SettingsPageProviderEnum
 import com.android.settingslib.spa.gallery.button.ActionButtonPageProvider
+import com.android.settingslib.spa.gallery.chart.ChartPageProvider
 import com.android.settingslib.spa.gallery.dialog.AlertDialogPageProvider
 import com.android.settingslib.spa.gallery.editor.EditorMainPageProvider
 import com.android.settingslib.spa.gallery.itemList.OperateListPageProvider
 import com.android.settingslib.spa.gallery.page.ArgumentPageModel
 import com.android.settingslib.spa.gallery.page.ArgumentPageProvider
-import com.android.settingslib.spa.gallery.page.ChartPageProvider
 import com.android.settingslib.spa.gallery.page.FooterPageProvider
 import com.android.settingslib.spa.gallery.page.IllustrationPageProvider
 import com.android.settingslib.spa.gallery.page.LoadingBarPageProvider
diff --git a/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/chart/BarChartScreenshotTest.kt b/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/chart/BarChartScreenshotTest.kt
index 27d270c..e6decb1 100644
--- a/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/chart/BarChartScreenshotTest.kt
+++ b/packages/SettingsLib/Spa/screenshot/src/com/android/settingslib/spa/screenshot/widget/chart/BarChartScreenshotTest.kt
@@ -16,6 +16,7 @@
 
 package com.android.settingslib.spa.screenshot
 
+import androidx.compose.material3.MaterialTheme
 import com.android.settingslib.spa.widget.chart.BarChart
 import com.android.settingslib.spa.widget.chart.BarChartData
 import com.android.settingslib.spa.widget.chart.BarChartModel
@@ -45,17 +46,19 @@
     @Test
     fun test() {
         screenshotRule.screenshotTest("barChart") {
+            val color = MaterialTheme.colorScheme.surfaceVariant
             BarChart(
                 barChartModel = object : BarChartModel {
                     override val chartDataList = listOf(
-                        BarChartData(x = 0f, y = 12f),
-                        BarChartData(x = 1f, y = 5f),
-                        BarChartData(x = 2f, y = 21f),
-                        BarChartData(x = 3f, y = 5f),
-                        BarChartData(x = 4f, y = 10f),
-                        BarChartData(x = 5f, y = 9f),
-                        BarChartData(x = 6f, y = 1f),
+                        BarChartData(x = 0f, y = listOf(12f)),
+                        BarChartData(x = 1f, y = listOf(5f)),
+                        BarChartData(x = 2f, y = listOf(21f)),
+                        BarChartData(x = 3f, y = listOf(5f)),
+                        BarChartData(x = 4f, y = listOf(10f)),
+                        BarChartData(x = 5f, y = listOf(9f)),
+                        BarChartData(x = 6f, y = listOf(1f)),
                     )
+                    override val colors = listOf(color)
                     override val xValueFormatter =
                         IAxisValueFormatter { value, _ ->
                             "${WeekDay.values()[value.toInt()]}"
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/chart/BarChart.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/chart/BarChart.kt
index 0b0f07e..7ca15d9 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/chart/BarChart.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/chart/BarChart.kt
@@ -31,6 +31,7 @@
 import androidx.compose.runtime.Composable
 import androidx.compose.ui.Alignment
 import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.Color
 import androidx.compose.ui.graphics.toArgb
 import androidx.compose.ui.unit.dp
 import androidx.compose.ui.viewinterop.AndroidView
@@ -52,6 +53,11 @@
     val chartDataList: List<BarChartData>
 
     /**
+     * The color list for [BarChart].
+     */
+    val colors: List<Color>
+
+    /**
      * The label text formatter for x value.
      */
     val xValueFormatter: IAxisValueFormatter?
@@ -83,34 +89,14 @@
 }
 
 data class BarChartData(
-    var x: Float?,
-    var y: Float?,
+    var x: Float,
+    var y: List<Float>,
 )
 
 @Composable
 fun BarChart(barChartModel: BarChartModel) {
-    BarChart(
-        chartDataList = barChartModel.chartDataList,
-        xValueFormatter = barChartModel.xValueFormatter,
-        yValueFormatter = barChartModel.yValueFormatter,
-        yAxisMinValue = barChartModel.yAxisMinValue,
-        yAxisMaxValue = barChartModel.yAxisMaxValue,
-        yAxisLabelCount = barChartModel.yAxisLabelCount,
-    )
-}
-
-@Composable
-fun BarChart(
-    chartDataList: List<BarChartData>,
-    modifier: Modifier = Modifier,
-    xValueFormatter: IAxisValueFormatter? = null,
-    yValueFormatter: IAxisValueFormatter? = null,
-    yAxisMinValue: Float = 0f,
-    yAxisMaxValue: Float = 30f,
-    yAxisLabelCount: Int = 4,
-) {
     Column(
-        modifier = modifier
+        modifier = Modifier
             .fillMaxWidth()
             .wrapContentHeight(),
         horizontalAlignment = Alignment.CenterHorizontally,
@@ -126,50 +112,61 @@
             val colorScheme = MaterialTheme.colorScheme
             val labelTextColor = colorScheme.onSurfaceVariant.toArgb()
             val labelTextSize = MaterialTheme.typography.bodyMedium.fontSize.value
-            Crossfade(targetState = chartDataList) { barChartData ->
-                AndroidView(factory = { context ->
-                    BarChart(context).apply {
-                        // Fixed Settings.
-                        layoutParams = LinearLayout.LayoutParams(
-                            ViewGroup.LayoutParams.MATCH_PARENT,
-                            ViewGroup.LayoutParams.MATCH_PARENT,
-                        )
-                        this.description.isEnabled = false
-                        this.legend.isEnabled = false
-                        this.extraBottomOffset = 4f
-                        this.setScaleEnabled(false)
+            Crossfade(
+                targetState = barChartModel.chartDataList,
+                label = "chartDataList",
+            ) { barChartData ->
+                AndroidView(
+                    factory = { context ->
+                        BarChart(context).apply {
+                            layoutParams = LinearLayout.LayoutParams(
+                                ViewGroup.LayoutParams.MATCH_PARENT,
+                                ViewGroup.LayoutParams.MATCH_PARENT,
+                            )
+                            description.isEnabled = false
+                            legend.isEnabled = false
+                            extraBottomOffset = 4f
+                            setScaleEnabled(false)
 
-                        this.xAxis.position = XAxis.XAxisPosition.BOTTOM
-                        this.xAxis.setDrawGridLines(false)
-                        this.xAxis.setDrawAxisLine(false)
-                        this.xAxis.textColor = labelTextColor
-                        this.xAxis.textSize = labelTextSize
-                        this.xAxis.yOffset = 10f
+                            xAxis.apply {
+                                position = XAxis.XAxisPosition.BOTTOM
+                                setDrawAxisLine(false)
+                                setDrawGridLines(false)
+                                textColor = labelTextColor
+                                textSize = labelTextSize
+                                valueFormatter = barChartModel.xValueFormatter
+                                yOffset = 10f
+                            }
 
-                        this.axisLeft.isEnabled = false
-                        this.axisRight.setDrawAxisLine(false)
-                        this.axisRight.textSize = labelTextSize
-                        this.axisRight.textColor = labelTextColor
-                        this.axisRight.gridColor = colorScheme.divider.toArgb()
-                        this.axisRight.xOffset = 10f
+                            axisLeft.apply {
+                                axisMaximum = barChartModel.yAxisMaxValue
+                                axisMinimum = barChartModel.yAxisMinValue
+                                isEnabled = false
+                            }
 
-                        // Customizable Settings.
-                        this.xAxis.valueFormatter = xValueFormatter
-                        this.axisRight.valueFormatter = yValueFormatter
-
-                        this.axisLeft.axisMinimum = yAxisMinValue
-                        this.axisLeft.axisMaximum = yAxisMaxValue
-                        this.axisRight.axisMinimum = yAxisMinValue
-                        this.axisRight.axisMaximum = yAxisMaxValue
-
-                        this.axisRight.setLabelCount(yAxisLabelCount, true)
-                    }
-                },
+                            axisRight.apply {
+                                axisMaximum = barChartModel.yAxisMaxValue
+                                axisMinimum = barChartModel.yAxisMinValue
+                                gridColor = colorScheme.divider.toArgb()
+                                setDrawAxisLine(false)
+                                setLabelCount(barChartModel.yAxisLabelCount, true)
+                                textColor = labelTextColor
+                                textSize = labelTextSize
+                                valueFormatter = barChartModel.yValueFormatter
+                                xOffset = 10f
+                            }
+                        }
+                    },
                     modifier = Modifier
                         .wrapContentSize()
                         .padding(4.dp),
-                    update = {
-                        updateBarChartWithData(it, barChartData, colorScheme)
+                    update = { barChart ->
+                        updateBarChartWithData(
+                            chart = barChart,
+                            data = barChartData,
+                            colorList = barChartModel.colors,
+                            colorScheme = colorScheme,
+                        )
                     }
                 )
             }
@@ -177,26 +174,25 @@
     }
 }
 
-fun updateBarChartWithData(
+private fun updateBarChartWithData(
     chart: BarChart,
     data: List<BarChartData>,
+    colorList: List<Color>,
     colorScheme: ColorScheme
 ) {
-    val entries = ArrayList<BarEntry>()
-    for (i in data.indices) {
-        val item = data[i]
-        entries.add(BarEntry(item.x ?: 0.toFloat(), item.y ?: 0.toFloat()))
+    val entries = data.map { item ->
+        BarEntry(item.x, item.y.toFloatArray())
     }
 
-    val ds = BarDataSet(entries, "")
-    ds.colors = arrayListOf(colorScheme.surfaceVariant.toArgb())
-    ds.setDrawValues(false)
-    ds.isHighlightEnabled = true
-    ds.highLightColor = colorScheme.primary.toArgb()
-    ds.highLightAlpha = 255
+    val ds = BarDataSet(entries, "").apply {
+        colors = colorList.map(Color::toArgb)
+        setDrawValues(false)
+        isHighlightEnabled = true
+        highLightColor = colorScheme.primary.toArgb()
+        highLightAlpha = 255
+    }
     // TODO: Sets round corners for bars.
 
-    val d = BarData(ds)
-    chart.data = d
+    chart.data = BarData(ds)
     chart.invalidate()
 }
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/editor/SettingsExposedDropdownMenuBox.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/editor/SettingsExposedDropdownMenuBox.kt
index d7bbf08..ec43aab 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/editor/SettingsExposedDropdownMenuBox.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/editor/SettingsExposedDropdownMenuBox.kt
@@ -18,20 +18,22 @@
 
 import androidx.compose.foundation.layout.fillMaxWidth
 import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.width
 import androidx.compose.material3.DropdownMenuItem
 import androidx.compose.material3.ExperimentalMaterial3Api
+import androidx.compose.material3.ExposedDropdownMenuBox
 import androidx.compose.material3.ExposedDropdownMenuDefaults
 import androidx.compose.material3.OutlinedTextField
 import androidx.compose.material3.Text
 import androidx.compose.runtime.Composable
-import androidx.compose.ui.Modifier
-import com.android.settingslib.spa.framework.theme.SettingsDimension
-import androidx.compose.material3.ExposedDropdownMenuBox
 import androidx.compose.runtime.getValue
 import androidx.compose.runtime.mutableStateOf
 import androidx.compose.runtime.remember
 import androidx.compose.runtime.setValue
+import androidx.compose.ui.Modifier
 import androidx.compose.ui.tooling.preview.Preview
+import androidx.compose.ui.unit.dp
+import com.android.settingslib.spa.framework.theme.SettingsDimension
 import com.android.settingslib.spa.framework.theme.SettingsTheme
 
 @Composable
@@ -48,6 +50,7 @@
         expanded = expanded,
         onExpandedChange = { expanded = it },
         modifier = Modifier
+            .width(350.dp)
             .padding(SettingsDimension.itemPadding),
     ) {
         OutlinedTextField(
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/editor/SettingsExposedDropdownMenuCheckBox.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/editor/SettingsExposedDropdownMenuCheckBox.kt
index 6b3ae77..459a783 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/editor/SettingsExposedDropdownMenuCheckBox.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/editor/SettingsExposedDropdownMenuCheckBox.kt
@@ -59,16 +59,16 @@
     ExposedDropdownMenuBox(
         expanded = expanded,
         onExpandedChange = { expanded = it },
-        modifier = Modifier.width(350.dp).padding(
-            horizontal = SettingsDimension.itemPaddingEnd,
-            vertical = SettingsDimension.itemPaddingVertical
-        ).onSizeChanged {
-            dropDownWidth = it.width
-        },
+        modifier = Modifier
+            .width(350.dp)
+            .padding(SettingsDimension.itemPadding)
+            .onSizeChanged { dropDownWidth = it.width },
     ) {
         OutlinedTextField(
             // The `menuAnchor` modifier must be passed to the text field for correctness.
-            modifier = Modifier.menuAnchor().fillMaxWidth(),
+            modifier = Modifier
+                .menuAnchor()
+                .fillMaxWidth(),
             value = selectedOptionsState.joinToString(", "),
             onValueChange = onselectedOptionStateChange,
             label = { Text(text = label) },
@@ -83,12 +83,15 @@
         if (options.isNotEmpty()) {
             ExposedDropdownMenu(
                 expanded = expanded,
-                modifier = Modifier.fillMaxWidth()
+                modifier = Modifier
+                    .fillMaxWidth()
                     .width(with(LocalDensity.current) { dropDownWidth.toDp() }),
                 onDismissRequest = { expanded = false },
             ) {
                 options.forEach { option ->
-                    TextButton(modifier = Modifier.fillMaxHeight().fillMaxWidth(), onClick = {
+                    TextButton(modifier = Modifier
+                        .fillMaxHeight()
+                        .fillMaxWidth(), onClick = {
                         if (selectedOptionsState.contains(option)) {
                             selectedOptionsState.remove(
                                 option
@@ -100,7 +103,9 @@
                         }
                     }) {
                         Row(
-                            modifier = Modifier.fillMaxHeight().fillMaxWidth(),
+                            modifier = Modifier
+                                .fillMaxHeight()
+                                .fillMaxWidth(),
                             horizontalArrangement = Arrangement.Start,
                             verticalAlignment = Alignment.CenterVertically
                         ) {
diff --git a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/chart/ChartTest.kt b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/chart/ChartTest.kt
index 2230d6c..0fe755f 100644
--- a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/chart/ChartTest.kt
+++ b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/chart/ChartTest.kt
@@ -17,13 +17,17 @@
 package com.android.settingslib.spa.widget.chart
 
 import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.Color
 import androidx.compose.ui.semantics.SemanticsPropertyKey
 import androidx.compose.ui.semantics.SemanticsPropertyReceiver
 import androidx.compose.ui.semantics.semantics
 import androidx.compose.ui.test.SemanticsMatcher
 import androidx.compose.ui.test.assertIsDisplayed
+import androidx.compose.ui.test.captureToImage
 import androidx.compose.ui.test.junit4.createComposeRule
+import androidx.compose.ui.test.onRoot
 import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.android.settingslib.spa.testutils.assertContainsColor
 import org.junit.Rule
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -61,22 +65,21 @@
     @Test
     fun bar_chart_displayed() {
         composeTestRule.setContent {
-            BarChart(
-                chartDataList = listOf(
-                    BarChartData(x = 0f, y = 12f),
-                    BarChartData(x = 1f, y = 5f),
-                    BarChartData(x = 2f, y = 21f),
-                    BarChartData(x = 3f, y = 5f),
-                    BarChartData(x = 4f, y = 10f),
-                    BarChartData(x = 5f, y = 9f),
-                    BarChartData(x = 6f, y = 1f),
-                ),
-                yAxisMaxValue = 30f,
-                modifier = Modifier.semantics { chart = "bar" }
-            )
+            BarChart(object : BarChartModel {
+                override val chartDataList = listOf(
+                    BarChartData(x = 0f, y = listOf(12f)),
+                    BarChartData(x = 1f, y = listOf(5f)),
+                    BarChartData(x = 2f, y = listOf(21f)),
+                    BarChartData(x = 3f, y = listOf(5f)),
+                    BarChartData(x = 4f, y = listOf(10f)),
+                    BarChartData(x = 5f, y = listOf(9f)),
+                    BarChartData(x = 6f, y = listOf(1f)),
+                )
+                override val colors = listOf(Color.Blue)
+            })
         }
 
-        composeTestRule.onNode(hasChart("bar")).assertIsDisplayed()
+        composeTestRule.onRoot().captureToImage().assertContainsColor(Color.Blue)
     }
 
     @Test
diff --git a/packages/SettingsLib/Spa/testutils/src/com/android/settingslib/spa/testutils/ImageAssertions.kt b/packages/SettingsLib/Spa/testutils/src/com/android/settingslib/spa/testutils/ImageAssertions.kt
new file mode 100644
index 0000000..0190989
--- /dev/null
+++ b/packages/SettingsLib/Spa/testutils/src/com/android/settingslib/spa/testutils/ImageAssertions.kt
@@ -0,0 +1,42 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.spa.testutils
+
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.graphics.ImageBitmap
+import androidx.compose.ui.graphics.toPixelMap
+
+/**
+ * Asserts that the expected color is present in this bitmap.
+ *
+ * @throws AssertionError if the expected color is not present.
+ */
+fun ImageBitmap.assertContainsColor(expectedColor: Color) {
+    assert(containsColor(expectedColor)) {
+        "The given color $expectedColor was not found in the bitmap."
+    }
+}
+
+private fun ImageBitmap.containsColor(expectedColor: Color): Boolean {
+    val pixels = toPixelMap()
+    for (x in 0 until width) {
+        for (y in 0 until height) {
+            if (pixels[x, y] == expectedColor) return true
+        }
+    }
+    return false
+}
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/BluetoothUtils.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothUtils.java
index 755d971..fa8c1fb 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothUtils.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothUtils.java
@@ -4,9 +4,9 @@
 
 import android.annotation.SuppressLint;
 import android.bluetooth.BluetoothClass;
+import android.bluetooth.BluetoothCsipSetCoordinator;
 import android.bluetooth.BluetoothDevice;
 import android.bluetooth.BluetoothProfile;
-import android.bluetooth.BluetoothCsipSetCoordinator;
 import android.content.Context;
 import android.content.Intent;
 import android.content.res.Resources;
@@ -14,6 +14,7 @@
 import android.graphics.Canvas;
 import android.graphics.drawable.BitmapDrawable;
 import android.graphics.drawable.Drawable;
+import android.media.AudioManager;
 import android.net.Uri;
 import android.provider.DeviceConfig;
 import android.provider.MediaStore;
@@ -23,6 +24,7 @@
 
 import androidx.annotation.DrawableRes;
 import androidx.annotation.NonNull;
+import androidx.annotation.WorkerThread;
 import androidx.core.graphics.drawable.IconCompat;
 
 import com.android.settingslib.R;
@@ -470,6 +472,136 @@
         return extraTagValue(KEY_HEARABLE_CONTROL_SLICE, data);
     }
 
+    /**
+     * Check if the Bluetooth device is an AvailableMediaBluetoothDevice, which means:
+     * 1) currently connected
+     * 2) is Hearing Aid or LE Audio
+     *    OR
+     * 3) connected profile matches currentAudioProfile
+     *
+     * @param cachedDevice the CachedBluetoothDevice
+     * @param audioManager audio manager to get the current audio profile
+     * @return if the device is AvailableMediaBluetoothDevice
+     */
+    @WorkerThread
+    public static boolean isAvailableMediaBluetoothDevice(
+            CachedBluetoothDevice cachedDevice, AudioManager audioManager) {
+        int audioMode = audioManager.getMode();
+        int currentAudioProfile;
+
+        if (audioMode == AudioManager.MODE_RINGTONE
+                || audioMode == AudioManager.MODE_IN_CALL
+                || audioMode == AudioManager.MODE_IN_COMMUNICATION) {
+            // in phone call
+            currentAudioProfile = BluetoothProfile.HEADSET;
+        } else {
+            // without phone call
+            currentAudioProfile = BluetoothProfile.A2DP;
+        }
+
+        boolean isFilterMatched = false;
+        if (isDeviceConnected(cachedDevice)) {
+            // If device is Hearing Aid or LE Audio, it is compatible with HFP and A2DP.
+            // It would show in Available Devices group.
+            if (cachedDevice.isConnectedAshaHearingAidDevice()
+                    || cachedDevice.isConnectedLeAudioDevice()) {
+                Log.d(TAG, "isFilterMatched() device : "
+                        + cachedDevice.getName() + ", the profile is connected.");
+                return true;
+            }
+            // According to the current audio profile type,
+            // this page will show the bluetooth device that have corresponding profile.
+            // For example:
+            // If current audio profile is a2dp, show the bluetooth device that have a2dp profile.
+            // If current audio profile is headset,
+            // show the bluetooth device that have headset profile.
+            switch (currentAudioProfile) {
+                case BluetoothProfile.A2DP:
+                    isFilterMatched = cachedDevice.isConnectedA2dpDevice();
+                    break;
+                case BluetoothProfile.HEADSET:
+                    isFilterMatched = cachedDevice.isConnectedHfpDevice();
+                    break;
+            }
+        }
+        return isFilterMatched;
+    }
+
+    /**
+     * Check if the Bluetooth device is a ConnectedBluetoothDevice, which means:
+     * 1) currently connected
+     * 2) is not Hearing Aid or LE Audio
+     *    AND
+     * 3) connected profile does not match currentAudioProfile
+     *
+     * @param cachedDevice the CachedBluetoothDevice
+     * @param audioManager audio manager to get the current audio profile
+     * @return if the device is AvailableMediaBluetoothDevice
+     */
+    @WorkerThread
+    public static boolean isConnectedBluetoothDevice(
+            CachedBluetoothDevice cachedDevice, AudioManager audioManager) {
+        int audioMode = audioManager.getMode();
+        int currentAudioProfile;
+
+        if (audioMode == AudioManager.MODE_RINGTONE
+                || audioMode == AudioManager.MODE_IN_CALL
+                || audioMode == AudioManager.MODE_IN_COMMUNICATION) {
+            // in phone call
+            currentAudioProfile = BluetoothProfile.HEADSET;
+        } else {
+            // without phone call
+            currentAudioProfile = BluetoothProfile.A2DP;
+        }
+
+        boolean isFilterMatched = false;
+        if (isDeviceConnected(cachedDevice)) {
+            // If device is Hearing Aid or LE Audio, it is compatible with HFP and A2DP.
+            // It would not show in Connected Devices group.
+            if (cachedDevice.isConnectedAshaHearingAidDevice()
+                    || cachedDevice.isConnectedLeAudioDevice()) {
+                return false;
+            }
+            // According to the current audio profile type,
+            // this page will show the bluetooth device that doesn't have corresponding profile.
+            // For example:
+            // If current audio profile is a2dp,
+            // show the bluetooth device that doesn't have a2dp profile.
+            // If current audio profile is headset,
+            // show the bluetooth device that doesn't have headset profile.
+            switch (currentAudioProfile) {
+                case BluetoothProfile.A2DP:
+                    isFilterMatched = !cachedDevice.isConnectedA2dpDevice();
+                    break;
+                case BluetoothProfile.HEADSET:
+                    isFilterMatched = !cachedDevice.isConnectedHfpDevice();
+                    break;
+            }
+        }
+        return isFilterMatched;
+    }
+
+    /**
+     * Check if the Bluetooth device is an active media device
+     *
+     * @param cachedDevice the CachedBluetoothDevice
+     * @return if the Bluetooth device is an active media device
+     */
+    public static boolean isActiveMediaDevice(CachedBluetoothDevice cachedDevice) {
+        return cachedDevice.isActiveDevice(BluetoothProfile.A2DP)
+                || cachedDevice.isActiveDevice(BluetoothProfile.HEADSET)
+                || cachedDevice.isActiveDevice(BluetoothProfile.HEARING_AID)
+                || cachedDevice.isActiveDevice(BluetoothProfile.LE_AUDIO);
+    }
+
+    private static boolean isDeviceConnected(CachedBluetoothDevice cachedDevice) {
+        if (cachedDevice == null) {
+            return false;
+        }
+        final BluetoothDevice device = cachedDevice.getDevice();
+        return device.getBondState() == BluetoothDevice.BOND_BONDED && device.isConnected();
+    }
+
     @SuppressLint("NewApi") // Hidden API made public
     private static boolean doesClassMatch(BluetoothClass btClass, int classId) {
         return btClass.doesClassMatch(classId);
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/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/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/BluetoothUtilsTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/BluetoothUtilsTest.java
index 83972e8..7409eea 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/BluetoothUtilsTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/BluetoothUtilsTest.java
@@ -16,7 +16,6 @@
 package com.android.settingslib.bluetooth;
 
 import static com.google.common.truth.Truth.assertThat;
-
 import static org.mockito.Mockito.spy;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
@@ -25,6 +24,7 @@
 import android.bluetooth.BluetoothDevice;
 import android.content.Context;
 import android.graphics.drawable.Drawable;
+import android.media.AudioManager;
 import android.net.Uri;
 import android.util.Pair;
 
@@ -46,6 +46,8 @@
     private CachedBluetoothDevice mCachedBluetoothDevice;
     @Mock
     private BluetoothDevice mBluetoothDevice;
+    @Mock
+    private AudioManager mAudioManager;
 
     private Context mContext;
     private static final String STRING_METADATA = "string_metadata";
@@ -255,4 +257,110 @@
     public void isAdvancedUntetheredDevice_noMetadata_returnFalse() {
         assertThat(BluetoothUtils.isAdvancedUntetheredDevice(mBluetoothDevice)).isEqualTo(false);
     }
+
+    @Test
+    public void isAvailableMediaBluetoothDevice_isConnectedLeAudioDevice_returnTrue() {
+        when(mCachedBluetoothDevice.isConnectedLeAudioDevice()).thenReturn(true);
+        when(mCachedBluetoothDevice.getDevice()).thenReturn(mBluetoothDevice);
+        when(mBluetoothDevice.getBondState()).thenReturn(BluetoothDevice.BOND_BONDED);
+        when(mBluetoothDevice.isConnected()).thenReturn(true);
+
+        assertThat(BluetoothUtils.isAvailableMediaBluetoothDevice(mCachedBluetoothDevice,
+                mAudioManager)).isEqualTo(true);
+    }
+
+    @Test
+    public void isAvailableMediaBluetoothDevice_isHeadset_isConnectedA2dpDevice_returnFalse() {
+        when(mAudioManager.getMode()).thenReturn(AudioManager.MODE_RINGTONE);
+        when(mCachedBluetoothDevice.isConnectedA2dpDevice()).thenReturn(true);
+        when(mCachedBluetoothDevice.getDevice()).thenReturn(mBluetoothDevice);
+        when(mBluetoothDevice.getBondState()).thenReturn(BluetoothDevice.BOND_BONDED);
+        when(mBluetoothDevice.isConnected()).thenReturn(true);
+
+        assertThat(BluetoothUtils.isAvailableMediaBluetoothDevice(mCachedBluetoothDevice,
+                mAudioManager)).isEqualTo(false);
+    }
+
+    @Test
+    public void isAvailableMediaBluetoothDevice_isA2dp_isConnectedA2dpDevice_returnTrue() {
+        when(mAudioManager.getMode()).thenReturn(AudioManager.MODE_NORMAL);
+        when(mCachedBluetoothDevice.isConnectedA2dpDevice()).thenReturn(true);
+        when(mCachedBluetoothDevice.getDevice()).thenReturn(mBluetoothDevice);
+        when(mBluetoothDevice.getBondState()).thenReturn(BluetoothDevice.BOND_BONDED);
+        when(mBluetoothDevice.isConnected()).thenReturn(true);
+
+        assertThat(BluetoothUtils.isAvailableMediaBluetoothDevice(mCachedBluetoothDevice,
+                mAudioManager)).isEqualTo(true);
+    }
+
+    @Test
+    public void isAvailableMediaBluetoothDevice_isHeadset_isConnectedHfpDevice_returnTrue() {
+        when(mAudioManager.getMode()).thenReturn(AudioManager.MODE_RINGTONE);
+        when(mCachedBluetoothDevice.isConnectedHfpDevice()).thenReturn(true);
+        when(mCachedBluetoothDevice.getDevice()).thenReturn(mBluetoothDevice);
+        when(mBluetoothDevice.getBondState()).thenReturn(BluetoothDevice.BOND_BONDED);
+        when(mBluetoothDevice.isConnected()).thenReturn(true);
+
+        assertThat(BluetoothUtils.isAvailableMediaBluetoothDevice(mCachedBluetoothDevice,
+                mAudioManager)).isEqualTo(true);
+    }
+
+    @Test
+    public void isConnectedBluetoothDevice_isConnectedLeAudioDevice_returnFalse() {
+        when(mCachedBluetoothDevice.isConnectedLeAudioDevice()).thenReturn(true);
+        when(mCachedBluetoothDevice.getDevice()).thenReturn(mBluetoothDevice);
+        when(mBluetoothDevice.getBondState()).thenReturn(BluetoothDevice.BOND_BONDED);
+        when(mBluetoothDevice.isConnected()).thenReturn(true);
+
+        assertThat(BluetoothUtils.isConnectedBluetoothDevice(mCachedBluetoothDevice,
+                mAudioManager)).isEqualTo(false);
+    }
+
+    @Test
+    public void isConnectedBluetoothDevice_isHeadset_isConnectedA2dpDevice_returnTrue() {
+        when(mAudioManager.getMode()).thenReturn(AudioManager.MODE_RINGTONE);
+        when(mCachedBluetoothDevice.isConnectedA2dpDevice()).thenReturn(true);
+        when(mCachedBluetoothDevice.getDevice()).thenReturn(mBluetoothDevice);
+        when(mBluetoothDevice.getBondState()).thenReturn(BluetoothDevice.BOND_BONDED);
+        when(mBluetoothDevice.isConnected()).thenReturn(true);
+
+        assertThat(BluetoothUtils.isConnectedBluetoothDevice(mCachedBluetoothDevice,
+                mAudioManager)).isEqualTo(true);
+    }
+
+    @Test
+    public void isConnectedBluetoothDevice_isA2dp_isConnectedA2dpDevice_returnFalse() {
+        when(mAudioManager.getMode()).thenReturn(AudioManager.MODE_NORMAL);
+        when(mCachedBluetoothDevice.isConnectedA2dpDevice()).thenReturn(true);
+        when(mCachedBluetoothDevice.getDevice()).thenReturn(mBluetoothDevice);
+        when(mBluetoothDevice.getBondState()).thenReturn(BluetoothDevice.BOND_BONDED);
+        when(mBluetoothDevice.isConnected()).thenReturn(true);
+
+        assertThat(BluetoothUtils.isConnectedBluetoothDevice(mCachedBluetoothDevice,
+                mAudioManager)).isEqualTo(false);
+    }
+
+    @Test
+    public void isConnectedBluetoothDevice_isHeadset_isConnectedHfpDevice_returnFalse() {
+        when(mAudioManager.getMode()).thenReturn(AudioManager.MODE_RINGTONE);
+        when(mCachedBluetoothDevice.isConnectedHfpDevice()).thenReturn(true);
+        when(mCachedBluetoothDevice.getDevice()).thenReturn(mBluetoothDevice);
+        when(mBluetoothDevice.getBondState()).thenReturn(BluetoothDevice.BOND_BONDED);
+        when(mBluetoothDevice.isConnected()).thenReturn(true);
+
+        assertThat(BluetoothUtils.isConnectedBluetoothDevice(mCachedBluetoothDevice,
+                mAudioManager)).isEqualTo(false);
+    }
+
+    @Test
+    public void isConnectedBluetoothDevice_isNotConnected_returnFalse() {
+        when(mAudioManager.getMode()).thenReturn(AudioManager.MODE_RINGTONE);
+        when(mCachedBluetoothDevice.isConnectedA2dpDevice()).thenReturn(true);
+        when(mCachedBluetoothDevice.getDevice()).thenReturn(mBluetoothDevice);
+        when(mBluetoothDevice.getBondState()).thenReturn(BluetoothDevice.BOND_BONDED);
+        when(mBluetoothDevice.isConnected()).thenReturn(false);
+
+        assertThat(BluetoothUtils.isConnectedBluetoothDevice(mCachedBluetoothDevice,
+                mAudioManager)).isEqualTo(false);
+    }
 }
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..000612b 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",
 }
@@ -354,6 +357,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/res/layout/media_projection_app_selector.xml b/packages/SystemUI/res/layout/media_projection_app_selector.xml
index e474938..b77f78d 100644
--- a/packages/SystemUI/res/layout/media_projection_app_selector.xml
+++ b/packages/SystemUI/res/layout/media_projection_app_selector.xml
@@ -20,10 +20,11 @@
     android:layout_width="match_parent"
     android:layout_height="match_parent"
     android:layout_gravity="center"
-    androidprv:maxCollapsedHeight="0dp"
+    androidprv:maxCollapsedHeight="10000dp"
     androidprv:maxCollapsedHeightSmall="56dp"
     androidprv:maxWidth="@*android:dimen/chooser_width"
     android:id="@*android:id/contentPanel">
+    <!-- maxCollapsedHeight above is huge, to make sure the layout is always expanded. -->
 
     <LinearLayout
         android:id="@*android:id/chooser_header"
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index 62c4424..b086ed8 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -1232,6 +1232,7 @@
     <dimen name="magnification_setting_image_button_open_in_full_padding_horizontal">28dp</dimen>
     <dimen name="magnification_setting_drag_corner_radius">28dp</dimen>
     <dimen name="magnification_setting_drag_size">56dp</dimen>
+    <fraction name="magnification_resize_window_size_amount">10%</fraction>
 
     <!-- Seekbar with icon buttons -->
     <dimen name="seekbar_icon_size">24dp</dimen>
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/res/values/ids.xml b/packages/SystemUI/res/values/ids.xml
index e48901e..bd251bd 100644
--- a/packages/SystemUI/res/values/ids.xml
+++ b/packages/SystemUI/res/values/ids.xml
@@ -172,6 +172,10 @@
     <item type="id" name="accessibility_action_move_right"/>
     <item type="id" name="accessibility_action_move_up"/>
     <item type="id" name="accessibility_action_move_down"/>
+    <item type="id" name="accessibility_action_increase_window_width"/>
+    <item type="id" name="accessibility_action_decrease_window_width"/>
+    <item type="id" name="accessibility_action_increase_window_height"/>
+    <item type="id" name="accessibility_action_decrease_window_height"/>
 
     <!-- Accessibility actions for Accessibility floating menu. -->
     <item type="id" name="action_move_top_left"/>
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index 6840108..29c9767 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -2403,6 +2403,16 @@
     <string name="accessibility_control_move_left">Move left</string>
     <!-- Action in accessibility menu to move the magnification window right. [CHAR LIMIT=30] -->
     <string name="accessibility_control_move_right">Move right</string>
+
+    <!-- Action in accessibility menu to increase the magnification window width. [CHAR LIMIT=NONE] -->
+    <string name="accessibility_control_increase_window_width">Increase width of magnifier</string>
+    <!-- Action in accessibility menu to decrease the magnification window width. [CHAR LIMIT=NONE] -->
+    <string name="accessibility_control_decrease_window_width">Decrease width of magnifier</string>
+    <!-- Action in accessibility menu to increase the magnification window height. [CHAR LIMIT=NONE] -->
+    <string name="accessibility_control_increase_window_height">Increase height of magnifier</string>
+    <!-- Action in accessibility menu to decrease the magnification window height. [CHAR LIMIT=NONE] -->
+    <string name="accessibility_control_decrease_window_height">Decrease height of magnifier</string>
+
     <!-- Content description for magnification mode switch. [CHAR LIMIT=NONE] -->
     <string name="magnification_mode_switch_description">Magnification switch</string>
     <!-- A11y state description for magnification mode switch that device is in full-screen mode. [CHAR LIMIT=NONE] -->
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java
index a2d8c50..7daea3f 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java
@@ -17,7 +17,6 @@
 package com.android.keyguard;
 
 import static android.app.StatusBarManager.SESSION_KEYGUARD;
-
 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 +73,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 +94,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 +203,6 @@
     };
 
     private KeyguardSecurityCallback mKeyguardSecurityCallback = new KeyguardSecurityCallback() {
-
         @Override
         public void onUserInput() {
             mBouncerMessageInteractor.onPrimaryBouncerUserInput();
@@ -297,21 +298,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 +329,6 @@
         }
     };
 
-
     private final SwipeListener mSwipeListener = new SwipeListener() {
         @Override
         public void onSwipeUp() {
@@ -416,6 +418,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 +451,7 @@
             DeviceProvisionedController deviceProvisionedController,
             FaceAuthAccessibilityDelegate faceAuthAccessibilityDelegate,
             KeyguardTransitionInteractor keyguardTransitionInteractor,
+            Lazy<PrimaryBouncerInteractor> primaryBouncerInteractor,
             Provider<AuthenticationInteractor> authenticationInteractor
     ) {
         super(view);
@@ -482,6 +486,7 @@
         mJavaAdapter = javaAdapter;
         mKeyguardTransitionInteractor = keyguardTransitionInteractor;
         mDeviceProvisionedController = deviceProvisionedController;
+        mPrimaryBouncerInteractor = primaryBouncerInteractor;
     }
 
     @Override
@@ -618,6 +623,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 +828,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 +836,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 +862,7 @@
                 case Pattern:
                 case Password:
                 case PIN:
-                    primaryAuth = true;
+                    authenticatedWithPrimaryAuth = true;
                     finish = true;
                     eventSubtype = BOUNCER_DISMISS_PASSWORD;
                     uiEvent = BouncerUiEvent.BOUNCER_DISMISS_PASSWORD;
@@ -901,6 +908,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 +1076,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/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/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/accessibility/WindowMagnificationController.java b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationController.java
index f1cebba..773241e 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationController.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/WindowMagnificationController.java
@@ -26,6 +26,7 @@
 
 import android.animation.ObjectAnimator;
 import android.animation.PropertyValuesHolder;
+import android.annotation.MainThread;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.UiContext;
@@ -719,6 +720,18 @@
     }
 
     /**
+     * Sets the window frame size with given width and height in pixels without changing the
+     * window center.
+     *
+     * @param width the window frame width in pixels
+     * @param height the window frame height in pixels.
+     */
+    @MainThread
+    private void setMagnificationFrameSize(int width, int height) {
+        setWindowSize(width + 2 * mMirrorSurfaceMargin, height + 2 * mMirrorSurfaceMargin);
+    }
+
+    /**
      * Sets the window size with given width and height in pixels without changing the
      * window center. The width or the height will be clamped in the range
      * [{@link #mMinWindowSize}, screen width or height].
@@ -1496,19 +1509,50 @@
                     AccessibilityAction.ACTION_CLICK.getId(), getClickAccessibilityActionLabel());
             info.addAction(clickAction);
             info.setClickable(true);
+
             info.addAction(
                     new AccessibilityAction(R.id.accessibility_action_zoom_in,
                             mContext.getString(R.string.accessibility_control_zoom_in)));
             info.addAction(new AccessibilityAction(R.id.accessibility_action_zoom_out,
                     mContext.getString(R.string.accessibility_control_zoom_out)));
-            info.addAction(new AccessibilityAction(R.id.accessibility_action_move_up,
-                    mContext.getString(R.string.accessibility_control_move_up)));
-            info.addAction(new AccessibilityAction(R.id.accessibility_action_move_down,
-                    mContext.getString(R.string.accessibility_control_move_down)));
-            info.addAction(new AccessibilityAction(R.id.accessibility_action_move_left,
-                    mContext.getString(R.string.accessibility_control_move_left)));
-            info.addAction(new AccessibilityAction(R.id.accessibility_action_move_right,
-                    mContext.getString(R.string.accessibility_control_move_right)));
+
+            if (!mEditSizeEnable) {
+                info.addAction(new AccessibilityAction(R.id.accessibility_action_move_up,
+                        mContext.getString(R.string.accessibility_control_move_up)));
+                info.addAction(new AccessibilityAction(R.id.accessibility_action_move_down,
+                        mContext.getString(R.string.accessibility_control_move_down)));
+                info.addAction(new AccessibilityAction(R.id.accessibility_action_move_left,
+                        mContext.getString(R.string.accessibility_control_move_left)));
+                info.addAction(new AccessibilityAction(R.id.accessibility_action_move_right,
+                        mContext.getString(R.string.accessibility_control_move_right)));
+            } else {
+                if ((mMagnificationFrame.width() + 2 * mMirrorSurfaceMargin)
+                        < mWindowBounds.width()) {
+                    info.addAction(new AccessibilityAction(
+                            R.id.accessibility_action_increase_window_width,
+                            mContext.getString(
+                                    R.string.accessibility_control_increase_window_width)));
+                }
+                if ((mMagnificationFrame.height() + 2 * mMirrorSurfaceMargin)
+                        < mWindowBounds.height()) {
+                    info.addAction(new AccessibilityAction(
+                            R.id.accessibility_action_increase_window_height,
+                            mContext.getString(
+                                    R.string.accessibility_control_increase_window_height)));
+                }
+                if ((mMagnificationFrame.width() + 2 * mMirrorSurfaceMargin) > mMinWindowSize) {
+                    info.addAction(new AccessibilityAction(
+                            R.id.accessibility_action_decrease_window_width,
+                            mContext.getString(
+                                    R.string.accessibility_control_decrease_window_width)));
+                }
+                if ((mMagnificationFrame.height() + 2 * mMirrorSurfaceMargin) > mMinWindowSize) {
+                    info.addAction(new AccessibilityAction(
+                            R.id.accessibility_action_decrease_window_height,
+                            mContext.getString(
+                                    R.string.accessibility_control_decrease_window_height)));
+                }
+            }
 
             info.setContentDescription(mContext.getString(R.string.magnification_window_title));
             info.setStateDescription(formatStateDescription(getScale()));
@@ -1523,6 +1567,11 @@
         }
 
         private boolean performA11yAction(int action) {
+            final float changeWindowSizeAmount = mContext.getResources().getFraction(
+                    R.fraction.magnification_resize_window_size_amount,
+                    /* base= */ 1,
+                    /* pbase= */ 1);
+
             if (action == AccessibilityAction.ACTION_CLICK.getId()) {
                 if (mEditSizeEnable) {
                     // When edit mode is enabled, click the magnifier to exit edit mode.
@@ -1544,9 +1593,26 @@
                 move(-mSourceBounds.width(), 0);
             } else if (action == R.id.accessibility_action_move_right) {
                 move(mSourceBounds.width(), 0);
+            } else if (action == R.id.accessibility_action_increase_window_width) {
+                int newFrameWidth =
+                        (int) (mMagnificationFrame.width() * (1 + changeWindowSizeAmount));
+                setMagnificationFrameSize(newFrameWidth, mMagnificationFrame.height());
+            } else if (action == R.id.accessibility_action_increase_window_height) {
+                int newFrameHeight =
+                        (int) (mMagnificationFrame.height() * (1 + changeWindowSizeAmount));
+                setMagnificationFrameSize(mMagnificationFrame.width(), newFrameHeight);
+            } else if (action == R.id.accessibility_action_decrease_window_width) {
+                int newFrameWidth =
+                        (int) (mMagnificationFrame.width() * (1 - changeWindowSizeAmount));
+                setMagnificationFrameSize(newFrameWidth, mMagnificationFrame.height());
+            } else if (action == R.id.accessibility_action_decrease_window_height) {
+                int newFrameHeight =
+                        (int) (mMagnificationFrame.height() * (1 - changeWindowSizeAmount));
+                setMagnificationFrameSize(mMagnificationFrame.width(), newFrameHeight);
             } else {
                 return false;
             }
+
             mWindowMagnifierCallback.onAccessibilityActionPerformed(mDisplayId);
             return true;
         }
@@ -1557,4 +1623,5 @@
                     mDisplayId, scale, /* updatePersistence= */ true);
         }
     }
+
 }
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsAnimationViewController.kt b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsAnimationViewController.kt
index 94b5fb2..b8c0e2c 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsAnimationViewController.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsAnimationViewController.kt
@@ -18,14 +18,21 @@
 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.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 +48,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 +61,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 +87,28 @@
         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 ->
+                notificationShadeVisible = expansion > 0f
+                view.onExpansionChanged(expansion)
+                updatePauseAuth()
+            }
+        }
+    }
+
     fun runDialogAlphaAnimator() {
         val hideAffordance = dialogManager.shouldHideAffordance()
         dialogAlphaAnimator?.cancel()
@@ -108,15 +129,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..11a578b 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;
@@ -92,7 +92,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;
@@ -118,6 +117,7 @@
 import javax.inject.Inject;
 import javax.inject.Provider;
 
+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..4031f2f 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsControllerOverlay.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsControllerOverlay.kt
@@ -60,7 +60,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 +89,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 +243,7 @@
                         updateAccessibilityViewLocation(sensorBounds)
                     },
                     statusBarStateController,
-                    shadeExpansionStateManager,
+                    primaryBouncerInteractor,
                     dialogManager,
                     dumpManager
                 )
@@ -256,7 +254,7 @@
                     UdfpsKeyguardViewController(
                         view.addUdfpsView(R.layout.udfps_keyguard_view),
                         statusBarStateController,
-                        shadeExpansionStateManager,
+                        primaryBouncerInteractor,
                         dialogManager,
                         dumpManager,
                         alternateBouncerInteractor,
@@ -268,7 +266,6 @@
                             updateSensorLocation(sensorBounds)
                         },
                         statusBarStateController,
-                        shadeExpansionStateManager,
                         statusBarKeyguardViewManager,
                         keyguardUpdateMonitor,
                         dumpManager,
@@ -291,7 +288,7 @@
                 UdfpsBpViewController(
                     view.addUdfpsView(R.layout.udfps_bp_view),
                     statusBarStateController,
-                    shadeExpansionStateManager,
+                    primaryBouncerInteractor,
                     dialogManager,
                     dumpManager
                 )
@@ -301,7 +298,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/data/repository/FingerprintPropertyRepository.kt b/packages/SystemUI/src/com/android/systemui/biometrics/data/repository/FingerprintPropertyRepository.kt
index daff5fe..aa33100 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/data/repository/FingerprintPropertyRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/data/repository/FingerprintPropertyRepository.kt
@@ -16,13 +16,19 @@
 
 package com.android.systemui.biometrics.data.repository
 
+import android.Manifest.permission.USE_BIOMETRIC_INTERNAL
+import android.annotation.RequiresPermission
+import android.hardware.biometrics.ComponentInfoInternal
 import android.hardware.biometrics.SensorLocationInternal
+import android.hardware.biometrics.SensorProperties
 import android.hardware.fingerprint.FingerprintManager
+import android.hardware.fingerprint.FingerprintSensorProperties
 import android.hardware.fingerprint.FingerprintSensorPropertiesInternal
 import android.hardware.fingerprint.IFingerprintAuthenticatorsRegisteredCallback
 import com.android.systemui.biometrics.shared.model.FingerprintSensorType
 import com.android.systemui.biometrics.shared.model.SensorStrength
 import com.android.systemui.biometrics.shared.model.toSensorStrength
+import com.android.systemui.biometrics.shared.model.toSensorType
 import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging
 import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
 import com.android.systemui.dagger.SysUISingleton
@@ -31,11 +37,10 @@
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.channels.awaitClose
 import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.MutableStateFlow
 import kotlinx.coroutines.flow.SharingStarted
 import kotlinx.coroutines.flow.StateFlow
-import kotlinx.coroutines.flow.asStateFlow
-import kotlinx.coroutines.flow.shareIn
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.stateIn
 
 /**
  * A repository for the global state of FingerprintProperty.
@@ -44,22 +49,17 @@
  */
 interface FingerprintPropertyRepository {
 
-    /**
-     * If the repository is initialized or not. Other properties are defaults until this is true.
-     */
-    val isInitialized: Flow<Boolean>
-
     /** The id of fingerprint sensor. */
-    val sensorId: StateFlow<Int>
+    val sensorId: Flow<Int>
 
     /** The security strength of sensor (convenience, weak, strong). */
-    val strength: StateFlow<SensorStrength>
+    val strength: Flow<SensorStrength>
 
     /** The types of fingerprint sensor (rear, ultrasonic, optical, etc.). */
-    val sensorType: StateFlow<FingerprintSensorType>
+    val sensorType: Flow<FingerprintSensorType>
 
     /** The sensor location relative to each physical display. */
-    val sensorLocations: StateFlow<Map<String, SensorLocationInternal>>
+    val sensorLocations: Flow<Map<String, SensorLocationInternal>>
 }
 
 @SysUISingleton
@@ -70,64 +70,64 @@
     private val fingerprintManager: FingerprintManager?,
 ) : FingerprintPropertyRepository {
 
-    override val isInitialized: Flow<Boolean> =
+    @RequiresPermission(USE_BIOMETRIC_INTERNAL)
+    private val props: StateFlow<FingerprintSensorPropertiesInternal> =
         conflatedCallbackFlow {
                 val callback =
                     object : IFingerprintAuthenticatorsRegisteredCallback.Stub() {
                         override fun onAllAuthenticatorsRegistered(
                             sensors: List<FingerprintSensorPropertiesInternal>
                         ) {
-                            if (sensors.isNotEmpty()) {
-                                setProperties(sensors[0])
-                                trySendWithFailureLogging(true, TAG, "initialize properties")
+                            if (sensors.isEmpty()) {
+                                trySendWithFailureLogging(
+                                    DEFAULT_PROPS,
+                                    TAG,
+                                    "no registered sensors, use default props"
+                                )
+                            } else {
+                                trySendWithFailureLogging(
+                                    sensors[0],
+                                    TAG,
+                                    "update properties on authenticators registered"
+                                )
                             }
                         }
                     }
                 fingerprintManager?.addAuthenticatorsRegisteredCallback(callback)
-                trySendWithFailureLogging(false, TAG, "initial value defaulting to false")
                 awaitClose {}
             }
-            .shareIn(scope = applicationScope, started = SharingStarted.Eagerly, replay = 1)
+            .stateIn(
+                applicationScope,
+                started = SharingStarted.Eagerly,
+                initialValue = DEFAULT_PROPS,
+            )
 
-    private val _sensorId: MutableStateFlow<Int> = MutableStateFlow(-1)
-    override val sensorId: StateFlow<Int> = _sensorId.asStateFlow()
+    override val sensorId: Flow<Int> = props.map { it.sensorId }
 
-    private val _strength: MutableStateFlow<SensorStrength> =
-        MutableStateFlow(SensorStrength.CONVENIENCE)
-    override val strength = _strength.asStateFlow()
+    override val strength: Flow<SensorStrength> = props.map { it.sensorStrength.toSensorStrength() }
 
-    private val _sensorType: MutableStateFlow<FingerprintSensorType> =
-        MutableStateFlow(FingerprintSensorType.UNKNOWN)
-    override val sensorType = _sensorType.asStateFlow()
+    override val sensorType: Flow<FingerprintSensorType> =
+        props.map { it.sensorType.toSensorType() }
 
-    private val _sensorLocations: MutableStateFlow<Map<String, SensorLocationInternal>> =
-        MutableStateFlow(mapOf("" to SensorLocationInternal.DEFAULT))
-    override val sensorLocations: StateFlow<Map<String, SensorLocationInternal>> =
-        _sensorLocations.asStateFlow()
-
-    private fun setProperties(prop: FingerprintSensorPropertiesInternal) {
-        _sensorId.value = prop.sensorId
-        _strength.value = prop.sensorStrength.toSensorStrength()
-        _sensorType.value = sensorTypeIntToObject(prop.sensorType)
-        _sensorLocations.value =
-            prop.allLocations.associateBy { sensorLocationInternal ->
+    override val sensorLocations: Flow<Map<String, SensorLocationInternal>> =
+        props.map {
+            it.allLocations.associateBy { sensorLocationInternal ->
                 sensorLocationInternal.displayId
             }
-    }
+        }
 
     companion object {
         private const val TAG = "FingerprintPropertyRepositoryImpl"
-    }
-}
-
-private fun sensorTypeIntToObject(value: Int): FingerprintSensorType {
-    return when (value) {
-        0 -> FingerprintSensorType.UNKNOWN
-        1 -> FingerprintSensorType.REAR
-        2 -> FingerprintSensorType.UDFPS_ULTRASONIC
-        3 -> FingerprintSensorType.UDFPS_OPTICAL
-        4 -> FingerprintSensorType.POWER_BUTTON
-        5 -> FingerprintSensorType.HOME_BUTTON
-        else -> throw IllegalArgumentException("Invalid SensorType value: $value")
+        private val DEFAULT_PROPS =
+            FingerprintSensorPropertiesInternal(
+                -1 /* sensorId */,
+                SensorProperties.STRENGTH_CONVENIENCE,
+                0 /* maxEnrollmentsPerUser */,
+                listOf<ComponentInfoInternal>(),
+                FingerprintSensorProperties.TYPE_UNKNOWN,
+                false /* halControlsIllumination */,
+                true /* resetLockoutRequiresHardwareAuthToken */,
+                listOf<SensorLocationInternal>(SensorLocationInternal.DEFAULT)
+            )
     }
 }
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 5badcaf..a6ad24e 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
@@ -32,7 +32,6 @@
 import com.android.systemui.dagger.SysUISingleton
 import javax.inject.Inject
 import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.StateFlow
 import kotlinx.coroutines.flow.combine
 import kotlinx.coroutines.flow.distinctUntilChanged
 import kotlinx.coroutines.flow.map
@@ -69,7 +68,7 @@
     val isConfirmationRequired: Flow<Boolean>
 
     /** Fingerprint sensor type */
-    val sensorType: StateFlow<FingerprintSensorType>
+    val sensorType: Flow<FingerprintSensorType>
 
     /** Use biometrics for authentication. */
     fun useBiometricsForAuthentication(
@@ -95,7 +94,7 @@
 class PromptSelectorInteractorImpl
 @Inject
 constructor(
-    private val fingerprintPropertyRepository: FingerprintPropertyRepository,
+    fingerprintPropertyRepository: FingerprintPropertyRepository,
     private val promptRepository: PromptRepository,
     lockPatternUtils: LockPatternUtils,
 ) : PromptSelectorInteractor {
@@ -147,8 +146,7 @@
             }
         }
 
-    override val sensorType: StateFlow<FingerprintSensorType> =
-        fingerprintPropertyRepository.sensorType
+    override val sensorType: Flow<FingerprintSensorType> = fingerprintPropertyRepository.sensorType
 
     override fun useBiometricsForAuthentication(
         promptInfo: PromptInfo,
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/SideFpsOverlayInteractor.kt b/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/SideFpsOverlayInteractor.kt
index aa85e5f3..75ae061 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/SideFpsOverlayInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/SideFpsOverlayInteractor.kt
@@ -17,32 +17,43 @@
 package com.android.systemui.biometrics.domain.interactor
 
 import android.hardware.biometrics.SensorLocationInternal
-import android.util.Log
 import com.android.systemui.biometrics.data.repository.FingerprintPropertyRepository
 import com.android.systemui.dagger.SysUISingleton
 import javax.inject.Inject
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.asStateFlow
+import kotlinx.coroutines.flow.combine
 
 /** Business logic for SideFps overlay offsets. */
 interface SideFpsOverlayInteractor {
 
-    /** Get the corresponding offsets based on different displayId. */
-    fun getOverlayOffsets(displayId: String): SensorLocationInternal
+    /** The displayId of the current display. */
+    val displayId: Flow<String>
+
+    /** Overlay offsets corresponding to given displayId. */
+    val overlayOffsets: Flow<SensorLocationInternal>
+
+    /** Called on display changes, used to keep the display state in sync */
+    fun onDisplayChanged(displayId: String)
 }
 
 @SysUISingleton
 class SideFpsOverlayInteractorImpl
 @Inject
-constructor(private val fingerprintPropertyRepository: FingerprintPropertyRepository) :
+constructor(fingerprintPropertyRepository: FingerprintPropertyRepository) :
     SideFpsOverlayInteractor {
 
-    override fun getOverlayOffsets(displayId: String): SensorLocationInternal {
-        val offsets = fingerprintPropertyRepository.sensorLocations.value
-        return if (offsets.containsKey(displayId)) {
-            offsets[displayId]!!
-        } else {
-            Log.w(TAG, "No location specified for display: $displayId")
-            offsets[""]!!
+    private val _displayId: MutableStateFlow<String> = MutableStateFlow("")
+    override val displayId: Flow<String> = _displayId.asStateFlow()
+
+    override val overlayOffsets: Flow<SensorLocationInternal> =
+        combine(displayId, fingerprintPropertyRepository.sensorLocations) { displayId, offsets ->
+            offsets[displayId] ?: SensorLocationInternal.DEFAULT
         }
+
+    override fun onDisplayChanged(displayId: String) {
+        _displayId.value = displayId
     }
 
     companion object {
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/shared/model/FingerprintSensorType.kt b/packages/SystemUI/src/com/android/systemui/biometrics/shared/model/FingerprintSensorType.kt
index df5cefd..c6fdcb3 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/shared/model/FingerprintSensorType.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/shared/model/FingerprintSensorType.kt
@@ -27,3 +27,15 @@
     POWER_BUTTON,
     HOME_BUTTON,
 }
+
+/** Convert [this] to corresponding [FingerprintSensorType] */
+fun Int.toSensorType(): FingerprintSensorType =
+    when (this) {
+        FingerprintSensorProperties.TYPE_UNKNOWN -> FingerprintSensorType.UNKNOWN
+        FingerprintSensorProperties.TYPE_REAR -> FingerprintSensorType.REAR
+        FingerprintSensorProperties.TYPE_UDFPS_ULTRASONIC -> FingerprintSensorType.UDFPS_ULTRASONIC
+        FingerprintSensorProperties.TYPE_UDFPS_OPTICAL -> FingerprintSensorType.UDFPS_OPTICAL
+        FingerprintSensorProperties.TYPE_POWER_BUTTON -> FingerprintSensorType.POWER_BUTTON
+        FingerprintSensorProperties.TYPE_HOME_BUTTON -> FingerprintSensorType.HOME_BUTTON
+        else -> throw IllegalArgumentException("Invalid SensorType value: $this")
+    }
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/shared/model/SensorStrength.kt b/packages/SystemUI/src/com/android/systemui/biometrics/shared/model/SensorStrength.kt
index 30e865e..476daac 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/shared/model/SensorStrength.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/shared/model/SensorStrength.kt
@@ -28,8 +28,8 @@
 /** Convert [this] to corresponding [SensorStrength] */
 fun Int.toSensorStrength(): SensorStrength =
     when (this) {
-        0 -> SensorStrength.CONVENIENCE
-        1 -> SensorStrength.WEAK
-        2 -> SensorStrength.STRONG
+        SensorProperties.STRENGTH_CONVENIENCE -> SensorStrength.CONVENIENCE
+        SensorProperties.STRENGTH_WEAK -> SensorStrength.WEAK
+        SensorProperties.STRENGTH_STRONG -> SensorStrength.STRONG
         else -> throw IllegalArgumentException("Invalid SensorStrength value: $this")
     }
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/PromptFingerprintIconViewModel.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptFingerprintIconViewModel.kt
index 9b30acb..b406ea4 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptFingerprintIconViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptFingerprintIconViewModel.kt
@@ -33,7 +33,7 @@
 @Inject
 constructor(
     private val displayStateInteractor: DisplayStateInteractor,
-    private val promptSelectorInteractor: PromptSelectorInteractor,
+    promptSelectorInteractor: PromptSelectorInteractor,
 ) {
     /** Current device rotation. */
     private var rotation: Int = Surface.ROTATION_0
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/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/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 2cc07fd..c29664e 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
+++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
@@ -268,10 +268,6 @@
     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")
-
     /** Flag meant to guard the talkback fix for the KeyguardIndicationTextView */
     // TODO(b/286563884): Tracking bug
     @JvmField val KEYGUARD_TALKBACK_FIX = releasedFlag("keyguard_talkback_fix")
@@ -312,11 +308,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 +356,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 +384,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")
 
@@ -785,7 +777,8 @@
 
     /** TODO(b/296223317): Enables the new keyguard presentation containing a clock. */
     @JvmField
-    val ENABLE_CLOCK_KEYGUARD_PRESENTATION = unreleasedFlag("enable_clock_keyguard_presentation")
+    val ENABLE_CLOCK_KEYGUARD_PRESENTATION =
+        unreleasedFlag("enable_clock_keyguard_presentation", teamfood = true)
 
     /** Enable the Compose implementation of the PeopleSpaceActivity. */
     @JvmField
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 8954947..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
@@ -50,7 +51,6 @@
 import com.android.systemui.log.FaceAuthenticationLogger
 import com.android.systemui.log.SessionTracker
 import com.android.systemui.log.table.TableLogBuffer
-import com.android.systemui.log.table.logDiffsForTable
 import com.android.systemui.statusbar.phone.KeyguardBypassController
 import com.android.systemui.user.data.model.SelectionStatus
 import com.android.systemui.user.data.repository.UserRepository
@@ -66,9 +66,10 @@
 import kotlinx.coroutines.delay
 import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.SharingStarted
 import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.asStateFlow
 import kotlinx.coroutines.flow.combine
-import kotlinx.coroutines.flow.distinctUntilChanged
 import kotlinx.coroutines.flow.filter
 import kotlinx.coroutines.flow.filterNotNull
 import kotlinx.coroutines.flow.flowOf
@@ -77,6 +78,7 @@
 import kotlinx.coroutines.flow.map
 import kotlinx.coroutines.flow.merge
 import kotlinx.coroutines.flow.onEach
+import kotlinx.coroutines.flow.stateIn
 import kotlinx.coroutines.launch
 import kotlinx.coroutines.withContext
 
@@ -153,13 +155,14 @@
     private val faceAuthLogger: FaceAuthenticationLogger,
     private val biometricSettingsRepository: BiometricSettingsRepository,
     private val deviceEntryFingerprintAuthRepository: DeviceEntryFingerprintAuthRepository,
-    private val trustRepository: TrustRepository,
+    trustRepository: TrustRepository,
     private val keyguardRepository: KeyguardRepository,
     private val keyguardInteractor: KeyguardInteractor,
     private val alternateBouncerInteractor: AlternateBouncerInteractor,
     @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 {
@@ -202,11 +205,9 @@
     private val keyguardSessionId: InstanceId?
         get() = sessionTracker.getSessionId(StatusBarManager.SESSION_KEYGUARD)
 
-    private val _canRunFaceAuth = MutableStateFlow(false)
     override val canRunFaceAuth: StateFlow<Boolean>
-        get() = _canRunFaceAuth
 
-    private val canRunDetection = MutableStateFlow(false)
+    private val canRunDetection: StateFlow<Boolean>
 
     private val _isAuthenticated = MutableStateFlow(false)
     override val isAuthenticated: Flow<Boolean>
@@ -252,10 +253,58 @@
         dumpManager.registerCriticalDumpable("DeviceEntryFaceAuthRepositoryImpl", this)
 
         if (featureFlags.isEnabled(Flags.FACE_AUTH_REFACTOR)) {
+            canRunFaceAuth =
+                listOf(
+                        *gatingConditionsForAuthAndDetect(),
+                        Pair(isLockedOut.isFalse(), "isNotInLockOutState"),
+                        Pair(
+                            trustRepository.isCurrentUserTrusted.isFalse(),
+                            "currentUserIsNotTrusted"
+                        ),
+                        Pair(
+                            biometricSettingsRepository.isFaceAuthCurrentlyAllowed,
+                            "isFaceAuthCurrentlyAllowed"
+                        ),
+                        Pair(isAuthenticated.isFalse(), "faceNotAuthenticated"),
+                    )
+                    .andAllFlows("canFaceAuthRun", faceAuthLog)
+                    .flowOn(mainDispatcher)
+                    .stateIn(applicationScope, SharingStarted.Eagerly, false)
+
+            // Face detection can run only when lockscreen bypass is enabled
+            // & detection is supported
+            //   & biometric unlock is not allowed
+            //     or user is trusted by trust manager & we want to run face detect to dismiss
+            // keyguard
+            canRunDetection =
+                listOf(
+                        *gatingConditionsForAuthAndDetect(),
+                        Pair(isBypassEnabled, "isBypassEnabled"),
+                        Pair(
+                            biometricSettingsRepository.isFaceAuthCurrentlyAllowed
+                                .isFalse()
+                                .or(trustRepository.isCurrentUserTrusted),
+                            "faceAuthIsNotCurrentlyAllowedOrCurrentUserIsTrusted"
+                        ),
+                        // We don't want to run face detect if fingerprint can be used to unlock the
+                        // device
+                        // but it's not possible to authenticate with FP from the bouncer (UDFPS)
+                        Pair(
+                            and(isUdfps(), deviceEntryFingerprintAuthRepository.isRunning)
+                                .isFalse(),
+                            "udfpsAuthIsNotPossibleAnymore"
+                        )
+                    )
+                    .andAllFlows("canFaceDetectRun", faceDetectLog)
+                    .flowOn(mainDispatcher)
+                    .stateIn(applicationScope, SharingStarted.Eagerly, false)
             observeFaceAuthGatingChecks()
             observeFaceDetectGatingChecks()
             observeFaceAuthResettingConditions()
             listenForSchedulingWatchdog()
+        } else {
+            canRunFaceAuth = MutableStateFlow(false).asStateFlow()
+            canRunDetection = MutableStateFlow(false).asStateFlow()
         }
     }
 
@@ -298,39 +347,13 @@
     }
 
     private fun observeFaceDetectGatingChecks() {
-        // Face detection can run only when lockscreen bypass is enabled
-        // & detection is supported
-        //   & biometric unlock is not allowed
-        //     or user is trusted by trust manager & we want to run face detect to dismiss keyguard
-        listOf(
-                canFaceAuthOrDetectRun(faceDetectLog),
-                logAndObserve(isBypassEnabled, "isBypassEnabled", faceDetectLog),
-                logAndObserve(
-                    biometricSettingsRepository.isFaceAuthCurrentlyAllowed
-                        .isFalse()
-                        .or(trustRepository.isCurrentUserTrusted),
-                    "faceAuthIsNotCurrentlyAllowedOrCurrentUserIsTrusted",
-                    faceDetectLog
-                ),
-                // We don't want to run face detect if fingerprint can be used to unlock the device
-                // but it's not possible to authenticate with FP from the bouncer (UDFPS)
-                logAndObserve(
-                    and(isUdfps(), deviceEntryFingerprintAuthRepository.isRunning).isFalse(),
-                    "udfpsAuthIsNotPossibleAnymore",
-                    faceDetectLog
-                )
-            )
-            .reduce(::and)
-            .distinctUntilChanged()
+        canRunDetection
             .onEach {
-                faceAuthLogger.canRunDetectionChanged(it)
-                canRunDetection.value = it
                 if (!it) {
                     cancelDetection()
                 }
             }
             .flowOn(mainDispatcher)
-            .logDiffsForTable(faceDetectLog, "", "canFaceDetectRun", false)
             .launchIn(applicationScope)
     }
 
@@ -339,76 +362,54 @@
             it == BiometricType.UNDER_DISPLAY_FINGERPRINT
         }
 
-    private fun canFaceAuthOrDetectRun(tableLogBuffer: TableLogBuffer): Flow<Boolean> {
-        return listOf(
-                logAndObserve(
-                    biometricSettingsRepository.isFaceAuthEnrolledAndEnabled,
-                    "isFaceAuthEnrolledAndEnabled",
-                    tableLogBuffer
-                ),
-                logAndObserve(faceAuthPaused.isFalse(), "faceAuthIsNotPaused", tableLogBuffer),
-                logAndObserve(
-                    keyguardRepository.isKeyguardGoingAway.isFalse(),
-                    "keyguardNotGoingAway",
-                    tableLogBuffer
-                ),
-                logAndObserve(
-                    keyguardRepository.wakefulness.map { it.isStartingToSleep() }.isFalse(),
-                    "deviceNotStartingToSleep",
-                    tableLogBuffer
-                ),
-                logAndObserve(
-                    keyguardInteractor.isSecureCameraActive
-                        .isFalse()
-                        .or(
-                            alternateBouncerInteractor.isVisible.or(
-                                keyguardInteractor.primaryBouncerShowing
-                            )
-                        ),
-                    "secureCameraNotActiveOrAnyBouncerIsShowing",
-                    tableLogBuffer
-                ),
-                logAndObserve(
-                    biometricSettingsRepository.isFaceAuthSupportedInCurrentPosture,
-                    "isFaceAuthSupportedInCurrentPosture",
-                    tableLogBuffer
-                ),
-                logAndObserve(
-                    biometricSettingsRepository.isCurrentUserInLockdown.isFalse(),
-                    "userHasNotLockedDownDevice",
-                    tableLogBuffer
-                ),
-                logAndObserve(
-                    keyguardRepository.isKeyguardShowing,
-                    "isKeyguardShowing",
-                    tableLogBuffer
-                )
-            )
-            .reduce(::and)
+    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"
+            ),
+            Pair(faceAuthPaused.isFalse(), "faceAuthIsNotPaused"),
+            Pair(keyguardRepository.isKeyguardGoingAway.isFalse(), "keyguardNotGoingAway"),
+            Pair(
+                keyguardRepository.wakefulness.map { it.isStartingToSleep() }.isFalse(),
+                "deviceNotStartingToSleep"
+            ),
+            Pair(
+                keyguardInteractor.isSecureCameraActive
+                    .isFalse()
+                    .or(
+                        alternateBouncerInteractor.isVisible.or(
+                            keyguardInteractor.primaryBouncerShowing
+                        )
+                    ),
+                "secureCameraNotActiveOrAnyBouncerIsShowing"
+            ),
+            Pair(
+                biometricSettingsRepository.isFaceAuthSupportedInCurrentPosture,
+                "isFaceAuthSupportedInCurrentPosture"
+            ),
+            Pair(
+                biometricSettingsRepository.isCurrentUserInLockdown.isFalse(),
+                "userHasNotLockedDownDevice"
+            ),
+            Pair(keyguardRepository.isKeyguardShowing, "isKeyguardShowing")
+        )
     }
 
     private fun observeFaceAuthGatingChecks() {
-        // Face auth can run only if all of the gating conditions are true.
-        listOf(
-                canFaceAuthOrDetectRun(faceAuthLog),
-                logAndObserve(isLockedOut.isFalse(), "isNotInLockOutState", faceAuthLog),
-                logAndObserve(
-                    trustRepository.isCurrentUserTrusted.isFalse(),
-                    "currentUserIsNotTrusted",
-                    faceAuthLog
-                ),
-                logAndObserve(
-                    biometricSettingsRepository.isFaceAuthCurrentlyAllowed,
-                    "isFaceAuthCurrentlyAllowed",
-                    faceAuthLog
-                ),
-                logAndObserve(isAuthenticated.isFalse(), "faceNotAuthenticated", faceAuthLog),
-            )
-            .reduce(::and)
-            .distinctUntilChanged()
+        canRunFaceAuth
             .onEach {
                 faceAuthLogger.canFaceAuthRunChanged(it)
-                _canRunFaceAuth.value = it
                 if (!it) {
                     // Cancel currently running auth if any of the gating checks are false.
                     faceAuthLogger.cancellingFaceAuth()
@@ -416,7 +417,6 @@
                 }
             }
             .flowOn(mainDispatcher)
-            .logDiffsForTable(faceAuthLog, "", "canFaceAuthRun", false)
             .launchIn(applicationScope)
     }
 
@@ -618,22 +618,6 @@
         _isAuthRunning.value = false
     }
 
-    private fun logAndObserve(
-        cond: Flow<Boolean>,
-        conditionName: String,
-        logBuffer: TableLogBuffer
-    ): Flow<Boolean> {
-        return cond
-            .distinctUntilChanged()
-            .logDiffsForTable(
-                logBuffer,
-                columnName = conditionName,
-                columnPrefix = "",
-                initialValue = false
-            )
-            .onEach { faceAuthLogger.observedConditionChanged(it, conditionName) }
-    }
-
     companion object {
         const val TAG = "DeviceEntryFaceAuthRepository"
 
@@ -688,3 +672,18 @@
 private fun Flow<Boolean>.isFalse(): Flow<Boolean> {
     return this.map { !it }
 }
+
+private fun List<Pair<Flow<Boolean>, String>>.andAllFlows(
+    combinedLoggingInfo: String,
+    tableLogBuffer: TableLogBuffer
+): Flow<Boolean> {
+    return combine(this.map { it.first }) {
+        val combinedValue =
+            it.reduceIndexed { index, accumulator, current ->
+                tableLogBuffer.logChange(prefix = "", columnName = this[index].second, current)
+                return@reduceIndexed accumulator && current
+            }
+        tableLogBuffer.logChange(prefix = "", combinedLoggingInfo, combinedValue)
+        return@combine combinedValue
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardBlueprintRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardBlueprintRepository.kt
index f91ae74..f5ef27d 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardBlueprintRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/KeyguardBlueprintRepository.kt
@@ -24,6 +24,7 @@
 import com.android.systemui.keyguard.ui.view.layout.blueprints.DefaultKeyguardBlueprint.Companion.DEFAULT
 import com.android.systemui.keyguard.ui.view.layout.blueprints.KeyguardBlueprintModule
 import java.io.PrintWriter
+import java.util.TreeMap
 import javax.inject.Inject
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.flow.Flow
@@ -52,11 +53,12 @@
     blueprints: Set<@JvmSuppressWildcards KeyguardBlueprint>,
     @Application private val applicationScope: CoroutineScope,
 ) {
-    private val blueprintIdMap: Map<String, KeyguardBlueprint> = blueprints.associateBy { it.id }
+    private val blueprintIdMap: TreeMap<String, KeyguardBlueprint> = TreeMap()
     private val _blueprint: MutableSharedFlow<KeyguardBlueprint> = MutableSharedFlow(replay = 1)
     val blueprint: Flow<KeyguardBlueprint> = _blueprint.asSharedFlow()
 
     init {
+        blueprintIdMap.putAll(blueprints.associateBy { it.id })
         applyBlueprint(blueprintIdMap[DEFAULT]!!)
         applicationScope.launch {
             configurationRepository.onAnyConfigurationChange.collect { refreshBlueprint() }
@@ -69,6 +71,20 @@
      * @param blueprintId
      * @return whether the transition has succeeded.
      */
+    fun applyBlueprint(index: Int): Boolean {
+        ArrayList(blueprintIdMap.values)[index]?.let {
+            applyBlueprint(it)
+            return true
+        }
+        return false
+    }
+
+    /**
+     * Emits the blueprint value to the collectors.
+     *
+     * @param blueprintId
+     * @return whether the transition has succeeded.
+     */
     fun applyBlueprint(blueprintId: String?): Boolean {
         val blueprint = blueprintIdMap[blueprintId] ?: return false
         applyBlueprint(blueprint)
@@ -89,6 +105,6 @@
 
     /** Prints all available blueprints to the PrintWriter. */
     fun printBlueprints(pw: PrintWriter) {
-        blueprintIdMap.forEach { entry -> pw.println("${entry.key}") }
+        blueprintIdMap.onEachIndexed { index, entry -> pw.println("$index: ${entry.key}") }
     }
 }
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/KeyguardBlueprintInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardBlueprintInteractor.kt
index 390ad7e..6ce9185 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardBlueprintInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardBlueprintInteractor.kt
@@ -37,6 +37,16 @@
         return keyguardBlueprintRepository.applyBlueprint(blueprintId)
     }
 
+    /**
+     * Transitions to a blueprint.
+     *
+     * @param blueprintId
+     * @return whether the transition has succeeded.
+     */
+    fun transitionToBlueprint(blueprintId: Int): Boolean {
+        return keyguardBlueprintRepository.applyBlueprint(blueprintId)
+    }
+
     /** Re-emits the blueprint value to the collectors. */
     fun refreshBlueprint() {
         keyguardBlueprintRepository.refreshBlueprint()
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/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/KeyguardBlueprintCommandListener.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/KeyguardBlueprintCommandListener.kt
index 36d21f1..ce7ec0e 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/KeyguardBlueprintCommandListener.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/KeyguardBlueprintCommandListener.kt
@@ -16,6 +16,7 @@
 
 package com.android.systemui.keyguard.ui.view.layout
 
+import androidx.core.text.isDigitsOnly
 import com.android.systemui.keyguard.data.repository.KeyguardBlueprintRepository
 import com.android.systemui.keyguard.domain.interactor.KeyguardBlueprintInteractor
 import com.android.systemui.statusbar.commandline.Command
@@ -45,7 +46,11 @@
                 return
             }
 
-            if (keyguardBlueprintInteractor.transitionToBlueprint(arg)) {
+            if (
+                arg.isDigitsOnly() && keyguardBlueprintInteractor.transitionToBlueprint(arg.toInt())
+            ) {
+                pw.println("Transition succeeded!")
+            } else if (keyguardBlueprintInteractor.transitionToBlueprint(arg)) {
                 pw.println("Transition succeeded!")
             } else {
                 pw.println("Invalid argument! To see available blueprint ids, run:")
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/blueprints/ShortcutsBesideUdfpsKeyguardBlueprint.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/blueprints/ShortcutsBesideUdfpsKeyguardBlueprint.kt
index 79a97fb..6534dcf 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/blueprints/ShortcutsBesideUdfpsKeyguardBlueprint.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/blueprints/ShortcutsBesideUdfpsKeyguardBlueprint.kt
@@ -61,6 +61,6 @@
         )
 
     companion object {
-        const val SHORTCUTS_BESIDE_UDFPS = "shortcutsBesideUdfps"
+        const val SHORTCUTS_BESIDE_UDFPS = "shortcuts-besides-udfps"
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/AlignShortcutsToUdfpsSection.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/AlignShortcutsToUdfpsSection.kt
index 79b7157..5aba229 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/AlignShortcutsToUdfpsSection.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/AlignShortcutsToUdfpsSection.kt
@@ -18,8 +18,6 @@
 package com.android.systemui.keyguard.ui.view.layout.sections
 
 import android.content.res.Resources
-import android.view.View
-import android.widget.ImageView
 import androidx.constraintlayout.widget.ConstraintLayout
 import androidx.constraintlayout.widget.ConstraintSet
 import androidx.constraintlayout.widget.ConstraintSet.BOTTOM
@@ -27,13 +25,10 @@
 import androidx.constraintlayout.widget.ConstraintSet.PARENT_ID
 import androidx.constraintlayout.widget.ConstraintSet.RIGHT
 import androidx.constraintlayout.widget.ConstraintSet.TOP
-import androidx.core.content.res.ResourcesCompat
 import com.android.systemui.R
-import com.android.systemui.animation.view.LaunchableImageView
 import com.android.systemui.dagger.qualifiers.Main
 import com.android.systemui.flags.FeatureFlags
 import com.android.systemui.flags.Flags
-import com.android.systemui.keyguard.shared.model.KeyguardSection
 import com.android.systemui.keyguard.ui.binder.KeyguardQuickAffordanceViewBinder
 import com.android.systemui.keyguard.ui.viewmodel.KeyguardQuickAffordancesCombinedViewModel
 import com.android.systemui.keyguard.ui.viewmodel.KeyguardRootViewModel
@@ -53,10 +48,7 @@
     private val falsingManager: FalsingManager,
     private val indicationController: KeyguardIndicationController,
     private val vibratorHelper: VibratorHelper,
-) : KeyguardSection() {
-    private var leftShortcutHandle: KeyguardQuickAffordanceViewBinder.Binding? = null
-    private var rightShortcutHandle: KeyguardQuickAffordanceViewBinder.Binding? = null
-
+) : BaseShortcutSection() {
     override fun addViews(constraintLayout: ConstraintLayout) {
         if (featureFlags.isEnabled(Flags.MIGRATE_SPLIT_KEYGUARD_BOTTOM_AREA)) {
             addLeftShortcut(constraintLayout)
@@ -109,67 +101,4 @@
             connect(R.id.end_button, BOTTOM, R.id.lock_icon_view, BOTTOM)
         }
     }
-
-    override fun removeViews(constraintLayout: ConstraintLayout) {
-        leftShortcutHandle?.destroy()
-        rightShortcutHandle?.destroy()
-        constraintLayout.removeView(R.id.start_button)
-        constraintLayout.removeView(R.id.end_button)
-    }
-
-    private fun addLeftShortcut(constraintLayout: ConstraintLayout) {
-        val padding =
-            constraintLayout.resources.getDimensionPixelSize(
-                R.dimen.keyguard_affordance_fixed_padding
-            )
-        val view =
-            LaunchableImageView(constraintLayout.context, null).apply {
-                id = R.id.start_button
-                scaleType = ImageView.ScaleType.FIT_CENTER
-                background =
-                    ResourcesCompat.getDrawable(
-                        context.resources,
-                        R.drawable.keyguard_bottom_affordance_bg,
-                        context.theme
-                    )
-                foreground =
-                    ResourcesCompat.getDrawable(
-                        context.resources,
-                        R.drawable.keyguard_bottom_affordance_selected_border,
-                        context.theme
-                    )
-                visibility = View.INVISIBLE
-                setPadding(padding, padding, padding, padding)
-            }
-        constraintLayout.addView(view)
-    }
-
-    private fun addRightShortcut(constraintLayout: ConstraintLayout) {
-        if (constraintLayout.findViewById<View>(R.id.end_button) != null) return
-
-        val padding =
-            constraintLayout.resources.getDimensionPixelSize(
-                R.dimen.keyguard_affordance_fixed_padding
-            )
-        val view =
-            LaunchableImageView(constraintLayout.context, null).apply {
-                id = R.id.end_button
-                scaleType = ImageView.ScaleType.FIT_CENTER
-                background =
-                    ResourcesCompat.getDrawable(
-                        context.resources,
-                        R.drawable.keyguard_bottom_affordance_bg,
-                        context.theme
-                    )
-                foreground =
-                    ResourcesCompat.getDrawable(
-                        context.resources,
-                        R.drawable.keyguard_bottom_affordance_selected_border,
-                        context.theme
-                    )
-                visibility = View.INVISIBLE
-                setPadding(padding, padding, padding, padding)
-            }
-        constraintLayout.addView(view)
-    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/BaseShortcutSection.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/BaseShortcutSection.kt
new file mode 100644
index 0000000..d046a19
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/BaseShortcutSection.kt
@@ -0,0 +1,99 @@
+package com.android.systemui.keyguard.ui.view.layout.sections
+
+import android.view.View
+import android.widget.ImageView
+import androidx.constraintlayout.widget.ConstraintLayout
+import androidx.core.content.res.ResourcesCompat
+import com.android.systemui.R
+import com.android.systemui.animation.view.LaunchableImageView
+import com.android.systemui.keyguard.shared.model.KeyguardSection
+import com.android.systemui.keyguard.ui.binder.KeyguardQuickAffordanceViewBinder
+
+abstract class BaseShortcutSection : KeyguardSection() {
+    protected var leftShortcutHandle: KeyguardQuickAffordanceViewBinder.Binding? = null
+    protected var rightShortcutHandle: KeyguardQuickAffordanceViewBinder.Binding? = null
+
+    override fun removeViews(constraintLayout: ConstraintLayout) {
+        leftShortcutHandle?.destroy()
+        rightShortcutHandle?.destroy()
+        constraintLayout.removeView(R.id.start_button)
+        constraintLayout.removeView(R.id.end_button)
+    }
+
+    protected fun addLeftShortcut(constraintLayout: ConstraintLayout) {
+        val padding =
+            constraintLayout.resources.getDimensionPixelSize(
+                R.dimen.keyguard_affordance_fixed_padding
+            )
+        val view =
+            LaunchableImageView(constraintLayout.context, null).apply {
+                id = R.id.start_button
+                scaleType = ImageView.ScaleType.FIT_CENTER
+                background =
+                    ResourcesCompat.getDrawable(
+                        context.resources,
+                        R.drawable.keyguard_bottom_affordance_bg,
+                        context.theme
+                    )
+                foreground =
+                    ResourcesCompat.getDrawable(
+                        context.resources,
+                        R.drawable.keyguard_bottom_affordance_selected_border,
+                        context.theme
+                    )
+                visibility = View.INVISIBLE
+                setPadding(padding, padding, padding, padding)
+            }
+        constraintLayout.addView(view)
+    }
+
+    protected fun addRightShortcut(constraintLayout: ConstraintLayout) {
+        if (constraintLayout.findViewById<View>(R.id.end_button) != null) return
+
+        val padding =
+            constraintLayout.resources.getDimensionPixelSize(
+                R.dimen.keyguard_affordance_fixed_padding
+            )
+        val view =
+            LaunchableImageView(constraintLayout.context, null).apply {
+                id = R.id.end_button
+                scaleType = ImageView.ScaleType.FIT_CENTER
+                background =
+                    ResourcesCompat.getDrawable(
+                        context.resources,
+                        R.drawable.keyguard_bottom_affordance_bg,
+                        context.theme
+                    )
+                foreground =
+                    ResourcesCompat.getDrawable(
+                        context.resources,
+                        R.drawable.keyguard_bottom_affordance_selected_border,
+                        context.theme
+                    )
+                visibility = View.INVISIBLE
+                setPadding(padding, padding, padding, padding)
+            }
+        constraintLayout.addView(view)
+    }
+    /**
+     * Defines equality as same class.
+     *
+     * This is to enable set operations to be done as an optimization to blueprint transitions.
+     */
+    override fun equals(other: Any?): Boolean {
+        return other is BaseShortcutSection
+    }
+
+    /**
+     * Defines hashcode as class.
+     *
+     * This is to enable set operations to be done as an optimization to blueprint transitions.
+     */
+    override fun hashCode(): Int {
+        return KEY.hashCode()
+    }
+
+    companion object {
+        private const val KEY = "shortcuts"
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultShortcutsSection.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultShortcutsSection.kt
index a2db1df..13ef985 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultShortcutsSection.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultShortcutsSection.kt
@@ -18,21 +18,16 @@
 package com.android.systemui.keyguard.ui.view.layout.sections
 
 import android.content.res.Resources
-import android.view.View
-import android.widget.ImageView
 import androidx.constraintlayout.widget.ConstraintLayout
 import androidx.constraintlayout.widget.ConstraintSet
 import androidx.constraintlayout.widget.ConstraintSet.BOTTOM
 import androidx.constraintlayout.widget.ConstraintSet.LEFT
 import androidx.constraintlayout.widget.ConstraintSet.PARENT_ID
 import androidx.constraintlayout.widget.ConstraintSet.RIGHT
-import androidx.core.content.res.ResourcesCompat
 import com.android.systemui.R
-import com.android.systemui.animation.view.LaunchableImageView
 import com.android.systemui.dagger.qualifiers.Main
 import com.android.systemui.flags.FeatureFlags
 import com.android.systemui.flags.Flags
-import com.android.systemui.keyguard.shared.model.KeyguardSection
 import com.android.systemui.keyguard.ui.binder.KeyguardQuickAffordanceViewBinder
 import com.android.systemui.keyguard.ui.viewmodel.KeyguardQuickAffordancesCombinedViewModel
 import com.android.systemui.keyguard.ui.viewmodel.KeyguardRootViewModel
@@ -52,10 +47,7 @@
     private val falsingManager: FalsingManager,
     private val indicationController: KeyguardIndicationController,
     private val vibratorHelper: VibratorHelper,
-) : KeyguardSection() {
-    private var leftShortcutHandle: KeyguardQuickAffordanceViewBinder.Binding? = null
-    private var rightShortcutHandle: KeyguardQuickAffordanceViewBinder.Binding? = null
-
+) : BaseShortcutSection() {
     override fun addViews(constraintLayout: ConstraintLayout) {
         if (featureFlags.isEnabled(Flags.MIGRATE_SPLIT_KEYGUARD_BOTTOM_AREA)) {
             addLeftShortcut(constraintLayout)
@@ -108,67 +100,4 @@
             connect(R.id.end_button, BOTTOM, PARENT_ID, BOTTOM, verticalOffsetMargin)
         }
     }
-
-    override fun removeViews(constraintLayout: ConstraintLayout) {
-        leftShortcutHandle?.destroy()
-        rightShortcutHandle?.destroy()
-        constraintLayout.removeView(R.id.start_button)
-        constraintLayout.removeView(R.id.end_button)
-    }
-
-    private fun addLeftShortcut(constraintLayout: ConstraintLayout) {
-        val padding =
-            constraintLayout.resources.getDimensionPixelSize(
-                R.dimen.keyguard_affordance_fixed_padding
-            )
-        val view =
-            LaunchableImageView(constraintLayout.context, null).apply {
-                id = R.id.start_button
-                scaleType = ImageView.ScaleType.FIT_CENTER
-                background =
-                    ResourcesCompat.getDrawable(
-                        context.resources,
-                        R.drawable.keyguard_bottom_affordance_bg,
-                        context.theme
-                    )
-                foreground =
-                    ResourcesCompat.getDrawable(
-                        context.resources,
-                        R.drawable.keyguard_bottom_affordance_selected_border,
-                        context.theme
-                    )
-                visibility = View.INVISIBLE
-                setPadding(padding, padding, padding, padding)
-            }
-        constraintLayout.addView(view)
-    }
-
-    private fun addRightShortcut(constraintLayout: ConstraintLayout) {
-        if (constraintLayout.findViewById<View>(R.id.end_button) != null) return
-
-        val padding =
-            constraintLayout.resources.getDimensionPixelSize(
-                R.dimen.keyguard_affordance_fixed_padding
-            )
-        val view =
-            LaunchableImageView(constraintLayout.context, null).apply {
-                id = R.id.end_button
-                scaleType = ImageView.ScaleType.FIT_CENTER
-                background =
-                    ResourcesCompat.getDrawable(
-                        context.resources,
-                        R.drawable.keyguard_bottom_affordance_bg,
-                        context.theme
-                    )
-                foreground =
-                    ResourcesCompat.getDrawable(
-                        context.resources,
-                        R.drawable.keyguard_bottom_affordance_selected_border,
-                        context.theme
-                    )
-                visibility = View.INVISIBLE
-                setPadding(padding, padding, padding, padding)
-            }
-        constraintLayout.addView(view)
-    }
 }
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/screenshot/RequestProcessor.kt b/packages/SystemUI/src/com/android/systemui/screenshot/RequestProcessor.kt
index 98f2fee..5154067 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/RequestProcessor.kt
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/RequestProcessor.kt
@@ -16,10 +16,8 @@
 
 package com.android.systemui.screenshot
 
-import android.graphics.Insets
 import android.util.Log
 import android.view.WindowManager.TAKE_SCREENSHOT_PROVIDED_IMAGE
-import com.android.internal.util.ScreenshotRequest
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.flags.FeatureFlags
@@ -49,64 +47,6 @@
         /** For the Java Async version, to invoke the callback. */
         @Application private val mainScope: CoroutineScope
 ) : ScreenshotRequestProcessor {
-    /**
-     * Inspects the incoming request, returning a potentially modified request depending on policy.
-     *
-     * @param request the request to process
-     */
-    // TODO: Delete once SCREENSHOT_METADATA flag is launched
-    suspend fun process(request: ScreenshotRequest): ScreenshotRequest {
-        var result = request
-
-        // Apply work profile screenshots policy:
-        //
-        // If the focused app belongs to a work profile, transforms a full screen
-        // (or partial) screenshot request to a task snapshot (provided image) screenshot.
-
-        // Whenever displayContentInfo is fetched, the topComponent is also populated
-        // regardless of the managed profile status.
-
-        if (request.type != TAKE_SCREENSHOT_PROVIDED_IMAGE) {
-            val info = policy.findPrimaryContent(policy.getDefaultDisplayId())
-            Log.d(TAG, "findPrimaryContent: $info")
-
-            result = if (policy.isManagedProfile(info.user.identifier)) {
-                val image = capture.captureTask(info.taskId)
-                        ?: error("Task snapshot returned a null Bitmap!")
-
-                // Provide the task snapshot as the screenshot
-                ScreenshotRequest.Builder(TAKE_SCREENSHOT_PROVIDED_IMAGE, request.source)
-                        .setTopComponent(info.component)
-                        .setTaskId(info.taskId)
-                        .setUserId(info.user.identifier)
-                        .setBitmap(image)
-                        .setBoundsOnScreen(info.bounds)
-                        .setInsets(Insets.NONE)
-                        .build()
-            } else {
-                // Create a new request of the same type which includes the top component
-                ScreenshotRequest.Builder(request.type, request.source)
-                        .setTopComponent(info.component).build()
-            }
-        }
-
-        return result
-    }
-
-    /**
-     * Note: This is for compatibility with existing Java. Prefer the suspending function when
-     * calling from a Coroutine context.
-     *
-     * @param request the request to process
-     * @param callback the callback to provide the processed request, invoked from the main thread
-     */
-    // TODO: Delete once SCREENSHOT_METADATA flag is launched
-    fun processAsync(request: ScreenshotRequest, callback: Consumer<ScreenshotRequest>) {
-        mainScope.launch {
-            val result = process(request)
-            callback.accept(result)
-        }
-    }
 
     override suspend fun process(screenshot: ScreenshotData): ScreenshotData {
         var result = screenshot
diff --git a/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotExecutor.kt b/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotExecutor.kt
index 6c886fc..c5bc2fb 100644
--- a/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotExecutor.kt
+++ b/packages/SystemUI/src/com/android/systemui/screenshot/TakeScreenshotExecutor.kt
@@ -4,6 +4,7 @@
 import android.os.Trace
 import android.util.Log
 import android.view.Display
+import android.view.WindowManager.TAKE_SCREENSHOT_PROVIDED_IMAGE
 import com.android.internal.logging.UiEventLogger
 import com.android.internal.util.ScreenshotRequest
 import com.android.systemui.dagger.SysUISingleton
@@ -55,7 +56,7 @@
         onSaved: (Uri) -> Unit,
         requestCallback: RequestCallback
     ) {
-        val displayIds = getDisplaysToScreenshot()
+        val displayIds = getDisplaysToScreenshot(screenshotRequest.type)
         val resultCallbackWrapper = MultiResultCallbackWrapper(requestCallback)
         screenshotRequest.oneForEachDisplay(displayIds).forEach { screenshotData: ScreenshotData ->
             dispatchToController(
@@ -93,8 +94,13 @@
             .handleScreenshot(screenshotData, onSaved, callback)
     }
 
-    private fun getDisplaysToScreenshot(): List<Int> {
-        return displays.value.filter { it.type in ALLOWED_DISPLAY_TYPES }.map { it.displayId }
+    private fun getDisplaysToScreenshot(requestType: Int): List<Int> {
+        return if (requestType == TAKE_SCREENSHOT_PROVIDED_IMAGE) {
+            // If this is a provided image, let's show the UI on the default display only.
+            listOf(Display.DEFAULT_DISPLAY)
+        } else {
+            displays.value.filter { it.type in ALLOWED_DISPLAY_TYPES }.map { it.displayId }
+        }
     }
 
     /**
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowControllerImpl.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowControllerImpl.java
index 4a76dd0..2dbcbc9 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationShadeWindowControllerImpl.java
@@ -40,6 +40,7 @@
 import android.view.IWindowSession;
 import android.view.View;
 import android.view.ViewGroup;
+import android.view.WindowInsets;
 import android.view.WindowManager;
 import android.view.WindowManager.LayoutParams;
 import android.view.WindowManagerGlobal;
@@ -409,9 +410,9 @@
     private void applyForceShowNavigationFlag(NotificationShadeWindowState state) {
         if (state.panelExpanded || state.bouncerShowing
                 || ENABLE_REMOTE_INPUT && state.remoteInputActive) {
-            mLpChanged.privateFlags |= LayoutParams.PRIVATE_FLAG_STATUS_FORCE_SHOW_NAVIGATION;
+            mLpChanged.forciblyShownTypes |= WindowInsets.Type.navigationBars();
         } else {
-            mLpChanged.privateFlags &= ~LayoutParams.PRIVATE_FLAG_STATUS_FORCE_SHOW_NAVIGATION;
+            mLpChanged.forciblyShownTypes &= ~WindowInsets.Type.navigationBars();
         }
     }
 
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/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/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/notification/stack/ui/viewmodel/SharedNotificationContainerViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModel.kt
index 6f4adeb..f750fed 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewmodel/SharedNotificationContainerViewModel.kt
@@ -44,7 +44,13 @@
     shadeInteractor: ShadeInteractor,
 ) {
     private val statesForConstrainedNotifications =
-        setOf(KeyguardState.LOCKSCREEN, KeyguardState.AOD, KeyguardState.DOZING)
+        setOf(
+            KeyguardState.LOCKSCREEN,
+            KeyguardState.AOD,
+            KeyguardState.DOZING,
+            KeyguardState.ALTERNATE_BOUNCER,
+            KeyguardState.PRIMARY_BOUNCER
+        )
 
     val configurationBasedDimensions: Flow<ConfigurationBasedDimensions> =
         interactor.configurationBasedDimensions
@@ -126,6 +132,7 @@
     /**
      * When on keyguard, there is limited space to display notifications so calculate how many could
      * be shown. Otherwise, there is no limit since the vertical space will be scrollable.
+     *
      * TODO: b/296606746 - Need to rerun logic when notifs change
      */
     val maxNotifications: Flow<Int> =
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 67c0c94..b59e3e9 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfaces.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfaces.java
@@ -295,8 +295,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 98ba6d9..ba32fd3 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesEmptyImpl.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/CentralSurfacesEmptyImpl.kt
@@ -81,7 +81,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 b45a688..30e139f 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()
@@ -2883,7 +2881,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())
@@ -2999,12 +2998,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/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/src/com/android/systemui/statusbar/window/StatusBarWindowController.java b/packages/SystemUI/src/com/android/systemui/statusbar/window/StatusBarWindowController.java
index 24987ab..f4cc0ed 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/window/StatusBarWindowController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/window/StatusBarWindowController.java
@@ -21,7 +21,6 @@
 import static android.view.WindowInsets.Type.tappableElement;
 import static android.view.WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
 import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_COLOR_SPACE_AGNOSTIC;
-import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_FORCE_SHOW_STATUS_BAR;
 
 import static com.android.systemui.util.leak.RotationUtils.ROTATION_LANDSCAPE;
 import static com.android.systemui.util.leak.RotationUtils.ROTATION_NONE;
@@ -44,6 +43,7 @@
 import android.view.Surface;
 import android.view.View;
 import android.view.ViewGroup;
+import android.view.WindowInsets;
 import android.view.WindowManager;
 
 import com.android.internal.policy.SystemBarUtils;
@@ -361,9 +361,9 @@
                 || state.mIsLaunchAnimationRunning
                 // Don't force-show the status bar if the user has already dismissed it.
                 || state.mOngoingProcessRequiresStatusBarVisible) {
-            mLpChanged.privateFlags |= PRIVATE_FLAG_FORCE_SHOW_STATUS_BAR;
+            mLpChanged.forciblyShownTypes |= WindowInsets.Type.statusBars();
         } else {
-            mLpChanged.privateFlags &= ~PRIVATE_FLAG_FORCE_SHOW_STATUS_BAR;
+            mLpChanged.forciblyShownTypes &= ~WindowInsets.Type.statusBars();
         }
     }
 }
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/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/accessibility/WindowMagnificationControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationControllerTest.java
index 0fb0b03..39fe6fb 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/WindowMagnificationControllerTest.java
@@ -751,6 +751,245 @@
     }
 
     @Test
+    public void windowWidthIsNotMax_performA11yActionIncreaseWidth_windowWidthIncreased() {
+        final Rect windowBounds = mWindowManager.getCurrentWindowMetrics().getBounds();
+        final int startingWidth = (int) (windowBounds.width() * 0.8);
+        final int startingHeight = (int) (windowBounds.height() * 0.8);
+        final float changeWindowSizeAmount = mContext.getResources().getFraction(
+                R.fraction.magnification_resize_window_size_amount,
+                /* base= */ 1,
+                /* pbase= */ 1);
+
+        mInstrumentation.runOnMainSync(() -> {
+            mWindowMagnificationController.enableWindowMagnificationInternal(Float.NaN, Float.NaN,
+                    Float.NaN);
+            mWindowMagnificationController.setWindowSize(startingWidth, startingHeight);
+            mWindowMagnificationController.setEditMagnifierSizeMode(true);
+        });
+
+        final View mirrorView = mWindowManager.getAttachedView();
+        final AtomicInteger actualWindowHeight = new AtomicInteger();
+        final AtomicInteger actualWindowWidth = new AtomicInteger();
+
+        mInstrumentation.runOnMainSync(
+                () -> {
+                    mirrorView.performAccessibilityAction(
+                            R.id.accessibility_action_increase_window_width, null);
+                    actualWindowHeight.set(mWindowManager.getLayoutParamsFromAttachedView().height);
+                    actualWindowWidth.set(mWindowManager.getLayoutParamsFromAttachedView().width);
+                });
+
+        final int mirrorSurfaceMargin = mResources.getDimensionPixelSize(
+                R.dimen.magnification_mirror_surface_margin);
+        // Window width includes the magnifier frame and the margin. Increasing the window size
+        // will be increasing the amount of the frame size only.
+        int newWindowWidth =
+                (int) ((startingWidth - 2 * mirrorSurfaceMargin) * (1 + changeWindowSizeAmount))
+                        + 2 * mirrorSurfaceMargin;
+        assertEquals(newWindowWidth, actualWindowWidth.get());
+        assertEquals(startingHeight, actualWindowHeight.get());
+    }
+
+    @Test
+    public void windowHeightIsNotMax_performA11yActionIncreaseHeight_windowHeightIncreased() {
+        final Rect windowBounds = mWindowManager.getCurrentWindowMetrics().getBounds();
+        final int startingWidth = (int) (windowBounds.width() * 0.8);
+        final int startingHeight = (int) (windowBounds.height() * 0.8);
+        final float changeWindowSizeAmount = mContext.getResources().getFraction(
+                R.fraction.magnification_resize_window_size_amount,
+                /* base= */ 1,
+                /* pbase= */ 1);
+
+        mInstrumentation.runOnMainSync(() -> {
+            mWindowMagnificationController.enableWindowMagnificationInternal(Float.NaN, Float.NaN,
+                    Float.NaN);
+            mWindowMagnificationController.setWindowSize(startingWidth, startingHeight);
+            mWindowMagnificationController.setEditMagnifierSizeMode(true);
+        });
+
+        final View mirrorView = mWindowManager.getAttachedView();
+        final AtomicInteger actualWindowHeight = new AtomicInteger();
+        final AtomicInteger actualWindowWidth = new AtomicInteger();
+
+        mInstrumentation.runOnMainSync(
+                () -> {
+                    mirrorView.performAccessibilityAction(
+                            R.id.accessibility_action_increase_window_height, null);
+                    actualWindowHeight.set(mWindowManager.getLayoutParamsFromAttachedView().height);
+                    actualWindowWidth.set(mWindowManager.getLayoutParamsFromAttachedView().width);
+                });
+
+        final int mirrorSurfaceMargin = mResources.getDimensionPixelSize(
+                R.dimen.magnification_mirror_surface_margin);
+        // Window height includes the magnifier frame and the margin. Increasing the window size
+        // will be increasing the amount of the frame size only.
+        int newWindowHeight =
+                (int) ((startingHeight - 2 * mirrorSurfaceMargin) * (1 + changeWindowSizeAmount))
+                        + 2 * mirrorSurfaceMargin;
+        assertEquals(startingWidth, actualWindowWidth.get());
+        assertEquals(newWindowHeight, actualWindowHeight.get());
+    }
+
+    @Test
+    public void windowWidthIsMax_noIncreaseWindowWidthA11yAction() {
+        final Rect windowBounds = mWindowManager.getCurrentWindowMetrics().getBounds();
+        final int startingWidth = windowBounds.width();
+        final int startingHeight = windowBounds.height();
+
+        mInstrumentation.runOnMainSync(() -> {
+            mWindowMagnificationController.enableWindowMagnificationInternal(Float.NaN, Float.NaN,
+                    Float.NaN);
+            mWindowMagnificationController.setWindowSize(startingWidth, startingHeight);
+            mWindowMagnificationController.setEditMagnifierSizeMode(true);
+        });
+
+        final View mirrorView = mWindowManager.getAttachedView();
+        final AccessibilityNodeInfo accessibilityNodeInfo =
+                mirrorView.createAccessibilityNodeInfo();
+        assertFalse(accessibilityNodeInfo.getActionList().contains(
+                new AccessibilityAction(R.id.accessibility_action_increase_window_width, null)));
+    }
+
+    @Test
+    public void windowHeightIsMax_noIncreaseWindowHeightA11yAction() {
+        final Rect windowBounds = mWindowManager.getCurrentWindowMetrics().getBounds();
+        final int startingWidth = windowBounds.width();
+        final int startingHeight = windowBounds.height();
+
+        mInstrumentation.runOnMainSync(() -> {
+            mWindowMagnificationController.enableWindowMagnificationInternal(Float.NaN, Float.NaN,
+                    Float.NaN);
+            mWindowMagnificationController.setWindowSize(startingWidth, startingHeight);
+            mWindowMagnificationController.setEditMagnifierSizeMode(true);
+        });
+
+        final View mirrorView = mWindowManager.getAttachedView();
+        final AccessibilityNodeInfo accessibilityNodeInfo =
+                mirrorView.createAccessibilityNodeInfo();
+        assertFalse(accessibilityNodeInfo.getActionList().contains(
+                new AccessibilityAction(R.id.accessibility_action_increase_window_height, null)));
+    }
+
+    @Test
+    public void windowWidthIsNotMin_performA11yActionDecreaseWidth_windowWidthDecreased() {
+        int mMinWindowSize = mResources.getDimensionPixelSize(
+                com.android.internal.R.dimen.accessibility_window_magnifier_min_size);
+        final int startingSize = (int) (mMinWindowSize * 1.1);
+        final float changeWindowSizeAmount = mContext.getResources().getFraction(
+                R.fraction.magnification_resize_window_size_amount,
+                /* base= */ 1,
+                /* pbase= */ 1);
+        mInstrumentation.runOnMainSync(() -> {
+            mWindowMagnificationController.enableWindowMagnificationInternal(Float.NaN, Float.NaN,
+                    Float.NaN);
+            mWindowMagnificationController.setWindowSize(startingSize, startingSize);
+            mWindowMagnificationController.setEditMagnifierSizeMode(true);
+        });
+
+        final View mirrorView = mWindowManager.getAttachedView();
+        final AtomicInteger actualWindowHeight = new AtomicInteger();
+        final AtomicInteger actualWindowWidth = new AtomicInteger();
+
+        mInstrumentation.runOnMainSync(
+                () -> {
+                    mirrorView.performAccessibilityAction(
+                            R.id.accessibility_action_decrease_window_width, null);
+                    actualWindowHeight.set(mWindowManager.getLayoutParamsFromAttachedView().height);
+                    actualWindowWidth.set(mWindowManager.getLayoutParamsFromAttachedView().width);
+                });
+
+        final int mirrorSurfaceMargin = mResources.getDimensionPixelSize(
+                R.dimen.magnification_mirror_surface_margin);
+        // Window width includes the magnifier frame and the margin. Decreasing the window size
+        // will be decreasing the amount of the frame size only.
+        int newWindowWidth =
+                (int) ((startingSize - 2 * mirrorSurfaceMargin) * (1 - changeWindowSizeAmount))
+                        + 2 * mirrorSurfaceMargin;
+        assertEquals(newWindowWidth, actualWindowWidth.get());
+        assertEquals(startingSize, actualWindowHeight.get());
+    }
+
+    @Test
+    public void windowHeightIsNotMin_performA11yActionDecreaseHeight_windowHeightDecreased() {
+        int mMinWindowSize = mResources.getDimensionPixelSize(
+                com.android.internal.R.dimen.accessibility_window_magnifier_min_size);
+        final int startingSize = (int) (mMinWindowSize * 1.1);
+        final float changeWindowSizeAmount = mContext.getResources().getFraction(
+                R.fraction.magnification_resize_window_size_amount,
+                /* base= */ 1,
+                /* pbase= */ 1);
+
+        mInstrumentation.runOnMainSync(() -> {
+            mWindowMagnificationController.enableWindowMagnificationInternal(Float.NaN, Float.NaN,
+                    Float.NaN);
+            mWindowMagnificationController.setWindowSize(startingSize, startingSize);
+            mWindowMagnificationController.setEditMagnifierSizeMode(true);
+        });
+
+        final View mirrorView = mWindowManager.getAttachedView();
+        final AtomicInteger actualWindowHeight = new AtomicInteger();
+        final AtomicInteger actualWindowWidth = new AtomicInteger();
+
+        mInstrumentation.runOnMainSync(
+                () -> {
+                    mirrorView.performAccessibilityAction(
+                            R.id.accessibility_action_decrease_window_height, null);
+                    actualWindowHeight.set(mWindowManager.getLayoutParamsFromAttachedView().height);
+                    actualWindowWidth.set(mWindowManager.getLayoutParamsFromAttachedView().width);
+                });
+
+        final int mirrorSurfaceMargin = mResources.getDimensionPixelSize(
+                R.dimen.magnification_mirror_surface_margin);
+        // Window height includes the magnifier frame and the margin. Decreasing the window size
+        // will be decreasing the amount of the frame size only.
+        int newWindowHeight =
+                (int) ((startingSize - 2 * mirrorSurfaceMargin) * (1 - changeWindowSizeAmount))
+                        + 2 * mirrorSurfaceMargin;
+        assertEquals(startingSize, actualWindowWidth.get());
+        assertEquals(newWindowHeight, actualWindowHeight.get());
+    }
+
+    @Test
+    public void windowWidthIsMin_noDecreaseWindowWidthA11yAction() {
+        int mMinWindowSize = mResources.getDimensionPixelSize(
+                com.android.internal.R.dimen.accessibility_window_magnifier_min_size);
+        final int startingSize = mMinWindowSize;
+
+        mInstrumentation.runOnMainSync(() -> {
+            mWindowMagnificationController.enableWindowMagnificationInternal(Float.NaN, Float.NaN,
+                    Float.NaN);
+            mWindowMagnificationController.setWindowSize(startingSize, startingSize);
+            mWindowMagnificationController.setEditMagnifierSizeMode(true);
+        });
+
+        final View mirrorView = mWindowManager.getAttachedView();
+        final AccessibilityNodeInfo accessibilityNodeInfo =
+                mirrorView.createAccessibilityNodeInfo();
+        assertFalse(accessibilityNodeInfo.getActionList().contains(
+                new AccessibilityAction(R.id.accessibility_action_decrease_window_width, null)));
+    }
+
+    @Test
+    public void windowHeightIsMin_noDecreaseWindowHeightA11yAcyion() {
+        int mMinWindowSize = mResources.getDimensionPixelSize(
+                com.android.internal.R.dimen.accessibility_window_magnifier_min_size);
+        final int startingSize = mMinWindowSize;
+
+        mInstrumentation.runOnMainSync(() -> {
+            mWindowMagnificationController.enableWindowMagnificationInternal(Float.NaN, Float.NaN,
+                    Float.NaN);
+            mWindowMagnificationController.setWindowSize(startingSize, startingSize);
+            mWindowMagnificationController.setEditMagnifierSizeMode(true);
+        });
+
+        final View mirrorView = mWindowManager.getAttachedView();
+        final AccessibilityNodeInfo accessibilityNodeInfo =
+                mirrorView.createAccessibilityNodeInfo();
+        assertFalse(accessibilityNodeInfo.getActionList().contains(
+                new AccessibilityAction(R.id.accessibility_action_decrease_window_height, null)));
+    }
+
+    @Test
     public void enableWindowMagnification_hasA11yWindowTitle() {
         mInstrumentation.runOnMainSync(() -> {
             mWindowMagnificationController.enableWindowMagnificationInternal(Float.NaN, Float.NaN,
@@ -1210,4 +1449,5 @@
         when(mContext.getDisplay()).thenReturn(display);
         return newRotation;
     }
+
 }
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..d10b81c 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
@@ -125,13 +127,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 +136,18 @@
 
     @Before
     fun setup() {
+        displayRepository = FakeDisplayRepository()
         featureFlags.set(Flags.BIOMETRIC_BP_STRONG, useNewBiometricPrompt)
         featureFlags.set(Flags.ONE_WAY_HAPTICS_API_MIGRATION, false)
+
+        displayStateInteractor =
+            DisplayStateInteractorImpl(
+                    testScope.backgroundScope,
+                    mContext,
+                    fakeExecutor,
+                    rearDisplayStateRepository,
+                    displayRepository,
+            )
     }
 
     @After
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/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..6eb637b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerOverlayTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/UdfpsControllerOverlayTest.kt
@@ -50,7 +50,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 +96,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 +143,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..755977f 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;
@@ -98,7 +95,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 +310,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/SystemUI/tests/src/com/android/systemui/biometrics/data/repository/FingerprintRepositoryImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/data/repository/FingerprintRepositoryImplTest.kt
index 239e317..ed9ae5e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/data/repository/FingerprintRepositoryImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/data/repository/FingerprintRepositoryImplTest.kt
@@ -29,6 +29,7 @@
 import com.android.systemui.biometrics.shared.model.SensorStrength
 import com.android.systemui.coroutines.collectLastValue
 import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.test.StandardTestDispatcher
 import kotlinx.coroutines.test.TestScope
 import kotlinx.coroutines.test.runCurrent
@@ -44,6 +45,7 @@
 import org.mockito.Mockito.verify
 import org.mockito.junit.MockitoJUnit
 
+@OptIn(ExperimentalCoroutinesApi::class)
 @SmallTest
 @RunWith(JUnit4::class)
 class FingerprintRepositoryImplTest : SysuiTestCase() {
@@ -73,10 +75,15 @@
     @Test
     fun initializeProperties() =
         testScope.runTest {
-            val isInitialized = collectLastValue(repository.isInitialized)
+            val sensorId by collectLastValue(repository.sensorId)
+            val strength by collectLastValue(repository.strength)
+            val sensorType by collectLastValue(repository.sensorType)
+            val sensorLocations by collectLastValue(repository.sensorLocations)
 
-            assertDefaultProperties()
-            assertThat(isInitialized()).isFalse()
+            // Assert default properties.
+            assertThat(sensorId).isEqualTo(-1)
+            assertThat(strength).isEqualTo(SensorStrength.CONVENIENCE)
+            assertThat(sensorType).isEqualTo(FingerprintSensorType.UNKNOWN)
 
             val fingerprintProps =
                 listOf(
@@ -115,31 +122,24 @@
 
             fingerprintAuthenticatorsCaptor.value.onAllAuthenticatorsRegistered(fingerprintProps)
 
-            assertThat(repository.sensorId.value).isEqualTo(1)
-            assertThat(repository.strength.value).isEqualTo(SensorStrength.STRONG)
-            assertThat(repository.sensorType.value).isEqualTo(FingerprintSensorType.REAR)
+            assertThat(sensorId).isEqualTo(1)
+            assertThat(strength).isEqualTo(SensorStrength.STRONG)
+            assertThat(sensorType).isEqualTo(FingerprintSensorType.REAR)
 
-            assertThat(repository.sensorLocations.value.size).isEqualTo(2)
-            assertThat(repository.sensorLocations.value).containsKey("display_id_1")
-            with(repository.sensorLocations.value["display_id_1"]!!) {
+            assertThat(sensorLocations?.size).isEqualTo(2)
+            assertThat(sensorLocations).containsKey("display_id_1")
+            with(sensorLocations?.get("display_id_1")!!) {
                 assertThat(displayId).isEqualTo("display_id_1")
                 assertThat(sensorLocationX).isEqualTo(100)
                 assertThat(sensorLocationY).isEqualTo(300)
                 assertThat(sensorRadius).isEqualTo(20)
             }
-            assertThat(repository.sensorLocations.value).containsKey("")
-            with(repository.sensorLocations.value[""]!!) {
+            assertThat(sensorLocations).containsKey("")
+            with(sensorLocations?.get("")!!) {
                 assertThat(displayId).isEqualTo("")
                 assertThat(sensorLocationX).isEqualTo(540)
                 assertThat(sensorLocationY).isEqualTo(1636)
                 assertThat(sensorRadius).isEqualTo(130)
             }
-            assertThat(isInitialized()).isTrue()
         }
-
-    private fun assertDefaultProperties() {
-        assertThat(repository.sensorId.value).isEqualTo(-1)
-        assertThat(repository.strength.value).isEqualTo(SensorStrength.CONVENIENCE)
-        assertThat(repository.sensorType.value).isEqualTo(FingerprintSensorType.UNKNOWN)
-    }
 }
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/SideFpsOverlayInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/SideFpsOverlayInteractorTest.kt
index fd96cf4..712eef1 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/SideFpsOverlayInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/SideFpsOverlayInteractorTest.kt
@@ -22,6 +22,7 @@
 import com.android.systemui.biometrics.data.repository.FakeFingerprintPropertyRepository
 import com.android.systemui.biometrics.shared.model.FingerprintSensorType
 import com.android.systemui.biometrics.shared.model.SensorStrength
+import com.android.systemui.coroutines.collectLastValue
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.test.StandardTestDispatcher
 import kotlinx.coroutines.test.TestScope
@@ -51,7 +52,7 @@
     }
 
     @Test
-    fun testGetOverlayOffsets() =
+    fun testOverlayOffsetUpdates() =
         testScope.runTest {
             fingerprintRepository.setProperties(
                 sensorId = 1,
@@ -76,16 +77,32 @@
                     )
             )
 
-            var offsets = interactor.getOverlayOffsets("display_id_1")
-            assertThat(offsets.displayId).isEqualTo("display_id_1")
-            assertThat(offsets.sensorLocationX).isEqualTo(100)
-            assertThat(offsets.sensorLocationY).isEqualTo(300)
-            assertThat(offsets.sensorRadius).isEqualTo(20)
+            val displayId by collectLastValue(interactor.displayId)
+            val offsets by collectLastValue(interactor.overlayOffsets)
 
-            offsets = interactor.getOverlayOffsets("invalid_display_id")
-            assertThat(offsets.displayId).isEqualTo("")
-            assertThat(offsets.sensorLocationX).isEqualTo(540)
-            assertThat(offsets.sensorLocationY).isEqualTo(1636)
-            assertThat(offsets.sensorRadius).isEqualTo(130)
+            // Assert offsets of empty displayId.
+            assertThat(displayId).isEqualTo("")
+            assertThat(offsets?.displayId).isEqualTo("")
+            assertThat(offsets?.sensorLocationX).isEqualTo(540)
+            assertThat(offsets?.sensorLocationY).isEqualTo(1636)
+            assertThat(offsets?.sensorRadius).isEqualTo(130)
+
+            // Offsets should be updated correctly.
+            interactor.onDisplayChanged("display_id_1")
+            assertThat(displayId).isEqualTo("display_id_1")
+            assertThat(offsets?.displayId).isEqualTo("display_id_1")
+            assertThat(offsets?.sensorLocationX).isEqualTo(100)
+            assertThat(offsets?.sensorLocationY).isEqualTo(300)
+            assertThat(offsets?.sensorRadius).isEqualTo(20)
+
+            // Should return default offset when the displayId is invalid.
+            interactor.onDisplayChanged("invalid_display_id")
+            assertThat(displayId).isEqualTo("invalid_display_id")
+            assertThat(offsets?.displayId).isEqualTo(SensorLocationInternal.DEFAULT.displayId)
+            assertThat(offsets?.sensorLocationX)
+                .isEqualTo(SensorLocationInternal.DEFAULT.sensorLocationX)
+            assertThat(offsets?.sensorLocationY)
+                .isEqualTo(SensorLocationInternal.DEFAULT.sensorLocationY)
+            assertThat(offsets?.sensorRadius).isEqualTo(SensorLocationInternal.DEFAULT.sensorRadius)
         }
 }
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..c03fd86 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
@@ -28,6 +28,7 @@
 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
@@ -38,6 +39,7 @@
 import com.android.systemui.biometrics.shared.model.BiometricModality
 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()
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/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/view/layout/KeyguardBlueprintCommandListenerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/KeyguardBlueprintCommandListenerTest.kt
index bb73dc6..dbf6a29 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/KeyguardBlueprintCommandListenerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/KeyguardBlueprintCommandListenerTest.kt
@@ -81,4 +81,10 @@
         command().execute(pw, listOf("fake"))
         verify(keyguardBlueprintInteractor).transitionToBlueprint("fake")
     }
+
+    @Test
+    fun testValidArg_Int() {
+        command().execute(pw, listOf("1"))
+        verify(keyguardBlueprintInteractor).transitionToBlueprint(1)
+    }
 }
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/screenshot/RequestProcessorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/screenshot/RequestProcessorTest.kt
index 1e47f78..0d694ee 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/screenshot/RequestProcessorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/screenshot/RequestProcessorTest.kt
@@ -51,30 +51,6 @@
 
     /** Tests the Java-compatible function wrapper, ensures callback is invoked. */
     @Test
-    fun testProcessAsync() {
-        val request =
-            ScreenshotRequest.Builder(TAKE_SCREENSHOT_PROVIDED_IMAGE, SCREENSHOT_KEY_OTHER)
-                .setBitmap(Bitmap.createBitmap(1, 1, Bitmap.Config.ARGB_8888))
-                .build()
-        val processor = RequestProcessor(imageCapture, policy, flags, scope)
-
-        var result: ScreenshotRequest? = null
-        var callbackCount = 0
-        val callback: (ScreenshotRequest) -> Unit = { processedRequest: ScreenshotRequest ->
-            result = processedRequest
-            callbackCount++
-        }
-
-        // runs synchronously, using Unconfined Dispatcher
-        processor.processAsync(request, callback)
-
-        // Callback invoked once returning the same request (no changes)
-        assertThat(callbackCount).isEqualTo(1)
-        assertThat(result).isEqualTo(request)
-    }
-
-    /** Tests the Java-compatible function wrapper, ensures callback is invoked. */
-    @Test
     fun testProcessAsync_ScreenshotData() {
         val request =
             ScreenshotData.fromRequest(
@@ -112,13 +88,6 @@
             ScreenshotRequest.Builder(TAKE_SCREENSHOT_FULLSCREEN, SCREENSHOT_OTHER).build()
         val processor = RequestProcessor(imageCapture, policy, flags, scope)
 
-        val processedRequest = processor.process(request)
-
-        // Request has topComponent added, but otherwise unchanged.
-        assertThat(processedRequest.type).isEqualTo(TAKE_SCREENSHOT_FULLSCREEN)
-        assertThat(processedRequest.source).isEqualTo(SCREENSHOT_OTHER)
-        assertThat(processedRequest.topComponent).isEqualTo(component)
-
         val processedData = processor.process(ScreenshotData.fromRequest(request))
 
         // Request has topComponent added, but otherwise unchanged.
@@ -144,18 +113,6 @@
             ScreenshotRequest.Builder(TAKE_SCREENSHOT_FULLSCREEN, SCREENSHOT_KEY_OTHER).build()
         val processor = RequestProcessor(imageCapture, policy, flags, scope)
 
-        val processedRequest = processor.process(request)
-
-        // Expect a task snapshot is taken, overriding the full screen mode
-        assertThat(processedRequest.type).isEqualTo(TAKE_SCREENSHOT_PROVIDED_IMAGE)
-        assertThat(bitmap.equalsHardwareBitmap(processedRequest.bitmap)).isTrue()
-        assertThat(processedRequest.boundsInScreen).isEqualTo(bounds)
-        assertThat(processedRequest.insets).isEqualTo(Insets.NONE)
-        assertThat(processedRequest.taskId).isEqualTo(TASK_ID)
-        assertThat(imageCapture.requestedTaskId).isEqualTo(TASK_ID)
-        assertThat(processedRequest.userId).isEqualTo(USER_ID)
-        assertThat(processedRequest.topComponent).isEqualTo(component)
-
         val processedData = processor.process(ScreenshotData.fromRequest(request))
 
         // Expect a task snapshot is taken, overriding the full screen mode
@@ -165,8 +122,6 @@
         assertThat(processedData.insets).isEqualTo(Insets.NONE)
         assertThat(processedData.taskId).isEqualTo(TASK_ID)
         assertThat(imageCapture.requestedTaskId).isEqualTo(TASK_ID)
-        assertThat(processedRequest.userId).isEqualTo(USER_ID)
-        assertThat(processedRequest.topComponent).isEqualTo(component)
     }
 
     @Test
@@ -186,9 +141,6 @@
         val processor = RequestProcessor(imageCapture, policy, flags, scope)
 
         Assert.assertThrows(IllegalStateException::class.java) {
-            runBlocking { processor.process(request) }
-        }
-        Assert.assertThrows(IllegalStateException::class.java) {
             runBlocking { processor.process(ScreenshotData.fromRequest(request)) }
         }
     }
@@ -212,11 +164,6 @@
                 .setInsets(Insets.NONE)
                 .build()
 
-        val processedRequest = processor.process(request)
-
-        // No changes
-        assertThat(processedRequest).isEqualTo(request)
-
         val screenshotData = ScreenshotData.fromRequest(request)
         val processedData = processor.process(screenshotData)
 
@@ -243,14 +190,10 @@
                 .setInsets(Insets.NONE)
                 .build()
 
-        val processedRequest = processor.process(request)
-
-        // Work profile, but already a task snapshot, so no changes
-        assertThat(processedRequest).isEqualTo(request)
-
         val screenshotData = ScreenshotData.fromRequest(request)
         val processedData = processor.process(screenshotData)
 
+        // Work profile, but already a task snapshot, so no changes
         assertThat(processedData).isEqualTo(screenshotData)
     }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/TakeScreenshotExecutorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/screenshot/TakeScreenshotExecutorTest.kt
index 97c2ed4..cfdf66e 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/screenshot/TakeScreenshotExecutorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/screenshot/TakeScreenshotExecutorTest.kt
@@ -1,6 +1,7 @@
 package com.android.systemui.screenshot
 
 import android.content.ComponentName
+import android.graphics.Bitmap
 import android.net.Uri
 import android.testing.AndroidTestingRunner
 import android.view.Display
@@ -10,6 +11,7 @@
 import android.view.Display.TYPE_VIRTUAL
 import android.view.Display.TYPE_WIFI
 import android.view.WindowManager
+import android.view.WindowManager.TAKE_SCREENSHOT_PROVIDED_IMAGE
 import androidx.test.filters.SmallTest
 import com.android.internal.logging.testing.UiEventLoggerFake
 import com.android.internal.util.ScreenshotRequest
@@ -95,6 +97,35 @@
         }
 
     @Test
+    fun executeScreenshots_providedImageType_callsOnlyDefaultDisplayController() =
+        testScope.runTest {
+            setDisplays(display(TYPE_INTERNAL, id = 0), display(TYPE_EXTERNAL, id = 1))
+            val onSaved = { _: Uri -> }
+            screenshotExecutor.executeScreenshots(
+                createScreenshotRequest(TAKE_SCREENSHOT_PROVIDED_IMAGE),
+                onSaved,
+                callback
+            )
+
+            verify(controllerFactory).create(eq(0))
+            verify(controllerFactory, never()).create(eq(1))
+
+            val capturer = ArgumentCaptor<ScreenshotData>()
+
+            verify(controller0).handleScreenshot(capturer.capture(), any(), any())
+            assertThat(capturer.value.displayId).isEqualTo(0)
+            // OnSaved callback should be different.
+            verify(controller1, never()).handleScreenshot(any(), any(), any())
+
+            assertThat(eventLogger.numLogs()).isEqualTo(1)
+            assertThat(eventLogger.get(0).eventId)
+                .isEqualTo(ScreenshotEvent.SCREENSHOT_REQUESTED_KEY_OTHER.id)
+            assertThat(eventLogger.get(0).packageName).isEqualTo(topComponent.packageName)
+
+            screenshotExecutor.onDestroy()
+        }
+
+    @Test
     fun executeScreenshots_onlyVirtualDisplays_noInteractionsWithControllers() =
         testScope.runTest {
             setDisplays(display(TYPE_VIRTUAL, id = 0), display(TYPE_VIRTUAL, id = 1))
@@ -283,12 +314,14 @@
         runCurrent()
     }
 
-    private fun createScreenshotRequest() =
-        ScreenshotRequest.Builder(
-                WindowManager.TAKE_SCREENSHOT_FULLSCREEN,
-                WindowManager.ScreenshotSource.SCREENSHOT_KEY_OTHER
-            )
+    private fun createScreenshotRequest(type: Int = WindowManager.TAKE_SCREENSHOT_FULLSCREEN) =
+        ScreenshotRequest.Builder(type, WindowManager.ScreenshotSource.SCREENSHOT_KEY_OTHER)
             .setTopComponent(topComponent)
+            .also {
+                if (type == TAKE_SCREENSHOT_PROVIDED_IMAGE) {
+                    it.setBitmap(Bitmap.createBitmap(100, 100, Bitmap.Config.ARGB_8888))
+                }
+            }
             .build()
 
     private class FakeRequestProcessor : ScreenshotRequestProcessor {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/TakeScreenshotServiceTest.kt b/packages/SystemUI/tests/src/com/android/systemui/screenshot/TakeScreenshotServiceTest.kt
index a08cda6..6205d90 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/screenshot/TakeScreenshotServiceTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/screenshot/TakeScreenshotServiceTest.kt
@@ -20,10 +20,6 @@
 import android.app.admin.DevicePolicyResources.Strings.SystemUi.SCREENSHOT_BLOCKED_BY_ADMIN
 import android.app.admin.DevicePolicyResourcesManager
 import android.content.ComponentName
-import android.graphics.Bitmap
-import android.graphics.Bitmap.Config.HARDWARE
-import android.graphics.ColorSpace
-import android.hardware.HardwareBuffer
 import android.os.UserHandle
 import android.os.UserManager
 import android.testing.AndroidTestingRunner
@@ -94,14 +90,6 @@
 
         // Stub request processor as a synchronous no-op for tests with the flag enabled
         doAnswer {
-                val request: ScreenshotRequest = it.getArgument(0) as ScreenshotRequest
-                val consumer: Consumer<ScreenshotRequest> = it.getArgument(1)
-                consumer.accept(request)
-            }
-            .whenever(requestProcessor)
-            .processAsync(/* request= */ any(ScreenshotRequest::class.java), /* callback= */ any())
-
-        doAnswer {
                 val request: ScreenshotData = it.getArgument(0) as ScreenshotData
                 val consumer: Consumer<ScreenshotData> = it.getArgument(1)
                 consumer.accept(request)
@@ -353,23 +341,3 @@
         return service
     }
 }
-
-private fun Bitmap.equalsHardwareBitmap(other: Bitmap): Boolean {
-    return config == HARDWARE &&
-        other.config == HARDWARE &&
-        hardwareBuffer == other.hardwareBuffer &&
-        colorSpace == other.colorSpace
-}
-
-/** A hardware Bitmap is mandated by use of ScreenshotHelper.HardwareBitmapBundler */
-private fun makeHardwareBitmap(width: Int, height: Int): Bitmap {
-    val buffer =
-        HardwareBuffer.create(
-            width,
-            height,
-            HardwareBuffer.RGBA_8888,
-            1,
-            HardwareBuffer.USAGE_GPU_SAMPLED_IMAGE
-        )
-    return Bitmap.wrapHardwareBuffer(buffer, ColorSpace.get(ColorSpace.Named.SRGB))!!
-}
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 75fb22d..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,
@@ -220,6 +226,14 @@
                 )
             )
             assertThat(isOnLockscreen).isTrue()
+
+            keyguardTransitionRepository.sendTransitionStep(
+                TransitionStep(
+                    to = KeyguardState.PRIMARY_BOUNCER,
+                    transitionState = TransitionState.FINISHED
+                )
+            )
+            assertThat(isOnLockscreen).isTrue()
         }
 
     @Test
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/biometrics/data/repository/FakeFingerprintPropertyRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/data/repository/FakeFingerprintPropertyRepository.kt
index 2362a52..0c5e438 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/data/repository/FakeFingerprintPropertyRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/data/repository/FakeFingerprintPropertyRepository.kt
@@ -20,16 +20,12 @@
 import com.android.systemui.biometrics.shared.model.FingerprintSensorType
 import com.android.systemui.biometrics.shared.model.SensorStrength
 import kotlinx.coroutines.flow.MutableStateFlow
-import kotlinx.coroutines.flow.StateFlow
 import kotlinx.coroutines.flow.asStateFlow
 
 class FakeFingerprintPropertyRepository : FingerprintPropertyRepository {
 
-    private val _isInitialized: MutableStateFlow<Boolean> = MutableStateFlow(false)
-    override val isInitialized = _isInitialized.asStateFlow()
-
     private val _sensorId: MutableStateFlow<Int> = MutableStateFlow(-1)
-    override val sensorId: StateFlow<Int> = _sensorId.asStateFlow()
+    override val sensorId = _sensorId.asStateFlow()
 
     private val _strength: MutableStateFlow<SensorStrength> =
         MutableStateFlow(SensorStrength.CONVENIENCE)
@@ -37,12 +33,11 @@
 
     private val _sensorType: MutableStateFlow<FingerprintSensorType> =
         MutableStateFlow(FingerprintSensorType.UNKNOWN)
-    override val sensorType: StateFlow<FingerprintSensorType> = _sensorType.asStateFlow()
+    override val sensorType = _sensorType.asStateFlow()
 
     private val _sensorLocations: MutableStateFlow<Map<String, SensorLocationInternal>> =
         MutableStateFlow(mapOf("" to SensorLocationInternal.DEFAULT))
-    override val sensorLocations: StateFlow<Map<String, SensorLocationInternal>> =
-        _sensorLocations.asStateFlow()
+    override val sensorLocations = _sensorLocations.asStateFlow()
 
     fun setProperties(
         sensorId: Int,
@@ -54,6 +49,5 @@
         _strength.value = strength
         _sensorType.value = sensorType
         _sensorLocations.value = sensorLocations
-        _isInitialized.value = true
     }
 }
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/AutofillManagerServiceShellCommand.java b/services/autofill/java/com/android/server/autofill/AutofillManagerServiceShellCommand.java
index 39756df..cae047f 100644
--- a/services/autofill/java/com/android/server/autofill/AutofillManagerServiceShellCommand.java
+++ b/services/autofill/java/com/android/server/autofill/AutofillManagerServiceShellCommand.java
@@ -18,7 +18,6 @@
 
 import static android.service.autofill.AutofillFieldClassificationService.EXTRA_SCORES;
 import static android.service.autofill.AutofillService.EXTRA_RESULT;
-
 import static com.android.server.autofill.AutofillManagerService.RECEIVER_BUNDLE_EXTRA_SESSIONS;
 
 import android.os.Bundle;
@@ -155,20 +154,31 @@
             pw.println("");
         }
 
+        Method[] flagMethods = {};
+
         try {
-            Method[] flagMethods = Flags.class.getMethods();
-            // For some reason, unreferenced flags do not show up here
-            // Maybe compiler optomized them out of bytecode?
-            for (Method method : flagMethods) {
-                if (Modifier.isPublic(method.getModifiers())) {
-                    pw.println(method.getName() + ": " + method.invoke(null));
-                }
-            }
-        } catch (Exception ex) {
-            pw.println(ex);
+            flagMethods = Flags.class.getDeclaredMethods();
+        } catch (SecurityException ex) {
+            ex.printStackTrace(pw);
             return -1;
         }
 
+        // For some reason, unreferenced flags do not show up here
+        // Maybe compiler optomized them out of bytecode?
+        for (Method method : flagMethods) {
+            if (!Modifier.isPublic(method.getModifiers())) {
+                continue;
+            }
+            try {
+                pw.print(method.getName() + ": ");
+                pw.print(method.invoke(null));
+            } catch (Exception ex) {
+                ex.printStackTrace(pw);
+            } finally {
+                pw.println("");
+            }
+        }
+
         return 0;
     }
 
diff --git a/services/companion/java/com/android/server/companion/virtual/GenericWindowPolicyController.java b/services/companion/java/com/android/server/companion/virtual/GenericWindowPolicyController.java
index b07a0bb..102c262 100644
--- a/services/companion/java/com/android/server/companion/virtual/GenericWindowPolicyController.java
+++ b/services/companion/java/com/android/server/companion/virtual/GenericWindowPolicyController.java
@@ -47,7 +47,6 @@
 
 import java.util.Set;
 
-
 /**
  * A controller to control the policies of the windows that can be displayed on the virtual display.
  */
@@ -106,12 +105,14 @@
     public static final long ALLOW_SECURE_ACTIVITY_DISPLAY_ON_REMOTE_DEVICE = 201712607L;
     @NonNull
     private final ArraySet<UserHandle> mAllowedUsers;
-    private final boolean mActivityLaunchAllowedByDefault;
+    @GuardedBy("mGenericWindowPolicyControllerLock")
+    private boolean mActivityLaunchAllowedByDefault;
     @NonNull
-    private final ArraySet<ComponentName> mActivityPolicyExceptions;
+    @GuardedBy("mGenericWindowPolicyControllerLock")
+    private final Set<ComponentName> mActivityPolicyExemptions;
     private final boolean mCrossTaskNavigationAllowedByDefault;
     @NonNull
-    private final ArraySet<ComponentName> mCrossTaskNavigationExceptions;
+    private final ArraySet<ComponentName> mCrossTaskNavigationExemptions;
     private final Object mGenericWindowPolicyControllerLock = new Object();
     @Nullable private final ActivityBlockedCallback mActivityBlockedCallback;
     private int mDisplayId = Display.INVALID_DISPLAY;
@@ -142,11 +143,11 @@
      * @param allowedUsers The set of users that are allowed to stream in this display.
      * @param activityLaunchAllowedByDefault Whether activities are default allowed to be launched
      *   or blocked.
-     * @param activityPolicyExceptions The set of activities explicitly exempt from the default
+     * @param activityPolicyExemptions The set of activities explicitly exempt from the default
      *   activity policy.
      * @param crossTaskNavigationAllowedByDefault Whether cross task navigations are allowed by
      *   default or not.
-     * @param crossTaskNavigationExceptions The set of components explicitly exempt from the default
+     * @param crossTaskNavigationExemptions The set of components explicitly exempt from the default
      *   navigation policy.
      * @param activityListener Activity listener to listen for activity changes.
      * @param activityBlockedCallback Callback that is called when an activity is blocked from
@@ -157,12 +158,14 @@
      *   passed in filters.
      * @param showTasksInHostDeviceRecents whether to show activities in recents on the host device.
      */
-    public GenericWindowPolicyController(int windowFlags, int systemWindowFlags,
+    public GenericWindowPolicyController(
+            int windowFlags,
+            int systemWindowFlags,
             @NonNull ArraySet<UserHandle> allowedUsers,
             boolean activityLaunchAllowedByDefault,
-            @NonNull Set<ComponentName> activityPolicyExceptions,
+            @NonNull Set<ComponentName> activityPolicyExemptions,
             boolean crossTaskNavigationAllowedByDefault,
-            @NonNull Set<ComponentName> crossTaskNavigationExceptions,
+            @NonNull Set<ComponentName> crossTaskNavigationExemptions,
             @Nullable ActivityListener activityListener,
             @Nullable PipBlockedCallback pipBlockedCallback,
             @Nullable ActivityBlockedCallback activityBlockedCallback,
@@ -173,9 +176,9 @@
         super();
         mAllowedUsers = allowedUsers;
         mActivityLaunchAllowedByDefault = activityLaunchAllowedByDefault;
-        mActivityPolicyExceptions = new ArraySet<>(activityPolicyExceptions);
+        mActivityPolicyExemptions = activityPolicyExemptions;
         mCrossTaskNavigationAllowedByDefault = crossTaskNavigationAllowedByDefault;
-        mCrossTaskNavigationExceptions = new ArraySet<>(crossTaskNavigationExceptions);
+        mCrossTaskNavigationExemptions = new ArraySet<>(crossTaskNavigationExemptions);
         mActivityBlockedCallback = activityBlockedCallback;
         setInterestedWindowFlags(windowFlags, systemWindowFlags);
         mActivityListener = activityListener;
@@ -202,6 +205,24 @@
         }
     }
 
+    void setActivityLaunchDefaultAllowed(boolean activityLaunchDefaultAllowed) {
+        synchronized (mGenericWindowPolicyControllerLock) {
+            mActivityLaunchAllowedByDefault = activityLaunchDefaultAllowed;
+        }
+    }
+
+    void addActivityPolicyExemption(@NonNull ComponentName componentName) {
+        synchronized (mGenericWindowPolicyControllerLock) {
+            mActivityPolicyExemptions.add(componentName);
+        }
+    }
+
+    void removeActivityPolicyExemption(@NonNull ComponentName componentName) {
+        synchronized (mGenericWindowPolicyControllerLock) {
+            mActivityPolicyExemptions.remove(componentName);
+        }
+    }
+
     /** Register a listener for running applications changes. */
     public void registerRunningAppsChangedListener(@NonNull RunningAppsChangedListener listener) {
         synchronized (mGenericWindowPolicyControllerLock) {
@@ -265,14 +286,17 @@
                     + mDisplayCategories);
             return false;
         }
-        if (!isAllowedByPolicy(mActivityLaunchAllowedByDefault, mActivityPolicyExceptions,
-                activityComponent)) {
-            Slog.d(TAG, "Virtual device launch disallowed by policy: " + activityComponent);
-            return false;
+        synchronized (mGenericWindowPolicyControllerLock) {
+            if (!isAllowedByPolicy(mActivityLaunchAllowedByDefault, mActivityPolicyExemptions,
+                    activityComponent)) {
+                Slog.d(TAG, "Virtual device launch disallowed by policy: "
+                        + activityComponent);
+                return false;
+            }
         }
         if (isNewTask && launchingFromDisplayId != DEFAULT_DISPLAY
                 && !isAllowedByPolicy(mCrossTaskNavigationAllowedByDefault,
-                        mCrossTaskNavigationExceptions, activityComponent)) {
+                        mCrossTaskNavigationExemptions, activityComponent)) {
             Slog.d(TAG, "Virtual device cross task navigation disallowed by policy: "
                     + activityComponent);
             return false;
@@ -378,11 +402,11 @@
                     && mDisplayCategories.contains(activityInfo.requiredDisplayCategory);
     }
 
-    private boolean isAllowedByPolicy(boolean allowedByDefault, ArraySet<ComponentName> exceptions,
-            ComponentName component) {
-        // Either allowed and the exceptions do not contain the component,
-        // or disallowed and the exceptions contain the component.
-        return allowedByDefault != exceptions.contains(component);
+    private static boolean isAllowedByPolicy(boolean allowedByDefault,
+            Set<ComponentName> exemptions, ComponentName component) {
+        // Either allowed and the exemptions do not contain the component,
+        // or disallowed and the exemptions contain the component.
+        return allowedByDefault != exemptions.contains(component);
     }
 
     @VisibleForTesting
diff --git a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java
index 8f765e4..3b13410 100644
--- a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java
+++ b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceImpl.java
@@ -22,6 +22,7 @@
 import static android.companion.virtual.VirtualDeviceParams.ACTIVITY_POLICY_DEFAULT_ALLOWED;
 import static android.companion.virtual.VirtualDeviceParams.DEVICE_POLICY_DEFAULT;
 import static android.companion.virtual.VirtualDeviceParams.NAVIGATION_POLICY_DEFAULT_ALLOWED;
+import static android.companion.virtual.VirtualDeviceParams.POLICY_TYPE_ACTIVITY;
 import static android.companion.virtual.VirtualDeviceParams.POLICY_TYPE_RECENTS;
 import static android.view.WindowManager.LayoutParams.FLAG_SECURE;
 import static android.view.WindowManager.LayoutParams.SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS;
@@ -93,7 +94,6 @@
 import android.view.WindowManager;
 import android.widget.Toast;
 
-
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.app.BlockedAppStreamingActivity;
@@ -174,11 +174,15 @@
     @NonNull
     private final VirtualDevice mPublicVirtualDeviceObject;
 
+    @GuardedBy("mVirtualDeviceLock")
+    @NonNull
+    private final Set<ComponentName> mActivityPolicyExemptions;
+
     private ActivityListener createListenerAdapter() {
         return new ActivityListener() {
 
             @Override
-            public void onTopActivityChanged(int displayId, ComponentName topActivity) {
+            public void onTopActivityChanged(int displayId, @NonNull ComponentName topActivity) {
                 try {
                     mActivityListener.onTopActivityChanged(displayId, topActivity,
                             UserHandle.USER_NULL);
@@ -188,7 +192,7 @@
             }
 
             @Override
-            public void onTopActivityChanged(int displayId, ComponentName topActivity,
+            public void onTopActivityChanged(int displayId, @NonNull ComponentName topActivity,
                     @UserIdInt int userId) {
                 try {
                     mActivityListener.onTopActivityChanged(displayId, topActivity, userId);
@@ -295,6 +299,18 @@
 
         mPublicVirtualDeviceObject = new VirtualDevice(
                 this, getDeviceId(), getPersistentDeviceId(), mParams.getName());
+
+        if (Flags.dynamicPolicy()) {
+            mActivityPolicyExemptions = new ArraySet<>(
+                    mParams.getDevicePolicy(POLICY_TYPE_ACTIVITY) == DEVICE_POLICY_DEFAULT
+                            ? mParams.getBlockedActivities()
+                            : mParams.getAllowedActivities());
+        } else {
+            mActivityPolicyExemptions =
+                    mParams.getDefaultActivityPolicy() == ACTIVITY_POLICY_DEFAULT_ALLOWED
+                            ? mParams.getBlockedActivities()
+                            : mParams.getAllowedActivities();
+        }
     }
 
     @VisibleForTesting
@@ -414,6 +430,34 @@
         }
     }
 
+    @Override // Binder call
+    @EnforcePermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)
+    public void addActivityPolicyExemption(@NonNull ComponentName componentName) {
+        super.addActivityPolicyExemption_enforcePermission();
+        synchronized (mVirtualDeviceLock) {
+            if (mActivityPolicyExemptions.add(componentName)) {
+                for (int i = 0; i < mVirtualDisplays.size(); i++) {
+                    mVirtualDisplays.valueAt(i).getWindowPolicyController()
+                            .addActivityPolicyExemption(componentName);
+                }
+            }
+        }
+    }
+
+    @Override // Binder call
+    @EnforcePermission(android.Manifest.permission.CREATE_VIRTUAL_DEVICE)
+    public void removeActivityPolicyExemption(@NonNull ComponentName componentName) {
+        super.removeActivityPolicyExemption_enforcePermission();
+        synchronized (mVirtualDeviceLock) {
+            if (mActivityPolicyExemptions.remove(componentName)) {
+                for (int i = 0; i < mVirtualDisplays.size(); i++) {
+                    mVirtualDisplays.valueAt(i).getWindowPolicyController()
+                            .removeActivityPolicyExemption(componentName);
+                }
+            }
+        }
+    }
+
     private void sendPendingIntent(int displayId, PendingIntent pendingIntent)
             throws PendingIntent.CanceledException {
         final ActivityOptions options = ActivityOptions.makeBasic().setLaunchDisplayId(displayId);
@@ -543,6 +587,16 @@
                     }
                 }
                 break;
+            case POLICY_TYPE_ACTIVITY:
+                synchronized (mVirtualDeviceLock) {
+                    mDevicePolicies.put(policyType, devicePolicy);
+                    for (int i = 0; i < mVirtualDisplays.size(); i++) {
+                        mVirtualDisplays.valueAt(i).getWindowPolicyController()
+                                .setActivityLaunchDefaultAllowed(
+                                        devicePolicy == DEVICE_POLICY_DEFAULT);
+                    }
+                }
+                break;
             default:
                 throw new IllegalArgumentException("Device policy " + policyType
                         + " cannot be changed at runtime. ");
@@ -840,24 +894,26 @@
         mSensorController.dump(fout);
     }
 
-    private GenericWindowPolicyController createWindowPolicyController(
+    @GuardedBy("mVirtualDeviceLock")
+    private GenericWindowPolicyController createWindowPolicyControllerLocked(
             @NonNull Set<String> displayCategories) {
         final boolean activityLaunchAllowedByDefault =
-                mParams.getDefaultActivityPolicy() == ACTIVITY_POLICY_DEFAULT_ALLOWED;
+                Flags.dynamicPolicy()
+                        ? getDevicePolicy(POLICY_TYPE_ACTIVITY) == DEVICE_POLICY_DEFAULT
+                        : mParams.getDefaultActivityPolicy() == ACTIVITY_POLICY_DEFAULT_ALLOWED;
         final boolean crossTaskNavigationAllowedByDefault =
                 mParams.getDefaultNavigationPolicy() == NAVIGATION_POLICY_DEFAULT_ALLOWED;
         final boolean showTasksInHostDeviceRecents =
-                mParams.getDevicePolicy(POLICY_TYPE_RECENTS) == DEVICE_POLICY_DEFAULT;
+                getDevicePolicy(POLICY_TYPE_RECENTS) == DEVICE_POLICY_DEFAULT;
 
         final GenericWindowPolicyController gwpc = new GenericWindowPolicyController(
                 FLAG_SECURE,
                 SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS,
                 getAllowedUserHandles(),
                 activityLaunchAllowedByDefault,
-                /*activityPolicyExceptions=*/activityLaunchAllowedByDefault
-                        ? mParams.getBlockedActivities() : mParams.getAllowedActivities(),
+                mActivityPolicyExemptions,
                 crossTaskNavigationAllowedByDefault,
-                /*crossTaskNavigationExceptions=*/crossTaskNavigationAllowedByDefault
+                /*crossTaskNavigationExemptions=*/crossTaskNavigationAllowedByDefault
                         ? mParams.getBlockedCrossTaskNavigations()
                         : mParams.getAllowedCrossTaskNavigations(),
                 createListenerAdapter(),
@@ -873,8 +929,10 @@
 
     int createVirtualDisplay(@NonNull VirtualDisplayConfig virtualDisplayConfig,
             @NonNull IVirtualDisplayCallback callback, String packageName) {
-        GenericWindowPolicyController gwpc = createWindowPolicyController(
-                virtualDisplayConfig.getDisplayCategories());
+        GenericWindowPolicyController gwpc;
+        synchronized (mVirtualDeviceLock) {
+            gwpc = createWindowPolicyControllerLocked(virtualDisplayConfig.getDisplayCategories());
+        }
         DisplayManagerInternal displayManager = LocalServices.getService(
                 DisplayManagerInternal.class);
         int displayId;
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..10a1b9e 100644
--- a/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java
+++ b/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java
@@ -115,28 +115,38 @@
     };
 
     // 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",
         "arc_next",
-        "test_suites",
-        "hardware_backed_security_mainline",
-        "threadnetwork",
-        "media_solutions",
-        "responsible_apis",
-        "rust",
-        "pixel_biometrics",
+        "biometrics_framework",
+        "biometrics_integration",
+        "camera_platform",
+        "car_framework",
         "car_perception",
         "car_security",
         "car_telemetry",
-        "car_framework",
-        "android-nfc",
+        "codec_fwk",
+        "core_experiments_team_internal",
+        "haptics",
+        "hardware_backed_security_mainline",
+        "media_solutions",
+        "nfc",
+        "pixel_system_sw_touch",
+        "pixel_watch",
+        "power",
+        "responsible_apis",
+        "rust",
+        "test_suites",
+        "text",
+        "threadnetwork",
+        "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/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/content/SyncStorageEngine.java b/services/core/java/com/android/server/content/SyncStorageEngine.java
index b890bbd..9805fd3 100644
--- a/services/core/java/com/android/server/content/SyncStorageEngine.java
+++ b/services/core/java/com/android/server/content/SyncStorageEngine.java
@@ -1754,7 +1754,7 @@
                     eventType = parser.next();
                 } while (eventType != XmlPullParser.END_DOCUMENT);
             }
-        } catch (XmlPullParserException e) {
+        } catch (XmlPullParserException | ArrayIndexOutOfBoundsException e) {
             Slog.w(TAG, "Error reading accounts", e);
             return;
         } catch (java.io.IOException e) {
diff --git a/services/core/java/com/android/server/display/feature/display_flags.aconfig b/services/core/java/com/android/server/display/feature/display_flags.aconfig
index 4d8c02b..2c3c66e 100644
--- a/services/core/java/com/android/server/display/feature/display_flags.aconfig
+++ b/services/core/java/com/android/server/display/feature/display_flags.aconfig
@@ -7,4 +7,5 @@
     namespace: "display_manager"
     description: "Feature flag for Connected Display managment"
     bug: "280739508"
+    is_fixed_read_only: true
 }
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..e58f272 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
@@ -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. */
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/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/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java
index 803b94b..e365e83 100644
--- a/services/core/java/com/android/server/pm/UserManagerService.java
+++ b/services/core/java/com/android/server/pm/UserManagerService.java
@@ -2750,7 +2750,8 @@
         }
     }
 
-    private void setUserRestrictionInner(int userId, @NonNull String key, boolean value) {
+    @VisibleForTesting
+    void setUserRestrictionInner(int userId, @NonNull String key, boolean value) {
         if (!UserRestrictionsUtils.isValidRestriction(key)) {
             Slog.e(LOG_TAG, "Setting invalid restriction " + key);
             return;
@@ -4360,11 +4361,11 @@
 
             UserRestrictionsUtils.writeRestrictions(serializer,
                     mDevicePolicyUserRestrictions.getRestrictions(UserHandle.USER_ALL),
-                    TAG_DEVICE_POLICY_RESTRICTIONS);
+                    TAG_DEVICE_POLICY_GLOBAL_RESTRICTIONS);
 
             UserRestrictionsUtils.writeRestrictions(serializer,
                     mDevicePolicyUserRestrictions.getRestrictions(userInfo.id),
-                    TAG_DEVICE_POLICY_RESTRICTIONS);
+                    TAG_DEVICE_POLICY_LOCAL_RESTRICTIONS);
         }
 
         if (userData.account != null) {
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/trust/TrustManagerService.java b/services/core/java/com/android/server/trust/TrustManagerService.java
index 9905ddf..635e11b 100644
--- a/services/core/java/com/android/server/trust/TrustManagerService.java
+++ b/services/core/java/com/android/server/trust/TrustManagerService.java
@@ -159,10 +159,26 @@
     private VirtualDeviceManagerInternal mVirtualDeviceManager;
 
     private enum TrustState {
-        UNTRUSTED, // the phone is not unlocked by any trustagents
-        TRUSTABLE, // the phone is in a semi-locked state that can be unlocked if
-        // FLAG_GRANT_TRUST_TEMPORARY_AND_RENEWABLE is passed and a trustagent is trusted
-        TRUSTED // the phone is unlocked
+        // UNTRUSTED means that TrustManagerService is currently *not* giving permission for the
+        // user's Keyguard to be dismissed, and grants of trust by trust agents are remembered in
+        // the corresponding TrustAgentWrapper but are not recognized until the device is unlocked
+        // for the user.  I.e., if the device is locked and the state is UNTRUSTED, it cannot be
+        // unlocked by a trust agent.  Automotive devices are an exception; grants of trust are
+        // always recognized on them.
+        UNTRUSTED,
+
+        // TRUSTABLE is the same as UNTRUSTED except that new grants of trust using
+        // FLAG_GRANT_TRUST_TEMPORARY_AND_RENEWABLE are recognized for moving to TRUSTED.  I.e., if
+        // the device is locked and the state is TRUSTABLE, it can be unlocked by a trust agent,
+        // provided that the trust agent chooses to use Active Unlock.  The TRUSTABLE state is only
+        // possible as a result of a downgrade from TRUSTED, after a trust agent used
+        // FLAG_GRANT_TRUST_TEMPORARY_AND_RENEWABLE in its most recent grant.
+        TRUSTABLE,
+
+        // TRUSTED means that TrustManagerService is currently giving permission for the user's
+        // Keyguard to be dismissed.  This implies that the device is unlocked for the user (where
+        // the case of Keyguard showing but dismissible just with swipe counts as "unlocked").
+        TRUSTED
     };
 
     @GuardedBy("mUserTrustState")
@@ -744,6 +760,12 @@
         }
     }
 
+    private TrustState getUserTrustStateInner(int userId) {
+        synchronized (mUserTrustState) {
+            return mUserTrustState.get(userId, TrustState.UNTRUSTED);
+        }
+    }
+
     boolean isDeviceLockedInner(int userId) {
         synchronized (mDeviceLockedForUser) {
             return mDeviceLockedForUser.get(userId, true);
@@ -806,7 +828,12 @@
                 continue;
             }
 
-            boolean trusted = aggregateIsTrusted(id);
+            final boolean trusted;
+            if (android.security.Flags.fixUnlockedDeviceRequiredKeys()) {
+                trusted = getUserTrustStateInner(id) == TrustState.TRUSTED;
+            } else {
+                trusted = aggregateIsTrusted(id);
+            }
             boolean showingKeyguard = true;
             boolean biometricAuthenticated = false;
             boolean currentUserIsUnlocked = false;
@@ -1627,7 +1654,7 @@
             if (isCurrent) {
                 fout.print(" (current)");
             }
-            fout.print(": trusted=" + dumpBool(aggregateIsTrusted(user.id)));
+            fout.print(": trustState=" + getUserTrustStateInner(user.id));
             fout.print(", trustManaged=" + dumpBool(aggregateIsTrustManaged(user.id)));
             fout.print(", deviceLocked=" + dumpBool(isDeviceLockedInner(user.id)));
             fout.print(", isActiveUnlockRunning=" + dumpBool(
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..74d3417 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
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index 582536b..b2eb383 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;
@@ -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 fd42077..6999c6a 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
@@ -68,7 +68,6 @@
 import static android.view.WindowManager.TRANSIT_PIP;
 import static android.view.WindowManager.TRANSIT_TO_FRONT;
 import static android.view.WindowManagerPolicyConstants.KEYGUARD_GOING_AWAY_FLAG_TO_LAUNCHER_CLEAR_SNAPSHOT;
-
 import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_CONFIGURATION;
 import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_DREAM;
 import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_FOCUS;
@@ -2221,7 +2220,7 @@
             callerApp = getProcessController(appThread);
         }
         final BackgroundActivityStartController balController =
-                getActivityStartController().getBackgroundActivityLaunchController();
+                mTaskSupervisor.getBackgroundActivityLaunchController();
         if (balController.shouldAbortBackgroundActivityStart(
                 callingUid,
                 callingPid,
@@ -3660,6 +3659,9 @@
             synchronized (mGlobalLock) {
                 if (r.getParent() == null) {
                     Slog.e(TAG, "Skip enterPictureInPictureMode, destroyed " + r);
+                    if (transition != null) {
+                        transition.abort();
+                    }
                     return;
                 }
                 EventLogTags.writeWmEnterPip(r.mUserId, System.identityHashCode(r),
@@ -5628,6 +5630,15 @@
         }
     }
 
+    void registerCompatScaleProvider(@CompatScaleProvider.CompatScaleModeOrderId int id,
+            @NonNull CompatScaleProvider provider) {
+        mCompatModePackages.registerCompatScaleProvider(id, provider);
+    }
+
+    void unregisterCompatScaleProvider(@CompatScaleProvider.CompatScaleModeOrderId int id) {
+        mCompatModePackages.unregisterCompatScaleProvider(id);
+    }
+
     /**
      * Returns {@code true} if the process represented by the pid passed as argument is
      * instrumented and the instrumentation source was granted with the permission also
diff --git a/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java b/services/core/java/com/android/server/wm/ActivityTaskSupervisor.java
index 6eb9ed69..69e679d 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() {
@@ -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 c6978fd..2439159 100644
--- a/services/core/java/com/android/server/wm/CompatModePackages.java
+++ b/services/core/java/com/android/server/wm/CompatModePackages.java
@@ -20,7 +20,10 @@
 import static com.android.server.wm.ActivityTaskManagerDebugConfig.TAG_ATM;
 import static com.android.server.wm.ActivityTaskManagerDebugConfig.TAG_WITH_CLASS_NAME;
 import static com.android.server.wm.ActivityTaskSupervisor.PRESERVE_WINDOWS;
+import static com.android.server.wm.CompatScaleProvider.COMPAT_SCALE_MODE_SYSTEM_FIRST;
+import static com.android.server.wm.CompatScaleProvider.COMPAT_SCALE_MODE_SYSTEM_LAST;
 
+import android.annotation.NonNull;
 import android.app.ActivityManager;
 import android.app.AppGlobals;
 import android.app.GameManagerInternal;
@@ -32,6 +35,7 @@
 import android.content.pm.ApplicationInfo;
 import android.content.pm.IPackageManager;
 import android.content.res.CompatibilityInfo;
+import android.content.res.CompatibilityInfo.CompatScale;
 import android.content.res.Configuration;
 import android.os.Build;
 import android.os.Handler;
@@ -332,6 +336,8 @@
     private final HashMap<String, Integer> mPackages = new HashMap<>();
     private final CompatHandler mHandler;
 
+    private final SparseArray<CompatScaleProvider> mProviders = new SparseArray<>();
+
     public CompatModePackages(ActivityTaskManagerService service, File systemDir, Handler handler) {
         mService = service;
         mFile = new AtomicFile(new File(systemDir, "packages-compat.xml"), "compat-mode");
@@ -441,13 +447,38 @@
 
     public CompatibilityInfo compatibilityInfoForPackageLocked(ApplicationInfo ai) {
         final boolean forceCompat = getPackageCompatModeEnabledLocked(ai);
-        final float compatScale = getCompatScale(ai.packageName, ai.uid);
+        final CompatScale compatScale = getCompatScaleFromProvider(ai.packageName, ai.uid);
+        final float appScale = compatScale != null
+                ? compatScale.mScaleFactor
+                : getCompatScale(ai.packageName, ai.uid, /* checkProvider= */ false);
+        final float densityScale = compatScale != null ? compatScale.mDensityScaleFactor : appScale;
         final Configuration config = mService.getGlobalConfiguration();
         return new CompatibilityInfo(ai, config.screenLayout, config.smallestScreenWidthDp,
-                forceCompat, compatScale);
+                forceCompat, appScale, densityScale);
     }
 
     float getCompatScale(String packageName, int uid) {
+        return getCompatScale(packageName, uid, /* checkProvider= */ true);
+    }
+
+    private CompatScale getCompatScaleFromProvider(String packageName, int uid) {
+        for (int i = 0; i < mProviders.size(); i++) {
+            final CompatScaleProvider provider = mProviders.valueAt(i);
+            final CompatScale compatScale = provider.getCompatScale(packageName, uid);
+            if (compatScale != null) {
+                return compatScale;
+            }
+        }
+        return null;
+    }
+
+    private float getCompatScale(String packageName, int uid, boolean checkProviders) {
+        if (checkProviders) {
+            final CompatScale compatScale = getCompatScaleFromProvider(packageName, uid);
+            if (compatScale != null) {
+                return compatScale.mScaleFactor;
+            }
+        }
         final UserHandle userHandle = UserHandle.getUserHandleForUid(uid);
         if (mGameManager == null) {
             mGameManager = LocalServices.getService(GameManagerInternal.class);
@@ -487,6 +518,36 @@
         return 1f;
     }
 
+    void registerCompatScaleProvider(@CompatScaleProvider.CompatScaleModeOrderId int id,
+            @NonNull CompatScaleProvider provider) {
+        synchronized (mService.mGlobalLock) {
+            if (mProviders.contains(id)) {
+                throw new IllegalArgumentException("Duplicate id provided: " + id);
+            }
+            if (provider == null) {
+                throw new IllegalArgumentException("The passed CompatScaleProvider "
+                        + "can not be null");
+            }
+            if (!CompatScaleProvider.isValidOrderId(id)) {
+                throw new IllegalArgumentException(
+                        "Provided id " + id + " is not in range of valid ids for system "
+                                + "services [" + COMPAT_SCALE_MODE_SYSTEM_FIRST + ","
+                                + COMPAT_SCALE_MODE_SYSTEM_LAST + "]");
+            }
+            mProviders.put(id, provider);
+        }
+    }
+
+    void unregisterCompatScaleProvider(@CompatScaleProvider.CompatScaleModeOrderId int id) {
+        synchronized (mService.mGlobalLock) {
+            if (!mProviders.contains(id)) {
+                throw new IllegalArgumentException(
+                        "CompatScaleProvider with id (" + id + ") is not registered");
+            }
+            mProviders.remove(id);
+        }
+    }
+
     private static float getScalingFactor(String packageName, UserHandle userHandle) {
         if (CompatChanges.isChangeEnabled(DOWNSCALE_90, packageName, userHandle)) {
             return 0.9f;
diff --git a/services/core/java/com/android/server/wm/CompatScaleProvider.java b/services/core/java/com/android/server/wm/CompatScaleProvider.java
new file mode 100644
index 0000000..5474ece
--- /dev/null
+++ b/services/core/java/com/android/server/wm/CompatScaleProvider.java
@@ -0,0 +1,86 @@
+/*
+ * 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.wm;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.res.CompatibilityInfo.CompatScale;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * An interface for services that need to provide compatibility scale different than
+ * the default android compatibility.
+ */
+public interface CompatScaleProvider {
+
+    /**
+     * The unique id of each provider registered by a system service which determines the order
+     * it will execute in.
+     */
+    @IntDef(prefix = { "COMPAT_SCALE_MODE_" }, value = {
+        // Order Ids for system services
+        COMPAT_SCALE_MODE_SYSTEM_FIRST,
+        COMPAT_SCALE_MODE_GAME,
+        COMPAT_SCALE_MODE_PRODUCT,
+        COMPAT_SCALE_MODE_SYSTEM_LAST, // Update this when adding new ids
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    @interface CompatScaleModeOrderId {}
+
+    /**
+     * The first id, used by the framework to determine the valid range of ids.
+     * @hide
+     */
+    int COMPAT_SCALE_MODE_SYSTEM_FIRST = 0;
+
+    /**
+     * TODO(b/295207384)
+     * The identifier for {@link android.app.GameManagerInternal} provider
+     * @hide
+     */
+    int COMPAT_SCALE_MODE_GAME = 1;
+
+    /**
+     * The identifier for a provider which is specific to the type of android product like
+     * Automotive, Wear, TV etc.
+     * @hide
+     */
+    int COMPAT_SCALE_MODE_PRODUCT = 2;
+
+    /**
+     * The final id, used by the framework to determine the valid range of ids. Update this when
+     * adding new ids.
+     * @hide
+     */
+    int COMPAT_SCALE_MODE_SYSTEM_LAST = COMPAT_SCALE_MODE_PRODUCT;
+
+    /**
+     * Returns {@code true} if the id is in the range of valid system services
+     * @hide
+     */
+    static boolean isValidOrderId(int id) {
+        return (id >= COMPAT_SCALE_MODE_SYSTEM_FIRST && id <= COMPAT_SCALE_MODE_SYSTEM_LAST);
+    }
+
+    /**
+     * @return an instance of {@link CompatScale} to apply for the given package
+     */
+    @Nullable
+    CompatScale getCompatScale(@NonNull String packageName, int uid);
+}
diff --git a/services/core/java/com/android/server/wm/ContentRecorder.java b/services/core/java/com/android/server/wm/ContentRecorder.java
index 5aa7c97..7cd07d6 100644
--- a/services/core/java/com/android/server/wm/ContentRecorder.java
+++ b/services/core/java/com/android/server/wm/ContentRecorder.java
@@ -35,6 +35,7 @@
 import android.os.ServiceManager;
 import android.provider.DeviceConfig;
 import android.view.ContentRecordingSession;
+import android.view.ContentRecordingSession.RecordContent;
 import android.view.Display;
 import android.view.SurfaceControl;
 
@@ -84,6 +85,7 @@
     /**
      * The last configuration orientation.
      */
+    @Configuration.Orientation
     private int mLastOrientation = ORIENTATION_UNDEFINED;
 
     ContentRecorder(@NonNull DisplayContent displayContent) {
@@ -149,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",
@@ -156,7 +172,8 @@
             // Retrieve the size of the region to record, and continue with the update
             // if the bounds or orientation has changed.
             final Rect recordedContentBounds = mRecordedWindowContainer.getBounds();
-            int recordedContentOrientation = mRecordedWindowContainer.getOrientation();
+            @Configuration.Orientation int recordedContentOrientation =
+                    mRecordedWindowContainer.getConfiguration().orientation;
             if (!mLastRecordedBounds.equals(recordedContentBounds)
                     || lastOrientation != recordedContentOrientation) {
                 Point surfaceSize = fetchSurfaceSizeIfPresent();
@@ -289,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,
@@ -302,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());
@@ -356,7 +381,7 @@
      */
     @Nullable
     private WindowContainer retrieveRecordedWindowContainer() {
-        final int contentToRecord = mContentRecordingSession.getContentToRecord();
+        @RecordContent final int contentToRecord = mContentRecordingSession.getContentToRecord();
         final IBinder tokenToRecord = mContentRecordingSession.getTokenToRecord();
         switch (contentToRecord) {
             case RECORD_CONTENT_DISPLAY:
@@ -472,6 +497,12 @@
             shiftedY = (surfaceSize.y - scaledHeight) / 2;
         }
 
+        ProtoLog.v(WM_DEBUG_CONTENT_RECORDING,
+                "Content Recording: Apply transformations of shift %d x %d, scale %f, crop %d x "
+                        + "%d for display %d",
+                shiftedX, shiftedY, scale, recordedContentBounds.width(),
+                recordedContentBounds.height(), mDisplayContent.getDisplayId());
+
         transaction
                 // Crop the area to capture to exclude the 'extra' wallpaper that is used
                 // for parallax (b/189930234).
diff --git a/services/core/java/com/android/server/wm/DisplayPolicy.java b/services/core/java/com/android/server/wm/DisplayPolicy.java
index 707b779..395ab3a 100644
--- a/services/core/java/com/android/server/wm/DisplayPolicy.java
+++ b/services/core/java/com/android/server/wm/DisplayPolicy.java
@@ -269,6 +269,8 @@
 
     private boolean mIsFreeformWindowOverlappingWithNavBar;
 
+    private @InsetsType int mForciblyShownTypes;
+
     private boolean mIsImmersiveMode;
 
     // The windows we were told about in focusChanged.
@@ -1402,6 +1404,7 @@
         mAllowLockscreenWhenOn = false;
         mShowingDream = false;
         mIsFreeformWindowOverlappingWithNavBar = false;
+        mForciblyShownTypes = 0;
     }
 
     /**
@@ -1459,6 +1462,10 @@
             }
         }
 
+        if (win.mSession.mCanForceShowingInsets) {
+            mForciblyShownTypes |= win.mAttrs.forciblyShownTypes;
+        }
+
         if (!affectsSystemUi) {
             return;
         }
@@ -1640,6 +1647,10 @@
         mService.mPolicy.setAllowLockscreenWhenOn(getDisplayId(), mAllowLockscreenWhenOn);
     }
 
+    boolean areTypesForciblyShownTransiently(@InsetsType int types) {
+        return (mForciblyShownTypes & types) == types;
+    }
+
     /**
      * Applies the keyguard policy to a specific window.
      *
diff --git a/services/core/java/com/android/server/wm/InsetsPolicy.java b/services/core/java/com/android/server/wm/InsetsPolicy.java
index 835c92d..d0d7f49 100644
--- a/services/core/java/com/android/server/wm/InsetsPolicy.java
+++ b/services/core/java/com/android/server/wm/InsetsPolicy.java
@@ -23,8 +23,6 @@
 import static android.app.WindowConfiguration.WINDOWING_MODE_PINNED;
 import static android.view.InsetsSource.ID_IME;
 import static android.view.WindowInsetsController.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE;
-import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_FORCE_SHOW_STATUS_BAR;
-import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_STATUS_FORCE_SHOW_NAVIGATION;
 import static android.view.WindowManager.LayoutParams.TYPE_INPUT_METHOD;
 
 import android.annotation.NonNull;
@@ -77,6 +75,18 @@
     /** Used to show system bars permanently. This will affect the layout. */
     private final InsetsControlTarget mPermanentControlTarget;
 
+    /**
+     * Used to override the visibility of {@link Type#statusBars()} when dispatching insets to
+     * clients.
+     */
+    private InsetsControlTarget mFakeStatusControlTarget;
+
+    /**
+     * Used to override the visibility of {@link Type#navigationBars()} when dispatching insets to
+     * clients.
+     */
+    private InsetsControlTarget mFakeNavControlTarget;
+
     private WindowState mFocusedWin;
     private final BarWindow mStatusBar = new BarWindow(StatusBarManager.WINDOW_STATUS_BAR);
     private final BarWindow mNavBar = new BarWindow(StatusBarManager.WINDOW_NAVIGATION_BAR);
@@ -103,25 +113,25 @@
             abortTransient();
         }
         mFocusedWin = focusedWin;
-        final InsetsControlTarget statusControlTarget =
-                getStatusControlTarget(focusedWin, false /* fake */);
-        final InsetsControlTarget navControlTarget =
-                getNavControlTarget(focusedWin, false /* fake */);
         final WindowState notificationShade = mPolicy.getNotificationShade();
         final WindowState topApp = mPolicy.getTopFullscreenOpaqueWindow();
+        final InsetsControlTarget statusControlTarget =
+                getStatusControlTarget(focusedWin, false /* fake */);
+        mFakeStatusControlTarget = statusControlTarget == mTransientControlTarget
+                ? getStatusControlTarget(focusedWin, true /* fake */)
+                : statusControlTarget == notificationShade
+                        ? getStatusControlTarget(topApp, true /* fake */)
+                        : null;
+        final InsetsControlTarget navControlTarget =
+                getNavControlTarget(focusedWin, false /* fake */);
+        mFakeNavControlTarget = navControlTarget == mTransientControlTarget
+                ? getNavControlTarget(focusedWin, true /* fake */)
+                : navControlTarget == notificationShade
+                        ? getNavControlTarget(topApp, true /* fake */)
+                        : null;
         mStateController.onBarControlTargetChanged(
-                statusControlTarget,
-                statusControlTarget == mTransientControlTarget
-                        ? getStatusControlTarget(focusedWin, true /* fake */)
-                        : statusControlTarget == notificationShade
-                                ? getStatusControlTarget(topApp, true /* fake */)
-                                : null,
-                navControlTarget,
-                navControlTarget == mTransientControlTarget
-                        ? getNavControlTarget(focusedWin, true /* fake */)
-                        : navControlTarget == notificationShade
-                                ? getNavControlTarget(topApp, true /* fake */)
-                                : null);
+                statusControlTarget, mFakeStatusControlTarget,
+                navControlTarget, mFakeNavControlTarget);
         mStatusBar.updateVisibility(statusControlTarget, Type.statusBars());
         mNavBar.updateVisibility(navControlTarget, Type.navigationBars());
     }
@@ -206,7 +216,7 @@
             boolean includesTransient) {
         InsetsState state;
         if (!includesTransient) {
-            state = adjustVisibilityForTransientTypes(originalState);
+            state = adjustVisibilityForFakeControllingSources(originalState);
         } else {
             state = originalState;
         }
@@ -321,24 +331,40 @@
         return state;
     }
 
-    private InsetsState adjustVisibilityForTransientTypes(InsetsState originalState) {
+    private InsetsState adjustVisibilityForFakeControllingSources(InsetsState originalState) {
+        if (mFakeStatusControlTarget == null && mFakeNavControlTarget == null) {
+            return originalState;
+        }
         InsetsState state = originalState;
         for (int i = state.sourceSize() - 1; i >= 0; i--) {
             final InsetsSource source = state.sourceAt(i);
-            if (isTransient(source.getType()) && source.isVisible()) {
-                if (state == originalState) {
-                    // The source will be modified, create a non-deep copy to store the new one.
-                    state = new InsetsState(originalState);
-                }
-                // Replace the source with a copy in invisible state.
-                final InsetsSource outSource = new InsetsSource(source);
-                outSource.setVisible(false);
-                state.addSource(outSource);
-            }
+            state = adjustVisibilityForFakeControllingSource(state, Type.statusBars(), source,
+                    mFakeStatusControlTarget);
+            state = adjustVisibilityForFakeControllingSource(state, Type.navigationBars(), source,
+                    mFakeNavControlTarget);
         }
         return state;
     }
 
+    private static InsetsState adjustVisibilityForFakeControllingSource(InsetsState originalState,
+            @InsetsType int type, InsetsSource source, InsetsControlTarget target) {
+        if (source.getType() != type || target == null) {
+            return originalState;
+        }
+        final boolean isRequestedVisible = target.isRequestedVisible(type);
+        if (source.isVisible() == isRequestedVisible) {
+            return originalState;
+        }
+        // The source will be modified, create a non-deep copy to store the new one.
+        final InsetsState state = new InsetsState(originalState);
+
+        // Replace the source with a copy with the overridden visibility.
+        final InsetsSource outSource = new InsetsSource(source);
+        outSource.setVisible(isRequestedVisible);
+        state.addSource(outSource);
+        return state;
+    }
+
     private InsetsState adjustVisibilityForIme(WindowState w, InsetsState originalState,
             boolean copyState) {
         if (w.mIsImWindow) {
@@ -473,7 +499,7 @@
             // we will dispatch the real visibility of status bar to the client.
             return mPermanentControlTarget;
         }
-        if (forceShowsStatusBarTransiently() && !fake) {
+        if (mPolicy.areTypesForciblyShownTransiently(Type.statusBars()) && !fake) {
             // Status bar is forcibly shown transiently, and its new visibility won't be
             // dispatched to the client so that we can keep the layout stable. We will dispatch the
             // fake control to the client, so that it can re-show the bar during this scenario.
@@ -505,7 +531,7 @@
         if (imeWin != null && imeWin.isVisible() && !mHideNavBarForKeyboard) {
             // Force showing navigation bar while IME is visible and if navigation bar is not
             // configured to be hidden by the IME.
-            return null;
+            return mPermanentControlTarget;
         }
         if (!fake && isTransient(Type.navigationBars())) {
             return mTransientControlTarget;
@@ -533,7 +559,7 @@
             // bar, and we will dispatch the real visibility of navigation bar to the client.
             return mPermanentControlTarget;
         }
-        if (forceShowsNavigationBarTransiently() && !fake) {
+        if (mPolicy.areTypesForciblyShownTransiently(Type.navigationBars()) && !fake) {
             // Navigation bar is forcibly shown transiently, and its new visibility won't be
             // dispatched to the client so that we can keep the layout stable. We will dispatch the
             // fake control to the client, so that it can re-show the bar during this scenario.
@@ -603,17 +629,6 @@
                 && focusedWin.getAttrs().type <= WindowManager.LayoutParams.LAST_APPLICATION_WINDOW;
     }
 
-    private boolean forceShowsStatusBarTransiently() {
-        final WindowState win = mPolicy.getStatusBar();
-        return win != null && (win.mAttrs.privateFlags & PRIVATE_FLAG_FORCE_SHOW_STATUS_BAR) != 0;
-    }
-
-    private boolean forceShowsNavigationBarTransiently() {
-        final WindowState win = mPolicy.getNotificationShade();
-        return win != null
-                && (win.mAttrs.privateFlags & PRIVATE_FLAG_STATUS_FORCE_SHOW_NAVIGATION) != 0;
-    }
-
     private void dispatchTransientSystemBarsVisibilityChanged(
             @Nullable WindowState focusedWindow,
             boolean areVisible,
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/RecentTasks.java b/services/core/java/com/android/server/wm/RecentTasks.java
index f33ecaa..184de58 100644
--- a/services/core/java/com/android/server/wm/RecentTasks.java
+++ b/services/core/java/com/android/server/wm/RecentTasks.java
@@ -301,9 +301,12 @@
         }
 
         // Always update the reordering time when this is called to ensure that the timeout
-        // is reset
+        // is reset.  Extend this duration when running in tests.
+        final long timeout = ActivityManager.isRunningInUserTestHarness()
+                ? mFreezeTaskListTimeoutMs * 10
+                : mFreezeTaskListTimeoutMs;
         mService.mH.removeCallbacks(mResetFreezeTaskListOnTimeoutRunnable);
-        mService.mH.postDelayed(mResetFreezeTaskListOnTimeoutRunnable, mFreezeTaskListTimeoutMs);
+        mService.mH.postDelayed(mResetFreezeTaskListOnTimeoutRunnable, timeout);
     }
 
     /**
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/Session.java b/services/core/java/com/android/server/wm/Session.java
index 1845ae8..0674ec1 100644
--- a/services/core/java/com/android/server/wm/Session.java
+++ b/services/core/java/com/android/server/wm/Session.java
@@ -23,6 +23,7 @@
 import static android.Manifest.permission.SET_UNRESTRICTED_GESTURE_EXCLUSION;
 import static android.Manifest.permission.SET_UNRESTRICTED_KEEP_CLEAR_AREAS;
 import static android.Manifest.permission.START_TASKS_FROM_RECENTS;
+import static android.Manifest.permission.STATUS_BAR_SERVICE;
 import static android.Manifest.permission.SYSTEM_APPLICATION_OVERLAY;
 import static android.app.ActivityTaskManager.INVALID_TASK_ID;
 import static android.content.ClipDescription.MIMETYPE_APPLICATION_ACTIVITY;
@@ -108,6 +109,7 @@
     private final ArraySet<WindowSurfaceController> mAlertWindowSurfaces = new ArraySet<>();
     private final DragDropController mDragDropController;
     final boolean mCanAddInternalSystemWindow;
+    boolean mCanForceShowingInsets;
     private final boolean mCanStartTasksFromRecents;
 
     final boolean mCanCreateSystemApplicationOverlay;
@@ -131,6 +133,9 @@
         mLastReportedAnimatorScale = service.getCurrentAnimatorScale();
         mCanAddInternalSystemWindow = service.mContext.checkCallingOrSelfPermission(
                 INTERNAL_SYSTEM_WINDOW) == PERMISSION_GRANTED;
+        mCanForceShowingInsets = service.mAtmService.isCallerRecents(mUid)
+                || service.mContext.checkCallingOrSelfPermission(STATUS_BAR_SERVICE)
+                == PERMISSION_GRANTED;
         mCanHideNonSystemOverlayWindows = service.mContext.checkCallingOrSelfPermission(
                 HIDE_NON_SYSTEM_OVERLAY_WINDOWS) == PERMISSION_GRANTED
                 || service.mContext.checkCallingOrSelfPermission(HIDE_OVERLAY_WINDOWS)
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/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/mockingservicestests/src/com/android/server/tare/AgentTest.java b/services/tests/mockingservicestests/src/com/android/server/tare/AgentTest.java
index 94fff22..a3917765 100644
--- a/services/tests/mockingservicestests/src/com/android/server/tare/AgentTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/tare/AgentTest.java
@@ -55,6 +55,7 @@
     @Mock
     private InternalResourceService mIrs;
 
+    private Agent mAgent;
     private Scribe mScribe;
 
     private static class MockScribe extends Scribe {
@@ -80,10 +81,13 @@
         doReturn(mIrs).when(mIrs).getLock();
         doReturn(mock(AlarmManager.class)).when(mContext).getSystemService(Context.ALARM_SERVICE);
         mScribe = new MockScribe(mIrs, mAnalyst);
+        mAgent = new Agent(mIrs, mScribe, mAnalyst);
     }
 
     @After
     public void tearDown() {
+        mAgent.tearDownLocked();
+
         if (mMockingSession != null) {
             mMockingSession.finishMocking();
         }
@@ -99,7 +103,6 @@
 
         final int userId = 0;
         final String pkgName = "com.test";
-        final Agent agent = new Agent(mIrs, mScribe, mAnalyst);
         final Ledger ledger = mScribe.getLedgerLocked(userId, pkgName);
 
         doReturn(consumptionLimit).when(mIrs).getConsumptionLimitLocked();
@@ -107,66 +110,64 @@
                 .getMaxSatiatedBalance(anyInt(), anyString());
 
         Ledger.Transaction transaction = new Ledger.Transaction(0, 0, 0, null, 5, 10);
-        agent.recordTransactionLocked(userId, pkgName, ledger, transaction, false);
+        mAgent.recordTransactionLocked(userId, pkgName, ledger, transaction, false);
         assertEquals(5, ledger.getCurrentBalance());
         assertEquals(remainingCakes - 10, mScribe.getRemainingConsumableCakesLocked());
 
-        agent.onPackageRemovedLocked(userId, pkgName);
+        mAgent.onPackageRemovedLocked(userId, pkgName);
         assertEquals(remainingCakes - 10, mScribe.getRemainingConsumableCakesLocked());
         assertLedgersEqual(new Ledger(), mScribe.getLedgerLocked(userId, pkgName));
     }
 
     @Test
     public void testRecordTransaction_UnderMax() {
-        Agent agent = new Agent(mIrs, mScribe, mAnalyst);
         Ledger ledger = new Ledger();
 
         doReturn(1_000_000L).when(mIrs).getConsumptionLimitLocked();
         doReturn(1_000_000L).when(mEconomicPolicy).getMaxSatiatedBalance(anyInt(), anyString());
 
         Ledger.Transaction transaction = new Ledger.Transaction(0, 0, 0, null, 5, 0);
-        agent.recordTransactionLocked(0, "com.test", ledger, transaction, false);
+        mAgent.recordTransactionLocked(0, "com.test", ledger, transaction, false);
         assertEquals(5, ledger.getCurrentBalance());
 
         transaction = new Ledger.Transaction(0, 0, 0, null, 995, 0);
-        agent.recordTransactionLocked(0, "com.test", ledger, transaction, false);
+        mAgent.recordTransactionLocked(0, "com.test", ledger, transaction, false);
         assertEquals(1000, ledger.getCurrentBalance());
 
         transaction = new Ledger.Transaction(0, 0, 0, null, -500, 250);
-        agent.recordTransactionLocked(0, "com.test", ledger, transaction, false);
+        mAgent.recordTransactionLocked(0, "com.test", ledger, transaction, false);
         assertEquals(500, ledger.getCurrentBalance());
 
         transaction = new Ledger.Transaction(0, 0, 0, null, 999_500L, 500);
-        agent.recordTransactionLocked(0, "com.test", ledger, transaction, false);
+        mAgent.recordTransactionLocked(0, "com.test", ledger, transaction, false);
         assertEquals(1_000_000L, ledger.getCurrentBalance());
 
         transaction = new Ledger.Transaction(0, 0, 0, null, -1_000_001L, 1000);
-        agent.recordTransactionLocked(0, "com.test", ledger, transaction, false);
+        mAgent.recordTransactionLocked(0, "com.test", ledger, transaction, false);
         assertEquals(-1, ledger.getCurrentBalance());
     }
 
     @Test
     public void testRecordTransaction_MaxConsumptionLimit() {
-        Agent agent = new Agent(mIrs, mScribe, mAnalyst);
         Ledger ledger = new Ledger();
 
         doReturn(1000L).when(mIrs).getConsumptionLimitLocked();
         doReturn(1_000_000L).when(mEconomicPolicy).getMaxSatiatedBalance(anyInt(), anyString());
 
         Ledger.Transaction transaction = new Ledger.Transaction(0, 0, 0, null, 5, 0);
-        agent.recordTransactionLocked(0, "com.test", ledger, transaction, false);
+        mAgent.recordTransactionLocked(0, "com.test", ledger, transaction, false);
         assertEquals(5, ledger.getCurrentBalance());
 
         transaction = new Ledger.Transaction(0, 0, 0, null, 995, 0);
-        agent.recordTransactionLocked(0, "com.test", ledger, transaction, false);
+        mAgent.recordTransactionLocked(0, "com.test", ledger, transaction, false);
         assertEquals(1000, ledger.getCurrentBalance());
 
         transaction = new Ledger.Transaction(0, 0, 0, null, -500, 250);
-        agent.recordTransactionLocked(0, "com.test", ledger, transaction, false);
+        mAgent.recordTransactionLocked(0, "com.test", ledger, transaction, false);
         assertEquals(500, ledger.getCurrentBalance());
 
         transaction = new Ledger.Transaction(0, 0, 0, null, 2000, 0);
-        agent.recordTransactionLocked(0, "com.test", ledger, transaction, false);
+        mAgent.recordTransactionLocked(0, "com.test", ledger, transaction, false);
         assertEquals(2500, ledger.getCurrentBalance());
 
         // ConsumptionLimit can change as the battery level changes. Ledger balances shouldn't be
@@ -174,57 +175,56 @@
         doReturn(900L).when(mIrs).getConsumptionLimitLocked();
 
         transaction = new Ledger.Transaction(0, 0, 0, null, 100, 0);
-        agent.recordTransactionLocked(0, "com.test", ledger, transaction, false);
+        mAgent.recordTransactionLocked(0, "com.test", ledger, transaction, false);
         assertEquals(2600, ledger.getCurrentBalance());
 
         transaction = new Ledger.Transaction(0, 0, 0, null, -50, 50);
-        agent.recordTransactionLocked(0, "com.test", ledger, transaction, false);
+        mAgent.recordTransactionLocked(0, "com.test", ledger, transaction, false);
         assertEquals(2550, ledger.getCurrentBalance());
 
         transaction = new Ledger.Transaction(0, 0, 0, null, -200, 100);
-        agent.recordTransactionLocked(0, "com.test", ledger, transaction, false);
+        mAgent.recordTransactionLocked(0, "com.test", ledger, transaction, false);
         assertEquals(2350, ledger.getCurrentBalance());
 
         doReturn(800L).when(mIrs).getConsumptionLimitLocked();
 
         transaction = new Ledger.Transaction(0, 0, 0, null, 100, 0);
-        agent.recordTransactionLocked(0, "com.test", ledger, transaction, false);
+        mAgent.recordTransactionLocked(0, "com.test", ledger, transaction, false);
         assertEquals(2450, ledger.getCurrentBalance());
     }
 
     @Test
     public void testRecordTransaction_MaxSatiatedBalance() {
-        Agent agent = new Agent(mIrs, mScribe, mAnalyst);
         Ledger ledger = new Ledger();
 
         doReturn(1_000_000L).when(mIrs).getConsumptionLimitLocked();
         doReturn(1000L).when(mEconomicPolicy).getMaxSatiatedBalance(anyInt(), anyString());
 
         Ledger.Transaction transaction = new Ledger.Transaction(0, 0, 0, null, 5, 0);
-        agent.recordTransactionLocked(0, "com.test", ledger, transaction, false);
+        mAgent.recordTransactionLocked(0, "com.test", ledger, transaction, false);
         assertEquals(5, ledger.getCurrentBalance());
 
         transaction = new Ledger.Transaction(0, 0, 0, null, 995, 0);
-        agent.recordTransactionLocked(0, "com.test", ledger, transaction, false);
+        mAgent.recordTransactionLocked(0, "com.test", ledger, transaction, false);
         assertEquals(1000, ledger.getCurrentBalance());
 
         transaction = new Ledger.Transaction(0, 0, 0, null, -500, 250);
-        agent.recordTransactionLocked(0, "com.test", ledger, transaction, false);
+        mAgent.recordTransactionLocked(0, "com.test", ledger, transaction, false);
         assertEquals(500, ledger.getCurrentBalance());
 
         transaction = new Ledger.Transaction(0, 0, 0, null, 999_500L, 1000);
-        agent.recordTransactionLocked(0, "com.test", ledger, transaction, false);
+        mAgent.recordTransactionLocked(0, "com.test", ledger, transaction, false);
         assertEquals(1_000, ledger.getCurrentBalance());
 
         // Shouldn't change in normal operation, but adding test case in case it does.
         doReturn(900L).when(mEconomicPolicy).getMaxSatiatedBalance(anyInt(), anyString());
 
         transaction = new Ledger.Transaction(0, 0, 0, null, 500, 0);
-        agent.recordTransactionLocked(0, "com.test", ledger, transaction, false);
+        mAgent.recordTransactionLocked(0, "com.test", ledger, transaction, false);
         assertEquals(1_000, ledger.getCurrentBalance());
 
         transaction = new Ledger.Transaction(0, 0, 0, null, -1001, 500);
-        agent.recordTransactionLocked(0, "com.test", ledger, transaction, false);
+        mAgent.recordTransactionLocked(0, "com.test", ledger, transaction, false);
         assertEquals(-1, ledger.getCurrentBalance());
     }
 }
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/companion/virtual/audio/VirtualAudioControllerTest.java b/services/tests/servicestests/src/com/android/server/companion/virtual/audio/VirtualAudioControllerTest.java
index 78655a5..c40ad28 100644
--- a/services/tests/servicestests/src/com/android/server/companion/virtual/audio/VirtualAudioControllerTest.java
+++ b/services/tests/servicestests/src/com/android/server/companion/virtual/audio/VirtualAudioControllerTest.java
@@ -79,9 +79,9 @@
                         SYSTEM_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS,
                         /* allowedUsers= */ new ArraySet<>(),
                         /* activityLaunchAllowedByDefault= */ true,
-                        /* activityPolicyExceptions= */ new ArraySet<>(),
+                        /* activityPolicyExemptions= */ new ArraySet<>(),
                         /* crossTaskNavigationAllowedByDefault= */ true,
-                        /* crossTaskNavigationExceptions= */ new ArraySet<>(),
+                        /* crossTaskNavigationExemptions= */ new ArraySet<>(),
                         /* activityListener= */ null,
                         /* pipBlockedCallback= */ null,
                         /* activityBlockedCallback= */ null,
diff --git a/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceUserInfoTest.java b/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceUserInfoTest.java
index 2273fcd..9f75cf8 100644
--- a/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceUserInfoTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/UserManagerServiceUserInfoTest.java
@@ -113,6 +113,42 @@
         assertUserInfoEquals(data.info, read.info, /* parcelCopy= */ false);
     }
 
+    /** Tests that device policy restrictions are written/read properly. */
+    @Test
+    public void testWriteReadDevicePolicyUserRestrictions() throws Exception {
+        final String globalRestriction = UserManager.DISALLOW_FACTORY_RESET;
+        final String localRestriction = UserManager.DISALLOW_CONFIG_DATE_TIME;
+
+        UserData data = new UserData();
+        data.info = createUser(100, FLAG_FULL, "A type");
+
+        mUserManagerService.putUserInfo(data.info);
+
+        // Set a global and user restriction so they get written out to the user file.
+        setUserRestrictions(data.info.id, globalRestriction, localRestriction, true);
+
+        ByteArrayOutputStream baos = new ByteArrayOutputStream();
+        DataOutputStream out = new DataOutputStream(baos);
+        mUserManagerService.writeUserLP(data, out);
+        byte[] bytes = baos.toByteArray();
+
+        // Clear the restrictions to see if they are properly read in from the user file.
+        setUserRestrictions(data.info.id, globalRestriction, localRestriction, false);
+
+        mUserManagerService.readUserLP(data.info.id, new ByteArrayInputStream(bytes));
+        assertTrue(mUserManagerService.hasUserRestrictionOnAnyUser(globalRestriction));
+        assertTrue(mUserManagerService.hasUserRestrictionOnAnyUser(localRestriction));
+    }
+
+    /** Sets a global and local restriction and verifies they were set properly **/
+    private void setUserRestrictions(int id, String global, String local, boolean enabled) {
+        mUserManagerService.setUserRestrictionInner(UserHandle.USER_ALL, global, enabled);
+        assertEquals(mUserManagerService.hasUserRestrictionOnAnyUser(global), enabled);
+
+        mUserManagerService.setUserRestrictionInner(id, local, enabled);
+        assertEquals(mUserManagerService.hasUserRestrictionOnAnyUser(local), enabled);
+    }
+
     @Test
     public void testParcelUnparcelUserInfo() throws Exception {
         UserInfo info = createUser();
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/CompatScaleProviderTest.java b/services/tests/wmtests/src/com/android/server/wm/CompatScaleProviderTest.java
new file mode 100644
index 0000000..96e3cb1
--- /dev/null
+++ b/services/tests/wmtests/src/com/android/server/wm/CompatScaleProviderTest.java
@@ -0,0 +1,166 @@
+/*
+ * 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.wm;
+
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.mock;
+import static org.junit.Assert.assertThrows;
+import static org.mockito.Mockito.inOrder;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+
+import android.platform.test.annotations.Presubmit;
+
+import androidx.test.filters.SmallTest;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.InOrder;
+
+/**
+ * Tests for the {@link CompatScaleProvider} interface.
+ * See {@link CompatModePackages} class for implementation.
+ *
+ * Build/Install/Run:
+ * atest WmTests:CompatScaleProviderTest
+ */
+@SmallTest
+@Presubmit
+public class CompatScaleProviderTest extends SystemServiceTestsBase {
+    private static final String TEST_PACKAGE = "compat.mode.packages";
+    static final int TEST_USER_ID = 1;
+
+    private ActivityTaskManagerService mAtm;
+
+    /**
+     * setup method before every test.
+     */
+    @Before
+    public void setUp() {
+        mAtm = mSystemServicesTestRule.getActivityTaskManagerService();
+    }
+
+    /**
+     * Registering a {@link CompatScaleProvider} with an invalid id should throw an exception.
+     */
+    @Test
+    public void registerCompatScaleProviderWithInvalidId() {
+        CompatScaleProvider compatScaleProvider = mock(CompatScaleProvider.class);
+        assertThrows(
+                IllegalArgumentException.class,
+                () ->  mAtm.registerCompatScaleProvider(-1, compatScaleProvider)
+        );
+    }
+
+    /**
+     * Registering a {@code null} {@link CompatScaleProvider} should throw an exception.
+     */
+    @Test
+    public void registerCompatScaleProviderFailIfCallbackIsNull() {
+        assertThrows(
+                IllegalArgumentException.class,
+                () ->  mAtm.registerCompatScaleProvider(
+                            CompatScaleProvider.COMPAT_SCALE_MODE_PRODUCT, null)
+        );
+    }
+
+    /**
+     * Registering a {@link CompatScaleProvider} with a already registered id should throw an
+     * exception.
+     */
+    @Test
+    public void registerCompatScaleProviderFailIfIdIsAlreadyRegistered() {
+        CompatScaleProvider compatScaleProvider = mock(CompatScaleProvider.class);
+        mAtm.registerCompatScaleProvider(CompatScaleProvider.COMPAT_SCALE_MODE_PRODUCT,
+                compatScaleProvider);
+        assertThrows(
+                IllegalArgumentException.class,
+                () ->  mAtm.registerCompatScaleProvider(
+                            CompatScaleProvider.COMPAT_SCALE_MODE_PRODUCT, compatScaleProvider)
+        );
+        mAtm.unregisterCompatScaleProvider(CompatScaleProvider.COMPAT_SCALE_MODE_PRODUCT);
+    }
+
+    /**
+     * Successfully registering a {@link CompatScaleProvider} with should result in callbacks
+     * getting called.
+     */
+    @Test
+    public void registerCompatScaleProviderSuccessfully() {
+        CompatScaleProvider compatScaleProvider = mock(CompatScaleProvider.class);
+        mAtm.registerCompatScaleProvider(CompatScaleProvider.COMPAT_SCALE_MODE_PRODUCT,
+                compatScaleProvider);
+        mAtm.mCompatModePackages.getCompatScale(TEST_PACKAGE, TEST_USER_ID);
+        verify(compatScaleProvider, times(1)).getCompatScale(TEST_PACKAGE, TEST_USER_ID);
+        mAtm.unregisterCompatScaleProvider(CompatScaleProvider.COMPAT_SCALE_MODE_PRODUCT);
+    }
+
+    /**
+     * Unregistering a {@link CompatScaleProvider} with a unregistered id should throw an exception.
+     */
+    @Test
+    public void unregisterCompatScaleProviderFailIfIdNotRegistered() {
+        assertThrows(
+                IllegalArgumentException.class,
+                () ->  mAtm.unregisterCompatScaleProvider(
+                            CompatScaleProvider.COMPAT_SCALE_MODE_PRODUCT)
+        );
+    }
+
+    /**
+     * Unregistering a {@link CompatScaleProvider} with an invalid id should throw an exception.
+     */
+    @Test
+    public void unregisterCompatScaleProviderFailIfIdNotInRange() {
+        assertThrows(
+                IllegalArgumentException.class,
+                () ->  mAtm.unregisterCompatScaleProvider(-1)
+        );
+    }
+
+    /**
+     * Successfully unregistering a {@link CompatScaleProvider} should stop the callbacks from
+     * getting called.
+     */
+    @Test
+    public void unregisterCompatScaleProviderSuccessfully() {
+        CompatScaleProvider compatScaleProvider = mock(CompatScaleProvider.class);
+        mAtm.registerCompatScaleProvider(CompatScaleProvider.COMPAT_SCALE_MODE_PRODUCT,
+                compatScaleProvider);
+        mAtm.unregisterCompatScaleProvider(CompatScaleProvider.COMPAT_SCALE_MODE_PRODUCT);
+        mAtm.mCompatModePackages.getCompatScale(TEST_PACKAGE, TEST_USER_ID);
+        verify(compatScaleProvider, never()).getCompatScale(TEST_PACKAGE, TEST_USER_ID);
+    }
+
+    /**
+     * Order of calling {@link CompatScaleProvider} is same as the id that was used for
+     * registering it.
+     */
+    @Test
+    public void registerCompatScaleProviderRespectsOrderId() {
+        CompatScaleProvider gameModeCompatScaleProvider = mock(CompatScaleProvider.class);
+        CompatScaleProvider productCompatScaleProvider = mock(CompatScaleProvider.class);
+        mAtm.registerCompatScaleProvider(CompatScaleProvider.COMPAT_SCALE_MODE_GAME,
+                gameModeCompatScaleProvider);
+        mAtm.registerCompatScaleProvider(CompatScaleProvider.COMPAT_SCALE_MODE_PRODUCT,
+                productCompatScaleProvider);
+        mAtm.mCompatModePackages.getCompatScale(TEST_PACKAGE, TEST_USER_ID);
+        InOrder inOrder = inOrder(gameModeCompatScaleProvider, productCompatScaleProvider);
+        inOrder.verify(gameModeCompatScaleProvider).getCompatScale(TEST_PACKAGE, TEST_USER_ID);
+        inOrder.verify(productCompatScaleProvider).getCompatScale(TEST_PACKAGE, TEST_USER_ID);
+    }
+}
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 c84eab3..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,11 @@
 
 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;
 import static android.view.Display.DEFAULT_DISPLAY;
 import static android.view.Display.INVALID_DISPLAY;
@@ -77,6 +82,7 @@
 @RunWith(WindowTestRunner.class)
 public class ContentRecorderTests extends WindowTestsBase {
     private static IBinder sTaskWindowContainerToken;
+    private DisplayContent mVirtualDisplayContent;
     private Task mTask;
     private final ContentRecordingSession mDisplaySession =
             ContentRecordingSession.createDisplaySession(DEFAULT_DISPLAY);
@@ -107,11 +113,11 @@
         displayInfo.logicalWidth = sSurfaceSize.x;
         displayInfo.logicalHeight = sSurfaceSize.y;
         displayInfo.state = STATE_ON;
-        final DisplayContent virtualDisplayContent = createNewDisplay(displayInfo);
-        final int displayId = virtualDisplayContent.getDisplayId();
-        mContentRecorder = new ContentRecorder(virtualDisplayContent,
+        mVirtualDisplayContent = createNewDisplay(displayInfo);
+        final int displayId = mVirtualDisplayContent.getDisplayId();
+        mContentRecorder = new ContentRecorder(mVirtualDisplayContent,
                 mMediaProjectionManagerWrapper);
-        spyOn(virtualDisplayContent);
+        spyOn(mVirtualDisplayContent);
 
         // GIVEN MediaProjection has already initialized the WindowToken of the DisplayArea to
         // record.
@@ -119,7 +125,7 @@
         mDisplaySession.setDisplayToRecord(mDefaultDisplay.mDisplayId);
 
         // GIVEN there is a window token associated with a task to record.
-        sTaskWindowContainerToken = setUpTaskWindowContainerToken(virtualDisplayContent);
+        sTaskWindowContainerToken = setUpTaskWindowContainerToken(mVirtualDisplayContent);
         mTaskSession = ContentRecordingSession.createTaskSession(sTaskWindowContainerToken);
         mTaskSession.setVirtualDisplayId(displayId);
 
@@ -252,7 +258,11 @@
     public void testOnConfigurationChanged_resizesSurface() {
         mContentRecorder.setContentRecordingSession(mDisplaySession);
         mContentRecorder.updateRecording();
-        mContentRecorder.onConfigurationChanged(ORIENTATION_PORTRAIT);
+        // Ensure a different orientation when we check if something has changed.
+        @Configuration.Orientation final int lastOrientation =
+                mDisplayContent.getConfiguration().orientation == ORIENTATION_PORTRAIT
+                        ? ORIENTATION_LANDSCAPE : ORIENTATION_PORTRAIT;
+        mContentRecorder.onConfigurationChanged(lastOrientation);
 
         verify(mTransaction, atLeast(2)).setPosition(eq(mRecordedSurface), anyFloat(),
                 anyFloat());
@@ -261,12 +271,53 @@
     }
 
     @Test
+    public void testOnConfigurationChanged_resizesVirtualDisplay() {
+        final int newWidth = 55;
+        mContentRecorder.setContentRecordingSession(mDisplaySession);
+        mContentRecorder.updateRecording();
+
+        // The user rotates the device, so the host app resizes the virtual display for the capture.
+        resizeDisplay(mDisplayContent, newWidth, sSurfaceSize.y);
+        resizeDisplay(mVirtualDisplayContent, newWidth, sSurfaceSize.y);
+        mContentRecorder.onConfigurationChanged(mDisplayContent.getConfiguration().orientation);
+
+        verify(mTransaction, atLeast(2)).setPosition(eq(mRecordedSurface), anyFloat(),
+                anyFloat());
+        verify(mTransaction, atLeast(2)).setMatrix(eq(mRecordedSurface), anyFloat(), anyFloat(),
+                anyFloat(), anyFloat());
+    }
+
+    @Test
+    public void testOnConfigurationChanged_rotateVirtualDisplay() {
+        mContentRecorder.setContentRecordingSession(mDisplaySession);
+        mContentRecorder.updateRecording();
+
+        // Change a value that we shouldn't rely upon; it has the wrong type.
+        mVirtualDisplayContent.setOverrideOrientation(SCREEN_ORIENTATION_FULL_SENSOR);
+        mContentRecorder.onConfigurationChanged(
+                mVirtualDisplayContent.getConfiguration().orientation);
+
+        // No resize is issued, only the initial transformations when we started recording.
+        verify(mTransaction).setPosition(eq(mRecordedSurface), anyFloat(),
+                anyFloat());
+        verify(mTransaction).setMatrix(eq(mRecordedSurface), anyFloat(), anyFloat(),
+                anyFloat(), anyFloat());
+    }
+
+    @Test
     public void testOnTaskOrientationConfigurationChanged_resizesSurface() {
         mContentRecorder.setContentRecordingSession(mTaskSession);
         mContentRecorder.updateRecording();
 
         Configuration config = mTask.getConfiguration();
-        config.orientation = ORIENTATION_PORTRAIT;
+        // Ensure a different orientation when we compare.
+        @Configuration.Orientation final int orientation =
+                config.orientation == ORIENTATION_PORTRAIT ? ORIENTATION_LANDSCAPE
+                        : ORIENTATION_PORTRAIT;
+        final Rect lastBounds = config.windowConfiguration.getBounds();
+        config.orientation = orientation;
+        config.windowConfiguration.setBounds(
+                new Rect(0, 0, lastBounds.height(), lastBounds.width()));
         mTask.onConfigurationChanged(config);
 
         verify(mTransaction, atLeast(2)).setPosition(eq(mRecordedSurface), anyFloat(),
@@ -279,13 +330,15 @@
     public void testOnTaskBoundsConfigurationChanged_notifiesCallback() {
         mTask.getRootTask().setWindowingMode(WindowConfiguration.WINDOWING_MODE_MULTI_WINDOW);
 
+        final int minWidth = 222;
+        final int minHeight = 777;
         final int recordedWidth = 333;
         final int recordedHeight = 999;
 
         final ActivityInfo info = new ActivityInfo();
         info.windowLayout = new ActivityInfo.WindowLayout(-1 /* width */,
                 -1 /* widthFraction */, -1 /* height */, -1 /* heightFraction */,
-                Gravity.NO_GRAVITY, recordedWidth, recordedHeight);
+                Gravity.NO_GRAVITY, minWidth, minHeight);
         mTask.setMinDimensions(info);
 
         // WHEN a recording is ongoing.
@@ -311,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);
@@ -335,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/InsetsPolicyTest.java b/services/tests/wmtests/src/com/android/server/wm/InsetsPolicyTest.java
index 994dcf1..ffa1ed9 100644
--- a/services/tests/wmtests/src/com/android/server/wm/InsetsPolicyTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/InsetsPolicyTest.java
@@ -23,8 +23,6 @@
 import static android.view.WindowInsets.Type.navigationBars;
 import static android.view.WindowInsets.Type.statusBars;
 import static android.view.WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
-import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_FORCE_SHOW_STATUS_BAR;
-import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_STATUS_FORCE_SHOW_NAVIGATION;
 import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION;
 import static android.view.WindowManager.LayoutParams.TYPE_NAVIGATION_BAR;
 import static android.view.WindowManager.LayoutParams.TYPE_NOTIFICATION_SHADE;
@@ -108,7 +106,7 @@
 
     @Test
     public void testControlsForDispatch_forceStatusBarVisible() {
-        addStatusBar().mAttrs.privateFlags |= PRIVATE_FLAG_FORCE_SHOW_STATUS_BAR;
+        addStatusBar().mAttrs.forciblyShownTypes |= statusBars();
         addNavigationBar();
 
         final InsetsSourceControl[] controls = addAppWindowAndGetControlsForDispatch();
@@ -120,8 +118,8 @@
 
     @Test
     public void testControlsForDispatch_statusBarForceShowNavigation() {
-        addWindow(TYPE_NOTIFICATION_SHADE, "notificationShade").mAttrs.privateFlags |=
-                PRIVATE_FLAG_STATUS_FORCE_SHOW_NAVIGATION;
+        addWindow(TYPE_NOTIFICATION_SHADE, "notificationShade").mAttrs.forciblyShownTypes |=
+                navigationBars();
         addStatusBar();
         addNavigationBar();
 
@@ -135,7 +133,7 @@
     @Test
     public void testControlsForDispatch_statusBarForceShowNavigation_butFocusedAnyways() {
         WindowState notifShade = addWindow(TYPE_NOTIFICATION_SHADE, "notificationShade");
-        notifShade.mAttrs.privateFlags |= PRIVATE_FLAG_STATUS_FORCE_SHOW_NAVIGATION;
+        notifShade.mAttrs.forciblyShownTypes |= navigationBars();
         addNavigationBar();
 
         mDisplayContent.getInsetsPolicy().updateBarControlTarget(notifShade);
@@ -329,6 +327,7 @@
                 addNavigationBar().getControllableInsetProvider().getSource();
         statusBarSource.setVisible(false);
         navBarSource.setVisible(false);
+        mAppWindow.setRequestedVisibleTypes(0, navigationBars() | statusBars());
         mAppWindow.mAboveInsetsState.addSource(navBarSource);
         mAppWindow.mAboveInsetsState.addSource(statusBarSource);
         final InsetsPolicy policy = mDisplayContent.getInsetsPolicy();
@@ -389,6 +388,50 @@
         assertFalse(policy.isTransient(navigationBars()));
     }
 
+    @Test
+    public void testFakeControlTarget_overrideVisibilityReceivedByWindows() {
+        final WindowState statusBar = addStatusBar();
+        final InsetsSourceProvider statusBarProvider = statusBar.getControllableInsetProvider();
+        statusBar.mSession.mCanForceShowingInsets = true;
+        statusBar.setHasSurface(true);
+        statusBarProvider.setServerVisible(true);
+
+        final InsetsSource statusBarSource = statusBarProvider.getSource();
+        final int statusBarId = statusBarSource.getId();
+        assertTrue(statusBarSource.isVisible());
+
+        final WindowState app1 = addWindow(TYPE_APPLICATION, "app1");
+        app1.mAboveInsetsState.addSource(statusBarSource);
+        assertTrue(app1.getInsetsState().peekSource(statusBarId).isVisible());
+
+        final WindowState app2 = addWindow(TYPE_APPLICATION, "app2");
+        app2.mAboveInsetsState.addSource(statusBarSource);
+        assertTrue(app2.getInsetsState().peekSource(statusBarId).isVisible());
+
+        app2.setRequestedVisibleTypes(0, navigationBars() | statusBars());
+        mDisplayContent.getInsetsPolicy().updateBarControlTarget(app2);
+        waitUntilWindowAnimatorIdle();
+
+        // app2 is the real control target now. It can override the visibility of all sources that
+        // it controls.
+        assertFalse(statusBarSource.isVisible());
+        assertFalse(app1.getInsetsState().peekSource(statusBarId).isVisible());
+        assertFalse(app2.getInsetsState().peekSource(statusBarId).isVisible());
+
+        statusBar.mAttrs.forciblyShownTypes = statusBars();
+        mDisplayContent.getDisplayPolicy().applyPostLayoutPolicyLw(
+                statusBar, statusBar.mAttrs, null, null);
+        mDisplayContent.getInsetsPolicy().updateBarControlTarget(app2);
+        waitUntilWindowAnimatorIdle();
+
+        // app2 is the fake control target now. It can only override the visibility of sources
+        // received by windows, but not the raw source.
+        assertTrue(statusBarSource.isVisible());
+        assertFalse(app1.getInsetsState().peekSource(statusBarId).isVisible());
+        assertFalse(app2.getInsetsState().peekSource(statusBarId).isVisible());
+
+    }
+
     private WindowState addNavigationBar() {
         final Binder owner = new Binder();
         final WindowState win = createWindow(null, TYPE_NAVIGATION_BAR, "navBar");
diff --git a/services/tests/wmtests/src/com/android/server/wm/InsetsStateControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/InsetsStateControllerTest.java
index 114796d..2085d61 100644
--- a/services/tests/wmtests/src/com/android/server/wm/InsetsStateControllerTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/InsetsStateControllerTest.java
@@ -367,9 +367,11 @@
         doReturn(rotatedState).when(app.mToken).getFixedRotationTransformInsetsState();
         assertTrue(rotatedState.isSourceOrDefaultVisible(ID_STATUS_BAR, statusBars()));
 
-        provider.getSource().setVisible(false);
+        app.setRequestedVisibleTypes(0, statusBars());
+        mDisplayContent.getInsetsPolicy().updateBarControlTarget(app);
         mDisplayContent.getInsetsPolicy().showTransient(statusBars(),
                 true /* isGestureOnSystemBar */);
+        waitUntilWindowAnimatorIdle();
 
         assertTrue(mDisplayContent.getInsetsPolicy().isTransient(statusBars()));
         assertFalse(app.getInsetsState().isSourceOrDefaultVisible(ID_STATUS_BAR, statusBars()));
diff --git a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
index 94de462..8f68c0f 100644
--- a/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/SizeCompatTests.java
@@ -2411,8 +2411,9 @@
                 .setUid(android.os.Process.myUid())
                 .build();
         activity.mDisplayContent.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */);
-        activity.mWmService.mLetterboxConfiguration
-                .setUserAppAspectRatioSettingsOverrideEnabled(enabled);
+        spyOn(activity.mWmService.mLetterboxConfiguration);
+        doReturn(enabled).when(activity.mWmService.mLetterboxConfiguration)
+                .isUserAppAspectRatioSettingsEnabled();
         // Set user aspect ratio override
         final IPackageManager pm = mAtm.getPackageManager();
         try {
@@ -4251,6 +4252,7 @@
         // Set up a display in landscape with a fixed-orientation PORTRAIT app
         setUpDisplaySizeWithApp(2800, 1400);
         mActivity.mDisplayContent.setIgnoreOrientationRequest(true /* ignoreOrientationRequest */);
+        mWm.mLetterboxConfiguration.setIsAutomaticReachabilityInBookModeEnabled(false);
         mWm.mLetterboxConfiguration.setLetterboxHorizontalPositionMultiplier(0.5f);
         prepareUnresizable(mActivity, 1.75f, SCREEN_ORIENTATION_PORTRAIT);
 
diff --git a/services/usage/java/com/android/server/usage/UsageStatsDatabase.java b/services/usage/java/com/android/server/usage/UsageStatsDatabase.java
index f1c5865..b028b47 100644
--- a/services/usage/java/com/android/server/usage/UsageStatsDatabase.java
+++ b/services/usage/java/com/android/server/usage/UsageStatsDatabase.java
@@ -22,6 +22,7 @@
 import android.app.usage.UsageStats;
 import android.app.usage.UsageStatsManager;
 import android.os.Build;
+import android.os.SystemClock;
 import android.os.SystemProperties;
 import android.util.ArrayMap;
 import android.util.ArraySet;
@@ -1567,6 +1568,13 @@
         }
     }
 
+    void deleteDataFor(String pkg) {
+        // reuse the existing prune method to delete data for the specified package.
+        // we'll use the current timestamp so that all events before now get pruned.
+        prunePackagesDataOnUpgrade(
+                new HashMap<>(Collections.singletonMap(pkg, SystemClock.elapsedRealtime())));
+    }
+
     IntervalStats readIntervalStatsForFile(int interval, long fileName) {
         synchronized (mLock) {
             final IntervalStats stats = new IntervalStats();
diff --git a/services/usage/java/com/android/server/usage/UsageStatsService.java b/services/usage/java/com/android/server/usage/UsageStatsService.java
index 90b798c..7db32a9 100644
--- a/services/usage/java/com/android/server/usage/UsageStatsService.java
+++ b/services/usage/java/com/android/server/usage/UsageStatsService.java
@@ -2043,6 +2043,12 @@
         mAppStandby.clearLastUsedTimestampsForTest(packageName, userId);
     }
 
+    void deletePackageData(@NonNull String packageName, @UserIdInt int userId) {
+        synchronized (mLock) {
+            mUserState.get(userId).deleteDataFor(packageName);
+        }
+    }
+
     private final class BinderService extends IUsageStatsManager.Stub {
 
         private boolean hasPermission(String callingPackage) {
diff --git a/services/usage/java/com/android/server/usage/UsageStatsShellCommand.java b/services/usage/java/com/android/server/usage/UsageStatsShellCommand.java
index 772b22a..4cb31f9 100644
--- a/services/usage/java/com/android/server/usage/UsageStatsShellCommand.java
+++ b/services/usage/java/com/android/server/usage/UsageStatsShellCommand.java
@@ -38,6 +38,8 @@
         switch (cmd) {
             case "clear-last-used-timestamps":
                 return runClearLastUsedTimestamps();
+            case "delete-package-data":
+                return deletePackageData();
             default:
                 return handleDefaultCommands(cmd);
         }
@@ -51,14 +53,38 @@
         pw.println("    Print this help text.");
         pw.println();
         pw.println("clear-last-used-timestamps PACKAGE_NAME [-u | --user USER_ID]");
-        pw.println("    Clears any existing usage data for the given package.");
+        pw.println("    Clears the last used timestamps for the given package.");
+        pw.println();
+        pw.println("delete-package-data PACKAGE_NAME [-u | --user USER_ID]");
+        pw.println("    Deletes all the usage stats for the given package.");
         pw.println();
     }
 
     @SuppressLint("AndroidFrameworkRequiresPermission")
     private int runClearLastUsedTimestamps() {
         final String packageName = getNextArgRequired();
+        final int userId = getUserId();
+        if (userId == -1) {
+            return -1;
+        }
 
+        mService.clearLastUsedTimestamps(packageName, userId);
+        return 0;
+    }
+
+    @SuppressLint("AndroidFrameworkRequiresPermission")
+    private int deletePackageData() {
+        final String packageName = getNextArgRequired();
+        final int userId = getUserId();
+        if (userId == -1) {
+            return -1;
+        }
+
+        mService.deletePackageData(packageName, userId);
+        return 0;
+    }
+
+    private int getUserId() {
         int userId = UserHandle.USER_CURRENT;
         String opt;
         while ((opt = getNextOption()) != null) {
@@ -72,8 +98,6 @@
         if (userId == UserHandle.USER_CURRENT) {
             userId = ActivityManager.getCurrentUser();
         }
-
-        mService.clearLastUsedTimestamps(packageName, userId);
-        return 0;
+        return userId;
     }
 }
diff --git a/services/usage/java/com/android/server/usage/UserUsageStatsService.java b/services/usage/java/com/android/server/usage/UserUsageStatsService.java
index fd56b6e..7d2e1a4 100644
--- a/services/usage/java/com/android/server/usage/UserUsageStatsService.java
+++ b/services/usage/java/com/android/server/usage/UserUsageStatsService.java
@@ -974,6 +974,10 @@
         mDatabase.dumpMappings(ipw);
     }
 
+    void deleteDataFor(String pkg) {
+        mDatabase.deleteDataFor(pkg);
+    }
+
     void dumpFile(IndentingPrintWriter ipw, String[] args) {
         if (args == null || args.length == 0) {
             // dump all files for every interval for specified user
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)
diff --git a/tests/TrustTests/Android.bp b/tests/TrustTests/Android.bp
index a1b888a..c216bce 100644
--- a/tests/TrustTests/Android.bp
+++ b/tests/TrustTests/Android.bp
@@ -25,6 +25,7 @@
         "androidx.test.rules",
         "androidx.test.ext.junit",
         "androidx.test.uiautomator_uiautomator",
+        "flag-junit",
         "mockito-target-minus-junit4",
         "servicestests-utils",
         "truth-prebuilt",
diff --git a/tests/TrustTests/src/android/trust/test/GrantAndRevokeTrustTest.kt b/tests/TrustTests/src/android/trust/test/GrantAndRevokeTrustTest.kt
index f864fed..1dfd5c0 100644
--- a/tests/TrustTests/src/android/trust/test/GrantAndRevokeTrustTest.kt
+++ b/tests/TrustTests/src/android/trust/test/GrantAndRevokeTrustTest.kt
@@ -16,6 +16,10 @@
 
 package android.trust.test
 
+import android.content.pm.PackageManager
+import android.platform.test.annotations.RequiresFlagsDisabled
+import android.platform.test.annotations.RequiresFlagsEnabled
+import android.platform.test.flag.junit.DeviceFlagsValueProvider
 import android.service.trust.GrantTrustResult
 import android.trust.BaseTrustAgentService
 import android.trust.TrustTestActivity
@@ -27,6 +31,7 @@
 import androidx.test.platform.app.InstrumentationRegistry.getInstrumentation
 import androidx.test.uiautomator.UiDevice
 import com.android.server.testutils.mock
+import org.junit.Assume.assumeFalse
 import org.junit.Before
 import org.junit.Rule
 import org.junit.Test
@@ -45,6 +50,7 @@
     private val activityScenarioRule = ActivityScenarioRule(TrustTestActivity::class.java)
     private val lockStateTrackingRule = LockStateTrackingRule()
     private val trustAgentRule = TrustAgentRule<GrantAndRevokeTrustAgent>()
+    private val packageManager = getInstrumentation().getTargetContext().getPackageManager()
 
     @get:Rule
     val rule: RuleChain = RuleChain
@@ -52,6 +58,7 @@
         .around(ScreenLockRule())
         .around(lockStateTrackingRule)
         .around(trustAgentRule)
+        .around(DeviceFlagsValueProvider.createCheckFlagsRule())
 
     @Before
     fun manageTrust() {
@@ -72,7 +79,7 @@
         trustAgentRule.agent.grantTrust(GRANT_MESSAGE, 10000, 0) {}
         uiDevice.sleep()
 
-        lockStateTrackingRule.assertUnlocked()
+        lockStateTrackingRule.assertUnlockedAndTrusted()
     }
 
     @Test
@@ -86,6 +93,51 @@
     }
 
     @Test
+    @RequiresFlagsEnabled(android.security.Flags.FLAG_FIX_UNLOCKED_DEVICE_REQUIRED_KEYS)
+    fun grantCannotActivelyUnlockDevice() {
+        // On automotive, trust agents can actively unlock the device.
+        assumeFalse(packageManager.hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE))
+
+        // Lock the device.
+        uiDevice.sleep()
+        lockStateTrackingRule.assertLocked()
+
+        // Grant trust.
+        trustAgentRule.agent.grantTrust(GRANT_MESSAGE, 10000, 0) {}
+
+        // The grant should not have unlocked the device.  Wait a bit so that
+        // TrustManagerService probably will have finished processing the grant.
+        await()
+        lockStateTrackingRule.assertLocked()
+
+        // Turn the screen on and off to cause TrustManagerService to refresh
+        // its deviceLocked state.  Then verify the state is still locked.  This
+        // part failed before the fix for b/296464083.
+        uiDevice.wakeUp()
+        uiDevice.sleep()
+        await()
+        lockStateTrackingRule.assertLocked()
+    }
+
+    @Test
+    @RequiresFlagsDisabled(android.security.Flags.FLAG_FIX_UNLOCKED_DEVICE_REQUIRED_KEYS)
+    fun grantCouldCauseWrongDeviceLockedStateDueToBug() {
+        // On automotive, trust agents can actively unlock the device.
+        assumeFalse(packageManager.hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE))
+
+        // Verify that b/296464083 exists.  That is, when the device is locked
+        // and a trust agent grants trust, the deviceLocked state incorrectly
+        // becomes false even though the device correctly remains locked.
+        uiDevice.sleep()
+        lockStateTrackingRule.assertLocked()
+        trustAgentRule.agent.grantTrust(GRANT_MESSAGE, 10000, 0) {}
+        uiDevice.wakeUp()
+        uiDevice.sleep()
+        await()
+        lockStateTrackingRule.assertUnlockedButNotReally()
+    }
+
+    @Test
     fun grantDoesNotCallBack() {
         val callback = mock<(GrantTrustResult) -> Unit>()
         trustAgentRule.agent.grantTrust(GRANT_MESSAGE, 0, 0, callback)
diff --git a/tests/TrustTests/src/android/trust/test/TemporaryAndRenewableTrustTest.kt b/tests/TrustTests/src/android/trust/test/TemporaryAndRenewableTrustTest.kt
index ae72247..96362b8 100644
--- a/tests/TrustTests/src/android/trust/test/TemporaryAndRenewableTrustTest.kt
+++ b/tests/TrustTests/src/android/trust/test/TemporaryAndRenewableTrustTest.kt
@@ -102,7 +102,7 @@
         trustAgentRule.agent.grantTrust(
             GRANT_MESSAGE, 0, FLAG_GRANT_TRUST_TEMPORARY_AND_RENEWABLE) {}
 
-        lockStateTrackingRule.assertUnlocked()
+        lockStateTrackingRule.assertUnlockedAndTrusted()
     }
 
     @Test
@@ -125,7 +125,7 @@
             Log.i(TAG, "Callback received; status=${it.status}")
             result = it
         }
-        lockStateTrackingRule.assertUnlocked()
+        lockStateTrackingRule.assertUnlockedAndTrusted()
 
         wait("callback triggered") { result?.status == STATUS_UNLOCKED_BY_GRANT }
     }
diff --git a/tests/TrustTests/src/android/trust/test/lib/LockStateTrackingRule.kt b/tests/TrustTests/src/android/trust/test/lib/LockStateTrackingRule.kt
index c1a7bd9..5a8f828 100644
--- a/tests/TrustTests/src/android/trust/test/lib/LockStateTrackingRule.kt
+++ b/tests/TrustTests/src/android/trust/test/lib/LockStateTrackingRule.kt
@@ -16,6 +16,7 @@
 
 package android.trust.test.lib
 
+import android.app.KeyguardManager
 import android.app.trust.TrustManager
 import android.content.Context
 import android.util.Log
@@ -26,18 +27,23 @@
 import org.junit.runners.model.Statement
 
 /**
- * Rule for tracking the lock state of the device based on events emitted to [TrustListener].
+ * Rule for tracking the trusted state of the device based on events emitted to
+ * [TrustListener].  Provides helper methods for verifying that the trusted
+ * state has a particular value and is consistent with (a) the keyguard "locked"
+ * (i.e. showing) value when applicable, and (b) the device locked value that is
+ * tracked by TrustManagerService and is queryable via KeyguardManager.
  */
 class LockStateTrackingRule : TestRule {
     private val context: Context = getApplicationContext()
     private val windowManager = checkNotNull(WindowManagerGlobal.getWindowManagerService())
+    private val keyguardManager = context.getSystemService(KeyguardManager::class.java) as KeyguardManager
 
-    @Volatile lateinit var lockState: LockState
+    @Volatile lateinit var trustState: TrustState
         private set
 
     override fun apply(base: Statement, description: Description) = object : Statement() {
         override fun evaluate() {
-            lockState = LockState(locked = windowManager.isKeyguardLocked)
+            trustState = TrustState()
             val trustManager = context.getSystemService(TrustManager::class.java) as TrustManager
             val listener = Listener()
 
@@ -51,12 +57,25 @@
     }
 
     fun assertLocked() {
-        wait("un-locked per TrustListener") { lockState.locked == true }
-        wait("keyguard lock") { windowManager.isKeyguardLocked }
+        wait("device locked") { keyguardManager.isDeviceLocked }
+        // isDeviceLocked implies isKeyguardLocked && !trusted.
+        wait("keyguard locked") { windowManager.isKeyguardLocked }
+        wait("not trusted") { trustState.trusted == false }
     }
 
-    fun assertUnlocked() {
-        wait("locked per TrustListener") { lockState.locked == false }
+    // TODO(b/299298338) remove this when removing FLAG_FIX_UNLOCKED_DEVICE_REQUIRED_KEYS
+    fun assertUnlockedButNotReally() {
+        wait("device unlocked") { !keyguardManager.isDeviceLocked }
+        wait("not trusted") { trustState.trusted == false }
+        wait("keyguard locked") { windowManager.isKeyguardLocked }
+    }
+
+    fun assertUnlockedAndTrusted() {
+        wait("device unlocked") { !keyguardManager.isDeviceLocked }
+        wait("trusted") { trustState.trusted == true }
+        // Can't check for !isKeyguardLocked here, since isKeyguardLocked
+        // returns true in the case where the keyguard is dismissible with
+        // swipe, which is considered "device unlocked"!
     }
 
     inner class Listener : TestTrustListener() {
@@ -68,12 +87,12 @@
             trustGrantedMessages: MutableList<String>
         ) {
             Log.d(TAG, "Device became trusted=$enabled")
-            lockState = lockState.copy(locked = !enabled)
+            trustState = trustState.copy(trusted=enabled)
         }
     }
 
-    data class LockState(
-        val locked: Boolean? = null
+    data class TrustState(
+        val trusted: Boolean? = null
     )
 
     companion object {
diff --git a/tools/aapt/ZipEntry.cpp b/tools/aapt/ZipEntry.cpp
index 5339285..6886993 100644
--- a/tools/aapt/ZipEntry.cpp
+++ b/tools/aapt/ZipEntry.cpp
@@ -18,6 +18,8 @@
 // Access to entries in a Zip archive.
 //
 
+#define _POSIX_THREAD_SAFE_FUNCTIONS // For mingw localtime_r().
+
 #define LOG_TAG "zip"
 
 #include "ZipEntry.h"
@@ -337,39 +339,26 @@
 /*
  * Set the CDE/LFH timestamp from UNIX time.
  */
-void ZipEntry::setModWhen(time_t when)
-{
-#if !defined(_WIN32)
-    struct tm tmResult;
-#endif
-    time_t even;
-    unsigned short zdate, ztime;
-
-    struct tm* ptm;
-
+void ZipEntry::setModWhen(time_t when) {
     /* round up to an even number of seconds */
-    even = (time_t)(((unsigned long)(when) + 1) & (~1));
+    time_t even = (time_t)(((unsigned long)(when) + 1) & (~1));
 
     /* expand */
-#if !defined(_WIN32)
-    ptm = localtime_r(&even, &tmResult);
-#else
-    ptm = localtime(&even);
-#endif
+    struct tm tmResult;
+    struct tm* ptm = localtime_r(&even, &tmResult);
 
     int year;
     year = ptm->tm_year;
     if (year < 80)
         year = 80;
 
-    zdate = (year - 80) << 9 | (ptm->tm_mon+1) << 5 | ptm->tm_mday;
-    ztime = ptm->tm_hour << 11 | ptm->tm_min << 5 | ptm->tm_sec >> 1;
+    unsigned short zdate = (year - 80) << 9 | (ptm->tm_mon + 1) << 5 | ptm->tm_mday;
+    unsigned short ztime = ptm->tm_hour << 11 | ptm->tm_min << 5 | ptm->tm_sec >> 1;
 
     mCDE.mLastModFileTime = mLFH.mLastModFileTime = ztime;
     mCDE.mLastModFileDate = mLFH.mLastModFileDate = zdate;
 }
 
-
 /*
  * ===========================================================================
  *      ZipEntry::LocalFileHeader
diff --git a/tools/aapt2/compile/InlineXmlFormatParser.h b/tools/aapt2/compile/InlineXmlFormatParser.h
index 4300023..3a5161b 100644
--- a/tools/aapt2/compile/InlineXmlFormatParser.h
+++ b/tools/aapt2/compile/InlineXmlFormatParser.h
@@ -21,8 +21,8 @@
 #include <vector>
 
 #include "android-base/macros.h"
-
 #include "process/IResourceTableConsumer.h"
+#include "xml/XmlDom.h"
 
 namespace aapt {