Merge "Introduce InputMethodUtils#splitEnabledImeStr()" into main
diff --git a/apct-tests/perftests/packagemanager/src/android/os/PackageParsingPerfTest.kt b/apct-tests/perftests/packagemanager/src/android/os/PackageParsingPerfTest.kt
index 6d1e6d0..4352c8a 100644
--- a/apct-tests/perftests/packagemanager/src/android/os/PackageParsingPerfTest.kt
+++ b/apct-tests/perftests/packagemanager/src/android/os/PackageParsingPerfTest.kt
@@ -24,10 +24,11 @@
 import android.content.res.TypedArray
 import android.perftests.utils.BenchmarkState
 import android.perftests.utils.PerfStatusReporter
+import android.util.ArraySet
 import androidx.test.filters.LargeTest
+import com.android.internal.pm.parsing.pkg.PackageImpl
+import com.android.internal.pm.pkg.parsing.ParsingPackageUtils
 import com.android.internal.util.ConcurrentUtils
-import com.android.server.pm.parsing.pkg.PackageImpl
-import com.android.server.pm.pkg.parsing.ParsingPackageUtils
 import java.io.File
 import java.io.FileOutputStream
 import java.util.concurrent.ArrayBlockingQueue
@@ -214,7 +215,10 @@
                     path,
                     manifestArray,
                     isCoreApp,
+                    this,
                 )
+                override fun getHiddenApiWhitelistedApps() = ArraySet<String>()
+                override fun getInstallConstraintsAllowlist() = ArraySet<String>()
             })
 
         override fun parseImpl(file: File) =
diff --git a/apex/jobscheduler/service/java/com/android/server/job/controllers/BackgroundJobsController.java b/apex/jobscheduler/service/java/com/android/server/job/controllers/BackgroundJobsController.java
index 03891bb..e3ba50d 100644
--- a/apex/jobscheduler/service/java/com/android/server/job/controllers/BackgroundJobsController.java
+++ b/apex/jobscheduler/service/java/com/android/server/job/controllers/BackgroundJobsController.java
@@ -26,6 +26,7 @@
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentFilter;
+import android.content.pm.PackageManager;
 import android.content.pm.PackageManagerInternal;
 import android.os.SystemClock;
 import android.os.UserHandle;
@@ -319,8 +320,8 @@
             final boolean isStopped = mPackageManagerInternal.isPackageStopped(packageName, uid);
             mPackageStoppedState.add(uid, packageName, isStopped);
             return isStopped;
-        } catch (IllegalArgumentException e) {
-            Slog.d(TAG, "Couldn't determine stopped state for unknown package: " + packageName);
+        } catch (PackageManager.NameNotFoundException e) {
+            Slog.e(TAG, "Couldn't determine stopped state for unknown package: " + packageName);
             return false;
         }
     }
diff --git a/core/api/current.txt b/core/api/current.txt
index 83e3fab..7f261d4 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -5285,9 +5285,10 @@
     field public static final int START_TIMESTAMP_RESERVED_RANGE_DEVELOPER_START = 21; // 0x15
     field public static final int START_TIMESTAMP_RESERVED_RANGE_SYSTEM = 20; // 0x14
     field public static final int START_TIMESTAMP_SURFACEFLINGER_COMPOSITION_COMPLETE = 7; // 0x7
-    field public static final int START_TYPE_COLD = 0; // 0x0
-    field public static final int START_TYPE_HOT = 2; // 0x2
-    field public static final int START_TYPE_WARM = 1; // 0x1
+    field public static final int START_TYPE_COLD = 1; // 0x1
+    field public static final int START_TYPE_HOT = 3; // 0x3
+    field public static final int START_TYPE_UNSET = 0; // 0x0
+    field public static final int START_TYPE_WARM = 2; // 0x2
   }
 
   public final class AsyncNotedAppOp implements android.os.Parcelable {
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index 9f56933..e92564b 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -4091,7 +4091,7 @@
     field @Deprecated public static final int INTENT_FILTER_VERIFICATION_SUCCESS = 1; // 0x1
     field @Deprecated public static final int MASK_PERMISSION_FLAGS = 255; // 0xff
     field public static final int MATCH_ANY_USER = 4194304; // 0x400000
-    field @Deprecated public static final int MATCH_CLONE_PROFILE = 536870912; // 0x20000000
+    field public static final int MATCH_CLONE_PROFILE = 536870912; // 0x20000000
     field @FlaggedApi("android.content.pm.fix_duplicated_flags") public static final long MATCH_CLONE_PROFILE_LONG = 17179869184L; // 0x400000000L
     field public static final int MATCH_FACTORY_ONLY = 2097152; // 0x200000
     field public static final int MATCH_HIDDEN_UNTIL_INSTALLED_COMPONENTS = 536870912; // 0x20000000
@@ -12930,6 +12930,7 @@
     field public static final int CONFIDENCE_LEVEL_LOW = 1; // 0x1
     field public static final int CONFIDENCE_LEVEL_MEDIUM = 2; // 0x2
     field public static final int CONFIDENCE_LEVEL_NONE = 0; // 0x0
+    field @FlaggedApi("android.service.voice.flags.allow_hotword_bump_egress") public static final int CONFIDENCE_LEVEL_VERY_HIGH = 4; // 0x4
     field @NonNull public static final android.os.Parcelable.Creator<android.service.voice.HotwordRejectedResult> CREATOR;
   }
 
@@ -13051,7 +13052,7 @@
     method @NonNull @RequiresPermission(android.Manifest.permission.MANAGE_HOTWORD_DETECTION) public final android.service.voice.HotwordDetector createHotwordDetector(@Nullable android.os.PersistableBundle, @Nullable android.os.SharedMemory, @NonNull java.util.concurrent.Executor, @NonNull android.service.voice.HotwordDetector.Callback);
     method @NonNull @RequiresPermission("android.permission.MANAGE_VOICE_KEYPHRASES") public final android.media.voice.KeyphraseModelManager createKeyphraseModelManager();
     method @NonNull @RequiresPermission(android.Manifest.permission.MANAGE_HOTWORD_DETECTION) public final android.service.voice.VisualQueryDetector createVisualQueryDetector(@Nullable android.os.PersistableBundle, @Nullable android.os.SharedMemory, @NonNull java.util.concurrent.Executor, @NonNull android.service.voice.VisualQueryDetector.Callback);
-    method @FlaggedApi("android.service.voice.flags.allow_training_data_egress_from_hds") @RequiresPermission(android.Manifest.permission.MANAGE_HOTWORD_DETECTION) public void setIsReceiveSandboxedTrainingDataAllowed(boolean);
+    method @FlaggedApi("android.service.voice.flags.allow_training_data_egress_from_hds") @RequiresPermission(android.Manifest.permission.MANAGE_HOTWORD_DETECTION) public void setShouldReceiveSandboxedTrainingData(boolean);
   }
 
 }
@@ -13701,6 +13702,7 @@
     method @NonNull public java.util.List<java.lang.Boolean> areCarrierIdentifiersAllowed(@NonNull java.util.List<android.service.carrier.CarrierIdentifier>);
     method public int describeContents();
     method @NonNull public java.util.List<android.service.carrier.CarrierIdentifier> getAllowedCarriers();
+    method @FlaggedApi("com.android.internal.telephony.flags.carrier_restriction_status") public int getCarrierRestrictionStatus();
     method public int getDefaultCarrierRestriction();
     method @NonNull public java.util.List<android.service.carrier.CarrierIdentifier> getExcludedCarriers();
     method public int getMultiSimPolicy();
@@ -16987,7 +16989,7 @@
     method @FlaggedApi("com.android.internal.telephony.flags.carrier_enabled_satellite_flag") @NonNull @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public java.util.Set<java.lang.Integer> getSatelliteAttachRestrictionReasonsForCarrier(int);
     method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void pollPendingSatelliteDatagrams(@NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Integer>);
     method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void provisionSatelliteService(@NonNull String, @NonNull byte[], @Nullable android.os.CancellationSignal, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Integer>);
-    method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public int registerForNtnSignalStrengthChanged(@NonNull java.util.concurrent.Executor, @NonNull android.telephony.satellite.NtnSignalStrengthCallback);
+    method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void registerForNtnSignalStrengthChanged(@NonNull java.util.concurrent.Executor, @NonNull android.telephony.satellite.NtnSignalStrengthCallback) throws android.telephony.satellite.SatelliteManager.SatelliteException;
     method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public int registerForSatelliteCapabilitiesChanged(@NonNull java.util.concurrent.Executor, @NonNull android.telephony.satellite.SatelliteCapabilitiesCallback);
     method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public int registerForSatelliteDatagram(@NonNull java.util.concurrent.Executor, @NonNull android.telephony.satellite.SatelliteDatagramCallback);
     method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public int registerForSatelliteModemStateChanged(@NonNull java.util.concurrent.Executor, @NonNull android.telephony.satellite.SatelliteStateCallback);
@@ -16999,7 +17001,7 @@
     method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void requestIsSatelliteEnabled(@NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<java.lang.Boolean,android.telephony.satellite.SatelliteManager.SatelliteException>);
     method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void requestIsSatelliteProvisioned(@NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<java.lang.Boolean,android.telephony.satellite.SatelliteManager.SatelliteException>);
     method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public void requestIsSatelliteSupported(@NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<java.lang.Boolean,android.telephony.satellite.SatelliteManager.SatelliteException>);
-    method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") @NonNull @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void requestNtnSignalStrength(@NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<android.telephony.satellite.NtnSignalStrength,android.telephony.satellite.SatelliteManager.SatelliteException>);
+    method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void requestNtnSignalStrength(@NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<android.telephony.satellite.NtnSignalStrength,android.telephony.satellite.SatelliteManager.SatelliteException>);
     method @FlaggedApi("com.android.internal.telephony.flags.carrier_enabled_satellite_flag") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void requestSatelliteAttachEnabledForCarrier(int, boolean, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Integer>);
     method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void requestSatelliteCapabilities(@NonNull java.util.concurrent.Executor, @NonNull android.os.OutcomeReceiver<android.telephony.satellite.SatelliteCapabilities,android.telephony.satellite.SatelliteManager.SatelliteException>);
     method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void requestSatelliteEnabled(boolean, boolean, @NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Integer>);
diff --git a/core/api/system-lint-baseline.txt b/core/api/system-lint-baseline.txt
index dec1ee5..cef11bb 100644
--- a/core/api/system-lint-baseline.txt
+++ b/core/api/system-lint-baseline.txt
@@ -1909,6 +1909,8 @@
     SAM-compatible parameters (such as parameter 1, "listener", in android.view.accessibility.AccessibilityManager.addAccessibilityStateChangeListener) should be last to improve Kotlin interoperability; see https://kotlinlang.org/docs/reference/java-interop.html#sam-conversions
 SamShouldBeLast: android.view.accessibility.AccessibilityManager#addTouchExplorationStateChangeListener(android.view.accessibility.AccessibilityManager.TouchExplorationStateChangeListener, android.os.Handler):
     SAM-compatible parameters (such as parameter 1, "listener", in android.view.accessibility.AccessibilityManager.addTouchExplorationStateChangeListener) should be last to improve Kotlin interoperability; see https://kotlinlang.org/docs/reference/java-interop.html#sam-conversions
+SamShouldBeLast: android.view.inputmethod.InputMethodInfo#dump(android.util.Printer, String):
+    SAM-compatible parameters (such as parameter 1, "pw", in android.view.inputmethod.InputMethodInfo.dump) should be last to improve Kotlin interoperability; see https://kotlinlang.org/docs/reference/java-interop.html#sam-conversions
 SamShouldBeLast: android.webkit.WebChromeClient#onShowFileChooser(android.webkit.WebView, android.webkit.ValueCallback<android.net.Uri[]>, android.webkit.WebChromeClient.FileChooserParams):
     SAM-compatible parameters (such as parameter 2, "filePathCallback", in android.webkit.WebChromeClient.onShowFileChooser) should be last to improve Kotlin interoperability; see https://kotlinlang.org/docs/reference/java-interop.html#sam-conversions
 
diff --git a/core/api/test-current.txt b/core/api/test-current.txt
index 39f2737..a3cd3dc 100644
--- a/core/api/test-current.txt
+++ b/core/api/test-current.txt
@@ -2,6 +2,7 @@
 package android {
 
   public static final class Manifest.permission {
+    field @FlaggedApi("com.android.server.accessibility.motion_event_observing") public static final String ACCESSIBILITY_MOTION_EVENT_OBSERVING = "android.permission.ACCESSIBILITY_MOTION_EVENT_OBSERVING";
     field public static final String ACCESS_NOTIFICATIONS = "android.permission.ACCESS_NOTIFICATIONS";
     field public static final String ACTIVITY_EMBEDDING = "android.permission.ACTIVITY_EMBEDDING";
     field public static final String ADJUST_RUNTIME_PERMISSIONS_POLICY = "android.permission.ADJUST_RUNTIME_PERMISSIONS_POLICY";
@@ -99,6 +100,8 @@
 
   public class AccessibilityServiceInfo implements android.os.Parcelable {
     method @NonNull public android.content.ComponentName getComponentName();
+    method @FlaggedApi("android.view.accessibility.motion_event_observing") public int getObservedMotionEventSources();
+    method @FlaggedApi("android.view.accessibility.motion_event_observing") public void setObservedMotionEventSources(int);
   }
 
 }
diff --git a/core/java/android/accessibilityservice/AccessibilityServiceInfo.java b/core/java/android/accessibilityservice/AccessibilityServiceInfo.java
index 8ad6ea2..fc342fa 100644
--- a/core/java/android/accessibilityservice/AccessibilityServiceInfo.java
+++ b/core/java/android/accessibilityservice/AccessibilityServiceInfo.java
@@ -20,6 +20,7 @@
 import static android.accessibilityservice.util.AccessibilityUtils.loadSafeAnimatedImage;
 import static android.content.pm.PackageManager.FEATURE_FINGERPRINT;
 
+import android.annotation.FlaggedApi;
 import android.annotation.IntDef;
 import android.annotation.IntRange;
 import android.annotation.NonNull;
@@ -53,6 +54,7 @@
 import android.view.View;
 import android.view.accessibility.AccessibilityEvent;
 import android.view.accessibility.AccessibilityNodeInfo;
+import android.view.accessibility.Flags;
 
 import com.android.internal.R;
 import com.android.internal.compat.IPlatformCompat;
@@ -630,7 +632,8 @@
             InputDevice.SOURCE_TOUCH_NAVIGATION,
             InputDevice.SOURCE_ROTARY_ENCODER,
             InputDevice.SOURCE_JOYSTICK,
-            InputDevice.SOURCE_SENSOR
+            InputDevice.SOURCE_SENSOR,
+            InputDevice.SOURCE_TOUCHSCREEN
     })
     @Retention(RetentionPolicy.SOURCE)
     public @interface MotionEventSources {}
@@ -642,6 +645,8 @@
     @MotionEventSources
     private int mMotionEventSources = 0;
 
+    private int mObservedMotionEventSources = 0;
+
     /**
      * Creates a new instance.
      */
@@ -817,6 +822,9 @@
         mInteractiveUiTimeout = other.mInteractiveUiTimeout;
         flags = other.flags;
         mMotionEventSources = other.mMotionEventSources;
+        if (Flags.motionEventObserving()) {
+            setObservedMotionEventSources(other.mObservedMotionEventSources);
+        }
         // NOTE: Ensure that only properties that are safe to be modified by the service itself
         // are included here (regardless of hidden setters, etc.).
     }
@@ -1024,16 +1032,75 @@
      */
     public void setMotionEventSources(@MotionEventSources int motionEventSources) {
         mMotionEventSources = motionEventSources;
+        mObservedMotionEventSources = 0;
+    }
+
+    /**
+     * Sets the bit mask of {@link android.view.InputDevice} sources that the accessibility service
+     * wants to observe generic {@link android.view.MotionEvent}s from if it has already requested
+     * to listen to them using {@link #setMotionEventSources(int)}. Events from these sources will
+     * be sent to the rest of the input pipeline without being consumed by accessibility services.
+     * This service will still be able to see them.
+     *
+     * <p><strong>Note:</strong> you will need to call this function every time you call {@link
+     * #setMotionEventSources(int)}. Calling {@link #setMotionEventSources(int)} clears the list of
+     * observed motion event sources for this service.
+     *
+     * <p><strong>Note:</strong> {@link android.view.InputDevice} sources contain source class bits
+     * that complicate bitwise flag removal operations. To remove a specific source you should
+     * rebuild the entire value using bitwise OR operations on the individual source constants.
+     *
+     * <p>Including an {@link android.view.InputDevice} source that does not send {@link
+     * android.view.MotionEvent}s is effectively a no-op for that source, since you will not receive
+     * any events from that source.
+     *
+     * <p><strong>Note:</strong> Calling this function with a source that has not been listened to
+     * using {@link #setMotionEventSources(int)} will throw an exception.
+     *
+     * @see AccessibilityService#onMotionEvent
+     * @see #MotionEventSources
+     * @see #setMotionEventSources(int)
+     * @hide
+     */
+    @FlaggedApi(Flags.FLAG_MOTION_EVENT_OBSERVING)
+    @TestApi
+    public void setObservedMotionEventSources(int observedMotionEventSources) {
+        // Confirm that any sources requested here have already been requested for listening.
+        if ((observedMotionEventSources & ~mMotionEventSources) != 0) {
+            String message =
+                    String.format(
+                            "Requested motion event sources for listening = 0x%x but requested"
+                                    + " motion event sources for observing = 0x%x.",
+                            mMotionEventSources, observedMotionEventSources);
+            throw new IllegalArgumentException(message);
+        }
+        mObservedMotionEventSources = observedMotionEventSources;
+    }
+
+    /**
+     * Returns the bit mask of {@link android.view.InputDevice} sources that the accessibility
+     * service wants to observe generic {@link android.view.MotionEvent}s from if it has already
+     * requested to listen to them using {@link #setMotionEventSources(int)}. Events from these
+     * sources will be sent to the rest of the input pipeline without being consumed by
+     * accessibility services. This service will still be able to see them.
+     *
+     * @hide
+     */
+    @FlaggedApi(Flags.FLAG_MOTION_EVENT_OBSERVING)
+    @MotionEventSources
+    @TestApi
+    public int getObservedMotionEventSources() {
+        return mObservedMotionEventSources;
     }
 
     /**
      * The localized summary of the accessibility service.
-     * <p>
-     *    <strong>Statically set from
-     *    {@link AccessibilityService#SERVICE_META_DATA meta-data}.</strong>
-     * </p>
-     * @return The localized summary if available, and {@code null} if a summary
-     * has not been provided.
+     *
+     * <p><strong>Statically set from {@link AccessibilityService#SERVICE_META_DATA
+     * meta-data}.</strong>
+     *
+     * @return The localized summary if available, and {@code null} if a summary has not been
+     *     provided.
      */
     public CharSequence loadSummary(PackageManager packageManager) {
         if (mSummaryResId == 0) {
@@ -1260,6 +1327,7 @@
         parcel.writeString(mTileServiceName);
         parcel.writeInt(mIntroResId);
         parcel.writeInt(mMotionEventSources);
+        parcel.writeInt(mObservedMotionEventSources);
     }
 
     private void initFromParcel(Parcel parcel) {
@@ -1285,6 +1353,8 @@
         mTileServiceName = parcel.readString();
         mIntroResId = parcel.readInt();
         mMotionEventSources = parcel.readInt();
+        // use the setter here because it throws an exception for invalid values.
+        setObservedMotionEventSources(parcel.readInt());
     }
 
     @Override
diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java
index ffed405..4a9fa9e 100644
--- a/core/java/android/app/Activity.java
+++ b/core/java/android/app/Activity.java
@@ -6884,8 +6884,8 @@
      * application package was involved.
      *
      * <p>If called while inside the handling of {@link #onNewIntent}, this function will
-     * return the referrer that submitted that new intent to the activity.  Otherwise, it
-     * always returns the referrer of the original Intent.</p>
+     * return the referrer that submitted that new intent to the activity only after
+     * {@link #setIntent(Intent)} is called with the provided intent.</p>
      *
      * <p>Note that this is <em>not</em> a security feature -- you can not trust the
      * referrer information, applications can spoof it.</p>
diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java
index 8b39ed6..6ddb36a 100644
--- a/core/java/android/app/ActivityThread.java
+++ b/core/java/android/app/ActivityThread.java
@@ -4077,7 +4077,7 @@
         final LoadedApk sdkApk = getPackageInfo(
                 contextInfo.getSdkApplicationInfo(),
                 r.packageInfo.getCompatibilityInfo(),
-                ActivityContextInfo.CONTEXT_FLAGS);
+                contextInfo.getContextFlags());
 
         final ContextImpl activityContext = ContextImpl.createActivityContext(
                 this, sdkApk, r.activityInfo, r.token, displayId, r.overrideConfig);
diff --git a/core/java/android/app/ApplicationStartInfo.java b/core/java/android/app/ApplicationStartInfo.java
index c8317c8..656feb0 100644
--- a/core/java/android/app/ApplicationStartInfo.java
+++ b/core/java/android/app/ApplicationStartInfo.java
@@ -104,14 +104,17 @@
     /** Process started due to Activity started for any reason not explicitly listed. */
     public static final int START_REASON_START_ACTIVITY = 11;
 
+    /** Start type not yet set. */
+    public static final int START_TYPE_UNSET = 0;
+
     /** Process started from scratch. */
-    public static final int START_TYPE_COLD = 0;
+    public static final int START_TYPE_COLD = 1;
 
     /** Process retained minimally SavedInstanceState. */
-    public static final int START_TYPE_WARM = 1;
+    public static final int START_TYPE_WARM = 2;
 
     /** Process brought back to foreground. */
-    public static final int START_TYPE_HOT = 2;
+    public static final int START_TYPE_HOT = 3;
 
     /**
      * Default. The system always creates a new instance of the activity in the target task and
@@ -277,6 +280,7 @@
     @IntDef(
             prefix = {"START_TYPE_"},
             value = {
+                START_TYPE_UNSET,
                 START_TYPE_COLD,
                 START_TYPE_WARM,
                 START_TYPE_HOT,
@@ -769,6 +773,7 @@
 
     private static String startTypeToString(@StartType int startType) {
         return switch (startType) {
+            case START_TYPE_UNSET -> "UNSET";
             case START_TYPE_COLD -> "COLD";
             case START_TYPE_WARM -> "WARM";
             case START_TYPE_HOT -> "HOT";
diff --git a/core/java/android/app/AutomaticZenRule.java b/core/java/android/app/AutomaticZenRule.java
index d935449..53a21cd 100644
--- a/core/java/android/app/AutomaticZenRule.java
+++ b/core/java/android/app/AutomaticZenRule.java
@@ -16,6 +16,8 @@
 
 package android.app;
 
+import static com.android.internal.util.Preconditions.checkArgument;
+
 import android.annotation.DrawableRes;
 import android.annotation.FlaggedApi;
 import android.annotation.IntDef;
@@ -390,7 +392,7 @@
      */
     @FlaggedApi(Flags.FLAG_MODES_API)
     public void setType(@Type int type) {
-        mType = type;
+        mType = checkValidType(type);
     }
 
     /**
@@ -451,6 +453,24 @@
         mAllowManualInvocation = allowManualInvocation;
     }
 
+    /** @hide */
+    @FlaggedApi(Flags.FLAG_MODES_API)
+    public void validate() {
+        if (Flags.modesApi()) {
+            checkValidType(mType);
+        }
+    }
+
+    @FlaggedApi(Flags.FLAG_MODES_API)
+    @Type
+    private static int checkValidType(@Type int type) {
+        checkArgument(type >= TYPE_UNKNOWN && type <= TYPE_MANAGED,
+                "Rule type must be one of TYPE_UNKNOWN, TYPE_OTHER, TYPE_SCHEDULE_TIME, "
+                        + "TYPE_SCHEDULE_CALENDAR, TYPE_BEDTIME, TYPE_DRIVING, TYPE_IMMERSIVE, "
+                        + "TYPE_THEATER, or TYPE_MANAGED");
+        return type;
+    }
+
     @Override
     public int describeContents() {
         return 0;
@@ -703,10 +723,10 @@
         }
 
         /**
-         * Sets the type of the rule
+         * Sets the type of the rule.
          */
         public @NonNull Builder setType(@Type int type) {
-            mType = type;
+            mType = checkValidType(type);
             return this;
         }
 
@@ -714,7 +734,7 @@
          * Sets a user visible description of when this rule will be active
          * (see {@link Condition#STATE_TRUE}).
          *
-         * A description should be a (localized) string like "Mon-Fri, 9pm-7am" or
+         * <p>A description should be a (localized) string like "Mon-Fri, 9pm-7am" or
          * "When connected to [Car Name]".
          */
         public @NonNull Builder setTriggerDescription(@Nullable String description) {
diff --git a/core/java/android/app/admin/DevicePolicyManager.java b/core/java/android/app/admin/DevicePolicyManager.java
index 90a2659..4a6349b1 100644
--- a/core/java/android/app/admin/DevicePolicyManager.java
+++ b/core/java/android/app/admin/DevicePolicyManager.java
@@ -9381,7 +9381,7 @@
     @Deprecated
     @SystemApi
     @RequiresPermission(MANAGE_DEVICE_ADMINS)
-    public boolean setActiveProfileOwner(@NonNull ComponentName admin, @Deprecated String ownerName)
+    public boolean setActiveProfileOwner(@NonNull ComponentName admin, String ownerName)
             throws IllegalArgumentException {
         throwIfParentInstance("setActiveProfileOwner");
         if (mService != null) {
diff --git a/core/java/android/appwidget/AppWidgetManager.java b/core/java/android/appwidget/AppWidgetManager.java
index 6e45147..4cf9fca 100644
--- a/core/java/android/appwidget/AppWidgetManager.java
+++ b/core/java/android/appwidget/AppWidgetManager.java
@@ -16,6 +16,8 @@
 
 package android.appwidget;
 
+import static android.appwidget.flags.Flags.remoteAdapterConversion;
+
 import android.annotation.BroadcastBehavior;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
@@ -566,11 +568,9 @@
     private void tryAdapterConversion(
             FunctionalUtils.RemoteExceptionIgnoringConsumer<RemoteViews> action,
             RemoteViews original, String failureMsg) {
-        final boolean isConvertingAdapter = RemoteViews.isAdapterConversionEnabled()
+        if (remoteAdapterConversion()
                 && (mHasPostedLegacyLists = mHasPostedLegacyLists
-                        || (original != null && original.hasLegacyLists()));
-
-        if (isConvertingAdapter) {
+                        || (original != null && original.hasLegacyLists()))) {
             final RemoteViews viewsCopy = new RemoteViews(original);
             Runnable updateWidgetWithTask = () -> {
                 try {
@@ -587,13 +587,12 @@
             }
 
             updateWidgetWithTask.run();
-            return;
-        }
-
-        try {
-            action.acceptOrThrow(original);
-        } catch (RemoteException re) {
-            throw re.rethrowFromSystemServer();
+        } else {
+            try {
+                action.acceptOrThrow(original);
+            } catch (RemoteException re) {
+                throw re.rethrowFromSystemServer();
+            }
         }
     }
 
@@ -838,22 +837,20 @@
             return;
         }
 
-        if (!RemoteViews.isAdapterConversionEnabled()) {
+        if (remoteAdapterConversion()) {
+            if (Looper.myLooper() == Looper.getMainLooper()) {
+                mHasPostedLegacyLists = true;
+                createUpdateExecutorIfNull().execute(() -> notifyCollectionWidgetChange(
+                        appWidgetIds, viewId));
+            } else {
+                notifyCollectionWidgetChange(appWidgetIds, viewId);
+            }
+        } else {
             try {
                 mService.notifyAppWidgetViewDataChanged(mPackageName, appWidgetIds, viewId);
             } catch (RemoteException re) {
                 throw re.rethrowFromSystemServer();
             }
-
-            return;
-        }
-
-        if (Looper.myLooper() == Looper.getMainLooper()) {
-            mHasPostedLegacyLists = true;
-            createUpdateExecutorIfNull().execute(() -> notifyCollectionWidgetChange(appWidgetIds,
-                    viewId));
-        } else {
-            notifyCollectionWidgetChange(appWidgetIds, viewId);
         }
     }
 
diff --git a/core/java/android/appwidget/flags.aconfig b/core/java/android/appwidget/flags.aconfig
index 6a735a4..c95b864 100644
--- a/core/java/android/appwidget/flags.aconfig
+++ b/core/java/android/appwidget/flags.aconfig
@@ -6,3 +6,10 @@
   description: "Enable support for generated previews in AppWidgetManager"
   bug: "306546610"
 }
+
+flag {
+  name: "remote_adapter_conversion"
+  namespace: "app_widgets"
+  description: "Enable adapter conversion to RemoteCollectionItemsAdapter"
+  bug: "245950570"
+}
\ No newline at end of file
diff --git a/core/java/android/content/pm/ILauncherApps.aidl b/core/java/android/content/pm/ILauncherApps.aidl
index e9f419e..a97de63 100644
--- a/core/java/android/content/pm/ILauncherApps.aidl
+++ b/core/java/android/content/pm/ILauncherApps.aidl
@@ -64,6 +64,9 @@
     PendingIntent getActivityLaunchIntent(String callingPackage, in ComponentName component,
             in UserHandle user);
     LauncherUserInfo getLauncherUserInfo(in UserHandle user);
+    List<String> getPreInstalledSystemPackages(in UserHandle user);
+    IntentSender getAppMarketActivityIntent(String callingPackage, String packageName,
+            in UserHandle user);
     void showAppDetailsAsUser(in IApplicationThread caller, String callingPackage,
             String callingFeatureId, in ComponentName component, in Rect sourceBounds,
             in Bundle opts, in UserHandle user);
diff --git a/core/java/android/content/pm/LauncherApps.java b/core/java/android/content/pm/LauncherApps.java
index 0cd4358..1d2b1af 100644
--- a/core/java/android/content/pm/LauncherApps.java
+++ b/core/java/android/content/pm/LauncherApps.java
@@ -799,6 +799,78 @@
         }
     }
 
+
+    /**
+     * Returns an intent sender which can be used to start the App Market activity (Installer
+     * Activity).
+     * This method is primarily used to get an intent sender which starts App Market activity for
+     * another profile, if the caller is not otherwise allowed to start activity in that profile.
+     *
+     * <p>When packageName is set, intent sender to start the App Market Activity which installed
+     * the package in calling user will be returned, but for the profile passed.
+     *
+     * <p>When packageName is not set, intent sender to launch the default App Market Activity for
+     * the profile will be returned. In case there are multiple App Market Activities available for
+     * the profile, IntentPicker will be started, allowing user to choose the preferred activity.
+     *
+     * <p>The method will fall back to the behaviour of not having the packageName set, in case:
+     * <ul>
+     *     <li>No activity for the packageName is found in calling user-space.</li>
+     *     <li>The App Market Activity which installed the package in calling user-space is not
+     *         present.</li>
+     *     <li>The App Market Activity which installed the package in calling user-space is not
+     *         present in the profile passed.</li>
+     * </ul>
+     * </p>
+     *
+     *
+     *
+     * @param packageName the package for which intent sender to launch App Market Activity is
+     *                    required.
+     * @param user the profile for which intent sender to launch App Market Activity is required.
+     * @return {@link IntentSender} object which launches the App Market Activity, null in case
+     *         there is no such activity.
+     * @hide
+     */
+    @Nullable
+    @FlaggedApi(Flags.FLAG_ALLOW_PRIVATE_PROFILE)
+    public IntentSender getAppMarketActivityIntent(@Nullable String packageName,
+            @NonNull UserHandle user) {
+        if (DEBUG) {
+            Log.i(TAG, "getAppMarketActivityIntent for package: " + packageName
+                    + " user: " + user);
+        }
+        try {
+            return mService.getAppMarketActivityIntent(mContext.getPackageName(),
+                    packageName, user);
+        } catch (RemoteException re) {
+            throw re.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Returns the list of the system packages that are installed at user creation.
+     *
+     * <p>An empty list denotes that all system packages are installed for that user at creation.
+     * This behaviour is inherited from the underlining UserManager API.
+     *
+     * @param userHandle the user for which installed system packages are required.
+     * @return {@link List} of {@link String}, representing the package name of the installed
+     *        package. Can be empty but not null.
+     * @hide
+     */
+    @FlaggedApi(Flags.FLAG_ALLOW_PRIVATE_PROFILE)
+    public List<String> getPreInstalledSystemPackages(@NonNull UserHandle userHandle) {
+        if (DEBUG) {
+            Log.i(TAG, "getPreInstalledSystemPackages for user: " + userHandle);
+        }
+        try {
+            return mService.getPreInstalledSystemPackages(userHandle);
+        } catch (RemoteException re) {
+            throw re.rethrowFromSystemServer();
+        }
+    }
+
     /**
      * Returns the activity info for a given intent and user handle, if it resolves. Otherwise it
      * returns null.
diff --git a/core/java/android/content/pm/PackageInfo.java b/core/java/android/content/pm/PackageInfo.java
index 5dee65b..4f61613 100644
--- a/core/java/android/content/pm/PackageInfo.java
+++ b/core/java/android/content/pm/PackageInfo.java
@@ -231,7 +231,7 @@
      * or null if there were none.  This is only filled in if the flag
      * {@link PackageManager#GET_PERMISSIONS} was set.  Each value matches
      * the corresponding entry in {@link #requestedPermissions}, and will have
-     * the flags {@link #REQUESTED_PERMISSION_GRANTED} and
+     * the flags {@link #REQUESTED_PERMISSION_GRANTED}, {@link #REQUESTED_PERMISSION_IMPLICIT}, and
      * {@link #REQUESTED_PERMISSION_NEVER_FOR_LOCATION} set as appropriate.
      */
     @Nullable
diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java
index e224329..a5d16f2 100644
--- a/core/java/android/content/pm/PackageManager.java
+++ b/core/java/android/content/pm/PackageManager.java
@@ -1225,12 +1225,10 @@
     public static final int MATCH_DEBUG_TRIAGED_MISSING = MATCH_DIRECT_BOOT_AUTO;
 
     /**
-     * @deprecated Use {@link #MATCH_CLONE_PROFILE_LONG} instead.
+     * Use {@link #MATCH_CLONE_PROFILE_LONG} instead.
      *
      * @hide
      */
-    @SuppressLint("UnflaggedApi") // Just adding the @Deprecated annotation
-    @Deprecated
     @SystemApi
     public static final int MATCH_CLONE_PROFILE = 0x20000000;
 
@@ -6302,6 +6300,11 @@
     /**
      * Check whether a particular package has been granted a particular
      * permission.
+     * <p>
+     * <strong>Note: </strong>This API returns the underlying permission state
+     * as-is and is mostly intended for permission managing system apps. To
+     * perform an access check for a certain app, please use the
+     * {@link Context#checkPermission} APIs instead.
      *
      * @param permName The name of the permission you are checking for.
      * @param packageName The name of the package you are checking against.
diff --git a/core/java/android/content/pm/multiuser.aconfig b/core/java/android/content/pm/multiuser.aconfig
index 6c6b33b..57025c2 100644
--- a/core/java/android/content/pm/multiuser.aconfig
+++ b/core/java/android/content/pm/multiuser.aconfig
@@ -49,3 +49,10 @@
     description: "Add support to unlock the private space using biometrics"
     bug: "312184187"
 }
+
+flag {
+    name: "support_autolock_for_private_space"
+    namespace: "profile_experiences"
+    description: "Add support to lock private space automatically after a time period"
+    bug: "303201022"
+}
diff --git a/core/java/android/hardware/biometrics/AuthenticationStateListener.aidl b/core/java/android/hardware/biometrics/AuthenticationStateListener.aidl
new file mode 100644
index 0000000..73ac333
--- /dev/null
+++ b/core/java/android/hardware/biometrics/AuthenticationStateListener.aidl
@@ -0,0 +1,36 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.hardware.biometrics;
+
+/**
+ * Low-level callback interface between <Biometric>Manager and <Auth>Service. Allows core system
+ * services (e.g. SystemUI) to register a listener for updates about the current state of biometric
+ * authentication.
+ * @hide
+ */
+oneway interface AuthenticationStateListener {
+    /**
+     * Defines behavior in response to authentication starting
+     * @param requestReason reason from [BiometricRequestConstants.RequestReason] for requesting
+     * authentication starting
+     */
+    void onAuthenticationStarted(int requestReason);
+
+    /**
+     * Defines behavior in response to authentication stopping
+     */
+    void onAuthenticationStopped();
+}
diff --git a/core/java/android/hardware/biometrics/BiometricManager.java b/core/java/android/hardware/biometrics/BiometricManager.java
index f82f79e..d7d1d1a 100644
--- a/core/java/android/hardware/biometrics/BiometricManager.java
+++ b/core/java/android/hardware/biometrics/BiometricManager.java
@@ -552,6 +552,44 @@
     }
 
     /**
+     * Registers listener for changes to biometric authentication state.
+     * Only sends callbacks for events that occur after the callback has been registered.
+     * @param listener Listener for changes to biometric authentication state
+     * @hide
+     */
+    @RequiresPermission(USE_BIOMETRIC_INTERNAL)
+    public void registerAuthenticationStateListener(AuthenticationStateListener listener) {
+        if (mService != null) {
+            try {
+                mService.registerAuthenticationStateListener(listener);
+            } catch (RemoteException e) {
+                throw e.rethrowFromSystemServer();
+            }
+        } else {
+            Slog.w(TAG, "registerAuthenticationStateListener(): Service not connected");
+        }
+    }
+
+    /**
+     * Unregisters listener for changes to biometric authentication state.
+     * @param listener Listener for changes to biometric authentication state
+     * @hide
+     */
+    @RequiresPermission(USE_BIOMETRIC_INTERNAL)
+    public void unregisterAuthenticationStateListener(AuthenticationStateListener listener) {
+        if (mService != null) {
+            try {
+                mService.unregisterAuthenticationStateListener(listener);
+            } catch (RemoteException e) {
+                throw e.rethrowFromSystemServer();
+            }
+        } else {
+            Slog.w(TAG, "unregisterAuthenticationStateListener(): Service not connected");
+        }
+    }
+
+
+    /**
      * Requests all {@link Authenticators.Types#BIOMETRIC_STRONG} sensors to have their
      * authenticatorId invalidated for the specified user. This happens when enrollments have been
      * added on devices with multiple biometric sensors.
diff --git a/core/java/android/hardware/biometrics/BiometricOverlayConstants.java b/core/java/android/hardware/biometrics/BiometricRequestConstants.java
similarity index 69%
rename from core/java/android/hardware/biometrics/BiometricOverlayConstants.java
rename to core/java/android/hardware/biometrics/BiometricRequestConstants.java
index 065ae64a..b036f30 100644
--- a/core/java/android/hardware/biometrics/BiometricOverlayConstants.java
+++ b/core/java/android/hardware/biometrics/BiometricRequestConstants.java
@@ -22,24 +22,27 @@
 import java.lang.annotation.RetentionPolicy;
 
 /**
- * Common constants for biometric overlays.
+ * Common constants for biometric requests.
  * @hide
  */
-public interface BiometricOverlayConstants {
+public class BiometricRequestConstants {
+
+    private BiometricRequestConstants() {}
+
     /** Unknown usage. */
-    int REASON_UNKNOWN = 0;
+    public static final int REASON_UNKNOWN = 0;
     /** User is about to enroll. */
-    int REASON_ENROLL_FIND_SENSOR = 1;
+    public static final int REASON_ENROLL_FIND_SENSOR = 1;
     /** User is enrolling. */
-    int REASON_ENROLL_ENROLLING = 2;
+    public static final int REASON_ENROLL_ENROLLING = 2;
     /** Usage from BiometricPrompt. */
-    int REASON_AUTH_BP = 3;
-    /** Usage from Keyguard. */
-    int REASON_AUTH_KEYGUARD = 4;
+    public static final int REASON_AUTH_BP = 3;
+    /** Usage from Device Entry. */
+    public static final int REASON_AUTH_KEYGUARD = 4;
     /** Non-specific usage (from FingerprintManager). */
-    int REASON_AUTH_OTHER = 5;
+    public static final int REASON_AUTH_OTHER = 5;
     /** Usage from Settings. */
-    int REASON_AUTH_SETTINGS = 6;
+    public static final int REASON_AUTH_SETTINGS = 6;
 
     @IntDef({REASON_UNKNOWN,
             REASON_ENROLL_FIND_SENSOR,
@@ -49,5 +52,5 @@
             REASON_AUTH_OTHER,
             REASON_AUTH_SETTINGS})
     @Retention(RetentionPolicy.SOURCE)
-    @interface ShowReason {}
+    public @interface RequestReason {}
 }
diff --git a/core/java/android/hardware/biometrics/IAuthService.aidl b/core/java/android/hardware/biometrics/IAuthService.aidl
index 5bdbe2b5..8514f98 100644
--- a/core/java/android/hardware/biometrics/IAuthService.aidl
+++ b/core/java/android/hardware/biometrics/IAuthService.aidl
@@ -16,6 +16,7 @@
 
 package android.hardware.biometrics;
 
+import android.hardware.biometrics.AuthenticationStateListener;
 import android.hardware.biometrics.IBiometricEnabledOnKeyguardCallback;
 import android.hardware.biometrics.IBiometricServiceReceiver;
 import android.hardware.biometrics.IInvalidationCallback;
@@ -66,6 +67,12 @@
     // Register callback for when keyguard biometric eligibility changes.
     void registerEnabledOnKeyguardCallback(IBiometricEnabledOnKeyguardCallback callback);
 
+    // Register listener for changes to authentication state.
+    void registerAuthenticationStateListener(AuthenticationStateListener listener);
+
+    // Unregister listener for changes to authentication state.
+    void unregisterAuthenticationStateListener(AuthenticationStateListener listener);
+
     // Requests all BIOMETRIC_STRONG sensors to have their authenticatorId invalidated for the
     // specified user. This happens when enrollments have been added on devices with multiple
     // biometric sensors.
diff --git a/core/java/android/hardware/camera2/CameraMetadata.java b/core/java/android/hardware/camera2/CameraMetadata.java
index 93fbe8a..7cf10d8 100644
--- a/core/java/android/hardware/camera2/CameraMetadata.java
+++ b/core/java/android/hardware/camera2/CameraMetadata.java
@@ -1216,7 +1216,7 @@
      * <ul>
      * <li>Profile {@link android.hardware.camera2.params.DynamicRangeProfiles#HLG10 }</li>
      * <li>All mandatory stream combinations for this specific capability as per
-     *   <a href="CameraDevice#10-bit-output-additional-guaranteed-configurations">documentation</a></li>
+     *   <a href="https://developer.android.com/reference/android/hardware/camera2/CameraDevice#10-bit-output-additional-guaranteed-configurations">documentation</a></li>
      * <li>In case the device is not able to capture some combination of supported
      *   standard 8-bit and/or 10-bit dynamic range profiles within the same capture request,
      *   then those constraints must be listed in
diff --git a/core/java/android/hardware/camera2/CaptureRequest.java b/core/java/android/hardware/camera2/CaptureRequest.java
index 06397c9..ded96a2 100644
--- a/core/java/android/hardware/camera2/CaptureRequest.java
+++ b/core/java/android/hardware/camera2/CaptureRequest.java
@@ -1484,7 +1484,7 @@
      * <p>To start a CaptureSession with a target FPS range different from the
      * capture request template's default value, the application
      * is strongly recommended to call
-     * {@link SessionConfiguration#setSessionParameters }
+     * {@link android.hardware.camera2.params.SessionConfiguration#setSessionParameters }
      * with the target fps range before creating the capture session. The aeTargetFpsRange is
      * typically a session parameter. Specifying it at session creation time helps avoid
      * session reconfiguration delays in cases like 60fps or high speed recording.</p>
@@ -2161,7 +2161,7 @@
      * OFF if the recording output is not stabilized, or if there are no output
      * Surface types that can be stabilized.</p>
      * <p>The application is strongly recommended to call
-     * {@link SessionConfiguration#setSessionParameters }
+     * {@link android.hardware.camera2.params.SessionConfiguration#setSessionParameters }
      * with the desired video stabilization mode before creating the capture session.
      * Video stabilization mode is a session parameter on many devices. Specifying
      * it at session creation time helps avoid reconfiguration delay caused by difference
diff --git a/core/java/android/hardware/camera2/CaptureResult.java b/core/java/android/hardware/camera2/CaptureResult.java
index ab4406c3..1d26d69 100644
--- a/core/java/android/hardware/camera2/CaptureResult.java
+++ b/core/java/android/hardware/camera2/CaptureResult.java
@@ -899,7 +899,7 @@
      * <p>To start a CaptureSession with a target FPS range different from the
      * capture request template's default value, the application
      * is strongly recommended to call
-     * {@link SessionConfiguration#setSessionParameters }
+     * {@link android.hardware.camera2.params.SessionConfiguration#setSessionParameters }
      * with the target fps range before creating the capture session. The aeTargetFpsRange is
      * typically a session parameter. Specifying it at session creation time helps avoid
      * session reconfiguration delays in cases like 60fps or high speed recording.</p>
@@ -2382,7 +2382,7 @@
      * OFF if the recording output is not stabilized, or if there are no output
      * Surface types that can be stabilized.</p>
      * <p>The application is strongly recommended to call
-     * {@link SessionConfiguration#setSessionParameters }
+     * {@link android.hardware.camera2.params.SessionConfiguration#setSessionParameters }
      * with the desired video stabilization mode before creating the capture session.
      * Video stabilization mode is a session parameter on many devices. Specifying
      * it at session creation time helps avoid reconfiguration delay caused by difference
diff --git a/core/java/android/hardware/fingerprint/FingerprintManager.java b/core/java/android/hardware/fingerprint/FingerprintManager.java
index 935157a..fe7de83 100644
--- a/core/java/android/hardware/fingerprint/FingerprintManager.java
+++ b/core/java/android/hardware/fingerprint/FingerprintManager.java
@@ -983,6 +983,7 @@
         }
     }
 
+    // TODO(b/288175061): remove with Flags.FLAG_SIDEFPS_CONTROLLER_REFACTOR
     /**
      * @hide
      */
diff --git a/core/java/android/hardware/fingerprint/IFingerprintService.aidl b/core/java/android/hardware/fingerprint/IFingerprintService.aidl
index 0100660..f594c00 100644
--- a/core/java/android/hardware/fingerprint/IFingerprintService.aidl
+++ b/core/java/android/hardware/fingerprint/IFingerprintService.aidl
@@ -15,6 +15,7 @@
  */
 package android.hardware.fingerprint;
 
+import android.hardware.biometrics.AuthenticationStateListener;
 import android.hardware.biometrics.IBiometricSensorReceiver;
 import android.hardware.biometrics.IBiometricServiceLockoutResetCallback;
 import android.hardware.biometrics.IBiometricStateListener;
@@ -203,6 +204,14 @@
     @EnforcePermission("USE_BIOMETRIC_INTERNAL")
     void setSidefpsController(in ISidefpsController controller);
 
+    // Registers AuthenticationStateListener.
+    @EnforcePermission("USE_BIOMETRIC_INTERNAL")
+    void registerAuthenticationStateListener(AuthenticationStateListener listener);
+
+    // Unregisters AuthenticationStateListener.
+    @EnforcePermission("USE_BIOMETRIC_INTERNAL")
+    void unregisterAuthenticationStateListener(AuthenticationStateListener listener);
+
     // Registers BiometricStateListener.
     @EnforcePermission("USE_BIOMETRIC_INTERNAL")
     void registerBiometricStateListener(IBiometricStateListener listener);
diff --git a/core/java/android/os/BatteryStats.java b/core/java/android/os/BatteryStats.java
index 16ffaef..10a8022 100644
--- a/core/java/android/os/BatteryStats.java
+++ b/core/java/android/os/BatteryStats.java
@@ -1657,6 +1657,7 @@
      */
     public abstract CpuScalingPolicies getCpuScalingPolicies();
 
+    @android.ravenwood.annotation.RavenwoodKeepWholeClass
     public final static class HistoryTag {
         public static final int HISTORY_TAG_POOL_OVERFLOW = -1;
 
@@ -1713,6 +1714,7 @@
      * Optional detailed information that can go into a history step.  This is typically
      * generated each time the battery level changes.
      */
+    @android.ravenwood.annotation.RavenwoodKeepWholeClass
     public final static class HistoryStepDetails {
         // Time (in 1/100 second) spent in user space and the kernel since the last step.
         public int userTime;
@@ -1797,6 +1799,7 @@
     /**
      * An extension to the history item describing a proc state change for a UID.
      */
+    @android.ravenwood.annotation.RavenwoodKeepWholeClass
     public static final class ProcessStateChange {
         public int uid;
         public @BatteryConsumer.ProcessState int processState;
@@ -1850,6 +1853,7 @@
     /**
      * Battery history record.
      */
+    @android.ravenwood.annotation.RavenwoodKeepWholeClass
     public static final class HistoryItem {
         @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.P)
         public HistoryItem next;
diff --git a/core/java/android/os/ConditionVariable.java b/core/java/android/os/ConditionVariable.java
index a13eaa6..b5ed53b 100644
--- a/core/java/android/os/ConditionVariable.java
+++ b/core/java/android/os/ConditionVariable.java
@@ -29,6 +29,7 @@
  * This class uses itself as the object to wait on, so if you wait()
  * or notify() on a ConditionVariable, the results are undefined.
  */
+@android.ravenwood.annotation.RavenwoodKeepWholeClass
 public class ConditionVariable
 {
     private volatile boolean mCondition;
diff --git a/core/java/android/os/HidlSupport.java b/core/java/android/os/HidlSupport.java
index 7716055..91b796a 100644
--- a/core/java/android/os/HidlSupport.java
+++ b/core/java/android/os/HidlSupport.java
@@ -218,13 +218,6 @@
     @SystemApi
     public static native int getPidIfSharable();
 
-    /**
-     * Return true if HIDL is supported on this device and false if not.
-     *
-     * @hide
-     */
-    public static native boolean isHidlSupported();
-
     /** @hide */
     public HidlSupport() {}
 }
diff --git a/core/java/android/os/HwBinder.java b/core/java/android/os/HwBinder.java
index bc19655..feed208 100644
--- a/core/java/android/os/HwBinder.java
+++ b/core/java/android/os/HwBinder.java
@@ -18,7 +18,6 @@
 
 import android.annotation.SystemApi;
 import android.compat.annotation.UnsupportedAppUsage;
-import android.util.Log;
 
 import libcore.util.NativeAllocationRegistry;
 
@@ -79,17 +78,6 @@
             String iface,
             String serviceName)
         throws RemoteException, NoSuchElementException {
-        if (!HidlSupport.isHidlSupported()
-                && (iface.equals("android.hidl.manager@1.0::IServiceManager")
-                        || iface.equals("android.hidl.manager@1.1::IServiceManager")
-                        || iface.equals("android.hidl.manager@1.2::IServiceManager"))) {
-            Log.i(
-                    TAG,
-                    "Replacing Java hwservicemanager with a fake HwNoService"
-                            + " because HIDL is not supported on this device.");
-            return new HwNoService();
-        }
-
         return getService(iface, serviceName, false /* retry */);
     }
     /**
diff --git a/core/java/android/os/HwNoService.java b/core/java/android/os/HwNoService.java
deleted file mode 100644
index 117c3ad..0000000
--- a/core/java/android/os/HwNoService.java
+++ /dev/null
@@ -1,52 +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.os;
-
-/**
- * A fake hwservicemanager that is used locally when HIDL isn't supported on the device.
- *
- * @hide
- */
-final class HwNoService implements IHwBinder, IHwInterface {
-    /** @hide */
-    @Override
-    public void transact(int code, HwParcel request, HwParcel reply, int flags) {}
-
-    /** @hide */
-    @Override
-    public IHwInterface queryLocalInterface(String descriptor) {
-        return new HwNoService();
-    }
-
-    /** @hide */
-    @Override
-    public boolean linkToDeath(DeathRecipient recipient, long cookie) {
-        return true;
-    }
-
-    /** @hide */
-    @Override
-    public boolean unlinkToDeath(DeathRecipient recipient) {
-        return true;
-    }
-
-    /** @hide */
-    @Override
-    public IHwBinder asBinder() {
-        return this;
-    }
-}
diff --git a/core/java/android/os/Parcel.java b/core/java/android/os/Parcel.java
index f2930fe..8e860c3 100644
--- a/core/java/android/os/Parcel.java
+++ b/core/java/android/os/Parcel.java
@@ -465,9 +465,7 @@
     private static native byte[] nativeMarshall(long nativePtr);
     private static native void nativeUnmarshall(
             long nativePtr, byte[] data, int offset, int length);
-    @RavenwoodThrow
     private static native int nativeCompareData(long thisNativePtr, long otherNativePtr);
-    @RavenwoodThrow
     private static native boolean nativeCompareDataInRange(
             long ptrA, int offsetA, long ptrB, int offsetB, int length);
     private static native void nativeAppendFrom(
diff --git a/core/java/android/os/flags.aconfig b/core/java/android/os/flags.aconfig
index 145981c..83d237d 100644
--- a/core/java/android/os/flags.aconfig
+++ b/core/java/android/os/flags.aconfig
@@ -78,6 +78,13 @@
 }
 
 flag {
+    name: "adpf_use_fmq_channel"
+    namespace: "game"
+    description: "Guards use of the FMQ channel for ADPF"
+    bug: "315894228"
+}
+
+flag {
     name: "battery_service_support_current_adb_command"
     namespace: "backstage_power"
     description: "Whether or not BatteryService supports adb commands for Current values."
diff --git a/core/java/android/os/vibrator/flags.aconfig b/core/java/android/os/vibrator/flags.aconfig
index 69d86a6..437668c 100644
--- a/core/java/android/os/vibrator/flags.aconfig
+++ b/core/java/android/os/vibrator/flags.aconfig
@@ -44,3 +44,10 @@
     description: "Enables the independent keyboard vibration settings feature"
     bug: "289107579"
 }
+
+flag {
+    namespace: "haptics"
+    name: "adaptive_haptics_enabled"
+    description: "Enables the adaptive haptics feature"
+    bug: "305961689"
+}
diff --git a/core/java/android/service/notification/Condition.java b/core/java/android/service/notification/Condition.java
index 531626b..6e771f8 100644
--- a/core/java/android/service/notification/Condition.java
+++ b/core/java/android/service/notification/Condition.java
@@ -1,4 +1,4 @@
-/**
+/*
  * Copyright (c) 2014, The Android Open Source Project
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
@@ -16,6 +16,8 @@
 
 package android.service.notification;
 
+import static com.android.internal.util.Preconditions.checkArgument;
+
 import android.annotation.FlaggedApi;
 import android.annotation.IntDef;
 import android.annotation.NonNull;
@@ -117,7 +119,7 @@
 
     /** The source of, or reason for, the state change represented by this Condition. **/
     @FlaggedApi(Flags.FLAG_MODES_API)
-    public final @Source int source;
+    public final @Source int source; // default = SOURCE_UNKNOWN
 
     /**
      * The maximum string length for any string contained in this condition.
@@ -179,7 +181,7 @@
         this.line2 = getTrimmedString(line2);
         this.icon = icon;
         this.state = state;
-        this.source = source;
+        this.source = checkValidSource(source);
         this.flags = flags;
     }
 
@@ -197,10 +199,26 @@
                 source.readInt());
     }
 
+    /** @hide */
+    public void validate() {
+        if (Flags.modesApi()) {
+            checkValidSource(source);
+        }
+    }
+
     private static boolean isValidState(int state) {
         return state >= STATE_FALSE && state <= STATE_ERROR;
     }
 
+    private static int checkValidSource(@Source int source) {
+        if (Flags.modesApi()) {
+            checkArgument(source >= SOURCE_UNKNOWN && source <= SOURCE_CONTEXT,
+                    "Condition source must be one of SOURCE_UNKNOWN, SOURCE_USER_ACTION, "
+                            + "SOURCE_SCHEDULE, or SOURCE_CONTEXT");
+        }
+        return source;
+    }
+
     @Override
     public void writeToParcel(Parcel dest, int flags) {
         dest.writeParcelable(id, 0);
diff --git a/core/java/android/service/notification/ZenModeConfig.java b/core/java/android/service/notification/ZenModeConfig.java
index a5b087c..fcdc5fe 100644
--- a/core/java/android/service/notification/ZenModeConfig.java
+++ b/core/java/android/service/notification/ZenModeConfig.java
@@ -1980,6 +1980,7 @@
         @Nullable public ZenDeviceEffects zenDeviceEffects;
         public boolean modified;    // rule has been modified from initial creation
         public String pkg;
+        @AutomaticZenRule.Type
         public int type = AutomaticZenRule.TYPE_UNKNOWN;
         public String triggerDescription;
         public String iconResName;
diff --git a/core/java/android/service/persistentdata/OWNERS b/core/java/android/service/persistentdata/OWNERS
new file mode 100644
index 0000000..6dfb888
--- /dev/null
+++ b/core/java/android/service/persistentdata/OWNERS
@@ -0,0 +1 @@
+include /services/core/java/com/android/server/pdb/OWNERS
diff --git a/core/java/android/service/voice/HotwordRejectedResult.java b/core/java/android/service/voice/HotwordRejectedResult.java
index 26c1ca4..eb1ac67 100644
--- a/core/java/android/service/voice/HotwordRejectedResult.java
+++ b/core/java/android/service/voice/HotwordRejectedResult.java
@@ -16,9 +16,11 @@
 
 package android.service.voice;
 
+import android.annotation.FlaggedApi;
 import android.annotation.IntDef;
 import android.annotation.SystemApi;
 import android.os.Parcelable;
+import android.service.voice.flags.Flags;
 
 import com.android.internal.util.DataClass;
 
@@ -53,12 +55,17 @@
     /** High confidence in hotword detector result. */
     public static final int CONFIDENCE_LEVEL_HIGH = 3;
 
+    /** Very high confidence in hotword detector result. **/
+    @FlaggedApi(Flags.FLAG_ALLOW_HOTWORD_BUMP_EGRESS)
+    public static final int CONFIDENCE_LEVEL_VERY_HIGH = 4;
+
     /** @hide */
     @IntDef(prefix = {"CONFIDENCE_LEVEL_"}, value = {
             CONFIDENCE_LEVEL_NONE,
             CONFIDENCE_LEVEL_LOW,
             CONFIDENCE_LEVEL_MEDIUM,
-            CONFIDENCE_LEVEL_HIGH
+            CONFIDENCE_LEVEL_HIGH,
+            CONFIDENCE_LEVEL_VERY_HIGH
     })
     @Retention(RetentionPolicy.SOURCE)
     @interface HotwordConfidenceLevelValue {
@@ -91,9 +98,10 @@
         CONFIDENCE_LEVEL_NONE,
         CONFIDENCE_LEVEL_LOW,
         CONFIDENCE_LEVEL_MEDIUM,
-        CONFIDENCE_LEVEL_HIGH
+        CONFIDENCE_LEVEL_HIGH,
+        CONFIDENCE_LEVEL_VERY_HIGH
     })
-    @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE)
+    @Retention(RetentionPolicy.SOURCE)
     @DataClass.Generated.Member
     public @interface ConfidenceLevel {}
 
@@ -109,6 +117,8 @@
                     return "CONFIDENCE_LEVEL_MEDIUM";
             case CONFIDENCE_LEVEL_HIGH:
                     return "CONFIDENCE_LEVEL_HIGH";
+            case CONFIDENCE_LEVEL_VERY_HIGH:
+                    return "CONFIDENCE_LEVEL_VERY_HIGH";
             default: return Integer.toHexString(value);
         }
     }
@@ -259,10 +269,10 @@
     }
 
     @DataClass.Generated(
-            time = 1621961370106L,
+            time = 1701990933632L,
             codegenVersion = "1.0.23",
             sourceFile = "frameworks/base/core/java/android/service/voice/HotwordRejectedResult.java",
-            inputSignatures = "public static final  int CONFIDENCE_LEVEL_NONE\npublic static final  int CONFIDENCE_LEVEL_LOW\npublic static final  int CONFIDENCE_LEVEL_MEDIUM\npublic static final  int CONFIDENCE_LEVEL_HIGH\nprivate final @android.service.voice.HotwordRejectedResult.HotwordConfidenceLevelValue int mConfidenceLevel\nprivate static  int defaultConfidenceLevel()\nclass HotwordRejectedResult extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genConstructor=false, genBuilder=true, genEqualsHashCode=true, genHiddenConstDefs=true, genParcelable=true, genToString=true)")
+            inputSignatures = "public static final  int CONFIDENCE_LEVEL_NONE\npublic static final  int CONFIDENCE_LEVEL_LOW\npublic static final  int CONFIDENCE_LEVEL_MEDIUM\npublic static final  int CONFIDENCE_LEVEL_HIGH\npublic static final @android.annotation.FlaggedApi int CONFIDENCE_LEVEL_VERY_HIGH\nprivate final @android.service.voice.HotwordRejectedResult.HotwordConfidenceLevelValue int mConfidenceLevel\nprivate static  int defaultConfidenceLevel()\nclass HotwordRejectedResult extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genConstructor=false, genBuilder=true, genEqualsHashCode=true, genHiddenConstDefs=true, genParcelable=true, genToString=true)")
     @Deprecated
     private void __metadata() {}
 
diff --git a/core/java/android/service/voice/VoiceInteractionService.java b/core/java/android/service/voice/VoiceInteractionService.java
index fba0923..75ab48a 100644
--- a/core/java/android/service/voice/VoiceInteractionService.java
+++ b/core/java/android/service/voice/VoiceInteractionService.java
@@ -1042,13 +1042,13 @@
     @SystemApi
     @FlaggedApi(Flags.FLAG_ALLOW_TRAINING_DATA_EGRESS_FROM_HDS)
     @RequiresPermission(Manifest.permission.MANAGE_HOTWORD_DETECTION)
-    public void setIsReceiveSandboxedTrainingDataAllowed(boolean allowed) {
-        Log.i(TAG, "setIsReceiveSandboxedTrainingDataAllowed to " + allowed);
+    public void setShouldReceiveSandboxedTrainingData(boolean allowed) {
+        Log.i(TAG, "setShouldReceiveSandboxedTrainingData to " + allowed);
         if (mSystemService == null) {
             throw new IllegalStateException("Not available until onReady() is called");
         }
         try {
-            mSystemService.setIsReceiveSandboxedTrainingDataAllowed(allowed);
+            mSystemService.setShouldReceiveSandboxedTrainingData(allowed);
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
diff --git a/core/java/android/service/voice/flags/flags.aconfig b/core/java/android/service/voice/flags/flags.aconfig
index c414ef8..b596666 100644
--- a/core/java/android/service/voice/flags/flags.aconfig
+++ b/core/java/android/service/voice/flags/flags.aconfig
@@ -6,3 +6,10 @@
     description: "This flag allows the hotword detection service to egress training data to the default assistant."
     bug: "296074924"
 }
+
+flag {
+    name: "allow_hotword_bump_egress"
+    namespace: "machine_learning"
+    description: "This flag allows hotword detection service to egress reason code for hotword bump."
+    bug: "290951024"
+}
diff --git a/core/java/android/view/ISurfaceControlViewHostParent.aidl b/core/java/android/view/ISurfaceControlViewHostParent.aidl
index f42e001..559c20e 100644
--- a/core/java/android/view/ISurfaceControlViewHostParent.aidl
+++ b/core/java/android/view/ISurfaceControlViewHostParent.aidl
@@ -16,6 +16,7 @@
 
 package android.view;
 
+import android.view.KeyEvent;
 import android.view.WindowManager;
 
 /**
@@ -24,4 +25,6 @@
  */
 oneway interface ISurfaceControlViewHostParent {
     void updateParams(in WindowManager.LayoutParams[] childAttrs);
+    // To forward the back key event from embedded to host app.
+    void forwardBackKeyToParent(in KeyEvent keyEvent);
 }
diff --git a/core/java/android/view/IWindowSession.aidl b/core/java/android/view/IWindowSession.aidl
index dbacca5..9bf43a3 100644
--- a/core/java/android/view/IWindowSession.aidl
+++ b/core/java/android/view/IWindowSession.aidl
@@ -162,6 +162,8 @@
      * @param flags See {@code View#startDragAndDrop}
      * @param surface Surface containing drag shadow image
      * @param touchSource See {@code InputDevice#getSource()}
+     * @param touchDeviceId device ID of last touch event
+     * @param pointerId pointer ID of last touch event
      * @param touchX X coordinate of last touch point
      * @param touchY Y coordinate of last touch point
      * @param thumbCenterX X coordinate for the position within the shadow image that should be
@@ -171,9 +173,9 @@
      * @param data Data transferred by drag and drop
      * @return Token of drag operation which will be passed to cancelDragAndDrop.
      */
-    @UnsupportedAppUsage(maxTargetSdk = 30, trackingBug = 170729553)
     IBinder performDrag(IWindow window, int flags, in SurfaceControl surface, int touchSource,
-            float touchX, float touchY, float thumbCenterX, float thumbCenterY, in ClipData data);
+            int touchDeviceId, int touchPointerId, float touchX, float touchY, float thumbCenterX,
+            float thumbCenterY, in ClipData data);
 
     /**
      * Drops the content of the current drag operation for accessibility
diff --git a/core/java/android/view/SurfaceControlViewHost.java b/core/java/android/view/SurfaceControlViewHost.java
index 4056531..4840f00 100644
--- a/core/java/android/view/SurfaceControlViewHost.java
+++ b/core/java/android/view/SurfaceControlViewHost.java
@@ -447,6 +447,7 @@
         addWindowToken(attrs);
         view.setLayoutParams(attrs);
         mViewRoot.setView(view, attrs, null);
+        mViewRoot.setBackKeyCallbackForWindowlessWindow(mWm::forwardBackKeyToParent);
     }
 
     /**
diff --git a/core/java/android/view/SurfaceView.java b/core/java/android/view/SurfaceView.java
index a44a95a..108de28 100644
--- a/core/java/android/view/SurfaceView.java
+++ b/core/java/android/view/SurfaceView.java
@@ -37,6 +37,7 @@
 import android.graphics.Rect;
 import android.graphics.Region;
 import android.graphics.RenderNode;
+import android.hardware.input.InputManager;
 import android.os.Build;
 import android.os.Handler;
 import android.os.IBinder;
@@ -159,6 +160,8 @@
     private static final boolean DEBUG = false;
     private static final boolean DEBUG_POSITION = false;
 
+    private static final long FORWARD_BACK_KEY_TOLERANCE_MS = 100;
+
     @UnsupportedAppUsage(
             maxTargetSdk = Build.VERSION_CODES.TIRAMISU,
             publicAlternatives = "Track {@link SurfaceHolder#addCallback} instead")
@@ -326,6 +329,41 @@
                 });
             }
         }
+
+        @Override
+        public void forwardBackKeyToParent(@NonNull KeyEvent keyEvent) {
+                runOnUiThread(() -> {
+                    if (!isAttachedToWindow() || keyEvent.getKeyCode() != KeyEvent.KEYCODE_BACK) {
+                        return;
+                    }
+                    final ViewRootImpl vri = getViewRootImpl();
+                    if (vri == null) {
+                        return;
+                    }
+                    final InputManager inputManager = mContext.getSystemService(InputManager.class);
+                    if (inputManager == null) {
+                        return;
+                    }
+                    // Check that the event was created recently.
+                    final long timeDiff = SystemClock.uptimeMillis() - keyEvent.getEventTime();
+                    if (timeDiff > FORWARD_BACK_KEY_TOLERANCE_MS) {
+                        Log.e(TAG, "Ignore the input event that exceed the tolerance time, "
+                                + "exceed " + timeDiff + "ms");
+                        return;
+                    }
+                    if (inputManager.verifyInputEvent(keyEvent) == null) {
+                        Log.e(TAG, "Received invalid input event");
+                        return;
+                    }
+                    try {
+                        vri.processingBackKey(true);
+                        vri.enqueueInputEvent(keyEvent, null /* receiver */, 0 /* flags */,
+                                true /* processImmediately */);
+                    } finally {
+                        vri.processingBackKey(false);
+                    }
+                });
+        }
     };
 
     private final boolean mRtDrivenClipping = Flags.clipSurfaceviews();
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index 75f8eba..bb5ee03 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -28340,6 +28340,8 @@
                 IBinder token = mAttachInfo.mSession.performDrag(
                         mAttachInfo.mWindow, flags, null,
                         mAttachInfo.mViewRootImpl.getLastTouchSource(),
+                        mAttachInfo.mViewRootImpl.getLastTouchDeviceId(),
+                        mAttachInfo.mViewRootImpl.getLastTouchPointerId(),
                         0f, 0f, 0f, 0f, data);
                 if (ViewDebug.DEBUG_DRAG) {
                     Log.d(VIEW_LOG_TAG, "startDragAndDrop via a11y action returned " + token);
@@ -28414,7 +28416,8 @@
             }
 
             token = mAttachInfo.mSession.performDrag(mAttachInfo.mWindow, flags, surfaceControl,
-                    root.getLastTouchSource(), lastTouchPoint.x, lastTouchPoint.y,
+                    root.getLastTouchSource(), root.getLastTouchDeviceId(),
+                    root.getLastTouchPointerId(), lastTouchPoint.x, lastTouchPoint.y,
                     shadowTouchPoint.x, shadowTouchPoint.y, data);
             if (ViewDebug.DEBUG_DRAG) {
                 Log.d(VIEW_LOG_TAG, "performDrag returned " + token);
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index 5cbb42e..e83488e 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -76,13 +76,8 @@
 import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_OPTIMIZE_MEASURE;
 import static android.view.WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE;
 import static android.view.WindowManager.LayoutParams.SOFT_INPUT_MASK_ADJUST;
-import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION;
 import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_STARTING;
-import static android.view.WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
-import static android.view.WindowManager.LayoutParams.TYPE_DRAWN_APPLICATION;
 import static android.view.WindowManager.LayoutParams.TYPE_INPUT_METHOD;
-import static android.view.WindowManager.LayoutParams.TYPE_NOTIFICATION_SHADE;
-import static android.view.WindowManager.LayoutParams.TYPE_STATUS_BAR;
 import static android.view.WindowManager.LayoutParams.TYPE_STATUS_BAR_ADDITIONAL;
 import static android.view.WindowManager.LayoutParams.TYPE_SYSTEM_ALERT;
 import static android.view.WindowManager.LayoutParams.TYPE_TOAST;
@@ -96,6 +91,7 @@
 import static android.view.inputmethod.InputMethodEditorTraceProto.InputMethodClientsTraceProto.ClientSideProto.IME_FOCUS_CONTROLLER;
 import static android.view.inputmethod.InputMethodEditorTraceProto.InputMethodClientsTraceProto.ClientSideProto.INSETS_CONTROLLER;
 import static android.view.flags.Flags.toolkitSetFrameRateReadOnly;
+import static android.view.flags.Flags.toolkitMetricsForFrameRateDecision;
 
 import static com.android.input.flags.Flags.enablePointerChoreographer;
 
@@ -255,7 +251,7 @@
 import java.util.Queue;
 import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.Executor;
-import java.util.function.Consumer;
+import java.util.function.Predicate;
 
 /**
  * The top of a view hierarchy, implementing the needed protocol between View
@@ -375,6 +371,8 @@
      */
     private static final int KEEP_CLEAR_AREA_REPORT_RATE_MILLIS = 100;
 
+    private static final long NANOS_PER_SEC = 1000000000;
+
     @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
     static final ThreadLocal<HandlerActionQueue> sRunQueues = new ThreadLocal<HandlerActionQueue>();
 
@@ -620,6 +618,13 @@
     boolean mUpcomingWindowFocus;
     @GuardedBy("this")
     boolean mUpcomingInTouchMode;
+    // While set, allow this VRI to handle back key without drop it.
+    private boolean mProcessingBackKey;
+    /**
+     * Compatibility {@link OnBackInvokedCallback} for windowless window, to forward the back
+     * key event host app.
+     */
+    private Predicate<KeyEvent> mWindowlessBackKeyCallback;
 
     public boolean mTraversalScheduled;
     int mTraversalBarrier;
@@ -808,6 +813,8 @@
     final PointF mDragPoint = new PointF();
     final PointF mLastTouchPoint = new PointF();
     int mLastTouchSource;
+    int mLastTouchDeviceId = KeyCharacterMap.VIRTUAL_KEYBOARD;
+    int mLastTouchPointerId;
     /** Tracks last {@link MotionEvent#getToolType(int)} with {@link MotionEvent#ACTION_UP}. **/
     private int mLastClickToolType;
 
@@ -822,6 +829,8 @@
 
     private boolean mInsetsAnimationRunning;
 
+    private long mPreviousFrameDrawnTime = -1;
+
     /**
      * The resolved pointer icon type requested by this window.
      * A null value indicates the resolved pointer icon has not yet been calculated.
@@ -1059,11 +1068,14 @@
     private boolean mChildBoundingInsetsChanged = false;
 
     private String mTag = TAG;
+    private String mFpsTraceName;
 
     private static boolean sToolkitSetFrameRateReadOnlyFlagValue;
+    private static boolean sToolkitMetricsForFrameRateDecisionFlagValue;
 
     static {
         sToolkitSetFrameRateReadOnlyFlagValue = toolkitSetFrameRateReadOnly();
+        sToolkitMetricsForFrameRateDecisionFlagValue = toolkitMetricsForFrameRateDecision();
     }
 
     // The latest input event from the gesture that was used to resolve the pointer icon.
@@ -1307,6 +1319,7 @@
 
                 attrs = mWindowAttributes;
                 setTag();
+                mFpsTraceName = "FPS of " + getTitle();
 
                 if (DEBUG_KEEP_SCREEN_ON && (mClientWindowLayoutFlags
                         & WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON) != 0
@@ -3194,7 +3207,11 @@
             host.dispatchAttachedToWindow(mAttachInfo, 0);
             mAttachInfo.mTreeObserver.dispatchOnWindowAttachedChange(true);
             dispatchApplyInsets(host);
-            if (!mOnBackInvokedDispatcher.isOnBackInvokedCallbackEnabled()) {
+            if (!mOnBackInvokedDispatcher.isOnBackInvokedCallbackEnabled()
+                    // Don't register compat OnBackInvokedCallback for windowless window.
+                    // The onBackInvoked event by default should forward to host app, so the
+                    // host app can decide the behavior.
+                    && mWindowlessBackKeyCallback == null) {
                 // For apps requesting legacy back behavior, we add a compat callback that
                 // dispatches {@link KeyEvent#KEYCODE_BACK} to their root views.
                 // This way from system point of view, these apps are providing custom
@@ -4726,6 +4743,31 @@
         }
     }
 
+    /**
+     * Called from draw() to collect metrics for frame rate decision.
+     */
+    private void collectFrameRateDecisionMetrics() {
+        if (!Trace.isEnabled()) {
+            if (mPreviousFrameDrawnTime > 0) mPreviousFrameDrawnTime = -1;
+            return;
+        }
+
+        if (mPreviousFrameDrawnTime < 0) {
+            mPreviousFrameDrawnTime = mChoreographer.getExpectedPresentationTimeNanos();
+            return;
+        }
+
+        long expectedDrawnTime = mChoreographer.getExpectedPresentationTimeNanos();
+        long timeDiff = expectedDrawnTime - mPreviousFrameDrawnTime;
+        if (timeDiff <= 0) {
+            return;
+        }
+
+        long fps = NANOS_PER_SEC / timeDiff;
+        Trace.setCounter(mFpsTraceName, fps);
+        mPreviousFrameDrawnTime = expectedDrawnTime;
+    }
+
     private void reportDrawFinished(@Nullable Transaction t, int seqId) {
         if (DEBUG_BLAST) {
             Log.d(mTag, "reportDrawFinished");
@@ -5044,6 +5086,9 @@
         if (DEBUG_FPS) {
             trackFPS();
         }
+        if (sToolkitMetricsForFrameRateDecisionFlagValue) {
+            collectFrameRateDecisionMetrics();
+        }
 
         if (!sFirstDrawComplete) {
             synchronized (sFirstDrawHandlers) {
@@ -6660,7 +6705,8 @@
 
             // Find a reason for dropping or canceling the event.
             final String reason;
-            if (!mAttachInfo.mHasWindowFocus
+            // The embedded window is focused, allow this VRI to handle back key.
+            if (!mAttachInfo.mHasWindowFocus && !(mProcessingBackKey && isBack(q.mEvent))
                     && !q.mEvent.isFromSource(InputDevice.SOURCE_CLASS_POINTER)
                     && !isAutofillUiShowing()) {
                 // This is a non-pointer event and the window doesn't currently have input focus
@@ -6883,10 +6929,20 @@
 
                 // If the new back dispatch is enabled, intercept KEYCODE_BACK before it reaches the
                 // view tree or IME, and invoke the appropriate {@link OnBackInvokedCallback}.
-                if (isBack(keyEvent)
-                        && mContext != null
-                        && mOnBackInvokedDispatcher.isOnBackInvokedCallbackEnabled()) {
-                    return doOnBackKeyEvent(keyEvent);
+                if (isBack(keyEvent)) {
+                    if (mWindowlessBackKeyCallback != null) {
+                        if (mWindowlessBackKeyCallback.test(keyEvent)) {
+                            return keyEvent.getAction() == KeyEvent.ACTION_UP
+                                    && !keyEvent.isCanceled()
+                                    ? FINISH_HANDLED : FINISH_NOT_HANDLED;
+                        } else {
+                            // Unable to forward the back key to host, forward to next stage.
+                            return FORWARD;
+                        }
+                    } else if (mContext != null
+                            && mOnBackInvokedDispatcher.isOnBackInvokedCallbackEnabled()) {
+                        return doOnBackKeyEvent(keyEvent);
+                    }
                 }
 
                 if (mInputQueue != null) {
@@ -7109,6 +7165,8 @@
                 mLastTouchPoint.x = event.getRawX();
                 mLastTouchPoint.y = event.getRawY();
                 mLastTouchSource = event.getSource();
+                mLastTouchDeviceId = event.getDeviceId();
+                mLastTouchPointerId = event.getPointerId(0);
 
                 // Register last ACTION_UP. This will be propagated to IME.
                 if (event.getActionMasked() == MotionEvent.ACTION_UP) {
@@ -8520,6 +8578,14 @@
         return mLastTouchSource;
     }
 
+    public int getLastTouchDeviceId() {
+        return mLastTouchDeviceId;
+    }
+
+    public int getLastTouchPointerId() {
+        return mLastTouchPointerId;
+    }
+
     /**
      * Used by InputMethodManager.
      * @hide
@@ -10529,6 +10595,11 @@
         mHandler.obtainMessage(MSG_REQUEST_SCROLL_CAPTURE, listener).sendToTarget();
     }
 
+    // Make this VRI able to process back key without drop it.
+    void processingBackKey(boolean processing) {
+        mProcessingBackKey = processing;
+    }
+
     /**
      * Collect and include any ScrollCaptureCallback instances registered with the window.
      *
@@ -11753,13 +11824,18 @@
                 KeyCharacterMap.VIRTUAL_KEYBOARD, 0 /* scancode */,
                 KeyEvent.FLAG_FROM_SYSTEM | KeyEvent.FLAG_VIRTUAL_HARD_KEY,
                 InputDevice.SOURCE_KEYBOARD);
-        enqueueInputEvent(ev);
+        enqueueInputEvent(ev, null /* receiver */, 0 /* flags */, true /* processImmediately */);
     }
 
     private void registerCompatOnBackInvokedCallback() {
         mCompatOnBackInvokedCallback = () -> {
-            sendBackKeyEvent(KeyEvent.ACTION_DOWN);
-            sendBackKeyEvent(KeyEvent.ACTION_UP);
+            try {
+                processingBackKey(true);
+                sendBackKeyEvent(KeyEvent.ACTION_DOWN);
+                sendBackKeyEvent(KeyEvent.ACTION_UP);
+            } finally {
+                processingBackKey(false);
+            }
         };
         if (mOnBackInvokedDispatcher.hasImeOnBackInvokedDispatcher()) {
             Log.d(TAG, "Skip registering CompatOnBackInvokedCallback on IME dispatcher");
@@ -12097,11 +12173,9 @@
         boolean desiredAction = motionEventAction == MotionEvent.ACTION_DOWN
                 || motionEventAction == MotionEvent.ACTION_MOVE
                 || motionEventAction == MotionEvent.ACTION_UP;
-        boolean desiredType = windowType == TYPE_BASE_APPLICATION || windowType == TYPE_APPLICATION
-                || windowType == TYPE_APPLICATION_STARTING || windowType == TYPE_DRAWN_APPLICATION
-                || windowType == TYPE_NOTIFICATION_SHADE || windowType == TYPE_STATUS_BAR;
+        boolean undesiredType = windowType == TYPE_INPUT_METHOD;
         // use toolkitSetFrameRate flag to gate the change
-        return desiredAction && desiredType && sToolkitSetFrameRateReadOnlyFlagValue;
+        return desiredAction && !undesiredType && sToolkitSetFrameRateReadOnlyFlagValue;
     }
 
     /**
@@ -12196,4 +12270,13 @@
         }
         return false;
     }
+
+    /**
+     * Set the default back key callback for windowless window, to forward the back key event
+     * to host app.
+     * MUST NOT call this method for normal window.
+     */
+    void setBackKeyCallbackForWindowlessWindow(@NonNull Predicate<KeyEvent> callback) {
+        mWindowlessBackKeyCallback = callback;
+    }
 }
diff --git a/core/java/android/view/WindowlessWindowManager.java b/core/java/android/view/WindowlessWindowManager.java
index d817e6f..d6ac562 100644
--- a/core/java/android/view/WindowlessWindowManager.java
+++ b/core/java/android/view/WindowlessWindowManager.java
@@ -16,6 +16,7 @@
 
 package android.view;
 
+import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.app.WindowConfiguration;
 import android.content.res.Configuration;
@@ -488,8 +489,9 @@
 
     @Override
     public android.os.IBinder performDrag(android.view.IWindow window, int flags,
-            android.view.SurfaceControl surface, int touchSource, float touchX, float touchY,
-            float thumbCenterX, float thumbCenterY, android.content.ClipData data) {
+            android.view.SurfaceControl surface, int touchSource, int touchDeviceId,
+            int touchPointerId, float touchX, float touchY, float thumbCenterX, float thumbCenterY,
+            android.content.ClipData data) {
         return null;
     }
 
@@ -703,4 +705,17 @@
             }
         }
     }
+
+    boolean forwardBackKeyToParent(@NonNull KeyEvent keyEvent) {
+        if (mParentInterface == null) {
+            return false;
+        }
+        try {
+            mParentInterface.forwardBackKeyToParent(keyEvent);
+        } catch (RemoteException e) {
+            Log.e(TAG, "Failed to forward back key To Parent: ", e);
+            return false;
+        }
+        return true;
+    }
 }
diff --git a/core/java/android/view/accessibility/flags/accessibility_flags.aconfig b/core/java/android/view/accessibility/flags/accessibility_flags.aconfig
index e057660..0cc19fb 100644
--- a/core/java/android/view/accessibility/flags/accessibility_flags.aconfig
+++ b/core/java/android/view/accessibility/flags/accessibility_flags.aconfig
@@ -59,6 +59,13 @@
 }
 
 flag {
+    name: "motion_event_observing"
+    namespace: "accessibility"
+    description: "Allows accessibility services to intercept but not consume motion events from specified sources."
+    bug: "297595990"
+}
+
+flag {
     namespace: "accessibility"
     name: "granular_scrolling"
     description: "Allow the use of granular scrolling. This allows scrollable nodes to scroll by increments other than a full screen"
diff --git a/core/java/android/view/flags/refresh_rate_flags.aconfig b/core/java/android/view/flags/refresh_rate_flags.aconfig
index a467afe..0aa516e 100644
--- a/core/java/android/view/flags/refresh_rate_flags.aconfig
+++ b/core/java/android/view/flags/refresh_rate_flags.aconfig
@@ -42,4 +42,12 @@
   namespace: "core_graphics"
   description: "Enable the `setFrameRate` callback"
   bug: "299946220"
+}
+
+flag {
+    name: "toolkit_metrics_for_frame_rate_decision"
+    namespace: "toolkit"
+    description: "Feature flag for toolkit metrics collecting for frame rate decision"
+    bug: "301343249"
+    is_fixed_read_only: true
 }
\ No newline at end of file
diff --git a/core/java/android/widget/RemoteViews.java b/core/java/android/widget/RemoteViews.java
index da31348..8ad10af 100644
--- a/core/java/android/widget/RemoteViews.java
+++ b/core/java/android/widget/RemoteViews.java
@@ -16,6 +16,7 @@
 
 package android.widget;
 
+import static android.appwidget.flags.Flags.remoteAdapterConversion;
 import static android.view.inputmethod.Flags.FLAG_HOME_SCREEN_HANDWRITING_DELEGATOR;
 
 import android.annotation.AttrRes;
@@ -36,7 +37,6 @@
 import android.app.Activity;
 import android.app.ActivityOptions;
 import android.app.ActivityThread;
-import android.app.AppGlobals;
 import android.app.Application;
 import android.app.LoadedApk;
 import android.app.PendingIntent;
@@ -108,7 +108,6 @@
 import android.widget.CompoundButton.OnCheckedChangeListener;
 
 import com.android.internal.R;
-import com.android.internal.config.sysui.SystemUiDeviceConfigFlags;
 import com.android.internal.util.Preconditions;
 import com.android.internal.widget.IRemoteViewsFactory;
 
@@ -4950,21 +4949,11 @@
      */
     @Deprecated
     public void setRemoteAdapter(@IdRes int viewId, Intent intent) {
-        if (isAdapterConversionEnabled()) {
+        if (remoteAdapterConversion()) {
             addAction(new SetRemoteCollectionItemListAdapterAction(viewId, intent));
-            return;
+        } else {
+            addAction(new SetRemoteViewsAdapterIntent(viewId, intent));
         }
-        addAction(new SetRemoteViewsAdapterIntent(viewId, intent));
-    }
-
-    /**
-     * @hide
-     * @return True if the remote adapter conversion is enabled
-     */
-    public static boolean isAdapterConversionEnabled() {
-        return AppGlobals.getIntCoreSetting(
-                SystemUiDeviceConfigFlags.REMOTEVIEWS_ADAPTER_CONVERSION,
-                SystemUiDeviceConfigFlags.REMOTEVIEWS_ADAPTER_CONVERSION_DEFAULT ? 1 : 0) != 0;
     }
 
     /**
diff --git a/core/java/android/window/flags/windowing_frontend.aconfig b/core/java/android/window/flags/windowing_frontend.aconfig
index 7f65c52..07beb11 100644
--- a/core/java/android/window/flags/windowing_frontend.aconfig
+++ b/core/java/android/window/flags/windowing_frontend.aconfig
@@ -16,7 +16,7 @@
 
 flag {
     name: "defer_display_updates"
-    namespace: "window_manager"
+    namespace: "windowing_frontend"
     description: "Feature flag for deferring DisplayManager updates to WindowManager if Shell transition is running"
     bug: "259220649"
     is_fixed_read_only: true
diff --git a/core/java/com/android/internal/app/IVoiceInteractionManagerService.aidl b/core/java/com/android/internal/app/IVoiceInteractionManagerService.aidl
index 82ee8fc..e92c6a6 100644
--- a/core/java/com/android/internal/app/IVoiceInteractionManagerService.aidl
+++ b/core/java/com/android/internal/app/IVoiceInteractionManagerService.aidl
@@ -397,5 +397,5 @@
       * sandboxed detection (from trusted process).
       */
       @EnforcePermission("MANAGE_HOTWORD_DETECTION")
-      void setIsReceiveSandboxedTrainingDataAllowed(boolean allowed);
+      void setShouldReceiveSandboxedTrainingData(boolean allowed);
 }
diff --git a/core/java/com/android/internal/config/sysui/SystemUiDeviceConfigFlags.java b/core/java/com/android/internal/config/sysui/SystemUiDeviceConfigFlags.java
index e494346..bd806bf 100644
--- a/core/java/com/android/internal/config/sysui/SystemUiDeviceConfigFlags.java
+++ b/core/java/com/android/internal/config/sysui/SystemUiDeviceConfigFlags.java
@@ -519,24 +519,6 @@
     public static final String TASK_MANAGER_SHOW_FOOTER_DOT = "task_manager_show_footer_dot";
 
     /**
-     * (boolean) Whether to enable the adapter conversion in RemoteViews
-     */
-    public static final String REMOTEVIEWS_ADAPTER_CONVERSION =
-            "CursorControlFeature__remoteviews_adapter_conversion";
-
-    /**
-     * The key name used in app core settings for {@link #REMOTEVIEWS_ADAPTER_CONVERSION}
-     */
-    public static final String KEY_REMOTEVIEWS_ADAPTER_CONVERSION =
-            "systemui__remoteviews_adapter_conversion";
-
-    /**
-     * Default value for whether the adapter conversion is enabled or not. This is set for
-     * RemoteViews and should not be a common practice.
-     */
-    public static final boolean REMOTEVIEWS_ADAPTER_CONVERSION_DEFAULT = false;
-
-    /**
      * (boolean) Whether the task manager should show a stop button if the app is allowlisted
      * by the user.
      */
diff --git a/core/java/com/android/internal/jank/Cuj.java b/core/java/com/android/internal/jank/Cuj.java
index f460233..96740c5 100644
--- a/core/java/com/android/internal/jank/Cuj.java
+++ b/core/java/com/android/internal/jank/Cuj.java
@@ -109,6 +109,7 @@
      * eg: Exit the app using back gesture.
      */
     public static final int CUJ_LAUNCHER_APP_CLOSE_TO_HOME_FALLBACK = 78;
+    // 79 is reserved.
     public static final int CUJ_IME_INSETS_SHOW_ANIMATION = 80;
     public static final int CUJ_IME_INSETS_HIDE_ANIMATION = 81;
 
@@ -119,10 +120,11 @@
     public static final int CUJ_PREDICTIVE_BACK_CROSS_ACTIVITY = 84;
     public static final int CUJ_PREDICTIVE_BACK_CROSS_TASK = 85;
     public static final int CUJ_PREDICTIVE_BACK_HOME = 86;
+    public static final int CUJ_LAUNCHER_SEARCH_QSB_OPEN = 87;
 
     // When adding a CUJ, update this and make sure to also update CUJ_TO_STATSD_INTERACTION_TYPE.
     @VisibleForTesting
-    static final int LAST_CUJ = CUJ_PREDICTIVE_BACK_HOME;
+    static final int LAST_CUJ = CUJ_LAUNCHER_SEARCH_QSB_OPEN;
 
     /** @hide */
     @IntDef({
@@ -204,6 +206,7 @@
             CUJ_PREDICTIVE_BACK_CROSS_ACTIVITY,
             CUJ_PREDICTIVE_BACK_CROSS_TASK,
             CUJ_PREDICTIVE_BACK_HOME,
+            CUJ_LAUNCHER_SEARCH_QSB_OPEN,
     })
     @Retention(RetentionPolicy.SOURCE)
     public @interface CujType {
@@ -295,6 +298,8 @@
         CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_PREDICTIVE_BACK_CROSS_ACTIVITY] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__PREDICTIVE_BACK_CROSS_ACTIVITY;
         CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_PREDICTIVE_BACK_CROSS_TASK] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__PREDICTIVE_BACK_CROSS_TASK;
         CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_PREDICTIVE_BACK_HOME] = FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__PREDICTIVE_BACK_HOME;
+        CUJ_TO_STATSD_INTERACTION_TYPE[CUJ_LAUNCHER_SEARCH_QSB_OPEN] =
+            FrameworkStatsLog.UIINTERACTION_FRAME_INFO_REPORTED__INTERACTION_TYPE__LAUNCHER_SEARCH_QSB_OPEN;
     }
 
     private Cuj() {
@@ -467,6 +472,8 @@
                 return "PREDICTIVE_BACK_CROSS_TASK";
             case CUJ_PREDICTIVE_BACK_HOME:
                 return "PREDICTIVE_BACK_HOME";
+            case CUJ_LAUNCHER_SEARCH_QSB_OPEN:
+                return "LAUNCHER_SEARCH_QSB_OPEN";
         }
         return "UNKNOWN";
     }
diff --git a/core/java/com/android/internal/net/TEST_MAPPING b/core/java/com/android/internal/net/TEST_MAPPING
index 971ad36..f935946c 100644
--- a/core/java/com/android/internal/net/TEST_MAPPING
+++ b/core/java/com/android/internal/net/TEST_MAPPING
@@ -1,5 +1,5 @@
 {
-  "postsubmit": [
+  "presubmit": [
     {
       "name": "FrameworksNetTests",
       "options": [
diff --git a/core/java/com/android/internal/os/BatteryStatsHistory.java b/core/java/com/android/internal/os/BatteryStatsHistory.java
index 7d78f29..0be9804 100644
--- a/core/java/com/android/internal/os/BatteryStatsHistory.java
+++ b/core/java/com/android/internal/os/BatteryStatsHistory.java
@@ -72,6 +72,7 @@
  * All interfaces in BatteryStatsHistory should only be called by BatteryStatsImpl and protected by
  * locks on BatteryStatsImpl object.
  */
+@android.ravenwood.annotation.RavenwoodKeepWholeClass
 public class BatteryStatsHistory {
     private static final boolean DEBUG = false;
     private static final String TAG = "BatteryStatsHistory";
@@ -259,6 +260,7 @@
      * until the first change occurs.
      */
     @VisibleForTesting
+    @android.ravenwood.annotation.RavenwoodKeepWholeClass
     public static class TraceDelegate {
         // Note: certain tests currently run as platform_app which is not allowed
         // to set debug system properties. To ensure that system properties are set
@@ -391,10 +393,18 @@
     public BatteryStatsHistory(int maxHistoryFiles, int maxHistoryBufferSize,
             HistoryStepDetailsCalculator stepDetailsCalculator, Clock clock,
             MonotonicClock monotonicClock) {
+        this(maxHistoryFiles, maxHistoryBufferSize, stepDetailsCalculator, clock, monotonicClock,
+                new TraceDelegate());
+    }
+
+    @VisibleForTesting
+    public BatteryStatsHistory(int maxHistoryFiles, int maxHistoryBufferSize,
+            HistoryStepDetailsCalculator stepDetailsCalculator, Clock clock,
+            MonotonicClock monotonicClock, TraceDelegate traceDelegate) {
         mMaxHistoryFiles = maxHistoryFiles;
         mMaxHistoryBufferSize = maxHistoryBufferSize;
         mStepDetailsCalculator = stepDetailsCalculator;
-        mTracer = new TraceDelegate();
+        mTracer = traceDelegate;
         mClock = clock;
         mMonotonicClock = monotonicClock;
 
@@ -2096,6 +2106,7 @@
      * fewer bytes.  It is a bit more expensive than just writing the long into the parcel,
      * but at scale saves a lot of storage and allows recording of longer battery history.
      */
+    @android.ravenwood.annotation.RavenwoodKeepWholeClass
     public static final class VarintParceler {
         /**
          * Writes an array of longs into Parcel using the varint format, see
diff --git a/core/java/com/android/internal/os/BatteryStatsHistoryIterator.java b/core/java/com/android/internal/os/BatteryStatsHistoryIterator.java
index 6bd5898..2dffe15 100644
--- a/core/java/com/android/internal/os/BatteryStatsHistoryIterator.java
+++ b/core/java/com/android/internal/os/BatteryStatsHistoryIterator.java
@@ -28,6 +28,7 @@
 /**
  * An iterator for {@link BatteryStats.HistoryItem}'s.
  */
+@android.ravenwood.annotation.RavenwoodKeepWholeClass
 public class BatteryStatsHistoryIterator implements Iterator<BatteryStats.HistoryItem>,
         AutoCloseable {
     private static final boolean DEBUG = false;
diff --git a/core/java/com/android/internal/os/PowerStats.java b/core/java/com/android/internal/os/PowerStats.java
index 1a7efac..56263fb 100644
--- a/core/java/com/android/internal/os/PowerStats.java
+++ b/core/java/com/android/internal/os/PowerStats.java
@@ -41,6 +41,7 @@
  * Container for power stats, acquired by various PowerStatsCollector classes. See subclasses for
  * details.
  */
+@android.ravenwood.annotation.RavenwoodKeepWholeClass
 public final class PowerStats {
     private static final String TAG = "PowerStats";
 
@@ -67,6 +68,7 @@
      * This descriptor is used for storing PowerStats and can also be used by power models
      * to adjust the algorithm in accordance with the stats available on the device.
      */
+    @android.ravenwood.annotation.RavenwoodKeepWholeClass
     public static class Descriptor {
         public static final String XML_TAG_DESCRIPTOR = "descriptor";
         private static final String XML_ATTR_ID = "id";
diff --git a/core/java/com/android/internal/pm/parsing/AppInfoUtils.java b/core/java/com/android/internal/pm/parsing/AppInfoUtils.java
new file mode 100644
index 0000000..38a2fe2
--- /dev/null
+++ b/core/java/com/android/internal/pm/parsing/AppInfoUtils.java
@@ -0,0 +1,124 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.internal.pm.parsing;
+
+import android.annotation.CheckResult;
+import android.content.pm.ApplicationInfo;
+
+import com.android.internal.pm.parsing.pkg.AndroidPackageLegacyUtils;
+import com.android.internal.pm.pkg.parsing.ParsingPackageUtils;
+import com.android.server.pm.pkg.AndroidPackage;
+
+public class AppInfoUtils {
+
+    /**
+     * @see ApplicationInfo#flags
+     */
+    public static int appInfoFlags(AndroidPackage pkg) {
+        // @formatter:off
+        int pkgWithoutStateFlags = flag(pkg.isExternalStorage(), ApplicationInfo.FLAG_EXTERNAL_STORAGE)
+                | flag(pkg.isHardwareAccelerated(), ApplicationInfo.FLAG_HARDWARE_ACCELERATED)
+                | flag(pkg.isBackupAllowed(), ApplicationInfo.FLAG_ALLOW_BACKUP)
+                | flag(pkg.isKillAfterRestoreAllowed(), ApplicationInfo.FLAG_KILL_AFTER_RESTORE)
+                | flag(pkg.isRestoreAnyVersion(), ApplicationInfo.FLAG_RESTORE_ANY_VERSION)
+                | flag(pkg.isFullBackupOnly(), ApplicationInfo.FLAG_FULL_BACKUP_ONLY)
+                | flag(pkg.isPersistent(), ApplicationInfo.FLAG_PERSISTENT)
+                | flag(pkg.isDebuggable(), ApplicationInfo.FLAG_DEBUGGABLE)
+                | flag(pkg.isVmSafeMode(), ApplicationInfo.FLAG_VM_SAFE_MODE)
+                | flag(pkg.isDeclaredHavingCode(), ApplicationInfo.FLAG_HAS_CODE)
+                | flag(pkg.isTaskReparentingAllowed(), ApplicationInfo.FLAG_ALLOW_TASK_REPARENTING)
+                | flag(pkg.isClearUserDataAllowed(), ApplicationInfo.FLAG_ALLOW_CLEAR_USER_DATA)
+                | flag(pkg.isLargeHeap(), ApplicationInfo.FLAG_LARGE_HEAP)
+                | flag(pkg.isCleartextTrafficAllowed(), ApplicationInfo.FLAG_USES_CLEARTEXT_TRAFFIC)
+                | flag(pkg.isRtlSupported(), ApplicationInfo.FLAG_SUPPORTS_RTL)
+                | flag(pkg.isTestOnly(), ApplicationInfo.FLAG_TEST_ONLY)
+                | flag(pkg.isMultiArch(), ApplicationInfo.FLAG_MULTIARCH)
+                | flag(pkg.isExtractNativeLibrariesRequested(), ApplicationInfo.FLAG_EXTRACT_NATIVE_LIBS)
+                | flag(pkg.isGame(), ApplicationInfo.FLAG_IS_GAME)
+                | flag(pkg.isSmallScreensSupported(), ApplicationInfo.FLAG_SUPPORTS_SMALL_SCREENS)
+                | flag(pkg.isNormalScreensSupported(), ApplicationInfo.FLAG_SUPPORTS_NORMAL_SCREENS)
+                | flag(pkg.isLargeScreensSupported(), ApplicationInfo.FLAG_SUPPORTS_LARGE_SCREENS)
+                | flag(pkg.isExtraLargeScreensSupported(), ApplicationInfo.FLAG_SUPPORTS_XLARGE_SCREENS)
+                | flag(pkg.isResizeable(), ApplicationInfo.FLAG_RESIZEABLE_FOR_SCREENS)
+                | flag(pkg.isAnyDensity(), ApplicationInfo.FLAG_SUPPORTS_SCREEN_DENSITIES)
+                | flag(AndroidPackageLegacyUtils.isSystem(pkg), ApplicationInfo.FLAG_SYSTEM)
+                | flag(pkg.isFactoryTest(), ApplicationInfo.FLAG_FACTORY_TEST);
+
+        return pkgWithoutStateFlags;
+        // @formatter:on
+    }
+
+    /** @see ApplicationInfo#privateFlags */
+    public static int appInfoPrivateFlags(AndroidPackage pkg) {
+        // @formatter:off
+        int pkgWithoutStateFlags = flag(pkg.isStaticSharedLibrary(), ApplicationInfo.PRIVATE_FLAG_STATIC_SHARED_LIBRARY)
+                | flag(pkg.isResourceOverlay(), ApplicationInfo.PRIVATE_FLAG_IS_RESOURCE_OVERLAY)
+                | flag(pkg.isIsolatedSplitLoading(), ApplicationInfo.PRIVATE_FLAG_ISOLATED_SPLIT_LOADING)
+                | flag(pkg.isHasDomainUrls(), ApplicationInfo.PRIVATE_FLAG_HAS_DOMAIN_URLS)
+                | flag(pkg.isProfileableByShell(), ApplicationInfo.PRIVATE_FLAG_PROFILEABLE_BY_SHELL)
+                | flag(pkg.isBackupInForeground(), ApplicationInfo.PRIVATE_FLAG_BACKUP_IN_FOREGROUND)
+                | flag(pkg.isUseEmbeddedDex(), ApplicationInfo.PRIVATE_FLAG_USE_EMBEDDED_DEX)
+                | flag(pkg.isDefaultToDeviceProtectedStorage(), ApplicationInfo.PRIVATE_FLAG_DEFAULT_TO_DEVICE_PROTECTED_STORAGE)
+                | flag(pkg.isDirectBootAware(), ApplicationInfo.PRIVATE_FLAG_DIRECT_BOOT_AWARE)
+                | flag(pkg.isPartiallyDirectBootAware(), ApplicationInfo.PRIVATE_FLAG_PARTIALLY_DIRECT_BOOT_AWARE)
+                | flag(pkg.isClearUserDataOnFailedRestoreAllowed(), ApplicationInfo.PRIVATE_FLAG_ALLOW_CLEAR_USER_DATA_ON_FAILED_RESTORE)
+                | flag(pkg.isAllowAudioPlaybackCapture(), ApplicationInfo.PRIVATE_FLAG_ALLOW_AUDIO_PLAYBACK_CAPTURE)
+                | flag(pkg.isRequestLegacyExternalStorage(), ApplicationInfo.PRIVATE_FLAG_REQUEST_LEGACY_EXTERNAL_STORAGE)
+                | flag(pkg.isNonSdkApiRequested(), ApplicationInfo.PRIVATE_FLAG_USES_NON_SDK_API)
+                | flag(pkg.isUserDataFragile(), ApplicationInfo.PRIVATE_FLAG_HAS_FRAGILE_USER_DATA)
+                | flag(pkg.isSaveStateDisallowed(), ApplicationInfo.PRIVATE_FLAG_CANT_SAVE_STATE)
+                | flag(pkg.isResizeableActivityViaSdkVersion(), ApplicationInfo.PRIVATE_FLAG_ACTIVITIES_RESIZE_MODE_RESIZEABLE_VIA_SDK_VERSION)
+                | flag(pkg.isAllowNativeHeapPointerTagging(), ApplicationInfo.PRIVATE_FLAG_ALLOW_NATIVE_HEAP_POINTER_TAGGING)
+                | flag(AndroidPackageLegacyUtils.isSystemExt(pkg), ApplicationInfo.PRIVATE_FLAG_SYSTEM_EXT)
+                | flag(AndroidPackageLegacyUtils.isPrivileged(pkg), ApplicationInfo.PRIVATE_FLAG_PRIVILEGED)
+                | flag(AndroidPackageLegacyUtils.isOem(pkg), ApplicationInfo.PRIVATE_FLAG_OEM)
+                | flag(AndroidPackageLegacyUtils.isVendor(pkg), ApplicationInfo.PRIVATE_FLAG_VENDOR)
+                | flag(AndroidPackageLegacyUtils.isProduct(pkg), ApplicationInfo.PRIVATE_FLAG_PRODUCT)
+                | flag(AndroidPackageLegacyUtils.isOdm(pkg), ApplicationInfo.PRIVATE_FLAG_ODM)
+                | flag(pkg.isSignedWithPlatformKey(), ApplicationInfo.PRIVATE_FLAG_SIGNED_WITH_PLATFORM_KEY);
+
+        Boolean resizeableActivity = pkg.getResizeableActivity();
+        if (resizeableActivity != null) {
+            if (resizeableActivity) {
+                pkgWithoutStateFlags |= ApplicationInfo.PRIVATE_FLAG_ACTIVITIES_RESIZE_MODE_RESIZEABLE;
+            } else {
+                pkgWithoutStateFlags |= ApplicationInfo.PRIVATE_FLAG_ACTIVITIES_RESIZE_MODE_UNRESIZEABLE;
+            }
+        }
+
+        return pkgWithoutStateFlags;
+        // @formatter:on
+    }
+
+
+    /** @see ApplicationInfo#privateFlagsExt */
+    public static int appInfoPrivateFlagsExt(AndroidPackage pkg,
+            boolean isAllowlistedForHiddenApis) {
+        // @formatter:off
+        int pkgWithoutStateFlags = flag(pkg.isProfileable(), ApplicationInfo.PRIVATE_FLAG_EXT_PROFILEABLE)
+                | flag(pkg.hasRequestForegroundServiceExemption(), ApplicationInfo.PRIVATE_FLAG_EXT_REQUEST_FOREGROUND_SERVICE_EXEMPTION)
+                | flag(pkg.isAttributionsUserVisible(), ApplicationInfo.PRIVATE_FLAG_EXT_ATTRIBUTIONS_ARE_USER_VISIBLE)
+                | flag(pkg.isOnBackInvokedCallbackEnabled(), ApplicationInfo.PRIVATE_FLAG_EXT_ENABLE_ON_BACK_INVOKED_CALLBACK)
+                | flag(isAllowlistedForHiddenApis, ApplicationInfo.PRIVATE_FLAG_EXT_ALLOWLISTED_FOR_HIDDEN_APIS);
+        return pkgWithoutStateFlags;
+        // @formatter:on
+    }
+
+    @CheckResult
+    private static int flag(boolean hasFlag, int flag) {
+        return hasFlag ? flag : 0;
+    }
+}
diff --git a/core/java/com/android/internal/pm/parsing/PackageParserException.java b/core/java/com/android/internal/pm/parsing/PackageParserException.java
new file mode 100644
index 0000000..4250bbd
--- /dev/null
+++ b/core/java/com/android/internal/pm/parsing/PackageParserException.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.internal.pm.parsing;
+
+public class PackageParserException extends Exception {
+    public final int error;
+
+    public PackageParserException(int error, String detailMessage) {
+        super(detailMessage);
+        this.error = error;
+    }
+
+    public PackageParserException(int error, String detailMessage, Throwable throwable) {
+        super(detailMessage, throwable);
+        this.error = error;
+    }
+}
\ No newline at end of file
diff --git a/core/java/com/android/internal/pm/parsing/pkg/AndroidPackageLegacyUtils.java b/core/java/com/android/internal/pm/parsing/pkg/AndroidPackageLegacyUtils.java
new file mode 100644
index 0000000..e65f1c9
--- /dev/null
+++ b/core/java/com/android/internal/pm/parsing/pkg/AndroidPackageLegacyUtils.java
@@ -0,0 +1,128 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.pm.parsing.pkg;
+
+import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageInfo;
+
+import com.android.internal.pm.pkg.parsing.ParsingPackageHidden;
+import com.android.server.pm.pkg.AndroidPackage;
+
+/** @hide */
+public class AndroidPackageLegacyUtils {
+
+    private AndroidPackageLegacyUtils() {
+    }
+
+    /**
+     * Returns the primary ABI as parsed from the package. Used only during parsing and derivation.
+     * Otherwise prefer {@link PackageState#getPrimaryCpuAbi()}.
+     */
+    public static String getRawPrimaryCpuAbi(AndroidPackage pkg) {
+        return ((AndroidPackageHidden) pkg).getPrimaryCpuAbi();
+    }
+
+    /**
+     * Returns the secondary ABI as parsed from the package. Used only during parsing and
+     * derivation. Otherwise prefer {@link PackageState#getSecondaryCpuAbi()}.
+     */
+    public static String getRawSecondaryCpuAbi(@NonNull AndroidPackage pkg) {
+        return ((AndroidPackageHidden) pkg).getSecondaryCpuAbi();
+    }
+
+    @Deprecated
+    @NonNull
+    public static ApplicationInfo generateAppInfoWithoutState(AndroidPackage pkg) {
+        return ((AndroidPackageHidden) pkg).toAppInfoWithoutState();
+    }
+
+    /**
+     * Replacement of unnecessary legacy getRealPackage. Only returns a value if the package was
+     * actually renamed.
+     */
+    @Nullable
+    public static String getRealPackageOrNull(@NonNull AndroidPackage pkg, boolean isSystem) {
+        if (pkg.getOriginalPackages().isEmpty() || !isSystem) {
+            return null;
+        }
+
+        return pkg.getManifestPackageName();
+    }
+
+    public static void fillVersionCodes(@NonNull AndroidPackage pkg, @NonNull PackageInfo info) {
+        info.versionCode = ((ParsingPackageHidden) pkg).getVersionCode();
+        info.versionCodeMajor = ((ParsingPackageHidden) pkg).getVersionCodeMajor();
+    }
+
+    /**
+     * @deprecated Use {@link PackageState#isSystem}
+     */
+    @Deprecated
+    public static boolean isSystem(@NonNull AndroidPackage pkg) {
+        return ((AndroidPackageHidden) pkg).isSystem();
+    }
+
+    /**
+     * @deprecated Use {@link PackageState#isSystemExt}
+     */
+    @Deprecated
+    public static boolean isSystemExt(@NonNull AndroidPackage pkg) {
+        return ((AndroidPackageHidden) pkg).isSystemExt();
+    }
+
+    /**
+     * @deprecated Use {@link PackageState#isPrivileged}
+     */
+    @Deprecated
+    public static boolean isPrivileged(@NonNull AndroidPackage pkg) {
+        return ((AndroidPackageHidden) pkg).isPrivileged();
+    }
+
+    /**
+     * @deprecated Use {@link PackageState#isOem}
+     */
+    @Deprecated
+    public static boolean isOem(@NonNull AndroidPackage pkg) {
+        return ((AndroidPackageHidden) pkg).isOem();
+    }
+
+    /**
+     * @deprecated Use {@link PackageState#isVendor}
+     */
+    @Deprecated
+    public static boolean isVendor(@NonNull AndroidPackage pkg) {
+        return ((AndroidPackageHidden) pkg).isVendor();
+    }
+
+    /**
+     * @deprecated Use {@link PackageState#isProduct}
+     */
+    @Deprecated
+    public static boolean isProduct(@NonNull AndroidPackage pkg) {
+        return ((AndroidPackageHidden) pkg).isProduct();
+    }
+
+    /**
+     * @deprecated Use {@link PackageState#isOdm}
+     */
+    @Deprecated
+    public static boolean isOdm(@NonNull AndroidPackage pkg) {
+        return ((AndroidPackageHidden) pkg).isOdm();
+    }
+}
diff --git a/services/core/java/com/android/server/pm/parsing/pkg/PackageImpl.java b/core/java/com/android/internal/pm/parsing/pkg/PackageImpl.java
similarity index 98%
rename from services/core/java/com/android/server/pm/parsing/pkg/PackageImpl.java
rename to core/java/com/android/internal/pm/parsing/pkg/PackageImpl.java
index da58d47..f7e1f72 100644
--- a/services/core/java/com/android/server/pm/parsing/pkg/PackageImpl.java
+++ b/core/java/com/android/internal/pm/parsing/pkg/PackageImpl.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.server.pm.parsing.pkg;
+package com.android.internal.pm.parsing.pkg;
 
 import static java.util.Collections.emptyList;
 import static java.util.Collections.emptyMap;
@@ -50,47 +50,44 @@
 
 import com.android.internal.R;
 import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.pm.parsing.pkg.AndroidPackageHidden;
-import com.android.internal.pm.parsing.pkg.AndroidPackageInternal;
-import com.android.internal.pm.parsing.pkg.ParsedPackage;
+import com.android.internal.pm.parsing.AppInfoUtils;
 import com.android.internal.pm.pkg.AndroidPackageSplitImpl;
+import com.android.internal.pm.pkg.SEInfoUtil;
+import com.android.internal.pm.pkg.component.ComponentMutateUtils;
 import com.android.internal.pm.pkg.component.ParsedActivity;
+import com.android.internal.pm.pkg.component.ParsedActivityImpl;
 import com.android.internal.pm.pkg.component.ParsedApexSystemService;
+import com.android.internal.pm.pkg.component.ParsedApexSystemServiceImpl;
 import com.android.internal.pm.pkg.component.ParsedAttribution;
+import com.android.internal.pm.pkg.component.ParsedAttributionImpl;
 import com.android.internal.pm.pkg.component.ParsedComponent;
 import com.android.internal.pm.pkg.component.ParsedInstrumentation;
+import com.android.internal.pm.pkg.component.ParsedInstrumentationImpl;
 import com.android.internal.pm.pkg.component.ParsedIntentInfo;
 import com.android.internal.pm.pkg.component.ParsedMainComponent;
 import com.android.internal.pm.pkg.component.ParsedPermission;
 import com.android.internal.pm.pkg.component.ParsedPermissionGroup;
+import com.android.internal.pm.pkg.component.ParsedPermissionGroupImpl;
+import com.android.internal.pm.pkg.component.ParsedPermissionImpl;
 import com.android.internal.pm.pkg.component.ParsedProcess;
+import com.android.internal.pm.pkg.component.ParsedProcessImpl;
 import com.android.internal.pm.pkg.component.ParsedProvider;
+import com.android.internal.pm.pkg.component.ParsedProviderImpl;
 import com.android.internal.pm.pkg.component.ParsedService;
+import com.android.internal.pm.pkg.component.ParsedServiceImpl;
 import com.android.internal.pm.pkg.component.ParsedUsesPermission;
+import com.android.internal.pm.pkg.component.ParsedUsesPermissionImpl;
 import com.android.internal.pm.pkg.parsing.ParsingPackage;
 import com.android.internal.pm.pkg.parsing.ParsingPackageHidden;
+import com.android.internal.pm.pkg.parsing.ParsingPackageUtils;
+import com.android.internal.pm.pkg.parsing.ParsingUtils;
 import com.android.internal.util.ArrayUtils;
 import com.android.internal.util.CollectionUtils;
 import com.android.internal.util.DataClass;
 import com.android.internal.util.Parcelling;
 import com.android.internal.util.Parcelling.BuiltIn.ForInternedString;
-import com.android.server.pm.parsing.PackageInfoUtils;
 import com.android.server.pm.pkg.AndroidPackage;
 import com.android.server.pm.pkg.AndroidPackageSplit;
-import com.android.server.pm.pkg.SELinuxUtil;
-import com.android.server.pm.pkg.component.ComponentMutateUtils;
-import com.android.server.pm.pkg.component.ParsedActivityImpl;
-import com.android.server.pm.pkg.component.ParsedApexSystemServiceImpl;
-import com.android.server.pm.pkg.component.ParsedAttributionImpl;
-import com.android.server.pm.pkg.component.ParsedInstrumentationImpl;
-import com.android.server.pm.pkg.component.ParsedPermissionGroupImpl;
-import com.android.server.pm.pkg.component.ParsedPermissionImpl;
-import com.android.server.pm.pkg.component.ParsedProcessImpl;
-import com.android.server.pm.pkg.component.ParsedProviderImpl;
-import com.android.server.pm.pkg.component.ParsedServiceImpl;
-import com.android.server.pm.pkg.component.ParsedUsesPermissionImpl;
-import com.android.server.pm.pkg.parsing.ParsingPackageUtils;
-import com.android.server.pm.pkg.parsing.ParsingUtils;
 
 import libcore.util.EmptyArray;
 
@@ -422,8 +419,10 @@
 
     @NonNull
     public static PackageImpl forParsing(@NonNull String packageName, @NonNull String baseCodePath,
-            @NonNull String codePath, @NonNull TypedArray manifestArray, boolean isCoreApp) {
-        return new PackageImpl(packageName, baseCodePath, codePath, manifestArray, isCoreApp);
+            @NonNull String codePath, @NonNull TypedArray manifestArray, boolean isCoreApp,
+            @Nullable ParsingPackageUtils.Callback callback) {
+        return new PackageImpl(
+                packageName, baseCodePath, codePath, manifestArray, isCoreApp, callback);
     }
 
     /**
@@ -453,7 +452,7 @@
     @NonNull
     @VisibleForTesting
     public static ParsingPackage forTesting(String packageName, String baseCodePath) {
-        return new PackageImpl(packageName, baseCodePath, baseCodePath, null, false);
+        return new PackageImpl(packageName, baseCodePath, baseCodePath, null, false, null);
     }
 
     @NonNull
@@ -2694,12 +2693,16 @@
     private String mBaseAppDataCredentialProtectedDirForSystemUser;
     private String mBaseAppDataDeviceProtectedDirForSystemUser;
 
+    ParsingPackageUtils.Callback mCallback;
+
     @VisibleForTesting
     public PackageImpl(@NonNull String packageName, @NonNull String baseApkPath,
-            @NonNull String path, @Nullable TypedArray manifestArray, boolean isCoreApp) {
+            @NonNull String path, @Nullable TypedArray manifestArray, boolean isCoreApp,
+            @Nullable ParsingPackageUtils.Callback callback) {
         this.packageName = TextUtils.safeIntern(packageName);
         this.mBaseApkPath = baseApkPath;
         this.mPath = path;
+        this.mCallback = callback;
 
         if (manifestArray != null) {
             versionCode = manifestArray.getInteger(R.styleable.AndroidManifest_versionCode, 0);
@@ -2750,9 +2753,11 @@
     }
 
     private void assignDerivedFields2() {
-        mBaseAppInfoFlags = PackageInfoUtils.appInfoFlags(this, null);
-        mBaseAppInfoPrivateFlags = PackageInfoUtils.appInfoPrivateFlags(this, null);
-        mBaseAppInfoPrivateFlagsExt = PackageInfoUtils.appInfoPrivateFlagsExt(this, null);
+        mBaseAppInfoFlags = AppInfoUtils.appInfoFlags(this);
+        mBaseAppInfoPrivateFlags = AppInfoUtils.appInfoPrivateFlags(this);
+        mBaseAppInfoPrivateFlagsExt = AppInfoUtils.appInfoPrivateFlagsExt(this,
+                mCallback == null ? false :
+                        mCallback.getHiddenApiWhitelistedApps().contains(this.packageName));
         String baseAppDataDir = Environment.getDataDirectoryPath(getVolumeUuid()) + File.separator;
         String systemUserSuffix = File.separator + UserHandle.USER_SYSTEM + File.separator;
         mBaseAppDataCredentialProtectedDirForSystemUser = TextUtils.safeIntern(
@@ -3087,7 +3092,7 @@
         appInfo.primaryCpuAbi = primaryCpuAbi;
         appInfo.secondaryCpuAbi = secondaryCpuAbi;
         appInfo.secondaryNativeLibraryDir = secondaryNativeLibraryDir;
-        appInfo.seInfoUser = SELinuxUtil.COMPLETE_STR;
+        appInfo.seInfoUser = SEInfoUtil.COMPLETE_STR;
         appInfo.uid = uid;
         return appInfo;
     }
diff --git a/services/core/java/com/android/server/pm/permission/CompatibilityPermissionInfo.java b/core/java/com/android/internal/pm/permission/CompatibilityPermissionInfo.java
similarity index 85%
rename from services/core/java/com/android/server/pm/permission/CompatibilityPermissionInfo.java
rename to core/java/com/android/internal/pm/permission/CompatibilityPermissionInfo.java
index d962505..a670c6d 100644
--- a/services/core/java/com/android/server/pm/permission/CompatibilityPermissionInfo.java
+++ b/core/java/com/android/internal/pm/permission/CompatibilityPermissionInfo.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.server.pm.permission;
+package com.android.internal.pm.permission;
 
 import android.Manifest;
 import android.annotation.NonNull;
@@ -67,7 +67,7 @@
     // CHECKSTYLE:OFF Generated code
     //
     // To regenerate run:
-    // $ codegen $ANDROID_BUILD_TOP/frameworks/base/core/java/android/content/pm/permission/CompatibilityPermissionInfo.java
+    // $ codegen $ANDROID_BUILD_TOP/frameworks/base/core/java/com/android/internal/pm/permission/CompatibilityPermissionInfo.java
     //
     // To exclude the generated code from IntelliJ auto-formatting enable (one-time):
     //   Settings > Editor > Code Style > Formatter Control
@@ -97,10 +97,10 @@
     }
 
     @DataClass.Generated(
-            time = 1627674427184L,
+            time = 1701338392152L,
             codegenVersion = "1.0.23",
-            sourceFile = "frameworks/base/core/java/android/content/pm/permission/CompatibilityPermissionInfo.java",
-            inputSignatures = "private final @android.annotation.NonNull java.lang.String mName\nprivate final  int mSdkVersion\npublic static final  android.content.pm.permission.CompatibilityPermissionInfo[] COMPAT_PERMS\nclass CompatibilityPermissionInfo extends java.lang.Object implements []\n@com.android.internal.util.DataClass(genGetters=true, genBuilder=false)")
+            sourceFile = "frameworks/base/core/java/com/android/internal/pm/permission/CompatibilityPermissionInfo.java",
+            inputSignatures = "private final @android.annotation.NonNull java.lang.String mName\nprivate final  int mSdkVersion\npublic static final  com.android.internal.pm.permission.CompatibilityPermissionInfo[] COMPAT_PERMS\nclass CompatibilityPermissionInfo extends java.lang.Object implements []\n@com.android.internal.util.DataClass(genGetters=true, genBuilder=false)")
     @Deprecated
     private void __metadata() {}
 
diff --git a/core/java/com/android/internal/pm/pkg/SEInfoUtil.java b/core/java/com/android/internal/pm/pkg/SEInfoUtil.java
new file mode 100644
index 0000000..a698882
--- /dev/null
+++ b/core/java/com/android/internal/pm/pkg/SEInfoUtil.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.internal.pm.pkg;
+
+/**
+ * Utility methods that need to be used in application space.
+ * @hide
+ */
+public final class SEInfoUtil {
+
+    /** Append to existing seinfo label for instant apps @hide */
+    public static final String INSTANT_APP_STR = ":ephemeralapp";
+
+    /** Append to existing seinfo when modifications are complete @hide */
+    public static final String COMPLETE_STR = ":complete";
+}
diff --git a/services/core/java/com/android/server/pm/pkg/component/ComponentMutateUtils.java b/core/java/com/android/internal/pm/pkg/component/ComponentMutateUtils.java
similarity index 88%
rename from services/core/java/com/android/server/pm/pkg/component/ComponentMutateUtils.java
rename to core/java/com/android/internal/pm/pkg/component/ComponentMutateUtils.java
index 1964df0..fd5f0f0 100644
--- a/services/core/java/com/android/server/pm/pkg/component/ComponentMutateUtils.java
+++ b/core/java/com/android/internal/pm/pkg/component/ComponentMutateUtils.java
@@ -14,19 +14,11 @@
  * limitations under the License.
  */
 
-package com.android.server.pm.pkg.component;
+package com.android.internal.pm.pkg.component;
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 
-import com.android.internal.pm.pkg.component.ParsedActivity;
-import com.android.internal.pm.pkg.component.ParsedComponent;
-import com.android.internal.pm.pkg.component.ParsedMainComponent;
-import com.android.internal.pm.pkg.component.ParsedPermission;
-import com.android.internal.pm.pkg.component.ParsedPermissionGroup;
-import com.android.internal.pm.pkg.component.ParsedProcess;
-import com.android.internal.pm.pkg.component.ParsedProvider;
-
 /**
  * Contains mutation methods so that code doesn't have to cast to the Impl. Meant to eventually
  * be removed once all post-parsing mutation is moved to parsing.
diff --git a/services/core/java/com/android/server/pm/pkg/component/ComponentParseUtils.java b/core/java/com/android/internal/pm/pkg/component/ComponentParseUtils.java
similarity index 85%
rename from services/core/java/com/android/server/pm/pkg/component/ComponentParseUtils.java
rename to core/java/com/android/internal/pm/pkg/component/ComponentParseUtils.java
index 019ca13..db08005 100644
--- a/services/core/java/com/android/server/pm/pkg/component/ComponentParseUtils.java
+++ b/core/java/com/android/internal/pm/pkg/component/ComponentParseUtils.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.server.pm.pkg.component;
+package com.android.internal.pm.pkg.component;
 
 import android.annotation.AttrRes;
 import android.annotation.NonNull;
@@ -29,14 +29,9 @@
 import android.content.res.XmlResourceParser;
 import android.text.TextUtils;
 
-import com.android.internal.pm.pkg.component.ParsedComponent;
-import com.android.internal.pm.pkg.component.ParsedIntentInfo;
-import com.android.internal.pm.pkg.component.ParsedMainComponent;
 import com.android.internal.pm.pkg.parsing.ParsingPackage;
-import com.android.server.pm.pkg.PackageUserState;
-import com.android.server.pm.pkg.PackageUserStateUtils;
-import com.android.server.pm.pkg.parsing.ParsingPackageUtils;
-import com.android.server.pm.pkg.parsing.ParsingUtils;
+import com.android.internal.pm.pkg.parsing.ParsingPackageUtils;
+import com.android.internal.pm.pkg.parsing.ParsingUtils;
 
 import org.xmlpull.v1.XmlPullParser;
 import org.xmlpull.v1.XmlPullParserException;
@@ -173,16 +168,4 @@
     public static int getIcon(ParsedComponent component) {
         return component.getIcon();
     }
-
-    public static boolean isMatch(PackageUserState state, boolean isSystem,
-            boolean isPackageEnabled, ParsedMainComponent component, long flags) {
-        return PackageUserStateUtils.isMatch(state, isSystem, isPackageEnabled,
-                component.isEnabled(), component.isDirectBootAware(), component.getName(), flags);
-    }
-
-    public static boolean isEnabled(PackageUserState state, boolean isPackageEnabled,
-            ParsedMainComponent parsedComponent, long flags) {
-        return PackageUserStateUtils.isEnabled(state, isPackageEnabled, parsedComponent.isEnabled(),
-                parsedComponent.getName(), flags);
-    }
 }
diff --git a/services/core/java/com/android/server/pm/pkg/component/InstallConstraintsTagParser.java b/core/java/com/android/internal/pm/pkg/component/InstallConstraintsTagParser.java
similarity index 94%
rename from services/core/java/com/android/server/pm/pkg/component/InstallConstraintsTagParser.java
rename to core/java/com/android/internal/pm/pkg/component/InstallConstraintsTagParser.java
index dd54cfc..0b04591 100644
--- a/services/core/java/com/android/server/pm/pkg/component/InstallConstraintsTagParser.java
+++ b/core/java/com/android/internal/pm/pkg/component/InstallConstraintsTagParser.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.server.pm.pkg.component;
+package com.android.internal.pm.pkg.component;
 
 
 import android.content.pm.parsing.result.ParseInput;
@@ -27,7 +27,6 @@
 
 import com.android.internal.R;
 import com.android.internal.pm.pkg.parsing.ParsingPackage;
-import com.android.server.SystemConfig;
 
 import org.xmlpull.v1.XmlPullParser;
 import org.xmlpull.v1.XmlPullParserException;
@@ -48,9 +47,8 @@
      * @hide
      */
     public static ParseResult<ParsingPackage> parseInstallConstraints(
-            ParseInput input, ParsingPackage pkg, Resources res, XmlResourceParser parser)
-            throws XmlPullParserException, IOException {
-        Set<String> allowlist = SystemConfig.getInstance().getInstallConstraintsAllowlist();
+            ParseInput input, ParsingPackage pkg, Resources res, XmlResourceParser parser,
+            Set<String> allowlist) throws XmlPullParserException, IOException {
         if (!allowlist.contains(pkg.getPackageName())) {
             return input.skip("install-constraints cannot be used by this package");
         }
diff --git a/services/core/java/com/android/server/pm/pkg/component/ParsedActivityImpl.java b/core/java/com/android/internal/pm/pkg/component/ParsedActivityImpl.java
similarity index 92%
rename from services/core/java/com/android/server/pm/pkg/component/ParsedActivityImpl.java
rename to core/java/com/android/internal/pm/pkg/component/ParsedActivityImpl.java
index f027901..2f977ee 100644
--- a/services/core/java/com/android/server/pm/pkg/component/ParsedActivityImpl.java
+++ b/core/java/com/android/internal/pm/pkg/component/ParsedActivityImpl.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.server.pm.pkg.component;
+package com.android.internal.pm.pkg.component;
 
 import static android.content.pm.ActivityInfo.RESIZE_MODE_FORCE_RESIZEABLE;
 import static android.content.pm.ActivityInfo.RESIZE_MODE_RESIZEABLE;
@@ -22,8 +22,8 @@
 import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED;
 import static android.view.WindowManager.LayoutParams.ROTATION_ANIMATION_UNSPECIFIED;
 
-import static com.android.server.pm.parsing.pkg.PackageImpl.sForInternedString;
-import static com.android.server.pm.parsing.pkg.PackageImpl.sForStringSet;
+import static com.android.internal.pm.parsing.pkg.PackageImpl.sForInternedString;
+import static com.android.internal.pm.parsing.pkg.PackageImpl.sForStringSet;
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
@@ -36,10 +36,9 @@
 import android.text.TextUtils;
 import android.util.ArraySet;
 
-import com.android.internal.pm.pkg.component.ParsedActivity;
 import com.android.internal.util.DataClass;
 import com.android.internal.util.Parcelling.BuiltIn.ForInternedString;
-import com.android.server.pm.pkg.parsing.ParsingUtils;
+import com.android.internal.pm.pkg.parsing.ParsingUtils;
 
 import java.util.Collections;
 import java.util.Locale;
@@ -380,7 +379,7 @@
     // CHECKSTYLE:OFF Generated code
     //
     // To regenerate run:
-    // $ codegen $ANDROID_BUILD_TOP/frameworks/base/services/core/java/com/android/server/pm/pkg/component/ParsedActivityImpl.java
+    // $ codegen $ANDROID_BUILD_TOP/frameworks/base/core/java/com/android/internal/pm/pkg/component/ParsedActivityImpl.java
     //
     // To exclude the generated code from IntelliJ auto-formatting enable (one-time):
     //   Settings > Editor > Code Style > Formatter Control
@@ -696,10 +695,10 @@
     }
 
     @DataClass.Generated(
-            time = 1669437519576L,
+            time = 1701338377709L,
             codegenVersion = "1.0.23",
-            sourceFile = "frameworks/base/services/core/java/com/android/server/pm/pkg/component/ParsedActivityImpl.java",
-            inputSignatures = "private  int theme\nprivate  int uiOptions\nprivate @android.annotation.Nullable @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForInternedString.class) java.lang.String targetActivity\nprivate @android.annotation.Nullable @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForInternedString.class) java.lang.String parentActivityName\nprivate @android.annotation.Nullable java.lang.String taskAffinity\nprivate  int privateFlags\nprivate @android.annotation.Nullable @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForInternedString.class) java.lang.String permission\nprivate @android.annotation.Nullable java.util.Set<java.lang.String> mKnownActivityEmbeddingCerts\nprivate  int launchMode\nprivate  int documentLaunchMode\nprivate  int maxRecents\nprivate  int configChanges\nprivate  int softInputMode\nprivate  int persistableMode\nprivate  int lockTaskLaunchMode\nprivate  int screenOrientation\nprivate  int resizeMode\nprivate  float maxAspectRatio\nprivate  float minAspectRatio\nprivate  boolean supportsSizeChanges\nprivate @android.annotation.Nullable java.lang.String requestedVrComponent\nprivate  int rotationAnimation\nprivate  int colorMode\nprivate @android.annotation.Nullable android.content.pm.ActivityInfo.WindowLayout windowLayout\nprivate @android.annotation.Nullable java.lang.String mRequiredDisplayCategory\npublic static final @android.annotation.NonNull android.os.Parcelable.Creator<com.android.server.pm.pkg.component.ParsedActivityImpl> CREATOR\nstatic @android.annotation.NonNull com.android.server.pm.pkg.component.ParsedActivityImpl makeAppDetailsActivity(java.lang.String,java.lang.String,int,java.lang.String,boolean)\nstatic @android.annotation.NonNull com.android.server.pm.pkg.component.ParsedActivityImpl makeAlias(java.lang.String,com.android.internal.pm.pkg.component.ParsedActivity)\npublic  com.android.server.pm.pkg.component.ParsedActivityImpl setMaxAspectRatio(int,float)\npublic  com.android.server.pm.pkg.component.ParsedActivityImpl setMinAspectRatio(int,float)\npublic  com.android.server.pm.pkg.component.ParsedActivityImpl setTargetActivity(java.lang.String)\npublic  com.android.server.pm.pkg.component.ParsedActivityImpl setPermission(java.lang.String)\npublic @android.annotation.NonNull @java.lang.Override java.util.Set<java.lang.String> getKnownActivityEmbeddingCerts()\npublic  void setKnownActivityEmbeddingCerts(java.util.Set<java.lang.String>)\npublic  java.lang.String toString()\npublic @java.lang.Override int describeContents()\npublic @java.lang.Override void writeToParcel(android.os.Parcel,int)\nclass ParsedActivityImpl extends com.android.server.pm.pkg.component.ParsedMainComponentImpl implements [com.android.internal.pm.pkg.component.ParsedActivity, android.os.Parcelable]\n@com.android.internal.util.DataClass(genGetters=true, genSetters=true, genBuilder=false, genParcelable=false)")
+            sourceFile = "frameworks/base/core/java/com/android/internal/pm/pkg/component/ParsedActivityImpl.java",
+            inputSignatures = "private  int theme\nprivate  int uiOptions\nprivate @android.annotation.Nullable @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForInternedString.class) java.lang.String targetActivity\nprivate @android.annotation.Nullable @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForInternedString.class) java.lang.String parentActivityName\nprivate @android.annotation.Nullable java.lang.String taskAffinity\nprivate  int privateFlags\nprivate @android.annotation.Nullable @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForInternedString.class) java.lang.String permission\nprivate @android.annotation.Nullable java.util.Set<java.lang.String> mKnownActivityEmbeddingCerts\nprivate  int launchMode\nprivate  int documentLaunchMode\nprivate  int maxRecents\nprivate  int configChanges\nprivate  int softInputMode\nprivate  int persistableMode\nprivate  int lockTaskLaunchMode\nprivate  int screenOrientation\nprivate  int resizeMode\nprivate  float maxAspectRatio\nprivate  float minAspectRatio\nprivate  boolean supportsSizeChanges\nprivate @android.annotation.Nullable java.lang.String requestedVrComponent\nprivate  int rotationAnimation\nprivate  int colorMode\nprivate @android.annotation.Nullable android.content.pm.ActivityInfo.WindowLayout windowLayout\nprivate @android.annotation.Nullable java.lang.String mRequiredDisplayCategory\npublic static final @android.annotation.NonNull android.os.Parcelable.Creator<com.android.internal.pm.pkg.component.ParsedActivityImpl> CREATOR\npublic static @android.annotation.NonNull com.android.internal.pm.pkg.component.ParsedActivityImpl makeAppDetailsActivity(java.lang.String,java.lang.String,int,java.lang.String,boolean)\nstatic @android.annotation.NonNull com.android.internal.pm.pkg.component.ParsedActivityImpl makeAlias(java.lang.String,com.android.internal.pm.pkg.component.ParsedActivity)\npublic  com.android.internal.pm.pkg.component.ParsedActivityImpl setMaxAspectRatio(int,float)\npublic  com.android.internal.pm.pkg.component.ParsedActivityImpl setMinAspectRatio(int,float)\npublic  com.android.internal.pm.pkg.component.ParsedActivityImpl setTargetActivity(java.lang.String)\npublic  com.android.internal.pm.pkg.component.ParsedActivityImpl setPermission(java.lang.String)\npublic @android.annotation.NonNull @java.lang.Override java.util.Set<java.lang.String> getKnownActivityEmbeddingCerts()\npublic  void setKnownActivityEmbeddingCerts(java.util.Set<java.lang.String>)\npublic  java.lang.String toString()\npublic @java.lang.Override int describeContents()\npublic @java.lang.Override void writeToParcel(android.os.Parcel,int)\nclass ParsedActivityImpl extends com.android.internal.pm.pkg.component.ParsedMainComponentImpl implements [com.android.internal.pm.pkg.component.ParsedActivity, android.os.Parcelable]\n@com.android.internal.util.DataClass(genGetters=true, genSetters=true, genBuilder=false, genParcelable=false)")
     @Deprecated
     private void __metadata() {}
 
diff --git a/services/core/java/com/android/server/pm/pkg/component/ParsedActivityUtils.java b/core/java/com/android/internal/pm/pkg/component/ParsedActivityUtils.java
similarity index 98%
rename from services/core/java/com/android/server/pm/pkg/component/ParsedActivityUtils.java
rename to core/java/com/android/internal/pm/pkg/component/ParsedActivityUtils.java
index 64985bd..c3f7dab 100644
--- a/services/core/java/com/android/server/pm/pkg/component/ParsedActivityUtils.java
+++ b/core/java/com/android/internal/pm/pkg/component/ParsedActivityUtils.java
@@ -14,14 +14,14 @@
  * limitations under the License.
  */
 
-package com.android.server.pm.pkg.component;
+package com.android.internal.pm.pkg.component;
 
 import static android.content.pm.ActivityInfo.LAUNCH_SINGLE_INSTANCE_PER_TASK;
 import static android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED;
 
-import static com.android.server.pm.pkg.component.ComponentParseUtils.flag;
-import static com.android.server.pm.pkg.parsing.ParsingUtils.NOT_SET;
-import static com.android.server.pm.pkg.parsing.ParsingUtils.parseKnownActivityEmbeddingCerts;
+import static com.android.internal.pm.pkg.component.ComponentParseUtils.flag;
+import static com.android.internal.pm.pkg.parsing.ParsingUtils.NOT_SET;
+import static com.android.internal.pm.pkg.parsing.ParsingUtils.parseKnownActivityEmbeddingCerts;
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
@@ -48,11 +48,10 @@
 
 import com.android.internal.R;
 import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.pm.pkg.component.ParsedActivity;
 import com.android.internal.pm.pkg.parsing.ParsingPackage;
+import com.android.internal.pm.pkg.parsing.ParsingPackageUtils;
+import com.android.internal.pm.pkg.parsing.ParsingUtils;
 import com.android.internal.util.ArrayUtils;
-import com.android.server.pm.pkg.parsing.ParsingPackageUtils;
-import com.android.server.pm.pkg.parsing.ParsingUtils;
 
 import org.xmlpull.v1.XmlPullParser;
 import org.xmlpull.v1.XmlPullParserException;
diff --git a/services/core/java/com/android/server/pm/pkg/component/ParsedApexSystemServiceImpl.java b/core/java/com/android/internal/pm/pkg/component/ParsedApexSystemServiceImpl.java
similarity index 95%
rename from services/core/java/com/android/server/pm/pkg/component/ParsedApexSystemServiceImpl.java
rename to core/java/com/android/internal/pm/pkg/component/ParsedApexSystemServiceImpl.java
index cfed19a..27f7eee 100644
--- a/services/core/java/com/android/server/pm/pkg/component/ParsedApexSystemServiceImpl.java
+++ b/core/java/com/android/internal/pm/pkg/component/ParsedApexSystemServiceImpl.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.server.pm.pkg.component;
+package com.android.internal.pm.pkg.component;
 
 
 import android.annotation.NonNull;
@@ -22,7 +22,6 @@
 import android.os.Parcelable;
 
 import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.pm.pkg.component.ParsedApexSystemService;
 import com.android.internal.util.DataClass;
 import com.android.internal.util.Parcelling;
 
@@ -60,7 +59,7 @@
     // CHECKSTYLE:OFF Generated code
     //
     // To regenerate run:
-    // $ codegen $ANDROID_BUILD_TOP/frameworks/base/services/core/java/com/android/server/pm/pkg/component/ParsedApexSystemServiceImpl.java
+    // $ codegen $ANDROID_BUILD_TOP/frameworks/base/core/java/com/android/internal/pm/pkg/component/ParsedApexSystemServiceImpl.java
     //
     // To exclude the generated code from IntelliJ auto-formatting enable (one-time):
     //   Settings > Editor > Code Style > Formatter Control
@@ -248,9 +247,9 @@
     };
 
     @DataClass.Generated(
-            time = 1643723578605L,
+            time = 1701710844088L,
             codegenVersion = "1.0.23",
-            sourceFile = "frameworks/base/services/core/java/com/android/server/pm/pkg/component/ParsedApexSystemServiceImpl.java",
+            sourceFile = "frameworks/base/core/java/com/android/internal/pm/pkg/component/ParsedApexSystemServiceImpl.java",
             inputSignatures = "private @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForInternedString.class) @android.annotation.NonNull java.lang.String name\nprivate @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForInternedString.class) @android.annotation.Nullable java.lang.String jarPath\nprivate @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForInternedString.class) @android.annotation.Nullable java.lang.String minSdkVersion\nprivate @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForInternedString.class) @android.annotation.Nullable java.lang.String maxSdkVersion\nprivate  int initOrder\nclass ParsedApexSystemServiceImpl extends java.lang.Object implements [com.android.internal.pm.pkg.component.ParsedApexSystemService, android.os.Parcelable]\n@com.android.internal.util.DataClass(genGetters=true, genAidl=false, genSetters=true, genParcelable=true)")
     @Deprecated
     private void __metadata() {}
diff --git a/services/core/java/com/android/server/pm/pkg/component/ParsedApexSystemServiceUtils.java b/core/java/com/android/internal/pm/pkg/component/ParsedApexSystemServiceUtils.java
similarity index 95%
rename from services/core/java/com/android/server/pm/pkg/component/ParsedApexSystemServiceUtils.java
rename to core/java/com/android/internal/pm/pkg/component/ParsedApexSystemServiceUtils.java
index d3fb29b..c69213f 100644
--- a/services/core/java/com/android/server/pm/pkg/component/ParsedApexSystemServiceUtils.java
+++ b/core/java/com/android/internal/pm/pkg/component/ParsedApexSystemServiceUtils.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.server.pm.pkg.component;
+package com.android.internal.pm.pkg.component;
 
 import android.R;
 import android.annotation.NonNull;
@@ -25,8 +25,6 @@
 import android.content.res.XmlResourceParser;
 import android.text.TextUtils;
 
-import com.android.internal.pm.pkg.component.ParsedApexSystemService;
-
 import org.xmlpull.v1.XmlPullParserException;
 
 import java.io.IOException;
diff --git a/services/core/java/com/android/server/pm/pkg/component/ParsedAttributionImpl.java b/core/java/com/android/internal/pm/pkg/component/ParsedAttributionImpl.java
similarity index 91%
rename from services/core/java/com/android/server/pm/pkg/component/ParsedAttributionImpl.java
rename to core/java/com/android/internal/pm/pkg/component/ParsedAttributionImpl.java
index 62b9947..e3bfb38 100644
--- a/services/core/java/com/android/server/pm/pkg/component/ParsedAttributionImpl.java
+++ b/core/java/com/android/internal/pm/pkg/component/ParsedAttributionImpl.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.server.pm.pkg.component;
+package com.android.internal.pm.pkg.component;
 
 import android.annotation.NonNull;
 import android.annotation.StringRes;
@@ -22,7 +22,6 @@
 import android.os.Parcelable;
 
 import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.pm.pkg.component.ParsedAttribution;
 import com.android.internal.util.DataClass;
 
 import java.util.ArrayList;
@@ -60,7 +59,7 @@
     // CHECKSTYLE:OFF Generated code
     //
     // To regenerate run:
-    // $ codegen $ANDROID_BUILD_TOP/frameworks/base/core/java/android/content/pm/parsing/component/ParsedAttributionImpl.java
+    // $ codegen $ANDROID_BUILD_TOP/frameworks/base/core/java/com/android/internal/pm/pkg/component/ParsedAttributionImpl.java
     //
     // To exclude the generated code from IntelliJ auto-formatting enable (one-time):
     //   Settings > Editor > Code Style > Formatter Control
@@ -207,10 +206,10 @@
     };
 
     @DataClass.Generated(
-            time = 1641431950829L,
+            time = 1701338881658L,
             codegenVersion = "1.0.23",
-            sourceFile = "frameworks/base/core/java/android/content/pm/parsing/component/ParsedAttributionImpl.java",
-            inputSignatures = "static final  int MAX_NUM_ATTRIBUTIONS\nprivate @android.annotation.NonNull java.lang.String tag\nprivate @android.annotation.StringRes int label\nprivate @android.annotation.NonNull java.util.List<java.lang.String> inheritFrom\nclass ParsedAttributionImpl extends java.lang.Object implements [android.content.pm.parsing.component.ParsedAttribution, android.os.Parcelable]\n@com.android.internal.util.DataClass(genAidl=false, genSetters=true, genBuilder=false, genParcelable=true)")
+            sourceFile = "frameworks/base/core/java/com/android/internal/pm/pkg/component/ParsedAttributionImpl.java",
+            inputSignatures = "static final  int MAX_NUM_ATTRIBUTIONS\nprivate @android.annotation.NonNull java.lang.String tag\nprivate @android.annotation.StringRes int label\nprivate @android.annotation.NonNull java.util.List<java.lang.String> inheritFrom\nclass ParsedAttributionImpl extends java.lang.Object implements [com.android.internal.pm.pkg.component.ParsedAttribution, android.os.Parcelable]\n@com.android.internal.util.DataClass(genAidl=false, genSetters=true, genBuilder=false, genParcelable=true)")
     @Deprecated
     private void __metadata() {}
 
diff --git a/services/core/java/com/android/server/pm/pkg/component/ParsedAttributionUtils.java b/core/java/com/android/internal/pm/pkg/component/ParsedAttributionUtils.java
similarity index 97%
rename from services/core/java/com/android/server/pm/pkg/component/ParsedAttributionUtils.java
rename to core/java/com/android/internal/pm/pkg/component/ParsedAttributionUtils.java
index 411220a..ee5c320 100644
--- a/services/core/java/com/android/server/pm/pkg/component/ParsedAttributionUtils.java
+++ b/core/java/com/android/internal/pm/pkg/component/ParsedAttributionUtils.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.server.pm.pkg.component;
+package com.android.internal.pm.pkg.component;
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
@@ -26,7 +26,6 @@
 import android.util.ArraySet;
 
 import com.android.internal.R;
-import com.android.internal.pm.pkg.component.ParsedAttribution;
 
 import org.xmlpull.v1.XmlPullParser;
 import org.xmlpull.v1.XmlPullParserException;
diff --git a/services/core/java/com/android/server/pm/pkg/component/ParsedComponentImpl.java b/core/java/com/android/internal/pm/pkg/component/ParsedComponentImpl.java
similarity index 82%
rename from services/core/java/com/android/server/pm/pkg/component/ParsedComponentImpl.java
rename to core/java/com/android/internal/pm/pkg/component/ParsedComponentImpl.java
index 512e5c7..7ee22f3 100644
--- a/services/core/java/com/android/server/pm/pkg/component/ParsedComponentImpl.java
+++ b/core/java/com/android/internal/pm/pkg/component/ParsedComponentImpl.java
@@ -14,9 +14,9 @@
  * limitations under the License.
  */
 
-package com.android.server.pm.pkg.component;
+package com.android.internal.pm.pkg.component;
 
-import static com.android.server.pm.parsing.pkg.PackageImpl.sForInternedString;
+import static com.android.internal.pm.parsing.pkg.PackageImpl.sForInternedString;
 
 import static java.util.Collections.emptyMap;
 
@@ -32,12 +32,10 @@
 import android.util.ArrayMap;
 
 import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.pm.pkg.component.ParsedComponent;
-import com.android.internal.pm.pkg.component.ParsedIntentInfo;
+import com.android.internal.pm.pkg.parsing.ParsingUtils;
 import com.android.internal.util.CollectionUtils;
 import com.android.internal.util.DataClass;
 import com.android.internal.util.Parcelling.BuiltIn.ForInternedString;
-import com.android.server.pm.pkg.parsing.ParsingUtils;
 
 import java.util.ArrayList;
 import java.util.Collections;
@@ -200,7 +198,7 @@
     // CHECKSTYLE:OFF Generated code
     //
     // To regenerate run:
-    // $ codegen $ANDROID_BUILD_TOP/frameworks/base/core/java/android/content/pm/parsing/component/ParsedComponentImpl.java
+    // $ codegen $ANDROID_BUILD_TOP/frameworks/base/core/java/com/android/internal/pm/pkg/component/ParsedComponentImpl.java
     //
     // To exclude the generated code from IntelliJ auto-formatting enable (one-time):
     //   Settings > Editor > Code Style > Formatter Control
@@ -306,10 +304,10 @@
     }
 
     @DataClass.Generated(
-            time = 1641414207885L,
+            time = 1701445673589L,
             codegenVersion = "1.0.23",
-            sourceFile = "frameworks/base/core/java/android/content/pm/parsing/component/ParsedComponentImpl.java",
-            inputSignatures = "private @android.annotation.NonNull @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForInternedString.class) java.lang.String name\nprivate  int icon\nprivate  int labelRes\nprivate @android.annotation.Nullable java.lang.CharSequence nonLocalizedLabel\nprivate  int logo\nprivate  int banner\nprivate  int descriptionRes\nprivate  int flags\nprivate @android.annotation.NonNull @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForInternedString.class) java.lang.String packageName\nprivate @android.annotation.NonNull @com.android.internal.util.DataClass.PluralOf(\"intent\") java.util.List<android.content.pm.parsing.component.ParsedIntentInfoImpl> intents\nprivate @android.annotation.Nullable android.content.ComponentName componentName\nprivate @android.annotation.Nullable android.os.Bundle metaData\nprivate @android.annotation.NonNull java.util.Map<java.lang.String,android.content.pm.PackageManager.Property> mProperties\n  void addIntent(android.content.pm.parsing.component.ParsedIntentInfoImpl)\n  void addProperty(android.content.pm.PackageManager.Property)\npublic  android.content.pm.parsing.component.ParsedComponentImpl setName(java.lang.String)\npublic @android.annotation.CallSuper void setPackageName(java.lang.String)\npublic @java.lang.Override @android.annotation.NonNull android.content.ComponentName getComponentName()\npublic @android.annotation.NonNull @java.lang.Override android.os.Bundle getMetaData()\npublic @android.annotation.NonNull @java.lang.Override java.util.List<android.content.pm.parsing.component.ParsedIntentInfo> getIntents()\npublic @java.lang.Override int describeContents()\npublic @java.lang.Override void writeToParcel(android.os.Parcel,int)\nclass ParsedComponentImpl extends java.lang.Object implements [android.content.pm.parsing.component.ParsedComponent, android.os.Parcelable]\n@com.android.internal.util.DataClass(genGetters=true, genSetters=true, genConstructor=false, genBuilder=false, genParcelable=false)")
+            sourceFile = "frameworks/base/core/java/com/android/internal/pm/pkg/component/ParsedComponentImpl.java",
+            inputSignatures = "private @android.annotation.NonNull @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForInternedString.class) java.lang.String name\nprivate  int icon\nprivate  int labelRes\nprivate @android.annotation.Nullable java.lang.CharSequence nonLocalizedLabel\nprivate  int logo\nprivate  int banner\nprivate  int descriptionRes\nprivate  int flags\nprivate @android.annotation.NonNull @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForInternedString.class) java.lang.String packageName\nprivate @android.annotation.NonNull @com.android.internal.util.DataClass.PluralOf(\"intent\") java.util.List<com.android.internal.pm.pkg.component.ParsedIntentInfoImpl> intents\nprivate @android.annotation.Nullable android.content.ComponentName componentName\nprivate @android.annotation.Nullable android.os.Bundle metaData\nprivate @android.annotation.NonNull java.util.Map<java.lang.String,android.content.pm.PackageManager.Property> mProperties\npublic  void addIntent(com.android.internal.pm.pkg.component.ParsedIntentInfoImpl)\npublic  void addProperty(android.content.pm.PackageManager.Property)\npublic  com.android.internal.pm.pkg.component.ParsedComponentImpl setName(java.lang.String)\npublic @android.annotation.CallSuper void setPackageName(java.lang.String)\npublic @java.lang.Override @android.annotation.NonNull android.content.ComponentName getComponentName()\npublic @android.annotation.NonNull @java.lang.Override android.os.Bundle getMetaData()\npublic @android.annotation.NonNull @java.lang.Override java.util.List<com.android.internal.pm.pkg.component.ParsedIntentInfo> getIntents()\npublic @java.lang.Override int describeContents()\npublic @java.lang.Override void writeToParcel(android.os.Parcel,int)\nclass ParsedComponentImpl extends java.lang.Object implements [com.android.internal.pm.pkg.component.ParsedComponent, android.os.Parcelable]\n@com.android.internal.util.DataClass(genGetters=true, genSetters=true, genConstructor=false, genBuilder=false, genParcelable=false)")
     @Deprecated
     private void __metadata() {}
 
diff --git a/services/core/java/com/android/server/pm/pkg/component/ParsedComponentUtils.java b/core/java/com/android/internal/pm/pkg/component/ParsedComponentUtils.java
similarity index 94%
rename from services/core/java/com/android/server/pm/pkg/component/ParsedComponentUtils.java
rename to core/java/com/android/internal/pm/pkg/component/ParsedComponentUtils.java
index 9322cf0..9e2548b 100644
--- a/services/core/java/com/android/server/pm/pkg/component/ParsedComponentUtils.java
+++ b/core/java/com/android/internal/pm/pkg/component/ParsedComponentUtils.java
@@ -14,9 +14,9 @@
  * limitations under the License.
  */
 
-package com.android.server.pm.pkg.component;
+package com.android.internal.pm.pkg.component;
 
-import static com.android.server.pm.pkg.parsing.ParsingUtils.NOT_SET;
+import static com.android.internal.pm.pkg.parsing.ParsingUtils.NOT_SET;
 
 import android.annotation.NonNull;
 import android.content.pm.PackageManager;
@@ -32,8 +32,8 @@
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.pm.pkg.parsing.ParsingPackage;
-import com.android.server.pm.pkg.parsing.ParsingPackageUtils;
-import com.android.server.pm.pkg.parsing.ParsingUtils;
+import com.android.internal.pm.pkg.parsing.ParsingPackageUtils;
+import com.android.internal.pm.pkg.parsing.ParsingUtils;
 
 /** @hide */
 class ParsedComponentUtils {
diff --git a/services/core/java/com/android/server/pm/pkg/component/ParsedInstrumentationImpl.java b/core/java/com/android/internal/pm/pkg/component/ParsedInstrumentationImpl.java
similarity index 82%
rename from services/core/java/com/android/server/pm/pkg/component/ParsedInstrumentationImpl.java
rename to core/java/com/android/internal/pm/pkg/component/ParsedInstrumentationImpl.java
index 7bfad14..07322e9 100644
--- a/services/core/java/com/android/server/pm/pkg/component/ParsedInstrumentationImpl.java
+++ b/core/java/com/android/internal/pm/pkg/component/ParsedInstrumentationImpl.java
@@ -14,9 +14,9 @@
  * limitations under the License.
  */
 
-package com.android.server.pm.pkg.component;
+package com.android.internal.pm.pkg.component;
 
-import static com.android.server.pm.parsing.pkg.PackageImpl.sForInternedString;
+import static com.android.internal.pm.parsing.pkg.PackageImpl.sForInternedString;
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
@@ -26,7 +26,6 @@
 import android.text.TextUtils;
 
 import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.pm.pkg.component.ParsedInstrumentation;
 import com.android.internal.util.DataClass;
 import com.android.internal.util.Parcelling.BuiltIn.ForInternedString;
 
@@ -112,7 +111,7 @@
     // CHECKSTYLE:OFF Generated code
     //
     // To regenerate run:
-    // $ codegen $ANDROID_BUILD_TOP/frameworks/base/core/java/android/content/pm/parsing/component/ParsedInstrumentationImpl.java
+    // $ codegen $ANDROID_BUILD_TOP/frameworks/base/core/java/com/android/internal/pm/pkg/component/ParsedInstrumentationImpl.java
     //
     // To exclude the generated code from IntelliJ auto-formatting enable (one-time):
     //   Settings > Editor > Code Style > Formatter Control
@@ -166,10 +165,10 @@
     }
 
     @DataClass.Generated(
-            time = 1641431951575L,
+            time = 1701445763455L,
             codegenVersion = "1.0.23",
-            sourceFile = "frameworks/base/core/java/android/content/pm/parsing/component/ParsedInstrumentationImpl.java",
-            inputSignatures = "private @android.annotation.Nullable @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForInternedString.class) java.lang.String targetPackage\nprivate @android.annotation.Nullable @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForInternedString.class) java.lang.String targetProcesses\nprivate  boolean handleProfiling\nprivate  boolean functionalTest\npublic static final @android.annotation.NonNull android.os.Parcelable.Creator<android.content.pm.parsing.component.ParsedInstrumentationImpl> CREATOR\npublic  android.content.pm.parsing.component.ParsedInstrumentationImpl setTargetPackage(java.lang.String)\npublic  android.content.pm.parsing.component.ParsedInstrumentationImpl setTargetProcesses(java.lang.String)\npublic  java.lang.String toString()\npublic @java.lang.Override int describeContents()\npublic @java.lang.Override void writeToParcel(android.os.Parcel,int)\nclass ParsedInstrumentationImpl extends android.content.pm.parsing.component.ParsedComponentImpl implements [android.content.pm.parsing.component.ParsedInstrumentation, android.os.Parcelable]\n@com.android.internal.util.DataClass(genGetters=true, genSetters=true, genBuilder=false, genParcelable=false)")
+            sourceFile = "frameworks/base/core/java/com/android/internal/pm/pkg/component/ParsedInstrumentationImpl.java",
+            inputSignatures = "private @android.annotation.Nullable @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForInternedString.class) java.lang.String targetPackage\nprivate @android.annotation.Nullable @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForInternedString.class) java.lang.String targetProcesses\nprivate  boolean handleProfiling\nprivate  boolean functionalTest\npublic static final @android.annotation.NonNull android.os.Parcelable.Creator<com.android.internal.pm.pkg.component.ParsedInstrumentationImpl> CREATOR\npublic  com.android.internal.pm.pkg.component.ParsedInstrumentationImpl setTargetPackage(java.lang.String)\npublic  com.android.internal.pm.pkg.component.ParsedInstrumentationImpl setTargetProcesses(java.lang.String)\npublic  java.lang.String toString()\npublic @java.lang.Override int describeContents()\npublic @java.lang.Override void writeToParcel(android.os.Parcel,int)\nclass ParsedInstrumentationImpl extends com.android.internal.pm.pkg.component.ParsedComponentImpl implements [com.android.internal.pm.pkg.component.ParsedInstrumentation, android.os.Parcelable]\n@com.android.internal.util.DataClass(genGetters=true, genSetters=true, genBuilder=false, genParcelable=false)")
     @Deprecated
     private void __metadata() {}
 
diff --git a/services/core/java/com/android/server/pm/pkg/component/ParsedInstrumentationUtils.java b/core/java/com/android/internal/pm/pkg/component/ParsedInstrumentationUtils.java
similarity index 94%
rename from services/core/java/com/android/server/pm/pkg/component/ParsedInstrumentationUtils.java
rename to core/java/com/android/internal/pm/pkg/component/ParsedInstrumentationUtils.java
index a711694..661c8b4 100644
--- a/services/core/java/com/android/server/pm/pkg/component/ParsedInstrumentationUtils.java
+++ b/core/java/com/android/internal/pm/pkg/component/ParsedInstrumentationUtils.java
@@ -14,9 +14,9 @@
  * limitations under the License.
  */
 
-package com.android.server.pm.pkg.component;
+package com.android.internal.pm.pkg.component;
 
-import static com.android.server.pm.pkg.parsing.ParsingUtils.NOT_SET;
+import static com.android.internal.pm.pkg.parsing.ParsingUtils.NOT_SET;
 
 import android.annotation.NonNull;
 import android.content.pm.parsing.result.ParseInput;
@@ -26,7 +26,6 @@
 import android.content.res.XmlResourceParser;
 
 import com.android.internal.R;
-import com.android.internal.pm.pkg.component.ParsedInstrumentation;
 import com.android.internal.pm.pkg.parsing.ParsingPackage;
 
 import org.xmlpull.v1.XmlPullParserException;
diff --git a/services/core/java/com/android/server/pm/pkg/component/ParsedIntentInfoImpl.java b/core/java/com/android/internal/pm/pkg/component/ParsedIntentInfoImpl.java
similarity index 89%
rename from services/core/java/com/android/server/pm/pkg/component/ParsedIntentInfoImpl.java
rename to core/java/com/android/internal/pm/pkg/component/ParsedIntentInfoImpl.java
index ab94043..adb49e9 100644
--- a/services/core/java/com/android/server/pm/pkg/component/ParsedIntentInfoImpl.java
+++ b/core/java/com/android/internal/pm/pkg/component/ParsedIntentInfoImpl.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.server.pm.pkg.component;
+package com.android.internal.pm.pkg.component;
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
@@ -23,7 +23,6 @@
 import android.os.Parcelable;
 
 import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.pm.pkg.component.ParsedIntentInfo;
 import com.android.internal.util.DataClass;
 
 /**
@@ -54,7 +53,7 @@
     // CHECKSTYLE:OFF Generated code
     //
     // To regenerate run:
-    // $ codegen $ANDROID_BUILD_TOP/frameworks/base/core/java/android/content/pm/parsing/component/ParsedIntentInfoImpl.java
+    // $ codegen $ANDROID_BUILD_TOP/frameworks/base/core/java/com/android/internal/pm/pkg/component/ParsedIntentInfoImpl.java
     //
     // To exclude the generated code from IntelliJ auto-formatting enable (one-time):
     //   Settings > Editor > Code Style > Formatter Control
@@ -170,10 +169,10 @@
     };
 
     @DataClass.Generated(
-            time = 1641431952314L,
+            time = 1701445800363L,
             codegenVersion = "1.0.23",
-            sourceFile = "frameworks/base/core/java/android/content/pm/parsing/component/ParsedIntentInfoImpl.java",
-            inputSignatures = "private  boolean mHasDefault\nprivate  int mLabelRes\nprivate @android.annotation.Nullable java.lang.CharSequence mNonLocalizedLabel\nprivate  int mIcon\nprivate @android.annotation.NonNull android.content.IntentFilter mIntentFilter\nclass ParsedIntentInfoImpl extends java.lang.Object implements [android.content.pm.parsing.component.ParsedIntentInfo, android.os.Parcelable]\n@com.android.internal.util.DataClass(genGetters=true, genSetters=true, genParcelable=true, genAidl=false, genBuilder=false, genConstructor=false)")
+            sourceFile = "frameworks/base/core/java/com/android/internal/pm/pkg/component/ParsedIntentInfoImpl.java",
+            inputSignatures = "private  boolean mHasDefault\nprivate  int mLabelRes\nprivate @android.annotation.Nullable java.lang.CharSequence mNonLocalizedLabel\nprivate  int mIcon\nprivate @android.annotation.NonNull android.content.IntentFilter mIntentFilter\nclass ParsedIntentInfoImpl extends java.lang.Object implements [com.android.internal.pm.pkg.component.ParsedIntentInfo, android.os.Parcelable]\n@com.android.internal.util.DataClass(genGetters=true, genSetters=true, genParcelable=true, genAidl=false, genBuilder=false, genConstructor=false)")
     @Deprecated
     private void __metadata() {}
 
diff --git a/services/core/java/com/android/server/pm/pkg/component/ParsedIntentInfoUtils.java b/core/java/com/android/internal/pm/pkg/component/ParsedIntentInfoUtils.java
similarity index 96%
rename from services/core/java/com/android/server/pm/pkg/component/ParsedIntentInfoUtils.java
rename to core/java/com/android/internal/pm/pkg/component/ParsedIntentInfoUtils.java
index e5e214d..c6683cf 100644
--- a/services/core/java/com/android/server/pm/pkg/component/ParsedIntentInfoUtils.java
+++ b/core/java/com/android/internal/pm/pkg/component/ParsedIntentInfoUtils.java
@@ -14,9 +14,9 @@
  * limitations under the License.
  */
 
-package com.android.server.pm.pkg.component;
+package com.android.internal.pm.pkg.component;
 
-import static com.android.server.pm.pkg.parsing.ParsingUtils.ANDROID_RES_NAMESPACE;
+import static com.android.internal.pm.pkg.parsing.ParsingUtils.ANDROID_RES_NAMESPACE;
 
 import android.annotation.NonNull;
 import android.content.Intent;
@@ -31,10 +31,9 @@
 import android.util.TypedValue;
 
 import com.android.internal.R;
-import com.android.internal.pm.pkg.component.ParsedIntentInfo;
 import com.android.internal.pm.pkg.parsing.ParsingPackage;
-import com.android.server.pm.pkg.parsing.ParsingPackageUtils;
-import com.android.server.pm.pkg.parsing.ParsingUtils;
+import com.android.internal.pm.pkg.parsing.ParsingPackageUtils;
+import com.android.internal.pm.pkg.parsing.ParsingUtils;
 
 import org.xmlpull.v1.XmlPullParser;
 import org.xmlpull.v1.XmlPullParserException;
diff --git a/services/core/java/com/android/server/pm/pkg/component/ParsedMainComponentImpl.java b/core/java/com/android/internal/pm/pkg/component/ParsedMainComponentImpl.java
similarity index 85%
rename from services/core/java/com/android/server/pm/pkg/component/ParsedMainComponentImpl.java
rename to core/java/com/android/internal/pm/pkg/component/ParsedMainComponentImpl.java
index f322eef..bb8f565 100644
--- a/services/core/java/com/android/server/pm/pkg/component/ParsedMainComponentImpl.java
+++ b/core/java/com/android/internal/pm/pkg/component/ParsedMainComponentImpl.java
@@ -14,9 +14,9 @@
  * limitations under the License.
  */
 
-package com.android.server.pm.pkg.component;
+package com.android.internal.pm.pkg.component;
 
-import static com.android.server.pm.parsing.pkg.PackageImpl.sForInternedString;
+import static com.android.internal.pm.parsing.pkg.PackageImpl.sForInternedString;
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
@@ -25,7 +25,6 @@
 import android.text.TextUtils;
 
 import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.pm.pkg.component.ParsedMainComponent;
 import com.android.internal.util.DataClass;
 import com.android.internal.util.Parcelling.BuiltIn.ForInternedString;
 
@@ -133,7 +132,7 @@
     // CHECKSTYLE:OFF Generated code
     //
     // To regenerate run:
-    // $ codegen $ANDROID_BUILD_TOP/frameworks/base/core/java/android/content/pm/parsing/component/ParsedMainComponentImpl.java
+    // $ codegen $ANDROID_BUILD_TOP/frameworks/base/core/java/com/android/internal/pm/pkg/component/ParsedMainComponentImpl.java
     //
     // To exclude the generated code from IntelliJ auto-formatting enable (one-time):
     //   Settings > Editor > Code Style > Formatter Control
@@ -227,10 +226,10 @@
     }
 
     @DataClass.Generated(
-            time = 1641414540422L,
+            time = 1701447884766L,
             codegenVersion = "1.0.23",
-            sourceFile = "frameworks/base/core/java/android/content/pm/parsing/component/ParsedMainComponentImpl.java",
-            inputSignatures = "private @android.annotation.Nullable @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForInternedString.class) java.lang.String processName\nprivate  boolean directBootAware\nprivate  boolean enabled\nprivate  boolean exported\nprivate  int order\nprivate @android.annotation.Nullable java.lang.String splitName\nprivate @android.annotation.Nullable java.lang.String[] attributionTags\npublic static final  android.os.Parcelable.Creator<android.content.pm.parsing.component.ParsedMainComponentImpl> CREATOR\npublic  android.content.pm.parsing.component.ParsedMainComponentImpl setProcessName(java.lang.String)\npublic  java.lang.String getClassName()\npublic @android.annotation.NonNull @java.lang.Override java.lang.String[] getAttributionTags()\npublic @java.lang.Override int describeContents()\npublic @java.lang.Override void writeToParcel(android.os.Parcel,int)\nclass ParsedMainComponentImpl extends android.content.pm.parsing.component.ParsedComponentImpl implements [android.content.pm.parsing.component.ParsedMainComponent, android.os.Parcelable]\n@com.android.internal.util.DataClass(genGetters=true, genSetters=true, genBuilder=false, genParcelable=false)")
+            sourceFile = "frameworks/base/core/java/com/android/internal/pm/pkg/component/ParsedMainComponentImpl.java",
+            inputSignatures = "private @android.annotation.Nullable @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForInternedString.class) java.lang.String processName\nprivate  boolean directBootAware\nprivate  boolean enabled\nprivate  boolean exported\nprivate  int order\nprivate @android.annotation.Nullable java.lang.String splitName\nprivate @android.annotation.Nullable java.lang.String[] attributionTags\npublic static final  android.os.Parcelable.Creator<com.android.internal.pm.pkg.component.ParsedMainComponentImpl> CREATOR\npublic  com.android.internal.pm.pkg.component.ParsedMainComponentImpl setProcessName(java.lang.String)\npublic  java.lang.String getClassName()\npublic @android.annotation.NonNull @java.lang.Override java.lang.String[] getAttributionTags()\npublic @java.lang.Override int describeContents()\npublic @java.lang.Override void writeToParcel(android.os.Parcel,int)\nclass ParsedMainComponentImpl extends com.android.internal.pm.pkg.component.ParsedComponentImpl implements [com.android.internal.pm.pkg.component.ParsedMainComponent, android.os.Parcelable]\n@com.android.internal.util.DataClass(genGetters=true, genSetters=true, genBuilder=false, genParcelable=false)")
     @Deprecated
     private void __metadata() {}
 
diff --git a/services/core/java/com/android/server/pm/pkg/component/ParsedMainComponentUtils.java b/core/java/com/android/internal/pm/pkg/component/ParsedMainComponentUtils.java
similarity index 95%
rename from services/core/java/com/android/server/pm/pkg/component/ParsedMainComponentUtils.java
rename to core/java/com/android/internal/pm/pkg/component/ParsedMainComponentUtils.java
index 8268f0f..7e56180 100644
--- a/services/core/java/com/android/server/pm/pkg/component/ParsedMainComponentUtils.java
+++ b/core/java/com/android/internal/pm/pkg/component/ParsedMainComponentUtils.java
@@ -14,9 +14,9 @@
  * limitations under the License.
  */
 
-package com.android.server.pm.pkg.component;
+package com.android.internal.pm.pkg.component;
 
-import static com.android.server.pm.pkg.parsing.ParsingUtils.NOT_SET;
+import static com.android.internal.pm.pkg.parsing.ParsingUtils.NOT_SET;
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
@@ -31,10 +31,8 @@
 import android.util.Slog;
 
 import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.pm.pkg.component.ParsedIntentInfo;
-import com.android.internal.pm.pkg.component.ParsedMainComponent;
 import com.android.internal.pm.pkg.parsing.ParsingPackage;
-import com.android.server.pm.pkg.parsing.ParsingUtils;
+import com.android.internal.pm.pkg.parsing.ParsingUtils;
 
 import org.xmlpull.v1.XmlPullParserException;
 
diff --git a/services/core/java/com/android/server/pm/pkg/component/ParsedPermissionGroupImpl.java b/core/java/com/android/internal/pm/pkg/component/ParsedPermissionGroupImpl.java
similarity index 85%
rename from services/core/java/com/android/server/pm/pkg/component/ParsedPermissionGroupImpl.java
rename to core/java/com/android/internal/pm/pkg/component/ParsedPermissionGroupImpl.java
index afe37bc..3622019 100644
--- a/services/core/java/com/android/server/pm/pkg/component/ParsedPermissionGroupImpl.java
+++ b/core/java/com/android/internal/pm/pkg/component/ParsedPermissionGroupImpl.java
@@ -14,14 +14,13 @@
  * limitations under the License.
  */
 
-package com.android.server.pm.pkg.component;
+package com.android.internal.pm.pkg.component;
 
 import android.annotation.NonNull;
 import android.os.Parcel;
 import android.os.Parcelable;
 
 import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.pm.pkg.component.ParsedPermissionGroup;
 import com.android.internal.util.DataClass;
 
 /**
@@ -75,7 +74,7 @@
     // CHECKSTYLE:OFF Generated code
     //
     // To regenerate run:
-    // $ codegen $ANDROID_BUILD_TOP/frameworks/base/services/core/java/com/android/server/pm/pkg/component/ParsedPermissionGroupImpl.java
+    // $ codegen $ANDROID_BUILD_TOP/frameworks/base/core/java/com/android/internal/pm/pkg/component/ParsedPermissionGroupImpl.java
     //
     // To exclude the generated code from IntelliJ auto-formatting enable (one-time):
     //   Settings > Editor > Code Style > Formatter Control
@@ -172,10 +171,10 @@
     };
 
     @DataClass.Generated(
-            time = 1642132854167L,
+            time = 1701445837884L,
             codegenVersion = "1.0.23",
-            sourceFile = "frameworks/base/services/core/java/com/android/server/pm/pkg/component/ParsedPermissionGroupImpl.java",
-            inputSignatures = "private  int requestDetailRes\nprivate  int backgroundRequestRes\nprivate  int backgroundRequestDetailRes\nprivate  int requestRes\nprivate  int priority\npublic  java.lang.String toString()\npublic @java.lang.Override @com.android.internal.util.DataClass.Generated.Member void writeToParcel(android.os.Parcel,int)\nclass ParsedPermissionGroupImpl extends com.android.server.pm.pkg.component.ParsedComponentImpl implements [com.android.internal.pm.pkg.component.ParsedPermissionGroup, android.os.Parcelable]\n@com.android.internal.util.DataClass(genGetters=true, genSetters=true, genBuilder=false, genParcelable=true, genAidl=false)")
+            sourceFile = "frameworks/base/core/java/com/android/internal/pm/pkg/component/ParsedPermissionGroupImpl.java",
+            inputSignatures = "private  int requestDetailRes\nprivate  int backgroundRequestRes\nprivate  int backgroundRequestDetailRes\nprivate  int requestRes\nprivate  int priority\npublic  java.lang.String toString()\npublic @java.lang.Override void writeToParcel(android.os.Parcel,int)\nclass ParsedPermissionGroupImpl extends com.android.internal.pm.pkg.component.ParsedComponentImpl implements [com.android.internal.pm.pkg.component.ParsedPermissionGroup, android.os.Parcelable]\n@com.android.internal.util.DataClass(genGetters=true, genSetters=true, genBuilder=false, genParcelable=true, genAidl=false)")
     @Deprecated
     private void __metadata() {}
 
diff --git a/services/core/java/com/android/server/pm/pkg/component/ParsedPermissionImpl.java b/core/java/com/android/internal/pm/pkg/component/ParsedPermissionImpl.java
similarity index 77%
rename from services/core/java/com/android/server/pm/pkg/component/ParsedPermissionImpl.java
rename to core/java/com/android/internal/pm/pkg/component/ParsedPermissionImpl.java
index 69e33c8..4dcce131 100644
--- a/services/core/java/com/android/server/pm/pkg/component/ParsedPermissionImpl.java
+++ b/core/java/com/android/internal/pm/pkg/component/ParsedPermissionImpl.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.server.pm.pkg.component;
+package com.android.internal.pm.pkg.component;
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
@@ -24,8 +24,6 @@
 import android.util.ArraySet;
 
 import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.pm.pkg.component.ParsedPermission;
-import com.android.internal.pm.pkg.component.ParsedPermissionGroup;
 import com.android.internal.util.DataClass;
 import com.android.internal.util.Parcelling;
 import com.android.internal.util.Parcelling.BuiltIn.ForInternedString;
@@ -148,7 +146,7 @@
     // CHECKSTYLE:OFF Generated code
     //
     // To regenerate run:
-    // $ codegen $ANDROID_BUILD_TOP/frameworks/base/core/java/android/content/pm/parsing/component/ParsedPermissionImpl.java
+    // $ codegen $ANDROID_BUILD_TOP/frameworks/base/core/java/com/android/internal/pm/pkg/component/ParsedPermissionImpl.java
     //
     // To exclude the generated code from IntelliJ auto-formatting enable (one-time):
     //   Settings > Editor > Code Style > Formatter Control
@@ -162,7 +160,7 @@
             int requestRes,
             int protectionLevel,
             boolean tree,
-            @Nullable ParsedPermissionGroupImpl parsedPermissionGroup,
+            @Nullable ParsedPermissionGroup parsedPermissionGroup,
             @Nullable Set<String> knownCerts) {
         this.backgroundPermission = backgroundPermission;
         this.group = group;
@@ -237,10 +235,10 @@
     }
 
     @DataClass.Generated(
-            time = 1641414649731L,
+            time = 1701445829812L,
             codegenVersion = "1.0.23",
-            sourceFile = "frameworks/base/core/java/android/content/pm/parsing/component/ParsedPermissionImpl.java",
-            inputSignatures = "private static  com.android.internal.util.Parcelling.BuiltIn.ForStringSet sForStringSet\nprivate @android.annotation.Nullable java.lang.String backgroundPermission\nprivate @android.annotation.Nullable @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForInternedString.class) java.lang.String group\nprivate  int requestRes\nprivate  int protectionLevel\nprivate  boolean tree\nprivate @android.annotation.Nullable android.content.pm.parsing.component.ParsedPermissionGroupImpl parsedPermissionGroup\nprivate @android.annotation.Nullable java.util.Set<java.lang.String> knownCerts\npublic static final @android.annotation.NonNull android.os.Parcelable.Creator<android.content.pm.parsing.component.ParsedPermissionImpl> CREATOR\npublic  android.content.pm.parsing.component.ParsedPermissionGroup getParsedPermissionGroup()\npublic  android.content.pm.parsing.component.ParsedPermissionImpl setGroup(java.lang.String)\nprotected  void setKnownCert(java.lang.String)\nprotected  void setKnownCerts(java.lang.String[])\npublic @android.annotation.NonNull @java.lang.Override java.util.Set<java.lang.String> getKnownCerts()\npublic  java.lang.String toString()\npublic @java.lang.Override int describeContents()\npublic @java.lang.Override void writeToParcel(android.os.Parcel,int)\nclass ParsedPermissionImpl extends android.content.pm.parsing.component.ParsedComponentImpl implements [android.content.pm.parsing.component.ParsedPermission, android.os.Parcelable]\n@com.android.internal.util.DataClass(genGetters=true, genSetters=true, genBuilder=false, genParcelable=false)")
+            sourceFile = "frameworks/base/core/java/com/android/internal/pm/pkg/component/ParsedPermissionImpl.java",
+            inputSignatures = "private static final  com.android.internal.util.Parcelling.BuiltIn.ForStringSet sForStringSet\nprivate @android.annotation.Nullable java.lang.String backgroundPermission\nprivate @android.annotation.Nullable @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForInternedString.class) java.lang.String group\nprivate  int requestRes\nprivate  int protectionLevel\nprivate  boolean tree\nprivate @android.annotation.Nullable com.android.internal.pm.pkg.component.ParsedPermissionGroup parsedPermissionGroup\nprivate @android.annotation.Nullable java.util.Set<java.lang.String> knownCerts\npublic static final @android.annotation.NonNull android.os.Parcelable.Creator<com.android.internal.pm.pkg.component.ParsedPermissionImpl> CREATOR\npublic  com.android.internal.pm.pkg.component.ParsedPermissionGroup getParsedPermissionGroup()\npublic  com.android.internal.pm.pkg.component.ParsedPermissionImpl setGroup(java.lang.String)\nprotected  void setKnownCert(java.lang.String)\nprotected  void setKnownCerts(java.lang.String[])\npublic @android.annotation.NonNull @java.lang.Override java.util.Set<java.lang.String> getKnownCerts()\npublic  java.lang.String toString()\npublic @java.lang.Override int describeContents()\npublic @java.lang.Override void writeToParcel(android.os.Parcel,int)\nclass ParsedPermissionImpl extends com.android.internal.pm.pkg.component.ParsedComponentImpl implements [com.android.internal.pm.pkg.component.ParsedPermission, android.os.Parcelable]\n@com.android.internal.util.DataClass(genGetters=true, genSetters=true, genBuilder=false, genParcelable=false)")
     @Deprecated
     private void __metadata() {}
 
diff --git a/services/core/java/com/android/server/pm/pkg/component/ParsedPermissionUtils.java b/core/java/com/android/internal/pm/pkg/component/ParsedPermissionUtils.java
similarity index 97%
rename from services/core/java/com/android/server/pm/pkg/component/ParsedPermissionUtils.java
rename to core/java/com/android/internal/pm/pkg/component/ParsedPermissionUtils.java
index 4b45d37..5651c1c 100644
--- a/services/core/java/com/android/server/pm/pkg/component/ParsedPermissionUtils.java
+++ b/core/java/com/android/internal/pm/pkg/component/ParsedPermissionUtils.java
@@ -14,9 +14,9 @@
  * limitations under the License.
  */
 
-package com.android.server.pm.pkg.component;
+package com.android.internal.pm.pkg.component;
 
-import static com.android.server.pm.pkg.parsing.ParsingUtils.NOT_SET;
+import static com.android.internal.pm.pkg.parsing.ParsingUtils.NOT_SET;
 
 import android.annotation.NonNull;
 import android.content.pm.PermissionInfo;
@@ -31,10 +31,8 @@
 import android.util.Slog;
 
 import com.android.internal.R;
-import com.android.internal.pm.pkg.component.ParsedPermission;
-import com.android.internal.pm.pkg.component.ParsedPermissionGroup;
 import com.android.internal.pm.pkg.parsing.ParsingPackage;
-import com.android.server.pm.pkg.parsing.ParsingUtils;
+import com.android.internal.pm.pkg.parsing.ParsingUtils;
 
 import org.xmlpull.v1.XmlPullParserException;
 
diff --git a/services/core/java/com/android/server/pm/pkg/component/ParsedProcessImpl.java b/core/java/com/android/internal/pm/pkg/component/ParsedProcessImpl.java
similarity index 93%
rename from services/core/java/com/android/server/pm/pkg/component/ParsedProcessImpl.java
rename to core/java/com/android/internal/pm/pkg/component/ParsedProcessImpl.java
index 40e3670..212fb86 100644
--- a/services/core/java/com/android/server/pm/pkg/component/ParsedProcessImpl.java
+++ b/core/java/com/android/internal/pm/pkg/component/ParsedProcessImpl.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.server.pm.pkg.component;
+package com.android.internal.pm.pkg.component;
 
 import static java.util.Collections.emptySet;
 
@@ -25,7 +25,6 @@
 import android.util.ArrayMap;
 import android.util.ArraySet;
 
-import com.android.internal.pm.pkg.component.ParsedProcess;
 import com.android.internal.util.CollectionUtils;
 import com.android.internal.util.DataClass;
 import com.android.internal.util.Parcelling;
@@ -98,7 +97,7 @@
     // CHECKSTYLE:OFF Generated code
     //
     // To regenerate run:
-    // $ codegen $ANDROID_BUILD_TOP/frameworks/base/core/java/android/content/pm/parsing/component/ParsedProcessImpl.java
+    // $ codegen $ANDROID_BUILD_TOP/frameworks/base/core/java/com/android/internal/pm/pkg/component/ParsedProcessImpl.java
     //
     // To exclude the generated code from IntelliJ auto-formatting enable (one-time):
     //   Settings > Editor > Code Style > Formatter Control
@@ -304,10 +303,10 @@
     };
 
     @DataClass.Generated(
-            time = 1641431953775L,
+            time = 1701445656489L,
             codegenVersion = "1.0.23",
-            sourceFile = "frameworks/base/core/java/android/content/pm/parsing/component/ParsedProcessImpl.java",
-            inputSignatures = "private @android.annotation.NonNull java.lang.String name\nprivate @android.annotation.NonNull android.util.ArrayMap<java.lang.String,java.lang.String> appClassNamesByPackage\nprivate @android.annotation.NonNull @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForInternedStringSet.class) java.util.Set<java.lang.String> deniedPermissions\nprivate @android.content.pm.ApplicationInfo.GwpAsanMode int gwpAsanMode\nprivate @android.content.pm.ApplicationInfo.MemtagMode int memtagMode\nprivate @android.content.pm.ApplicationInfo.NativeHeapZeroInitialized int nativeHeapZeroInitialized\npublic  void addStateFrom(android.content.pm.parsing.component.ParsedProcess)\npublic  void putAppClassNameForPackage(java.lang.String,java.lang.String)\nclass ParsedProcessImpl extends java.lang.Object implements [android.content.pm.parsing.component.ParsedProcess, android.os.Parcelable]\n@com.android.internal.util.DataClass(genGetters=true, genSetters=true, genParcelable=true, genAidl=false, genBuilder=false)")
+            sourceFile = "frameworks/base/core/java/com/android/internal/pm/pkg/component/ParsedProcessImpl.java",
+            inputSignatures = "private @android.annotation.NonNull java.lang.String name\nprivate @android.annotation.NonNull android.util.ArrayMap<java.lang.String,java.lang.String> appClassNamesByPackage\nprivate @android.annotation.NonNull @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForInternedStringSet.class) java.util.Set<java.lang.String> deniedPermissions\nprivate @android.content.pm.ApplicationInfo.GwpAsanMode int gwpAsanMode\nprivate @android.content.pm.ApplicationInfo.MemtagMode int memtagMode\nprivate @android.content.pm.ApplicationInfo.NativeHeapZeroInitialized int nativeHeapZeroInitialized\npublic  void addStateFrom(com.android.internal.pm.pkg.component.ParsedProcess)\npublic  void putAppClassNameForPackage(java.lang.String,java.lang.String)\nclass ParsedProcessImpl extends java.lang.Object implements [com.android.internal.pm.pkg.component.ParsedProcess, android.os.Parcelable]\n@com.android.internal.util.DataClass(genGetters=true, genSetters=true, genParcelable=true, genAidl=false, genBuilder=false)")
     @Deprecated
     private void __metadata() {}
 
diff --git a/services/core/java/com/android/server/pm/pkg/component/ParsedProcessUtils.java b/core/java/com/android/internal/pm/pkg/component/ParsedProcessUtils.java
similarity index 97%
rename from services/core/java/com/android/server/pm/pkg/component/ParsedProcessUtils.java
rename to core/java/com/android/internal/pm/pkg/component/ParsedProcessUtils.java
index a849549..3b2056e 100644
--- a/services/core/java/com/android/server/pm/pkg/component/ParsedProcessUtils.java
+++ b/core/java/com/android/internal/pm/pkg/component/ParsedProcessUtils.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.server.pm.pkg.component;
+package com.android.internal.pm.pkg.component;
 
 import android.annotation.NonNull;
 import android.content.pm.ApplicationInfo;
@@ -27,11 +27,10 @@
 import android.util.ArraySet;
 
 import com.android.internal.R;
-import com.android.internal.pm.pkg.component.ParsedProcess;
 import com.android.internal.pm.pkg.parsing.ParsingPackage;
+import com.android.internal.pm.pkg.parsing.ParsingUtils;
 import com.android.internal.util.CollectionUtils;
 import com.android.internal.util.XmlUtils;
-import com.android.server.pm.pkg.parsing.ParsingUtils;
 
 import org.xmlpull.v1.XmlPullParser;
 import org.xmlpull.v1.XmlPullParserException;
diff --git a/services/core/java/com/android/server/pm/pkg/component/ParsedProviderImpl.java b/core/java/com/android/internal/pm/pkg/component/ParsedProviderImpl.java
similarity index 90%
rename from services/core/java/com/android/server/pm/pkg/component/ParsedProviderImpl.java
rename to core/java/com/android/internal/pm/pkg/component/ParsedProviderImpl.java
index 81a3c17..987fd41 100644
--- a/services/core/java/com/android/server/pm/pkg/component/ParsedProviderImpl.java
+++ b/core/java/com/android/internal/pm/pkg/component/ParsedProviderImpl.java
@@ -14,9 +14,9 @@
  * limitations under the License.
  */
 
-package com.android.server.pm.pkg.component;
+package com.android.internal.pm.pkg.component;
 
-import static com.android.server.pm.parsing.pkg.PackageImpl.sForInternedString;
+import static com.android.internal.pm.parsing.pkg.PackageImpl.sForInternedString;
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
@@ -28,7 +28,6 @@
 import android.text.TextUtils;
 
 import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.pm.pkg.component.ParsedProvider;
 import com.android.internal.util.CollectionUtils;
 import com.android.internal.util.DataClass;
 import com.android.internal.util.Parcelling.BuiltIn.ForInternedString;
@@ -302,7 +301,7 @@
             time = 1642560323360L,
             codegenVersion = "1.0.23",
             sourceFile = "frameworks/base/services/core/java/com/android/server/pm/pkg/component/ParsedProviderImpl.java",
-            inputSignatures = "private @android.annotation.Nullable @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForInternedString.class) java.lang.String authority\nprivate  boolean syncable\nprivate @android.annotation.Nullable @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForInternedString.class) java.lang.String readPermission\nprivate @android.annotation.Nullable @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForInternedString.class) java.lang.String writePermission\nprivate  boolean grantUriPermissions\nprivate  boolean forceUriPermissions\nprivate  boolean multiProcess\nprivate  int initOrder\nprivate @android.annotation.NonNull java.util.List<android.os.PatternMatcher> uriPermissionPatterns\nprivate @android.annotation.NonNull java.util.List<android.content.pm.PathPermission> pathPermissions\npublic static final @android.annotation.NonNull android.os.Parcelable.Creator<com.android.server.pm.pkg.component.ParsedProviderImpl> CREATOR\npublic  com.android.server.pm.pkg.component.ParsedProviderImpl setReadPermission(java.lang.String)\npublic  com.android.server.pm.pkg.component.ParsedProviderImpl setWritePermission(java.lang.String)\npublic @android.annotation.NonNull com.android.server.pm.pkg.component.ParsedProviderImpl addUriPermissionPattern(android.os.PatternMatcher)\npublic @android.annotation.NonNull com.android.server.pm.pkg.component.ParsedProviderImpl addPathPermission(android.content.pm.PathPermission)\npublic  java.lang.String toString()\npublic @java.lang.Override int describeContents()\npublic @java.lang.Override void writeToParcel(android.os.Parcel,int)\nclass ParsedProviderImpl extends com.android.server.pm.pkg.component.ParsedMainComponentImpl implements [com.android.internal.pm.pkg.component.ParsedProvider, android.os.Parcelable]\n@com.android.internal.util.DataClass(genSetters=true, genGetters=true, genParcelable=false, genBuilder=false)")
+            inputSignatures = "private @android.annotation.Nullable @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForInternedString.class) java.lang.String authority\nprivate  boolean syncable\nprivate @android.annotation.Nullable @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForInternedString.class) java.lang.String readPermission\nprivate @android.annotation.Nullable @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForInternedString.class) java.lang.String writePermission\nprivate  boolean grantUriPermissions\nprivate  boolean forceUriPermissions\nprivate  boolean multiProcess\nprivate  int initOrder\nprivate @android.annotation.NonNull java.util.List<android.os.PatternMatcher> uriPermissionPatterns\nprivate @android.annotation.NonNull java.util.List<android.content.pm.PathPermission> pathPermissions\npublic static final @android.annotation.NonNull android.os.Parcelable.Creator<com.android.internal.pm.pkg.component.ParsedProviderImpl> CREATOR\npublic  com.android.internal.pm.pkg.component.ParsedProviderImpl setReadPermission(java.lang.String)\npublic  com.android.internal.pm.pkg.component.ParsedProviderImpl setWritePermission(java.lang.String)\npublic @android.annotation.NonNull com.android.internal.pm.pkg.component.ParsedProviderImpl addUriPermissionPattern(android.os.PatternMatcher)\npublic @android.annotation.NonNull com.android.internal.pm.pkg.component.ParsedProviderImpl addPathPermission(android.content.pm.PathPermission)\npublic  java.lang.String toString()\npublic @java.lang.Override int describeContents()\npublic @java.lang.Override void writeToParcel(android.os.Parcel,int)\nclass ParsedProviderImpl extends com.android.internal.pm.pkg.component.ParsedMainComponentImpl implements [com.android.internal.pm.pkg.component.ParsedProvider, android.os.Parcelable]\n@com.android.internal.util.DataClass(genSetters=true, genGetters=true, genParcelable=false, genBuilder=false)")
     @Deprecated
     private void __metadata() {}
 
diff --git a/services/core/java/com/android/server/pm/pkg/component/ParsedProviderUtils.java b/core/java/com/android/internal/pm/pkg/component/ParsedProviderUtils.java
similarity index 97%
rename from services/core/java/com/android/server/pm/pkg/component/ParsedProviderUtils.java
rename to core/java/com/android/internal/pm/pkg/component/ParsedProviderUtils.java
index 0b28a12..5d82d04 100644
--- a/services/core/java/com/android/server/pm/pkg/component/ParsedProviderUtils.java
+++ b/core/java/com/android/internal/pm/pkg/component/ParsedProviderUtils.java
@@ -14,10 +14,10 @@
  * limitations under the License.
  */
 
-package com.android.server.pm.pkg.component;
+package com.android.internal.pm.pkg.component;
 
-import static com.android.server.pm.pkg.component.ComponentParseUtils.flag;
-import static com.android.server.pm.pkg.parsing.ParsingPackageUtils.RIGID_PARSER;
+import static com.android.internal.pm.pkg.component.ComponentParseUtils.flag;
+import static com.android.internal.pm.pkg.parsing.ParsingPackageUtils.RIGID_PARSER;
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
@@ -34,9 +34,8 @@
 import android.util.Slog;
 
 import com.android.internal.R;
-import com.android.internal.pm.pkg.component.ParsedProvider;
 import com.android.internal.pm.pkg.parsing.ParsingPackage;
-import com.android.server.pm.pkg.parsing.ParsingUtils;
+import com.android.internal.pm.pkg.parsing.ParsingUtils;
 
 import org.xmlpull.v1.XmlPullParser;
 import org.xmlpull.v1.XmlPullParserException;
diff --git a/services/core/java/com/android/server/pm/pkg/component/ParsedServiceImpl.java b/core/java/com/android/internal/pm/pkg/component/ParsedServiceImpl.java
similarity index 80%
rename from services/core/java/com/android/server/pm/pkg/component/ParsedServiceImpl.java
rename to core/java/com/android/internal/pm/pkg/component/ParsedServiceImpl.java
index ca8c45d..f4662d8 100644
--- a/services/core/java/com/android/server/pm/pkg/component/ParsedServiceImpl.java
+++ b/core/java/com/android/internal/pm/pkg/component/ParsedServiceImpl.java
@@ -14,9 +14,9 @@
  * limitations under the License.
  */
 
-package com.android.server.pm.pkg.component;
+package com.android.internal.pm.pkg.component;
 
-import static com.android.server.pm.parsing.pkg.PackageImpl.sForInternedString;
+import static com.android.internal.pm.parsing.pkg.PackageImpl.sForInternedString;
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
@@ -26,8 +26,6 @@
 import android.text.TextUtils;
 
 import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.pm.pkg.component.ParsedMainComponent;
-import com.android.internal.pm.pkg.component.ParsedService;
 import com.android.internal.util.DataClass;
 import com.android.internal.util.Parcelling.BuiltIn.ForInternedString;
 
@@ -107,7 +105,7 @@
     // CHECKSTYLE:OFF Generated code
     //
     // To regenerate run:
-    // $ codegen $ANDROID_BUILD_TOP/frameworks/base/core/java/android/content/pm/parsing/component/ParsedServiceImpl.java
+    // $ codegen $ANDROID_BUILD_TOP/frameworks/base/core/java/com/android/internal/pm/pkg/component/ParsedServiceImpl.java
     //
     // To exclude the generated code from IntelliJ auto-formatting enable (one-time):
     //   Settings > Editor > Code Style > Formatter Control
@@ -141,10 +139,10 @@
     }
 
     @DataClass.Generated(
-            time = 1641431954479L,
+            time = 1701445638370L,
             codegenVersion = "1.0.23",
-            sourceFile = "frameworks/base/core/java/android/content/pm/parsing/component/ParsedServiceImpl.java",
-            inputSignatures = "private  int foregroundServiceType\nprivate @android.annotation.Nullable @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForInternedString.class) java.lang.String permission\npublic static final @android.annotation.NonNull android.os.Parcelable.Creator<android.content.pm.parsing.component.ParsedServiceImpl> CREATOR\npublic  android.content.pm.parsing.component.ParsedMainComponent setPermission(java.lang.String)\npublic  java.lang.String toString()\npublic @java.lang.Override int describeContents()\npublic @java.lang.Override void writeToParcel(android.os.Parcel,int)\nclass ParsedServiceImpl extends android.content.pm.parsing.component.ParsedMainComponentImpl implements [android.content.pm.parsing.component.ParsedService, android.os.Parcelable]\n@com.android.internal.util.DataClass(genSetters=true, genGetters=true, genParcelable=false, genBuilder=false)")
+            sourceFile = "frameworks/base/core/java/com/android/internal/pm/pkg/component/ParsedServiceImpl.java",
+            inputSignatures = "private  int foregroundServiceType\nprivate @android.annotation.Nullable @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForInternedString.class) java.lang.String permission\npublic static final @android.annotation.NonNull android.os.Parcelable.Creator<com.android.internal.pm.pkg.component.ParsedServiceImpl> CREATOR\npublic  com.android.internal.pm.pkg.component.ParsedMainComponent setPermission(java.lang.String)\npublic  java.lang.String toString()\npublic @java.lang.Override int describeContents()\npublic @java.lang.Override void writeToParcel(android.os.Parcel,int)\nclass ParsedServiceImpl extends com.android.internal.pm.pkg.component.ParsedMainComponentImpl implements [com.android.internal.pm.pkg.component.ParsedService, android.os.Parcelable]\n@com.android.internal.util.DataClass(genSetters=true, genGetters=true, genParcelable=false, genBuilder=false)")
     @Deprecated
     private void __metadata() {}
 
diff --git a/services/core/java/com/android/server/pm/pkg/component/ParsedServiceUtils.java b/core/java/com/android/internal/pm/pkg/component/ParsedServiceUtils.java
similarity index 96%
rename from services/core/java/com/android/server/pm/pkg/component/ParsedServiceUtils.java
rename to core/java/com/android/internal/pm/pkg/component/ParsedServiceUtils.java
index 171ef59..a1dd19a3 100644
--- a/services/core/java/com/android/server/pm/pkg/component/ParsedServiceUtils.java
+++ b/core/java/com/android/internal/pm/pkg/component/ParsedServiceUtils.java
@@ -14,9 +14,9 @@
  * limitations under the License.
  */
 
-package com.android.server.pm.pkg.component;
+package com.android.internal.pm.pkg.component;
 
-import static com.android.server.pm.pkg.component.ComponentParseUtils.flag;
+import static com.android.internal.pm.pkg.component.ComponentParseUtils.flag;
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
@@ -32,9 +32,8 @@
 import android.os.Build;
 
 import com.android.internal.R;
-import com.android.internal.pm.pkg.component.ParsedService;
 import com.android.internal.pm.pkg.parsing.ParsingPackage;
-import com.android.server.pm.pkg.parsing.ParsingUtils;
+import com.android.internal.pm.pkg.parsing.ParsingUtils;
 
 import org.xmlpull.v1.XmlPullParser;
 import org.xmlpull.v1.XmlPullParserException;
diff --git a/services/core/java/com/android/server/pm/pkg/component/ParsedUsesPermissionImpl.java b/core/java/com/android/internal/pm/pkg/component/ParsedUsesPermissionImpl.java
similarity index 87%
rename from services/core/java/com/android/server/pm/pkg/component/ParsedUsesPermissionImpl.java
rename to core/java/com/android/internal/pm/pkg/component/ParsedUsesPermissionImpl.java
index 78377a8..fd131df 100644
--- a/services/core/java/com/android/server/pm/pkg/component/ParsedUsesPermissionImpl.java
+++ b/core/java/com/android/internal/pm/pkg/component/ParsedUsesPermissionImpl.java
@@ -14,14 +14,13 @@
  * limitations under the License.
  */
 
-package com.android.server.pm.pkg.component;
+package com.android.internal.pm.pkg.component;
 
 import android.annotation.NonNull;
 import android.os.Parcel;
 import android.os.Parcelable;
 
 import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.pm.pkg.component.ParsedUsesPermission;
 import com.android.internal.util.DataClass;
 import com.android.internal.util.Parcelling;
 
@@ -51,7 +50,7 @@
     // CHECKSTYLE:OFF Generated code
     //
     // To regenerate run:
-    // $ codegen $ANDROID_BUILD_TOP/frameworks/base/core/java/android/content/pm/parsing/component/ParsedUsesPermissionImpl.java
+    // $ codegen $ANDROID_BUILD_TOP/frameworks/base/core/java/com/android/internal/pm/pkg/component/ParsedUsesPermissionImpl.java
     //
     // To exclude the generated code from IntelliJ auto-formatting enable (one-time):
     //   Settings > Editor > Code Style > Formatter Control
@@ -158,10 +157,10 @@
     };
 
     @DataClass.Generated(
-            time = 1641431955242L,
+            time = 1701445626268L,
             codegenVersion = "1.0.23",
-            sourceFile = "frameworks/base/core/java/android/content/pm/parsing/component/ParsedUsesPermissionImpl.java",
-            inputSignatures = "private @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForInternedString.class) @android.annotation.NonNull java.lang.String name\nprivate @android.content.pm.parsing.component.ParsedUsesPermission.UsesPermissionFlags int usesPermissionFlags\nclass ParsedUsesPermissionImpl extends java.lang.Object implements [android.content.pm.parsing.component.ParsedUsesPermission, android.os.Parcelable]\n@com.android.internal.util.DataClass(genGetters=true, genSetters=true, genBuilder=false, genParcelable=true, genAidl=false)")
+            sourceFile = "frameworks/base/core/java/com/android/internal/pm/pkg/component/ParsedUsesPermissionImpl.java",
+            inputSignatures = "private @com.android.internal.util.DataClass.ParcelWith(com.android.internal.util.Parcelling.BuiltIn.ForInternedString.class) @android.annotation.NonNull java.lang.String name\nprivate @com.android.internal.pm.pkg.component.ParsedUsesPermission.UsesPermissionFlags int usesPermissionFlags\nclass ParsedUsesPermissionImpl extends java.lang.Object implements [com.android.internal.pm.pkg.component.ParsedUsesPermission, android.os.Parcelable]\n@com.android.internal.util.DataClass(genGetters=true, genSetters=true, genBuilder=false, genParcelable=true, genAidl=false)")
     @Deprecated
     private void __metadata() {}
 
diff --git a/services/core/java/com/android/server/pm/pkg/parsing/ParsingPackageUtils.java b/core/java/com/android/internal/pm/pkg/parsing/ParsingPackageUtils.java
similarity index 97%
rename from services/core/java/com/android/server/pm/pkg/parsing/ParsingPackageUtils.java
rename to core/java/com/android/internal/pm/pkg/parsing/ParsingPackageUtils.java
index aa0fb27..dbe4fba 100644
--- a/services/core/java/com/android/server/pm/pkg/parsing/ParsingPackageUtils.java
+++ b/core/java/com/android/internal/pm/pkg/parsing/ParsingPackageUtils.java
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package com.android.server.pm.pkg.parsing;
+package com.android.internal.pm.pkg.parsing;
 
 import static android.content.pm.ActivityInfo.FLAG_SUPPORTS_PICTURE_IN_PICTURE;
 import static android.content.pm.ActivityInfo.RESIZE_MODE_UNRESIZEABLE;
@@ -30,7 +30,7 @@
 import static android.os.Build.VERSION_CODES.O;
 import static android.os.Trace.TRACE_TAG_PACKAGE_MANAGER;
 
-import static com.android.server.pm.pkg.parsing.ParsingUtils.parseKnownActivityEmbeddingCerts;
+import static com.android.internal.pm.pkg.parsing.ParsingUtils.parseKnownActivityEmbeddingCerts;
 
 import android.annotation.AnyRes;
 import android.annotation.CheckResult;
@@ -57,7 +57,6 @@
 import android.content.pm.parsing.result.ParseInput;
 import android.content.pm.parsing.result.ParseInput.DeferredError;
 import android.content.pm.parsing.result.ParseResult;
-import android.content.pm.parsing.result.ParseTypeImpl;
 import android.content.res.ApkAssets;
 import android.content.res.AssetManager;
 import android.content.res.Configuration;
@@ -90,43 +89,40 @@
 import com.android.internal.R;
 import com.android.internal.os.ClassLoaderFactory;
 import com.android.internal.pm.parsing.pkg.ParsedPackage;
+import com.android.internal.pm.permission.CompatibilityPermissionInfo;
+import com.android.internal.pm.pkg.component.ComponentMutateUtils;
+import com.android.internal.pm.pkg.component.ComponentParseUtils;
+import com.android.internal.pm.pkg.component.InstallConstraintsTagParser;
 import com.android.internal.pm.pkg.component.ParsedActivity;
+import com.android.internal.pm.pkg.component.ParsedActivityImpl;
+import com.android.internal.pm.pkg.component.ParsedActivityUtils;
 import com.android.internal.pm.pkg.component.ParsedApexSystemService;
+import com.android.internal.pm.pkg.component.ParsedApexSystemServiceUtils;
 import com.android.internal.pm.pkg.component.ParsedAttribution;
+import com.android.internal.pm.pkg.component.ParsedAttributionUtils;
 import com.android.internal.pm.pkg.component.ParsedComponent;
 import com.android.internal.pm.pkg.component.ParsedInstrumentation;
+import com.android.internal.pm.pkg.component.ParsedInstrumentationUtils;
 import com.android.internal.pm.pkg.component.ParsedIntentInfo;
+import com.android.internal.pm.pkg.component.ParsedIntentInfoImpl;
+import com.android.internal.pm.pkg.component.ParsedIntentInfoUtils;
 import com.android.internal.pm.pkg.component.ParsedMainComponent;
 import com.android.internal.pm.pkg.component.ParsedPermission;
 import com.android.internal.pm.pkg.component.ParsedPermissionGroup;
+import com.android.internal.pm.pkg.component.ParsedPermissionUtils;
 import com.android.internal.pm.pkg.component.ParsedProcess;
+import com.android.internal.pm.pkg.component.ParsedProcessUtils;
 import com.android.internal.pm.pkg.component.ParsedProvider;
+import com.android.internal.pm.pkg.component.ParsedProviderUtils;
 import com.android.internal.pm.pkg.component.ParsedService;
+import com.android.internal.pm.pkg.component.ParsedServiceUtils;
 import com.android.internal.pm.pkg.component.ParsedUsesPermission;
-import com.android.internal.pm.pkg.parsing.ParsingPackage;
+import com.android.internal.pm.pkg.component.ParsedUsesPermissionImpl;
+import com.android.internal.pm.split.DefaultSplitAssetLoader;
+import com.android.internal.pm.split.SplitAssetDependencyLoader;
+import com.android.internal.pm.split.SplitAssetLoader;
 import com.android.internal.util.ArrayUtils;
 import com.android.internal.util.XmlUtils;
-import com.android.server.pm.SharedUidMigration;
-import com.android.server.pm.parsing.pkg.PackageImpl;
-import com.android.server.pm.permission.CompatibilityPermissionInfo;
-import com.android.server.pm.pkg.component.ComponentMutateUtils;
-import com.android.server.pm.pkg.component.ComponentParseUtils;
-import com.android.server.pm.pkg.component.InstallConstraintsTagParser;
-import com.android.server.pm.pkg.component.ParsedActivityImpl;
-import com.android.server.pm.pkg.component.ParsedActivityUtils;
-import com.android.server.pm.pkg.component.ParsedApexSystemServiceUtils;
-import com.android.server.pm.pkg.component.ParsedAttributionUtils;
-import com.android.server.pm.pkg.component.ParsedInstrumentationUtils;
-import com.android.server.pm.pkg.component.ParsedIntentInfoImpl;
-import com.android.server.pm.pkg.component.ParsedIntentInfoUtils;
-import com.android.server.pm.pkg.component.ParsedPermissionUtils;
-import com.android.server.pm.pkg.component.ParsedProcessUtils;
-import com.android.server.pm.pkg.component.ParsedProviderUtils;
-import com.android.server.pm.pkg.component.ParsedServiceUtils;
-import com.android.server.pm.pkg.component.ParsedUsesPermissionImpl;
-import com.android.server.pm.split.DefaultSplitAssetLoader;
-import com.android.server.pm.split.SplitAssetDependencyLoader;
-import com.android.server.pm.split.SplitAssetLoader;
 
 import libcore.io.IoUtils;
 import libcore.util.EmptyArray;
@@ -267,18 +263,6 @@
     public @interface ParseFlags {}
 
     /**
-     * @see #parseDefault(ParseInput, File, int, List, boolean)
-     */
-    @NonNull
-    public static ParseResult<ParsedPackage> parseDefaultOneTime(File file,
-            @ParseFlags int parseFlags,
-            @NonNull List<PermissionManager.SplitPermissionInfo> splitPermissions,
-            boolean collectCertificates) {
-        ParseInput input = ParseTypeImpl.forDefaultParsing().reset();
-        return parseDefault(input, file, parseFlags, splitPermissions, collectCertificates);
-    }
-
-    /**
      * For cases outside of PackageManagerService when an APK needs to be parsed as a one-off
      * request, without caching the input object and without querying the internal system state for
      * feature support.
@@ -287,30 +271,10 @@
     public static ParseResult<ParsedPackage> parseDefault(ParseInput input, File file,
             @ParseFlags int parseFlags,
             @NonNull List<PermissionManager.SplitPermissionInfo> splitPermissions,
-            boolean collectCertificates) {
-        ParseResult<ParsedPackage> result;
+            boolean collectCertificates, Callback callback) {
 
         ParsingPackageUtils parser = new ParsingPackageUtils(null /*separateProcesses*/,
-                null /*displayMetrics*/, splitPermissions, new Callback() {
-            @Override
-            public boolean hasFeature(String feature) {
-                // Assume the device doesn't support anything. This will affect permission
-                // parsing and will force <uses-permission/> declarations to include all
-                // requiredNotFeature permissions and exclude all requiredFeature
-                // permissions. This mirrors the old behavior.
-                return false;
-            }
-
-            @Override
-            public ParsingPackage startParsingPackage(
-                    @NonNull String packageName,
-                    @NonNull String baseApkPath,
-                    @NonNull String path,
-                    @NonNull TypedArray manifestArray, boolean isCoreApp) {
-                return PackageImpl.forParsing(packageName, baseApkPath, path, manifestArray,
-                        isCoreApp);
-            }
-        });
+                null /*displayMetrics*/, splitPermissions, callback);
         var parseResult = parser.parsePackage(input, file, parseFlags);
         if (parseResult.isError()) {
             return input.error(parseResult);
@@ -1146,7 +1110,8 @@
             case TAG_RESTRICT_UPDATE:
                 return parseRestrictUpdateHash(flags, input, pkg, res, parser);
             case TAG_INSTALL_CONSTRAINTS:
-                return parseInstallConstraints(input, pkg, res, parser);
+                return parseInstallConstraints(input, pkg, res, parser,
+                        mCallback.getInstallConstraintsAllowlist());
             case TAG_QUERIES:
                 return parseQueries(input, pkg, res, parser);
             default:
@@ -1172,7 +1137,7 @@
         }
 
         boolean leaving = false;
-        if (!SharedUidMigration.isDisabled()) {
+        if (PackageManager.ENABLE_SHARED_UID_MIGRATION) {
             int max = anInteger(0, R.styleable.AndroidManifest_sharedUserMaxSdkVersion, sa);
             leaving = (max != 0) && (max < Build.VERSION.RESOURCES_SDK_INT);
         }
@@ -1858,10 +1823,11 @@
         return input.success(pkg);
     }
 
-    private static ParseResult<ParsingPackage> parseInstallConstraints(
-            ParseInput input, ParsingPackage pkg, Resources res, XmlResourceParser parser)
+    private static ParseResult<ParsingPackage> parseInstallConstraints(ParseInput input,
+            ParsingPackage pkg, Resources res, XmlResourceParser parser, Set<String> allowlist)
             throws IOException, XmlPullParserException {
-        return InstallConstraintsTagParser.parseInstallConstraints(input, pkg, res, parser);
+        return InstallConstraintsTagParser.parseInstallConstraints(
+                input, pkg, res, parser, allowlist);
     }
 
     private static ParseResult<ParsingPackage> parseQueries(ParseInput input, ParsingPackage pkg,
@@ -3485,5 +3451,9 @@
         ParsingPackage startParsingPackage(@NonNull String packageName,
                 @NonNull String baseApkPath, @NonNull String path,
                 @NonNull TypedArray manifestArray, boolean isCoreApp);
+
+        @NonNull Set<String> getHiddenApiWhitelistedApps();
+
+        @NonNull Set<String> getInstallConstraintsAllowlist();
     }
 }
diff --git a/services/core/java/com/android/server/pm/pkg/parsing/ParsingUtils.java b/core/java/com/android/internal/pm/pkg/parsing/ParsingUtils.java
similarity index 95%
rename from services/core/java/com/android/server/pm/pkg/parsing/ParsingUtils.java
rename to core/java/com/android/internal/pm/pkg/parsing/ParsingUtils.java
index 1d15955..26822c6 100644
--- a/services/core/java/com/android/server/pm/pkg/parsing/ParsingUtils.java
+++ b/core/java/com/android/internal/pm/pkg/parsing/ParsingUtils.java
@@ -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,9 +14,9 @@
  * limitations under the License.
  */
 
-package com.android.server.pm.pkg.parsing;
+package com.android.internal.pm.pkg.parsing;
 
-import static com.android.server.pm.pkg.parsing.ParsingPackageUtils.RIGID_PARSER;
+import static com.android.internal.pm.pkg.parsing.ParsingPackageUtils.RIGID_PARSER;
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
@@ -31,10 +31,9 @@
 import android.util.Slog;
 
 import com.android.internal.pm.pkg.component.ParsedIntentInfo;
-import com.android.internal.pm.pkg.parsing.ParsingPackage;
+import com.android.internal.pm.pkg.component.ParsedIntentInfoImpl;
 import com.android.internal.util.Parcelling;
 import com.android.internal.util.XmlUtils;
-import com.android.server.pm.pkg.component.ParsedIntentInfoImpl;
 
 import org.xmlpull.v1.XmlPullParserException;
 
diff --git a/services/core/java/com/android/server/pm/split/DefaultSplitAssetLoader.java b/core/java/com/android/internal/pm/split/DefaultSplitAssetLoader.java
similarity index 94%
rename from services/core/java/com/android/server/pm/split/DefaultSplitAssetLoader.java
rename to core/java/com/android/internal/pm/split/DefaultSplitAssetLoader.java
index 0bb969f..50c6243 100644
--- a/services/core/java/com/android/server/pm/split/DefaultSplitAssetLoader.java
+++ b/core/java/com/android/internal/pm/split/DefaultSplitAssetLoader.java
@@ -13,7 +13,7 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package com.android.server.pm.split;
+package com.android.internal.pm.split;
 
 import android.content.pm.parsing.ApkLiteParseUtils;
 import android.content.pm.parsing.PackageLite;
@@ -21,9 +21,9 @@
 import android.content.res.AssetManager;
 import android.os.Build;
 
+import com.android.internal.pm.pkg.parsing.ParsingPackageUtils;
+import com.android.internal.pm.pkg.parsing.ParsingPackageUtils.ParseFlags;
 import com.android.internal.util.ArrayUtils;
-import com.android.server.pm.pkg.parsing.ParsingPackageUtils;
-import com.android.server.pm.pkg.parsing.ParsingPackageUtils.ParseFlags;
 
 import libcore.io.IoUtils;
 
diff --git a/services/core/java/com/android/server/pm/split/SplitAssetDependencyLoader.java b/core/java/com/android/internal/pm/split/SplitAssetDependencyLoader.java
similarity index 96%
rename from services/core/java/com/android/server/pm/split/SplitAssetDependencyLoader.java
rename to core/java/com/android/internal/pm/split/SplitAssetDependencyLoader.java
index 56d92fb..c166cdc 100644
--- a/services/core/java/com/android/server/pm/split/SplitAssetDependencyLoader.java
+++ b/core/java/com/android/internal/pm/split/SplitAssetDependencyLoader.java
@@ -13,7 +13,7 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package com.android.server.pm.split;
+package com.android.internal.pm.split;
 
 import android.annotation.NonNull;
 import android.content.pm.parsing.ApkLiteParseUtils;
@@ -24,8 +24,8 @@
 import android.os.Build;
 import android.util.SparseArray;
 
-import com.android.server.pm.pkg.parsing.ParsingPackageUtils;
-import com.android.server.pm.pkg.parsing.ParsingPackageUtils.ParseFlags;
+import com.android.internal.pm.pkg.parsing.ParsingPackageUtils;
+import com.android.internal.pm.pkg.parsing.ParsingPackageUtils.ParseFlags;
 
 import libcore.io.IoUtils;
 
diff --git a/services/core/java/com/android/server/pm/split/SplitAssetLoader.java b/core/java/com/android/internal/pm/split/SplitAssetLoader.java
similarity index 96%
rename from services/core/java/com/android/server/pm/split/SplitAssetLoader.java
rename to core/java/com/android/internal/pm/split/SplitAssetLoader.java
index 8450159..c7c409d 100644
--- a/services/core/java/com/android/server/pm/split/SplitAssetLoader.java
+++ b/core/java/com/android/internal/pm/split/SplitAssetLoader.java
@@ -13,7 +13,7 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package com.android.server.pm.split;
+package com.android.internal.pm.split;
 
 import android.content.res.ApkAssets;
 import android.content.res.AssetManager;
diff --git a/core/java/com/android/internal/widget/LockPatternUtils.java b/core/java/com/android/internal/widget/LockPatternUtils.java
index 28fd2b4..bf8e613 100644
--- a/core/java/com/android/internal/widget/LockPatternUtils.java
+++ b/core/java/com/android/internal/widget/LockPatternUtils.java
@@ -897,13 +897,26 @@
     }
 
     /**
-     * Returns true if {@code userHandle} is a managed profile with separate challenge.
+     * Returns true if {@code userHandle} is a profile with separate challenge.
+     * <p>
+     * Returns false if {@code userHandle} is a profile with unified challenge, a profile whose
+     * credential is not shareable with its parent, or a non-profile user.
      */
     public boolean isSeparateProfileChallengeEnabled(int userHandle) {
         return isCredentialSharableWithParent(userHandle) && hasSeparateChallenge(userHandle);
     }
 
     /**
+     * Returns true if {@code userHandle} is a profile with unified challenge.
+     * <p>
+     * Returns false if {@code userHandle} is a profile with separate challenge, a profile whose
+     * credential is not shareable with its parent, or a non-profile user.
+     */
+    public boolean isProfileWithUnifiedChallenge(int userHandle) {
+        return isCredentialSharableWithParent(userHandle) && !hasSeparateChallenge(userHandle);
+    }
+
+    /**
      * Returns true if {@code userHandle} is a managed profile with unified challenge.
      */
     public boolean isManagedProfileWithUnifiedChallenge(int userHandle) {
diff --git a/core/java/com/android/internal/widget/LockscreenCredential.java b/core/java/com/android/internal/widget/LockscreenCredential.java
index c88763c..18d5f6d 100644
--- a/core/java/com/android/internal/widget/LockscreenCredential.java
+++ b/core/java/com/android/internal/widget/LockscreenCredential.java
@@ -134,12 +134,12 @@
     }
 
     /**
-     * Creates a LockscreenCredential object representing a managed password for profile with
-     * unified challenge. This credentiall will have type {@code CREDENTIAL_TYPE_PASSWORD} for now.
-     * TODO: consider add a new credential type for this. This can then supersede the
-     * isLockTiedToParent argument in various places in LSS.
+     * Creates a LockscreenCredential object representing the system-generated, system-managed
+     * password for a profile with unified challenge. This credential has type {@code
+     * CREDENTIAL_TYPE_PASSWORD} for now. TODO: consider add a new credential type for this. This
+     * can then supersede the isLockTiedToParent argument in various places in LSS.
      */
-    public static LockscreenCredential createManagedPassword(@NonNull byte[] password) {
+    public static LockscreenCredential createUnifiedProfilePassword(@NonNull byte[] password) {
         return new LockscreenCredential(CREDENTIAL_TYPE_PASSWORD,
                 Arrays.copyOf(password, password.length), /* hasInvalidChars= */ false);
     }
diff --git a/core/java/com/android/server/pm/pkg/AndroidPackage.java b/core/java/com/android/server/pm/pkg/AndroidPackage.java
index f86595f..adb0c69 100644
--- a/core/java/com/android/server/pm/pkg/AndroidPackage.java
+++ b/core/java/com/android/server/pm/pkg/AndroidPackage.java
@@ -58,6 +58,7 @@
 import com.android.internal.pm.pkg.component.ParsedProvider;
 import com.android.internal.pm.pkg.component.ParsedService;
 import com.android.internal.pm.pkg.component.ParsedUsesPermission;
+import com.android.internal.pm.pkg.parsing.ParsingPackageUtils;
 
 import java.security.PublicKey;
 import java.util.List;
@@ -690,7 +691,7 @@
 
     /**
      * The names of packages to adopt ownership of permissions from, parsed under {@link
-     * com.android.server.pm.pkg.parsing.ParsingPackageUtils#TAG_ADOPT_PERMISSIONS}.
+     * ParsingPackageUtils#TAG_ADOPT_PERMISSIONS}.
      *
      * @see R.styleable#AndroidManifestOriginalPackage_name
      * @hide
@@ -795,7 +796,7 @@
 
     /**
      * For use with {@link com.android.server.pm.KeySetManagerService}. Parsed in {@link
-     * com.android.server.pm.pkg.parsing.ParsingPackageUtils#TAG_KEY_SETS}.
+     * ParsingPackageUtils#TAG_KEY_SETS}.
      *
      * @see R.styleable#AndroidManifestKeySet
      * @see R.styleable#AndroidManifestPublicKey
@@ -1266,7 +1267,7 @@
 
     /**
      * For use with {@link com.android.server.pm.KeySetManagerService}. Parsed in {@link
-     * com.android.server.pm.pkg.parsing.ParsingPackageUtils#TAG_KEY_SETS}.
+     * ParsingPackageUtils#TAG_KEY_SETS}.
      *
      * @see R.styleable#AndroidManifestUpgradeKeySet
      * @hide
diff --git a/core/jni/android_os_HidlSupport.cpp b/core/jni/android_os_HidlSupport.cpp
index 3e51e93..e3602d8 100644
--- a/core/jni/android_os_HidlSupport.cpp
+++ b/core/jni/android_os_HidlSupport.cpp
@@ -15,7 +15,6 @@
  */
 
 #include <hidl/HidlTransportSupport.h>
-#include <hidl/ServiceManagement.h>
 #include <nativehelper/JNIHelp.h>
 
 #include "core_jni_helpers.h"
@@ -25,13 +24,8 @@
     return android::hardware::details::getPidIfSharable();
 }
 
-static jboolean android_os_HidlSupport_isHidlSupported(JNIEnv*, jclass) {
-    return android::hardware::isHidlSupported();
-}
-
 static const JNINativeMethod gHidlSupportMethods[] = {
-        {"getPidIfSharable", "()I", (void*)android_os_HidlSupport_getPidIfSharable},
-        {"isHidlSupported", "()Z", (void*)android_os_HidlSupport_isHidlSupported},
+    {"getPidIfSharable", "()I", (void*)android_os_HidlSupport_getPidIfSharable},
 };
 
 const char* const kHidlSupportPathName = "android/os/HidlSupport";
diff --git a/core/jni/com_android_internal_os_ZygoteCommandBuffer.cpp b/core/jni/com_android_internal_os_ZygoteCommandBuffer.cpp
index 87ab496..54c4cd5 100644
--- a/core/jni/com_android_internal_os_ZygoteCommandBuffer.cpp
+++ b/core/jni/com_android_internal_os_ZygoteCommandBuffer.cpp
@@ -63,6 +63,7 @@
   std::optional<std::pair<char*, char*>> readLine(FailFn fail_fn) {
     char* result = mBuffer + mNext;
     while (true) {
+      // We have scanned up to, but not including mNext for this line's newline.
       if (mNext == mEnd) {
         if (mEnd == MAX_COMMAND_BYTES) {
           return {};
@@ -89,7 +90,7 @@
       } else {
         mNext = nl - mBuffer + 1;
         if (--mLinesLeft < 0) {
-          fail_fn("ZygoteCommandBuffer.readLine attempted to read past mEnd of command");
+          fail_fn("ZygoteCommandBuffer.readLine attempted to read past end of command");
         }
         return std::make_pair(result, nl);
       }
@@ -125,8 +126,8 @@
     mEnd += lineLen + 1;
   }
 
-  // Clear mBuffer, start reading new command, return the number of arguments, leaving mBuffer
-  // positioned at the beginning of first argument. Return 0 on EOF.
+  // Start reading new command, return the number of arguments, leaving mBuffer positioned at the
+  // beginning of first argument. Return 0 on EOF.
   template<class FailFn>
   int getCount(FailFn fail_fn) {
     mLinesLeft = 1;
@@ -451,11 +452,14 @@
             (CREATE_ERROR("Write unexpectedly returned short: %d < 5", res));
       }
     }
-    // Clear buffer and get count from next command.
-    n_buffer->clear();
     for (;;) {
+      // Clear buffer and get count from next command.
+      n_buffer->clear();
       // Poll isn't strictly necessary for now. But without it, disconnect is hard to detect.
       int poll_res = TEMP_FAILURE_RETRY(poll(fd_structs, 2, -1 /* infinite timeout */));
+      if (poll_res < 0) {
+        fail_fn_z(CREATE_ERROR("Poll failed: %d: %s", errno, strerror(errno)));
+      }
       if ((fd_structs[SESSION_IDX].revents & POLLIN) != 0) {
         if (n_buffer->getCount(fail_fn_z) != 0) {
           break;
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index dd93586..c6a241f 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -4806,6 +4806,13 @@
     <permission android:name="android.permission.CHANGE_ACCESSIBILITY_VOLUME"
                 android:protectionLevel="signature" />
 
+    <!-- @FlaggedApi("com.android.server.accessibility.motion_event_observing")
+    @hide
+    @TestApi
+    Allows an accessibility service to observe motion events without consuming them. -->
+    <permission android:name="android.permission.ACCESSIBILITY_MOTION_EVENT_OBSERVING"
+                android:protectionLevel="signature" />
+
     <!-- @hide Allows an application to collect frame statistics -->
     <permission android:name="android.permission.FRAME_STATS"
          android:protectionLevel="signature" />
diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml
index 1f6ac80..4596ca7 100644
--- a/core/res/res/values/strings.xml
+++ b/core/res/res/values/strings.xml
@@ -5252,6 +5252,8 @@
     <!-- Zen mode - name of default automatic calendar time-based rule that is triggered every night (when sleeping). [CHAR LIMIT=40] -->
     <string name="zen_mode_default_every_night_name">Sleeping</string>
 
+    <!-- Zen mode - Trigger description of the rule, indicating which app owns it. [CHAR_LIMIT=100] -->
+    <string name="zen_mode_implicit_trigger_description">Managed by <xliff:g id="app_name">%1$s</xliff:g></string>
     <!-- Zen mode - Condition summary when a rule is activated due to a call to setInterruptionFilter(). [CHAR_LIMIT=NONE] -->
     <string name="zen_mode_implicit_activated">On</string>
     <!-- Zen mode - Condition summary when a rule is deactivated due to a call to setInterruptionFilter(). [CHAR_LIMIT=NONE] -->
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 5791ddb..fd6158d 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -2586,6 +2586,7 @@
   <java-symbol type="string" name="zen_mode_default_weekends_name" />
   <java-symbol type="string" name="zen_mode_default_events_name" />
   <java-symbol type="string" name="zen_mode_default_every_night_name" />
+  <java-symbol type="string" name="zen_mode_implicit_trigger_description" />
   <java-symbol type="string" name="zen_mode_implicit_activated" />
   <java-symbol type="string" name="zen_mode_implicit_deactivated" />
   <java-symbol type="string" name="display_rotation_camera_compat_toast_after_rotation" />
diff --git a/core/tests/coretests/src/android/app/AutomaticZenRuleTest.java b/core/tests/coretests/src/android/app/AutomaticZenRuleTest.java
index ba2ea88..d629f6a 100644
--- a/core/tests/coretests/src/android/app/AutomaticZenRuleTest.java
+++ b/core/tests/coretests/src/android/app/AutomaticZenRuleTest.java
@@ -19,15 +19,20 @@
 import static junit.framework.Assert.assertEquals;
 import static junit.framework.Assert.fail;
 
+import static org.junit.Assert.assertThrows;
+
 import android.content.ComponentName;
 import android.net.Uri;
 import android.os.Parcel;
+import android.platform.test.annotations.EnableFlags;
+import android.platform.test.flag.junit.SetFlagsRule;
 
 import androidx.test.ext.junit.runners.AndroidJUnit4;
 import androidx.test.filters.SmallTest;
 
 import com.google.common.base.Strings;
 
+import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
@@ -38,6 +43,9 @@
 public class AutomaticZenRuleTest {
     private static final String CLASS = "android.app.AutomaticZenRule";
 
+    @Rule
+    public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
+
     @Test
     public void testLongFields_inConstructor() {
         String longString = Strings.repeat("A", 65536);
@@ -100,6 +108,7 @@
     }
 
     @Test
+    @EnableFlags(Flags.FLAG_MODES_API)
     public void testLongInputsFromParcel() {
         // Create a rule with long fields, set directly via reflection so that we can confirm that
         // a rule with too-long fields that comes in via a parcel has its fields truncated directly.
@@ -152,6 +161,60 @@
                 fromParcel.getOwner().getPackageName().length());
         assertEquals(AutomaticZenRule.MAX_STRING_LENGTH,
                 fromParcel.getOwner().getClassName().length());
-        assertEquals(AutomaticZenRule.MAX_DESC_LENGTH, rule.getTriggerDescription().length());
+        assertEquals(AutomaticZenRule.MAX_DESC_LENGTH, fromParcel.getTriggerDescription().length());
+    }
+
+    @Test
+    @EnableFlags(Flags.FLAG_MODES_API)
+    public void validate_builderWithValidType_succeeds() throws Exception {
+        AutomaticZenRule rule = new AutomaticZenRule.Builder("rule", Uri.parse("uri"))
+                .setType(AutomaticZenRule.TYPE_BEDTIME)
+                .build();
+        rule.validate(); // No exception.
+    }
+
+    @Test
+    @EnableFlags(Flags.FLAG_MODES_API)
+    public void validate_builderWithoutType_succeeds() throws Exception {
+        AutomaticZenRule rule = new AutomaticZenRule.Builder("rule", Uri.parse("uri")).build();
+        rule.validate(); // No exception.
+    }
+
+    @Test
+    @EnableFlags(Flags.FLAG_MODES_API)
+    public void validate_constructorWithoutType_succeeds() throws Exception {
+        AutomaticZenRule rule = new AutomaticZenRule("rule", new ComponentName("pkg", "cps"),
+                new ComponentName("pkg", "activity"), Uri.parse("condition"), null,
+                NotificationManager.INTERRUPTION_FILTER_PRIORITY, true);
+        rule.validate(); // No exception.
+    }
+
+    @Test
+    @EnableFlags(Flags.FLAG_MODES_API)
+    public void validate_invalidType_throws() throws Exception {
+        AutomaticZenRule rule = new AutomaticZenRule.Builder("rule", Uri.parse("uri")).build();
+
+        // Set the field via reflection.
+        Field typeField = AutomaticZenRule.class.getDeclaredField("mType");
+        typeField.setAccessible(true);
+        typeField.set(rule, 100);
+
+        assertThrows(IllegalArgumentException.class, rule::validate);
+    }
+
+    @Test
+    @EnableFlags(Flags.FLAG_MODES_API)
+    public void setType_invalidType_throws() {
+        AutomaticZenRule rule = new AutomaticZenRule.Builder("rule", Uri.parse("uri")).build();
+
+        assertThrows(IllegalArgumentException.class, () -> rule.setType(100));
+    }
+
+    @Test
+    @EnableFlags(Flags.FLAG_MODES_API)
+    public void setTypeBuilder_invalidType_throws() {
+        AutomaticZenRule.Builder builder = new AutomaticZenRule.Builder("rule", Uri.parse("uri"));
+
+        assertThrows(IllegalArgumentException.class, () -> builder.setType(100));
     }
 }
diff --git a/core/tests/coretests/src/android/os/BundleTest.java b/core/tests/coretests/src/android/os/BundleTest.java
index 8c231de..e7b5dff6 100644
--- a/core/tests/coretests/src/android/os/BundleTest.java
+++ b/core/tests/coretests/src/android/os/BundleTest.java
@@ -197,7 +197,6 @@
     }
 
     @Test
-    @IgnoreUnderRavenwood(blockedBy = Parcel.class)
     public void kindofEquals_bothParcelled_same() {
         Bundle bundle1 = new Bundle();
         bundle1.putString("StringKey", "S");
@@ -215,7 +214,6 @@
     }
 
     @Test
-    @IgnoreUnderRavenwood(blockedBy = Parcel.class)
     public void kindofEquals_bothParcelled_different() {
         Bundle bundle1 = new Bundle();
         bundle1.putString("StringKey", "S");
@@ -247,7 +245,6 @@
     }
 
     @Test
-    @IgnoreUnderRavenwood(blockedBy = Parcel.class)
     public void kindofEquals_lazyValues() {
         Parcelable p1 = new CustomParcelable(13, "Tiramisu");
         Parcelable p2 = new CustomParcelable(13, "Tiramisu");
@@ -281,7 +278,6 @@
     }
 
     @Test
-    @IgnoreUnderRavenwood(blockedBy = Parcel.class)
     public void kindofEquals_lazyValuesWithIdenticalParcels_returnsTrue() {
         Parcelable p1 = new CustomParcelable(13, "Tiramisu");
         Parcelable p2 = new CustomParcelable(13, "Tiramisu");
diff --git a/core/tests/coretests/src/android/os/MessageQueueTest.java b/core/tests/coretests/src/android/os/MessageQueueTest.java
index 8cd6773..851e612 100644
--- a/core/tests/coretests/src/android/os/MessageQueueTest.java
+++ b/core/tests/coretests/src/android/os/MessageQueueTest.java
@@ -16,6 +16,7 @@
 
 package android.os;
 
+import android.platform.test.annotations.IgnoreUnderRavenwood;
 import android.platform.test.ravenwood.RavenwoodRule;
 
 import androidx.test.filters.MediumTest;
@@ -153,6 +154,7 @@
 
     @Test
     @MediumTest
+    @IgnoreUnderRavenwood(reason = "Flaky test, b/315872700")
     public void testFieldIntegrity() throws Exception {
 
         TestHandlerThread tester = new TestFieldIntegrityHandler() {
diff --git a/core/tests/coretests/src/android/os/ParcelTest.java b/core/tests/coretests/src/android/os/ParcelTest.java
index 5bbd221..26f6d69 100644
--- a/core/tests/coretests/src/android/os/ParcelTest.java
+++ b/core/tests/coretests/src/android/os/ParcelTest.java
@@ -132,7 +132,6 @@
     }
 
     @Test
-    @IgnoreUnderRavenwood(blockedBy = Parcel.class)
     public void testCompareDataInRange_whenSameData() {
         Parcel pA = Parcel.obtain();
         int iA = pA.dataPosition();
@@ -169,7 +168,6 @@
     }
 
     @Test
-    @IgnoreUnderRavenwood(blockedBy = Parcel.class)
     public void testCompareDataInRange_whenDifferentData() {
         Parcel pA = Parcel.obtain();
         int iA = pA.dataPosition();
@@ -186,7 +184,6 @@
     }
 
     @Test
-    @IgnoreUnderRavenwood(blockedBy = Parcel.class)
     public void testCompareDataInRange_whenLimitOutOfBounds_throws() {
         Parcel pA = Parcel.obtain();
         int iA = pA.dataPosition();
@@ -213,7 +210,6 @@
     }
 
     @Test
-    @IgnoreUnderRavenwood(blockedBy = Parcel.class)
     public void testCompareDataInRange_whenLengthZero() {
         Parcel pA = Parcel.obtain();
         int iA = pA.dataPosition();
@@ -232,7 +228,6 @@
     }
 
     @Test
-    @IgnoreUnderRavenwood(blockedBy = Parcel.class)
     public void testCompareDataInRange_whenNegativeLength_throws() {
         Parcel pA = Parcel.obtain();
         int iA = pA.dataPosition();
@@ -248,7 +243,6 @@
     }
 
     @Test
-    @IgnoreUnderRavenwood(blockedBy = Parcel.class)
     public void testCompareDataInRange_whenNegativeOffset_throws() {
         Parcel pA = Parcel.obtain();
         int iA = pA.dataPosition();
diff --git a/core/tests/coretests/src/android/service/notification/ConditionTest.java b/core/tests/coretests/src/android/service/notification/ConditionTest.java
index 612562e..e94273e 100644
--- a/core/tests/coretests/src/android/service/notification/ConditionTest.java
+++ b/core/tests/coretests/src/android/service/notification/ConditionTest.java
@@ -21,9 +21,12 @@
 import static junit.framework.Assert.assertEquals;
 import static junit.framework.Assert.fail;
 
+import static org.junit.Assert.assertThrows;
+
 import android.app.Flags;
 import android.net.Uri;
 import android.os.Parcel;
+import android.platform.test.annotations.EnableFlags;
 import android.platform.test.flag.junit.SetFlagsRule;
 
 import androidx.test.ext.junit.runners.AndroidJUnit4;
@@ -36,6 +39,7 @@
 import org.junit.runner.RunWith;
 
 import java.lang.reflect.Field;
+import java.lang.reflect.Modifier;
 
 @RunWith(AndroidJUnit4.class)
 @SmallTest
@@ -194,4 +198,59 @@
         Condition fromParcel = new Condition(parcel);
         assertThat(fromParcel).isEqualTo(cond);
     }
+
+    @Test
+    @EnableFlags(Flags.FLAG_MODES_API)
+    public void constructor_unspecifiedSource_succeeds() {
+        new Condition(Uri.parse("id"), "Summary", Condition.STATE_TRUE);
+        // No exception.
+    }
+
+    @Test
+    @EnableFlags(Flags.FLAG_MODES_API)
+    public void constructor_validSource_succeeds() {
+        new Condition(Uri.parse("id"), "Summary", Condition.STATE_TRUE, Condition.SOURCE_CONTEXT);
+        // No exception.
+    }
+
+    @Test
+    @EnableFlags(Flags.FLAG_MODES_API)
+    public void constructor_invalidSource_throws() {
+        assertThrows(IllegalArgumentException.class,
+                () -> new Condition(Uri.parse("uri"), "Summary", Condition.STATE_TRUE, 1000));
+    }
+
+    @Test
+    @EnableFlags(Flags.FLAG_MODES_API)
+    public void constructor_parcelWithInvalidSource_throws() {
+        Condition original = new Condition(Uri.parse("condition"), "Summary", Condition.STATE_TRUE,
+                Condition.SOURCE_SCHEDULE);
+        Parcel parcel = Parcel.obtain();
+        original.writeToParcel(parcel, 0);
+
+        // Tweak the parcel to contain and invalid source value.
+        parcel.setDataPosition(parcel.dataPosition() - 8); // going back two int fields.
+        parcel.writeInt(100);
+        parcel.setDataPosition(0);
+
+        assertThrows(IllegalArgumentException.class, () -> new Condition(parcel));
+    }
+
+    @Test
+    @EnableFlags(Flags.FLAG_MODES_API)
+    public void validate_invalidSource_throws() throws Exception {
+        Condition condition = new Condition(Uri.parse("condition"), "Summary", Condition.STATE_TRUE,
+                Condition.SOURCE_SCHEDULE);
+
+        Field typeField = Condition.class.getDeclaredField("source");
+
+        // Reflection on reflection (ugh) to make a final field non-final
+        Field fieldAccessFlagsField = Field.class.getDeclaredField("accessFlags");
+        fieldAccessFlagsField.setAccessible(true);
+        fieldAccessFlagsField.setInt(typeField, typeField.getModifiers() & ~Modifier.FINAL);
+
+        typeField.setInt(condition, 30);
+
+        assertThrows(IllegalArgumentException.class, condition::validate);
+    }
 }
diff --git a/core/tests/coretests/src/android/util/SparseSetArrayTest.java b/core/tests/coretests/src/android/util/SparseSetArrayTest.java
index 1df1090..1c72185 100644
--- a/core/tests/coretests/src/android/util/SparseSetArrayTest.java
+++ b/core/tests/coretests/src/android/util/SparseSetArrayTest.java
@@ -37,6 +37,7 @@
     public final RavenwoodRule mRavenwood = new RavenwoodRule();
 
     @Test
+    @IgnoreUnderRavenwood(reason = "Flaky test, b/315872700")
     public void testAddAll() {
         final SparseSetArray<Integer> sparseSetArray = new SparseSetArray<>();
 
diff --git a/core/tests/coretests/src/com/android/internal/os/LongArrayMultiStateCounterTest.java b/core/tests/coretests/src/com/android/internal/os/LongArrayMultiStateCounterTest.java
index f34b185..c9536b9 100644
--- a/core/tests/coretests/src/com/android/internal/os/LongArrayMultiStateCounterTest.java
+++ b/core/tests/coretests/src/com/android/internal/os/LongArrayMultiStateCounterTest.java
@@ -57,6 +57,16 @@
     }
 
     @Test
+    public void setValue() {
+        LongArrayMultiStateCounter counter = new LongArrayMultiStateCounter(2, 4);
+
+        counter.setValues(0, new long[]{1, 2, 3, 4});
+        counter.setValues(1, new long[]{5, 6, 7, 8});
+        assertCounts(counter, 0, new long[]{1, 2, 3, 4});
+        assertCounts(counter, 1, new long[]{5, 6, 7, 8});
+    }
+
+    @Test
     public void setEnabled() {
         LongArrayMultiStateCounter counter = new LongArrayMultiStateCounter(2, 4);
         counter.setState(0, 1000);
diff --git a/core/tests/utiltests/src/android/util/TimeUtilsTest.java b/core/tests/utiltests/src/android/util/TimeUtilsTest.java
index e8246c8..ac659e1 100644
--- a/core/tests/utiltests/src/android/util/TimeUtilsTest.java
+++ b/core/tests/utiltests/src/android/util/TimeUtilsTest.java
@@ -18,8 +18,12 @@
 
 import static org.junit.Assert.assertEquals;
 
+import android.platform.test.annotations.IgnoreUnderRavenwood;
+import android.platform.test.ravenwood.RavenwoodRule;
+
 import androidx.test.runner.AndroidJUnit4;
 
+import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
@@ -29,6 +33,9 @@
 
 @RunWith(AndroidJUnit4.class)
 public class TimeUtilsTest {
+    @Rule
+    public final RavenwoodRule mRavenwood = new RavenwoodRule();
+
     public static final long SECOND_IN_MILLIS = 1000;
     public static final long MINUTE_IN_MILLIS = SECOND_IN_MILLIS * 60;
     public static final long HOUR_IN_MILLIS = MINUTE_IN_MILLIS * 60;
@@ -78,6 +85,7 @@
     }
 
     @Test
+    @IgnoreUnderRavenwood(reason = "Flaky test, b/315872700")
     public void testDumpTime() {
         assertEquals("2023-01-01 00:00:00.000", runWithPrintWriter((pw) -> {
             TimeUtils.dumpTime(pw, 1672556400000L);
@@ -91,6 +99,7 @@
     }
 
     @Test
+    @IgnoreUnderRavenwood(reason = "Flaky test, b/315872700")
     public void testFormatForLogging() {
         assertEquals("unknown", TimeUtils.formatForLogging(0));
         assertEquals("unknown", TimeUtils.formatForLogging(-1));
@@ -99,6 +108,7 @@
     }
 
     @Test
+    @IgnoreUnderRavenwood(reason = "Flaky test, b/315872700")
     public void testLogTimeOfDay() {
         assertEquals("01-01 00:00:00.000", TimeUtils.logTimeOfDay(1672556400000L));
     }
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/area/WindowAreaComponentImpl.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/area/WindowAreaComponentImpl.java
index 15d14e8..b315f94 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/area/WindowAreaComponentImpl.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/area/WindowAreaComponentImpl.java
@@ -241,7 +241,7 @@
         for (int i = 0; i < displays.length; i++) {
             DisplayAddress.Physical address =
                     (DisplayAddress.Physical) displays[i].getAddress();
-            if (mRearDisplayAddress == address.getPhysicalDisplayId()) {
+            if (address != null && mRearDisplayAddress == address.getPhysicalDisplayId()) {
                 rearDisplayMetrics = new DisplayMetrics();
                 final Display rearDisplay = displays[i];
 
diff --git a/libs/WindowManager/Shell/OWNERS b/libs/WindowManager/Shell/OWNERS
index f0ed6ee..e346b51 100644
--- a/libs/WindowManager/Shell/OWNERS
+++ b/libs/WindowManager/Shell/OWNERS
@@ -1,4 +1,4 @@
 xutan@google.com
 
 # Give submodule owners in shell resource approval
-per-file res*/*/*.xml = atsjenk@google.com, hwwang@google.com, jorgegil@google.com, lbill@google.com, madym@google.com
+per-file res*/*/*.xml = atsjenk@google.com, hwwang@google.com, jorgegil@google.com, lbill@google.com, madym@google.com, nmusgrave@google.com, pbdr@google.com, tkachenkoi@google.com
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/DividerView.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/DividerView.java
index 0f0fbd9c..f801b0d 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/DividerView.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/DividerView.java
@@ -83,6 +83,7 @@
     private int mStartPos;
     private GestureDetector mDoubleTapDetector;
     private boolean mInteractive;
+    private boolean mHideHandle;
     private boolean mSetTouchRegion = true;
     private int mLastDraggingPosition;
     private int mHandleRegionWidth;
@@ -211,11 +212,8 @@
     }
 
     /** Sets up essential dependencies of the divider bar. */
-    public void setup(
-            SplitLayout layout,
-            SplitWindowManager splitWindowManager,
-            SurfaceControlViewHost viewHost,
-            InsetsState insetsState) {
+    public void setup(SplitLayout layout, SplitWindowManager splitWindowManager,
+            SurfaceControlViewHost viewHost, InsetsState insetsState) {
         mSplitLayout = layout;
         mSplitWindowManager = splitWindowManager;
         mViewHost = viewHost;
@@ -277,6 +275,7 @@
                 R.dimen.docked_stack_divider_lift_elevation);
         mDoubleTapDetector = new GestureDetector(getContext(), new DoubleTapListener());
         mInteractive = true;
+        mHideHandle = false;
         setOnTouchListener(this);
         mHandle.setAccessibilityDelegate(mHandleDelegate);
         setWillNotDraw(false);
@@ -469,10 +468,11 @@
     void setInteractive(boolean interactive, boolean hideHandle, String from) {
         if (interactive == mInteractive) return;
         ProtoLog.d(ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN,
-                "Set divider bar %s from %s", interactive ? "interactive" : "non-interactive",
-                from);
+                "Set divider bar %s hide handle=%b from %s",
+                interactive ? "interactive" : "non-interactive", hideHandle, from);
         mInteractive = interactive;
-        if (!mInteractive && hideHandle && mMoving) {
+        mHideHandle = hideHandle;
+        if (!mInteractive && mHideHandle && mMoving) {
             final int position = mSplitLayout.getDividePosition();
             mSplitLayout.flingDividePosition(
                     mLastDraggingPosition,
@@ -482,7 +482,15 @@
             mMoving = false;
         }
         releaseTouching();
-        mHandle.setVisibility(!mInteractive && hideHandle ? View.INVISIBLE : View.VISIBLE);
+        mHandle.setVisibility(!mInteractive && mHideHandle ? View.INVISIBLE : View.VISIBLE);
+    }
+
+    boolean isInteractive() {
+        return mInteractive;
+    }
+
+    boolean isHandleHidden() {
+        return mHideHandle;
     }
 
     private class DoubleTapListener extends GestureDetector.SimpleOnGestureListener {
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 b699533..53caddb 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
@@ -59,6 +59,7 @@
 import androidx.annotation.Nullable;
 
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.protolog.common.ProtoLog;
 import com.android.wm.shell.R;
 import com.android.wm.shell.ShellTaskOrganizer;
 import com.android.wm.shell.animation.Interpolators;
@@ -70,6 +71,7 @@
 import com.android.wm.shell.common.split.SplitScreenConstants.PersistentSnapPosition;
 import com.android.wm.shell.common.split.SplitScreenConstants.SnapPosition;
 import com.android.wm.shell.common.split.SplitScreenConstants.SplitPosition;
+import com.android.wm.shell.protolog.ShellProtoLogGroup;
 
 import java.io.PrintWriter;
 import java.util.function.Consumer;
@@ -420,7 +422,7 @@
     public void init() {
         if (mInitialized) return;
         mInitialized = true;
-        mSplitWindowManager.init(this, mInsetsState);
+        mSplitWindowManager.init(this, mInsetsState, false /* isRestoring */);
         mDisplayImeController.addPositionProcessor(mImePositionProcessor);
     }
 
@@ -442,14 +444,19 @@
     }
 
     /** Releases and re-inflates {@link DividerView} on the root surface. */
-    public void update(SurfaceControl.Transaction t) {
+    public void update(SurfaceControl.Transaction t, boolean resetImePosition) {
         if (!mInitialized) {
             init();
             return;
         }
         mSplitWindowManager.release(t);
-        mImePositionProcessor.reset();
-        mSplitWindowManager.init(this, mInsetsState);
+        if (resetImePosition) {
+            mImePositionProcessor.reset();
+        }
+        mSplitWindowManager.init(this, mInsetsState, true /* isRestoring */);
+        // Update the surface positions again after recreating the divider in case nothing else
+        // triggers it
+        mSplitLayoutHandler.onLayoutPositionChanging(SplitLayout.this);
     }
 
     @Override
@@ -868,6 +875,9 @@
         pw.println(prefix + TAG + ":");
         pw.println(innerPrefix + "mAllowLeftRightSplitInPortrait=" + mAllowLeftRightSplitInPortrait);
         pw.println(innerPrefix + "mIsLeftRightSplit=" + mIsLeftRightSplit);
+        pw.println(innerPrefix + "mFreezeDividerWindow=" + mFreezeDividerWindow);
+        pw.println(innerPrefix + "mDimNonImeSide=" + mDimNonImeSide);
+        pw.println(innerPrefix + "mDividerPosition=" + mDividerPosition);
         pw.println(innerPrefix + "bounds1=" + mBounds1.toShortString());
         pw.println(innerPrefix + "dividerBounds=" + mDividerBounds.toShortString());
         pw.println(innerPrefix + "bounds2=" + mBounds2.toShortString());
@@ -1151,14 +1161,16 @@
             mTargetYOffset = needOffset ? getTargetYOffset() : 0;
 
             if (mTargetYOffset != mLastYOffset) {
+                ProtoLog.v(ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN,
+                        "Split IME animation starting, fromY=%d toY=%d",
+                        mLastYOffset, mTargetYOffset);
                 // Freeze the configuration size with offset to prevent app get a configuration
                 // changed or relaunch. This is required to make sure client apps will calculate
                 // insets properly after layout shifted.
                 if (mTargetYOffset == 0) {
                     mSplitLayoutHandler.setLayoutOffsetTarget(0, 0, SplitLayout.this);
                 } else {
-                    mSplitLayoutHandler.setLayoutOffsetTarget(0, mTargetYOffset - mLastYOffset,
-                            SplitLayout.this);
+                    mSplitLayoutHandler.setLayoutOffsetTarget(0, mTargetYOffset, SplitLayout.this);
                 }
             }
 
@@ -1183,6 +1195,8 @@
         public void onImeEndPositioning(int displayId, boolean cancel,
                 SurfaceControl.Transaction t) {
             if (displayId != mDisplayId || !mHasImeFocus || cancel) return;
+            ProtoLog.v(ShellProtoLogGroup.WM_SHELL_SPLIT_SCREEN,
+                    "Split IME animation ending, canceled=%b", cancel);
             onProgress(1.0f);
             mSplitLayoutHandler.onLayoutPositionChanging(SplitLayout.this);
         }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitWindowManager.java b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitWindowManager.java
index 00361d9..8fb9bda 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitWindowManager.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/common/split/SplitWindowManager.java
@@ -62,6 +62,10 @@
     // Used to "pass" a transaction to WWM.remove so that view removal can be synchronized.
     private SurfaceControl.Transaction mSyncTransaction = null;
 
+    // For saving/restoring state
+    private boolean mLastDividerInteractive = true;
+    private boolean mLastDividerHandleHidden;
+
     public interface ParentContainerCallbacks {
         void attachToParentSurface(SurfaceControl.Builder b);
         void onLeashReady(SurfaceControl leash);
@@ -107,7 +111,7 @@
     }
 
     /** Inflates {@link DividerView} on to the root surface. */
-    void init(SplitLayout splitLayout, InsetsState insetsState) {
+    void init(SplitLayout splitLayout, InsetsState insetsState, boolean isRestoring) {
         if (mDividerView != null || mViewHost != null) {
             throw new UnsupportedOperationException(
                     "Try to inflate divider view again without release first");
@@ -130,6 +134,10 @@
         lp.accessibilityTitle = mContext.getResources().getString(R.string.accessibility_divider);
         mViewHost.setView(mDividerView, lp);
         mDividerView.setup(splitLayout, this, mViewHost, insetsState);
+        if (isRestoring) {
+            mDividerView.setInteractive(mLastDividerInteractive, mLastDividerHandleHidden,
+                    "restore_setup");
+        }
     }
 
     /**
@@ -138,6 +146,8 @@
      */
     void release(@Nullable SurfaceControl.Transaction t) {
         if (mDividerView != null) {
+            mLastDividerInteractive = mDividerView.isInteractive();
+            mLastDividerHandleHidden = mDividerView.isHandleHidden();
             mDividerView = null;
         }
 
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/UserAspectRatioSettingsWindowManager.java b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/UserAspectRatioSettingsWindowManager.java
index ef763ec..afd3b14 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/UserAspectRatioSettingsWindowManager.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/compatui/UserAspectRatioSettingsWindowManager.java
@@ -22,6 +22,7 @@
 import android.annotation.Nullable;
 import android.app.TaskInfo;
 import android.content.Context;
+import android.content.Intent;
 import android.graphics.Rect;
 import android.os.SystemClock;
 import android.view.LayoutInflater;
@@ -227,9 +228,12 @@
     }
 
     private boolean getHasUserAspectRatioSettingsButton(@NonNull TaskInfo taskInfo) {
+        final Intent intent = taskInfo.baseIntent;
         return taskInfo.appCompatTaskInfo.topActivityEligibleForUserAspectRatioButton
                 && (taskInfo.appCompatTaskInfo.topActivityBoundsLetterboxed
                     || taskInfo.appCompatTaskInfo.isUserFullscreenOverrideEnabled)
+                && Intent.ACTION_MAIN.equals(intent.getAction())
+                && intent.hasCategory(Intent.CATEGORY_LAUNCHER)
                 && (!mUserAspectRatioButtonShownChecker.get() || isShowingButton());
     }
 
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/OWNERS b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/OWNERS
index deb7c6d..1385f42 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/OWNERS
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/desktopmode/OWNERS
@@ -1,3 +1,7 @@
 # WM shell sub-module desktop owners
 atsjenk@google.com
+jorgegil@google.com
 madym@google.com
+nmusgrave@google.com
+pbdr@google.com
+tkachenkoi@google.com
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/OWNERS b/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/OWNERS
index a3803ed..8a0eea0 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/OWNERS
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/freeform/OWNERS
@@ -2,3 +2,6 @@
 atsjenk@google.com
 jorgegil@google.com
 madym@google.com
+nmusgrave@google.com
+pbdr@google.com
+tkachenkoi@google.com
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipAnimationController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipAnimationController.java
index f5c01d0..4c47737 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipAnimationController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipAnimationController.java
@@ -769,7 +769,6 @@
                         getSurfaceTransactionHelper().crop(tx, leash, destBounds);
                     }
                     if (mContentOverlay != null) {
-                        mContentOverlay.onAnimationEnd(tx, destBounds);
                         clearContentOverlay();
                     }
                 }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipContentOverlay.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipContentOverlay.java
index a2bd47c..e11e859 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipContentOverlay.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipContentOverlay.java
@@ -67,15 +67,6 @@
     public abstract void onAnimationUpdate(SurfaceControl.Transaction atomicTx,
             Rect currentBounds, float fraction);
 
-    /**
-     * Callback when reaches the end of animation on the internal {@link #mLeash}.
-     * @param atomicTx {@link SurfaceControl.Transaction} to operate, you should not explicitly
-     *                 call apply on this transaction, it should be applied on the caller side.
-     * @param destinationBounds {@link Rect} of the final bounds.
-     */
-    public abstract void onAnimationEnd(SurfaceControl.Transaction atomicTx,
-            Rect destinationBounds);
-
     /** A {@link PipContentOverlay} uses solid color. */
     public static final class PipColorOverlay extends PipContentOverlay {
         private static final String TAG = PipColorOverlay.class.getSimpleName();
@@ -107,11 +98,6 @@
             atomicTx.setAlpha(mLeash, fraction < 0.5f ? 0 : (fraction - 0.5f) * 2);
         }
 
-        @Override
-        public void onAnimationEnd(SurfaceControl.Transaction atomicTx, Rect destinationBounds) {
-            // Do nothing. Color overlay should be fully opaque by now, ready for fade out.
-        }
-
         private float[] getContentOverlayColor(Context context) {
             final TypedArray ta = context.obtainStyledAttributes(new int[] {
                     android.R.attr.colorBackground });
@@ -164,11 +150,6 @@
                 Rect currentBounds, float fraction) {
             // Do nothing. Keep the snapshot till animation ends.
         }
-
-        @Override
-        public void onAnimationEnd(SurfaceControl.Transaction atomicTx, Rect destinationBounds) {
-            // Do nothing. Snapshot overlay should be fully opaque by now, ready for fade out.
-        }
     }
 
     /** A {@link PipContentOverlay} shows app icon on solid color background. */
@@ -255,11 +236,6 @@
         }
 
         @Override
-        public void onAnimationEnd(SurfaceControl.Transaction atomicTx, Rect destinationBounds) {
-            // Do nothing. Icon overlay should be fully opaque by now, ready for fade out.
-        }
-
-        @Override
         public void detach(SurfaceControl.Transaction tx) {
             super.detach(tx);
             if (mBitmap != null && !mBitmap.isRecycled()) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java
index 743b1ea..3635165 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTaskOrganizer.java
@@ -329,15 +329,7 @@
     private @Surface.Rotation int mCurrentRotation;
 
     /**
-     * An optional overlay used to mask content changing between an app in/out of PiP, only set if
-     * {@link PipTransitionState#getInSwipePipToHomeTransition()} is true, only in gesture nav.
-     */
-    @Nullable
-    SurfaceControl mSwipePipToHomeOverlay;
-
-    /**
-     * An optional overlay used to mask content changing between an app in/out of PiP, only set if
-     * {@link PipTransitionState#getInSwipePipToHomeTransition()} is false.
+     * An optional overlay used to mask content changing between an app in/out of PiP.
      */
     @Nullable
     SurfaceControl mPipOverlay;
@@ -480,7 +472,7 @@
             return;
         }
         mPipBoundsState.setBounds(destinationBounds);
-        mSwipePipToHomeOverlay = overlay;
+        mPipOverlay = overlay;
         if (ENABLE_SHELL_TRANSITIONS && overlay != null) {
             // With Shell transition, the overlay was attached to the remote transition leash, which
             // will be removed when the current transition is finished, so we need to reparent it
@@ -892,7 +884,7 @@
         }
 
         final Rect destinationBounds = mPipBoundsState.getBounds();
-        final SurfaceControl swipeToHomeOverlay = mSwipePipToHomeOverlay;
+        final SurfaceControl swipeToHomeOverlay = mPipOverlay;
         final SurfaceControl.Transaction tx = mSurfaceControlTransactionFactory.getTransaction();
         mSurfaceTransactionHelper
                 .resetScale(tx, mLeash, destinationBounds)
@@ -911,7 +903,7 @@
             }
         }, tx);
         mPipTransitionState.setInSwipePipToHomeTransition(false);
-        mSwipePipToHomeOverlay = null;
+        mPipOverlay = null;
     }
 
     private void applyEnterPipSyncTransaction(Rect destinationBounds, Runnable runnable,
@@ -1126,9 +1118,9 @@
         }
 
         clearWaitForFixedRotation();
-        if (mSwipePipToHomeOverlay != null) {
-            removeContentOverlay(mSwipePipToHomeOverlay, null /* callback */);
-            mSwipePipToHomeOverlay = null;
+        if (mPipOverlay != null) {
+            removeContentOverlay(mPipOverlay, null /* callback */);
+            mPipOverlay = null;
         }
         resetShadowRadius();
         mPipTransitionState.setInSwipePipToHomeTransition(false);
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java
index 0f3c162..f5f15d8 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipTransition.java
@@ -465,7 +465,7 @@
                     mSurfaceTransactionHelper.crop(tx, leash, destinationBounds)
                             .resetScale(tx, leash, destinationBounds)
                             .round(tx, leash, true /* applyCornerRadius */);
-                    if (mPipOrganizer.mSwipePipToHomeOverlay != null && !mInitBounds.isEmpty()) {
+                    if (mPipOrganizer.mPipOverlay != null && !mInitBounds.isEmpty()) {
                         // Resetting the scale for pinned task while re-adjusting its crop,
                         // also scales the overlay. So we need to update the overlay leash too.
                         Rect overlayBounds = new Rect(destinationBounds);
@@ -476,7 +476,7 @@
                                 (destinationBounds.width() - overlaySize) / 2,
                                 (destinationBounds.height() - overlaySize) / 2);
                         mSurfaceTransactionHelper.resetScale(tx,
-                                mPipOrganizer.mSwipePipToHomeOverlay, overlayBounds);
+                                mPipOrganizer.mPipOverlay, overlayBounds);
                     }
                 }
                 mInitBounds.setEmpty();
@@ -615,9 +615,9 @@
             }
         }
         // if overlay is present remove it immediately, as exit transition came before it faded out
-        if (mPipOrganizer.mSwipePipToHomeOverlay != null) {
-            startTransaction.remove(mPipOrganizer.mSwipePipToHomeOverlay);
-            clearSwipePipToHomeOverlay();
+        if (mPipOrganizer.mPipOverlay != null) {
+            startTransaction.remove(mPipOrganizer.mPipOverlay);
+            clearPipOverlay();
         }
         if (pipChange == null) {
             ProtoLog.w(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
@@ -1077,7 +1077,7 @@
         if (mFixedRotationState == FIXED_ROTATION_CALLBACK && appBounds != null) {
             mInitBounds.set(appBounds);
         }
-        final SurfaceControl swipePipToHomeOverlay = mPipOrganizer.mSwipePipToHomeOverlay;
+        final SurfaceControl swipePipToHomeOverlay = mPipOrganizer.mPipOverlay;
         if (swipePipToHomeOverlay != null) {
             // Launcher fade in the overlay on top of the fullscreen Task. It is possible we
             // reparent the PIP activity to a new PIP task (in case there are other activities
@@ -1106,7 +1106,7 @@
         sendOnPipTransitionFinished(TRANSITION_DIRECTION_TO_PIP);
         if (swipePipToHomeOverlay != null) {
             mPipOrganizer.fadeOutAndRemoveOverlay(swipePipToHomeOverlay,
-                    this::clearSwipePipToHomeOverlay /* callback */, false /* withStartDelay */);
+                    this::clearPipOverlay /* callback */, false /* withStartDelay */);
         }
         mPipTransitionState.setInSwipePipToHomeTransition(false);
     }
@@ -1250,8 +1250,8 @@
         mPipMenuController.updateMenuBounds(destinationBounds);
     }
 
-    private void clearSwipePipToHomeOverlay() {
-        mPipOrganizer.mSwipePipToHomeOverlay = null;
+    private void clearPipOverlay() {
+        mPipOrganizer.mPipOverlay = null;
     }
 
     @Override
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java
index 0367ba1..d023cea 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/recents/RecentsTransitionHandler.java
@@ -995,16 +995,10 @@
                     t.show(mOpeningTasks.get(i).mTaskSurface);
                 }
                 for (int i = 0; i < mPausingTasks.size(); ++i) {
-                    if (!sendUserLeaveHint && mPausingTasks.get(i).isLeaf()) {
-                        // This means recents is not *actually* finishing, so of course we gotta
-                        // do special stuff in WMCore to accommodate.
-                        wct.setDoNotPip(mPausingTasks.get(i).mToken);
-                    }
-                    // Since we will reparent out of the leashes, pre-emptively hide the child
-                    // surface to match the leash. Otherwise, there will be a flicker before the
-                    // visibility gets committed in Core when using split-screen (in splitscreen,
-                    // the leaf-tasks are not "independent" so aren't hidden by normal setup).
-                    t.hide(mPausingTasks.get(i).mTaskSurface);
+                    cleanUpPausingOrClosingTask(mPausingTasks.get(i), wct, t, sendUserLeaveHint);
+                }
+                for (int i = 0; i < mClosingTasks.size(); ++i) {
+                    cleanUpPausingOrClosingTask(mClosingTasks.get(i), wct, t, sendUserLeaveHint);
                 }
                 if (mPipTransaction != null && sendUserLeaveHint) {
                     SurfaceControl pipLeash = null;
@@ -1053,6 +1047,20 @@
             }
         }
 
+        private void cleanUpPausingOrClosingTask(TaskState task, WindowContainerTransaction wct,
+                SurfaceControl.Transaction finishTransaction, boolean sendUserLeaveHint) {
+            if (!sendUserLeaveHint && task.isLeaf()) {
+                // This means recents is not *actually* finishing, so of course we gotta
+                // do special stuff in WMCore to accommodate.
+                wct.setDoNotPip(task.mToken);
+            }
+            // Since we will reparent out of the leashes, pre-emptively hide the child
+            // surface to match the leash. Otherwise, there will be a flicker before the
+            // visibility gets committed in Core when using split-screen (in splitscreen,
+            // the leaf-tasks are not "independent" so aren't hidden by normal setup).
+            finishTransaction.hide(task.mTaskSurface);
+        }
+
         @Override
         public void setDeferCancelUntilNextTransition(boolean defer, boolean screenshot) {
         }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
index 449bef5..96e57e7 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/splitscreen/StageCoordinator.java
@@ -223,6 +223,7 @@
     private boolean mExitSplitScreenOnHide;
     private boolean mIsDividerRemoteAnimating;
     private boolean mIsDropEntering;
+    private boolean mSkipEvictingMainStageChildren;
     private boolean mIsExiting;
     private boolean mIsRootTranslucent;
     @VisibleForTesting
@@ -468,6 +469,7 @@
         }
         // Due to drag already pip task entering split by this method so need to reset flag here.
         mIsDropEntering = false;
+        mSkipEvictingMainStageChildren = false;
         return true;
     }
 
@@ -572,6 +574,15 @@
             return;
         }
 
+        // Don't evict the main stage children as this can race and happen after the activity is
+        // started into that stage
+        if (!isSplitScreenVisible()) {
+            mSkipEvictingMainStageChildren = true;
+            // Starting the split task without evicting children will bring the single root task
+            // container forward, so ensure that we hide the divider before we start animate it
+            setDividerVisibility(false, null);
+        }
+
         // If split screen is not activated, we're expecting to open a pair of apps to split.
         final int extraTransitType = mMainStage.isActive()
                 ? TRANSIT_SPLIT_SCREEN_OPEN_TO_SIDE : TRANSIT_SPLIT_SCREEN_PAIR_OPEN;
@@ -600,6 +611,15 @@
             return;
         }
 
+        // Don't evict the main stage children as this can race and happen after the activity is
+        // started into that stage
+        if (!isSplitScreenVisible()) {
+            mSkipEvictingMainStageChildren = true;
+            // Starting the split task without evicting children will bring the single root task
+            // container forward, so ensure that we hide the divider before we start animate it
+            setDividerVisibility(false, null);
+        }
+
         // If split screen is not activated, we're expecting to open a pair of apps to split.
         final int extraTransitType = mMainStage.isActive()
                 ? TRANSIT_SPLIT_SCREEN_OPEN_TO_SIDE : TRANSIT_SPLIT_SCREEN_PAIR_OPEN;
@@ -1618,7 +1638,7 @@
             // Ensure to evict old splitting tasks because the new split pair might be composed by
             // one of the splitting tasks, evicting the task when finishing entering transition
             // won't guarantee to put the task to the indicated new position.
-            if (!mIsDropEntering) {
+            if (!mSkipEvictingMainStageChildren) {
                 mMainStage.evictAllChildren(wct);
             }
             mMainStage.reparentTopTask(wct);
@@ -1666,7 +1686,7 @@
     }
 
     void finishEnterSplitScreen(SurfaceControl.Transaction finishT) {
-        mSplitLayout.update(finishT);
+        mSplitLayout.update(finishT, true /* resetImePosition */);
         mMainStage.getSplitDecorManager().inflate(mContext, mMainStage.mRootLeash,
                 getMainStageBounds());
         mSideStage.getSplitDecorManager().inflate(mContext, mSideStage.mRootLeash,
@@ -1680,6 +1700,7 @@
         finishT.show(mRootTaskLeash);
         setSplitsVisible(true);
         mIsDropEntering = false;
+        mSkipEvictingMainStageChildren = false;
         mSplitRequest = null;
         updateRecentTasksSplitPair();
         if (!mLogger.hasStartedSession()) {
@@ -1860,9 +1881,10 @@
                 && mSplitLayout.updateConfiguration(mRootTaskInfo.configuration)
                 && mMainStage.isActive()) {
             // Clear the divider remote animating flag as the divider will be re-rendered to apply
-            // the new rotation config.
+            // the new rotation config.  Don't reset the IME state since those updates are not in
+            // sync with task info changes.
             mIsDividerRemoteAnimating = false;
-            mSplitLayout.update(null /* t */);
+            mSplitLayout.update(null /* t */, false /* resetImePosition */);
             onLayoutSizeChanged(mSplitLayout);
         }
     }
@@ -1928,6 +1950,7 @@
                 if (mIsDropEntering) {
                     updateSurfaceBounds(mSplitLayout, t, false /* applyResizingOffset */);
                     mIsDropEntering = false;
+                    mSkipEvictingMainStageChildren = false;
                 } else {
                     mShowDecorImmediately = true;
                     mSplitLayout.flingDividerToCenter();
@@ -2122,6 +2145,7 @@
                 if (mIsDropEntering) {
                     updateSurfaceBounds(mSplitLayout, t, false /* applyResizingOffset */);
                     mIsDropEntering = false;
+                    mSkipEvictingMainStageChildren = false;
                 } else {
                     mShowDecorImmediately = true;
                     mSplitLayout.flingDividerToCenter();
@@ -2325,7 +2349,7 @@
      */
     public void updateSurfaces(SurfaceControl.Transaction transaction) {
         updateSurfaceBounds(mSplitLayout, transaction, /* applyResizingOffset */ false);
-        mSplitLayout.update(transaction);
+        mSplitLayout.update(transaction, true /* resetImePosition */);
     }
 
     private void onDisplayChange(int displayId, int fromRotation, int toRotation,
@@ -2598,7 +2622,9 @@
                 final TransitionInfo.Change change = info.getChanges().get(iC);
                 if (change.getMode() == TRANSIT_CHANGE
                         && (change.getFlags() & FLAG_IS_DISPLAY) != 0) {
-                    mSplitLayout.update(startTransaction);
+                    // Don't reset the IME state since those updates are not in sync with the
+                    // display change transition
+                    mSplitLayout.update(startTransaction, false /* resetImePosition */);
                 }
 
                 if (mMixedHandler.isEnteringPip(change, transitType)) {
@@ -2699,7 +2725,7 @@
                     startTransaction, finishTransaction, finishCallback)) {
                 if (mSplitTransitions.isPendingResize(transition)) {
                     // Only need to update in resize because divider exist before transition.
-                    mSplitLayout.update(startTransaction);
+                    mSplitLayout.update(startTransaction, true /* resetImePosition */);
                     startTransaction.apply();
                 }
                 return true;
@@ -3242,6 +3268,7 @@
     public void onDroppedToSplit(@SplitPosition int position, InstanceId dragSessionId) {
         if (!isSplitScreenVisible()) {
             mIsDropEntering = true;
+            mSkipEvictingMainStageChildren = true;
         }
         if (!isSplitScreenVisible() && !ENABLE_SHELL_TRANSITIONS) {
             // If split running background, exit split first.
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskViewTransitions.java b/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskViewTransitions.java
index e03f825..34c015f 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskViewTransitions.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/taskview/TaskViewTransitions.java
@@ -330,6 +330,11 @@
                     continue;
                 }
                 if (isHide) {
+                    if (pending.mType == TRANSIT_TO_BACK) {
+                        // TO_BACK is only used when setting the task view visibility immediately,
+                        // so in that case we can also hide the surface immediately
+                        startTransaction.hide(chg.getLeash());
+                    }
                     tv.prepareHideAnimation(finishTransaction);
                 } else {
                     tv.prepareCloseAnimation();
diff --git a/libs/WindowManager/Shell/tests/OWNERS b/libs/WindowManager/Shell/tests/OWNERS
index deebad5..d718e15 100644
--- a/libs/WindowManager/Shell/tests/OWNERS
+++ b/libs/WindowManager/Shell/tests/OWNERS
@@ -9,3 +9,6 @@
 chenghsiuchang@google.com
 atsjenk@google.com
 jorgegil@google.com
+nmusgrave@google.com
+pbdr@google.com
+tkachenkoi@google.com
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/split/DividerViewTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/split/DividerViewTest.java
index 145c8f0..636c632 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/split/DividerViewTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/split/DividerViewTest.java
@@ -69,7 +69,7 @@
         SplitWindowManager splitWindowManager = new SplitWindowManager("TestSplitWindowManager",
                 mContext,
                 configuration, mCallbacks);
-        splitWindowManager.init(mSplitLayout, new InsetsState());
+        splitWindowManager.init(mSplitLayout, new InsetsState(), false /* isRestoring */);
         mDividerView = spy((DividerView) splitWindowManager.getDividerView());
     }
 
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/split/SplitWindowManagerTests.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/split/SplitWindowManagerTests.java
index 2e5078d..150aa13 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/split/SplitWindowManagerTests.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/common/split/SplitWindowManagerTests.java
@@ -59,7 +59,7 @@
     @Test
     @UiThreadTest
     public void testInitRelease() {
-        mSplitWindowManager.init(mSplitLayout, new InsetsState());
+        mSplitWindowManager.init(mSplitLayout, new InsetsState(), false /* isRestoring */);
         assertThat(mSplitWindowManager.getSurfaceControl()).isNotNull();
         mSplitWindowManager.release(null /* t */);
         assertThat(mSplitWindowManager.getSurfaceControl()).isNull();
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/UserAspectRatioSettingsWindowManagerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/UserAspectRatioSettingsWindowManagerTest.java
index 0652939..9fe2cb1 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/UserAspectRatioSettingsWindowManagerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/compatui/UserAspectRatioSettingsWindowManagerTest.java
@@ -16,6 +16,9 @@
 
 package com.android.wm.shell.compatui;
 
+import static android.content.Intent.ACTION_MAIN;
+import static android.content.Intent.CATEGORY_LAUNCHER;
+import static android.hardware.usb.UsbManager.ACTION_USB_STATE;
 import static android.view.WindowInsets.Type.navigationBars;
 
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
@@ -33,6 +36,7 @@
 import android.app.ActivityManager;
 import android.app.TaskInfo;
 import android.content.ComponentName;
+import android.content.Intent;
 import android.content.res.Configuration;
 import android.graphics.Rect;
 import android.testing.AndroidTestingRunner;
@@ -108,7 +112,7 @@
         MockitoAnnotations.initMocks(this);
         mExecutor = new TestShellExecutor();
         mTaskInfo = createTaskInfo(/* eligibleForUserAspectRatioButton= */
-                false, /* topActivityBoundsLetterboxed */ true);
+                false, /* topActivityBoundsLetterboxed */ true, ACTION_MAIN, CATEGORY_LAUNCHER);
         mWindowManager = new UserAspectRatioSettingsWindowManager(mContext, mTaskInfo,
                 mSyncTransactionQueue, mTaskListener, new DisplayLayout(), new CompatUIHintsState(),
                 mOnUserAspectRatioSettingsButtonClicked, mExecutor, flags -> 0,
@@ -179,7 +183,7 @@
         // No diff
         clearInvocations(mWindowManager);
         TaskInfo taskInfo = createTaskInfo(/* eligibleForUserAspectRatioButton= */
-                true, /* topActivityBoundsLetterboxed */ true);
+                true, /* topActivityBoundsLetterboxed */ true, ACTION_MAIN, CATEGORY_LAUNCHER);
         assertTrue(mWindowManager.updateCompatInfo(taskInfo, mTaskListener, /* canShow= */ true));
 
         verify(mWindowManager, never()).updateSurfacePosition();
@@ -200,7 +204,24 @@
         clearInvocations(mWindowManager);
         clearInvocations(mLayout);
         taskInfo = createTaskInfo(/* eligibleForUserAspectRatioButton= */
-                false, /* topActivityBoundsLetterboxed */ true);
+                false, /* topActivityBoundsLetterboxed */ true, ACTION_MAIN, CATEGORY_LAUNCHER);
+        assertFalse(
+                mWindowManager.updateCompatInfo(taskInfo, newTaskListener, /* canShow= */ true));
+        verify(mWindowManager).release();
+
+        // Recreate button
+        clearInvocations(mWindowManager);
+        taskInfo = createTaskInfo(/* eligibleForUserAspectRatioButton= */
+                true, /* topActivityBoundsLetterboxed */ true, ACTION_MAIN, CATEGORY_LAUNCHER);
+        assertTrue(mWindowManager.updateCompatInfo(taskInfo, newTaskListener, /* canShow= */ true));
+
+        verify(mWindowManager).release();
+        verify(mWindowManager).createLayout(/* canShow= */ true);
+
+        // Change has no launcher category and is not main intent, dispose the component
+        clearInvocations(mWindowManager);
+        taskInfo = createTaskInfo(/* eligibleForUserAspectRatioButton= */
+                true, /* topActivityBoundsLetterboxed */ true, ACTION_USB_STATE, "");
         assertFalse(
                 mWindowManager.updateCompatInfo(taskInfo, newTaskListener, /* canShow= */ true));
         verify(mWindowManager).release();
@@ -217,7 +238,7 @@
         // inflated
         clearInvocations(mWindowManager);
         TaskInfo taskInfo = createTaskInfo(/* eligibleForUserAspectRatioButton= */
-                false, /* topActivityBoundsLetterboxed */ true);
+                false, /* topActivityBoundsLetterboxed */ true, ACTION_MAIN, CATEGORY_LAUNCHER);
         mWindowManager.updateCompatInfo(taskInfo, mTaskListener, /* canShow= */ true);
 
         verify(mWindowManager, never()).inflateLayout();
@@ -225,7 +246,7 @@
         // Change topActivityInSizeCompat to true and pass canShow true, layout should be inflated.
         clearInvocations(mWindowManager);
         taskInfo = createTaskInfo(/* eligibleForUserAspectRatioButton= */
-                true, /* topActivityBoundsLetterboxed */ true);
+                true, /* topActivityBoundsLetterboxed */ true, ACTION_MAIN, CATEGORY_LAUNCHER);
         mWindowManager.updateCompatInfo(taskInfo, mTaskListener, /* canShow= */ true);
 
         verify(mWindowManager).inflateLayout();
@@ -304,7 +325,7 @@
         clearInvocations(mWindowManager);
         spyOn(mWindowManager);
         TaskInfo taskInfo = createTaskInfo(/* eligibleForUserAspectRatioButton= */
-                true, /* topActivityBoundsLetterboxed */ true);
+                true, /* topActivityBoundsLetterboxed */ true, ACTION_MAIN, CATEGORY_LAUNCHER);
 
         // User aspect ratio settings button has not yet been shown.
         doReturn(false).when(mUserAspectRatioButtonShownChecker).get();
@@ -378,7 +399,7 @@
     }
 
     private static TaskInfo createTaskInfo(boolean eligibleForUserAspectRatioButton,
-            boolean topActivityBoundsLetterboxed) {
+            boolean topActivityBoundsLetterboxed, String action, String category) {
         ActivityManager.RunningTaskInfo taskInfo = new ActivityManager.RunningTaskInfo();
         taskInfo.taskId = TASK_ID;
         taskInfo.appCompatTaskInfo.topActivityEligibleForUserAspectRatioButton =
@@ -386,6 +407,7 @@
         taskInfo.appCompatTaskInfo.topActivityBoundsLetterboxed = topActivityBoundsLetterboxed;
         taskInfo.configuration.uiMode &= ~Configuration.UI_MODE_TYPE_DESK;
         taskInfo.realActivity = new ComponentName("com.mypackage.test", "TestActivity");
+        taskInfo.baseIntent = new Intent(action).addCategory(category);
         return taskInfo;
     }
 }
diff --git a/libs/input/tests/Android.bp b/libs/input/tests/Android.bp
index 8445032..69718a6 100644
--- a/libs/input/tests/Android.bp
+++ b/libs/input/tests/Android.bp
@@ -43,12 +43,15 @@
     },
     shared_libs: [
         "libandroid_runtime",
+        "libbase",
+        "libinput",
         "libinputservice",
         "libhwui",
         "libgui",
         "libutils",
     ],
     static_libs: [
+        "libflagtest",
         "libgmock",
         "libgtest",
     ],
diff --git a/libs/input/tests/PointerController_test.cpp b/libs/input/tests/PointerController_test.cpp
index d9efd3c..adfa91e 100644
--- a/libs/input/tests/PointerController_test.cpp
+++ b/libs/input/tests/PointerController_test.cpp
@@ -14,6 +14,8 @@
  * limitations under the License.
  */
 
+#include <com_android_input_flags.h>
+#include <flag_macros.h>
 #include <gmock/gmock.h>
 #include <gtest/gtest.h>
 #include <input/PointerController.h>
@@ -28,6 +30,8 @@
 
 namespace android {
 
+namespace input_flags = com::android::input::flags;
+
 enum TestCursorType {
     CURSOR_TYPE_DEFAULT = 0,
     CURSOR_TYPE_HOVER,
@@ -261,7 +265,20 @@
     mPointerController->reloadPointerResources();
 }
 
-TEST_F(PointerControllerTest, updatePointerIcon) {
+TEST_F_WITH_FLAGS(PointerControllerTest, setPresentationBeforeDisplayViewportDoesNotLoadResources,
+                  REQUIRES_FLAGS_ENABLED(ACONFIG_FLAG(input_flags, enable_pointer_choreographer))) {
+    // Setting the presentation mode before a display viewport is set will not load any resources.
+    mPointerController->setPresentation(PointerController::Presentation::POINTER);
+    ASSERT_TRUE(mPolicy->noResourcesAreLoaded());
+
+    // When the display is set, then the resources are loaded.
+    ensureDisplayViewportIsSet();
+    ASSERT_TRUE(mPolicy->allResourcesAreLoaded());
+}
+
+TEST_F_WITH_FLAGS(PointerControllerTest, updatePointerIcon,
+                  REQUIRES_FLAGS_DISABLED(ACONFIG_FLAG(input_flags,
+                                                       enable_pointer_choreographer))) {
     ensureDisplayViewportIsSet();
     mPointerController->setPresentation(PointerController::Presentation::POINTER);
     mPointerController->unfade(PointerController::Transition::IMMEDIATE);
@@ -277,6 +294,24 @@
     mPointerController->updatePointerIcon(static_cast<PointerIconStyle>(type));
 }
 
+TEST_F_WITH_FLAGS(PointerControllerTest, updatePointerIconWithChoreographer,
+                  REQUIRES_FLAGS_ENABLED(ACONFIG_FLAG(input_flags, enable_pointer_choreographer))) {
+    // When PointerChoreographer is enabled, the presentation mode is set before the viewport.
+    mPointerController->setPresentation(PointerController::Presentation::POINTER);
+    ensureDisplayViewportIsSet();
+    mPointerController->unfade(PointerController::Transition::IMMEDIATE);
+
+    int32_t type = CURSOR_TYPE_ADDITIONAL;
+    std::pair<float, float> hotspot = getHotSpotCoordinatesForType(type);
+    EXPECT_CALL(*mPointerSprite, setVisible(true));
+    EXPECT_CALL(*mPointerSprite, setAlpha(1.0f));
+    EXPECT_CALL(*mPointerSprite,
+                setIcon(AllOf(Field(&SpriteIcon::style, static_cast<PointerIconStyle>(type)),
+                              Field(&SpriteIcon::hotSpotX, hotspot.first),
+                              Field(&SpriteIcon::hotSpotY, hotspot.second))));
+    mPointerController->updatePointerIcon(static_cast<PointerIconStyle>(type));
+}
+
 TEST_F(PointerControllerTest, setCustomPointerIcon) {
     ensureDisplayViewportIsSet();
     mPointerController->unfade(PointerController::Transition::IMMEDIATE);
diff --git a/media/OWNERS b/media/OWNERS
index 4a6648e..994a7b8 100644
--- a/media/OWNERS
+++ b/media/OWNERS
@@ -21,7 +21,6 @@
 include platform/frameworks/av:/media/janitors/media_solutions_OWNERS
 
 # SEO
-sungsoo@google.com
 
 # SEA/KIR/BVE
 jtinker@google.com
diff --git a/media/java/android/media/OWNERS b/media/java/android/media/OWNERS
index bbe5e06..058c5be 100644
--- a/media/java/android/media/OWNERS
+++ b/media/java/android/media/OWNERS
@@ -2,7 +2,6 @@
 fgoldfain@google.com
 elaurent@google.com
 lajos@google.com
-sungsoo@google.com
 jmtrivi@google.com
 
 # go/android-fwk-media-solutions for info on areas of ownership.
diff --git a/media/java/android/media/tv/ITvInputManager.aidl b/media/java/android/media/tv/ITvInputManager.aidl
index 901ea46..a8ffd2b 100644
--- a/media/java/android/media/tv/ITvInputManager.aidl
+++ b/media/java/android/media/tv/ITvInputManager.aidl
@@ -110,6 +110,10 @@
     void pauseRecording(in IBinder sessionToken, in Bundle params, int userId);
     void resumeRecording(in IBinder sessionToken, in Bundle params, int userId);
 
+    // For playback control
+    void startPlayback(in IBinder sessionToken, int userId);
+    void stopPlayback(in IBinder sessionToken, int mode, int userId);
+
     // For broadcast info
     void requestBroadcastInfo(in IBinder sessionToken, in BroadcastInfoRequest request, int userId);
     void removeBroadcastInfo(in IBinder sessionToken, int id, int userId);
diff --git a/media/java/android/media/tv/ITvInputSession.aidl b/media/java/android/media/tv/ITvInputSession.aidl
index 5246f5c4..e37ee6e 100644
--- a/media/java/android/media/tv/ITvInputSession.aidl
+++ b/media/java/android/media/tv/ITvInputSession.aidl
@@ -63,6 +63,9 @@
     void timeShiftSetMode(int mode);
     void timeShiftEnablePositionTracking(boolean enable);
 
+    void startPlayback();
+    void stopPlayback(int mode);
+
     // For the recording session
     void startRecording(in Uri programUri, in Bundle params);
     void stopRecording();
diff --git a/media/java/android/media/tv/ITvInputSessionWrapper.java b/media/java/android/media/tv/ITvInputSessionWrapper.java
index d749b91..ae3ee65 100644
--- a/media/java/android/media/tv/ITvInputSessionWrapper.java
+++ b/media/java/android/media/tv/ITvInputSessionWrapper.java
@@ -79,6 +79,8 @@
     private static final int DO_TIME_SHIFT_SET_MODE = 30;
     private static final int DO_SET_TV_MESSAGE_ENABLED = 31;
     private static final int DO_NOTIFY_TV_MESSAGE = 32;
+    private static final int DO_STOP_PLAYBACK = 33;
+    private static final int DO_START_PLAYBACK = 34;
 
     private final boolean mIsRecordingSession;
     private final HandlerCaller mCaller;
@@ -286,6 +288,14 @@
                 mTvInputSessionImpl.onTvMessageReceived((Integer) args.arg1, (Bundle) args.arg2);
                 break;
             }
+            case DO_STOP_PLAYBACK: {
+                mTvInputSessionImpl.stopPlayback(msg.arg1);
+                break;
+            }
+            case DO_START_PLAYBACK: {
+                mTvInputSessionImpl.startPlayback();
+                break;
+            }
             default: {
                 Log.w(TAG, "Unhandled message code: " + msg.what);
                 break;
@@ -483,6 +493,17 @@
                 enabled));
     }
 
+    @Override
+    public void stopPlayback(int mode) {
+        mCaller.executeOrSendMessage(mCaller.obtainMessageO(DO_STOP_PLAYBACK, mode));
+    }
+
+    @Override
+    public void startPlayback() {
+        mCaller.executeOrSendMessage(mCaller.obtainMessage(DO_START_PLAYBACK));
+    }
+
+
     private final class TvInputEventReceiver extends InputEventReceiver {
         TvInputEventReceiver(InputChannel inputChannel, Looper looper) {
             super(inputChannel, looper);
diff --git a/media/java/android/media/tv/TvInputManager.java b/media/java/android/media/tv/TvInputManager.java
index 631ab9a..c685a5a 100644
--- a/media/java/android/media/tv/TvInputManager.java
+++ b/media/java/android/media/tv/TvInputManager.java
@@ -339,6 +339,14 @@
      */
     public static final int VIDEO_UNAVAILABLE_REASON_CAS_UNKNOWN = VIDEO_UNAVAILABLE_REASON_END;
 
+    /**
+     * Reason for {@link TvInputService.Session#notifyVideoUnavailable(int)} and
+     * {@link TvView.TvInputCallback#onVideoUnavailable(String, int)}: Video is unavailable because
+     * it has been stopped by stopPlayback.
+     * @hide
+     */
+    public static final int VIDEO_UNAVAILABLE_REASON_STOPPED = 19;
+
     /** @hide */
     @Retention(RetentionPolicy.SOURCE)
     @IntDef({TIME_SHIFT_STATUS_UNKNOWN, TIME_SHIFT_STATUS_UNSUPPORTED,
@@ -3302,6 +3310,30 @@
             }
         }
 
+        void stopPlayback(int mode) {
+            if (mToken == null) {
+                Log.w(TAG, "The session has been already released");
+                return;
+            }
+            try {
+                mService.stopPlayback(mToken, mode, mUserId);
+            } catch (RemoteException e) {
+                throw e.rethrowFromSystemServer();
+            }
+        }
+
+        void startPlayback() {
+            if (mToken == null) {
+                Log.w(TAG, "The session has been already released");
+                return;
+            }
+            try {
+                mService.startPlayback(mToken, mUserId);
+            } catch (RemoteException e) {
+                throw e.rethrowFromSystemServer();
+            }
+        }
+
         /**
          * Sends TV messages to the service for testing purposes
          */
diff --git a/media/java/android/media/tv/TvInputService.java b/media/java/android/media/tv/TvInputService.java
index 720d9a6..55fa517 100644
--- a/media/java/android/media/tv/TvInputService.java
+++ b/media/java/android/media/tv/TvInputService.java
@@ -34,6 +34,7 @@
 import android.hardware.hdmi.HdmiDeviceInfo;
 import android.media.AudioPresentation;
 import android.media.PlaybackParams;
+import android.media.tv.interactive.TvInteractiveAppService;
 import android.net.Uri;
 import android.os.AsyncTask;
 import android.os.Build;
@@ -1531,6 +1532,32 @@
         }
 
         /**
+         * Called when the application requests playback of the Audio, Video, and CC streams to be
+         * stopped, but the metadata should continue to be filtered.
+         *
+         * <p>The metadata that will continue to be filtered includes the PSI
+         * (Program specific information) and SI (Service Information), part of ISO/IEC 13818-1.
+         *
+         * <p> Note that this is different form {@link #timeShiftPause()} as should release the
+         * stream, making it impossible to resume from this position again.
+         * @param mode
+         * @hide
+         */
+        public void onStopPlayback(@TvInteractiveAppService.PlaybackCommandStopMode int mode) {
+        }
+
+        /**
+         * Starts playback of the Audio, Video, and CC streams.
+         *
+         * <p> Note that this is different form {@link #timeShiftResume()} as this is intended to be
+         * used after stopping playback. This is used to restart playback from the current position
+         * in the live broadcast.
+         * @hide
+         */
+        public void onStartPlayback() {
+        }
+
+        /**
          * Called when the application requests to play a given recorded TV program.
          *
          * @param recordedProgramUri The URI of a recorded TV program.
@@ -1993,6 +2020,20 @@
         }
 
         /**
+         * Calls {@link #onStopPlayback(int)}.
+         */
+        void stopPlayback(int mode) {
+            onStopPlayback(mode);
+        }
+
+        /**
+         * Calls {@link #onStartPlayback()}.
+         */
+        void startPlayback() {
+            onStartPlayback();
+        }
+
+        /**
          * Calls {@link #onTimeShiftPlay(Uri)}.
          */
         void timeShiftPlay(Uri recordedProgramUri) {
diff --git a/media/java/android/media/tv/TvView.java b/media/java/android/media/tv/TvView.java
index 196b5c3..233f966 100644
--- a/media/java/android/media/tv/TvView.java
+++ b/media/java/android/media/tv/TvView.java
@@ -37,6 +37,7 @@
 import android.media.tv.TvInputManager.Session;
 import android.media.tv.TvInputManager.Session.FinishedInputEventCallback;
 import android.media.tv.TvInputManager.SessionCallback;
+import android.media.tv.interactive.TvInteractiveAppService;
 import android.net.Uri;
 import android.os.Bundle;
 import android.os.Handler;
@@ -643,6 +644,35 @@
         }
     }
 
+    /**
+     * Stops playback of the Audio, Video, and CC streams, but continue filtering the metadata.
+     *
+     * <p>The metadata that will continue to be filtered includes the PSI
+     * (Program specific information) and SI (Service Information), part of ISO/IEC 13818-1.
+     *
+     * <p> Note that this is different form {@link #timeShiftPause()} as this completely drops
+     * the stream, making it impossible to resume from this position again.
+     * @hide
+     */
+    public void stopPlayback(@TvInteractiveAppService.PlaybackCommandStopMode int mode) {
+        if (mSession != null) {
+            mSession.stopPlayback(mode);
+        }
+    }
+
+    /**
+     * Starts playback of the Audio, Video, and CC streams.
+     *
+     * <p> Note that this is different form {@link #timeShiftResume()} as this is intended to be
+     * used after stopping playback. This is used to restart playback from the current position
+     * in the live broadcast.
+     * @hide
+     */
+    public void startPlayback() {
+        if (mSession != null) {
+            mSession.startPlayback();
+        }
+    }
 
     /**
      * Sends TV messages to the session for testing purposes
diff --git a/packages/CompanionDeviceManager/res/values/strings.xml b/packages/CompanionDeviceManager/res/values/strings.xml
index 281eba6..6019aa8 100644
--- a/packages/CompanionDeviceManager/res/values/strings.xml
+++ b/packages/CompanionDeviceManager/res/values/strings.xml
@@ -156,7 +156,7 @@
     <string name="permission_storage">Photos and media</string>
 
     <!-- Notification permission will be granted of corresponding profile [CHAR LIMIT=30] -->
-    <string name="permission_notification">Notifications</string>
+    <string name="permission_notifications">Notifications</string>
 
     <!-- Apps permission will be granted of corresponding profile [CHAR LIMIT=30] -->
     <string name="permission_app_streaming">Apps</string>
@@ -165,28 +165,31 @@
     <string name="permission_nearby_device_streaming">Streaming</string>
 
     <!-- Description of phone permission of corresponding profile [CHAR LIMIT=NONE] -->
-    <string name="permission_phone_summary">Can make and manage phone calls</string>
+    <string name="permission_phone_summary">Make and manage phone calls</string>
 
     <!-- Description of Call logs permission of corresponding profile [CHAR LIMIT=NONE] -->
-    <string name="permission_call_logs_summary">Can read and write phone call log</string>
+    <string name="permission_call_logs_summary">Read and write phone call log</string>
 
     <!-- Description of SMS permission of corresponding profile [CHAR LIMIT=NONE] -->
-    <string name="permission_sms_summary">Can send and view SMS messages</string>
+    <string name="permission_sms_summary">Send and view SMS messages</string>
 
     <!-- Description of contacts permission of corresponding profile [CHAR LIMIT=NONE] -->
-    <string name="permission_contacts_summary">Can access your contacts</string>
+    <string name="permission_contacts_summary">Access your contacts</string>
 
     <!-- Description of calendar permission of corresponding profile [CHAR LIMIT=NONE] -->
-    <string name="permission_calendar_summary">Can access your calendar</string>
+    <string name="permission_calendar_summary">Access your calendar</string>
 
     <!-- Description of microphone permission of corresponding profile [CHAR LIMIT=NONE] -->
-    <string name="permission_microphone_summary">Can record audio</string>
+    <string name="permission_microphone_summary">Record audio</string>
 
     <!-- Description of nearby devices' permission of corresponding profile [CHAR LIMIT=NONE] -->
-    <string name="permission_nearby_devices_summary">Can find, connect to, and determine the relative position of nearby devices</string>
+    <string name="permission_nearby_devices_summary">Find, connect to, and determine the relative position of nearby devices</string>
 
-    <!-- Description of notification permission of corresponding profile [CHAR LIMIT=NONE] -->
-    <string name="permission_notification_summary">Can read all notifications, including information like contacts, messages, and photos</string>
+    <!-- Description of NLA (notification listener access) of corresponding profile [CHAR LIMIT=NONE] -->
+    <string name="permission_notification_listener_access_summary">Read all notifications, including information like contacts, messages, and photos</string>
+
+    <!-- Description of NLA & POST_NOTIFICATION of corresponding profile [CHAR LIMIT=NONE] -->
+    <string name="permission_notifications_summary">\u2022 Read all notifications, including info like contacts, messages, and photos&lt;br/>\u2022 Send notifications&lt;br/>&lt;br/>You can manage this app\'s ability to read and send notifications anytime in Settings > Notifications.</string>
 
     <!-- Description of app streaming permission of corresponding profile [CHAR LIMIT=NONE] -->
     <string name="permission_app_streaming_summary">Stream your phone\u2019s apps</string>
diff --git a/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceActivity.java b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceActivity.java
index 97016f5..66282dc 100644
--- a/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceActivity.java
+++ b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceActivity.java
@@ -27,13 +27,13 @@
 
 import static com.android.companiondevicemanager.CompanionDeviceDiscoveryService.DiscoveryState;
 import static com.android.companiondevicemanager.CompanionDeviceDiscoveryService.DiscoveryState.FINISHED_TIMEOUT;
-import static com.android.companiondevicemanager.CompanionDeviceResources.PERMISSION_TYPES;
-import static com.android.companiondevicemanager.CompanionDeviceResources.PROFILES_NAME;
-import static com.android.companiondevicemanager.CompanionDeviceResources.PROFILE_ICON;
-import static com.android.companiondevicemanager.CompanionDeviceResources.SUMMARIES;
+import static com.android.companiondevicemanager.CompanionDeviceResources.PROFILE_PERMISSIONS;
+import static com.android.companiondevicemanager.CompanionDeviceResources.PROFILE_NAMES;
+import static com.android.companiondevicemanager.CompanionDeviceResources.PROFILE_ICONS;
+import static com.android.companiondevicemanager.CompanionDeviceResources.PROFILE_SUMMARIES;
 import static com.android.companiondevicemanager.CompanionDeviceResources.SUPPORTED_PROFILES;
 import static com.android.companiondevicemanager.CompanionDeviceResources.SUPPORTED_SELF_MANAGED_PROFILES;
-import static com.android.companiondevicemanager.CompanionDeviceResources.TITLES;
+import static com.android.companiondevicemanager.CompanionDeviceResources.PROFILE_TITLES;
 import static com.android.companiondevicemanager.Utils.getApplicationLabel;
 import static com.android.companiondevicemanager.Utils.getHtmlFromResources;
 import static com.android.companiondevicemanager.Utils.getIcon;
@@ -482,7 +482,7 @@
             return;
         }
 
-        title = getHtmlFromResources(this, TITLES.get(deviceProfile), deviceName);
+        title = getHtmlFromResources(this, PROFILE_TITLES.get(deviceProfile), deviceName);
         setupPermissionList(deviceProfile);
 
         // Summary is not needed for selfManaged dialog.
@@ -525,7 +525,16 @@
 
         mSelectedDevice = requireNonNull(deviceFilterPairs.get(0));
 
-        final Drawable profileIcon = getIcon(this, PROFILE_ICON.get(deviceProfile));
+        final Drawable profileIcon = getIcon(this, PROFILE_ICONS.get(deviceProfile));
+
+        // No need to show permission consent dialog if it is a isSkipPrompt(true)
+        // AssociationRequest. See AssociationRequestsProcessor#mayAssociateWithoutPrompt.
+        if (mRequest.isSkipPrompt()) {
+            Log.d(TAG, "Skipping the permission consent dialog.");
+            mSingleDeviceSpinner.setVisibility(View.GONE);
+            onUserSelectedDevice(mSelectedDevice);
+            return;
+        }
 
         updatePermissionUi();
 
@@ -545,14 +554,14 @@
             throw new RuntimeException("Unsupported profile " + deviceProfile);
         }
 
-        profileIcon = getIcon(this, PROFILE_ICON.get(deviceProfile));
+        profileIcon = getIcon(this, PROFILE_ICONS.get(deviceProfile));
 
         if (deviceProfile == null) {
             title = getHtmlFromResources(this, R.string.chooser_title_non_profile, appLabel);
             mButtonNotAllowMultipleDevices.setText(R.string.consent_no);
         } else {
             title = getHtmlFromResources(this,
-                    R.string.chooser_title, getString(PROFILES_NAME.get(deviceProfile)));
+                    R.string.chooser_title, getString(PROFILE_NAMES.get(deviceProfile)));
         }
 
         mDeviceAdapter = new DeviceListAdapter(this, this::onDeviceClicked);
@@ -598,6 +607,14 @@
 
         Log.d(TAG, "onDeviceClicked(): " + mSelectedDevice.toShortString());
 
+        // No need to show permission consent dialog if it is a isSkipPrompt(true)
+        // AssociationRequest. See AssociationRequestsProcessor#mayAssociateWithoutPrompt.
+        if (mRequest.isSkipPrompt()) {
+            Log.d(TAG, "Skipping the permission consent dialog.");
+            onUserSelectedDevice(mSelectedDevice);
+            return;
+        }
+
         updatePermissionUi();
 
         mSummary.setVisibility(View.VISIBLE);
@@ -609,20 +626,12 @@
 
     private void updatePermissionUi() {
         final String deviceProfile = mRequest.getDeviceProfile();
-        final int summaryResourceId = SUMMARIES.get(deviceProfile);
+        final int summaryResourceId = PROFILE_SUMMARIES.get(deviceProfile);
         final String remoteDeviceName = mSelectedDevice.getDisplayName();
         final Spanned title = getHtmlFromResources(
-                this, TITLES.get(deviceProfile), mAppLabel, remoteDeviceName);
+                this, PROFILE_TITLES.get(deviceProfile), mAppLabel, remoteDeviceName);
         final Spanned summary;
 
-        // No need to show permission consent dialog if it is a isSkipPrompt(true)
-        // AssociationRequest. See AssociationRequestsProcessor#mayAssociateWithoutPrompt.
-        if (mRequest.isSkipPrompt()) {
-            mSingleDeviceSpinner.setVisibility(View.GONE);
-            onUserSelectedDevice(mSelectedDevice);
-            return;
-        }
-
         if (deviceProfile == null && mRequest.isSingleDevice()) {
             summary = getHtmlFromResources(this, summaryResourceId, remoteDeviceName);
             mConstraintList.setVisibility(View.GONE);
@@ -680,7 +689,8 @@
     // and when mPermissionListRecyclerView is fully populated.
     // Lastly, disable the Allow and Don't allow buttons.
     private void setupPermissionList(String deviceProfile) {
-        final List<Integer> permissionTypes = new ArrayList<>(PERMISSION_TYPES.get(deviceProfile));
+        final List<Integer> permissionTypes = new ArrayList<>(
+                PROFILE_PERMISSIONS.get(deviceProfile));
         mPermissionListAdapter.setPermissionType(permissionTypes);
         mPermissionListRecyclerView.setAdapter(mPermissionListAdapter);
         mPermissionListRecyclerView.setLayoutManager(mPermissionsLayoutManager);
diff --git a/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceResources.java b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceResources.java
index 551e975..23a11d6 100644
--- a/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceResources.java
+++ b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/CompanionDeviceResources.java
@@ -22,28 +22,15 @@
 import static android.companion.AssociationRequest.DEVICE_PROFILE_GLASSES;
 import static android.companion.AssociationRequest.DEVICE_PROFILE_NEARBY_DEVICE_STREAMING;
 import static android.companion.AssociationRequest.DEVICE_PROFILE_WATCH;
-
-import static com.android.companiondevicemanager.PermissionListAdapter.PERMISSION_APP_STREAMING;
-import static com.android.companiondevicemanager.PermissionListAdapter.PERMISSION_CALENDAR;
-import static com.android.companiondevicemanager.PermissionListAdapter.PERMISSION_CALL_LOGS;
-import static com.android.companiondevicemanager.PermissionListAdapter.PERMISSION_CHANGE_MEDIA_OUTPUT;
-import static com.android.companiondevicemanager.PermissionListAdapter.PERMISSION_CONTACTS;
-import static com.android.companiondevicemanager.PermissionListAdapter.PERMISSION_MICROPHONE;
-import static com.android.companiondevicemanager.PermissionListAdapter.PERMISSION_NEARBY_DEVICES;
-import static com.android.companiondevicemanager.PermissionListAdapter.PERMISSION_NEARBY_DEVICE_STREAMING;
-import static com.android.companiondevicemanager.PermissionListAdapter.PERMISSION_NOTIFICATION;
-import static com.android.companiondevicemanager.PermissionListAdapter.PERMISSION_PHONE;
-import static com.android.companiondevicemanager.PermissionListAdapter.PERMISSION_SMS;
-import static com.android.companiondevicemanager.PermissionListAdapter.PERMISSION_STORAGE;
+import static android.os.Build.VERSION_CODES.UPSIDE_DOWN_CAKE;
 
 import static java.util.Collections.unmodifiableMap;
 import static java.util.Collections.unmodifiableSet;
 
+import android.os.Build;
 import android.util.ArrayMap;
 import android.util.ArraySet;
 
-import com.android.media.flags.Flags;
-
 import java.util.Arrays;
 import java.util.List;
 import java.util.Map;
@@ -54,7 +41,85 @@
  * for the corresponding profile.
  */
 final class CompanionDeviceResources {
-    static final Map<String, Integer> TITLES;
+
+    // Permission resources
+    private static final int PERMISSION_NOTIFICATION_LISTENER_ACCESS = 0;
+    private static final int PERMISSION_STORAGE = 1;
+    private static final int PERMISSION_APP_STREAMING = 2;
+    private static final int PERMISSION_PHONE = 3;
+    private static final int PERMISSION_SMS = 4;
+    private static final int PERMISSION_CONTACTS = 5;
+    private static final int PERMISSION_CALENDAR = 6;
+    private static final int PERMISSION_NEARBY_DEVICES = 7;
+    private static final int PERMISSION_NEARBY_DEVICE_STREAMING = 8;
+    private static final int PERMISSION_MICROPHONE = 9;
+    private static final int PERMISSION_CALL_LOGS = 10;
+    // Notification Listener Access & POST_NOTIFICATION permission
+    private static final int PERMISSION_NOTIFICATIONS = 11;
+    private static final int PERMISSION_CHANGE_MEDIA_OUTPUT = 12;
+
+    static final Map<Integer, Integer> PERMISSION_TITLES;
+    static {
+        final Map<Integer, Integer> map = new ArrayMap<>();
+        map.put(PERMISSION_NOTIFICATION_LISTENER_ACCESS, R.string.permission_notifications);
+        map.put(PERMISSION_STORAGE, R.string.permission_storage);
+        map.put(PERMISSION_APP_STREAMING, R.string.permission_app_streaming);
+        map.put(PERMISSION_PHONE, R.string.permission_phone);
+        map.put(PERMISSION_SMS, R.string.permission_sms);
+        map.put(PERMISSION_CONTACTS, R.string.permission_contacts);
+        map.put(PERMISSION_CALENDAR, R.string.permission_calendar);
+        map.put(PERMISSION_NEARBY_DEVICES, R.string.permission_nearby_devices);
+        map.put(PERMISSION_NEARBY_DEVICE_STREAMING, R.string.permission_nearby_device_streaming);
+        map.put(PERMISSION_MICROPHONE, R.string.permission_microphone);
+        map.put(PERMISSION_CALL_LOGS, R.string.permission_call_logs);
+        map.put(PERMISSION_NOTIFICATIONS, R.string.permission_notifications);
+        map.put(PERMISSION_CHANGE_MEDIA_OUTPUT, R.string.permission_media_routing_control);
+        PERMISSION_TITLES = unmodifiableMap(map);
+    }
+
+    static final Map<Integer, Integer> PERMISSION_SUMMARIES;
+    static {
+        final Map<Integer, Integer> map = new ArrayMap<>();
+        map.put(PERMISSION_NOTIFICATION_LISTENER_ACCESS,
+                R.string.permission_notification_listener_access_summary);
+        map.put(PERMISSION_STORAGE, R.string.permission_storage_summary);
+        map.put(PERMISSION_APP_STREAMING, R.string.permission_app_streaming_summary);
+        map.put(PERMISSION_PHONE, R.string.permission_phone_summary);
+        map.put(PERMISSION_SMS, R.string.permission_sms_summary);
+        map.put(PERMISSION_CONTACTS, R.string.permission_contacts_summary);
+        map.put(PERMISSION_CALENDAR, R.string.permission_calendar_summary);
+        map.put(PERMISSION_NEARBY_DEVICES, R.string.permission_nearby_devices_summary);
+        map.put(PERMISSION_NEARBY_DEVICE_STREAMING,
+                R.string.permission_nearby_device_streaming_summary);
+        map.put(PERMISSION_MICROPHONE, R.string.permission_microphone_summary);
+        map.put(PERMISSION_CALL_LOGS, R.string.permission_call_logs_summary);
+        map.put(PERMISSION_NOTIFICATIONS, R.string.permission_notifications_summary);
+        map.put(PERMISSION_CHANGE_MEDIA_OUTPUT, R.string.permission_media_routing_control_summary);
+        PERMISSION_SUMMARIES = unmodifiableMap(map);
+    }
+
+    static final Map<Integer, Integer> PERMISSION_ICONS;
+    static {
+        final Map<Integer, Integer> map = new ArrayMap<>();
+        map.put(PERMISSION_NOTIFICATION_LISTENER_ACCESS, R.drawable.ic_permission_notifications);
+        map.put(PERMISSION_STORAGE, R.drawable.ic_permission_storage);
+        map.put(PERMISSION_APP_STREAMING, R.drawable.ic_permission_app_streaming);
+        map.put(PERMISSION_PHONE, R.drawable.ic_permission_phone);
+        map.put(PERMISSION_SMS, R.drawable.ic_permission_sms);
+        map.put(PERMISSION_CONTACTS, R.drawable.ic_permission_contacts);
+        map.put(PERMISSION_CALENDAR, R.drawable.ic_permission_calendar);
+        map.put(PERMISSION_NEARBY_DEVICES, R.drawable.ic_permission_nearby_devices);
+        map.put(PERMISSION_NEARBY_DEVICE_STREAMING,
+                R.drawable.ic_permission_nearby_device_streaming);
+        map.put(PERMISSION_MICROPHONE, R.drawable.ic_permission_microphone);
+        map.put(PERMISSION_CALL_LOGS, R.drawable.ic_permission_call_logs);
+        map.put(PERMISSION_NOTIFICATIONS, R.drawable.ic_permission_notifications);
+        map.put(PERMISSION_CHANGE_MEDIA_OUTPUT, R.drawable.ic_permission_media_routing_control);
+        PERMISSION_ICONS = unmodifiableMap(map);
+    }
+
+    // Profile resources
+    static final Map<String, Integer> PROFILE_TITLES;
     static {
         final Map<String, Integer> map = new ArrayMap<>();
         map.put(DEVICE_PROFILE_APP_STREAMING, R.string.title_app_streaming);
@@ -65,71 +130,61 @@
         map.put(DEVICE_PROFILE_GLASSES, R.string.confirmation_title_glasses);
         map.put(null, R.string.confirmation_title);
 
-        TITLES = unmodifiableMap(map);
+        PROFILE_TITLES = unmodifiableMap(map);
     }
 
-    static final Map<String, List<Integer>> PERMISSION_TYPES;
-    static {
-        final Map<String, List<Integer>> map = new ArrayMap<>();
-        map.put(DEVICE_PROFILE_APP_STREAMING, Arrays.asList(PERMISSION_APP_STREAMING));
-        map.put(DEVICE_PROFILE_COMPUTER, Arrays.asList(
-                PERMISSION_NOTIFICATION, PERMISSION_STORAGE));
-        map.put(DEVICE_PROFILE_NEARBY_DEVICE_STREAMING,
-                Arrays.asList(PERMISSION_NEARBY_DEVICE_STREAMING));
-        if (!Flags.enablePrivilegedRoutingForMediaRoutingControl()) {
-            map.put(DEVICE_PROFILE_WATCH, Arrays.asList(PERMISSION_NOTIFICATION, PERMISSION_PHONE,
-                    PERMISSION_CALL_LOGS, PERMISSION_SMS, PERMISSION_CONTACTS, PERMISSION_CALENDAR,
-                    PERMISSION_NEARBY_DEVICES));
-        } else {
-            map.put(DEVICE_PROFILE_WATCH, Arrays.asList(PERMISSION_NOTIFICATION, PERMISSION_PHONE,
-                    PERMISSION_CALL_LOGS, PERMISSION_SMS, PERMISSION_CONTACTS, PERMISSION_CALENDAR,
-                    PERMISSION_NEARBY_DEVICES, PERMISSION_CHANGE_MEDIA_OUTPUT));
-        }
-        map.put(DEVICE_PROFILE_GLASSES, Arrays.asList(PERMISSION_NOTIFICATION, PERMISSION_PHONE,
-                PERMISSION_SMS, PERMISSION_CONTACTS, PERMISSION_MICROPHONE,
-                PERMISSION_NEARBY_DEVICES));
-
-        PERMISSION_TYPES = unmodifiableMap(map);
-    }
-
-    static final Map<String, Integer> SUMMARIES;
+    static final Map<String, Integer> PROFILE_SUMMARIES;
     static {
         final Map<String, Integer> map = new ArrayMap<>();
         map.put(DEVICE_PROFILE_WATCH, R.string.summary_watch);
         map.put(DEVICE_PROFILE_GLASSES, R.string.summary_glasses);
         map.put(null, R.string.summary_generic);
 
-        SUMMARIES = unmodifiableMap(map);
+        PROFILE_SUMMARIES = unmodifiableMap(map);
     }
 
-    static final Map<String, Integer> PROFILES_NAME;
+    static final Map<String, List<Integer>> PROFILE_PERMISSIONS;
+    static {
+        final Map<String, List<Integer>> map = new ArrayMap<>();
+        map.put(DEVICE_PROFILE_APP_STREAMING, Arrays.asList(PERMISSION_APP_STREAMING));
+        map.put(DEVICE_PROFILE_COMPUTER, Arrays.asList(
+                PERMISSION_NOTIFICATION_LISTENER_ACCESS, PERMISSION_STORAGE));
+        map.put(DEVICE_PROFILE_NEARBY_DEVICE_STREAMING,
+                Arrays.asList(PERMISSION_NEARBY_DEVICE_STREAMING));
+        if (Build.VERSION.SDK_INT > UPSIDE_DOWN_CAKE) {
+            map.put(DEVICE_PROFILE_WATCH, Arrays.asList(PERMISSION_NOTIFICATIONS, PERMISSION_PHONE,
+                    PERMISSION_CALL_LOGS, PERMISSION_SMS, PERMISSION_CONTACTS, PERMISSION_CALENDAR,
+                    PERMISSION_NEARBY_DEVICES, PERMISSION_CHANGE_MEDIA_OUTPUT));
+        } else {
+            map.put(DEVICE_PROFILE_WATCH, Arrays.asList(PERMISSION_NOTIFICATION_LISTENER_ACCESS,
+                    PERMISSION_PHONE, PERMISSION_CALL_LOGS, PERMISSION_SMS, PERMISSION_CONTACTS,
+                    PERMISSION_CALENDAR, PERMISSION_NEARBY_DEVICES));
+        }
+        map.put(DEVICE_PROFILE_GLASSES, Arrays.asList(PERMISSION_NOTIFICATION_LISTENER_ACCESS,
+                PERMISSION_PHONE, PERMISSION_SMS, PERMISSION_CONTACTS, PERMISSION_MICROPHONE,
+                PERMISSION_NEARBY_DEVICES));
+
+        PROFILE_PERMISSIONS = unmodifiableMap(map);
+    }
+
+    static final Map<String, Integer> PROFILE_NAMES;
     static {
         final Map<String, Integer> map = new ArrayMap<>();
         map.put(DEVICE_PROFILE_WATCH, R.string.profile_name_watch);
         map.put(DEVICE_PROFILE_GLASSES, R.string.profile_name_glasses);
         map.put(null, R.string.profile_name_generic);
 
-        PROFILES_NAME = unmodifiableMap(map);
+        PROFILE_NAMES = unmodifiableMap(map);
     }
 
-    static final Map<String, Integer> PROFILES_NAME_MULTI;
-    static {
-        final Map<String, Integer> map = new ArrayMap<>();
-        map.put(DEVICE_PROFILE_GLASSES, R.string.profile_name_generic);
-        map.put(DEVICE_PROFILE_WATCH, R.string.profile_name_watch);
-        map.put(null, R.string.profile_name_generic);
-
-        PROFILES_NAME_MULTI = unmodifiableMap(map);
-    }
-
-    static final Map<String, Integer> PROFILE_ICON;
+    static final Map<String, Integer> PROFILE_ICONS;
     static {
         final Map<String, Integer> map = new ArrayMap<>();
         map.put(DEVICE_PROFILE_WATCH, R.drawable.ic_watch);
         map.put(DEVICE_PROFILE_GLASSES, R.drawable.ic_glasses);
         map.put(null, R.drawable.ic_device_other);
 
-        PROFILE_ICON = unmodifiableMap(map);
+        PROFILE_ICONS = unmodifiableMap(map);
     }
 
     static final Set<String> SUPPORTED_PROFILES;
diff --git a/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/PermissionListAdapter.java b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/PermissionListAdapter.java
index e21aee3..4a1f801 100644
--- a/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/PermissionListAdapter.java
+++ b/packages/CompanionDeviceManager/src/com/android/companiondevicemanager/PermissionListAdapter.java
@@ -16,14 +16,14 @@
 
 package com.android.companiondevicemanager;
 
+import static com.android.companiondevicemanager.CompanionDeviceResources.PERMISSION_ICONS;
+import static com.android.companiondevicemanager.CompanionDeviceResources.PERMISSION_SUMMARIES;
+import static com.android.companiondevicemanager.CompanionDeviceResources.PERMISSION_TITLES;
 import static com.android.companiondevicemanager.Utils.getHtmlFromResources;
 import static com.android.companiondevicemanager.Utils.getIcon;
 
-import static java.util.Collections.unmodifiableMap;
-
 import android.content.Context;
 import android.text.Spanned;
-import android.util.ArrayMap;
 import android.view.LayoutInflater;
 import android.view.View;
 import android.view.ViewGroup;
@@ -35,7 +35,6 @@
 import androidx.recyclerview.widget.RecyclerView;
 
 import java.util.List;
-import java.util.Map;
 
 class PermissionListAdapter extends RecyclerView.Adapter<PermissionListAdapter.ViewHolder> {
     private final Context mContext;
@@ -43,75 +42,6 @@
     // Add the expand buttons if permissions are more than PERMISSION_SIZE in the permission list.
     private static final int PERMISSION_SIZE = 2;
 
-    static final int PERMISSION_NOTIFICATION = 0;
-    static final int PERMISSION_STORAGE = 1;
-    static final int PERMISSION_APP_STREAMING = 2;
-    static final int PERMISSION_PHONE = 3;
-    static final int PERMISSION_SMS = 4;
-    static final int PERMISSION_CONTACTS = 5;
-    static final int PERMISSION_CALENDAR = 6;
-    static final int PERMISSION_NEARBY_DEVICES = 7;
-    static final int PERMISSION_NEARBY_DEVICE_STREAMING = 8;
-    static final int PERMISSION_MICROPHONE = 9;
-    static final int PERMISSION_CALL_LOGS = 10;
-    static final int PERMISSION_CHANGE_MEDIA_OUTPUT = 11;
-
-    private static final Map<Integer, Integer> sTitleMap;
-    static {
-        final Map<Integer, Integer> map = new ArrayMap<>();
-        map.put(PERMISSION_NOTIFICATION, R.string.permission_notification);
-        map.put(PERMISSION_STORAGE, R.string.permission_storage);
-        map.put(PERMISSION_APP_STREAMING, R.string.permission_app_streaming);
-        map.put(PERMISSION_PHONE, R.string.permission_phone);
-        map.put(PERMISSION_SMS, R.string.permission_sms);
-        map.put(PERMISSION_CONTACTS, R.string.permission_contacts);
-        map.put(PERMISSION_CALENDAR, R.string.permission_calendar);
-        map.put(PERMISSION_NEARBY_DEVICES, R.string.permission_nearby_devices);
-        map.put(PERMISSION_NEARBY_DEVICE_STREAMING, R.string.permission_nearby_device_streaming);
-        map.put(PERMISSION_MICROPHONE, R.string.permission_microphone);
-        map.put(PERMISSION_CALL_LOGS, R.string.permission_call_logs);
-        map.put(PERMISSION_CHANGE_MEDIA_OUTPUT, R.string.permission_media_routing_control);
-        sTitleMap = unmodifiableMap(map);
-    }
-
-    private static final Map<Integer, Integer> sSummaryMap;
-    static {
-        final Map<Integer, Integer> map = new ArrayMap<>();
-        map.put(PERMISSION_NOTIFICATION, R.string.permission_notification_summary);
-        map.put(PERMISSION_STORAGE, R.string.permission_storage_summary);
-        map.put(PERMISSION_APP_STREAMING, R.string.permission_app_streaming_summary);
-        map.put(PERMISSION_PHONE, R.string.permission_phone_summary);
-        map.put(PERMISSION_SMS, R.string.permission_sms_summary);
-        map.put(PERMISSION_CONTACTS, R.string.permission_contacts_summary);
-        map.put(PERMISSION_CALENDAR, R.string.permission_calendar_summary);
-        map.put(PERMISSION_NEARBY_DEVICES, R.string.permission_nearby_devices_summary);
-        map.put(PERMISSION_NEARBY_DEVICE_STREAMING,
-                R.string.permission_nearby_device_streaming_summary);
-        map.put(PERMISSION_MICROPHONE, R.string.permission_microphone_summary);
-        map.put(PERMISSION_CALL_LOGS, R.string.permission_call_logs_summary);
-        map.put(PERMISSION_CHANGE_MEDIA_OUTPUT, R.string.permission_media_routing_control_summary);
-        sSummaryMap = unmodifiableMap(map);
-    }
-
-    private static final Map<Integer, Integer> sIconMap;
-    static {
-        final Map<Integer, Integer> map = new ArrayMap<>();
-        map.put(PERMISSION_NOTIFICATION, R.drawable.ic_permission_notifications);
-        map.put(PERMISSION_STORAGE, R.drawable.ic_permission_storage);
-        map.put(PERMISSION_APP_STREAMING, R.drawable.ic_permission_app_streaming);
-        map.put(PERMISSION_PHONE, R.drawable.ic_permission_phone);
-        map.put(PERMISSION_SMS, R.drawable.ic_permission_sms);
-        map.put(PERMISSION_CONTACTS, R.drawable.ic_permission_contacts);
-        map.put(PERMISSION_CALENDAR, R.drawable.ic_permission_calendar);
-        map.put(PERMISSION_NEARBY_DEVICES, R.drawable.ic_permission_nearby_devices);
-        map.put(PERMISSION_NEARBY_DEVICE_STREAMING,
-                R.drawable.ic_permission_nearby_device_streaming);
-        map.put(PERMISSION_MICROPHONE, R.drawable.ic_permission_microphone);
-        map.put(PERMISSION_CALL_LOGS, R.drawable.ic_permission_call_logs);
-        map.put(PERMISSION_CHANGE_MEDIA_OUTPUT, R.drawable.ic_permission_media_routing_control);
-        sIconMap = unmodifiableMap(map);
-    }
-
     PermissionListAdapter(Context context) {
         mContext = context;
     }
@@ -121,7 +51,8 @@
         View view = LayoutInflater.from(parent.getContext()).inflate(
                 R.layout.list_item_permission, parent, false);
         ViewHolder viewHolder = new ViewHolder(view);
-        viewHolder.mPermissionIcon.setImageDrawable(getIcon(mContext, sIconMap.get(viewType)));
+        viewHolder.mPermissionIcon.setImageDrawable(
+                getIcon(mContext, PERMISSION_ICONS.get(viewType)));
 
         if (viewHolder.mExpandButton.getTag() == null) {
             viewHolder.mExpandButton.setTag(R.drawable.btn_expand_more);
@@ -165,8 +96,8 @@
     @Override
     public void onBindViewHolder(ViewHolder holder, int position) {
         int type = getItemViewType(position);
-        final Spanned title = getHtmlFromResources(mContext, sTitleMap.get(type));
-        final Spanned summary = getHtmlFromResources(mContext, sSummaryMap.get(type));
+        final Spanned title = getHtmlFromResources(mContext, PERMISSION_TITLES.get(type));
+        final Spanned summary = getHtmlFromResources(mContext, PERMISSION_SUMMARIES.get(type));
 
         holder.mPermissionSummary.setText(summary);
         holder.mPermissionName.setText(title);
@@ -192,6 +123,7 @@
         private final TextView mPermissionSummary;
         private final ImageView mPermissionIcon;
         private final ImageButton mExpandButton;
+
         ViewHolder(View itemView) {
             super(itemView);
             mPermissionName = itemView.findViewById(R.id.permission_name);
@@ -203,7 +135,7 @@
 
     private void setAccessibility(View view, int viewType, int action, int statusResourceId,
             int actionResourceId) {
-        final String permission = mContext.getString(sTitleMap.get(viewType));
+        final String permission = mContext.getString(PERMISSION_TITLES.get(viewType));
 
         if (actionResourceId != 0) {
             view.announceForAccessibility(
diff --git a/packages/PackageInstaller/res/values/strings.xml b/packages/PackageInstaller/res/values/strings.xml
index e3b93ba..f4641b9 100644
--- a/packages/PackageInstaller/res/values/strings.xml
+++ b/packages/PackageInstaller/res/values/strings.xml
@@ -202,11 +202,6 @@
     <!-- Dialog attributes to indicate parse errors -->
     <string name="Parse_error_dlg_text">There was a problem parsing the package.</string>
 
-    <!-- Title of dialog telling users that Install/Uninstall action is not supported on Android Wear. [CHAR LIMIT=30] -->
-    <string name="wear_not_allowed_dlg_title">Android Wear</string>
-    <!-- Title of dialog telling users that Install/Uninstall action is not supported on Android Wear. [CHAR LIMIT=none] -->
-    <string name="wear_not_allowed_dlg_text">Install/Uninstall actions not supported on Wear.</string>
-
     <!-- Message that the app to be installed is being staged [CHAR LIMIT=50] -->
     <string name="message_staging">Staging app&#8230;</string>
 
diff --git a/packages/PackageInstaller/src/com/android/packageinstaller/handheld/UninstallAlertDialogFragment.java b/packages/PackageInstaller/src/com/android/packageinstaller/handheld/UninstallAlertDialogFragment.java
index e07e942..2da8c8c 100644
--- a/packages/PackageInstaller/src/com/android/packageinstaller/handheld/UninstallAlertDialogFragment.java
+++ b/packages/PackageInstaller/src/com/android/packageinstaller/handheld/UninstallAlertDialogFragment.java
@@ -156,10 +156,9 @@
                 if (customUserManager.isUserOfType(USER_TYPE_PROFILE_MANAGED)
                         && customUserManager.isSameProfileGroup(dialogInfo.user, myUserHandle)) {
                     messageBuilder.append(isArchive
-                            ? getString(R.string.archive_application_text_current_user_work_profile,
-                                    userName) : getString(
-                            R.string.uninstall_application_text_current_user_work_profile,
-                            userName));
+                            ? getString(R.string.archive_application_text_current_user_work_profile)
+                            : getString(
+                                    R.string.uninstall_application_text_current_user_work_profile));
                 } else if (customUserManager.isUserOfType(USER_TYPE_PROFILE_CLONE)
                         && customUserManager.isSameProfileGroup(dialogInfo.user, myUserHandle)) {
                     mIsClonedApp = true;
@@ -168,11 +167,11 @@
                 } else if (Flags.allowPrivateProfile()
                         && customUserManager.isPrivateProfile()
                         && customUserManager.isSameProfileGroup(dialogInfo.user, myUserHandle)) {
-                    messageBuilder.append(isArchive ? getString(
-                            R.string.archive_application_text_current_user_private_profile,
-                            userName) : getString(
-                            R.string.uninstall_application_text_current_user_private_profile,
-                            userName));
+                    messageBuilder.append(
+                            isArchive ? getString(
+                                    R.string.archive_application_text_current_user_private_profile)
+                            : getString(
+                                R.string.uninstall_application_text_current_user_private_profile));
                 } else if (isArchive) {
                     messageBuilder.append(
                             getString(R.string.archive_application_text_user, userName));
diff --git a/packages/SettingsLib/RestrictedLockUtils/src/com/android/settingslib/RestrictedLockUtils.java b/packages/SettingsLib/RestrictedLockUtils/src/com/android/settingslib/RestrictedLockUtils.java
index 96a11ee..5b39f4e 100644
--- a/packages/SettingsLib/RestrictedLockUtils/src/com/android/settingslib/RestrictedLockUtils.java
+++ b/packages/SettingsLib/RestrictedLockUtils/src/com/android/settingslib/RestrictedLockUtils.java
@@ -112,26 +112,6 @@
     }
 
     /**
-     * Shows restricted setting dialog.
-     */
-    @RequiresApi(Build.VERSION_CODES.TIRAMISU)
-    public static void sendShowRestrictedSettingDialogIntent(Context context,
-            String packageName, int uid) {
-        final Intent intent = getShowRestrictedSettingsIntent(packageName, uid);
-        context.startActivity(intent);
-    }
-
-    /**
-     * Gets restricted settings dialog intent.
-     */
-    private static Intent getShowRestrictedSettingsIntent(String packageName, int uid) {
-        final Intent intent = new Intent(Settings.ACTION_SHOW_RESTRICTED_SETTING_DIALOG);
-        intent.putExtra(Intent.EXTRA_PACKAGE_NAME, packageName);
-        intent.putExtra(Intent.EXTRA_UID, uid);
-        return intent;
-    }
-
-    /**
      * Checks if current user is profile or not
      */
     @RequiresApi(Build.VERSION_CODES.M)
@@ -238,4 +218,35 @@
                     + '}';
         }
     }
+
+
+    /**
+     * Shows restricted setting dialog.
+     *
+     * @deprecated TODO(b/308921175): This will be deleted with the
+     * {@link android.security.Flags#extendEcmToAllSettings} feature flag. Do not use for any new
+     * code.
+     */
+    @Deprecated
+    @RequiresApi(Build.VERSION_CODES.TIRAMISU)
+    public static void sendShowRestrictedSettingDialogIntent(Context context,
+                                                             String packageName, int uid) {
+        final Intent intent = getShowRestrictedSettingsIntent(packageName, uid);
+        context.startActivity(intent);
+    }
+
+    /**
+     * Gets restricted settings dialog intent.
+     *
+     * @deprecated TODO(b/308921175): This will be deleted with the
+     * {@link android.security.Flags#extendEcmToAllSettings} feature flag. Do not use for any new
+     * code.
+     */
+    @Deprecated
+    private static Intent getShowRestrictedSettingsIntent(String packageName, int uid) {
+        final Intent intent = new Intent(Settings.ACTION_SHOW_RESTRICTED_SETTING_DIALOG);
+        intent.putExtra(Intent.EXTRA_PACKAGE_NAME, packageName);
+        intent.putExtra(Intent.EXTRA_UID, uid);
+        return intent;
+    }
 }
diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/card/CardPageProvider.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/card/CardPageProvider.kt
index f216abb..66bd6f5 100644
--- a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/card/CardPageProvider.kt
+++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/card/CardPageProvider.kt
@@ -28,7 +28,14 @@
 import androidx.compose.material.icons.outlined.WarningAmber
 import androidx.compose.material3.Text
 import androidx.compose.runtime.Composable
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateListOf
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.saveable.rememberSaveable
+import androidx.compose.runtime.setValue
 import androidx.compose.ui.Modifier
+import androidx.compose.ui.platform.LocalContext
 import androidx.compose.ui.res.stringResource
 import androidx.compose.ui.tooling.preview.Preview
 import com.android.settingslib.spa.framework.common.SettingsEntryBuilder
@@ -41,14 +48,14 @@
 import com.android.settingslib.spa.widget.card.CardButton
 import com.android.settingslib.spa.widget.card.CardModel
 import com.android.settingslib.spa.widget.card.SettingsCard
-import com.android.settingslib.spa.widget.card.SettingsCollapsibleCard
 import com.android.settingslib.spa.widget.card.SettingsCardContent
+import com.android.settingslib.spa.widget.card.SettingsCollapsibleCard
 import com.android.settingslib.spa.widget.preference.Preference
 import com.android.settingslib.spa.widget.preference.PreferenceModel
 import com.android.settingslib.spa.widget.scaffold.RegularScaffold
 
 object CardPageProvider : SettingsPageProvider {
-    override val name = "CardPage"
+    override val name = "Card"
 
     override fun getTitle(arguments: Bundle?) = TITLE
 
@@ -79,10 +86,13 @@
 
     @Composable
     private fun SettingsCardWithoutIcon() {
+        var isVisible by rememberSaveable { mutableStateOf(true) }
         SettingsCard(
             CardModel(
                 title = stringResource(R.string.sample_title),
                 text = stringResource(R.string.sample_text),
+                isVisible = { isVisible },
+                onDismiss = { isVisible = false },
                 buttons = listOf(
                     CardButton(text = "Action") {},
                 ),
@@ -92,21 +102,23 @@
 
     @Composable
     fun SampleSettingsCollapsibleCard() {
-        SettingsCollapsibleCard(
-            title = "More alerts",
-            imageVector = Icons.Outlined.Error,
-            models = listOf(
+        val context = LocalContext.current
+        var isVisible0 by rememberSaveable { mutableStateOf(true) }
+        val cards = remember {
+            mutableStateListOf(
                 CardModel(
-                    title = stringResource(R.string.sample_title),
-                    text = stringResource(R.string.sample_text),
+                    title = context.getString(R.string.sample_title),
+                    text = context.getString(R.string.sample_text),
                     imageVector = Icons.Outlined.PowerOff,
+                    isVisible = { isVisible0 },
+                    onDismiss = { isVisible0 = false },
                     buttons = listOf(
                         CardButton(text = "Action") {},
                     )
                 ),
                 CardModel(
-                    title = stringResource(R.string.sample_title),
-                    text = stringResource(R.string.sample_text),
+                    title = context.getString(R.string.sample_title),
+                    text = context.getString(R.string.sample_text),
                     imageVector = Icons.Outlined.Shield,
                     buttons = listOf(
                         CardButton(text = "Action") {},
@@ -114,6 +126,11 @@
                     )
                 )
             )
+        }
+        SettingsCollapsibleCard(
+            title = "More alerts",
+            imageVector = Icons.Outlined.Error,
+            models = cards.toList()
         )
     }
 
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/SettingsDimension.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/SettingsDimension.kt
index c143390..993cb4a 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/SettingsDimension.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/SettingsDimension.kt
@@ -37,6 +37,7 @@
     val itemPaddingAround = 8.dp
     val itemDividerHeight = 32.dp
 
+    val iconSmall = 16.dp
     val iconLarge = 48.dp
 
     /** The size when app icon is displayed in list. */
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/card/CardModel.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/card/CardModel.kt
index c113f43..b18a1bc 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/card/CardModel.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/card/CardModel.kt
@@ -28,5 +28,14 @@
     val title: String,
     val text: String,
     val imageVector: ImageVector? = null,
+    val isVisible: () -> Boolean = { true },
+
+    /**
+     * A dismiss button will be displayed if this is not null.
+     *
+     * And this callback will be called when user clicks the button.
+     */
+    val onDismiss: (() -> Unit)? = null,
+
     val buttons: List<CardButton> = emptyList(),
 )
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/card/SettingsCard.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/card/SettingsCard.kt
index 4379278..7eec888 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/card/SettingsCard.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/card/SettingsCard.kt
@@ -16,28 +16,35 @@
 
 package com.android.settingslib.spa.widget.card
 
+import androidx.compose.animation.AnimatedVisibility
 import androidx.compose.foundation.layout.Arrangement
 import androidx.compose.foundation.layout.Column
 import androidx.compose.foundation.layout.ColumnScope
 import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.Spacer
 import androidx.compose.foundation.layout.fillMaxWidth
 import androidx.compose.foundation.layout.padding
 import androidx.compose.foundation.layout.size
+import androidx.compose.foundation.shape.CircleShape
 import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.outlined.Close
 import androidx.compose.material.icons.outlined.WarningAmber
 import androidx.compose.material3.Button
 import androidx.compose.material3.ButtonDefaults
 import androidx.compose.material3.Card
 import androidx.compose.material3.CardDefaults
 import androidx.compose.material3.Icon
+import androidx.compose.material3.IconButton
 import androidx.compose.material3.MaterialTheme
 import androidx.compose.material3.OutlinedButton
+import androidx.compose.material3.Surface
 import androidx.compose.material3.Text
 import androidx.compose.runtime.Composable
 import androidx.compose.ui.Alignment
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.graphics.Color
 import androidx.compose.ui.graphics.vector.ImageVector
+import androidx.compose.ui.res.stringResource
 import androidx.compose.ui.unit.dp
 import com.android.settingslib.spa.debug.UiModePreviews
 import com.android.settingslib.spa.framework.theme.SettingsDimension
@@ -87,20 +94,31 @@
 
 @Composable
 internal fun SettingsCardImpl(model: CardModel) {
-    SettingsCardContent {
-        Column(
-            modifier = Modifier.padding(SettingsDimension.itemPaddingStart),
-            verticalArrangement = Arrangement.spacedBy(SettingsDimension.itemPaddingAround)
-        ) {
-            CardIcon(model.imageVector)
-            SettingsTitle(model.title)
-            SettingsBody(model.text)
-            Buttons(model.buttons)
+    AnimatedVisibility(visible = model.isVisible()) {
+        SettingsCardContent {
+            Column(
+                modifier = Modifier.padding(SettingsDimension.itemPaddingStart),
+                verticalArrangement = Arrangement.spacedBy(SettingsDimension.itemPaddingAround)
+            ) {
+                CardHeader(model.imageVector, model.onDismiss)
+                SettingsTitle(model.title)
+                SettingsBody(model.text)
+                Buttons(model.buttons)
+            }
         }
     }
 }
 
 @Composable
+fun CardHeader(imageVector: ImageVector?, onDismiss: (() -> Unit)? = null) {
+    Row(Modifier.fillMaxWidth()) {
+        CardIcon(imageVector)
+        Spacer(modifier = Modifier.weight(1f))
+        DismissButton(onDismiss)
+    }
+}
+
+@Composable
 private fun CardIcon(imageVector: ImageVector?) {
     if (imageVector != null) {
         Icon(
@@ -113,6 +131,28 @@
 }
 
 @Composable
+private fun DismissButton(onDismiss: (() -> Unit)?) {
+    if (onDismiss == null) return
+    Surface(
+        shape = CircleShape,
+        color = MaterialTheme.colorScheme.secondaryContainer,
+    ) {
+        IconButton(
+            onClick = onDismiss,
+            modifier = Modifier.size(SettingsDimension.itemIconSize)
+        ) {
+            Icon(
+                imageVector = Icons.Outlined.Close,
+                contentDescription = stringResource(
+                    androidx.compose.material3.R.string.m3c_snackbar_dismiss
+                ),
+                modifier = Modifier.size(SettingsDimension.iconSmall),
+            )
+        }
+    }
+}
+
+@Composable
 private fun Buttons(buttons: List<CardButton>) {
     if (buttons.isNotEmpty()) {
         Row(
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/card/SettingsCollapsibleCard.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/card/SettingsCollapsibleCard.kt
index bf192a1..6e36490 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/card/SettingsCollapsibleCard.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/card/SettingsCollapsibleCard.kt
@@ -58,7 +58,7 @@
     var expanded by rememberSaveable { mutableStateOf(false) }
     SettingsCard {
         SettingsCardContent {
-            Header(title, imageVector, models.size, expanded) { expanded = it }
+            Header(title, imageVector, models.count { it.isVisible() }, expanded) { expanded = it }
         }
         AnimatedVisibility(expanded) {
             Column {
diff --git a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/card/SettingsCardTest.kt b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/card/SettingsCardTest.kt
index fd3ae49..beb9433 100644
--- a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/card/SettingsCardTest.kt
+++ b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/card/SettingsCardTest.kt
@@ -16,10 +16,18 @@
 
 package com.android.settingslib.spa.widget.card
 
+import android.content.Context
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.setValue
 import androidx.compose.ui.test.assertIsDisplayed
+import androidx.compose.ui.test.isNotDisplayed
 import androidx.compose.ui.test.junit4.createComposeRule
+import androidx.compose.ui.test.onNodeWithContentDescription
 import androidx.compose.ui.test.onNodeWithText
 import androidx.compose.ui.test.performClick
+import androidx.test.core.app.ApplicationProvider
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import com.google.common.truth.Truth.assertThat
 import org.junit.Rule
@@ -31,6 +39,8 @@
     @get:Rule
     val composeTestRule = createComposeRule()
 
+    private val context: Context = ApplicationProvider.getApplicationContext()
+
     @Test
     fun settingsCard_titleDisplayed() {
         composeTestRule.setContent {
@@ -96,6 +106,27 @@
         assertThat(buttonClicked).isTrue()
     }
 
+    @Test
+    fun settingsCard_dismiss() {
+        composeTestRule.setContent {
+            var isVisible by remember { mutableStateOf(true) }
+            SettingsCard(
+                CardModel(
+                    title = TITLE,
+                    text = "",
+                    isVisible = { isVisible },
+                    onDismiss = { isVisible = false },
+                )
+            )
+        }
+
+        composeTestRule.onNodeWithContentDescription(
+            context.getString(androidx.compose.material3.R.string.m3c_snackbar_dismiss)
+        ).performClick()
+
+        composeTestRule.onNodeWithText(TEXT).isNotDisplayed()
+    }
+
     private companion object {
         const val TITLE = "Title"
         const val TEXT = "Text"
diff --git a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/card/SettingsCollapsibleCardTest.kt b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/card/SettingsCollapsibleCardTest.kt
index efe1c70..aba9d7b 100644
--- a/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/card/SettingsCollapsibleCardTest.kt
+++ b/packages/SettingsLib/Spa/tests/src/com/android/settingslib/spa/widget/card/SettingsCollapsibleCardTest.kt
@@ -16,12 +16,20 @@
 
 package com.android.settingslib.spa.widget.card
 
+import android.content.Context
 import androidx.compose.material.icons.Icons
 import androidx.compose.material.icons.outlined.Error
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.saveable.rememberSaveable
+import androidx.compose.runtime.setValue
 import androidx.compose.ui.test.assertIsDisplayed
+import androidx.compose.ui.test.isNotDisplayed
 import androidx.compose.ui.test.junit4.createComposeRule
+import androidx.compose.ui.test.onNodeWithContentDescription
 import androidx.compose.ui.test.onNodeWithText
 import androidx.compose.ui.test.performClick
+import androidx.test.core.app.ApplicationProvider
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import org.junit.Rule
 import org.junit.Test
@@ -32,6 +40,8 @@
     @get:Rule
     val composeTestRule = createComposeRule()
 
+    private val context: Context = ApplicationProvider.getApplicationContext()
+
     @Test
     fun settingsCollapsibleCard_titleDisplayed() {
         setContent()
@@ -62,8 +72,22 @@
         composeTestRule.onNodeWithText(CARD_TEXT).assertIsDisplayed()
     }
 
+    @Test
+    fun settingsCollapsibleCard_dismiss() {
+        setContent()
+        composeTestRule.onNodeWithText(TITLE).performClick()
+
+        composeTestRule.onNodeWithContentDescription(
+            context.getString(androidx.compose.material3.R.string.m3c_snackbar_dismiss)
+        ).performClick()
+
+        composeTestRule.onNodeWithText(CARD_TEXT).isNotDisplayed()
+        composeTestRule.onNodeWithText("0").assertIsDisplayed()
+    }
+
     private fun setContent() {
         composeTestRule.setContent {
+            var isVisible by rememberSaveable { mutableStateOf(true) }
             SettingsCollapsibleCard(
                 title = TITLE,
                 imageVector = Icons.Outlined.Error,
@@ -71,6 +95,8 @@
                     CardModel(
                         title = "",
                         text = CARD_TEXT,
+                        isVisible = { isVisible },
+                        onDismiss = { isVisible = false },
                     )
                 ),
             )
diff --git a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/framework/common/BroadcastReceiverFlow.kt b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/framework/common/BroadcastReceiverFlow.kt
index d0d2dc0..e099f11 100644
--- a/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/framework/common/BroadcastReceiverFlow.kt
+++ b/packages/SettingsLib/SpaPrivileged/src/com/android/settingslib/spaprivileged/framework/common/BroadcastReceiverFlow.kt
@@ -20,13 +20,17 @@
 import android.content.Context
 import android.content.Intent
 import android.content.IntentFilter
+import android.util.Log
 import kotlinx.coroutines.Dispatchers
 import kotlinx.coroutines.channels.awaitClose
 import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.callbackFlow
+import kotlinx.coroutines.flow.catch
 import kotlinx.coroutines.flow.conflate
 import kotlinx.coroutines.flow.flowOn
 
+private const val TAG = "BroadcastReceiverFlow"
+
 /**
  * A [BroadcastReceiver] flow for the given [intentFilter].
  */
@@ -39,4 +43,6 @@
     registerReceiver(broadcastReceiver, intentFilter, Context.RECEIVER_NOT_EXPORTED)
 
     awaitClose { unregisterReceiver(broadcastReceiver) }
+}.catch { e ->
+    Log.e(TAG, "Error while broadcastReceiverFlow", e)
 }.conflate().flowOn(Dispatchers.Default)
diff --git a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/framework/common/BroadcastReceiverFlowTest.kt b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/framework/common/BroadcastReceiverFlowTest.kt
index dfaf3c6..eef5225 100644
--- a/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/framework/common/BroadcastReceiverFlowTest.kt
+++ b/packages/SettingsLib/SpaPrivileged/tests/src/com/android/settingslib/spaprivileged/framework/common/BroadcastReceiverFlowTest.kt
@@ -31,8 +31,10 @@
 import org.junit.runner.RunWith
 import org.mockito.kotlin.any
 import org.mockito.kotlin.doAnswer
+import org.mockito.kotlin.doThrow
 import org.mockito.kotlin.eq
 import org.mockito.kotlin.mock
+import org.mockito.kotlin.stub
 
 @RunWith(AndroidJUnit4::class)
 class BroadcastReceiverFlowTest {
@@ -74,6 +76,18 @@
         assertThat(onReceiveIsCalled).isTrue()
     }
 
+    @Test
+    fun broadcastReceiverFlow_unregisterReceiverThrowException_noCrash() = runBlocking {
+        context.stub {
+            on { unregisterReceiver(any()) } doThrow IllegalArgumentException()
+        }
+        val flow = context.broadcastReceiverFlow(INTENT_FILTER)
+
+        flow.firstWithTimeoutOrNull()
+
+        assertThat(registeredBroadcastReceiver).isNotNull()
+    }
+
     private companion object {
         val INTENT_FILTER = IntentFilter()
     }
diff --git a/packages/SettingsLib/src/com/android/settingslib/RestrictedLockUtilsInternal.java b/packages/SettingsLib/src/com/android/settingslib/RestrictedLockUtilsInternal.java
index 4454b71..0237446 100644
--- a/packages/SettingsLib/src/com/android/settingslib/RestrictedLockUtilsInternal.java
+++ b/packages/SettingsLib/src/com/android/settingslib/RestrictedLockUtilsInternal.java
@@ -77,6 +77,9 @@
             ECM_KEYS.add(AppOpsManager.OPSTR_LOADER_USAGE_STATS);
             ECM_KEYS.add(Manifest.permission.BIND_DEVICE_ADMIN);
         }
+
+        ECM_KEYS.add(AppOpsManager.OPSTR_ACCESS_NOTIFICATIONS);
+        ECM_KEYS.add(AppOpsManager.OPSTR_BIND_ACCESSIBILITY_SERVICE);
     }
 
     /**
diff --git a/packages/SettingsLib/src/com/android/settingslib/RestrictedPreference.java b/packages/SettingsLib/src/com/android/settingslib/RestrictedPreference.java
index db2a6ec..50e3bd0 100644
--- a/packages/SettingsLib/src/com/android/settingslib/RestrictedPreference.java
+++ b/packages/SettingsLib/src/com/android/settingslib/RestrictedPreference.java
@@ -96,12 +96,29 @@
         mHelper.checkRestrictionAndSetDisabled(userRestriction, userId);
     }
 
+    /**
+     * Checks if the given setting is subject to Enhanced Confirmation Mode restrictions for this
+     * package. Marks the preference as disabled if so.
+     * @param restriction The key identifying the setting
+     * @param packageName the package to check the restriction for
+     * @param uid the uid of the package
+     */
+    public void checkEcmRestrictionAndSetDisabled(String restriction, String packageName, int uid) {
+        mHelper.checkEcmRestrictionAndSetDisabled(restriction, packageName, uid);
+    }
+
     @Override
     public void setEnabled(boolean enabled) {
         if (enabled && isDisabledByAdmin()) {
             mHelper.setDisabledByAdmin(null);
             return;
         }
+
+        if (enabled && isDisabledByEcm()) {
+            mHelper.setDisabledByEcm(null);
+            return;
+        }
+
         super.setEnabled(enabled);
     }
 
@@ -111,16 +128,14 @@
         }
     }
 
-    public void setDisabledByAppOps(boolean disabled) {
-        if (mHelper.setDisabledByAppOps(disabled)) {
-            notifyChanged();
-        }
-    }
-
     public boolean isDisabledByAdmin() {
         return mHelper.isDisabledByAdmin();
     }
 
+    public boolean isDisabledByEcm() {
+        return mHelper.isDisabledByEcm();
+    }
+
     public int getUid() {
         return mHelper != null ? mHelper.uid : Process.INVALID_UID;
     }
@@ -128,4 +143,16 @@
     public String getPackageName() {
         return mHelper != null ? mHelper.packageName : null;
     }
+
+    /**
+     * @deprecated TODO(b/308921175): This will be deleted with the
+     * {@link android.security.Flags#extendEcmToAllSettings} feature flag. Do not use for any new
+     * code.
+     */
+    @Deprecated
+    public void setDisabledByAppOps(boolean disabled) {
+        if (mHelper.setDisabledByAppOps(disabled)) {
+            notifyChanged();
+        }
+    }
 }
diff --git a/packages/SettingsLib/src/com/android/settingslib/RestrictedPreferenceHelper.java b/packages/SettingsLib/src/com/android/settingslib/RestrictedPreferenceHelper.java
index 29ea25e..a479269 100644
--- a/packages/SettingsLib/src/com/android/settingslib/RestrictedPreferenceHelper.java
+++ b/packages/SettingsLib/src/com/android/settingslib/RestrictedPreferenceHelper.java
@@ -17,10 +17,12 @@
 package com.android.settingslib;
 
 import static android.app.admin.DevicePolicyResources.Strings.Settings.CONTROLLED_BY_ADMIN_SUMMARY;
+
 import static com.android.settingslib.RestrictedLockUtils.EnforcedAdmin;
 
 import android.app.admin.DevicePolicyManager;
 import android.content.Context;
+import android.content.Intent;
 import android.content.res.TypedArray;
 import android.os.Build;
 import android.os.UserHandle;
@@ -52,7 +54,8 @@
     private String mAttrUserRestriction = null;
     private boolean mDisabledSummary = false;
 
-    private boolean mDisabledByAppOps;
+    private boolean mDisabledByEcm;
+    private Intent mDisabledByEcmIntent = null;
 
     public RestrictedPreferenceHelper(Context context, Preference preference,
             AttributeSet attrs, String packageName, int uid) {
@@ -101,7 +104,7 @@
      * Modify PreferenceViewHolder to add padlock if restriction is disabled.
      */
     public void onBindViewHolder(PreferenceViewHolder holder) {
-        if (mDisabledByAdmin || mDisabledByAppOps) {
+        if (mDisabledByAdmin || mDisabledByEcm) {
             holder.itemView.setEnabled(true);
         }
         if (mDisabledSummary) {
@@ -112,7 +115,7 @@
                         : mContext.getString(R.string.disabled_by_admin_summary_text);
                 if (mDisabledByAdmin) {
                     summaryView.setText(disabledText);
-                } else if (mDisabledByAppOps) {
+                } else if (mDisabledByEcm) {
                     summaryView.setText(R.string.disabled_by_app_ops_text);
                 } else if (TextUtils.equals(disabledText, summaryView.getText())) {
                     // It's previously set to disabled text, clear it.
@@ -144,7 +147,12 @@
             RestrictedLockUtils.sendShowAdminSupportDetailsIntent(mContext, mEnforcedAdmin);
             return true;
         }
-        if (mDisabledByAppOps) {
+        if (mDisabledByEcm) {
+            if (android.security.Flags.extendEcmToAllSettings()) {
+                mContext.startActivity(mDisabledByEcmIntent);
+                return true;
+            }
+
             RestrictedLockUtilsInternal.sendShowRestrictedSettingDialogIntent(mContext, packageName,
                     uid);
             return true;
@@ -174,6 +182,20 @@
     }
 
     /**
+     * Checks if the given setting is subject to Enhanced Confirmation Mode restrictions for this
+     * package. Marks the preference as disabled if so.
+     * @param restriction The key identifying the setting
+     * @param packageName the package to check the restriction for
+     * @param uid the uid of the package
+     */
+    public void checkEcmRestrictionAndSetDisabled(String restriction, String packageName, int uid) {
+        updatePackageDetails(packageName, uid);
+        Intent intent = RestrictedLockUtilsInternal.checkIfRequiresEnhancedConfirmation(
+                mContext, restriction, uid, packageName);
+        setDisabledByEcm(intent);
+    }
+
+    /**
      * @return EnforcedAdmin if we have been passed the restriction in the xml.
      */
     public EnforcedAdmin checkRestrictionEnforced() {
@@ -211,10 +233,19 @@
         return changed;
     }
 
-    public boolean setDisabledByAppOps(boolean disabled) {
+    /**
+     * Disable the preference based on the passed in Intent
+     * @param disabledIntent The intent which is started when the user clicks the disabled
+     * preference. If it is {@code null}, then this preference will be enabled. Otherwise, it will
+     * be disabled.
+     * @return true if the disabled state was changed.
+     */
+    public boolean setDisabledByEcm(Intent disabledIntent) {
+        boolean disabled = disabledIntent != null;
         boolean changed = false;
-        if (mDisabledByAppOps != disabled) {
-            mDisabledByAppOps = disabled;
+        if (mDisabledByEcm != disabled) {
+            mDisabledByEcmIntent = disabledIntent;
+            mDisabledByEcm = disabled;
             changed = true;
             updateDisabledState();
         }
@@ -226,8 +257,8 @@
         return mDisabledByAdmin;
     }
 
-    public boolean isDisabledByAppOps() {
-        return mDisabledByAppOps;
+    public boolean isDisabledByEcm() {
+        return mDisabledByEcm;
     }
 
     public void updatePackageDetails(String packageName, int uid) {
@@ -236,13 +267,31 @@
     }
 
     private void updateDisabledState() {
+        boolean isEnabled = !(mDisabledByAdmin || mDisabledByEcm);
         if (!(mPreference instanceof RestrictedTopLevelPreference)) {
-            mPreference.setEnabled(!(mDisabledByAdmin || mDisabledByAppOps));
+            mPreference.setEnabled(isEnabled);
         }
 
         if (mPreference instanceof PrimarySwitchPreference) {
-            ((PrimarySwitchPreference) mPreference)
-                    .setSwitchEnabled(!(mDisabledByAdmin || mDisabledByAppOps));
+            ((PrimarySwitchPreference) mPreference).setSwitchEnabled(isEnabled);
         }
     }
+
+
+    /**
+     * @deprecated TODO(b/308921175): This will be deleted with the
+     * {@link android.security.Flags#extendEcmToAllSettings} feature flag. Do not use for any new
+     * code.
+     */
+    @Deprecated
+    public boolean setDisabledByAppOps(boolean disabled) {
+        boolean changed = false;
+        if (mDisabledByEcm != disabled) {
+            mDisabledByEcm = disabled;
+            changed = true;
+            updateDisabledState();
+        }
+
+        return changed;
+    }
 }
diff --git a/packages/SettingsLib/src/com/android/settingslib/RestrictedSwitchPreference.java b/packages/SettingsLib/src/com/android/settingslib/RestrictedSwitchPreference.java
index 60321eb..3b8f665 100644
--- a/packages/SettingsLib/src/com/android/settingslib/RestrictedSwitchPreference.java
+++ b/packages/SettingsLib/src/com/android/settingslib/RestrictedSwitchPreference.java
@@ -197,6 +197,17 @@
         mHelper.checkRestrictionAndSetDisabled(userRestriction, userId);
     }
 
+    /**
+     * Checks if the given setting is subject to Enhanced Confirmation Mode restrictions for this
+     * package. Marks the preference as disabled if so.
+     * @param restriction The key identifying the setting
+     * @param packageName the package to check the restriction for
+     * @param uid the uid of the package
+     */
+    public void checkEcmRestrictionAndSetDisabled(String restriction, String packageName, int uid) {
+        mHelper.checkEcmRestrictionAndSetDisabled(restriction, packageName, uid);
+    }
+
     @Override
     public void setEnabled(boolean enabled) {
         boolean changed = false;
@@ -204,8 +215,8 @@
             mHelper.setDisabledByAdmin(null);
             changed = true;
         }
-        if (enabled && isDisabledByAppOps()) {
-            mHelper.setDisabledByAppOps(false);
+        if (enabled && isDisabledByEcm()) {
+            mHelper.setDisabledByEcm(null);
             changed = true;
         }
         if (!changed) {
@@ -223,25 +234,50 @@
         return mHelper.isDisabledByAdmin();
     }
 
+    public boolean isDisabledByEcm() {
+        return mHelper.isDisabledByEcm();
+    }
+
+    /**
+     * @deprecated TODO(b/308921175): This will be deleted with the
+     * {@link android.security.Flags#extendEcmToAllSettings} feature flag. Do not use for any new
+     * code.
+     */
+    @Deprecated
     private void setDisabledByAppOps(boolean disabled) {
         if (mHelper.setDisabledByAppOps(disabled)) {
             notifyChanged();
         }
     }
 
-    public boolean isDisabledByAppOps() {
-        return mHelper.isDisabledByAppOps();
-    }
-
+    /**
+     * @deprecated TODO(b/308921175): This will be deleted with the
+     * {@link android.security.Flags#extendEcmToAllSettings} feature flag. Do not use for any new
+     * code.
+     */
+    @Deprecated
     public int getUid() {
         return mHelper != null ? mHelper.uid : Process.INVALID_UID;
     }
 
+    /**
+     * @deprecated TODO(b/308921175): This will be deleted with the
+     * {@link android.security.Flags#extendEcmToAllSettings} feature flag. Do not use for any new
+     * code.
+     */
+    @Deprecated
     public String getPackageName() {
         return mHelper != null ? mHelper.packageName : null;
     }
 
-    /** Updates enabled state based on associated package. */
+    /**
+     * Updates enabled state based on associated package
+     *
+     * @deprecated TODO(b/308921175): This will be deleted with the
+     * {@link android.security.Flags#extendEcmToAllSettings} feature flag. Do not use for any new
+     * code.
+     */
+    @Deprecated
     public void updateState(
             @NonNull String packageName, int uid, boolean isEnableAllowed, boolean isEnabled) {
         mHelper.updatePackageDetails(packageName, uid);
@@ -258,7 +294,7 @@
             setEnabled(false);
         } else if (isEnabled) {
             setEnabled(true);
-        } else if (appOpsAllowed && isDisabledByAppOps()) {
+        } else if (appOpsAllowed && isDisabledByEcm()) {
             setEnabled(true);
         } else if (!appOpsAllowed){
             setDisabledByAppOps(true);
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcast.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcast.java
index 69b61c7..49ac0f8 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcast.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcast.java
@@ -28,10 +28,11 @@
 import android.bluetooth.BluetoothLeBroadcastAssistant;
 import android.bluetooth.BluetoothLeBroadcastMetadata;
 import android.bluetooth.BluetoothLeBroadcastReceiveState;
+import android.bluetooth.BluetoothLeBroadcastSettings;
 import android.bluetooth.BluetoothLeBroadcastSubgroup;
+import android.bluetooth.BluetoothLeBroadcastSubgroupSettings;
 import android.bluetooth.BluetoothProfile;
 import android.bluetooth.BluetoothProfile.ServiceListener;
-import android.bluetooth.BluetoothStatusCodes;
 import android.content.ContentResolver;
 import android.content.Context;
 import android.database.ContentObserver;
@@ -42,26 +43,30 @@
 import android.provider.Settings;
 import android.text.TextUtils;
 import android.util.Log;
-import android.util.Pair;
 
+import androidx.annotation.Nullable;
 import androidx.annotation.RequiresApi;
 
 import com.android.settingslib.R;
 
+import com.google.common.collect.ImmutableList;
+
 import java.nio.charset.StandardCharsets;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collections;
 import java.util.List;
+import java.util.Map;
 import java.util.UUID;
+import java.util.concurrent.ConcurrentHashMap;
 import java.util.concurrent.Executor;
 import java.util.concurrent.Executors;
 import java.util.concurrent.ThreadLocalRandom;
 
 /**
- * LocalBluetoothLeBroadcast provides an interface between the Settings app
- * and the functionality of the local {@link BluetoothLeBroadcast}.
- * Use the {@link BluetoothLeBroadcast.Callback} to get the result callback.
+ * LocalBluetoothLeBroadcast provides an interface between the Settings app and the functionality of
+ * the local {@link BluetoothLeBroadcast}. Use the {@link BluetoothLeBroadcast.Callback} to get the
+ * result callback.
  */
 public class LocalBluetoothLeBroadcast implements LocalBluetoothProfile {
     private static final String TAG = "LocalBluetoothLeBroadcast";
@@ -74,11 +79,12 @@
     // Order of this profile in device profiles list
     private static final int ORDINAL = 1;
     private static final int UNKNOWN_VALUE_PLACEHOLDER = -1;
-    private static final Uri[] SETTINGS_URIS = new Uri[]{
-            Settings.Secure.getUriFor(Settings.Secure.BLUETOOTH_LE_BROADCAST_PROGRAM_INFO),
-            Settings.Secure.getUriFor(Settings.Secure.BLUETOOTH_LE_BROADCAST_CODE),
-            Settings.Secure.getUriFor(Settings.Secure.BLUETOOTH_LE_BROADCAST_APP_SOURCE_NAME),
-    };
+    private static final Uri[] SETTINGS_URIS =
+            new Uri[] {
+                Settings.Secure.getUriFor(Settings.Secure.BLUETOOTH_LE_BROADCAST_PROGRAM_INFO),
+                Settings.Secure.getUriFor(Settings.Secure.BLUETOOTH_LE_BROADCAST_CODE),
+                Settings.Secure.getUriFor(Settings.Secure.BLUETOOTH_LE_BROADCAST_APP_SOURCE_NAME),
+            };
 
     private BluetoothLeBroadcast mServiceBroadcast;
     private BluetoothLeBroadcastAssistant mServiceBroadcastAssistant;
@@ -95,62 +101,82 @@
     private Executor mExecutor;
     private ContentResolver mContentResolver;
     private ContentObserver mSettingsObserver;
+    // Cached broadcast callbacks being register before service is connected.
+    private Map<BluetoothLeBroadcast.Callback, Executor> mCachedBroadcastCallbackExecutorMap =
+            new ConcurrentHashMap<>();
 
-    private final ServiceListener mServiceListener = new ServiceListener() {
-        @Override
-        public void onServiceConnected(int profile, BluetoothProfile proxy) {
-            if (DEBUG) {
-                Log.d(TAG, "Bluetooth service connected: " + profile);
-            }
-            if ((profile == BluetoothProfile.LE_AUDIO_BROADCAST) && !mIsBroadcastProfileReady) {
-                mServiceBroadcast = (BluetoothLeBroadcast) proxy;
-                mIsBroadcastProfileReady = true;
-                registerServiceCallBack(mExecutor, mBroadcastCallback);
-                List<BluetoothLeBroadcastMetadata> metadata = getAllBroadcastMetadata();
-                if (!metadata.isEmpty()) {
-                    updateBroadcastInfoFromBroadcastMetadata(metadata.get(0));
+    private final ServiceListener mServiceListener =
+            new ServiceListener() {
+                @Override
+                public void onServiceConnected(int profile, BluetoothProfile proxy) {
+                    if (DEBUG) {
+                        Log.d(TAG, "Bluetooth service connected: " + profile);
+                    }
+                    if ((profile == BluetoothProfile.LE_AUDIO_BROADCAST)
+                            && !mIsBroadcastProfileReady) {
+                        mServiceBroadcast = (BluetoothLeBroadcast) proxy;
+                        mIsBroadcastProfileReady = true;
+                        registerServiceCallBack(mExecutor, mBroadcastCallback);
+                        List<BluetoothLeBroadcastMetadata> metadata = getAllBroadcastMetadata();
+                        if (!metadata.isEmpty()) {
+                            updateBroadcastInfoFromBroadcastMetadata(metadata.get(0));
+                        }
+                        registerContentObserver();
+                        if (DEBUG) {
+                            Log.d(
+                                    TAG,
+                                    "onServiceConnected: register "
+                                            + "mCachedBroadcastCallbackExecutorMap = "
+                                            + mCachedBroadcastCallbackExecutorMap);
+                        }
+                        mCachedBroadcastCallbackExecutorMap.forEach(
+                                (callback, executor) ->
+                                        registerServiceCallBack(executor, callback));
+                    } else if ((profile == BluetoothProfile.LE_AUDIO_BROADCAST_ASSISTANT)
+                            && !mIsBroadcastAssistantProfileReady) {
+                        mIsBroadcastAssistantProfileReady = true;
+                        mServiceBroadcastAssistant = (BluetoothLeBroadcastAssistant) proxy;
+                        registerBroadcastAssistantCallback(mExecutor, mBroadcastAssistantCallback);
+                    }
                 }
-                registerContentObserver();
-            } else if ((profile == BluetoothProfile.LE_AUDIO_BROADCAST_ASSISTANT)
-                    && !mIsBroadcastAssistantProfileReady) {
-                mIsBroadcastAssistantProfileReady = true;
-                mServiceBroadcastAssistant = (BluetoothLeBroadcastAssistant) proxy;
-                registerBroadcastAssistantCallback(mExecutor, mBroadcastAssistantCallback);
-            }
-        }
 
-        @Override
-        public void onServiceDisconnected(int profile) {
-            if (DEBUG) {
-                Log.d(TAG, "Bluetooth service disconnected");
-            }
-            if ((profile == BluetoothProfile.LE_AUDIO_BROADCAST) && mIsBroadcastProfileReady) {
-                mIsBroadcastProfileReady = false;
-                unregisterServiceCallBack(mBroadcastCallback);
-            }
-            if ((profile == BluetoothProfile.LE_AUDIO_BROADCAST_ASSISTANT)
-                    && mIsBroadcastAssistantProfileReady) {
-                mIsBroadcastAssistantProfileReady = false;
-                unregisterBroadcastAssistantCallback(mBroadcastAssistantCallback);
-            }
+                @Override
+                public void onServiceDisconnected(int profile) {
+                    if (DEBUG) {
+                        Log.d(TAG, "Bluetooth service disconnected: " + profile);
+                    }
+                    if ((profile == BluetoothProfile.LE_AUDIO_BROADCAST)
+                            && mIsBroadcastProfileReady) {
+                        mIsBroadcastProfileReady = false;
+                        unregisterServiceCallBack(mBroadcastCallback);
+                        mCachedBroadcastCallbackExecutorMap.clear();
+                    }
+                    if ((profile == BluetoothProfile.LE_AUDIO_BROADCAST_ASSISTANT)
+                            && mIsBroadcastAssistantProfileReady) {
+                        mIsBroadcastAssistantProfileReady = false;
+                        unregisterBroadcastAssistantCallback(mBroadcastAssistantCallback);
+                    }
 
-            if (!mIsBroadcastAssistantProfileReady && !mIsBroadcastProfileReady) {
-                unregisterContentObserver();
-            }
-        }
-    };
+                    if (!mIsBroadcastAssistantProfileReady && !mIsBroadcastProfileReady) {
+                        unregisterContentObserver();
+                    }
+                }
+            };
 
     private final BluetoothLeBroadcast.Callback mBroadcastCallback =
             new BluetoothLeBroadcast.Callback() {
                 @Override
                 public void onBroadcastStarted(int reason, int broadcastId) {
                     if (DEBUG) {
-                        Log.d(TAG,
-                                "onBroadcastStarted(), reason = " + reason + ", broadcastId = "
+                        Log.d(
+                                TAG,
+                                "onBroadcastStarted(), reason = "
+                                        + reason
+                                        + ", broadcastId = "
                                         + broadcastId);
                     }
                     setLatestBroadcastId(broadcastId);
-                    setAppSourceName(mNewAppSourceName, /*updateContentResolver=*/ true);
+                    setAppSourceName(mNewAppSourceName, /* updateContentResolver= */ true);
                 }
 
                 @Override
@@ -161,8 +187,8 @@
                 }
 
                 @Override
-                public void onBroadcastMetadataChanged(int broadcastId,
-                        @NonNull BluetoothLeBroadcastMetadata metadata) {
+                public void onBroadcastMetadataChanged(
+                        int broadcastId, @NonNull BluetoothLeBroadcastMetadata metadata) {
                     if (DEBUG) {
                         Log.d(TAG, "onBroadcastMetadataChanged(), broadcastId = " + broadcastId);
                     }
@@ -172,8 +198,11 @@
                 @Override
                 public void onBroadcastStopped(int reason, int broadcastId) {
                     if (DEBUG) {
-                        Log.d(TAG,
-                                "onBroadcastStopped(), reason = " + reason + ", broadcastId = "
+                        Log.d(
+                                TAG,
+                                "onBroadcastStopped(), reason = "
+                                        + reason
+                                        + ", broadcastId = "
                                         + broadcastId);
                     }
 
@@ -191,37 +220,42 @@
                 @Override
                 public void onBroadcastUpdated(int reason, int broadcastId) {
                     if (DEBUG) {
-                        Log.d(TAG,
-                                "onBroadcastUpdated(), reason = " + reason + ", broadcastId = "
+                        Log.d(
+                                TAG,
+                                "onBroadcastUpdated(), reason = "
+                                        + reason
+                                        + ", broadcastId = "
                                         + broadcastId);
                     }
                     setLatestBroadcastId(broadcastId);
-                    setAppSourceName(mNewAppSourceName, /*updateContentResolver=*/ true);
+                    setAppSourceName(mNewAppSourceName, /* updateContentResolver= */ true);
                 }
 
                 @Override
                 public void onBroadcastUpdateFailed(int reason, int broadcastId) {
                     if (DEBUG) {
-                        Log.d(TAG,
-                                "onBroadcastUpdateFailed(), reason = " + reason + ", broadcastId = "
+                        Log.d(
+                                TAG,
+                                "onBroadcastUpdateFailed(), reason = "
+                                        + reason
+                                        + ", broadcastId = "
                                         + broadcastId);
                     }
                 }
 
                 @Override
-                public void onPlaybackStarted(int reason, int broadcastId) {
-                }
+                public void onPlaybackStarted(int reason, int broadcastId) {}
 
                 @Override
-                public void onPlaybackStopped(int reason, int broadcastId) {
-                }
+                public void onPlaybackStopped(int reason, int broadcastId) {}
             };
 
     private final BluetoothLeBroadcastAssistant.Callback mBroadcastAssistantCallback =
             new BluetoothLeBroadcastAssistant.Callback() {
                 @Override
-                public void onSourceAdded(@NonNull BluetoothDevice sink, int sourceId,
-                        int reason) {}
+                public void onSourceAdded(
+                        @NonNull BluetoothDevice sink, int sourceId, int reason) {}
+
                 @Override
                 public void onSearchStarted(int reason) {}
 
@@ -238,38 +272,65 @@
                 public void onSourceFound(@NonNull BluetoothLeBroadcastMetadata source) {}
 
                 @Override
-                public void onSourceAddFailed(@NonNull BluetoothDevice sink,
-                        @NonNull BluetoothLeBroadcastMetadata source, int reason) {}
-
-                @Override
-                public void onSourceModified(@NonNull BluetoothDevice sink, int sourceId,
+                public void onSourceAddFailed(
+                        @NonNull BluetoothDevice sink,
+                        @NonNull BluetoothLeBroadcastMetadata source,
                         int reason) {}
 
                 @Override
-                public void onSourceModifyFailed(@NonNull BluetoothDevice sink, int sourceId,
-                        int reason) {}
+                public void onSourceModified(
+                        @NonNull BluetoothDevice sink, int sourceId, int reason) {}
 
                 @Override
-                public void onSourceRemoved(@NonNull BluetoothDevice sink, int sourceId,
-                        int reason) {
+                public void onSourceModifyFailed(
+                        @NonNull BluetoothDevice sink, int sourceId, int reason) {}
+
+                @Override
+                public void onSourceRemoved(
+                        @NonNull BluetoothDevice sink, int sourceId, int reason) {
                     if (DEBUG) {
-                        Log.d(TAG, "onSourceRemoved(), sink = " + sink + ", reason = "
-                                + reason + ", sourceId = " + sourceId);
+                        Log.d(
+                                TAG,
+                                "onSourceRemoved(), sink = "
+                                        + sink
+                                        + ", reason = "
+                                        + reason
+                                        + ", sourceId = "
+                                        + sourceId);
                     }
                 }
 
                 @Override
-                public void onSourceRemoveFailed(@NonNull BluetoothDevice sink, int sourceId,
-                        int reason) {
+                public void onSourceRemoveFailed(
+                        @NonNull BluetoothDevice sink, int sourceId, int reason) {
                     if (DEBUG) {
-                        Log.d(TAG, "onSourceRemoveFailed(), sink = " + sink + ", reason = "
-                                + reason + ", sourceId = " + sourceId);
+                        Log.d(
+                                TAG,
+                                "onSourceRemoveFailed(), sink = "
+                                        + sink
+                                        + ", reason = "
+                                        + reason
+                                        + ", sourceId = "
+                                        + sourceId);
                     }
                 }
 
                 @Override
-                public void onReceiveStateChanged(@NonNull BluetoothDevice sink, int sourceId,
-                        @NonNull BluetoothLeBroadcastReceiveState state) {}
+                public void onReceiveStateChanged(
+                        @NonNull BluetoothDevice sink,
+                        int sourceId,
+                        @NonNull BluetoothLeBroadcastReceiveState state) {
+                    if (DEBUG) {
+                        Log.d(
+                                TAG,
+                                "onReceiveStateChanged(), sink = "
+                                        + sink
+                                        + ", sourceId = "
+                                        + sourceId
+                                        + ", state = "
+                                        + state);
+                    }
+                }
             };
 
     private class BroadcastSettingsObserver extends ContentObserver {
@@ -296,8 +357,8 @@
         BluetoothAdapter.getDefaultAdapter()
                 .getProfileProxy(context, mServiceListener, BluetoothProfile.LE_AUDIO_BROADCAST);
         BluetoothAdapter.getDefaultAdapter()
-                .getProfileProxy(context, mServiceListener,
-                BluetoothProfile.LE_AUDIO_BROADCAST_ASSISTANT);
+                .getProfileProxy(
+                        context, mServiceListener, BluetoothProfile.LE_AUDIO_BROADCAST_ASSISTANT);
     }
 
     /**
@@ -312,20 +373,91 @@
         }
         String programInfo = getProgramInfo();
         if (DEBUG) {
-            Log.d(TAG,
-                    "startBroadcast: language = " + language + " ,programInfo = " + programInfo);
+            Log.d(TAG, "startBroadcast: language = " + language + " ,programInfo = " + programInfo);
         }
         buildContentMetadata(language, programInfo);
-        mServiceBroadcast.startBroadcast(mBluetoothLeAudioContentMetadata,
+        mServiceBroadcast.startBroadcast(
+                mBluetoothLeAudioContentMetadata,
                 (mBroadcastCode != null && mBroadcastCode.length > 0) ? mBroadcastCode : null);
     }
 
+    /**
+     * Start the private Broadcast for personal audio sharing or qr code sharing.
+     *
+     * <p>The broadcast will use random string for both broadcast name and subgroup program info;
+     * The broadcast will use random string for broadcast code; The broadcast will only have one
+     * subgroup due to system limitation; The subgroup language will be null.
+     *
+     * <p>If the system started the LE Broadcast, then the system calls the corresponding callback
+     * {@link BluetoothLeBroadcast.Callback}.
+     */
+    public void startPrivateBroadcast(int quality) {
+        mNewAppSourceName = "Sharing audio";
+        if (mServiceBroadcast == null) {
+            Log.d(TAG, "The BluetoothLeBroadcast is null when starting the private broadcast.");
+            return;
+        }
+        if (mServiceBroadcast.getAllBroadcastMetadata().size()
+                >= mServiceBroadcast.getMaximumNumberOfBroadcasts()) {
+            Log.d(TAG, "Skip starting the broadcast due to number limit.");
+            return;
+        }
+        String programInfo = getProgramInfo();
+        if (DEBUG) {
+            Log.d(TAG, "startBroadcast: language = null ,programInfo = " + programInfo);
+        }
+        // Current broadcast framework only support one subgroup
+        BluetoothLeBroadcastSubgroupSettings subgroupSettings =
+                buildBroadcastSubgroupSettings(/* language= */ null, programInfo, quality);
+        BluetoothLeBroadcastSettings settings =
+                buildBroadcastSettings(
+                        true, // TODO: set to false after framework fix
+                        TextUtils.isEmpty(programInfo) ? null : programInfo,
+                        (mBroadcastCode != null && mBroadcastCode.length > 0)
+                                ? mBroadcastCode
+                                : null,
+                        ImmutableList.of(subgroupSettings));
+        mServiceBroadcast.startBroadcast(settings);
+    }
+
+    private BluetoothLeBroadcastSettings buildBroadcastSettings(
+            boolean isPublic,
+            @Nullable String broadcastName,
+            @Nullable byte[] broadcastCode,
+            List<BluetoothLeBroadcastSubgroupSettings> subgroupSettingsList) {
+        BluetoothLeBroadcastSettings.Builder builder =
+                new BluetoothLeBroadcastSettings.Builder()
+                        .setPublicBroadcast(isPublic)
+                        .setBroadcastName(broadcastName)
+                        .setBroadcastCode(broadcastCode);
+        for (BluetoothLeBroadcastSubgroupSettings subgroupSettings : subgroupSettingsList) {
+            builder.addSubgroupSettings(subgroupSettings);
+        }
+        return builder.build();
+    }
+
+    private BluetoothLeBroadcastSubgroupSettings buildBroadcastSubgroupSettings(
+            @Nullable String language, @Nullable String programInfo, int quality) {
+        BluetoothLeAudioContentMetadata metadata =
+                new BluetoothLeAudioContentMetadata.Builder()
+                        .setLanguage(language)
+                        .setProgramInfo(programInfo)
+                        .build();
+        // Current broadcast framework only support one subgroup, thus we still maintain the latest
+        // metadata to keep legacy UI working.
+        mBluetoothLeAudioContentMetadata = metadata;
+        return new BluetoothLeBroadcastSubgroupSettings.Builder()
+                .setPreferredQuality(quality)
+                .setContentMetadata(mBluetoothLeAudioContentMetadata)
+                .build();
+    }
+
     public String getProgramInfo() {
         return mProgramInfo;
     }
 
     public void setProgramInfo(String programInfo) {
-        setProgramInfo(programInfo, /*updateContentResolver=*/ true);
+        setProgramInfo(programInfo, /* updateContentResolver= */ true);
     }
 
     private void setProgramInfo(String programInfo, boolean updateContentResolver) {
@@ -344,8 +476,10 @@
                 Log.d(TAG, "mContentResolver is null");
                 return;
             }
-            Settings.Secure.putString(mContentResolver,
-                    Settings.Secure.BLUETOOTH_LE_BROADCAST_PROGRAM_INFO, programInfo);
+            Settings.Secure.putString(
+                    mContentResolver,
+                    Settings.Secure.BLUETOOTH_LE_BROADCAST_PROGRAM_INFO,
+                    programInfo);
         }
     }
 
@@ -354,7 +488,7 @@
     }
 
     public void setBroadcastCode(byte[] broadcastCode) {
-        setBroadcastCode(broadcastCode, /*updateContentResolver=*/ true);
+        setBroadcastCode(broadcastCode, /* updateContentResolver= */ true);
     }
 
     private void setBroadcastCode(byte[] broadcastCode, boolean updateContentResolver) {
@@ -372,7 +506,9 @@
                 Log.d(TAG, "mContentResolver is null");
                 return;
             }
-            Settings.Secure.putString(mContentResolver, Settings.Secure.BLUETOOTH_LE_BROADCAST_CODE,
+            Settings.Secure.putString(
+                    mContentResolver,
+                    Settings.Secure.BLUETOOTH_LE_BROADCAST_CODE,
                     new String(broadcastCode, StandardCharsets.UTF_8));
         }
     }
@@ -401,8 +537,10 @@
                 Log.d(TAG, "mContentResolver is null");
                 return;
             }
-            Settings.Secure.putString(mContentResolver,
-                    Settings.Secure.BLUETOOTH_LE_BROADCAST_APP_SOURCE_NAME, mAppSourceName);
+            Settings.Secure.putString(
+                    mContentResolver,
+                    Settings.Secure.BLUETOOTH_LE_BROADCAST_APP_SOURCE_NAME,
+                    mAppSourceName);
         }
     }
 
@@ -427,10 +565,11 @@
         if (mBluetoothLeBroadcastMetadata == null) {
             final List<BluetoothLeBroadcastMetadata> metadataList =
                     mServiceBroadcast.getAllBroadcastMetadata();
-            mBluetoothLeBroadcastMetadata = metadataList.stream()
-                    .filter(i -> i.getBroadcastId() == mBroadcastId)
-                    .findFirst()
-                    .orElse(null);
+            mBluetoothLeBroadcastMetadata =
+                    metadataList.stream()
+                            .filter(i -> i.getBroadcastId() == mBroadcastId)
+                            .findFirst()
+                            .orElse(null);
         }
         return mBluetoothLeBroadcastMetadata;
     }
@@ -440,22 +579,27 @@
             Log.d(TAG, "updateBroadcastInfoFromContentProvider: mContentResolver is null");
             return;
         }
-        String programInfo = Settings.Secure.getString(mContentResolver,
-                Settings.Secure.BLUETOOTH_LE_BROADCAST_PROGRAM_INFO);
+        String programInfo =
+                Settings.Secure.getString(
+                        mContentResolver, Settings.Secure.BLUETOOTH_LE_BROADCAST_PROGRAM_INFO);
         if (programInfo == null) {
             programInfo = getDefaultValueOfProgramInfo();
         }
-        setProgramInfo(programInfo, /*updateContentResolver=*/ false);
+        setProgramInfo(programInfo, /* updateContentResolver= */ false);
 
-        String prefBroadcastCode = Settings.Secure.getString(mContentResolver,
-                Settings.Secure.BLUETOOTH_LE_BROADCAST_CODE);
-        byte[] broadcastCode = (prefBroadcastCode == null) ? getDefaultValueOfBroadcastCode()
-                : prefBroadcastCode.getBytes(StandardCharsets.UTF_8);
-        setBroadcastCode(broadcastCode, /*updateContentResolver=*/ false);
+        String prefBroadcastCode =
+                Settings.Secure.getString(
+                        mContentResolver, Settings.Secure.BLUETOOTH_LE_BROADCAST_CODE);
+        byte[] broadcastCode =
+                (prefBroadcastCode == null)
+                        ? getDefaultValueOfBroadcastCode()
+                        : prefBroadcastCode.getBytes(StandardCharsets.UTF_8);
+        setBroadcastCode(broadcastCode, /* updateContentResolver= */ false);
 
-        String appSourceName = Settings.Secure.getString(mContentResolver,
-                Settings.Secure.BLUETOOTH_LE_BROADCAST_APP_SOURCE_NAME);
-        setAppSourceName(appSourceName, /*updateContentResolver=*/ false);
+        String appSourceName =
+                Settings.Secure.getString(
+                        mContentResolver, Settings.Secure.BLUETOOTH_LE_BROADCAST_APP_SOURCE_NAME);
+        setAppSourceName(appSourceName, /* updateContentResolver= */ false);
     }
 
     private void updateBroadcastInfoFromBroadcastMetadata(
@@ -474,12 +618,12 @@
         }
         BluetoothLeAudioContentMetadata contentMetadata = subgroup.get(0).getContentMetadata();
         setProgramInfo(contentMetadata.getProgramInfo());
-        setAppSourceName(getAppSourceName(), /*updateContentResolver=*/ true);
+        setAppSourceName(getAppSourceName(), /* updateContentResolver= */ true);
     }
 
     /**
-     * Stop the latest LE Broadcast. If the system stopped the LE Broadcast, then the system
-     * calls the corresponding callback {@link BluetoothLeBroadcast.Callback}.
+     * Stop the latest LE Broadcast. If the system stopped the LE Broadcast, then the system calls
+     * the corresponding callback {@link BluetoothLeBroadcast.Callback}.
      */
     public void stopLatestBroadcast() {
         stopBroadcast(mBroadcastId);
@@ -511,7 +655,8 @@
         }
         String programInfo = getProgramInfo();
         if (DEBUG) {
-            Log.d(TAG,
+            Log.d(
+                    TAG,
                     "updateBroadcast: language = " + language + " ,programInfo = " + programInfo);
         }
         mNewAppSourceName = appSourceName;
@@ -519,50 +664,79 @@
         mServiceBroadcast.updateBroadcast(mBroadcastId, mBluetoothLeAudioContentMetadata);
     }
 
-    public void registerServiceCallBack(@NonNull @CallbackExecutor Executor executor,
-            @NonNull BluetoothLeBroadcast.Callback callback) {
-        if (mServiceBroadcast == null) {
-            Log.d(TAG, "The BluetoothLeBroadcast is null.");
-            return;
-        }
-
-        mServiceBroadcast.registerCallback(executor, callback);
-    }
-
     /**
-     * Register Broadcast Assistant Callbacks to track it's state and receivers
+     * Register Broadcast Callbacks to track its state and receivers
      *
      * @param executor Executor object for callback
      * @param callback Callback object to be registered
      */
-    public void registerBroadcastAssistantCallback(@NonNull @CallbackExecutor Executor executor,
+    public void registerServiceCallBack(
+            @NonNull @CallbackExecutor Executor executor,
+            @NonNull BluetoothLeBroadcast.Callback callback) {
+        if (mServiceBroadcast == null) {
+            Log.d(TAG, "registerServiceCallBack failed, the BluetoothLeBroadcast is null.");
+            mCachedBroadcastCallbackExecutorMap.putIfAbsent(callback, executor);
+            return;
+        }
+
+        try {
+            mServiceBroadcast.registerCallback(executor, callback);
+        } catch (IllegalArgumentException e) {
+            Log.w(TAG, "registerServiceCallBack failed. " + e.getMessage());
+        }
+    }
+
+    /**
+     * Register Broadcast Assistant Callbacks to track its state and receivers
+     *
+     * @param executor Executor object for callback
+     * @param callback Callback object to be registered
+     */
+    private void registerBroadcastAssistantCallback(
+            @NonNull @CallbackExecutor Executor executor,
             @NonNull BluetoothLeBroadcastAssistant.Callback callback) {
         if (mServiceBroadcastAssistant == null) {
-            Log.d(TAG, "The BluetoothLeBroadcastAssisntant is null.");
+            Log.d(
+                    TAG,
+                    "registerBroadcastAssistantCallback failed, "
+                            + "the BluetoothLeBroadcastAssistant is null.");
             return;
         }
 
         mServiceBroadcastAssistant.registerCallback(executor, callback);
     }
 
-    public void unregisterServiceCallBack(@NonNull BluetoothLeBroadcast.Callback callback) {
-        if (mServiceBroadcast == null) {
-            Log.d(TAG, "The BluetoothLeBroadcast is null.");
-            return;
-        }
-
-        mServiceBroadcast.unregisterCallback(callback);
-    }
-
     /**
-     * Unregister previousely registered Broadcast Assistant Callbacks
+     * Unregister previously registered Broadcast Callbacks
      *
      * @param callback Callback object to be unregistered
      */
-    public void unregisterBroadcastAssistantCallback(
+    public void unregisterServiceCallBack(@NonNull BluetoothLeBroadcast.Callback callback) {
+        mCachedBroadcastCallbackExecutorMap.remove(callback);
+        if (mServiceBroadcast == null) {
+            Log.d(TAG, "unregisterServiceCallBack failed, the BluetoothLeBroadcast is null.");
+            return;
+        }
+
+        try {
+            mServiceBroadcast.unregisterCallback(callback);
+        } catch (IllegalArgumentException e) {
+            Log.w(TAG, "unregisterServiceCallBack failed. " + e.getMessage());
+        }
+    }
+
+    /**
+     * Unregister previously registered Broadcast Assistant Callbacks
+     *
+     * @param callback Callback object to be unregistered
+     */
+    private void unregisterBroadcastAssistantCallback(
             @NonNull BluetoothLeBroadcastAssistant.Callback callback) {
         if (mServiceBroadcastAssistant == null) {
-            Log.d(TAG, "The BluetoothLeBroadcastAssisntant is null.");
+            Log.d(
+                    TAG,
+                    "unregisterBroadcastAssistantCallback, "
+                            + "the BluetoothLeBroadcastAssistant is null.");
             return;
         }
 
@@ -570,8 +744,8 @@
     }
 
     private void buildContentMetadata(String language, String programInfo) {
-        mBluetoothLeAudioContentMetadata = mBuilder.setLanguage(language).setProgramInfo(
-                programInfo).build();
+        mBluetoothLeAudioContentMetadata =
+                mBuilder.setLanguage(language).setProgramInfo(programInfo).build();
     }
 
     public LocalBluetoothLeBroadcastMetadata getLocalBluetoothLeBroadcastMetaData() {
@@ -600,9 +774,7 @@
         return true;
     }
 
-    /**
-     * Not supported since LE Audio Broadcasts do not establish a connection.
-     */
+    /** Not supported since LE Audio Broadcasts do not establish a connection. */
     public int getConnectionStatus(BluetoothDevice device) {
         if (mServiceBroadcast == null) {
             return BluetoothProfile.STATE_DISCONNECTED;
@@ -611,9 +783,7 @@
         return mServiceBroadcast.getConnectionState(device);
     }
 
-    /**
-     * Not supported since LE Audio Broadcasts do not establish a connection.
-     */
+    /** Not supported since LE Audio Broadcasts do not establish a connection. */
     public List<BluetoothDevice> getConnectedDevices() {
         if (mServiceBroadcast == null) {
             return new ArrayList<BluetoothDevice>(0);
@@ -622,8 +792,8 @@
         return mServiceBroadcast.getConnectedDevices();
     }
 
-    public @NonNull
-    List<BluetoothLeBroadcastMetadata> getAllBroadcastMetadata() {
+    /** Get all broadcast metadata. */
+    public @NonNull List<BluetoothLeBroadcastMetadata> getAllBroadcastMetadata() {
         if (mServiceBroadcast == null) {
             Log.d(TAG, "The BluetoothLeBroadcast is null.");
             return Collections.emptyList();
@@ -640,16 +810,14 @@
         return !mServiceBroadcast.getAllBroadcastMetadata().isEmpty();
     }
 
-    /**
-     * Service does not provide method to get/set policy.
-     */
+    /** Service does not provide method to get/set policy. */
     public int getConnectionPolicy(BluetoothDevice device) {
         return CONNECTION_POLICY_FORBIDDEN;
     }
 
     /**
-     * Service does not provide "setEnabled" method. Please use {@link #startBroadcast},
-     * {@link #stopBroadcast()} or {@link #updateBroadcast(String, String)}
+     * Service does not provide "setEnabled" method. Please use {@link #startBroadcast}, {@link
+     * #stopBroadcast()} or {@link #updateBroadcast(String, String)}
      */
     public boolean setEnabled(BluetoothDevice device, boolean enabled) {
         return false;
@@ -683,9 +851,8 @@
         }
         if (mServiceBroadcast != null) {
             try {
-                BluetoothAdapter.getDefaultAdapter().closeProfileProxy(
-                        BluetoothProfile.LE_AUDIO_BROADCAST,
-                        mServiceBroadcast);
+                BluetoothAdapter.getDefaultAdapter()
+                        .closeProfileProxy(BluetoothProfile.LE_AUDIO_BROADCAST, mServiceBroadcast);
                 mServiceBroadcast = null;
             } catch (Throwable t) {
                 Log.w(TAG, "Error cleaning up LeAudio proxy", t);
@@ -694,13 +861,13 @@
     }
 
     private String getDefaultValueOfProgramInfo() {
-        //set the default value;
+        // set the default value;
         int postfix = ThreadLocalRandom.current().nextInt(DEFAULT_CODE_MIN, DEFAULT_CODE_MAX);
         return BluetoothAdapter.getDefaultAdapter().getName() + UNDERLINE + postfix;
     }
 
     private byte[] getDefaultValueOfBroadcastCode() {
-        //set the default value;
+        // set the default value;
         return generateRandomPassword().getBytes(StandardCharsets.UTF_8);
     }
 
@@ -708,14 +875,14 @@
         if (DEBUG) {
             Log.d(TAG, "resetCacheInfo:");
         }
-        setAppSourceName("", /*updateContentResolver=*/ true);
+        setAppSourceName("", /* updateContentResolver= */ true);
         mBluetoothLeBroadcastMetadata = null;
         mBroadcastId = UNKNOWN_VALUE_PLACEHOLDER;
     }
 
     private String generateRandomPassword() {
         String randomUUID = UUID.randomUUID().toString();
-        //first 12 chars from xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx
+        // first 12 chars from xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx
         return randomUUID.substring(0, 8) + randomUUID.substring(9, 13);
     }
 
@@ -752,5 +919,4 @@
             }
         }
     }
-
 }
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcastAssistant.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcastAssistant.java
index bb103b8..34008ac 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcastAssistant.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcastAssistant.java
@@ -39,13 +39,14 @@
 
 import java.util.ArrayList;
 import java.util.List;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
 import java.util.concurrent.Executor;
 
-
 /**
- * LocalBluetoothLeBroadcastAssistant provides an interface between the Settings app
- * and the functionality of the local {@link BluetoothLeBroadcastAssistant}.
- * Use the {@link BluetoothLeBroadcastAssistant.Callback} to get the result callback.
+ * LocalBluetoothLeBroadcastAssistant provides an interface between the Settings app and the
+ * functionality of the local {@link BluetoothLeBroadcastAssistant}. Use the {@link
+ * BluetoothLeBroadcastAssistant.Callback} to get the result callback.
  */
 public class LocalBluetoothLeBroadcastAssistant implements LocalBluetoothProfile {
     private static final String TAG = "LocalBluetoothLeBroadcastAssistant";
@@ -62,58 +63,76 @@
     private BluetoothLeBroadcastMetadata mBluetoothLeBroadcastMetadata;
     private BluetoothLeBroadcastMetadata.Builder mBuilder;
     private boolean mIsProfileReady;
+    // Cached assistant callbacks being register before service is connected.
+    private final Map<BluetoothLeBroadcastAssistant.Callback, Executor> mCachedCallbackExecutorMap =
+            new ConcurrentHashMap<>();
 
-    private final ServiceListener mServiceListener = new ServiceListener() {
-        @Override
-        public void onServiceConnected(int profile, BluetoothProfile proxy) {
-            if (DEBUG) {
-                Log.d(TAG, "Bluetooth service connected");
-            }
-            mService = (BluetoothLeBroadcastAssistant) proxy;
-            // We just bound to the service, so refresh the UI for any connected LeAudio devices.
-            List<BluetoothDevice> deviceList = mService.getConnectedDevices();
-            while (!deviceList.isEmpty()) {
-                BluetoothDevice nextDevice = deviceList.remove(0);
-                CachedBluetoothDevice device = mDeviceManager.findDevice(nextDevice);
-                // we may add a new device here, but generally this should not happen
-                if (device == null) {
+    private final ServiceListener mServiceListener =
+            new ServiceListener() {
+                @Override
+                public void onServiceConnected(int profile, BluetoothProfile proxy) {
                     if (DEBUG) {
-                        Log.d(TAG, "LocalBluetoothLeBroadcastAssistant found new device: "
-                                + nextDevice);
+                        Log.d(TAG, "Bluetooth service connected");
                     }
-                    device = mDeviceManager.addDevice(nextDevice);
+                    mService = (BluetoothLeBroadcastAssistant) proxy;
+                    // We just bound to the service, so refresh the UI for any connected LeAudio
+                    // devices.
+                    List<BluetoothDevice> deviceList = mService.getConnectedDevices();
+                    while (!deviceList.isEmpty()) {
+                        BluetoothDevice nextDevice = deviceList.remove(0);
+                        CachedBluetoothDevice device = mDeviceManager.findDevice(nextDevice);
+                        // we may add a new device here, but generally this should not happen
+                        if (device == null) {
+                            if (DEBUG) {
+                                Log.d(
+                                        TAG,
+                                        "LocalBluetoothLeBroadcastAssistant found new device: "
+                                                + nextDevice);
+                            }
+                            device = mDeviceManager.addDevice(nextDevice);
+                        }
+                        device.onProfileStateChanged(
+                                LocalBluetoothLeBroadcastAssistant.this,
+                                BluetoothProfile.STATE_CONNECTED);
+                        device.refresh();
+                    }
+
+                    mProfileManager.callServiceConnectedListeners();
+                    mIsProfileReady = true;
+                    if (DEBUG) {
+                        Log.d(
+                                TAG,
+                                "onServiceConnected, register mCachedCallbackExecutorMap = "
+                                        + mCachedCallbackExecutorMap);
+                    }
+                    mCachedCallbackExecutorMap.forEach(
+                            (callback, executor) -> registerServiceCallBack(executor, callback));
                 }
-                device.onProfileStateChanged(LocalBluetoothLeBroadcastAssistant.this,
-                        BluetoothProfile.STATE_CONNECTED);
-                device.refresh();
-            }
 
-            mProfileManager.callServiceConnectedListeners();
-            mIsProfileReady = true;
-        }
+                @Override
+                public void onServiceDisconnected(int profile) {
+                    if (profile != BluetoothProfile.LE_AUDIO_BROADCAST_ASSISTANT) {
+                        Log.d(TAG, "The profile is not LE_AUDIO_BROADCAST_ASSISTANT");
+                        return;
+                    }
+                    if (DEBUG) {
+                        Log.d(TAG, "Bluetooth service disconnected");
+                    }
+                    mProfileManager.callServiceDisconnectedListeners();
+                    mIsProfileReady = false;
+                    mCachedCallbackExecutorMap.clear();
+                }
+            };
 
-        @Override
-        public void onServiceDisconnected(int profile) {
-            if (profile != BluetoothProfile.LE_AUDIO_BROADCAST_ASSISTANT) {
-                Log.d(TAG, "The profile is not LE_AUDIO_BROADCAST_ASSISTANT");
-                return;
-            }
-            if (DEBUG) {
-                Log.d(TAG, "Bluetooth service disconnected");
-            }
-            mProfileManager.callServiceDisconnectedListeners();
-            mIsProfileReady = false;
-        }
-    };
-
-    public LocalBluetoothLeBroadcastAssistant(Context context,
+    public LocalBluetoothLeBroadcastAssistant(
+            Context context,
             CachedBluetoothDeviceManager deviceManager,
             LocalBluetoothProfileManager profileManager) {
         mProfileManager = profileManager;
         mDeviceManager = deviceManager;
-        BluetoothAdapter.getDefaultAdapter().
-                getProfileProxy(context, mServiceListener,
-                        BluetoothProfile.LE_AUDIO_BROADCAST_ASSISTANT);
+        BluetoothAdapter.getDefaultAdapter()
+                .getProfileProxy(
+                        context, mServiceListener, BluetoothProfile.LE_AUDIO_BROADCAST_ASSISTANT);
         mBuilder = new BluetoothLeBroadcastMetadata.Builder();
     }
 
@@ -123,11 +142,11 @@
      * @param sink Broadcast Sink to which the Broadcast Source should be added
      * @param metadata Broadcast Source metadata to be added to the Broadcast Sink
      * @param isGroupOp {@code true} if Application wants to perform this operation for all
-     *                  coordinated set members throughout this session. Otherwise, caller
-     *                  would have to add, modify, and remove individual set members.
+     *     coordinated set members throughout this session. Otherwise, caller would have to add,
+     *     modify, and remove individual set members.
      */
-    public void addSource(BluetoothDevice sink, BluetoothLeBroadcastMetadata metadata,
-            boolean isGroupOp) {
+    public void addSource(
+            BluetoothDevice sink, BluetoothLeBroadcastMetadata metadata, boolean isGroupOp) {
         if (mService == null) {
             Log.d(TAG, "The BluetoothLeBroadcastAssistant is null");
             return;
@@ -140,36 +159,55 @@
      * the qr code string.
      *
      * @param sink Broadcast Sink to which the Broadcast Source should be added
-     * @param sourceAddressType hardware MAC Address of the device. See
-     *                          {@link BluetoothDevice.AddressType}.
+     * @param sourceAddressType hardware MAC Address of the device. See {@link
+     *     BluetoothDevice.AddressType}.
      * @param presentationDelayMicros presentation delay of this Broadcast Source in microseconds.
      * @param sourceAdvertisingSid 1-byte long Advertising_SID of the Broadcast Source.
      * @param broadcastId 3-byte long Broadcast_ID of the Broadcast Source.
-     * @param paSyncInterval Periodic Advertising Sync interval of the broadcast Source,
-     *                       {@link BluetoothLeBroadcastMetadata#PA_SYNC_INTERVAL_UNKNOWN} if
-     *                       unknown.
+     * @param paSyncInterval Periodic Advertising Sync interval of the broadcast Source, {@link
+     *     BluetoothLeBroadcastMetadata#PA_SYNC_INTERVAL_UNKNOWN} if unknown.
      * @param isEncrypted whether the Broadcast Source is encrypted.
      * @param broadcastCode Broadcast Code for this Broadcast Source, null if code is not required.
      * @param sourceDevice source advertiser address.
      * @param isGroupOp {@code true} if Application wants to perform this operation for all
-     *                  coordinated set members throughout this session. Otherwise, caller
-     *                  would have to add, modify, and remove individual set members.
+     *     coordinated set members throughout this session. Otherwise, caller would have to add,
+     *     modify, and remove individual set members.
      */
-    public void addSource(@NonNull BluetoothDevice sink, int sourceAddressType,
-            int presentationDelayMicros, int sourceAdvertisingSid, int broadcastId,
-            int paSyncInterval, boolean isEncrypted, byte[] broadcastCode,
-            BluetoothDevice sourceDevice, boolean isGroupOp) {
+    public void addSource(
+            @NonNull BluetoothDevice sink,
+            int sourceAddressType,
+            int presentationDelayMicros,
+            int sourceAdvertisingSid,
+            int broadcastId,
+            int paSyncInterval,
+            boolean isEncrypted,
+            byte[] broadcastCode,
+            BluetoothDevice sourceDevice,
+            boolean isGroupOp) {
         if (DEBUG) {
             Log.d(TAG, "addSource()");
         }
-        buildMetadata(sourceAddressType, presentationDelayMicros, sourceAdvertisingSid, broadcastId,
-                paSyncInterval, isEncrypted, broadcastCode, sourceDevice);
+        buildMetadata(
+                sourceAddressType,
+                presentationDelayMicros,
+                sourceAdvertisingSid,
+                broadcastId,
+                paSyncInterval,
+                isEncrypted,
+                broadcastCode,
+                sourceDevice);
         addSource(sink, mBluetoothLeBroadcastMetadata, isGroupOp);
     }
 
-    private void buildMetadata(int sourceAddressType, int presentationDelayMicros,
-            int sourceAdvertisingSid, int broadcastId, int paSyncInterval, boolean isEncrypted,
-            byte[] broadcastCode, BluetoothDevice sourceDevice) {
+    private void buildMetadata(
+            int sourceAddressType,
+            int presentationDelayMicros,
+            int sourceAdvertisingSid,
+            int broadcastId,
+            int paSyncInterval,
+            boolean isEncrypted,
+            byte[] broadcastCode,
+            BluetoothDevice sourceDevice) {
         mBluetoothLeBroadcastMetadata =
                 mBuilder.setSourceDevice(sourceDevice, sourceAddressType)
                         .setSourceAdvertisingSid(sourceAdvertisingSid)
@@ -223,10 +261,10 @@
     /**
      * Stops an ongoing search for nearby Broadcast Sources.
      *
-     * On success, {@link BluetoothLeBroadcastAssistant.Callback#onSearchStopped(int)} will be
-     * called with reason code {@link BluetoothStatusCodes#REASON_LOCAL_APP_REQUEST}.
-     * On failure, {@link BluetoothLeBroadcastAssistant.Callback#onSearchStopFailed(int)} will be
-     * called with reason code
+     * <p>On success, {@link BluetoothLeBroadcastAssistant.Callback#onSearchStopped(int)} will be
+     * called with reason code {@link BluetoothStatusCodes#REASON_LOCAL_APP_REQUEST}. On failure,
+     * {@link BluetoothLeBroadcastAssistant.Callback#onSearchStopFailed(int)} will be called with
+     * reason code
      *
      * @throws IllegalStateException if callback was not registered
      */
@@ -245,8 +283,8 @@
      * Get information about all Broadcast Sources that a Broadcast Sink knows about.
      *
      * @param sink Broadcast Sink from which to get all Broadcast Sources
-     * @return the list of Broadcast Receive State {@link BluetoothLeBroadcastReceiveState}
-     *         stored in the Broadcast Sink
+     * @return the list of Broadcast Receive State {@link BluetoothLeBroadcastReceiveState} stored
+     *     in the Broadcast Sink
      * @throws NullPointerException when <var>sink</var> is null
      */
     public @NonNull List<BluetoothLeBroadcastReceiveState> getAllSources(
@@ -261,24 +299,50 @@
         return mService.getAllSources(sink);
     }
 
-    public void registerServiceCallBack(@NonNull @CallbackExecutor Executor executor,
+    /**
+     * Register Broadcast Assistant Callbacks to track its state and receivers
+     *
+     * @param executor Executor object for callback
+     * @param callback Callback object to be registered
+     */
+    public void registerServiceCallBack(
+            @NonNull @CallbackExecutor Executor executor,
             @NonNull BluetoothLeBroadcastAssistant.Callback callback) {
         if (mService == null) {
-            Log.d(TAG, "The BluetoothLeBroadcast is null.");
+            Log.d(
+                    TAG,
+                    "registerServiceCallBack failed, the BluetoothLeBroadcastAssistant is null.");
+            mCachedCallbackExecutorMap.putIfAbsent(callback, executor);
             return;
         }
 
-        mService.registerCallback(executor, callback);
+        try {
+            mService.registerCallback(executor, callback);
+        } catch (IllegalArgumentException e) {
+            Log.w(TAG, "registerServiceCallBack failed. " + e.getMessage());
+        }
     }
 
+    /**
+     * Unregister previously registered Broadcast Assistant Callbacks
+     *
+     * @param callback Callback object to be unregistered
+     */
     public void unregisterServiceCallBack(
             @NonNull BluetoothLeBroadcastAssistant.Callback callback) {
+        mCachedCallbackExecutorMap.remove(callback);
         if (mService == null) {
-            Log.d(TAG, "The BluetoothLeBroadcast is null.");
+            Log.d(
+                    TAG,
+                    "unregisterServiceCallBack failed, the BluetoothLeBroadcastAssistant is null.");
             return;
         }
 
-        mService.unregisterCallback(callback);
+        try {
+            mService.unregisterCallback(callback);
+        } catch (IllegalArgumentException e) {
+            Log.w(TAG, "unregisterServiceCallBack failed. " + e.getMessage());
+        }
     }
 
     public boolean isProfileReady() {
@@ -310,9 +374,11 @@
             return new ArrayList<BluetoothDevice>(0);
         }
         return mService.getDevicesMatchingConnectionStates(
-                new int[]{BluetoothProfile.STATE_CONNECTED,
-                        BluetoothProfile.STATE_CONNECTING,
-                        BluetoothProfile.STATE_DISCONNECTING});
+                new int[] {
+                    BluetoothProfile.STATE_CONNECTED,
+                    BluetoothProfile.STATE_CONNECTING,
+                    BluetoothProfile.STATE_DISCONNECTING
+                });
     }
 
     public boolean isEnabled(BluetoothDevice device) {
@@ -373,9 +439,8 @@
         }
         if (mService != null) {
             try {
-                BluetoothAdapter.getDefaultAdapter().closeProfileProxy(
-                        BluetoothProfile.LE_AUDIO_BROADCAST_ASSISTANT,
-                        mService);
+                BluetoothAdapter.getDefaultAdapter()
+                        .closeProfileProxy(BluetoothProfile.LE_AUDIO_BROADCAST_ASSISTANT, mService);
                 mService = null;
             } catch (Throwable t) {
                 Log.w(TAG, "Error cleaning up LeAudio proxy", t);
diff --git a/packages/Shell/AndroidManifest.xml b/packages/Shell/AndroidManifest.xml
index d12d9d6..bacab0f 100644
--- a/packages/Shell/AndroidManifest.xml
+++ b/packages/Shell/AndroidManifest.xml
@@ -879,6 +879,9 @@
 
     <uses-permission android:name="android.permission.GET_BINDING_UID_IMPORTANCE" />
 
+    <!-- Permissions required for CTS test - CtsAccessibilityServiceTestCases-->
+    <uses-permission android:name="android.permission.ACCESSIBILITY_MOTION_EVENT_OBSERVING" />
+
     <application
         android:label="@string/app_label"
         android:theme="@android:style/Theme.DeviceDefault.DayNight"
diff --git a/packages/SystemUI/Android.bp b/packages/SystemUI/Android.bp
index 7061e2c..f10ac1b 100644
--- a/packages/SystemUI/Android.bp
+++ b/packages/SystemUI/Android.bp
@@ -204,6 +204,7 @@
         "lottie",
         "LowLightDreamLib",
         "motion_tool_lib",
+        "notification_flags_lib",
     ],
     libs: [
         "keepanno-annotations",
@@ -328,6 +329,7 @@
         "androidx.compose.ui_ui",
         "flag-junit",
         "platform-test-annotations",
+        "notification_flags_lib",
     ],
 }
 
diff --git a/packages/SystemUI/compose/facade/disabled/src/com/android/systemui/compose/ComposeFacade.kt b/packages/SystemUI/compose/facade/disabled/src/com/android/systemui/compose/ComposeFacade.kt
index 914e5f2..fd04b5ee0 100644
--- a/packages/SystemUI/compose/facade/disabled/src/com/android/systemui/compose/ComposeFacade.kt
+++ b/packages/SystemUI/compose/facade/disabled/src/com/android/systemui/compose/ComposeFacade.kt
@@ -51,6 +51,7 @@
         activity: ComponentActivity,
         viewModel: BaseCommunalViewModel,
         onOpenWidgetPicker: () -> Unit,
+        onEditDone: () -> Unit,
     ) {
         throwComposeUnavailableError()
     }
diff --git a/packages/SystemUI/compose/facade/enabled/src/com/android/systemui/compose/ComposeFacade.kt b/packages/SystemUI/compose/facade/enabled/src/com/android/systemui/compose/ComposeFacade.kt
index 59bd95b..5055ee1 100644
--- a/packages/SystemUI/compose/facade/enabled/src/com/android/systemui/compose/ComposeFacade.kt
+++ b/packages/SystemUI/compose/facade/enabled/src/com/android/systemui/compose/ComposeFacade.kt
@@ -66,12 +66,14 @@
         activity: ComponentActivity,
         viewModel: BaseCommunalViewModel,
         onOpenWidgetPicker: () -> Unit,
+        onEditDone: () -> Unit,
     ) {
         activity.setContent {
             PlatformTheme {
                 CommunalHub(
                     viewModel = viewModel,
                     onOpenWidgetPicker = onOpenWidgetPicker,
+                    onEditDone = onEditDone,
                 )
             }
         }
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt
index e8ecd3a..2a9cf0f 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/CommunalHub.kt
@@ -25,10 +25,12 @@
 import androidx.compose.foundation.layout.Arrangement
 import androidx.compose.foundation.layout.Box
 import androidx.compose.foundation.layout.PaddingValues
+import androidx.compose.foundation.layout.Row
 import androidx.compose.foundation.layout.Spacer
-import androidx.compose.foundation.layout.fillMaxHeight
 import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.fillMaxWidth
 import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.layout.padding
 import androidx.compose.foundation.layout.width
 import androidx.compose.foundation.lazy.grid.GridCells
 import androidx.compose.foundation.lazy.grid.GridItemSpan
@@ -36,23 +38,41 @@
 import androidx.compose.foundation.lazy.grid.rememberLazyGridState
 import androidx.compose.material.icons.Icons
 import androidx.compose.material.icons.filled.Add
-import androidx.compose.material.icons.filled.Close
 import androidx.compose.material.icons.filled.Edit
+import androidx.compose.material.icons.outlined.Delete
+import androidx.compose.material3.Button
+import androidx.compose.material3.ButtonColors
+import androidx.compose.material3.ButtonDefaults
 import androidx.compose.material3.Card
 import androidx.compose.material3.CardDefaults
 import androidx.compose.material3.Icon
 import androidx.compose.material3.IconButton
+import androidx.compose.material3.OutlinedButton
+import androidx.compose.material3.Text
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.collectAsState
 import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.setValue
 import androidx.compose.ui.Alignment
 import androidx.compose.ui.Modifier
+import androidx.compose.ui.geometry.Offset
 import androidx.compose.ui.graphics.Color
 import androidx.compose.ui.input.pointer.pointerInput
+import androidx.compose.ui.layout.LayoutCoordinates
+import androidx.compose.ui.layout.onGloballyPositioned
+import androidx.compose.ui.layout.onSizeChanged
+import androidx.compose.ui.layout.positionInWindow
+import androidx.compose.ui.platform.LocalConfiguration
+import androidx.compose.ui.platform.LocalDensity
 import androidx.compose.ui.res.stringResource
 import androidx.compose.ui.unit.Dp
+import androidx.compose.ui.unit.IntSize
+import androidx.compose.ui.unit.LayoutDirection
 import androidx.compose.ui.unit.dp
 import androidx.compose.ui.viewinterop.AndroidView
+import com.android.compose.theme.LocalAndroidColorScheme
 import com.android.systemui.communal.domain.model.CommunalContentModel
 import com.android.systemui.communal.shared.model.CommunalContentSize
 import com.android.systemui.communal.ui.viewmodel.BaseCommunalViewModel
@@ -66,21 +86,38 @@
     modifier: Modifier = Modifier,
     viewModel: BaseCommunalViewModel,
     onOpenWidgetPicker: (() -> Unit)? = null,
+    onEditDone: (() -> Unit)? = null,
 ) {
     val communalContent by viewModel.communalContent.collectAsState(initial = emptyList())
+    var removeButtonCoordinates: LayoutCoordinates? by remember { mutableStateOf(null) }
+    var toolbarSize: IntSize? by remember { mutableStateOf(null) }
+    var gridCoordinates: LayoutCoordinates? by remember { mutableStateOf(null) }
+    var isDraggingToRemove by remember { mutableStateOf(false) }
+
     Box(
         modifier = modifier.fillMaxSize().background(Color.White),
     ) {
         CommunalHubLazyGrid(
-            modifier = Modifier.height(Dimensions.GridHeight).align(Alignment.CenterStart),
+            modifier = Modifier.align(Alignment.CenterStart),
             communalContent = communalContent,
-            isEditMode = viewModel.isEditMode,
             viewModel = viewModel,
-        )
-        if (viewModel.isEditMode && onOpenWidgetPicker != null) {
-            IconButton(onClick = onOpenWidgetPicker) {
-                Icon(Icons.Default.Add, stringResource(R.string.hub_mode_add_widget_button_text))
+            contentPadding = gridContentPadding(viewModel.isEditMode, toolbarSize),
+            setGridCoordinates = { gridCoordinates = it },
+            updateDragPositionForRemove = {
+                isDraggingToRemove =
+                    checkForDraggingToRemove(it, removeButtonCoordinates, gridCoordinates)
+                isDraggingToRemove
             }
+        )
+
+        if (viewModel.isEditMode && onOpenWidgetPicker != null && onEditDone != null) {
+            Toolbar(
+                isDraggingToRemove = isDraggingToRemove,
+                setToolbarSize = { toolbarSize = it },
+                setRemoveButtonCoordinates = { removeButtonCoordinates = it },
+                onEditDone = onEditDone,
+                onOpenWidgetPicker = onOpenWidgetPicker,
+            )
         } else {
             IconButton(onClick = viewModel::onOpenWidgetEditor) {
                 Icon(Icons.Default.Edit, stringResource(R.string.button_to_open_widget_editor))
@@ -103,25 +140,38 @@
 @Composable
 private fun CommunalHubLazyGrid(
     communalContent: List<CommunalContentModel>,
-    isEditMode: Boolean,
     viewModel: BaseCommunalViewModel,
     modifier: Modifier = Modifier,
+    contentPadding: PaddingValues,
+    setGridCoordinates: (coordinates: LayoutCoordinates) -> Unit,
+    updateDragPositionForRemove: (offset: Offset) -> Boolean,
 ) {
     var gridModifier = modifier
     val gridState = rememberLazyGridState()
     var list = communalContent
     var dragDropState: GridDragDropState? = null
-    if (isEditMode && viewModel is CommunalEditModeViewModel) {
+    if (viewModel.isEditMode && viewModel is CommunalEditModeViewModel) {
         val contentListState = rememberContentListState(communalContent, viewModel)
         list = contentListState.list
-        dragDropState = rememberGridDragDropState(gridState, contentListState)
-        gridModifier = gridModifier.dragContainer(dragDropState)
+        dragDropState =
+            rememberGridDragDropState(
+                gridState = gridState,
+                contentListState = contentListState,
+                updateDragPositionForRemove = updateDragPositionForRemove
+            )
+        gridModifier =
+            gridModifier
+                .fillMaxSize()
+                .dragContainer(dragDropState, beforeContentPadding(contentPadding))
+                .onGloballyPositioned { setGridCoordinates(it) }
+    } else {
+        gridModifier = gridModifier.height(Dimensions.GridHeight)
     }
     LazyHorizontalGrid(
         modifier = gridModifier,
         state = gridState,
         rows = GridCells.Fixed(CommunalContentSize.FULL.span),
-        contentPadding = PaddingValues(horizontal = Dimensions.Spacing),
+        contentPadding = contentPadding,
         horizontalArrangement = Arrangement.spacedBy(Dimensions.Spacing),
         verticalArrangement = Arrangement.spacedBy(Dimensions.Spacing),
     ) {
@@ -130,19 +180,18 @@
             key = { index -> list[index].key },
             span = { index -> GridItemSpan(list[index].size.span) },
         ) { index ->
-            val cardModifier = Modifier.fillMaxHeight().width(Dimensions.CardWidth)
+            val cardModifier = Modifier.width(Dimensions.CardWidth)
             val size =
                 SizeF(
                     Dimensions.CardWidth.value,
                     list[index].size.dp().value,
                 )
-            if (isEditMode && dragDropState != null) {
+            if (viewModel.isEditMode && dragDropState != null) {
                 DraggableItem(dragDropState = dragDropState, enabled = true, index = index) {
                     isDragging ->
                     val elevation by animateDpAsState(if (isDragging) 4.dp else 1.dp)
                     CommunalContent(
                         modifier = cardModifier,
-                        deleteOnClick = viewModel::onDeleteWidget,
                         elevation = elevation,
                         model = list[index],
                         viewModel = viewModel,
@@ -161,6 +210,95 @@
     }
 }
 
+/**
+ * Toolbar that contains action buttons to
+ * 1) open the widget picker
+ * 2) remove a widget from the grid and
+ * 3) exit the edit mode.
+ */
+@Composable
+private fun Toolbar(
+    isDraggingToRemove: Boolean,
+    setToolbarSize: (toolbarSize: IntSize) -> Unit,
+    setRemoveButtonCoordinates: (coordinates: LayoutCoordinates) -> Unit,
+    onOpenWidgetPicker: () -> Unit,
+    onEditDone: () -> Unit,
+) {
+    Row(
+        modifier =
+            Modifier.fillMaxWidth()
+                .padding(
+                    top = Dimensions.ToolbarPaddingTop,
+                    start = Dimensions.ToolbarPaddingHorizontal,
+                    end = Dimensions.ToolbarPaddingHorizontal,
+                )
+                .onSizeChanged { setToolbarSize(it) },
+        horizontalArrangement = Arrangement.SpaceBetween,
+        verticalAlignment = Alignment.CenterVertically
+    ) {
+        val buttonContentPadding =
+            PaddingValues(
+                vertical = Dimensions.ToolbarButtonPaddingVertical,
+                horizontal = Dimensions.ToolbarButtonPaddingHorizontal,
+            )
+        val spacerModifier = Modifier.width(Dimensions.ToolbarButtonSpaceBetween)
+        Button(
+            onClick = onOpenWidgetPicker,
+            colors = filledSecondaryButtonColors(),
+            contentPadding = buttonContentPadding
+        ) {
+            Icon(Icons.Default.Add, stringResource(R.string.button_to_open_widget_editor))
+            Spacer(spacerModifier)
+            Text(
+                text = stringResource(R.string.hub_mode_add_widget_button_text),
+            )
+        }
+
+        val buttonColors =
+            if (isDraggingToRemove) filledButtonColors() else ButtonDefaults.outlinedButtonColors()
+        OutlinedButton(
+            onClick = {},
+            colors = buttonColors,
+            contentPadding = buttonContentPadding,
+            modifier = Modifier.onGloballyPositioned { setRemoveButtonCoordinates(it) },
+        ) {
+            Icon(Icons.Outlined.Delete, stringResource(R.string.button_to_open_widget_editor))
+            Spacer(spacerModifier)
+            Text(
+                text = stringResource(R.string.button_to_remove_widget),
+            )
+        }
+
+        Button(
+            onClick = onEditDone,
+            colors = filledButtonColors(),
+            contentPadding = buttonContentPadding
+        ) {
+            Text(
+                text = stringResource(R.string.hub_mode_editing_exit_button_text),
+            )
+        }
+    }
+}
+
+@Composable
+private fun filledButtonColors(): ButtonColors {
+    val colors = LocalAndroidColorScheme.current
+    return ButtonDefaults.buttonColors(
+        containerColor = colors.primary,
+        contentColor = colors.onPrimary,
+    )
+}
+
+@Composable
+private fun filledSecondaryButtonColors(): ButtonColors {
+    val colors = LocalAndroidColorScheme.current
+    return ButtonDefaults.buttonColors(
+        containerColor = colors.secondary,
+        contentColor = colors.onSecondary,
+    )
+}
+
 @Composable
 private fun CommunalContent(
     model: CommunalContentModel,
@@ -168,11 +306,9 @@
     size: SizeF,
     modifier: Modifier = Modifier,
     elevation: Dp = 0.dp,
-    deleteOnClick: ((id: Int) -> Unit)? = null,
 ) {
     when (model) {
-        is CommunalContentModel.Widget ->
-            WidgetContent(model, size, elevation, deleteOnClick, modifier)
+        is CommunalContentModel.Widget -> WidgetContent(model, size, elevation, modifier)
         is CommunalContentModel.Smartspace -> SmartspaceContent(model, modifier)
         is CommunalContentModel.Tutorial -> TutorialContent(modifier)
         is CommunalContentModel.Umo -> Umo(viewModel, modifier)
@@ -184,19 +320,12 @@
     model: CommunalContentModel.Widget,
     size: SizeF,
     elevation: Dp,
-    deleteOnClick: ((id: Int) -> Unit)?,
     modifier: Modifier = Modifier,
 ) {
-    // TODO(b/309009246): update background color
     Card(
-        modifier = modifier.fillMaxSize().background(Color.White),
+        modifier = modifier.height(size.height.dp),
         elevation = CardDefaults.cardElevation(draggedElevation = elevation),
     ) {
-        if (deleteOnClick != null) {
-            IconButton(onClick = { deleteOnClick(model.appWidgetId) }) {
-                Icon(Icons.Default.Close, stringResource(R.string.button_to_remove_widget))
-            }
-        }
         AndroidView(
             modifier = modifier,
             factory = { context ->
@@ -249,6 +378,60 @@
     )
 }
 
+/**
+ * Returns the `contentPadding` of the grid. Use the vertical padding to push the grid content area
+ * below the toolbar and let the grid take the max size. This ensures the item can be dragged
+ * outside the grid over the toolbar, without part of it getting clipped by the container.
+ */
+@Composable
+private fun gridContentPadding(isEditMode: Boolean, toolbarSize: IntSize?): PaddingValues {
+    if (!isEditMode || toolbarSize == null) {
+        return PaddingValues(horizontal = Dimensions.Spacing)
+    }
+    val configuration = LocalConfiguration.current
+    val density = LocalDensity.current
+    val screenHeight = configuration.screenHeightDp.dp
+    val toolbarHeight = with(density) { Dimensions.ToolbarPaddingTop + toolbarSize.height.toDp() }
+    val verticalPadding =
+        ((screenHeight - toolbarHeight - Dimensions.GridHeight) / 2).coerceAtLeast(
+            Dimensions.Spacing
+        )
+    return PaddingValues(
+        start = Dimensions.ToolbarPaddingHorizontal,
+        end = Dimensions.ToolbarPaddingHorizontal,
+        top = verticalPadding + toolbarHeight,
+        bottom = verticalPadding
+    )
+}
+
+@Composable
+private fun beforeContentPadding(paddingValues: PaddingValues): ContentPaddingInPx {
+    return with(LocalDensity.current) {
+        ContentPaddingInPx(
+            startPadding = paddingValues.calculateLeftPadding(LayoutDirection.Ltr).toPx(),
+            topPadding = paddingValues.calculateTopPadding().toPx()
+        )
+    }
+}
+
+/**
+ * Check whether the pointer position that the item is being dragged at is within the coordinates of
+ * the remove button in the toolbar. Returns true if the item is removable.
+ */
+private fun checkForDraggingToRemove(
+    offset: Offset,
+    removeButtonCoordinates: LayoutCoordinates?,
+    gridCoordinates: LayoutCoordinates?,
+): Boolean {
+    if (removeButtonCoordinates == null || gridCoordinates == null) {
+        return false
+    }
+    val pointer = gridCoordinates.positionInWindow() + offset
+    val removeButton = removeButtonCoordinates.positionInWindow()
+    return pointer.x in removeButton.x..removeButton.x + removeButtonCoordinates.size.width &&
+        pointer.y in removeButton.y..removeButton.y + removeButtonCoordinates.size.height
+}
+
 private fun CommunalContentSize.dp(): Dp {
     return when (this) {
         CommunalContentSize.FULL -> Dimensions.CardHeightFull
@@ -257,6 +440,8 @@
     }
 }
 
+data class ContentPaddingInPx(val startPadding: Float, val topPadding: Float)
+
 object Dimensions {
     val CardWidth = 464.dp
     val CardHeightFull = 630.dp
@@ -264,4 +449,11 @@
     val CardHeightThird = 199.dp
     val GridHeight = CardHeightFull
     val Spacing = 16.dp
+
+    // The sizing/padding of the toolbar in glanceable hub edit mode
+    val ToolbarPaddingTop = 27.dp
+    val ToolbarPaddingHorizontal = 16.dp
+    val ToolbarButtonPaddingHorizontal = 24.dp
+    val ToolbarButtonPaddingVertical = 16.dp
+    val ToolbarButtonSpaceBetween = 8.dp
 }
diff --git a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/GridDragDropState.kt b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/GridDragDropState.kt
index 6cfa2f2..5451d05 100644
--- a/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/GridDragDropState.kt
+++ b/packages/SystemUI/compose/features/src/com/android/systemui/communal/ui/compose/GridDragDropState.kt
@@ -48,12 +48,18 @@
 @Composable
 fun rememberGridDragDropState(
     gridState: LazyGridState,
-    contentListState: ContentListState
+    contentListState: ContentListState,
+    updateDragPositionForRemove: (offset: Offset) -> Boolean,
 ): GridDragDropState {
     val scope = rememberCoroutineScope()
     val state =
         remember(gridState, contentListState) {
-            GridDragDropState(state = gridState, contentListState = contentListState, scope = scope)
+            GridDragDropState(
+                state = gridState,
+                contentListState = contentListState,
+                scope = scope,
+                updateDragPositionForRemove = updateDragPositionForRemove
+            )
         }
     LaunchedEffect(state) {
         while (true) {
@@ -67,23 +73,30 @@
 /**
  * Handles drag and drop cards in the glanceable hub. While dragging to move, other items that are
  * affected will dynamically get positioned and the state is tracked by [ContentListState]. When
- * dragging to remove, affected cards will be moved and [ContentListState.onRemove] is called to
- * remove the dragged item. On dragging ends, call [ContentListState.onSaveList] to persist the
- * change.
+ * dragging to remove, affected cards will be moved and [updateDragPositionForRemove] is called to
+ * check whether the dragged item can be removed. On dragging ends, call [ContentListState.onRemove]
+ * to remove the dragged item if condition met and call [ContentListState.onSaveList] to persist any
+ * change in ordering.
  */
 class GridDragDropState
 internal constructor(
     private val state: LazyGridState,
     private val contentListState: ContentListState,
     private val scope: CoroutineScope,
+    private val updateDragPositionForRemove: (offset: Offset) -> Boolean
 ) {
     var draggingItemIndex by mutableStateOf<Int?>(null)
         private set
 
+    var isDraggingToRemove by mutableStateOf(false)
+        private set
+
     internal val scrollChannel = Channel<Float>()
 
     private var draggingItemDraggedDelta by mutableStateOf(Offset.Zero)
     private var draggingItemInitialOffset by mutableStateOf(Offset.Zero)
+    private var dragStartPointerOffset by mutableStateOf(Offset.Zero)
+
     internal val draggingItemOffset: Offset
         get() =
             draggingItemLayoutInfo?.let { item ->
@@ -94,27 +107,36 @@
     private val draggingItemLayoutInfo: LazyGridItemInfo?
         get() = state.layoutInfo.visibleItemsInfo.firstOrNull { it.index == draggingItemIndex }
 
-    internal fun onDragStart(offset: Offset) {
+    internal fun onDragStart(offset: Offset, contentOffset: Offset) {
         state.layoutInfo.visibleItemsInfo
             .firstOrNull { item ->
+                // grid item offset is based off grid content container so we need to deduct
+                // before content padding from the initial pointer position
                 item.isEditable &&
-                    offset.x.toInt() in item.offset.x..item.offsetEnd.x &&
-                    offset.y.toInt() in item.offset.y..item.offsetEnd.y
+                    (offset.x - contentOffset.x).toInt() in item.offset.x..item.offsetEnd.x &&
+                    (offset.y - contentOffset.y).toInt() in item.offset.y..item.offsetEnd.y
             }
             ?.apply {
+                dragStartPointerOffset = offset - this.offset.toOffset()
                 draggingItemIndex = index
                 draggingItemInitialOffset = this.offset.toOffset()
             }
     }
 
     internal fun onDragInterrupted() {
-        if (draggingItemIndex != null) {
+        draggingItemIndex?.let {
+            if (isDraggingToRemove) {
+                contentListState.onRemove(it)
+                isDraggingToRemove = false
+                updateDragPositionForRemove(Offset.Zero)
+            }
             // persist list editing changes on dragging ends
             contentListState.onSaveList()
             draggingItemIndex = null
         }
         draggingItemDraggedDelta = Offset.Zero
         draggingItemInitialOffset = Offset.Zero
+        dragStartPointerOffset = Offset.Zero
     }
 
     internal fun onDrag(offset: Offset) {
@@ -152,18 +174,13 @@
                 contentListState.onMove(draggingItem.index, targetItem.index)
             }
             draggingItemIndex = targetItem.index
+            isDraggingToRemove = false
         } else {
             val overscroll = checkForOverscroll(startOffset, endOffset)
             if (overscroll != 0f) {
                 scrollChannel.trySend(overscroll)
             }
-            val removeOffset = checkForRemove(startOffset)
-            if (removeOffset != 0f) {
-                draggingItemIndex?.let {
-                    contentListState.onRemove(it)
-                    draggingItemIndex = null
-                }
-            }
+            isDraggingToRemove = checkForRemove(startOffset)
         }
     }
 
@@ -185,14 +202,11 @@
         }
     }
 
-    // TODO(b/309968801): a temporary solution to decide whether to remove card when it's dragged up
-    //  and out of grid. Once we have a taskbar, calculate the intersection of the dragged item with
-    //  the Remove button.
-    private fun checkForRemove(startOffset: Offset): Float {
+    /** Calls the callback with the updated drag position and returns whether to remove the item. */
+    private fun checkForRemove(startOffset: Offset): Boolean {
         return if (draggingItemDraggedDelta.y < 0)
-            (startOffset.y + Dimensions.CardHeightHalf.value - state.layoutInfo.viewportStartOffset)
-                .coerceAtMost(0f)
-        else 0f
+            updateDragPositionForRemove(startOffset + dragStartPointerOffset)
+        else false
     }
 }
 
@@ -204,14 +218,22 @@
     return Offset(x + size.width, y + size.height)
 }
 
-fun Modifier.dragContainer(dragDropState: GridDragDropState): Modifier {
-    return pointerInput(dragDropState) {
+fun Modifier.dragContainer(
+    dragDropState: GridDragDropState,
+    beforeContentPadding: ContentPaddingInPx
+): Modifier {
+    return pointerInput(dragDropState, beforeContentPadding) {
         detectDragGesturesAfterLongPress(
             onDrag = { change, offset ->
                 change.consume()
                 dragDropState.onDrag(offset = offset)
             },
-            onDragStart = { offset -> dragDropState.onDragStart(offset) },
+            onDragStart = { offset ->
+                dragDropState.onDragStart(
+                    offset,
+                    Offset(beforeContentPadding.startPadding, beforeContentPadding.topPadding)
+                )
+            },
             onDragEnd = { dragDropState.onDragInterrupted() },
             onDragCancel = { dragDropState.onDragInterrupted() }
         )
@@ -237,6 +259,7 @@
             Modifier.zIndex(1f).graphicsLayer {
                 translationX = dragDropState.draggingItemOffset.x
                 translationY = dragDropState.draggingItemOffset.y
+                alpha = if (dragDropState.isDraggingToRemove) 0.5f else 1f
             }
         } else {
             Modifier.animateItemPlacement()
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/AnimateSharedAsState.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/AnimateSharedAsState.kt
index 009f8bb..2944bd9 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/AnimateSharedAsState.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/AnimateSharedAsState.kt
@@ -174,12 +174,8 @@
     lerp: (T, T, Float) -> T,
     canOverflow: Boolean,
 ): T {
-    val state = layoutImpl.state.transitionState
-    if (
-        state !is TransitionState.Transition ||
-            state.fromScene == state.toScene ||
-            !layoutImpl.isTransitionReady(state)
-    ) {
+    val transition = layoutImpl.state.currentTransition
+    if (transition == null || !layoutImpl.isTransitionReady(transition)) {
         return sharedValue.value
     }
 
@@ -195,10 +191,11 @@
         return value as Element.SharedValue<T>
     }
 
-    val fromValue = sceneValue(state.fromScene)
-    val toValue = sceneValue(state.toScene)
+    val fromValue = sceneValue(transition.fromScene)
+    val toValue = sceneValue(transition.toScene)
     return if (fromValue != null && toValue != null) {
-        val progress = if (canOverflow) state.progress else state.progress.coerceIn(0f, 1f)
+        val progress =
+            if (canOverflow) transition.progress else transition.progress.coerceIn(0f, 1f)
         lerp(fromValue.value, toValue.value, progress)
     } else if (fromValue != null) {
         fromValue.value
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/AnimateToScene.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/AnimateToScene.kt
index 199832b..ba6d00e 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/AnimateToScene.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/AnimateToScene.kt
@@ -28,11 +28,11 @@
  * the currently running transition, if there is one.
  */
 internal fun CoroutineScope.animateToScene(
-    layoutImpl: SceneTransitionLayoutImpl,
+    layoutState: SceneTransitionLayoutStateImpl,
     target: SceneKey,
 ) {
-    val state = layoutImpl.state.transitionState
-    if (state.currentScene == target) {
+    val transitionState = layoutState.transitionState
+    if (transitionState.currentScene == target) {
         // This can happen in 3 different situations, for which there isn't anything else to do:
         //  1. There is no ongoing transition and [target] is already the current scene.
         //  2. The user is swiping to [target] from another scene and released their pointer such
@@ -44,50 +44,47 @@
         return
     }
 
-    when (state) {
-        is TransitionState.Idle -> animate(layoutImpl, target)
+    when (transitionState) {
+        is TransitionState.Idle -> animate(layoutState, target)
         is TransitionState.Transition -> {
-            if (state.toScene == state.fromScene) {
-                // Same as idle.
-                animate(layoutImpl, target)
-                return
-            }
-
             // A transition is currently running: first check whether `transition.toScene` or
             // `transition.fromScene` is the same as our target scene, in which case the transition
             // can be accelerated or reversed to end up in the target state.
 
-            if (state.toScene == target) {
+            if (transitionState.toScene == target) {
                 // The user is currently swiping to [target] but didn't release their pointer yet:
                 // animate the progress to `1`.
 
-                check(state.fromScene == state.currentScene)
-                val progress = state.progress
+                check(transitionState.fromScene == transitionState.currentScene)
+                val progress = transitionState.progress
                 if ((1f - progress).absoluteValue < ProgressVisibilityThreshold) {
-                    // The transition is already finished (progress ~= 1): no need to animate.
-                    layoutImpl.state.transitionState = TransitionState.Idle(state.currentScene)
+                    // The transition is already finished (progress ~= 1): no need to animate. We
+                    // finish the current transition early to make sure that the current state
+                    // change is committed.
+                    layoutState.finishTransition(transitionState, transitionState.currentScene)
                 } else {
                     // The transition is in progress: start the canned animation at the same
                     // progress as it was in.
                     // TODO(b/290184746): Also take the current velocity into account.
-                    animate(layoutImpl, target, startProgress = progress)
+                    animate(layoutState, target, startProgress = progress)
                 }
 
                 return
             }
 
-            if (state.fromScene == target) {
+            if (transitionState.fromScene == target) {
                 // There is a transition from [target] to another scene: simply animate the same
                 // transition progress to `0`.
 
-                check(state.toScene == state.currentScene)
-                val progress = state.progress
+                check(transitionState.toScene == transitionState.currentScene)
+                val progress = transitionState.progress
                 if (progress.absoluteValue < ProgressVisibilityThreshold) {
-                    // The transition is at progress ~= 0: no need to animate.
-                    layoutImpl.state.transitionState = TransitionState.Idle(state.currentScene)
+                    // The transition is at progress ~= 0: no need to animate.We finish the current
+                    // transition early to make sure that the current state change is committed.
+                    layoutState.finishTransition(transitionState, transitionState.currentScene)
                 } else {
                     // TODO(b/290184746): Also take the current velocity into account.
-                    animate(layoutImpl, target, startProgress = progress, reversed = true)
+                    animate(layoutState, target, startProgress = progress, reversed = true)
                 }
 
                 return
@@ -95,27 +92,22 @@
 
             // Generic interruption; the current transition is neither from or to [target].
             // TODO(b/290930950): Better handle interruptions here.
-            animate(layoutImpl, target)
+            animate(layoutState, target)
         }
     }
 }
 
 private fun CoroutineScope.animate(
-    layoutImpl: SceneTransitionLayoutImpl,
+    layoutState: SceneTransitionLayoutStateImpl,
     target: SceneKey,
     startProgress: Float = 0f,
     reversed: Boolean = false,
 ) {
-    val fromScene = layoutImpl.state.transitionState.currentScene
+    val fromScene = layoutState.transitionState.currentScene
     val isUserInput =
-        (layoutImpl.state.transitionState as? TransitionState.Transition)?.isInitiatedByUserInput
+        (layoutState.transitionState as? TransitionState.Transition)?.isInitiatedByUserInput
             ?: false
 
-    val animationSpec = layoutImpl.transitions.transitionSpec(fromScene, target).spec
-    val visibilityThreshold =
-        (animationSpec as? SpringSpec)?.visibilityThreshold ?: ProgressVisibilityThreshold
-    val animatable = Animatable(startProgress, visibilityThreshold = visibilityThreshold)
-
     val targetProgress = if (reversed) 0f else 1f
     val transition =
         if (reversed) {
@@ -125,7 +117,6 @@
                 currentScene = target,
                 isInitiatedByUserInput = isUserInput,
                 isUserInputOngoing = false,
-                animatable = animatable,
             )
         } else {
             OneOffTransition(
@@ -134,32 +125,46 @@
                 currentScene = target,
                 isInitiatedByUserInput = isUserInput,
                 isUserInputOngoing = false,
-                animatable = animatable,
             )
         }
 
-    // Change the current layout state to use this new transition.
-    layoutImpl.state.transitionState = transition
+    // Change the current layout state to start this new transition. This will compute the
+    // TransformationSpec associated to this transition, which we need to initialize the Animatable
+    // that will actually animate it.
+    layoutState.startTransition(transition)
+
+    // The transformation now contains the spec that we should use to instantiate the Animatable.
+    val animationSpec = layoutState.transformationSpec.progressSpec
+    val visibilityThreshold =
+        (animationSpec as? SpringSpec)?.visibilityThreshold ?: ProgressVisibilityThreshold
+    val animatable =
+        Animatable(startProgress, visibilityThreshold = visibilityThreshold).also {
+            transition.animatable = it
+        }
 
     // Animate the progress to its target value.
     launch {
         animatable.animateTo(targetProgress, animationSpec)
-
-        // Unless some other external state change happened, the state should now be idle.
-        if (layoutImpl.state.transitionState == transition) {
-            layoutImpl.state.transitionState = TransitionState.Idle(target)
-        }
+        layoutState.finishTransition(transition, target)
     }
 }
 
 private class OneOffTransition(
-    override val fromScene: SceneKey,
-    override val toScene: SceneKey,
+    fromScene: SceneKey,
+    toScene: SceneKey,
     override val currentScene: SceneKey,
     override val isInitiatedByUserInput: Boolean,
     override val isUserInputOngoing: Boolean,
-    private val animatable: Animatable<Float, AnimationVector1D>,
-) : TransitionState.Transition {
+) : TransitionState.Transition(fromScene, toScene) {
+    /**
+     * The animatable used to animate this transition.
+     *
+     * Note: This is lateinit because we need to first create this Transition object so that
+     * [SceneTransitionLayoutState] can compute the transformations and animation spec associated to
+     * it, which is need to initialize this Animatable.
+     */
+    lateinit var animatable: Animatable<Float, AnimationVector1D>
+
     override val progress: Float
         get() = animatable.value
 }
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Element.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Element.kt
index 31604a6..5dc1079 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Element.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Element.kt
@@ -181,15 +181,11 @@
 }
 
 internal class ElementNode(
-    layoutImpl: SceneTransitionLayoutImpl,
-    scene: Scene,
-    element: Element,
-    sceneValues: Element.TargetValues,
+    private var layoutImpl: SceneTransitionLayoutImpl,
+    private var scene: Scene,
+    private var element: Element,
+    private var sceneValues: Element.TargetValues,
 ) : Modifier.Node(), DrawModifierNode {
-    private var layoutImpl: SceneTransitionLayoutImpl = layoutImpl
-    private var scene: Scene = scene
-    private var element: Element = element
-    private var sceneValues: Element.TargetValues = sceneValues
 
     override fun onAttach() {
         super.onAttach()
@@ -283,27 +279,27 @@
     scene: Scene,
     element: Element,
 ): Boolean {
-    val state = layoutImpl.state.transitionState
+    val transition = layoutImpl.state.currentTransition
 
     // Always draw the element if there is no ongoing transition or if the element is not shared.
     if (
-        state !is TransitionState.Transition ||
-            state.fromScene == state.toScene ||
-            !layoutImpl.isTransitionReady(state) ||
-            state.fromScene !in element.sceneValues ||
-            state.toScene !in element.sceneValues
+        transition == null ||
+            !layoutImpl.isTransitionReady(transition) ||
+            transition.fromScene !in element.sceneValues ||
+            transition.toScene !in element.sceneValues
     ) {
         return true
     }
 
-    val sharedTransformation = sharedElementTransformation(layoutImpl, state, element.key)
+    val sharedTransformation =
+        sharedElementTransformation(layoutImpl.state, transition, element.key)
     if (sharedTransformation?.enabled == false) {
         return true
     }
 
     return shouldDrawOrComposeSharedElement(
         layoutImpl,
-        state,
+        transition,
         scene.key,
         element.key,
         sharedTransformation,
@@ -332,21 +328,21 @@
 }
 
 private fun isSharedElementEnabled(
-    layoutImpl: SceneTransitionLayoutImpl,
+    layoutState: SceneTransitionLayoutStateImpl,
     transition: TransitionState.Transition,
     element: ElementKey,
 ): Boolean {
-    return sharedElementTransformation(layoutImpl, transition, element)?.enabled ?: true
+    return sharedElementTransformation(layoutState, transition, element)?.enabled ?: true
 }
 
 internal fun sharedElementTransformation(
-    layoutImpl: SceneTransitionLayoutImpl,
+    layoutState: SceneTransitionLayoutStateImpl,
     transition: TransitionState.Transition,
     element: ElementKey,
 ): SharedElementTransformation? {
-    val spec = layoutImpl.transitions.transitionSpec(transition.fromScene, transition.toScene)
-    val sharedInFromScene = spec.transformations(element, transition.fromScene).shared
-    val sharedInToScene = spec.transformations(element, transition.toScene).shared
+    val transformationSpec = layoutState.transformationSpec
+    val sharedInFromScene = transformationSpec.transformations(element, transition.fromScene).shared
+    val sharedInToScene = transformationSpec.transformations(element, transition.toScene).shared
 
     // The sharedElement() transformation must either be null or be the same in both scenes.
     if (sharedInFromScene != sharedInToScene) {
@@ -372,13 +368,9 @@
     scene: Scene,
     sceneValues: Element.TargetValues,
 ): Boolean {
-    val state = layoutImpl.state.transitionState
+    val transition = layoutImpl.state.currentTransition ?: return true
 
-    if (state !is TransitionState.Transition || state.fromScene == state.toScene) {
-        return true
-    }
-
-    if (!layoutImpl.isTransitionReady(state)) {
+    if (!layoutImpl.isTransitionReady(transition)) {
         val lastValue =
             sceneValues.lastValues.alpha.takeIf { it != Element.AlphaUnspecified }
                 ?: element.lastSharedValues.alpha.takeIf { it != Element.AlphaUnspecified } ?: 1f
@@ -386,8 +378,8 @@
         return lastValue == 1f
     }
 
-    val fromScene = state.fromScene
-    val toScene = state.toScene
+    val fromScene = transition.fromScene
+    val toScene = transition.toScene
     val fromValues = element.sceneValues[fromScene]
     val toValues = element.sceneValues[toScene]
 
@@ -396,14 +388,11 @@
     }
 
     val isSharedElement = fromValues != null && toValues != null
-    if (isSharedElement && isSharedElementEnabled(layoutImpl, state, element.key)) {
+    if (isSharedElement && isSharedElementEnabled(layoutImpl.state, transition, element.key)) {
         return true
     }
 
-    return layoutImpl.transitions
-        .transitionSpec(fromScene, toScene)
-        .transformations(element.key, scene.key)
-        .alpha == null
+    return layoutImpl.state.transformationSpec.transformations(element.key, scene.key).alpha == null
 }
 
 /**
@@ -608,24 +597,22 @@
     lastValue: () -> T,
     lerp: (T, T, Float) -> T,
 ): T {
-    val state = layoutImpl.state.transitionState
-
-    // There is no ongoing transition.
-    if (state !is TransitionState.Transition || state.fromScene == state.toScene) {
-        // Even if this element SceneTransitionLayout is not animated, the layout itself might be
-        // animated (e.g. by another parent SceneTransitionLayout), in which case this element still
-        // need to participate in the layout phase.
-        return currentValue()
-    }
+    val transition =
+        layoutImpl.state.currentTransition
+        // There is no ongoing transition. Even if this element SceneTransitionLayout is not
+        // animated, the layout itself might be animated (e.g. by another parent
+        // SceneTransitionLayout), in which case this element still need to participate in the
+        // layout phase.
+        ?: return currentValue()
 
     // A transition was started but it's not ready yet (not all elements have been composed/laid
     // out yet). Use the last value that was set, to make sure elements don't unexpectedly jump.
-    if (!layoutImpl.isTransitionReady(state)) {
+    if (!layoutImpl.isTransitionReady(transition)) {
         return lastValue()
     }
 
-    val fromScene = state.fromScene
-    val toScene = state.toScene
+    val fromScene = transition.fromScene
+    val toScene = transition.toScene
     val fromValues = element.sceneValues[fromScene]
     val toValues = element.sceneValues[toScene]
 
@@ -639,21 +626,17 @@
     // TODO(b/290184746): Support non linear shared paths as well as a way to make sure that shared
     // elements follow the finger direction.
     val isSharedElement = fromValues != null && toValues != null
-    if (isSharedElement && isSharedElementEnabled(layoutImpl, state, element.key)) {
+    if (isSharedElement && isSharedElementEnabled(layoutImpl.state, transition, element.key)) {
         val start = sceneValue(fromValues!!)
         val end = sceneValue(toValues!!)
 
         // Make sure we don't read progress if values are the same and we don't need to interpolate,
         // so we don't invalidate the phase where this is read.
-        return if (start == end) start else lerp(start, end, state.progress)
+        return if (start == end) start else lerp(start, end, transition.progress)
     }
 
     val transformation =
-        transformation(
-            layoutImpl.transitions
-                .transitionSpec(fromScene, toScene)
-                .transformations(element.key, scene.key)
-        )
+        transformation(layoutImpl.state.transformationSpec.transformations(element.key, scene.key))
         // If there is no transformation explicitly associated to this element value, let's use
         // the value given by the system (like the current position and size given by the layout
         // pass).
@@ -676,7 +659,7 @@
             scene,
             element,
             sceneValues,
-            state,
+            transition,
             idleValue,
         )
 
@@ -686,7 +669,7 @@
         return targetValue
     }
 
-    val progress = state.progress
+    val progress = transition.progress
     // TODO(b/290184746): Make sure that we don't overflow transformations associated to a range.
     val rangeProgress = transformation.range?.progress(progress) ?: progress
 
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/MovableElement.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/MovableElement.kt
index fa385d0..306f276 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/MovableElement.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/MovableElement.kt
@@ -120,21 +120,13 @@
     scene: SceneKey,
     element: Element,
 ): Boolean {
-    val transitionState = layoutImpl.state.transitionState
-
-    // If we are idle, there is only one [scene] that is composed so we can compose our movable
-    // content here.
-    if (transitionState is TransitionState.Idle) {
-        check(transitionState.currentScene == scene)
-        return true
-    }
-
-    val fromScene = (transitionState as TransitionState.Transition).fromScene
-    val toScene = transitionState.toScene
-    if (fromScene == toScene) {
-        check(fromScene == scene)
-        return true
-    }
+    val transition =
+        layoutImpl.state.currentTransition
+        // If we are idle, there is only one [scene] that is composed so we can compose our
+        // movable content here.
+        ?: return true
+    val fromScene = transition.fromScene
+    val toScene = transition.toScene
 
     val fromReady = layoutImpl.isSceneReady(fromScene)
     val toReady = layoutImpl.isSceneReady(toScene)
@@ -185,10 +177,10 @@
 
     return shouldDrawOrComposeSharedElement(
         layoutImpl,
-        transitionState,
+        transition,
         scene,
         element.key,
-        sharedElementTransformation(layoutImpl, transitionState, element.key),
+        sharedElementTransformation(layoutImpl.state, transition, element.key),
     )
 }
 
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/NestedScrollToScene.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/NestedScrollToScene.kt
index ded6cc1..e78f326 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/NestedScrollToScene.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/NestedScrollToScene.kt
@@ -73,37 +73,37 @@
 internal fun Modifier.nestedScrollToScene(
     layoutImpl: SceneTransitionLayoutImpl,
     orientation: Orientation,
-    startBehavior: NestedScrollBehavior,
-    endBehavior: NestedScrollBehavior,
+    topOrLeftBehavior: NestedScrollBehavior,
+    bottomOrRightBehavior: NestedScrollBehavior,
 ) =
     this then
         NestedScrollToSceneElement(
             layoutImpl = layoutImpl,
             orientation = orientation,
-            startBehavior = startBehavior,
-            endBehavior = endBehavior,
+            topOrLeftBehavior = topOrLeftBehavior,
+            bottomOrRightBehavior = bottomOrRightBehavior,
         )
 
 private data class NestedScrollToSceneElement(
     private val layoutImpl: SceneTransitionLayoutImpl,
     private val orientation: Orientation,
-    private val startBehavior: NestedScrollBehavior,
-    private val endBehavior: NestedScrollBehavior,
+    private val topOrLeftBehavior: NestedScrollBehavior,
+    private val bottomOrRightBehavior: NestedScrollBehavior,
 ) : ModifierNodeElement<NestedScrollToSceneNode>() {
     override fun create() =
         NestedScrollToSceneNode(
             layoutImpl = layoutImpl,
             orientation = orientation,
-            startBehavior = startBehavior,
-            endBehavior = endBehavior,
+            topOrLeftBehavior = topOrLeftBehavior,
+            bottomOrRightBehavior = bottomOrRightBehavior,
         )
 
     override fun update(node: NestedScrollToSceneNode) {
         node.update(
             layoutImpl = layoutImpl,
             orientation = orientation,
-            startBehavior = startBehavior,
-            endBehavior = endBehavior,
+            topOrLeftBehavior = topOrLeftBehavior,
+            bottomOrRightBehavior = bottomOrRightBehavior,
         )
     }
 
@@ -111,23 +111,23 @@
         name = "nestedScrollToScene"
         properties["layoutImpl"] = layoutImpl
         properties["orientation"] = orientation
-        properties["startBehavior"] = startBehavior
-        properties["endBehavior"] = endBehavior
+        properties["topOrLeftBehavior"] = topOrLeftBehavior
+        properties["bottomOrRightBehavior"] = bottomOrRightBehavior
     }
 }
 
 private class NestedScrollToSceneNode(
     layoutImpl: SceneTransitionLayoutImpl,
     orientation: Orientation,
-    startBehavior: NestedScrollBehavior,
-    endBehavior: NestedScrollBehavior,
+    topOrLeftBehavior: NestedScrollBehavior,
+    bottomOrRightBehavior: NestedScrollBehavior,
 ) : DelegatingNode() {
     private var priorityNestedScrollConnection: PriorityNestedScrollConnection =
         scenePriorityNestedScrollConnection(
             layoutImpl = layoutImpl,
             orientation = orientation,
-            startBehavior = startBehavior,
-            endBehavior = endBehavior,
+            topOrLeftBehavior = topOrLeftBehavior,
+            bottomOrRightBehavior = bottomOrRightBehavior,
         )
 
     private var nestedScrollNode: DelegatableNode =
@@ -148,8 +148,8 @@
     fun update(
         layoutImpl: SceneTransitionLayoutImpl,
         orientation: Orientation,
-        startBehavior: NestedScrollBehavior,
-        endBehavior: NestedScrollBehavior,
+        topOrLeftBehavior: NestedScrollBehavior,
+        bottomOrRightBehavior: NestedScrollBehavior,
     ) {
         // Clean up the old nested scroll connection
         priorityNestedScrollConnection.reset()
@@ -160,8 +160,8 @@
             scenePriorityNestedScrollConnection(
                 layoutImpl = layoutImpl,
                 orientation = orientation,
-                startBehavior = startBehavior,
-                endBehavior = endBehavior,
+                topOrLeftBehavior = topOrLeftBehavior,
+                bottomOrRightBehavior = bottomOrRightBehavior,
             )
         nestedScrollNode =
             nestedScrollModifierNode(
@@ -175,12 +175,13 @@
 private fun scenePriorityNestedScrollConnection(
     layoutImpl: SceneTransitionLayoutImpl,
     orientation: Orientation,
-    startBehavior: NestedScrollBehavior,
-    endBehavior: NestedScrollBehavior,
+    topOrLeftBehavior: NestedScrollBehavior,
+    bottomOrRightBehavior: NestedScrollBehavior,
 ) =
     SceneNestedScrollHandler(
-            gestureHandler = layoutImpl.gestureHandler(orientation = orientation),
-            startBehavior = startBehavior,
-            endBehavior = endBehavior,
+            layoutImpl = layoutImpl,
+            orientation = orientation,
+            topOrLeftBehavior = topOrLeftBehavior,
+            bottomOrRightBehavior = bottomOrRightBehavior,
         )
         .connection
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/ObservableTransitionState.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/ObservableTransitionState.kt
index 1b79dbd..983cff8 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/ObservableTransitionState.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/ObservableTransitionState.kt
@@ -73,17 +73,13 @@
             when (val state = transitionState) {
                 is TransitionState.Idle -> ObservableTransitionState.Idle(state.currentScene)
                 is TransitionState.Transition -> {
-                    if (state.fromScene == state.toScene) {
-                        ObservableTransitionState.Idle(state.currentScene)
-                    } else {
-                        ObservableTransitionState.Transition(
-                            fromScene = state.fromScene,
-                            toScene = state.toScene,
-                            progress = snapshotFlow { state.progress },
-                            isInitiatedByUserInput = state.isInitiatedByUserInput,
-                            isUserInputOngoing = snapshotFlow { state.isUserInputOngoing },
-                        )
-                    }
+                    ObservableTransitionState.Transition(
+                        fromScene = state.fromScene,
+                        toScene = state.toScene,
+                        progress = snapshotFlow { state.progress },
+                        isInitiatedByUserInput = state.isInitiatedByUserInput,
+                        isUserInputOngoing = snapshotFlow { state.isUserInputOngoing },
+                    )
                 }
             }
         }
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Scene.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Scene.kt
index f5561cb..6a7a3a0 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Scene.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/Scene.kt
@@ -86,16 +86,26 @@
         return element(layoutImpl, scene, key)
     }
 
-    override fun Modifier.nestedScrollToScene(
-        orientation: Orientation,
-        startBehavior: NestedScrollBehavior,
-        endBehavior: NestedScrollBehavior,
+    override fun Modifier.horizontalNestedScrollToScene(
+        leftBehavior: NestedScrollBehavior,
+        rightBehavior: NestedScrollBehavior,
     ): Modifier =
         nestedScrollToScene(
             layoutImpl = layoutImpl,
-            orientation = orientation,
-            startBehavior = startBehavior,
-            endBehavior = endBehavior,
+            orientation = Orientation.Horizontal,
+            topOrLeftBehavior = leftBehavior,
+            bottomOrRightBehavior = rightBehavior,
+        )
+
+    override fun Modifier.verticalNestedScrollToScene(
+        topBehavior: NestedScrollBehavior,
+        bottomBehavior: NestedScrollBehavior
+    ): Modifier =
+        nestedScrollToScene(
+            layoutImpl = layoutImpl,
+            orientation = Orientation.Vertical,
+            topOrLeftBehavior = topBehavior,
+            bottomOrRightBehavior = bottomBehavior,
         )
 
     @Composable
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneGestureHandler.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneGestureHandler.kt
index 01179d3..338557d 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneGestureHandler.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneGestureHandler.kt
@@ -41,39 +41,37 @@
     internal val orientation: Orientation,
     private val coroutineScope: CoroutineScope,
 ) {
+    private val layoutState = layoutImpl.state
     val draggable: DraggableHandler = SceneDraggableHandler(this)
 
-    internal var transitionState
-        get() = layoutImpl.state.transitionState
+    private var _swipeTransition: SwipeTransition? = null
+    internal var swipeTransition: SwipeTransition
+        get() = _swipeTransition ?: error("SwipeTransition needs to be initialized")
         set(value) {
-            layoutImpl.state.transitionState = value
+            _swipeTransition = value
         }
 
-    internal var swipeTransition: SwipeTransition = SwipeTransition(currentScene, currentScene, 1f)
-        private set
-
     private fun updateTransition(newTransition: SwipeTransition, force: Boolean = false) {
-        if (isDrivingTransition || force) transitionState = newTransition
+        if (isDrivingTransition || force) layoutState.startTransition(newTransition)
         swipeTransition = newTransition
     }
 
-    internal val currentScene: Scene
-        get() = layoutImpl.scene(transitionState.currentScene)
-
     internal val isDrivingTransition
-        get() = transitionState == swipeTransition
+        get() = layoutState.transitionState == _swipeTransition
 
     /**
      * The velocity threshold at which the intent of the user is to swipe up or down. It is the same
      * as SwipeableV2Defaults.VelocityThreshold.
      */
-    internal val velocityThreshold = with(layoutImpl.density) { 125.dp.toPx() }
+    internal val velocityThreshold: Float
+        get() = with(layoutImpl.density) { 125.dp.toPx() }
 
     /**
      * The positional threshold at which the intent of the user is to swipe to the next scene. It is
      * the same as SwipeableV2Defaults.PositionalThreshold.
      */
-    private val positionalThreshold = with(layoutImpl.density) { 56.dp.toPx() }
+    private val positionalThreshold
+        get() = with(layoutImpl.density) { 56.dp.toPx() }
 
     internal var gestureWithPriority: Any? = null
 
@@ -82,34 +80,34 @@
     private var actionDownOrRight: UserAction? = null
     private var actionUpOrLeftNoEdge: UserAction? = null
     private var actionDownOrRightNoEdge: UserAction? = null
+    private var upOrLeftScene: SceneKey? = null
+    private var downOrRightScene: SceneKey? = null
 
     internal fun onDragStarted(pointersDown: Int, startedPosition: Offset?, overSlop: Float) {
         if (isDrivingTransition) {
             // This [transition] was already driving the animation: simply take over it.
             // Stop animating and start from where the current offset.
             swipeTransition.cancelOffsetAnimation()
+            updateTargetScenes(swipeTransition._fromScene)
             return
         }
 
-        val transition = transitionState
-        if (transition is TransitionState.Transition) {
+        val transitionState = layoutState.transitionState
+        if (transitionState is TransitionState.Transition) {
             // TODO(b/290184746): Better handle interruptions here if state != idle.
             Log.w(
                 TAG,
                 "start from TransitionState.Transition is not fully supported: from" +
-                    " ${transition.fromScene} to ${transition.toScene} " +
-                    "(progress ${transition.progress})"
+                    " ${transitionState.fromScene} to ${transitionState.toScene} " +
+                    "(progress ${transitionState.progress})"
             )
         }
 
-        val fromScene = currentScene
+        val fromScene = layoutImpl.scene(transitionState.currentScene)
         setCurrentActions(fromScene, startedPosition, pointersDown)
 
-        if (fromScene.upOrLeft() == null && fromScene.downOrRight() == null) {
-            return
-        }
-
-        val (targetScene, distance) = fromScene.findTargetSceneAndDistance(overSlop)
+        val (targetScene, distance) =
+            findTargetSceneAndDistance(fromScene, overSlop, updateScenes = true) ?: return
 
         updateTransition(SwipeTransition(fromScene, targetScene, distance), force = true)
     }
@@ -179,16 +177,21 @@
 
         val (fromScene, acceleratedOffset) =
             computeFromSceneConsideringAcceleratedSwipe(swipeTransition)
+
+        val isNewFromScene = fromScene.key != swipeTransition.fromScene
+        val (targetScene, distance) =
+            findTargetSceneAndDistance(
+                fromScene,
+                swipeTransition.dragOffset,
+                updateScenes = isNewFromScene,
+            )
+                ?: run {
+                    onDragStopped(delta, true)
+                    return
+                }
         swipeTransition.dragOffset += acceleratedOffset
 
-        // Compute the target scene depending on the current offset.
-        val (targetScene, distance) =
-            fromScene.findTargetSceneAndDistance(swipeTransition.dragOffset)
-
-        // TODO(b/290184746): support long scroll A => B => C? especially for non fullscreen scenes
-        if (
-            fromScene.key != swipeTransition.fromScene || targetScene.key != swipeTransition.toScene
-        ) {
+        if (isNewFromScene || targetScene.key != swipeTransition.toScene) {
             updateTransition(
                 SwipeTransition(fromScene, targetScene, distance).apply {
                     this.dragOffset = swipeTransition.dragOffset
@@ -197,6 +200,11 @@
         }
     }
 
+    private fun updateTargetScenes(fromScene: Scene) {
+        upOrLeftScene = fromScene.upOrLeft()
+        downOrRightScene = fromScene.downOrRight()
+    }
+
     /**
      * Change fromScene in the case where the user quickly swiped multiple times in the same
      * direction to accelerate the transition from A => B then B => C.
@@ -214,37 +222,71 @@
         val absoluteDistance = swipeTransition.distance.absoluteValue
 
         // If the swipe was not committed, don't do anything.
-        if (fromScene == toScene || swipeTransition._currentScene != toScene) {
+        if (swipeTransition._currentScene != toScene) {
             return Pair(fromScene, 0f)
         }
 
         // If the offset is past the distance then let's change fromScene so that the user can swipe
         // to the next screen or go back to the previous one.
         val offset = swipeTransition.dragOffset
-        return if (offset <= -absoluteDistance && fromScene.upOrLeft() == toScene.key) {
+        return if (offset <= -absoluteDistance && upOrLeftScene == toScene.key) {
             Pair(toScene, absoluteDistance)
-        } else if (offset >= absoluteDistance && fromScene.downOrRight() == toScene.key) {
+        } else if (offset >= absoluteDistance && downOrRightScene == toScene.key) {
             Pair(toScene, -absoluteDistance)
         } else {
             Pair(fromScene, 0f)
         }
     }
 
-    // TODO(b/290184746): there are two bugs here:
-    // 1. if both upOrLeft and downOrRight become `null` during a transition this will crash
-    // 2. if one of them changes during a transition, the transition will jump cut to the new target
-    private inline fun Scene.findTargetSceneAndDistance(
-        directionOffset: Float
-    ): Pair<Scene, Float> {
-        val upOrLeft = upOrLeft()
-        val downOrRight = downOrRight()
-        val absoluteDistance = getAbsoluteDistance()
+    /**
+     * Returns the target scene and distance from [fromScene] in the direction [directionOffset].
+     *
+     * @param fromScene the scene from which we look for the target
+     * @param directionOffset signed float that indicates the direction. Positive is down or right
+     *   negative is up or left.
+     * @param updateScenes whether the target scenes should be updated to the current values held in
+     *   the Scenes map. Usually we don't want to update them while doing a drag, because this could
+     *   change the target scene (jump cutting) to a different scene, when some system state changed
+     *   the targets the background. However, an update is needed any time we calculate the targets
+     *   for a new fromScene.
+     * @return null when there are no targets in either direction. If one direction is null and you
+     *   drag into the null direction this function will return the opposite direction, assuming
+     *   that the users intention is to start the drag into the other direction eventually. If
+     *   [directionOffset] is 0f and both direction are available, it will default to
+     *   [upOrLeftScene].
+     */
+    private inline fun findTargetSceneAndDistance(
+        fromScene: Scene,
+        directionOffset: Float,
+        updateScenes: Boolean,
+    ): Pair<Scene, Float>? {
+        if (updateScenes) updateTargetScenes(fromScene)
+        val absoluteDistance = fromScene.getAbsoluteDistance()
 
         // Compute the target scene depending on the current offset.
-        return if ((directionOffset < 0f && upOrLeft != null) || downOrRight == null) {
-            Pair(layoutImpl.scene(upOrLeft!!), -absoluteDistance)
-        } else {
-            Pair(layoutImpl.scene(downOrRight), absoluteDistance)
+        return when {
+            upOrLeftScene == null && downOrRightScene == null -> null
+            (directionOffset < 0f && upOrLeftScene != null) || downOrRightScene == null ->
+                Pair(layoutImpl.scene(upOrLeftScene!!), -absoluteDistance)
+            else -> Pair(layoutImpl.scene(downOrRightScene!!), absoluteDistance)
+        }
+    }
+
+    /**
+     * A strict version of [findTargetSceneAndDistance] that will return null when there is no Scene
+     * in [directionOffset] direction
+     */
+    private inline fun findTargetSceneAndDistanceStrict(
+        fromScene: Scene,
+        directionOffset: Float,
+    ): Pair<Scene, Float>? {
+        val absoluteDistance = fromScene.getAbsoluteDistance()
+        return when {
+            directionOffset > 0f ->
+                upOrLeftScene?.let { Pair(layoutImpl.scene(it), -absoluteDistance) }
+            directionOffset < 0f ->
+                downOrRightScene?.let { Pair(layoutImpl.scene(it), absoluteDistance) }
+            else -> null
         }
     }
 
@@ -311,20 +353,21 @@
             val startFromIdlePosition = swipeTransition.dragOffset == 0f
 
             if (startFromIdlePosition) {
-                // If there is a next scene, we start the overscroll animation.
-                val (targetScene, distance) = fromScene.findTargetSceneAndDistance(velocity)
-                val isValidTarget = distance != 0f && targetScene.key != fromScene.key
-                if (isValidTarget) {
-                    updateTransition(
-                        SwipeTransition(fromScene, targetScene, distance).apply {
-                            _currentScene = swipeTransition._currentScene
+                // If there is a target scene, we start the overscroll animation.
+                val (targetScene, distance) =
+                    findTargetSceneAndDistanceStrict(fromScene, velocity)
+                        ?: run {
+                            // We will not animate
+                            layoutState.finishTransition(swipeTransition, idleScene = fromScene.key)
+                            return
                         }
-                    )
-                    animateTo(targetScene = fromScene, targetOffset = 0f)
-                } else {
-                    // We will not animate
-                    transitionState = TransitionState.Idle(fromScene.key)
-                }
+
+                updateTransition(
+                    SwipeTransition(fromScene, targetScene, distance).apply {
+                        _currentScene = swipeTransition._currentScene
+                    }
+                )
+                animateTo(targetScene = fromScene, targetOffset = 0f)
             } else {
                 // We were between two scenes: animate to the initial scene.
                 animateTo(targetScene = fromScene, targetOffset = 0f)
@@ -390,14 +433,7 @@
                 )
 
                 swipeTransition.finishOffsetAnimation()
-
-                // Now that the animation is done, the state should be idle. Note that if the state
-                // was changed since this animation started, some external code changed it and we
-                // shouldn't do anything here. Note also that this job will be cancelled in the case
-                // where the user intercepts this swipe.
-                if (isDrivingTransition) {
-                    transitionState = TransitionState.Idle(targetScene)
-                }
+                layoutState.finishTransition(swipeTransition, targetScene)
             }
         }
     }
@@ -410,15 +446,11 @@
          * above or to the left of [toScene].
          */
         val distance: Float
-    ) : TransitionState.Transition {
+    ) : TransitionState.Transition(_fromScene.key, _toScene.key) {
         var _currentScene by mutableStateOf(_fromScene)
         override val currentScene: SceneKey
             get() = _currentScene.key
 
-        override val fromScene: SceneKey = _fromScene.key
-
-        override val toScene: SceneKey = _toScene.key
-
         override val progress: Float
             get() {
                 val offset = if (isAnimatingOffset) offsetAnimatable.value else dragOffset
@@ -494,10 +526,14 @@
 }
 
 internal class SceneNestedScrollHandler(
-    private val gestureHandler: SceneGestureHandler,
-    private val startBehavior: NestedScrollBehavior,
-    private val endBehavior: NestedScrollBehavior,
+    private val layoutImpl: SceneTransitionLayoutImpl,
+    private val orientation: Orientation,
+    private val topOrLeftBehavior: NestedScrollBehavior,
+    private val bottomOrRightBehavior: NestedScrollBehavior,
 ) : NestedScrollHandler {
+    private val layoutState = layoutImpl.state
+    private val gestureHandler = layoutImpl.gestureHandler(orientation)
+
     override val connection: PriorityNestedScrollConnection = nestedScrollConnection()
 
     private fun nestedScrollConnection(): PriorityNestedScrollConnection {
@@ -508,7 +544,7 @@
         val actionUpOrLeft =
             Swipe(
                 direction =
-                    when (gestureHandler.orientation) {
+                    when (orientation) {
                         Orientation.Horizontal -> SwipeDirection.Left
                         Orientation.Vertical -> SwipeDirection.Up
                     },
@@ -518,7 +554,7 @@
         val actionDownOrRight =
             Swipe(
                 direction =
-                    when (gestureHandler.orientation) {
+                    when (orientation) {
                         Orientation.Horizontal -> SwipeDirection.Right
                         Orientation.Vertical -> SwipeDirection.Down
                     },
@@ -526,7 +562,7 @@
             )
 
         fun hasNextScene(amount: Float): Boolean {
-            val fromScene = gestureHandler.currentScene
+            val fromScene = layoutImpl.scene(layoutState.transitionState.currentScene)
             val nextScene =
                 when {
                     amount < 0f -> fromScene.userActions[actionUpOrLeft]
@@ -537,7 +573,7 @@
         }
 
         return PriorityNestedScrollConnection(
-            orientation = gestureHandler.orientation,
+            orientation = orientation,
             canStartPreScroll = { offsetAvailable, offsetBeforeStart ->
                 canChangeScene = offsetBeforeStart == 0f
 
@@ -545,8 +581,9 @@
                     canChangeScene && gestureHandler.isDrivingTransition && offsetAvailable != 0f
                 if (!canInterceptSwipeTransition) return@PriorityNestedScrollConnection false
 
-                val progress = gestureHandler.swipeTransition.progress
-                val threshold = gestureHandler.layoutImpl.transitionInterceptionThreshold
+                val swipeTransition = gestureHandler.swipeTransition
+                val progress = swipeTransition.progress
+                val threshold = layoutImpl.transitionInterceptionThreshold
                 fun isProgressCloseTo(value: Float) = (progress - value).absoluteValue <= threshold
 
                 // The transition is always between 0 and 1. If it is close to either of these
@@ -554,9 +591,8 @@
                 // The progress value can go beyond this range in the case of overscroll.
                 val shouldSnapToIdle = isProgressCloseTo(0f) || isProgressCloseTo(1f)
                 if (shouldSnapToIdle) {
-                    gestureHandler.swipeTransition.cancelOffsetAnimation()
-                    gestureHandler.transitionState =
-                        TransitionState.Idle(gestureHandler.swipeTransition.currentScene)
+                    swipeTransition.cancelOffsetAnimation()
+                    layoutState.finishTransition(swipeTransition, swipeTransition.currentScene)
                 }
 
                 // Start only if we cannot consume this event
@@ -565,8 +601,8 @@
             canStartPostScroll = { offsetAvailable, offsetBeforeStart ->
                 val behavior: NestedScrollBehavior =
                     when {
-                        offsetAvailable > 0f -> startBehavior
-                        offsetAvailable < 0f -> endBehavior
+                        offsetAvailable > 0f -> topOrLeftBehavior
+                        offsetAvailable < 0f -> bottomOrRightBehavior
                         else -> return@PriorityNestedScrollConnection false
                     }
 
@@ -594,8 +630,8 @@
             canStartPostFling = { velocityAvailable ->
                 val behavior: NestedScrollBehavior =
                     when {
-                        velocityAvailable > 0f -> startBehavior
-                        velocityAvailable < 0f -> endBehavior
+                        velocityAvailable > 0f -> topOrLeftBehavior
+                        velocityAvailable < 0f -> bottomOrRightBehavior
                         else -> return@PriorityNestedScrollConnection false
                     }
 
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayout.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayout.kt
index afa184b..3608e37 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayout.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayout.kt
@@ -19,6 +19,8 @@
 import androidx.annotation.FloatRange
 import androidx.compose.foundation.gestures.Orientation
 import androidx.compose.runtime.Composable
+import androidx.compose.runtime.LaunchedEffect
+import androidx.compose.runtime.SideEffect
 import androidx.compose.runtime.Stable
 import androidx.compose.runtime.State
 import androidx.compose.runtime.remember
@@ -27,6 +29,7 @@
 import androidx.compose.ui.graphics.Shape
 import androidx.compose.ui.input.nestedscroll.NestedScrollConnection
 import androidx.compose.ui.platform.LocalDensity
+import kotlinx.coroutines.channels.Channel
 
 /**
  * [SceneTransitionLayout] is a container that automatically animates its content whenever
@@ -126,14 +129,24 @@
      * Adds a [NestedScrollConnection] to intercept scroll events not handled by the scrollable
      * component.
      *
-     * @param orientation is used to determine if we handle top/bottom or left/right events.
-     * @param startBehavior when we should perform the overscroll animation at the top/left.
-     * @param endBehavior when we should perform the overscroll animation at the bottom/right.
+     * @param leftBehavior when we should perform the overscroll animation at the left.
+     * @param rightBehavior when we should perform the overscroll animation at the right.
      */
-    fun Modifier.nestedScrollToScene(
-        orientation: Orientation,
-        startBehavior: NestedScrollBehavior = NestedScrollBehavior.EdgeNoPreview,
-        endBehavior: NestedScrollBehavior = NestedScrollBehavior.EdgeNoPreview,
+    fun Modifier.horizontalNestedScrollToScene(
+        leftBehavior: NestedScrollBehavior = NestedScrollBehavior.EdgeNoPreview,
+        rightBehavior: NestedScrollBehavior = NestedScrollBehavior.EdgeNoPreview,
+    ): Modifier
+
+    /**
+     * Adds a [NestedScrollConnection] to intercept scroll events not handled by the scrollable
+     * component.
+     *
+     * @param topBehavior when we should perform the overscroll animation at the top.
+     * @param bottomBehavior when we should perform the overscroll animation at the bottom.
+     */
+    fun Modifier.verticalNestedScrollToScene(
+        topBehavior: NestedScrollBehavior = NestedScrollBehavior.EdgeNoPreview,
+        bottomBehavior: NestedScrollBehavior = NestedScrollBehavior.EdgeNoPreview,
     ): Modifier
 
     /**
@@ -256,24 +269,45 @@
     val coroutineScope = rememberCoroutineScope()
     val layoutImpl = remember {
         SceneTransitionLayoutImpl(
+                state = state as SceneTransitionLayoutStateImpl,
                 onChangeScene = onChangeScene,
-                builder = scenes,
-                transitions = transitions,
-                state = state,
                 density = density,
                 edgeDetector = edgeDetector,
                 transitionInterceptionThreshold = transitionInterceptionThreshold,
+                builder = scenes,
                 coroutineScope = coroutineScope,
             )
             .also { onLayoutImpl?.invoke(it) }
     }
 
-    layoutImpl.onChangeScene = onChangeScene
-    layoutImpl.transitions = transitions
-    layoutImpl.density = density
-    layoutImpl.edgeDetector = edgeDetector
+    val targetSceneChannel = remember { Channel<SceneKey>(Channel.CONFLATED) }
+    SideEffect {
+        if (state != layoutImpl.state) {
+            error(
+                "This SceneTransitionLayout was bound to a different SceneTransitionLayoutState" +
+                    " that was used when creating it, which is not supported"
+            )
+        }
 
-    layoutImpl.setScenes(scenes)
-    layoutImpl.setCurrentScene(currentScene)
+        layoutImpl.onChangeScene = onChangeScene
+        (state as SceneTransitionLayoutStateImpl).transitions = transitions
+        layoutImpl.density = density
+        layoutImpl.edgeDetector = edgeDetector
+        layoutImpl.updateScenes(scenes)
+
+        state.transitions = transitions
+
+        targetSceneChannel.trySend(currentScene)
+    }
+
+    LaunchedEffect(targetSceneChannel) {
+        for (newKey in targetSceneChannel) {
+            // Inspired by AnimateAsState.kt: let's poll the last value to avoid being one frame
+            // late.
+            val newKey = targetSceneChannel.tryReceive().getOrNull() ?: newKey
+            animateToScene(layoutImpl.state, newKey)
+        }
+    }
+
     layoutImpl.Content(modifier)
 }
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutImpl.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutImpl.kt
index 02ddccb..c99c325 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutImpl.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutImpl.kt
@@ -22,13 +22,8 @@
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.DisposableEffect
 import androidx.compose.runtime.LaunchedEffect
-import androidx.compose.runtime.SideEffect
 import androidx.compose.runtime.Stable
-import androidx.compose.runtime.getValue
 import androidx.compose.runtime.key
-import androidx.compose.runtime.mutableStateOf
-import androidx.compose.runtime.remember
-import androidx.compose.runtime.setValue
 import androidx.compose.runtime.snapshots.SnapshotStateMap
 import androidx.compose.ui.ExperimentalComposeUiApi
 import androidx.compose.ui.Modifier
@@ -40,36 +35,40 @@
 import androidx.compose.ui.util.fastForEach
 import com.android.compose.ui.util.lerp
 import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.channels.Channel
 
 @Stable
 internal class SceneTransitionLayoutImpl(
-    onChangeScene: (SceneKey) -> Unit,
+    internal val state: SceneTransitionLayoutStateImpl,
+    internal var onChangeScene: (SceneKey) -> Unit,
+    internal var density: Density,
+    internal var edgeDetector: EdgeDetector,
+    internal var transitionInterceptionThreshold: Float,
     builder: SceneTransitionLayoutScope.() -> Unit,
-    transitions: SceneTransitions,
-    internal val state: SceneTransitionLayoutState,
-    density: Density,
-    edgeDetector: EdgeDetector,
-    transitionInterceptionThreshold: Float,
     coroutineScope: CoroutineScope,
 ) {
-    internal val scenes = SnapshotStateMap<SceneKey, Scene>()
+    internal val scenes = mutableMapOf<SceneKey, Scene>()
+
+    /**
+     * The map of [Element]s.
+     *
+     * Note that this map is *mutated* directly during composition, so it is a [SnapshotStateMap] to
+     * make sure that mutations are reverted if composition is cancelled.
+     */
     internal val elements = SnapshotStateMap<ElementKey, Element>()
 
-    /** The scenes that are "ready", i.e. they were composed and fully laid-out at least once. */
+    /**
+     * The scenes that are "ready", i.e. they were composed and fully laid-out at least once.
+     *
+     * Note that this map is *read* during composition, so it is a [SnapshotStateMap] to make sure
+     * that we recompose when modifications are made to this map.
+     */
     private val readyScenes = SnapshotStateMap<SceneKey, Boolean>()
 
-    internal var onChangeScene by mutableStateOf(onChangeScene)
-    internal var transitions by mutableStateOf(transitions)
-    internal var density: Density by mutableStateOf(density)
-    internal var edgeDetector by mutableStateOf(edgeDetector)
-    internal var transitionInterceptionThreshold by mutableStateOf(transitionInterceptionThreshold)
-
     private val horizontalGestureHandler: SceneGestureHandler
     private val verticalGestureHandler: SceneGestureHandler
 
     init {
-        setScenes(builder)
+        updateScenes(builder)
 
         // SceneGestureHandler must wait for the scenes to be initialized, in order to access the
         // current scene (required for SwipeTransition).
@@ -98,7 +97,7 @@
         return scenes[key] ?: error("Scene $key is not configured")
     }
 
-    internal fun setScenes(builder: SceneTransitionLayoutScope.() -> Unit) {
+    internal fun updateScenes(builder: SceneTransitionLayoutScope.() -> Unit) {
         // Keep a reference of the current scenes. After processing [builder], the scenes that were
         // not configured will be removed.
         val scenesToRemove = scenes.keys.toMutableSet()
@@ -141,20 +140,6 @@
     }
 
     @Composable
-    internal fun setCurrentScene(key: SceneKey) {
-        val channel = remember { Channel<SceneKey>(Channel.CONFLATED) }
-        SideEffect { channel.trySend(key) }
-        LaunchedEffect(channel) {
-            for (newKey in channel) {
-                // Inspired by AnimateAsState.kt: let's poll the last value to avoid being one frame
-                // late.
-                val newKey = channel.tryReceive().getOrNull() ?: newKey
-                animateToScene(this@SceneTransitionLayoutImpl, newKey)
-            }
-        }
-    }
-
-    @Composable
     @OptIn(ExperimentalComposeUiApi::class)
     internal fun Content(modifier: Modifier) {
         Box(
@@ -171,14 +156,14 @@
 
                     val width: Int
                     val height: Int
-                    val state = state.transitionState
-                    if (state !is TransitionState.Transition || state.fromScene == state.toScene) {
+                    val transition = state.currentTransition
+                    if (transition == null) {
                         width = placeable.width
                         height = placeable.height
                     } else {
                         // Interpolate the size.
-                        val fromSize = scene(state.fromScene).targetSize
-                        val toSize = scene(state.toScene).targetSize
+                        val fromSize = scene(transition.fromScene).targetSize
+                        val toSize = scene(transition.toScene).targetSize
 
                         // Optimization: make sure we don't read state.progress if fromSize ==
                         // toSize to avoid running this code every frame when the layout size does
@@ -187,7 +172,7 @@
                             width = fromSize.width
                             height = fromSize.height
                         } else {
-                            val size = lerp(fromSize, toSize, state.progress)
+                            val size = lerp(fromSize, toSize, transition.progress)
                             width = size.width.coerceAtLeast(0)
                             height = size.height.coerceAtLeast(0)
                         }
@@ -228,16 +213,12 @@
 
                             scene.Content(
                                 Modifier.drawWithContent {
-                                    when (val state = state.transitionState) {
-                                        is TransitionState.Idle -> drawContent()
-                                        is TransitionState.Transition -> {
-                                            // Don't draw scenes that are not ready yet.
-                                            if (
-                                                readyScenes.containsKey(key) ||
-                                                    state.fromScene == state.toScene
-                                            ) {
-                                                drawContent()
-                                            }
+                                    if (state.currentTransition == null) {
+                                        drawContent()
+                                    } else {
+                                        // Don't draw scenes that are not ready yet.
+                                        if (readyScenes.containsKey(key)) {
+                                            drawContent()
                                         }
                                     }
                                 }
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutState.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutState.kt
index f48e914..d1ba582 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutState.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitionLayoutState.kt
@@ -23,36 +23,32 @@
 
 /** The state of a [SceneTransitionLayout]. */
 @Stable
-class SceneTransitionLayoutState(initialScene: SceneKey) {
+sealed interface SceneTransitionLayoutState {
     /**
      * The current [TransitionState]. All values read here are backed by the Snapshot system.
      *
      * To observe those values outside of Compose/the Snapshot system, use
      * [SceneTransitionLayoutState.observableTransitionState] instead.
      */
-    var transitionState: TransitionState by mutableStateOf(TransitionState.Idle(initialScene))
+    val transitionState: TransitionState
+
+    /** The current transition, or `null` if we are idle. */
+    val currentTransition: TransitionState.Transition?
+        get() = transitionState as? TransitionState.Transition
 
     /**
-     * Whether we are transitioning, optionally restricting the check to the transition between
-     * [from] and [to].
+     * Whether we are transitioning. If [from] or [to] is empty, we will also check that they match
+     * the scenes we are animating from and/or to.
      */
-    fun isTransitioning(from: SceneKey? = null, to: SceneKey? = null): Boolean {
-        val transition = transitionState as? TransitionState.Transition ?: return false
-
-        // TODO(b/310915136): Remove this check.
-        if (transition.fromScene == transition.toScene) {
-            return false
-        }
-
-        return (from == null || transition.fromScene == from) &&
-            (to == null || transition.toScene == to)
-    }
+    fun isTransitioning(from: SceneKey? = null, to: SceneKey? = null): Boolean
 
     /** Whether we are transitioning from [scene] to [other], or from [other] to [scene]. */
-    fun isTransitioningBetween(scene: SceneKey, other: SceneKey): Boolean {
-        return isTransitioning(from = scene, to = other) ||
-            isTransitioning(from = other, to = scene)
-    }
+    fun isTransitioningBetween(scene: SceneKey, other: SceneKey): Boolean
+}
+
+/** Create a new [SceneTransitionLayoutState] that is currently idle at scene [currentScene]. */
+fun SceneTransitionLayoutState(currentScene: SceneKey): SceneTransitionLayoutState {
+    return SceneTransitionLayoutStateImpl(currentScene, SceneTransitions.Empty)
 }
 
 @Stable
@@ -71,32 +67,77 @@
     /** No transition/animation is currently running. */
     data class Idle(override val currentScene: SceneKey) : TransitionState
 
-    /**
-     * There is a transition animating between two scenes.
-     *
-     * Important note: [fromScene] and [toScene] might be the same, in which case this [Transition]
-     * should be treated the same as [Idle]. This is designed on purpose so that a [Transition] can
-     * be started without knowing in advance where it is transitioning to, making the logic of
-     * [swipeToScene] easier to reason about.
-     */
-    interface Transition : TransitionState {
-        /** The scene this transition is starting from. */
-        val fromScene: SceneKey
+    /** There is a transition animating between two scenes. */
+    abstract class Transition(
+        /** The scene this transition is starting from. Can't be the same as toScene */
+        val fromScene: SceneKey,
 
-        /** The scene this transition is going to. */
+        /** The scene this transition is going to. Can't be the same as fromScene */
         val toScene: SceneKey
+    ) : TransitionState {
+
+        init {
+            check(fromScene != toScene)
+        }
 
         /**
          * The progress of the transition. This is usually in the `[0; 1]` range, but it can also be
          * less than `0` or greater than `1` when using transitions with a spring AnimationSpec or
          * when flinging quickly during a swipe gesture.
          */
-        val progress: Float
+        abstract val progress: Float
 
         /** Whether the transition was triggered by user input rather than being programmatic. */
-        val isInitiatedByUserInput: Boolean
+        abstract val isInitiatedByUserInput: Boolean
 
         /** Whether user input is currently driving the transition. */
-        val isUserInputOngoing: Boolean
+        abstract val isUserInputOngoing: Boolean
+    }
+}
+
+internal class SceneTransitionLayoutStateImpl(
+    initialScene: SceneKey,
+    internal var transitions: SceneTransitions,
+) : SceneTransitionLayoutState {
+    override var transitionState: TransitionState by
+        mutableStateOf(TransitionState.Idle(initialScene))
+        private set
+
+    /**
+     * The current [transformationSpec] associated to [transitionState]. Accessing this value makes
+     * sense only if [transitionState] is a [TransitionState.Transition].
+     */
+    internal var transformationSpec: TransformationSpecImpl = TransformationSpec.Empty
+
+    override fun isTransitioning(from: SceneKey?, to: SceneKey?): Boolean {
+        val transition = currentTransition ?: return false
+        return (from == null || transition.fromScene == from) &&
+            (to == null || transition.toScene == to)
+    }
+
+    override fun isTransitioningBetween(scene: SceneKey, other: SceneKey): Boolean {
+        return isTransitioning(from = scene, to = other) ||
+            isTransitioning(from = other, to = scene)
+    }
+
+    /** Start a new [transition], instantly interrupting any ongoing transition if there was one. */
+    internal fun startTransition(transition: TransitionState.Transition) {
+        // Compute the [TransformationSpec] when the transition starts.
+        transformationSpec =
+            transitions
+                .transitionSpec(transition.fromScene, transition.toScene)
+                .transformationSpec()
+
+        transitionState = transition
+    }
+
+    /**
+     * Notify that [transition] was finished and that we should settle to [idleScene]. This will do
+     * nothing if [transition] was interrupted since it was started.
+     */
+    internal fun finishTransition(transition: TransitionState.Transition, idleScene: SceneKey) {
+        if (transitionState == transition) {
+            transitionState = TransitionState.Idle(idleScene)
+        }
     }
 }
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitions.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitions.kt
index f91895b..3a55567 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitions.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SceneTransitions.kt
@@ -18,11 +18,9 @@
 
 import androidx.compose.animation.core.AnimationSpec
 import androidx.compose.animation.core.snap
-import androidx.compose.runtime.Stable
 import androidx.compose.ui.geometry.Offset
 import androidx.compose.ui.unit.IntSize
 import androidx.compose.ui.util.fastForEach
-import androidx.compose.ui.util.fastMap
 import com.android.compose.animation.scene.transformation.AnchoredSize
 import com.android.compose.animation.scene.transformation.AnchoredTranslate
 import com.android.compose.animation.scene.transformation.DrawScale
@@ -36,16 +34,17 @@
 import com.android.compose.animation.scene.transformation.Translate
 
 /** The transitions configuration of a [SceneTransitionLayout]. */
-class SceneTransitions(
-    internal val transitionSpecs: List<TransitionSpec>,
+class SceneTransitions
+internal constructor(
+    internal val transitionSpecs: List<TransitionSpecImpl>,
 ) {
-    private val cache = mutableMapOf<SceneKey, MutableMap<SceneKey, TransitionSpec>>()
+    private val cache = mutableMapOf<SceneKey, MutableMap<SceneKey, TransitionSpecImpl>>()
 
-    internal fun transitionSpec(from: SceneKey, to: SceneKey): TransitionSpec {
+    internal fun transitionSpec(from: SceneKey, to: SceneKey): TransitionSpecImpl {
         return cache.getOrPut(from) { mutableMapOf() }.getOrPut(to) { findSpec(from, to) }
     }
 
-    private fun findSpec(from: SceneKey, to: SceneKey): TransitionSpec {
+    private fun findSpec(from: SceneKey, to: SceneKey): TransitionSpecImpl {
         val spec = transition(from, to) { it.from == from && it.to == to }
         if (spec != null) {
             return spec
@@ -53,7 +52,7 @@
 
         val reversed = transition(from, to) { it.from == to && it.to == from }
         if (reversed != null) {
-            return reversed.reverse()
+            return reversed.reversed()
         }
 
         val relaxedSpec =
@@ -67,16 +66,16 @@
         return transition(from, to) {
                 (it.from == to && it.to == null) || (it.to == from && it.from == null)
             }
-            ?.reverse()
+            ?.reversed()
             ?: defaultTransition(from, to)
     }
 
     private fun transition(
         from: SceneKey,
         to: SceneKey,
-        filter: (TransitionSpec) -> Boolean,
-    ): TransitionSpec? {
-        var match: TransitionSpec? = null
+        filter: (TransitionSpecImpl) -> Boolean,
+    ): TransitionSpecImpl? {
+        var match: TransitionSpecImpl? = null
         transitionSpecs.fastForEach { spec ->
             if (filter(spec)) {
                 if (match != null) {
@@ -89,28 +88,88 @@
     }
 
     private fun defaultTransition(from: SceneKey, to: SceneKey) =
-        TransitionSpec(from, to, emptyList(), snap())
+        TransitionSpecImpl(from, to, TransformationSpec.EmptyProvider)
+
+    companion object {
+        val Empty = SceneTransitions(transitionSpecs = emptyList())
+    }
 }
 
 /** The definition of a transition between [from] and [to]. */
-@Stable
-data class TransitionSpec(
-    val from: SceneKey?,
-    val to: SceneKey?,
-    val transformations: List<Transformation>,
-    val spec: AnimationSpec<Float>,
-) {
-    // TODO(b/302300957): Make sure this cache does not infinitely grow.
-    private val cache = mutableMapOf<ElementKey, MutableMap<SceneKey, ElementTransformations>>()
+interface TransitionSpec {
+    /**
+     * The scene we are transitioning from. If `null`, this spec can be used to animate from any
+     * scene.
+     */
+    val from: SceneKey?
 
-    internal fun reverse(): TransitionSpec {
-        return copy(
+    /**
+     * The scene we are transitioning to. If `null`, this spec can be used to animate from any
+     * scene.
+     */
+    val to: SceneKey?
+
+    /**
+     * Return a reversed version of this [TransitionSpec] for a transition going from [to] to
+     * [from].
+     */
+    fun reversed(): TransitionSpec
+
+    /*
+     * The [TransformationSpec] associated to this [TransitionSpec].
+     *
+     * Note that this is called once every a transition associated to this [TransitionSpec] is
+     * started.
+     */
+    fun transformationSpec(): TransformationSpec
+}
+
+interface TransformationSpec {
+    /** The [AnimationSpec] used to animate the associated transition progress. */
+    val progressSpec: AnimationSpec<Float>
+
+    /** The list of [Transformation] applied to elements during this transition. */
+    val transformations: List<Transformation>
+
+    companion object {
+        internal val Empty =
+            TransformationSpecImpl(progressSpec = snap(), transformations = emptyList())
+        internal val EmptyProvider = { Empty }
+    }
+}
+
+internal class TransitionSpecImpl(
+    override val from: SceneKey?,
+    override val to: SceneKey?,
+    private val transformationSpec: () -> TransformationSpecImpl,
+) : TransitionSpec {
+    override fun reversed(): TransitionSpecImpl {
+        return TransitionSpecImpl(
             from = to,
             to = from,
-            transformations = transformations.fastMap { it.reverse() },
+            transformationSpec = {
+                val reverse = transformationSpec.invoke()
+                TransformationSpecImpl(
+                    progressSpec = reverse.progressSpec,
+                    transformations = reverse.transformations.map { it.reversed() }
+                )
+            }
         )
     }
 
+    override fun transformationSpec(): TransformationSpecImpl = this.transformationSpec.invoke()
+}
+
+/**
+ * An implementation of [TransformationSpec] that allows the quick retrieval of an element
+ * [ElementTransformations].
+ */
+internal class TransformationSpecImpl(
+    override val progressSpec: AnimationSpec<Float>,
+    override val transformations: List<Transformation>,
+) : TransformationSpec {
+    private val cache = mutableMapOf<ElementKey, MutableMap<SceneKey, ElementTransformations>>()
+
     internal fun transformations(element: ElementKey, scene: SceneKey): ElementTransformations {
         return cache
             .getOrPut(element) { mutableMapOf() }
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SwipeToScene.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SwipeToScene.kt
index 116a666..0d3bc7d 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SwipeToScene.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/SwipeToScene.kt
@@ -27,7 +27,8 @@
     fun Scene.shouldEnableSwipes(orientation: Orientation): Boolean =
         userActions.keys.any { it is Swipe && it.direction.orientation == orientation }
 
-    val currentScene = gestureHandler.currentScene
+    val layoutImpl = gestureHandler.layoutImpl
+    val currentScene = layoutImpl.scene(layoutImpl.state.transitionState.currentScene)
     val orientation = gestureHandler.orientation
     val canSwipe = currentScene.shouldEnableSwipes(orientation)
     val canOppositeSwipe =
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/TransitionDslImpl.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/TransitionDslImpl.kt
index 8f4a36e..7046866 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/TransitionDslImpl.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/TransitionDslImpl.kt
@@ -44,7 +44,7 @@
 }
 
 private class SceneTransitionsBuilderImpl : SceneTransitionsBuilder {
-    val transitionSpecs = mutableListOf<TransitionSpec>()
+    val transitionSpecs = mutableListOf<TransitionSpecImpl>()
 
     override fun to(to: SceneKey, builder: TransitionBuilder.() -> Unit): TransitionSpec {
         return transition(from = null, to = to, builder)
@@ -63,14 +63,15 @@
         to: SceneKey?,
         builder: TransitionBuilder.() -> Unit,
     ): TransitionSpec {
-        val impl = TransitionBuilderImpl().apply(builder)
-        val spec =
-            TransitionSpec(
-                from,
-                to,
-                impl.transformations,
-                impl.spec,
+        fun transformationSpec(): TransformationSpecImpl {
+            val impl = TransitionBuilderImpl().apply(builder)
+            return TransformationSpecImpl(
+                progressSpec = impl.spec,
+                transformations = impl.transformations,
             )
+        }
+
+        val spec = TransitionSpecImpl(from, to, ::transformationSpec)
         transitionSpecs.add(spec)
         return spec
     }
@@ -143,7 +144,7 @@
 
         transformations.add(
             if (reversed) {
-                transformation.reverse()
+                transformation.reversed()
             } else {
                 transformation
             }
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/Transformation.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/Transformation.kt
index 2069355..0cd11b9 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/Transformation.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/transformation/Transformation.kt
@@ -42,7 +42,7 @@
      * Reverse this transformation. This is called when we use Transition(from = A, to = B) when
      * animating from B to A and there is no Transition(from = B, to = A) defined.
      */
-    fun reverse(): Transformation = this
+    fun reversed(): Transformation = this
 }
 
 internal class SharedElementTransformation(
@@ -77,10 +77,10 @@
     val delegate: PropertyTransformation<T>,
     override val range: TransformationRange,
 ) : PropertyTransformation<T> by delegate {
-    override fun reverse(): Transformation {
+    override fun reversed(): Transformation {
         return RangedPropertyTransformation(
-            delegate.reverse() as PropertyTransformation<T>,
-            range.reverse()
+            delegate.reversed() as PropertyTransformation<T>,
+            range.reversed()
         )
     }
 }
@@ -102,7 +102,7 @@
     }
 
     /** Reverse this range. */
-    fun reverse() = TransformationRange(start = reverseBound(end), end = reverseBound(start))
+    fun reversed() = TransformationRange(start = reverseBound(end), end = reverseBound(start))
 
     /** Get the progress of this range given the global [transitionProgress]. */
     fun progress(transitionProgress: Float): Float {
diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneGestureHandlerTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneGestureHandlerTest.kt
index aa942e0..d9ce519 100644
--- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneGestureHandlerTest.kt
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneGestureHandlerTest.kt
@@ -55,19 +55,25 @@
     ) {
         private var internalCurrentScene: SceneKey by mutableStateOf(SceneA)
 
-        private val layoutState: SceneTransitionLayoutState =
-            SceneTransitionLayoutState(internalCurrentScene)
+        private val layoutState =
+            SceneTransitionLayoutStateImpl(internalCurrentScene, EmptyTestTransitions)
+
+        val mutableUserActionsA: MutableMap<UserAction, SceneKey> =
+            mutableMapOf(Swipe.Up to SceneB, Swipe.Down to SceneC)
+
+        val mutableUserActionsB: MutableMap<UserAction, SceneKey> =
+            mutableMapOf(Swipe.Up to SceneC, Swipe.Down to SceneA)
 
         private val scenesBuilder: SceneTransitionLayoutScope.() -> Unit = {
             scene(
                 key = SceneA,
-                userActions = mapOf(Swipe.Up to SceneB, Swipe.Down to SceneC),
+                userActions = mutableUserActionsA,
             ) {
                 Text("SceneA")
             }
             scene(
                 key = SceneB,
-                userActions = mapOf(Swipe.Up to SceneC, Swipe.Down to SceneA),
+                userActions = mutableUserActionsB,
             ) {
                 Text("SceneB")
             }
@@ -87,38 +93,26 @@
 
         private val layoutImpl =
             SceneTransitionLayoutImpl(
-                    onChangeScene = { internalCurrentScene = it },
-                    builder = scenesBuilder,
-                    transitions = EmptyTestTransitions,
                     state = layoutState,
+                    onChangeScene = { internalCurrentScene = it },
                     density = Density(1f),
                     edgeDetector = DefaultEdgeDetector,
                     transitionInterceptionThreshold = transitionInterceptionThreshold,
+                    builder = scenesBuilder,
                     coroutineScope = coroutineScope,
                 )
                 .apply { setScenesTargetSizeForTest(LAYOUT_SIZE) }
 
-        val sceneGestureHandler =
-            SceneGestureHandler(
-                layoutImpl = layoutImpl,
-                orientation = Orientation.Vertical,
-                coroutineScope = coroutineScope,
-            )
-
-        val horizontalSceneGestureHandler =
-            SceneGestureHandler(
-                layoutImpl = layoutImpl,
-                orientation = Orientation.Horizontal,
-                coroutineScope = coroutineScope,
-            )
-
+        val sceneGestureHandler = layoutImpl.gestureHandler(Orientation.Vertical)
+        val horizontalSceneGestureHandler = layoutImpl.gestureHandler(Orientation.Horizontal)
         val draggable = sceneGestureHandler.draggable
 
         fun nestedScrollConnection(nestedScrollBehavior: NestedScrollBehavior) =
             SceneNestedScrollHandler(
-                    gestureHandler = sceneGestureHandler,
-                    startBehavior = nestedScrollBehavior,
-                    endBehavior = nestedScrollBehavior,
+                    layoutImpl,
+                    orientation = sceneGestureHandler.orientation,
+                    topOrLeftBehavior = nestedScrollBehavior,
+                    bottomOrRightBehavior = nestedScrollBehavior,
                 )
                 .connection
 
@@ -412,6 +406,70 @@
     }
 
     @Test
+    fun onAccelaratedScrollBothTargetsBecomeNull_settlesToIdle() = runGestureTest {
+        draggable.onDragStarted()
+        draggable.onDelta(up(0.2f))
+
+        draggable.onDelta(up(0.2f))
+        draggable.onDragStopped(velocity = -velocityThreshold)
+        assertTransition(currentScene = SceneB, fromScene = SceneA, toScene = SceneB)
+
+        mutableUserActionsA.remove(Swipe.Up)
+        mutableUserActionsA.remove(Swipe.Down)
+        mutableUserActionsB.remove(Swipe.Up)
+        mutableUserActionsB.remove(Swipe.Down)
+
+        // start accelaratedScroll and scroll over to B -> null
+        draggable.onDragStarted()
+        draggable.onDelta(up(0.5f))
+        draggable.onDelta(up(0.5f))
+
+        // here onDragStopped is already triggered, but subsequent onDelta/onDragStopped calls may
+        // still be called. Make sure that they don't crash or change the scene
+        draggable.onDelta(up(0.5f))
+        draggable.onDragStopped(0f)
+
+        advanceUntilIdle()
+        assertIdle(SceneB)
+
+        // These events can still come in after the animation has settled
+        draggable.onDelta(up(0.5f))
+        draggable.onDragStopped(0f)
+        assertIdle(SceneB)
+    }
+
+    @Test
+    fun onDragTargetsChanged_targetStaysTheSame() = runGestureTest {
+        draggable.onDragStarted(up(0.1f))
+        assertTransition(fromScene = SceneA, toScene = SceneB, progress = 0.1f)
+
+        mutableUserActionsA[Swipe.Up] = SceneC
+        draggable.onDelta(up(0.1f))
+        // target stays B even though UserActions changed
+        assertTransition(fromScene = SceneA, toScene = SceneB, progress = 0.2f)
+        draggable.onDragStopped(down(0.1f))
+        advanceUntilIdle()
+
+        // now target changed to C for new drag
+        draggable.onDragStarted(up(0.1f))
+        assertTransition(fromScene = SceneA, toScene = SceneC, progress = 0.1f)
+    }
+
+    @Test
+    fun onDragTargetsChanged_targetsChangeWhenStartingNewDrag() = runGestureTest {
+        draggable.onDragStarted(up(0.1f))
+        assertTransition(fromScene = SceneA, toScene = SceneB, progress = 0.1f)
+
+        mutableUserActionsA[Swipe.Up] = SceneC
+        draggable.onDelta(up(0.1f))
+        draggable.onDragStopped(down(0.1f))
+
+        // now target changed to C for new drag that started before previous drag settled to Idle
+        draggable.onDragStarted(up(0.1f))
+        assertTransition(fromScene = SceneA, toScene = SceneC, progress = 0.3f)
+    }
+
+    @Test
     fun startGestureDuringAnimatingOffset_shouldImmediatelyStopTheAnimation() = runGestureTest {
         draggable.onDragStarted()
         assertTransition(currentScene = SceneA)
diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneTransitionLayoutStateTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneTransitionLayoutStateTest.kt
index 94c51ca..c5b8d9a 100644
--- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneTransitionLayoutStateTest.kt
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/SceneTransitionLayoutStateTest.kt
@@ -39,21 +39,9 @@
     }
 
     @Test
-    fun isTransitioningTo_fromSceneEqualToToScene() {
-        val state = SceneTransitionLayoutState(TestScenes.SceneA)
-        state.transitionState = transition(from = TestScenes.SceneA, to = TestScenes.SceneA)
-
-        assertThat(state.isTransitioning()).isFalse()
-        assertThat(state.isTransitioning(from = TestScenes.SceneA)).isFalse()
-        assertThat(state.isTransitioning(to = TestScenes.SceneB)).isFalse()
-        assertThat(state.isTransitioning(from = TestScenes.SceneA, to = TestScenes.SceneB))
-            .isFalse()
-    }
-
-    @Test
     fun isTransitioningTo_transition() {
-        val state = SceneTransitionLayoutState(TestScenes.SceneA)
-        state.transitionState = transition(from = TestScenes.SceneA, to = TestScenes.SceneB)
+        val state = SceneTransitionLayoutStateImpl(TestScenes.SceneA, SceneTransitions.Empty)
+        state.startTransition(transition(from = TestScenes.SceneA, to = TestScenes.SceneB))
 
         assertThat(state.isTransitioning()).isTrue()
         assertThat(state.isTransitioning(from = TestScenes.SceneA)).isTrue()
@@ -64,10 +52,8 @@
     }
 
     private fun transition(from: SceneKey, to: SceneKey): TransitionState.Transition {
-        return object : TransitionState.Transition {
+        return object : TransitionState.Transition(from, to) {
             override val currentScene: SceneKey = from
-            override val fromScene: SceneKey = from
-            override val toScene: SceneKey = to
             override val progress: Float = 0f
             override val isInitiatedByUserInput: Boolean = false
             override val isUserInputOngoing: Boolean = false
diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/TransitionDslTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/TransitionDslTest.kt
index fa94b250..ef72992 100644
--- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/TransitionDslTest.kt
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/TransitionDslTest.kt
@@ -55,7 +55,7 @@
 
         assertThat(transitions.transitionSpecs)
             .comparingElementsUsing(
-                Correspondence.transforming<TransitionSpec, Pair<SceneKey?, SceneKey?>>(
+                Correspondence.transforming<TransitionSpecImpl, Pair<SceneKey?, SceneKey?>>(
                     { it?.from to it?.to },
                     "has (from, to) equal to"
                 )
@@ -70,8 +70,8 @@
     @Test
     fun defaultTransitionSpec() {
         val transitions = transitions { from(TestScenes.SceneA, to = TestScenes.SceneB) }
-        val transition = transitions.transitionSpecs.single()
-        assertThat(transition.spec).isInstanceOf(SpringSpec::class.java)
+        val transformationSpec = transitions.transitionSpecs.single().transformationSpec()
+        assertThat(transformationSpec.progressSpec).isInstanceOf(SpringSpec::class.java)
     }
 
     @Test
@@ -79,9 +79,9 @@
         val transitions = transitions {
             from(TestScenes.SceneA, to = TestScenes.SceneB) { spec = tween(durationMillis = 42) }
         }
-        val transition = transitions.transitionSpecs.single()
-        assertThat(transition.spec).isInstanceOf(TweenSpec::class.java)
-        assertThat((transition.spec as TweenSpec).durationMillis).isEqualTo(42)
+        val transformationSpec = transitions.transitionSpecs.single().transformationSpec()
+        assertThat(transformationSpec.progressSpec).isInstanceOf(TweenSpec::class.java)
+        assertThat((transformationSpec.progressSpec as TweenSpec).durationMillis).isEqualTo(42)
     }
 
     @Test
@@ -90,9 +90,10 @@
             from(TestScenes.SceneA, to = TestScenes.SceneB) { fade(TestElements.Foo) }
         }
 
-        val transition = transitions.transitionSpecs.single()
-        assertThat(transition.transformations.size).isEqualTo(1)
-        assertThat(transition.transformations.single().range).isEqualTo(null)
+        val transformations =
+            transitions.transitionSpecs.single().transformationSpec().transformations
+        assertThat(transformations.size).isEqualTo(1)
+        assertThat(transformations.single().range).isEqualTo(null)
     }
 
     @Test
@@ -105,8 +106,9 @@
             }
         }
 
-        val transition = transitions.transitionSpecs.single()
-        assertThat(transition.transformations)
+        val transformations =
+            transitions.transitionSpecs.single().transformationSpec().transformations
+        assertThat(transformations)
             .comparingElementsUsing(TRANSFORMATION_RANGE)
             .containsExactly(
                 TransformationRange(start = 0.1f, end = 0.8f),
@@ -127,8 +129,9 @@
             }
         }
 
-        val transition = transitions.transitionSpecs.single()
-        assertThat(transition.transformations)
+        val transformations =
+            transitions.transitionSpecs.single().transformationSpec().transformations
+        assertThat(transformations)
             .comparingElementsUsing(TRANSFORMATION_RANGE)
             .containsExactly(
                 TransformationRange(start = 100 / 500f, end = 300 / 500f),
@@ -149,8 +152,9 @@
             }
         }
 
-        val transition = transitions.transitionSpecs.single()
-        assertThat(transition.transformations)
+        val transformations =
+            transitions.transitionSpecs.single().transformationSpec().transformations
+        assertThat(transformations)
             .comparingElementsUsing(TRANSFORMATION_RANGE)
             .containsExactly(
                 TransformationRange(start = 1f - 0.8f, end = 1f - 0.1f),
@@ -170,9 +174,13 @@
 
         // Fetch the transition from B to A, which will automatically reverse the transition from A
         // to B we defined.
-        val transition =
-            transitions.transitionSpec(from = TestScenes.SceneB, to = TestScenes.SceneA)
-        assertThat(transition.transformations)
+        val transformations =
+            transitions
+                .transitionSpec(from = TestScenes.SceneB, to = TestScenes.SceneA)
+                .transformationSpec()
+                .transformations
+
+        assertThat(transformations)
             .comparingElementsUsing(TRANSFORMATION_RANGE)
             .containsExactly(
                 TransformationRange(start = 1f - 0.8f, end = 1f - 0.1f),
diff --git a/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.kt
index 695d888..f170135 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/keyguard/KeyguardSecurityContainerControllerTest.kt
@@ -18,7 +18,7 @@
 package com.android.keyguard
 
 import android.content.res.Configuration
-import android.hardware.biometrics.BiometricOverlayConstants
+import android.hardware.biometrics.BiometricRequestConstants
 import android.media.AudioManager
 import android.telephony.TelephonyManager
 import android.testing.TestableLooper.RunWithLooper
@@ -59,6 +59,7 @@
 import com.android.systemui.scene.shared.model.ObservableTransitionState
 import com.android.systemui.scene.shared.model.SceneKey
 import com.android.systemui.scene.shared.model.SceneModel
+import com.android.systemui.shared.Flags.FLAG_SIDEFPS_CONTROLLER_REFACTOR
 import com.android.systemui.statusbar.policy.ConfigurationController
 import com.android.systemui.statusbar.policy.DevicePostureController
 import com.android.systemui.statusbar.policy.DeviceProvisionedController
@@ -235,6 +236,7 @@
                 sceneInteractor = sceneInteractor,
             )
 
+        mSetFlagsRule.disableFlags(FLAG_SIDEFPS_CONTROLLER_REFACTOR)
         underTest =
             KeyguardSecurityContainerController(
                 view,
@@ -763,16 +765,18 @@
 
     @Test
     fun sideFpsControllerShow() {
+        mSetFlagsRule.disableFlags(FLAG_SIDEFPS_CONTROLLER_REFACTOR)
         underTest.updateSideFpsVisibility(/* isVisible= */ true)
         verify(sideFpsController)
             .show(
                 SideFpsUiRequestSource.PRIMARY_BOUNCER,
-                BiometricOverlayConstants.REASON_AUTH_KEYGUARD
+                BiometricRequestConstants.REASON_AUTH_KEYGUARD
             )
     }
 
     @Test
     fun sideFpsControllerHide() {
+        mSetFlagsRule.disableFlags(FLAG_SIDEFPS_CONTROLLER_REFACTOR)
         underTest.updateSideFpsVisibility(/* isVisible= */ false)
         verify(sideFpsController).hide(SideFpsUiRequestSource.PRIMARY_BOUNCER)
     }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/data/repository/ColorCorrectionRepositoryImplTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/data/repository/ColorCorrectionRepositoryImplTest.kt
new file mode 100644
index 0000000..74f50d8
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/accessibility/data/repository/ColorCorrectionRepositoryImplTest.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.accessibility.data.repository
+
+import android.os.UserHandle
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.util.settings.FakeSettings
+import com.google.common.truth.Truth
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.first
+import kotlinx.coroutines.flow.launchIn
+import kotlinx.coroutines.test.StandardTestDispatcher
+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
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class ColorCorrectionRepositoryImplTest : SysuiTestCase() {
+    companion object {
+        val TEST_USER_1 = UserHandle.of(1)!!
+        val TEST_USER_2 = UserHandle.of(2)!!
+    }
+
+    private val testDispatcher = StandardTestDispatcher()
+    private val scope = TestScope(testDispatcher)
+    private val settings: FakeSettings = FakeSettings()
+
+    private lateinit var underTest: ColorCorrectionRepository
+
+    @Before
+    fun setUp() {
+        underTest =
+            ColorCorrectionRepositoryImpl(
+                testDispatcher,
+                settings,
+            )
+    }
+
+    @Test
+    fun isEnabled_initiallyGetsSettingsValue() =
+        scope.runTest {
+            settings.putIntForUser(
+                ColorCorrectionRepositoryImpl.SETTING_NAME,
+                1,
+                TEST_USER_1.identifier
+            )
+
+            underTest =
+                ColorCorrectionRepositoryImpl(
+                    testDispatcher,
+                    settings,
+                )
+
+            underTest.isEnabled(TEST_USER_1).launchIn(backgroundScope)
+            runCurrent()
+
+            val actualValue: Boolean = underTest.isEnabled(TEST_USER_1).first()
+            Truth.assertThat(actualValue).isTrue()
+        }
+
+    @Test
+    fun isEnabled_settingUpdated_valueUpdated() =
+        scope.runTest {
+            underTest.isEnabled(TEST_USER_1).launchIn(backgroundScope)
+
+            settings.putIntForUser(
+                ColorCorrectionRepositoryImpl.SETTING_NAME,
+                ColorCorrectionRepositoryImpl.DISABLED,
+                TEST_USER_1.identifier
+            )
+            runCurrent()
+            Truth.assertThat(underTest.isEnabled(TEST_USER_1).first()).isFalse()
+
+            settings.putIntForUser(
+                ColorCorrectionRepositoryImpl.SETTING_NAME,
+                ColorCorrectionRepositoryImpl.ENABLED,
+                TEST_USER_1.identifier
+            )
+            runCurrent()
+            Truth.assertThat(underTest.isEnabled(TEST_USER_1).first()).isTrue()
+
+            settings.putIntForUser(
+                ColorCorrectionRepositoryImpl.SETTING_NAME,
+                ColorCorrectionRepositoryImpl.DISABLED,
+                TEST_USER_1.identifier
+            )
+            runCurrent()
+            Truth.assertThat(underTest.isEnabled(TEST_USER_1).first()).isFalse()
+        }
+
+    @Test
+    fun isEnabled_settingForUserOneOnly_valueUpdatedForUserOneOnly() =
+        scope.runTest {
+            underTest.isEnabled(TEST_USER_1).launchIn(backgroundScope)
+            settings.putIntForUser(
+                ColorCorrectionRepositoryImpl.SETTING_NAME,
+                ColorCorrectionRepositoryImpl.DISABLED,
+                TEST_USER_1.identifier
+            )
+            underTest.isEnabled(TEST_USER_2).launchIn(backgroundScope)
+            settings.putIntForUser(
+                ColorCorrectionRepositoryImpl.SETTING_NAME,
+                ColorCorrectionRepositoryImpl.DISABLED,
+                TEST_USER_2.identifier
+            )
+
+            runCurrent()
+            Truth.assertThat(underTest.isEnabled(TEST_USER_1).first()).isFalse()
+            Truth.assertThat(underTest.isEnabled(TEST_USER_2).first()).isFalse()
+
+            settings.putIntForUser(
+                ColorCorrectionRepositoryImpl.SETTING_NAME,
+                ColorCorrectionRepositoryImpl.ENABLED,
+                TEST_USER_1.identifier
+            )
+            runCurrent()
+            Truth.assertThat(underTest.isEnabled(TEST_USER_1).first()).isTrue()
+            Truth.assertThat(underTest.isEnabled(TEST_USER_2).first()).isFalse()
+        }
+
+    @Test
+    fun setEnabled() =
+        scope.runTest {
+            val success = underTest.setIsEnabled(true, TEST_USER_1)
+            runCurrent()
+            Truth.assertThat(success).isTrue()
+
+            val actualValue =
+                settings.getIntForUser(
+                    ColorCorrectionRepositoryImpl.SETTING_NAME,
+                    TEST_USER_1.identifier
+                )
+            Truth.assertThat(actualValue).isEqualTo(ColorCorrectionRepositoryImpl.ENABLED)
+        }
+
+    @Test
+    fun setDisabled() =
+        scope.runTest {
+            val success = underTest.setIsEnabled(false, TEST_USER_1)
+            runCurrent()
+            Truth.assertThat(success).isTrue()
+
+            val actualValue =
+                settings.getIntForUser(
+                    ColorCorrectionRepositoryImpl.SETTING_NAME,
+                    TEST_USER_1.identifier
+                )
+            Truth.assertThat(actualValue).isEqualTo(ColorCorrectionRepositoryImpl.DISABLED)
+        }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/SideFpsControllerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/SideFpsControllerTest.kt
index a1b801c..f8321b7 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/SideFpsControllerTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/SideFpsControllerTest.kt
@@ -22,9 +22,9 @@
 import android.content.ComponentName
 import android.graphics.Insets
 import android.graphics.Rect
-import android.hardware.biometrics.BiometricOverlayConstants.REASON_AUTH_KEYGUARD
-import android.hardware.biometrics.BiometricOverlayConstants.REASON_AUTH_SETTINGS
-import android.hardware.biometrics.BiometricOverlayConstants.REASON_UNKNOWN
+import android.hardware.biometrics.BiometricRequestConstants.REASON_AUTH_KEYGUARD
+import android.hardware.biometrics.BiometricRequestConstants.REASON_AUTH_SETTINGS
+import android.hardware.biometrics.BiometricRequestConstants.REASON_UNKNOWN
 import android.hardware.biometrics.SensorLocationInternal
 import android.hardware.biometrics.SensorProperties
 import android.hardware.display.DisplayManager
@@ -65,6 +65,7 @@
 import com.android.systemui.keyguard.data.repository.FakeBiometricSettingsRepository
 import com.android.systemui.plugins.statusbar.StatusBarStateController
 import com.android.systemui.res.R
+import com.android.systemui.shared.Flags.FLAG_SIDEFPS_CONTROLLER_REFACTOR
 import com.android.systemui.statusbar.policy.KeyguardStateController
 import com.android.systemui.util.concurrency.FakeExecutor
 import com.android.systemui.util.time.FakeSystemClock
@@ -144,6 +145,7 @@
 
     @Before
     fun setup() {
+        mSetFlagsRule.disableFlags(FLAG_SIDEFPS_CONTROLLER_REFACTOR)
         displayRepository = FakeDisplayRepository()
         displayStateRepository = FakeDisplayStateRepository()
         keyguardBouncerRepository = FakeKeyguardBouncerRepository()
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/UdfpsControllerOverlayTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/UdfpsControllerOverlayTest.kt
index 90d36e7..a726b7c 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/UdfpsControllerOverlayTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/UdfpsControllerOverlayTest.kt
@@ -17,12 +17,12 @@
 package com.android.systemui.biometrics
 
 import android.graphics.Rect
-import android.hardware.biometrics.BiometricOverlayConstants.REASON_AUTH_BP
-import android.hardware.biometrics.BiometricOverlayConstants.REASON_AUTH_KEYGUARD
-import android.hardware.biometrics.BiometricOverlayConstants.REASON_AUTH_OTHER
-import android.hardware.biometrics.BiometricOverlayConstants.REASON_AUTH_SETTINGS
-import android.hardware.biometrics.BiometricOverlayConstants.REASON_ENROLL_ENROLLING
-import android.hardware.biometrics.BiometricOverlayConstants.ShowReason
+import android.hardware.biometrics.BiometricRequestConstants.REASON_AUTH_BP
+import android.hardware.biometrics.BiometricRequestConstants.REASON_AUTH_KEYGUARD
+import android.hardware.biometrics.BiometricRequestConstants.REASON_AUTH_OTHER
+import android.hardware.biometrics.BiometricRequestConstants.REASON_AUTH_SETTINGS
+import android.hardware.biometrics.BiometricRequestConstants.REASON_ENROLL_ENROLLING
+import android.hardware.biometrics.BiometricRequestConstants.RequestReason
 import android.hardware.fingerprint.IUdfpsOverlayControllerCallback
 import android.testing.TestableLooper.RunWithLooper
 import android.view.LayoutInflater
@@ -135,7 +135,7 @@
     }
 
     private fun withReason(
-        @ShowReason reason: Int,
+        @RequestReason reason: Int,
         isDebuggable: Boolean = false,
         enableDeviceEntryUdfpsRefactor: Boolean = false,
         block: () -> Unit,
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/UdfpsControllerTest.java b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/UdfpsControllerTest.java
index 97ee526..dddcf18 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/UdfpsControllerTest.java
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/biometrics/UdfpsControllerTest.java
@@ -43,7 +43,7 @@
 
 import android.graphics.Rect;
 import android.hardware.biometrics.BiometricFingerprintConstants;
-import android.hardware.biometrics.BiometricOverlayConstants;
+import android.hardware.biometrics.BiometricRequestConstants;
 import android.hardware.biometrics.ComponentInfoInternal;
 import android.hardware.biometrics.SensorProperties;
 import android.hardware.display.DisplayManager;
@@ -359,7 +359,7 @@
     @Test
     public void dozeTimeTick() throws RemoteException {
         mOverlayController.showUdfpsOverlay(TEST_REQUEST_ID, mOpticalProps.sensorId,
-                BiometricOverlayConstants.REASON_AUTH_KEYGUARD, mUdfpsOverlayControllerCallback);
+                BiometricRequestConstants.REASON_AUTH_KEYGUARD, mUdfpsOverlayControllerCallback);
         mFgExecutor.runAllReady();
         mUdfpsController.dozeTimeTick();
         verify(mUdfpsView).dozeTimeTick();
@@ -455,7 +455,7 @@
     public void hideUdfpsOverlay_resetsAltAuthBouncerWhenShowing() throws RemoteException {
         // GIVEN overlay was showing and the udfps bouncer is showing
         mOverlayController.showUdfpsOverlay(TEST_REQUEST_ID, mOpticalProps.sensorId,
-                BiometricOverlayConstants.REASON_AUTH_KEYGUARD, mUdfpsOverlayControllerCallback);
+                BiometricRequestConstants.REASON_AUTH_KEYGUARD, mUdfpsOverlayControllerCallback);
         when(mAlternateBouncerInteractor.isVisibleState()).thenReturn(true);
 
         // WHEN the overlay is hidden
@@ -469,7 +469,7 @@
     @Test
     public void showUdfpsOverlay_callsListener() throws RemoteException {
         mOverlayController.showUdfpsOverlay(TEST_REQUEST_ID, mOpticalProps.sensorId,
-                BiometricOverlayConstants.REASON_AUTH_KEYGUARD, mUdfpsOverlayControllerCallback);
+                BiometricRequestConstants.REASON_AUTH_KEYGUARD, mUdfpsOverlayControllerCallback);
         mFgExecutor.runAllReady();
 
         verify(mFingerprintManager).onUdfpsUiEvent(FingerprintManager.UDFPS_UI_OVERLAY_SHOWN,
@@ -479,7 +479,7 @@
     @Test
     public void testSubscribesToOrientationChangesWhenShowingOverlay() throws Exception {
         mOverlayController.showUdfpsOverlay(TEST_REQUEST_ID, mOpticalProps.sensorId,
-                BiometricOverlayConstants.REASON_AUTH_KEYGUARD, mUdfpsOverlayControllerCallback);
+                BiometricRequestConstants.REASON_AUTH_KEYGUARD, mUdfpsOverlayControllerCallback);
         mFgExecutor.runAllReady();
 
         verify(mDisplayManager).registerDisplayListener(any(), eq(mHandler), anyLong());
@@ -520,7 +520,7 @@
                             reset(mWindowManager);
                             mOverlayController.showUdfpsOverlay(TEST_REQUEST_ID,
                                     mOpticalProps.sensorId,
-                                    BiometricOverlayConstants.REASON_ENROLL_ENROLLING,
+                                    BiometricRequestConstants.REASON_ENROLL_ENROLLING,
                                     mUdfpsOverlayControllerCallback);
                             mFgExecutor.runAllReady();
                             verify(mWindowManager).addView(any(), any());
@@ -555,7 +555,7 @@
 
         // Show the overlay.
         mOverlayController.showUdfpsOverlay(TEST_REQUEST_ID, mOpticalProps.sensorId,
-                BiometricOverlayConstants.REASON_ENROLL_ENROLLING, mUdfpsOverlayControllerCallback);
+                BiometricRequestConstants.REASON_ENROLL_ENROLLING, mUdfpsOverlayControllerCallback);
         mFgExecutor.runAllReady();
         verify(mWindowManager).addView(any(), any());
 
@@ -637,7 +637,7 @@
 
         // Show the overlay.
         mOverlayController.showUdfpsOverlay(TEST_REQUEST_ID, testParams.sensorProps.sensorId,
-                BiometricOverlayConstants.REASON_ENROLL_ENROLLING, mUdfpsOverlayControllerCallback);
+                BiometricRequestConstants.REASON_ENROLL_ENROLLING, mUdfpsOverlayControllerCallback);
         mFgExecutor.runAllReady();
         verify(mUdfpsView).setOnTouchListener(mTouchListenerCaptor.capture());
 
@@ -720,7 +720,7 @@
 
         initUdfpsController(testParams.sensorProps);
         mOverlayController.showUdfpsOverlay(TEST_REQUEST_ID, testParams.sensorProps.sensorId,
-                BiometricOverlayConstants.REASON_AUTH_KEYGUARD, mUdfpsOverlayControllerCallback);
+                BiometricRequestConstants.REASON_AUTH_KEYGUARD, mUdfpsOverlayControllerCallback);
         mFgExecutor.runAllReady();
 
         // WHEN ACTION_DOWN is received
@@ -778,7 +778,7 @@
 
         // GIVEN that the overlay is showing and screen is on and fp is running
         mOverlayController.showUdfpsOverlay(TEST_REQUEST_ID, testParams.sensorProps.sensorId,
-                BiometricOverlayConstants.REASON_AUTH_KEYGUARD, mUdfpsOverlayControllerCallback);
+                BiometricRequestConstants.REASON_AUTH_KEYGUARD, mUdfpsOverlayControllerCallback);
         mScreenObserver.onScreenTurnedOn();
         mFgExecutor.runAllReady();
         // WHEN fingerprint is requested because of AOD interrupt
@@ -808,7 +808,7 @@
 
         // GIVEN AOD interrupt
         mOverlayController.showUdfpsOverlay(TEST_REQUEST_ID, testParams.sensorProps.sensorId,
-                BiometricOverlayConstants.REASON_AUTH_KEYGUARD, mUdfpsOverlayControllerCallback);
+                BiometricRequestConstants.REASON_AUTH_KEYGUARD, mUdfpsOverlayControllerCallback);
         mScreenObserver.onScreenTurnedOn();
         mFgExecutor.runAllReady();
         mUdfpsController.onAodInterrupt(0, 0, 0f, 0f);
@@ -886,7 +886,7 @@
 
         // GIVEN overlay is showing
         mOverlayController.showUdfpsOverlay(TEST_REQUEST_ID, testParams.sensorProps.sensorId,
-                BiometricOverlayConstants.REASON_AUTH_KEYGUARD, mUdfpsOverlayControllerCallback);
+                BiometricRequestConstants.REASON_AUTH_KEYGUARD, mUdfpsOverlayControllerCallback);
         mFgExecutor.runAllReady();
         if (testParams.sensorProps.sensorType == FingerprintSensorProperties.TYPE_UDFPS_OPTICAL) {
             when(mUdfpsView.isDisplayConfigured()).thenReturn(true);
@@ -917,7 +917,7 @@
 
         // GIVEN AOD interrupt
         mOverlayController.showUdfpsOverlay(TEST_REQUEST_ID, testParams.sensorProps.sensorId,
-                BiometricOverlayConstants.REASON_AUTH_KEYGUARD, mUdfpsOverlayControllerCallback);
+                BiometricRequestConstants.REASON_AUTH_KEYGUARD, mUdfpsOverlayControllerCallback);
         mScreenObserver.onScreenTurnedOn();
         mFgExecutor.runAllReady();
         mUdfpsController.onAodInterrupt(0, 0, 0f, 0f);
@@ -958,7 +958,7 @@
         final Pair<TouchProcessorResult, TouchProcessorResult> touchProcessorResult =
                 givenFingerEvent(InteractionEvent.DOWN, InteractionEvent.UP, false);
         mOverlayController.showUdfpsOverlay(TEST_REQUEST_ID, testParams.sensorProps.sensorId,
-                BiometricOverlayConstants.REASON_AUTH_KEYGUARD, mUdfpsOverlayControllerCallback);
+                BiometricRequestConstants.REASON_AUTH_KEYGUARD, mUdfpsOverlayControllerCallback);
 
         if (testParams.sensorProps.sensorType == FingerprintSensorProperties.TYPE_UDFPS_OPTICAL) {
             // Configure UdfpsView to accept the ACTION_UP event
@@ -1019,7 +1019,7 @@
 
         // GIVEN screen off
         mOverlayController.showUdfpsOverlay(TEST_REQUEST_ID, testParams.sensorProps.sensorId,
-                BiometricOverlayConstants.REASON_AUTH_KEYGUARD, mUdfpsOverlayControllerCallback);
+                BiometricRequestConstants.REASON_AUTH_KEYGUARD, mUdfpsOverlayControllerCallback);
         mScreenObserver.onScreenTurnedOff();
         mFgExecutor.runAllReady();
 
@@ -1041,7 +1041,7 @@
 
         // GIVEN showing overlay
         mOverlayController.showUdfpsOverlay(TEST_REQUEST_ID, testParams.sensorProps.sensorId,
-                BiometricOverlayConstants.REASON_AUTH_KEYGUARD, mUdfpsOverlayControllerCallback);
+                BiometricRequestConstants.REASON_AUTH_KEYGUARD, mUdfpsOverlayControllerCallback);
         mScreenObserver.onScreenTurnedOn();
         mFgExecutor.runAllReady();
 
@@ -1126,7 +1126,7 @@
         // GIVEN that the overlay is showing and a11y touch exploration NOT enabled
         when(mAccessibilityManager.isTouchExplorationEnabled()).thenReturn(a11y);
         mOverlayController.showUdfpsOverlay(TEST_REQUEST_ID, mOpticalProps.sensorId,
-                BiometricOverlayConstants.REASON_AUTH_KEYGUARD, mUdfpsOverlayControllerCallback);
+                BiometricRequestConstants.REASON_AUTH_KEYGUARD, mUdfpsOverlayControllerCallback);
         mFgExecutor.runAllReady();
 
         if (a11y) {
@@ -1148,7 +1148,7 @@
         // GIVEN that the overlay is showing and a11y touch exploration NOT enabled
         when(mAlternateBouncerInteractor.isVisibleState()).thenReturn(true);
         mOverlayController.showUdfpsOverlay(TEST_REQUEST_ID, mOpticalProps.sensorId,
-                BiometricOverlayConstants.REASON_AUTH_KEYGUARD, mUdfpsOverlayControllerCallback);
+                BiometricRequestConstants.REASON_AUTH_KEYGUARD, mUdfpsOverlayControllerCallback);
         mFgExecutor.runAllReady();
 
         verify(mUdfpsView).setOnTouchListener(mTouchListenerCaptor.capture());
@@ -1197,7 +1197,7 @@
                         -1 /* pointerId */, touchData);
 
         mOverlayController.showUdfpsOverlay(TEST_REQUEST_ID, mOpticalProps.sensorId,
-                BiometricOverlayConstants.REASON_AUTH_KEYGUARD, mUdfpsOverlayControllerCallback);
+                BiometricRequestConstants.REASON_AUTH_KEYGUARD, mUdfpsOverlayControllerCallback);
         mFgExecutor.runAllReady();
 
         verify(mUdfpsView).setOnTouchListener(mTouchListenerCaptor.capture());
@@ -1289,7 +1289,7 @@
     public void onAodInterrupt_onAcquiredGood_fingerNoLongerDown() throws RemoteException {
         // GIVEN UDFPS overlay is showing
         mOverlayController.showUdfpsOverlay(TEST_REQUEST_ID, mOpticalProps.sensorId,
-                BiometricOverlayConstants.REASON_AUTH_KEYGUARD, mUdfpsOverlayControllerCallback);
+                BiometricRequestConstants.REASON_AUTH_KEYGUARD, mUdfpsOverlayControllerCallback);
         mFgExecutor.runAllReady();
 
         // GIVEN there's been an AoD interrupt
@@ -1316,7 +1316,7 @@
             throws RemoteException {
         // GIVEN UDFPS overlay is showing
         mOverlayController.showUdfpsOverlay(TEST_REQUEST_ID, mOpticalProps.sensorId,
-                BiometricOverlayConstants.REASON_AUTH_KEYGUARD, mUdfpsOverlayControllerCallback);
+                BiometricRequestConstants.REASON_AUTH_KEYGUARD, mUdfpsOverlayControllerCallback);
         mFgExecutor.runAllReady();
 
         // GIVEN there's been an AoD interrupt
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/KeyguardBouncerViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/KeyguardBouncerViewModelTest.kt
index 90e0c19..a3bf3f4 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/KeyguardBouncerViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/KeyguardBouncerViewModelTest.kt
@@ -33,6 +33,7 @@
 import com.android.systemui.keyguard.DismissCallbackRegistry
 import com.android.systemui.keyguard.data.repository.TrustRepository
 import com.android.systemui.keyguard.domain.interactor.KeyguardFaceAuthInteractor
+import com.android.systemui.shared.Flags.FLAG_SIDEFPS_CONTROLLER_REFACTOR
 import com.android.systemui.statusbar.policy.KeyguardStateController
 import com.android.systemui.user.domain.interactor.SelectedUserInteractor
 import com.android.systemui.utils.os.FakeHandler
@@ -105,8 +106,10 @@
         job.cancel()
     }
 
+    // TODO(b/288175061): remove with Flags.FLAG_SIDEFPS_CONTROLLER_REFACTOR
     @Test
     fun shouldUpdateSideFps_show() = runTest {
+        mSetFlagsRule.disableFlags(FLAG_SIDEFPS_CONTROLLER_REFACTOR)
         var count = 0
         val job = underTest.shouldUpdateSideFps.onEach { count++ }.launchIn(this)
         repository.setPrimaryShow(true)
@@ -116,8 +119,10 @@
         job.cancel()
     }
 
+    // TODO(b/288175061): remove with Flags.FLAG_SIDEFPS_CONTROLLER_REFACTOR
     @Test
     fun shouldUpdateSideFps_hide() = runTest {
+        mSetFlagsRule.disableFlags(FLAG_SIDEFPS_CONTROLLER_REFACTOR)
         repository.setPrimaryShow(true)
         var count = 0
         val job = underTest.shouldUpdateSideFps.onEach { count++ }.launchIn(this)
@@ -128,8 +133,10 @@
         job.cancel()
     }
 
+    // TODO(b/288175061): remove with Flags.FLAG_SIDEFPS_CONTROLLER_REFACTOR
     @Test
     fun sideFpsShowing() = runTest {
+        mSetFlagsRule.disableFlags(FLAG_SIDEFPS_CONTROLLER_REFACTOR)
         var sideFpsIsShowing = false
         val job = underTest.sideFpsShowing.onEach { sideFpsIsShowing = it }.launchIn(this)
         repository.setSideFpsShowing(true)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorTest.kt
index 16cfa23..1f8e29a 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/domain/interactor/CommunalInteractorTest.kt
@@ -161,7 +161,7 @@
             whenever(target1.remoteViews).thenReturn(mock(RemoteViews::class.java))
 
             val targets = listOf(target1, target2, target3)
-            smartspaceRepository.setLockscreenSmartspaceTargets(targets)
+            smartspaceRepository.setCommunalSmartspaceTargets(targets)
 
             val smartspaceContent by collectLastValue(underTest.smartspaceContent)
             assertThat(smartspaceContent?.size).isEqualTo(1)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalEditModeViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalEditModeViewModelTest.kt
index 8896e6e..314dfdf 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalEditModeViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalEditModeViewModelTest.kt
@@ -116,7 +116,7 @@
             whenever(target.smartspaceTargetId).thenReturn("target")
             whenever(target.featureType).thenReturn(SmartspaceTarget.FEATURE_TIMER)
             whenever(target.remoteViews).thenReturn(Mockito.mock(RemoteViews::class.java))
-            smartspaceRepository.setLockscreenSmartspaceTargets(listOf(target))
+            smartspaceRepository.setCommunalSmartspaceTargets(listOf(target))
 
             // Media playing.
             mediaRepository.mediaPlaying.value = true
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalViewModelTest.kt
index 7fbcae0..8a71168 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/communal/view/viewmodel/CommunalViewModelTest.kt
@@ -135,7 +135,7 @@
             whenever(target.smartspaceTargetId).thenReturn("target")
             whenever(target.featureType).thenReturn(SmartspaceTarget.FEATURE_TIMER)
             whenever(target.remoteViews).thenReturn(Mockito.mock(RemoteViews::class.java))
-            smartspaceRepository.setLockscreenSmartspaceTargets(listOf(target))
+            smartspaceRepository.setCommunalSmartspaceTargets(listOf(target))
 
             // Media playing.
             mediaRepository.mediaPlaying.value = true
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/repository/DeviceEntryFaceAuthRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/repository/DeviceEntryFaceAuthRepositoryTest.kt
index 45aca17..d6d5b23 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/repository/DeviceEntryFaceAuthRepositoryTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/repository/DeviceEntryFaceAuthRepositoryTest.kt
@@ -827,25 +827,6 @@
         }
 
     @Test
-    fun isAuthenticatedIsResetToFalseWhenKeyguardIsGoingAway() =
-        testScope.runTest {
-            initCollectors()
-            allPreconditionsToRunFaceAuthAreTrue()
-
-            triggerFaceAuth(false)
-
-            authenticationCallback.value.onAuthenticationSucceeded(
-                mock(FaceManager.AuthenticationResult::class.java)
-            )
-
-            assertThat(authenticated()).isTrue()
-
-            keyguardRepository.setKeyguardGoingAway(true)
-
-            assertThat(authenticated()).isFalse()
-        }
-
-    @Test
     fun isAuthenticatedIsResetToFalseWhenDeviceStartsGoingToSleep() =
         testScope.runTest {
             initCollectors()
@@ -906,6 +887,25 @@
         }
 
     @Test
+    fun isAuthenticatedIsResetToFalseWhenKeyguardDoneAnimationsFinished() =
+        testScope.runTest {
+            initCollectors()
+            allPreconditionsToRunFaceAuthAreTrue()
+
+            triggerFaceAuth(false)
+
+            authenticationCallback.value.onAuthenticationSucceeded(
+                mock(FaceManager.AuthenticationResult::class.java)
+            )
+
+            assertThat(authenticated()).isTrue()
+
+            keyguardRepository.keyguardDoneAnimationsFinished()
+
+            assertThat(authenticated()).isFalse()
+        }
+
+    @Test
     fun detectDoesNotRunWhenFaceIsNotUsuallyAllowed() =
         testScope.runTest {
             testGatingCheckForDetect {
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/repository/DeviceEntryFingerprintAuthRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/repository/DeviceEntryFingerprintAuthRepositoryTest.kt
index 2b7221e..6b7d263 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/repository/DeviceEntryFingerprintAuthRepositoryTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/data/repository/DeviceEntryFingerprintAuthRepositoryTest.kt
@@ -304,4 +304,34 @@
             )
             assertThat(authenticationStatus).isNull()
         }
+
+    @Test
+    fun onBiometricRunningStateChanged_shouldUpdateIndicatorVisibility() =
+        testScope.runTest {
+            val shouldUpdateIndicatorVisibility by
+                collectLastValue(underTest.shouldUpdateIndicatorVisibility)
+            runCurrent()
+
+            verify(keyguardUpdateMonitor).registerCallback(updateMonitorCallback.capture())
+            assertThat(shouldUpdateIndicatorVisibility).isFalse()
+
+            invokeOnCallback {
+                it.onBiometricRunningStateChanged(false, BiometricSourceType.FINGERPRINT)
+            }
+            assertThat(shouldUpdateIndicatorVisibility).isTrue()
+        }
+
+    @Test
+    fun onStrongAuthStateChanged_shouldUpdateIndicatorVisibility() =
+        testScope.runTest {
+            val shouldUpdateIndicatorVisibility by
+                collectLastValue(underTest.shouldUpdateIndicatorVisibility)
+            runCurrent()
+
+            verify(keyguardUpdateMonitor).registerCallback(updateMonitorCallback.capture())
+            assertThat(shouldUpdateIndicatorVisibility).isFalse()
+
+            invokeOnCallback { it.onStrongAuthStateChanged(0) }
+            assertThat(shouldUpdateIndicatorVisibility).isTrue()
+        }
 }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/preview/KeyguardRemotePreviewManagerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/preview/KeyguardRemotePreviewManagerTest.kt
new file mode 100644
index 0000000..e850456
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/keyguard/ui/preview/KeyguardRemotePreviewManagerTest.kt
@@ -0,0 +1,40 @@
+package com.android.systemui.keyguard.ui.preview
+
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.util.mockito.mock
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.test.StandardTestDispatcher
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.runTest
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class KeyguardRemotePreviewManagerTest : SysuiTestCase() {
+
+    private val testDispatcher = StandardTestDispatcher()
+    private val testScope = TestScope(testDispatcher)
+
+    @Test
+    fun onDestroy_clearsReferencesToRenderer() =
+        testScope.runTest {
+            val renderer = mock<KeyguardPreviewRenderer>()
+            val onDestroy: (PreviewLifecycleObserver) -> Unit = {}
+
+            val observer = PreviewLifecycleObserver(this, testDispatcher, renderer, onDestroy)
+
+            // Precondition check.
+            assertThat(observer.renderer).isNotNull()
+            assertThat(observer.onDestroy).isNotNull()
+
+            observer.onDestroy()
+
+            // The verification checks renderer/requestDestruction lambda because they-re
+            // non-singletons which can't leak KeyguardPreviewRenderer.
+            assertThat(observer.renderer).isNull()
+            assertThat(observer.onDestroy).isNull()
+        }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/data/restoreprocessors/WorkTileRestoreProcessorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/data/restoreprocessors/WorkTileRestoreProcessorTest.kt
new file mode 100644
index 0000000..30d1822
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/data/restoreprocessors/WorkTileRestoreProcessorTest.kt
@@ -0,0 +1,95 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.pipeline.data.restoreprocessors
+
+import android.os.UserHandle
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.qs.pipeline.data.model.RestoreData
+import com.android.systemui.qs.pipeline.shared.TileSpec
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+@OptIn(ExperimentalCoroutinesApi::class)
+class WorkTileRestoreProcessorTest : SysuiTestCase() {
+
+    private val underTest = WorkTileRestoreProcessor()
+    @Test
+    fun restoreWithWorkTile_removeTracking() = runTest {
+        val removeTracking by collectLastValue(underTest.removeTrackingForUser(UserHandle.of(USER)))
+        runCurrent()
+
+        val restoreData =
+            RestoreData(
+                restoredTiles = listOf(TILE_SPEC),
+                restoredAutoAddedTiles = setOf(TILE_SPEC),
+                USER,
+            )
+
+        underTest.postProcessRestore(restoreData)
+
+        assertThat(removeTracking).isEqualTo(Unit)
+    }
+
+    @Test
+    fun restoreWithWorkTile_otherUser_noRemoveTracking() = runTest {
+        val removeTracking by
+            collectLastValue(underTest.removeTrackingForUser(UserHandle.of(USER + 1)))
+        runCurrent()
+
+        val restoreData =
+            RestoreData(
+                restoredTiles = listOf(TILE_SPEC),
+                restoredAutoAddedTiles = setOf(TILE_SPEC),
+                USER,
+            )
+
+        underTest.postProcessRestore(restoreData)
+
+        assertThat(removeTracking).isNull()
+    }
+
+    @Test
+    fun restoreWithoutWorkTile_noSignal() = runTest {
+        val removeTracking by collectLastValue(underTest.removeTrackingForUser(UserHandle.of(USER)))
+        runCurrent()
+
+        val restoreData =
+            RestoreData(
+                restoredTiles = emptyList(),
+                restoredAutoAddedTiles = emptySet(),
+                USER,
+            )
+
+        underTest.postProcessRestore(restoreData)
+
+        assertThat(removeTracking).isNull()
+    }
+
+    companion object {
+        private const val USER = 10
+        private val TILE_SPEC = TileSpec.Companion.create("work")
+    }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/autoaddable/WorkTileAutoAddableTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/autoaddable/WorkTileAutoAddableTest.kt
index adccc84..c7e7845 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/autoaddable/WorkTileAutoAddableTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/autoaddable/WorkTileAutoAddableTest.kt
@@ -25,6 +25,11 @@
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.coroutines.collectValues
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.qs.pipeline.data.model.RestoreData
+import com.android.systemui.qs.pipeline.data.model.RestoreProcessor
+import com.android.systemui.qs.pipeline.data.model.workTileRestoreProcessor
 import com.android.systemui.qs.pipeline.domain.model.AutoAddSignal
 import com.android.systemui.qs.pipeline.domain.model.AutoAddTracking
 import com.android.systemui.qs.pipeline.shared.TileSpec
@@ -32,25 +37,28 @@
 import com.android.systemui.settings.FakeUserTracker
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.runCurrent
 import kotlinx.coroutines.test.runTest
 import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
-import org.mockito.MockitoAnnotations
 
 @OptIn(ExperimentalCoroutinesApi::class)
 @SmallTest
 @RunWith(AndroidJUnit4::class)
 class WorkTileAutoAddableTest : SysuiTestCase() {
 
+    private val kosmos = Kosmos()
+
+    private val restoreProcessor: RestoreProcessor
+        get() = kosmos.workTileRestoreProcessor
+
     private lateinit var userTracker: FakeUserTracker
 
     private lateinit var underTest: WorkTileAutoAddable
 
     @Before
     fun setup() {
-        MockitoAnnotations.initMocks(this)
-
         userTracker =
             FakeUserTracker(
                 _userId = USER_INFO_0.id,
@@ -58,7 +66,7 @@
                 _userProfiles = listOf(USER_INFO_0)
             )
 
-        underTest = WorkTileAutoAddable(userTracker)
+        underTest = WorkTileAutoAddable(userTracker, kosmos.workTileRestoreProcessor)
     }
 
     @Test
@@ -114,10 +122,80 @@
         assertThat(underTest.autoAddTracking).isEqualTo(AutoAddTracking.Always)
     }
 
+    @Test
+    fun restoreDataWithWorkTile_noCurrentManagedProfile_triggersRemove() = runTest {
+        val userId = 0
+        val signal by collectLastValue(underTest.autoAddSignal(userId))
+        runCurrent()
+
+        val restoreData = createRestoreWithWorkTile(userId)
+
+        restoreProcessor.postProcessRestore(restoreData)
+
+        assertThat(signal!!).isEqualTo(AutoAddSignal.RemoveTracking(SPEC))
+    }
+
+    @Test
+    fun restoreDataWithWorkTile_currentlyManagedProfile_doesntTriggerRemove() = runTest {
+        userTracker.set(listOf(USER_INFO_0, USER_INFO_WORK), selectedUserIndex = 0)
+        val userId = 0
+        val signals by collectValues(underTest.autoAddSignal(userId))
+        runCurrent()
+
+        val restoreData = createRestoreWithWorkTile(userId)
+
+        restoreProcessor.postProcessRestore(restoreData)
+
+        assertThat(signals).doesNotContain(AutoAddSignal.RemoveTracking(SPEC))
+    }
+
+    @Test
+    fun restoreDataWithoutWorkTile_noManagedProfile_doesntTriggerRemove() = runTest {
+        val userId = 0
+        val signals by collectValues(underTest.autoAddSignal(userId))
+        runCurrent()
+
+        val restoreData = createRestoreWithoutWorkTile(userId)
+
+        restoreProcessor.postProcessRestore(restoreData)
+
+        assertThat(signals).doesNotContain(AutoAddSignal.RemoveTracking(SPEC))
+    }
+
+    @Test
+    fun restoreDataWithoutWorkTile_managedProfile_doesntTriggerRemove() = runTest {
+        userTracker.set(listOf(USER_INFO_0, USER_INFO_WORK), selectedUserIndex = 0)
+        val userId = 0
+        val signals by collectValues(underTest.autoAddSignal(userId))
+        runCurrent()
+
+        val restoreData = createRestoreWithoutWorkTile(userId)
+
+        restoreProcessor.postProcessRestore(restoreData)
+
+        assertThat(signals).doesNotContain(AutoAddSignal.RemoveTracking(SPEC))
+    }
+
     companion object {
         private val SPEC = TileSpec.create(WorkModeTile.TILE_SPEC)
         private val USER_INFO_0 = UserInfo(0, "", FLAG_PRIMARY or FLAG_FULL)
         private val USER_INFO_1 = UserInfo(1, "", FLAG_FULL)
         private val USER_INFO_WORK = UserInfo(10, "", FLAG_PROFILE or FLAG_MANAGED_PROFILE)
+
+        private fun createRestoreWithWorkTile(userId: Int): RestoreData {
+            return RestoreData(
+                listOf(TileSpec.create("a"), SPEC, TileSpec.create("b")),
+                setOf(SPEC),
+                userId,
+            )
+        }
+
+        private fun createRestoreWithoutWorkTile(userId: Int): RestoreData {
+            return RestoreData(
+                listOf(TileSpec.create("a"), TileSpec.create("b")),
+                emptySet(),
+                userId,
+            )
+        }
     }
 }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/interactor/AutoAddInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/interactor/AutoAddInteractorTest.kt
index 41a7ec0..54b03a9 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/interactor/AutoAddInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/interactor/AutoAddInteractorTest.kt
@@ -183,6 +183,22 @@
             assertThat(autoAddedTiles).contains(SPEC)
         }
 
+    @Test
+    fun autoAddable_removeTrackingSignal_notRemovedButUnmarked() =
+        testScope.runTest {
+            autoAddRepository.markTileAdded(USER, SPEC)
+            val autoAddedTiles by collectLastValue(autoAddRepository.autoAddedTiles(USER))
+            val fakeAutoAddable = FakeAutoAddable(SPEC, AutoAddTracking.Always)
+
+            underTest = createInteractor(setOf(fakeAutoAddable))
+
+            fakeAutoAddable.sendRemoveTrackingSignal(USER)
+            runCurrent()
+
+            verify(currentTilesInteractor, never()).removeTiles(any())
+            assertThat(autoAddedTiles).doesNotContain(SPEC)
+        }
+
     private fun createInteractor(autoAddables: Set<AutoAddable>): AutoAddInteractor {
         return AutoAddInteractor(
                 autoAddables,
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/interactor/RestoreReconciliationInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/interactor/RestoreReconciliationInteractorTest.kt
index f73cab8..b2a9783 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/interactor/RestoreReconciliationInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/interactor/RestoreReconciliationInteractorTest.kt
@@ -5,10 +5,15 @@
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.coroutines.collectLastValue
 import com.android.systemui.qs.pipeline.data.model.RestoreData
+import com.android.systemui.qs.pipeline.data.model.RestoreProcessor
 import com.android.systemui.qs.pipeline.data.repository.FakeAutoAddRepository
 import com.android.systemui.qs.pipeline.data.repository.FakeQSSettingsRestoredRepository
 import com.android.systemui.qs.pipeline.data.repository.FakeTileSpecRepository
 import com.android.systemui.qs.pipeline.data.repository.TilesSettingConverter
+import com.android.systemui.qs.pipeline.domain.interactor.RestoreReconciliationInteractorTest.TestableRestoreProcessor.Companion.POSTPROCESS
+import com.android.systemui.qs.pipeline.domain.interactor.RestoreReconciliationInteractorTest.TestableRestoreProcessor.Companion.PREPROCESS
+import com.android.systemui.qs.pipeline.shared.logging.QSPipelineLogger
+import com.android.systemui.util.mockito.mock
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.test.StandardTestDispatcher
 import kotlinx.coroutines.test.TestScope
@@ -17,7 +22,7 @@
 import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
-import org.mockito.MockitoAnnotations
+import org.mockito.Mockito.inOrder
 
 @RunWith(AndroidJUnit4::class)
 @SmallTest
@@ -28,6 +33,9 @@
 
     private val qsSettingsRestoredRepository = FakeQSSettingsRestoredRepository()
 
+    private val restoreProcessor: TestableRestoreProcessor = TestableRestoreProcessor()
+    private val qsLogger: QSPipelineLogger = mock()
+
     private lateinit var underTest: RestoreReconciliationInteractor
 
     private val testDispatcher = StandardTestDispatcher()
@@ -35,13 +43,13 @@
 
     @Before
     fun setUp() {
-        MockitoAnnotations.initMocks(this)
-
         underTest =
             RestoreReconciliationInteractor(
                 tileSpecRepository,
                 autoAddRepository,
                 qsSettingsRestoredRepository,
+                setOf(restoreProcessor),
+                qsLogger,
                 testScope.backgroundScope,
                 testDispatcher
             )
@@ -85,6 +93,44 @@
             assertThat(autoAdd).isEqualTo(expectedAutoAdd.toTilesSet())
         }
 
+    @Test
+    fun restoreProcessorsCalled() =
+        testScope.runTest {
+            val user = 10
+
+            val restoredSpecs = "a,c,d,f"
+            val restoredAutoAdded = "d,e"
+
+            val restoreData =
+                RestoreData(
+                    restoredSpecs.toTilesList(),
+                    restoredAutoAdded.toTilesSet(),
+                    user,
+                )
+
+            qsSettingsRestoredRepository.onDataRestored(restoreData)
+            runCurrent()
+
+            assertThat(restoreProcessor.calls).containsExactly(PREPROCESS, POSTPROCESS).inOrder()
+        }
+
+    private class TestableRestoreProcessor : RestoreProcessor {
+        val calls = mutableListOf<Any>()
+
+        override suspend fun preProcessRestore(restoreData: RestoreData) {
+            calls.add(PREPROCESS)
+        }
+
+        override suspend fun postProcessRestore(restoreData: RestoreData) {
+            calls.add(POSTPROCESS)
+        }
+
+        companion object {
+            val PREPROCESS = Any()
+            val POSTPROCESS = Any()
+        }
+    }
+
     companion object {
         private fun String.toTilesList() = TilesSettingConverter.toTilesList(this)
         private fun String.toTilesSet() = TilesSettingConverter.toTilesSet(this)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/interactor/WorkProfileAutoAddedAfterRestoreTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/interactor/WorkProfileAutoAddedAfterRestoreTest.kt
new file mode 100644
index 0000000..96d5774
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/pipeline/domain/interactor/WorkProfileAutoAddedAfterRestoreTest.kt
@@ -0,0 +1,176 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.pipeline.domain.interactor
+
+import android.content.pm.UserInfo
+import android.os.UserManager
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.MediumTest
+import com.android.systemui.Flags.FLAG_QS_NEW_PIPELINE
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.testScope
+import com.android.systemui.plugins.qs.QSTile
+import com.android.systemui.qs.FakeQSFactory
+import com.android.systemui.qs.pipeline.data.model.RestoreData
+import com.android.systemui.qs.pipeline.data.repository.fakeRestoreRepository
+import com.android.systemui.qs.pipeline.data.repository.fakeTileSpecRepository
+import com.android.systemui.qs.pipeline.shared.TileSpec
+import com.android.systemui.qs.qsTileFactory
+import com.android.systemui.settings.fakeUserTracker
+import com.android.systemui.settings.userTracker
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+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
+
+/**
+ * This integration test is for testing the solution to b/314781280. In particular, there are two
+ * issues we want to verify after a restore of a device with a work profile and a work mode tile:
+ * * When the work profile is re-enabled in the target device, it is auto-added.
+ * * The tile is auto-added in the same position that it was in the restored device.
+ */
+@MediumTest
+@RunWith(AndroidJUnit4::class)
+@OptIn(ExperimentalCoroutinesApi::class)
+class WorkProfileAutoAddedAfterRestoreTest : SysuiTestCase() {
+
+    private val kosmos = Kosmos().apply { fakeUserTracker.set(listOf(USER_0_INFO), 0) }
+    // Getter here so it can change when there is a managed profile.
+    private val workTileAvailable: Boolean
+        get() = hasManagedProfile()
+    private val currentUser: Int
+        get() = kosmos.userTracker.userId
+
+    private val testScope: TestScope
+        get() = kosmos.testScope
+
+    @Before
+    fun setUp() {
+        mSetFlagsRule.enableFlags(FLAG_QS_NEW_PIPELINE)
+
+        kosmos.qsTileFactory = FakeQSFactory(::tileCreator)
+        kosmos.restoreReconciliationInteractor.start()
+        kosmos.autoAddInteractor.init(kosmos.currentTilesInteractor)
+    }
+
+    @Test
+    fun workTileRestoredAndPreviouslyAutoAdded_notAvailable_willBeAutoaddedInCorrectPosition() =
+        testScope.runTest {
+            val tiles by collectLastValue(kosmos.currentTilesInteractor.currentTiles)
+
+            // Set up
+            val currentTiles = listOf("a".toTileSpec())
+            kosmos.fakeTileSpecRepository.setTiles(currentUser, currentTiles)
+
+            val restoredTiles =
+                listOf(WORK_TILE_SPEC) + listOf("b", "c", "d").map { it.toTileSpec() }
+            val restoredAutoAdded = setOf(WORK_TILE_SPEC)
+
+            val restoreData = RestoreData(restoredTiles, restoredAutoAdded, currentUser)
+
+            // WHEN we restore tiles that auto-added the WORK tile and it's not available (there
+            // are no managed profiles)
+            kosmos.fakeRestoreRepository.onDataRestored(restoreData)
+
+            // THEN the work tile is not part of the current tiles
+            assertThat(tiles!!).hasSize(3)
+            assertThat(tiles!!.map { it.spec }).doesNotContain(WORK_TILE_SPEC)
+
+            // WHEN we add a work profile
+            createManagedProfileAndAdd()
+
+            // THEN the work profile is added in the correct place
+            assertThat(tiles!!.first().spec).isEqualTo(WORK_TILE_SPEC)
+        }
+
+    @Test
+    fun workTileNotRestoredAndPreviouslyAutoAdded_wontBeAutoAddedWhenWorkProfileIsAdded() =
+        testScope.runTest {
+            val tiles by collectLastValue(kosmos.currentTilesInteractor.currentTiles)
+
+            // Set up
+            val currentTiles = listOf("a".toTileSpec())
+            kosmos.fakeTileSpecRepository.setTiles(currentUser, currentTiles)
+            runCurrent()
+
+            val restoredTiles = listOf("b", "c", "d").map { it.toTileSpec() }
+            val restoredAutoAdded = setOf(WORK_TILE_SPEC)
+
+            val restoreData = RestoreData(restoredTiles, restoredAutoAdded, currentUser)
+
+            // WHEN we restore tiles that auto-added the WORK tile
+            kosmos.fakeRestoreRepository.onDataRestored(restoreData)
+
+            // THEN the work tile is not part of the current tiles
+            assertThat(tiles!!).hasSize(3)
+            assertThat(tiles!!.map { it.spec }).doesNotContain(WORK_TILE_SPEC)
+
+            // WHEN we add a work profile
+            createManagedProfileAndAdd()
+
+            // THEN the work profile is not added because the user had manually removed it in the
+            // past
+            assertThat(tiles!!.map { it.spec }).doesNotContain(WORK_TILE_SPEC)
+        }
+
+    private fun tileCreator(spec: String): QSTile {
+        return if (spec == WORK_TILE_SPEC.spec) {
+            FakeQSTile(currentUser, workTileAvailable)
+        } else {
+            FakeQSTile(currentUser)
+        }
+    }
+
+    private fun hasManagedProfile(): Boolean {
+        return kosmos.userTracker.userProfiles.any { it.isManagedProfile }
+    }
+
+    private fun TestScope.createManagedProfileAndAdd() {
+        kosmos.fakeUserTracker.set(
+            listOf(USER_0_INFO, MANAGED_USER_INFO),
+            0,
+        )
+        runCurrent()
+    }
+
+    private companion object {
+        val WORK_TILE_SPEC = "work".toTileSpec()
+        val USER_0_INFO =
+            UserInfo(
+                0,
+                "zero",
+                "",
+                UserInfo.FLAG_ADMIN or UserInfo.FLAG_FULL,
+            )
+        val MANAGED_USER_INFO =
+            UserInfo(
+                10,
+                "ten-managed",
+                "",
+                0,
+                UserManager.USER_TYPE_PROFILE_MANAGED,
+            )
+
+        fun String.toTileSpec() = TileSpec.create(this)
+    }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/alarm/domain/AlarmTileMapperTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/alarm/domain/AlarmTileMapperTest.kt
index 2b744ac..00405d0 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/alarm/domain/AlarmTileMapperTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/alarm/domain/AlarmTileMapperTest.kt
@@ -17,6 +17,7 @@
 package com.android.systemui.qs.tiles.impl.alarm.domain
 
 import android.app.AlarmManager
+import android.graphics.drawable.TestStubDrawable
 import android.widget.Switch
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
@@ -40,7 +41,14 @@
     private val kosmos = Kosmos()
     private val alarmTileConfig = kosmos.qsAlarmTileConfig
     // Using lazy (versus =) to make sure we override the right context -- see b/311612168
-    private val mapper by lazy { AlarmTileMapper(context.orCreateTestableResources.resources) }
+    private val mapper by lazy {
+        AlarmTileMapper(
+            context.orCreateTestableResources
+                .apply { addOverride(R.drawable.ic_alarm, TestStubDrawable()) }
+                .resources,
+            context.theme
+        )
+    }
 
     @Test
     fun notAlarmSet() {
@@ -100,7 +108,7 @@
     ): QSTileState {
         val label = context.getString(R.string.status_bar_alarm)
         return QSTileState(
-            { Icon.Resource(R.drawable.ic_alarm, null) },
+            { Icon.Loaded(context.getDrawable(R.drawable.ic_alarm)!!, null) },
             label,
             activationState,
             secondaryLabel,
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/colorcorrection/domain/ColorCorrectionTileMapperTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/colorcorrection/domain/ColorCorrectionTileMapperTest.kt
new file mode 100644
index 0000000..8ee6d20
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/colorcorrection/domain/ColorCorrectionTileMapperTest.kt
@@ -0,0 +1,91 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.tiles.impl.colorcorrection.domain
+
+import android.graphics.drawable.TestStubDrawable
+import android.widget.Switch
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.common.shared.model.Icon
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.qs.tiles.impl.colorcorrection.domain.model.ColorCorrectionTileModel
+import com.android.systemui.qs.tiles.impl.colorcorrection.qsColorCorrectionTileConfig
+import com.android.systemui.qs.tiles.impl.custom.QSTileStateSubject
+import com.android.systemui.qs.tiles.viewmodel.QSTileState
+import com.android.systemui.res.R
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class ColorCorrectionTileMapperTest : SysuiTestCase() {
+    private val kosmos = Kosmos()
+    private val colorCorrectionTileConfig = kosmos.qsColorCorrectionTileConfig
+    private val subtitleArray =
+        context.resources.getStringArray(R.array.tile_states_color_correction)
+    // Using lazy (versus =) to make sure we override the right context -- see b/311612168
+    private val mapper by lazy {
+        ColorCorrectionTileMapper(
+            context.orCreateTestableResources
+                .apply { addOverride(R.drawable.ic_qs_color_correction, TestStubDrawable()) }
+                .resources,
+            context.theme
+        )
+    }
+
+    @Test
+    fun disabledModel() {
+        val inputModel = ColorCorrectionTileModel(false)
+
+        val outputState = mapper.map(colorCorrectionTileConfig, inputModel)
+
+        val expectedState =
+            createColorCorrectionTileState(QSTileState.ActivationState.INACTIVE, subtitleArray[1])
+        QSTileStateSubject.assertThat(outputState).isEqualTo(expectedState)
+    }
+
+    @Test
+    fun enabledModel() {
+        val inputModel = ColorCorrectionTileModel(true)
+
+        val outputState = mapper.map(colorCorrectionTileConfig, inputModel)
+
+        val expectedState =
+            createColorCorrectionTileState(QSTileState.ActivationState.ACTIVE, subtitleArray[2])
+        QSTileStateSubject.assertThat(outputState).isEqualTo(expectedState)
+    }
+
+    private fun createColorCorrectionTileState(
+        activationState: QSTileState.ActivationState,
+        secondaryLabel: String
+    ): QSTileState {
+        val label = context.getString(R.string.quick_settings_color_correction_label)
+        return QSTileState(
+            { Icon.Loaded(context.getDrawable(R.drawable.ic_qs_color_correction)!!, null) },
+            label,
+            activationState,
+            secondaryLabel,
+            setOf(QSTileState.UserAction.CLICK, QSTileState.UserAction.LONG_CLICK),
+            label,
+            null,
+            QSTileState.SideViewIcon.None,
+            QSTileState.EnabledState.ENABLED,
+            Switch::class.qualifiedName
+        )
+    }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/colorcorrection/domain/interactor/ColorCorrectionTileDataInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/colorcorrection/domain/interactor/ColorCorrectionTileDataInteractorTest.kt
new file mode 100644
index 0000000..8c612ac
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/colorcorrection/domain/interactor/ColorCorrectionTileDataInteractorTest.kt
@@ -0,0 +1,72 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.tiles.impl.colorcorrection.domain.interactor
+
+import android.os.UserHandle
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.accessibility.data.repository.FakeColorCorrectionRepository
+import com.android.systemui.coroutines.collectValues
+import com.android.systemui.qs.tiles.base.interactor.DataUpdateTrigger
+import com.android.systemui.qs.tiles.impl.colorcorrection.domain.model.ColorCorrectionTileModel
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.flowOf
+import kotlinx.coroutines.flow.toCollection
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class ColorCorrectionTileDataInteractorTest : SysuiTestCase() {
+
+    private val colorCorrectionRepository = FakeColorCorrectionRepository()
+    private val underTest: ColorCorrectionTileDataInteractor =
+        ColorCorrectionTileDataInteractor(colorCorrectionRepository)
+
+    @Test
+    fun alwaysAvailable() = runTest {
+        val availability = underTest.availability(TEST_USER).toCollection(mutableListOf())
+
+        assertThat(availability).hasSize(1)
+        assertThat(availability.last()).isTrue()
+    }
+
+    @Test
+    fun dataMatchesTheRepository() = runTest {
+        val dataList: List<ColorCorrectionTileModel> by
+            collectValues(underTest.tileData(TEST_USER, flowOf(DataUpdateTrigger.InitialRequest)))
+        runCurrent()
+
+        colorCorrectionRepository.setIsEnabled(true, TEST_USER)
+        runCurrent()
+
+        colorCorrectionRepository.setIsEnabled(false, TEST_USER)
+        runCurrent()
+
+        assertThat(dataList).hasSize(3)
+        assertThat(dataList.map { it.isEnabled }).isEqualTo(listOf(false, true, false))
+    }
+
+    private companion object {
+        val TEST_USER = UserHandle.of(1)!!
+    }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/colorcorrection/domain/interactor/ColorCorrectionTileUserActionInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/colorcorrection/domain/interactor/ColorCorrectionTileUserActionInteractorTest.kt
new file mode 100644
index 0000000..3049cc0
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/colorcorrection/domain/interactor/ColorCorrectionTileUserActionInteractorTest.kt
@@ -0,0 +1,89 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.tiles.impl.colorcorrection.domain.interactor
+
+import android.os.UserHandle
+import android.provider.Settings
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.accessibility.data.repository.FakeColorCorrectionRepository
+import com.android.systemui.qs.tiles.base.actions.FakeQSTileIntentUserInputHandler
+import com.android.systemui.qs.tiles.base.actions.QSTileIntentUserInputHandlerSubject
+import com.android.systemui.qs.tiles.base.interactor.QSTileInputTestKtx
+import com.android.systemui.qs.tiles.impl.colorcorrection.domain.model.ColorCorrectionTileModel
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.test.runTest
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class ColorCorrectionTileUserActionInteractorTest : SysuiTestCase() {
+
+    private val testUser = UserHandle.CURRENT
+    private val repository = FakeColorCorrectionRepository()
+    private val inputHandler = FakeQSTileIntentUserInputHandler()
+
+    private val underTest =
+        ColorCorrectionUserActionInteractor(
+            repository,
+            inputHandler,
+        )
+
+    @Test
+    fun handleClickWhenEnabled() = runTest {
+        val wasEnabled = true
+        repository.setIsEnabled(wasEnabled, testUser)
+
+        underTest.handleInput(QSTileInputTestKtx.click(ColorCorrectionTileModel(wasEnabled)))
+
+        assertThat(repository.isEnabled(testUser).value).isEqualTo(!wasEnabled)
+    }
+
+    @Test
+    fun handleClickWhenDisabled() = runTest {
+        val wasEnabled = false
+        repository.setIsEnabled(wasEnabled, testUser)
+
+        underTest.handleInput(QSTileInputTestKtx.click(ColorCorrectionTileModel(wasEnabled)))
+
+        assertThat(repository.isEnabled(testUser).value).isEqualTo(!wasEnabled)
+    }
+
+    @Test
+    fun handleLongClickWhenDisabled() = runTest {
+        val enabled = false
+
+        underTest.handleInput(QSTileInputTestKtx.longClick(ColorCorrectionTileModel(enabled)))
+
+        QSTileIntentUserInputHandlerSubject.assertThat(inputHandler).handledOneIntentInput {
+            assertThat(it.intent.action).isEqualTo(Settings.ACTION_COLOR_CORRECTION_SETTINGS)
+        }
+    }
+
+    @Test
+    fun handleLongClickWhenEnabled() = runTest {
+        val enabled = true
+
+        underTest.handleInput(QSTileInputTestKtx.longClick(ColorCorrectionTileModel(enabled)))
+
+        QSTileIntentUserInputHandlerSubject.assertThat(inputHandler).handledOneIntentInput {
+            assertThat(it.intent.action).isEqualTo(Settings.ACTION_COLOR_CORRECTION_SETTINGS)
+        }
+    }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/flashlight/domain/FlashlightMapperTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/flashlight/domain/FlashlightMapperTest.kt
index 7b2ac90..b60f483 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/flashlight/domain/FlashlightMapperTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/flashlight/domain/FlashlightMapperTest.kt
@@ -16,6 +16,7 @@
 
 package com.android.systemui.qs.tiles.impl.flashlight.domain
 
+import android.graphics.drawable.TestStubDrawable
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
@@ -35,7 +36,17 @@
 class FlashlightMapperTest : SysuiTestCase() {
     private val kosmos = Kosmos()
     private val qsTileConfig = kosmos.qsFlashlightTileConfig
-    private val mapper by lazy { FlashlightMapper(context.orCreateTestableResources.resources) }
+    private val mapper by lazy {
+        FlashlightMapper(
+            context.orCreateTestableResources
+                .apply {
+                    addOverride(R.drawable.qs_flashlight_icon_off, TestStubDrawable())
+                    addOverride(R.drawable.qs_flashlight_icon_on, TestStubDrawable())
+                }
+                .resources,
+            context.theme
+        )
+    }
 
     @Test
     fun mapsDisabledDataToInactiveState() {
@@ -56,20 +67,20 @@
 
     @Test
     fun mapsEnabledDataToOnIconState() {
-        val expectedIcon = Icon.Resource(R.drawable.qs_flashlight_icon_on, null)
-
         val tileState: QSTileState = mapper.map(qsTileConfig, FlashlightTileModel(true))
 
+        val expectedIcon =
+            Icon.Loaded(context.getDrawable(R.drawable.qs_flashlight_icon_on)!!, null)
         val actualIcon = tileState.icon()
         assertThat(actualIcon).isEqualTo(expectedIcon)
     }
 
     @Test
     fun mapsDisabledDataToOffIconState() {
-        val expectedIcon = Icon.Resource(R.drawable.qs_flashlight_icon_off, null)
-
         val tileState: QSTileState = mapper.map(qsTileConfig, FlashlightTileModel(false))
 
+        val expectedIcon =
+            Icon.Loaded(context.getDrawable(R.drawable.qs_flashlight_icon_off)!!, null)
         val actualIcon = tileState.icon()
         assertThat(actualIcon).isEqualTo(expectedIcon)
     }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/location/domain/LocationTileMapperTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/location/domain/LocationTileMapperTest.kt
index 8791877..ea74a4c 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/location/domain/LocationTileMapperTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/location/domain/LocationTileMapperTest.kt
@@ -16,6 +16,7 @@
 
 package com.android.systemui.qs.tiles.impl.location.domain
 
+import android.graphics.drawable.TestStubDrawable
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
@@ -36,7 +37,17 @@
     private val kosmos = Kosmos()
     private val qsTileConfig = kosmos.qsLocationTileConfig
 
-    private val mapper by lazy { LocationTileMapper(context.orCreateTestableResources.resources) }
+    private val mapper by lazy {
+        LocationTileMapper(
+            context.orCreateTestableResources
+                .apply {
+                    addOverride(R.drawable.qs_location_icon_off, TestStubDrawable())
+                    addOverride(R.drawable.qs_location_icon_on, TestStubDrawable())
+                }
+                .resources,
+            context.theme
+        )
+    }
 
     @Test
     fun mapsDisabledDataToInactiveState() {
@@ -56,20 +67,18 @@
 
     @Test
     fun mapsEnabledDataToOnIconState() {
-        val expectedIcon = Icon.Resource(R.drawable.qs_location_icon_on, null)
-
         val tileState: QSTileState = mapper.map(qsTileConfig, LocationTileModel(true))
 
+        val expectedIcon = Icon.Loaded(context.getDrawable(R.drawable.qs_location_icon_on)!!, null)
         val actualIcon = tileState.icon()
         Truth.assertThat(actualIcon).isEqualTo(expectedIcon)
     }
 
     @Test
     fun mapsDisabledDataToOffIconState() {
-        val expectedIcon = Icon.Resource(R.drawable.qs_location_icon_off, null)
-
         val tileState: QSTileState = mapper.map(qsTileConfig, LocationTileModel(false))
 
+        val expectedIcon = Icon.Loaded(context.getDrawable(R.drawable.qs_location_icon_off)!!, null)
         val actualIcon = tileState.icon()
         Truth.assertThat(actualIcon).isEqualTo(expectedIcon)
     }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/saver/domain/DataSaverTileMapperTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/saver/domain/DataSaverTileMapperTest.kt
index d182412..d162c77 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/saver/domain/DataSaverTileMapperTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/saver/domain/DataSaverTileMapperTest.kt
@@ -16,6 +16,7 @@
 
 package com.android.systemui.qs.tiles.impl.saver.domain
 
+import android.graphics.drawable.TestStubDrawable
 import android.widget.Switch
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
@@ -37,7 +38,17 @@
     private val dataSaverTileConfig = kosmos.qsDataSaverTileConfig
 
     // Using lazy (versus =) to make sure we override the right context -- see b/311612168
-    private val mapper by lazy { DataSaverTileMapper(context.orCreateTestableResources.resources) }
+    private val mapper by lazy {
+        DataSaverTileMapper(
+            context.orCreateTestableResources
+                .apply {
+                    addOverride(R.drawable.qs_data_saver_icon_off, TestStubDrawable())
+                    addOverride(R.drawable.qs_data_saver_icon_on, TestStubDrawable())
+                }
+                .resources,
+            context.theme
+        )
+    }
 
     @Test
     fun activeStateMatchesEnabledModel() {
@@ -80,7 +91,7 @@
             else context.resources.getStringArray(R.array.tile_states_saver)[0]
 
         return QSTileState(
-            { Icon.Resource(iconRes, null) },
+            { Icon.Loaded(context.getDrawable(iconRes)!!, null) },
             label,
             activationState,
             secondaryLabel,
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/uimodenight/domain/UiModeNightTileMapperTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/uimodenight/domain/UiModeNightTileMapperTest.kt
index 87f5009..a977606 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/uimodenight/domain/UiModeNightTileMapperTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/qs/tiles/impl/uimodenight/domain/UiModeNightTileMapperTest.kt
@@ -17,6 +17,7 @@
 package com.android.systemui.qs.tiles.impl.uimodenight.domain
 
 import android.app.UiModeManager
+import android.graphics.drawable.TestStubDrawable
 import android.text.TextUtils
 import android.view.View
 import android.widget.Switch
@@ -41,7 +42,15 @@
     private val qsTileConfig = kosmos.qsUiModeNightTileConfig
 
     private val mapper by lazy {
-        UiModeNightTileMapper(context.orCreateTestableResources.resources)
+        UiModeNightTileMapper(
+            context.orCreateTestableResources
+                .apply {
+                    addOverride(R.drawable.qs_light_dark_theme_icon_off, TestStubDrawable())
+                    addOverride(R.drawable.qs_light_dark_theme_icon_on, TestStubDrawable())
+                }
+                .resources,
+            context.theme
+        )
     }
 
     private fun createUiNightModeTileState(
@@ -60,7 +69,7 @@
         expandedAccessibilityClass: KClass<out View>? = Switch::class,
     ): QSTileState {
         return QSTileState(
-            { Icon.Resource(iconRes, null) },
+            { Icon.Loaded(context.getDrawable(iconRes)!!, null) },
             label,
             activationState,
             secondaryLabel,
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/smartspace/CommunalSmartspaceControllerTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/smartspace/CommunalSmartspaceControllerTest.kt
new file mode 100644
index 0000000..ef2046d
--- /dev/null
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/smartspace/CommunalSmartspaceControllerTest.kt
@@ -0,0 +1,172 @@
+/*
+ * 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.smartspace
+
+import android.app.smartspace.SmartspaceManager
+import android.app.smartspace.SmartspaceSession
+import android.app.smartspace.SmartspaceTarget
+import android.content.Context
+import android.graphics.drawable.Drawable
+import android.testing.TestableLooper
+import android.view.View
+import android.widget.FrameLayout
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.communal.smartspace.CommunalSmartspaceController
+import com.android.systemui.plugins.BcSmartspaceConfigPlugin
+import com.android.systemui.plugins.BcSmartspaceDataPlugin
+import com.android.systemui.plugins.BcSmartspaceDataPlugin.SmartspaceView
+import com.android.systemui.plugins.FalsingManager
+import com.android.systemui.util.concurrency.Execution
+import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.withArgCaptor
+import com.google.common.truth.Truth.assertThat
+import java.util.Optional
+import java.util.concurrent.Executor
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mock
+import org.mockito.Mockito
+import org.mockito.Mockito.verify
+import org.mockito.Mockito.`when`
+import org.mockito.MockitoAnnotations
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+@TestableLooper.RunWithLooper
+class CommunalSmartspaceControllerTest : SysuiTestCase() {
+    @Mock private lateinit var smartspaceManager: SmartspaceManager
+
+    @Mock private lateinit var execution: Execution
+
+    @Mock private lateinit var uiExecutor: Executor
+
+    @Mock private lateinit var targetFilter: SmartspaceTargetFilter
+
+    @Mock private lateinit var plugin: BcSmartspaceDataPlugin
+
+    @Mock private lateinit var precondition: SmartspacePrecondition
+
+    @Mock private lateinit var listener: BcSmartspaceDataPlugin.SmartspaceTargetListener
+
+    @Mock private lateinit var session: SmartspaceSession
+
+    private lateinit var controller: CommunalSmartspaceController
+
+    // TODO(b/272811280): Remove usage of real view
+    private val fakeParent = FrameLayout(context)
+
+    /**
+     * A class which implements SmartspaceView and extends View. This is mocked to provide the right
+     * object inheritance and interface implementation used in CommunalSmartspaceController
+     */
+    private class TestView(context: Context?) : View(context), SmartspaceView {
+        override fun registerDataProvider(plugin: BcSmartspaceDataPlugin?) {}
+
+        override fun registerConfigProvider(plugin: BcSmartspaceConfigPlugin?) {}
+
+        override fun setPrimaryTextColor(color: Int) {}
+
+        override fun setUiSurface(uiSurface: String) {}
+
+        override fun setDozeAmount(amount: Float) {}
+
+        override fun setIntentStarter(intentStarter: BcSmartspaceDataPlugin.IntentStarter?) {}
+
+        override fun setFalsingManager(falsingManager: FalsingManager?) {}
+
+        override fun setDnd(image: Drawable?, description: String?) {}
+
+        override fun setNextAlarm(image: Drawable?, description: String?) {}
+
+        override fun setMediaTarget(target: SmartspaceTarget?) {}
+
+        override fun getSelectedPage(): Int {
+            return 0
+        }
+
+        override fun getCurrentCardTopPadding(): Int {
+            return 0
+        }
+    }
+
+    @Before
+    fun setup() {
+        MockitoAnnotations.initMocks(this)
+        `when`(smartspaceManager.createSmartspaceSession(any())).thenReturn(session)
+
+        controller =
+            CommunalSmartspaceController(
+                context,
+                smartspaceManager,
+                execution,
+                uiExecutor,
+                precondition,
+                Optional.of(targetFilter),
+                Optional.of(plugin)
+            )
+    }
+
+    /** Ensures smartspace session begins on a listener only flow. */
+    @Test
+    fun testConnectOnListen() {
+        `when`(precondition.conditionsMet()).thenReturn(true)
+        controller.addListener(listener)
+
+        verify(smartspaceManager).createSmartspaceSession(any())
+
+        var targetListener =
+            withArgCaptor<SmartspaceSession.OnTargetsAvailableListener> {
+                verify(session).addOnTargetsAvailableListener(any(), capture())
+            }
+
+        `when`(targetFilter.filterSmartspaceTarget(any())).thenReturn(true)
+
+        var target = Mockito.mock(SmartspaceTarget::class.java)
+        targetListener.onTargetsAvailable(listOf(target))
+
+        var targets =
+            withArgCaptor<List<SmartspaceTarget>> { verify(plugin).onTargetsAvailable(capture()) }
+
+        assertThat(targets.contains(target)).isTrue()
+
+        controller.removeListener(listener)
+
+        verify(session).close()
+    }
+
+    /**
+     * Ensures session is closed and weather plugin unregisters the notifier when weather smartspace
+     * view is detached.
+     */
+    @Test
+    fun testDisconnect_emitsEmptyListAndRemovesNotifier() {
+        `when`(precondition.conditionsMet()).thenReturn(true)
+        controller.addListener(listener)
+
+        verify(smartspaceManager).createSmartspaceSession(any())
+
+        controller.removeListener(listener)
+
+        verify(session).close()
+
+        // And the listener receives an empty list of targets and unregisters the notifier
+        verify(plugin).onTargetsAvailable(emptyList())
+        verify(plugin).registerSmartspaceEventNotifier(null)
+    }
+}
diff --git a/packages/SystemUI/plugin/bcsmartspace/src/com/android/systemui/plugins/BcSmartspaceDataPlugin.java b/packages/SystemUI/plugin/bcsmartspace/src/com/android/systemui/plugins/BcSmartspaceDataPlugin.java
index 64c0f99..c99cb39 100644
--- a/packages/SystemUI/plugin/bcsmartspace/src/com/android/systemui/plugins/BcSmartspaceDataPlugin.java
+++ b/packages/SystemUI/plugin/bcsmartspace/src/com/android/systemui/plugins/BcSmartspaceDataPlugin.java
@@ -44,6 +44,7 @@
     String UI_SURFACE_HOME_SCREEN = "home";
     String UI_SURFACE_MEDIA = "media_data_manager";
     String UI_SURFACE_DREAM = "dream";
+    String UI_SURFACE_GLANCEABLE_HUB = "glanceable_hub";
 
     String ACTION = "com.android.systemui.action.PLUGIN_BC_SMARTSPACE_DATA";
     int VERSION = 1;
diff --git a/packages/SystemUI/res-keyguard/drawable/bouncer_password_text_view_focused_background.xml b/packages/SystemUI/res-keyguard/drawable/bouncer_password_text_view_focused_background.xml
deleted file mode 100644
index 02e10cd..0000000
--- a/packages/SystemUI/res-keyguard/drawable/bouncer_password_text_view_focused_background.xml
+++ /dev/null
@@ -1,24 +0,0 @@
-<?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.
--->
-<selector xmlns:android="http://schemas.android.com/apk/res/android">
-    <item android:state_focused="true">
-        <shape android:shape="rectangle">
-            <corners android:radius="16dp" />
-            <stroke android:width="3dp"
-                android:color="@color/bouncer_password_focus_color" />
-        </shape>
-    </item>
-</selector>
\ No newline at end of file
diff --git a/packages/SystemUI/res-keyguard/layout/keyguard_bouncer_message_area.xml b/packages/SystemUI/res-keyguard/layout/keyguard_bouncer_message_area.xml
index 0b35559..66c54f2 100644
--- a/packages/SystemUI/res-keyguard/layout/keyguard_bouncer_message_area.xml
+++ b/packages/SystemUI/res-keyguard/layout/keyguard_bouncer_message_area.xml
@@ -23,7 +23,7 @@
         android:layout_marginTop="@dimen/keyguard_lock_padding"
         android:importantForAccessibility="no"
         android:ellipsize="marquee"
-        android:focusable="false"
+        android:focusable="true"
         android:gravity="center"
         android:singleLine="true" />
 </merge>
diff --git a/packages/SystemUI/res-keyguard/values/styles.xml b/packages/SystemUI/res-keyguard/values/styles.xml
index 6e6709f..88f7bcd 100644
--- a/packages/SystemUI/res-keyguard/values/styles.xml
+++ b/packages/SystemUI/res-keyguard/values/styles.xml
@@ -76,7 +76,6 @@
     </style>
     <style name="Widget.TextView.Password" parent="@android:style/Widget.TextView">
         <item name="android:fontFamily">@*android:string/config_headlineFontFamily</item>
-        <item name="android:background">@drawable/bouncer_password_text_view_focused_background</item>
         <item name="android:gravity">center</item>
         <item name="android:textColor">?android:attr/textColorPrimary</item>
     </style>
diff --git a/packages/SystemUI/res/drawable/arrow_pointing_down.xml b/packages/SystemUI/res/drawable/arrow_pointing_down.xml
new file mode 100644
index 0000000..be39683
--- /dev/null
+++ b/packages/SystemUI/res/drawable/arrow_pointing_down.xml
@@ -0,0 +1,26 @@
+<!--
+  ~ Copyright (C) 2023 The Android Open Source Project
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~      http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="24dp"
+    android:height="24dp"
+    android:viewportWidth="24.0"
+    android:viewportHeight="24.0"
+    android:tint="?attr/colorControlNormal">
+    <path
+        android:fillColor="@android:color/white"
+        android:pathData="M5.41,7.59L4,9l8,8 8,-8 -1.41,-1.41L12,14.17" />
+</vector>
\ No newline at end of file
diff --git a/packages/SystemUI/res/layout/record_issue_dialog.xml b/packages/SystemUI/res/layout/record_issue_dialog.xml
new file mode 100644
index 0000000..53ad9f1
--- /dev/null
+++ b/packages/SystemUI/res/layout/record_issue_dialog.xml
@@ -0,0 +1,81 @@
+<?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.
+  -->
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    android:layout_width="match_parent"
+    android:layout_height="wrap_content"
+    android:layout_gravity="center"
+    android:orientation="vertical" >
+
+    <TextView
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:layout_gravity="center"
+        android:textAppearance="@style/TextAppearance.Dialog.Body.Message"
+        android:text="@string/qs_record_issue_dropdown_header" />
+
+    <Button
+        android:id="@+id/issue_type_button"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:text="@string/qs_record_issue_dropdown_prompt"
+        android:lines="1"
+        android:drawableRight="@drawable/arrow_pointing_down"
+        android:layout_marginTop="@dimen/qqs_layout_margin_top"
+        android:focusable="false"
+        android:clickable="true" />
+
+    <!-- Screen Record Switch -->
+    <LinearLayout
+        android:id="@+id/screenrecord_switch_container"
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:layout_marginTop="@dimen/qqs_layout_margin_top"
+        android:orientation="horizontal">
+
+        <ImageView
+            android:layout_width="@dimen/screenrecord_option_icon_size"
+            android:layout_height="@dimen/screenrecord_option_icon_size"
+            android:layout_weight="0"
+            android:src="@drawable/ic_screenrecord"
+            app:tint="?androidprv:attr/materialColorOnSurface"
+            android:layout_gravity="center"
+            android:layout_marginEnd="@dimen/screenrecord_option_padding" />
+
+        <TextView
+            android:layout_width="0dp"
+            android:layout_height="wrap_content"
+            android:minHeight="@dimen/screenrecord_option_icon_size"
+            android:layout_weight="1"
+            android:layout_gravity="fill_vertical"
+            android:gravity="center"
+            android:text="@string/quick_settings_screen_record_label"
+            android:textAppearance="@style/TextAppearance.Dialog.Body.Message"
+            android:importantForAccessibility="no"/>
+
+        <Switch
+            android:id="@+id/screenrecord_switch"
+            android:layout_width="wrap_content"
+            android:minHeight="@dimen/screenrecord_option_icon_size"
+            android:layout_height="wrap_content"
+            android:gravity="center"
+            android:layout_gravity="fill_vertical"
+            android:layout_weight="0"
+            android:contentDescription="@string/quick_settings_screen_record_label" />
+    </LinearLayout>
+</LinearLayout>
\ No newline at end of file
diff --git a/packages/SystemUI/res/layout/sidefps_view.xml b/packages/SystemUI/res/layout/sidefps_view.xml
index 4d95220..fc4bf8a 100644
--- a/packages/SystemUI/res/layout/sidefps_view.xml
+++ b/packages/SystemUI/res/layout/sidefps_view.xml
@@ -14,7 +14,7 @@
   ~ See the License for the specific language governing permissions and
   ~ limitations under the License.
   -->
-<com.android.systemui.biometrics.SideFpsLottieViewWrapper
+<com.android.systemui.biometrics.SideFpsIndicatorView
     xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:app="http://schemas.android.com/apk/res-auto"
     android:id="@+id/sidefps_animation"
diff --git a/packages/SystemUI/res/values-night/colors.xml b/packages/SystemUI/res/values-night/colors.xml
index a22fd18..bcc3c83 100644
--- a/packages/SystemUI/res/values-night/colors.xml
+++ b/packages/SystemUI/res/values-night/colors.xml
@@ -93,9 +93,6 @@
     <color name="qs_user_switcher_selected_avatar_icon_color">#202124</color>
     <!-- Color of background circle of user avatars in quick settings user switcher -->
     <color name="qs_user_switcher_avatar_background">#3C4043</color>
-    <!-- Color of border for keyguard password input when focused -->
-    <color name="bouncer_password_focus_color">@*android:color/system_secondary_dark</color>
-
 
     <!-- Accessibility floating menu -->
     <color name="accessibility_floating_menu_background">#B3000000</color> <!-- 70% -->
diff --git a/packages/SystemUI/res/values/colors.xml b/packages/SystemUI/res/values/colors.xml
index 462fc95..5f6a39a 100644
--- a/packages/SystemUI/res/values/colors.xml
+++ b/packages/SystemUI/res/values/colors.xml
@@ -56,8 +56,6 @@
     <color name="kg_user_switcher_restricted_avatar_icon_color">@color/GM2_grey_600</color>
     <!-- Color of background circle of user avatars in keyguard user switcher -->
     <color name="user_avatar_color_bg">?android:attr/colorBackgroundFloating</color>
-    <!-- Color of border for keyguard password input when focused -->
-    <color name="bouncer_password_focus_color">@*android:color/system_secondary_light</color>
 
     <!-- Icon color for user avatars in user switcher quick settings -->
     <color name="qs_user_switcher_avatar_icon_color">#3C4043</color>
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index 78b701c..e10925d 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -831,6 +831,20 @@
     <!-- QuickSettings: Text to prompt the user to stop an ongoing recording [CHAR LIMIT=20] -->
     <string name="qs_record_issue_stop">Stop</string>
 
+    <!-- QuickSettings: Issue Type Drop down options in Record Issue Start Dialog [CHAR LIMIT=50] -->
+    <string name="qs_record_issue_dropdown_header">What part of your device experience was affected?</string>
+    <!-- QuickSettings: Issue Type Drop down prompt in Record Issue Start Dialog [CHAR LIMIT=30] -->
+    <string name="qs_record_issue_dropdown_prompt">Select issue type</string>
+    <!-- QuickSettings: Screen record switch label in Record Issue Start Dialog [CHAR LIMIT=20] -->
+    <string name="qs_record_issue_dropdown_screenrecord">Screen record</string>
+
+    <!-- QuickSettings: Issue Type Drop down choices list in Record Issue Start Dialog [CHAR LIMIT=30] -->
+    <string-array name="qs_record_issue_types">
+        <item>Performance</item>
+        <item>User Interface</item>
+        <item>Battery</item>
+    </string-array>
+
     <!-- QuickSettings: Label for the toggle that controls whether One-handed mode is enabled. [CHAR LIMIT=NONE] -->
     <string name="quick_settings_onehanded_label">One-handed mode</string>
 
@@ -1066,9 +1080,11 @@
     <!-- Description for the button that opens the widget editor on click. [CHAR LIMIT=50] -->
     <string name="button_to_open_widget_editor">Open the widget editor</string>
     <!-- Description for the button that removes a widget on click. [CHAR LIMIT=50] -->
-    <string name="button_to_remove_widget">Remove a widget</string>
+    <string name="button_to_remove_widget">Remove</string>
     <!-- Text for the button that launches the hub mode widget picker. [CHAR LIMIT=50] -->
-    <string name="hub_mode_add_widget_button_text">Add Widget</string>
+    <string name="hub_mode_add_widget_button_text">Add widget</string>
+    <!-- Text for the button that exits the hub mode editing mode. [CHAR LIMIT=50] -->
+    <string name="hub_mode_editing_exit_button_text">Done</string>
 
     <!-- Related to user switcher --><skip/>
 
diff --git a/packages/SystemUI/shared/biometrics/src/com/android/systemui/biometrics/shared/model/BiometricModalities.kt b/packages/SystemUI/shared/biometrics/src/com/android/systemui/biometrics/shared/model/BiometricModalities.kt
index 80f70a0..3064829 100644
--- a/packages/SystemUI/shared/biometrics/src/com/android/systemui/biometrics/shared/model/BiometricModalities.kt
+++ b/packages/SystemUI/shared/biometrics/src/com/android/systemui/biometrics/shared/model/BiometricModalities.kt
@@ -37,6 +37,10 @@
     val hasSfps: Boolean
         get() = hasFingerprint && fingerprintProperties!!.isAnySidefpsType
 
+    /** If UDFPS authentication is available. */
+    val hasUdfps: Boolean
+        get() = hasFingerprint && fingerprintProperties!!.isAnyUdfpsType
+
     /** If fingerprint authentication is available (and [faceProperties] is non-null). */
     val hasFace: Boolean
         get() = faceProperties != null
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/InteractionJankMonitorWrapper.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/InteractionJankMonitorWrapper.java
index df7182b..0169f59 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/InteractionJankMonitorWrapper.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/InteractionJankMonitorWrapper.java
@@ -49,6 +49,7 @@
     public static final int CUJ_APP_SWIPE_TO_RECENTS = Cuj.CUJ_LAUNCHER_APP_SWIPE_TO_RECENTS;
     public static final int CUJ_OPEN_SEARCH_RESULT = Cuj.CUJ_LAUNCHER_OPEN_SEARCH_RESULT;
     public static final int CUJ_LAUNCHER_UNFOLD_ANIM = Cuj.CUJ_LAUNCHER_UNFOLD_ANIM;
+    public static final int CUJ_SEARCH_QSB_OPEN = Cuj.CUJ_LAUNCHER_SEARCH_QSB_OPEN;
 
     @IntDef({
             CUJ_APP_LAUNCH_FROM_RECENTS,
@@ -66,6 +67,7 @@
             CUJ_CLOSE_ALL_APPS_TO_HOME,
             CUJ_OPEN_SEARCH_RESULT,
             CUJ_LAUNCHER_UNFOLD_ANIM,
+            CUJ_SEARCH_QSB_OPEN,
     })
     @Retention(RetentionPolicy.SOURCE)
     public @interface CujType {
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputView.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputView.java
index 9764de1..36fe75f 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputView.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPinBasedInputView.java
@@ -168,6 +168,7 @@
 
         // Set selected property on so the view can send accessibility events.
         mPasswordEntry.setSelected(true);
+        mPasswordEntry.setDefaultFocusHighlightEnabled(false);
 
         mOkButton = findViewById(R.id.key_enter);
 
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java
index 0a4378e..cce2018 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardSecurityContainerController.java
@@ -38,7 +38,7 @@
 import android.content.Intent;
 import android.content.res.ColorStateList;
 import android.content.res.Resources;
-import android.hardware.biometrics.BiometricOverlayConstants;
+import android.hardware.biometrics.BiometricRequestConstants;
 import android.media.AudioManager;
 import android.metrics.LogMaker;
 import android.os.SystemClock;
@@ -74,6 +74,7 @@
 import com.android.systemui.biometrics.FaceAuthAccessibilityDelegate;
 import com.android.systemui.biometrics.SideFpsController;
 import com.android.systemui.biometrics.SideFpsUiRequestSource;
+import com.android.systemui.biometrics.shared.SideFpsControllerRefactor;
 import com.android.systemui.bouncer.domain.interactor.BouncerMessageInteractor;
 import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerInteractor;
 import com.android.systemui.classifier.FalsingA11yDelegate;
@@ -486,7 +487,11 @@
         mSceneContainerFlags = sceneContainerFlags;
         mGlobalSettings = globalSettings;
         mSessionTracker = sessionTracker;
-        mSideFpsController = sideFpsController;
+        if (SideFpsControllerRefactor.isEnabled()) {
+            mSideFpsController = Optional.empty();
+        } else {
+            mSideFpsController = sideFpsController;
+        }
         mFalsingA11yDelegate = falsingA11yDelegate;
         mTelephonyManager = telephonyManager;
         mViewMediatorCallback = viewMediatorCallback;
@@ -569,12 +574,14 @@
         mView.clearFocus();
     }
 
+    // TODO(b/288175061): remove with Flags.FLAG_SIDEFPS_CONTROLLER_REFACTOR
     /**
      * Shows and hides the side finger print sensor animation.
      *
      * @param isVisible sets whether we show or hide the side fps animation
      */
     public void updateSideFpsVisibility(boolean isVisible) {
+        SideFpsControllerRefactor.assertInLegacyMode();
         if (!mSideFpsController.isPresent()) {
             return;
         }
@@ -582,7 +589,7 @@
         if (isVisible) {
             mSideFpsController.get().show(
                     SideFpsUiRequestSource.PRIMARY_BOUNCER,
-                    BiometricOverlayConstants.REASON_AUTH_KEYGUARD
+                    BiometricRequestConstants.REASON_AUTH_KEYGUARD
             );
         } else {
             mSideFpsController.get().hide(SideFpsUiRequestSource.PRIMARY_BOUNCER);
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/AccessibilityModule.kt b/packages/SystemUI/src/com/android/systemui/accessibility/AccessibilityModule.kt
index ca859de..24aa11e 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/AccessibilityModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/AccessibilityModule.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.
@@ -16,61 +16,16 @@
 
 package com.android.systemui.accessibility
 
-import com.android.systemui.qs.tileimpl.QSTileImpl
-import com.android.systemui.qs.tiles.ColorCorrectionTile
-import com.android.systemui.qs.tiles.ColorInversionTile
-import com.android.systemui.qs.tiles.DreamTile
-import com.android.systemui.qs.tiles.FontScalingTile
-import com.android.systemui.qs.tiles.NightDisplayTile
-import com.android.systemui.qs.tiles.OneHandedModeTile
-import com.android.systemui.qs.tiles.ReduceBrightColorsTile
+import com.android.systemui.accessibility.data.repository.ColorCorrectionRepository
+import com.android.systemui.accessibility.data.repository.ColorCorrectionRepositoryImpl
+import com.android.systemui.accessibility.qs.QSAccessibilityModule
 import dagger.Binds
 import dagger.Module
-import dagger.multibindings.IntoMap
-import dagger.multibindings.StringKey
 
-@Module
+@Module(includes = [QSAccessibilityModule::class])
 interface AccessibilityModule {
-
-    /** Inject ColorInversionTile into tileMap in QSModule */
     @Binds
-    @IntoMap
-    @StringKey(ColorInversionTile.TILE_SPEC)
-    fun bindColorInversionTile(colorInversionTile: ColorInversionTile): QSTileImpl<*>
-
-    /** Inject NightDisplayTile into tileMap in QSModule */
-    @Binds
-    @IntoMap
-    @StringKey(NightDisplayTile.TILE_SPEC)
-    fun bindNightDisplayTile(nightDisplayTile: NightDisplayTile): QSTileImpl<*>
-
-    /** Inject ReduceBrightColorsTile into tileMap in QSModule */
-    @Binds
-    @IntoMap
-    @StringKey(ReduceBrightColorsTile.TILE_SPEC)
-    fun bindReduceBrightColorsTile(reduceBrightColorsTile: ReduceBrightColorsTile): QSTileImpl<*>
-
-    /** Inject OneHandedModeTile into tileMap in QSModule */
-    @Binds
-    @IntoMap
-    @StringKey(OneHandedModeTile.TILE_SPEC)
-    fun bindOneHandedModeTile(oneHandedModeTile: OneHandedModeTile): QSTileImpl<*>
-
-    /** Inject ColorCorrectionTile into tileMap in QSModule */
-    @Binds
-    @IntoMap
-    @StringKey(ColorCorrectionTile.TILE_SPEC)
-    fun bindColorCorrectionTile(colorCorrectionTile: ColorCorrectionTile): QSTileImpl<*>
-
-    /** Inject DreamTile into tileMap in QSModule */
-    @Binds
-    @IntoMap
-    @StringKey(DreamTile.TILE_SPEC)
-    fun bindDreamTile(dreamTile: DreamTile): QSTileImpl<*>
-
-    /** Inject FontScalingTile into tileMap in QSModule */
-    @Binds
-    @IntoMap
-    @StringKey(FontScalingTile.TILE_SPEC)
-    fun bindFontScalingTile(fontScalingTile: FontScalingTile): QSTileImpl<*>
+    abstract fun colorCorrectionRepository(
+        impl: ColorCorrectionRepositoryImpl
+    ): ColorCorrectionRepository
 }
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/data/repository/ColorCorrectionRepository.kt b/packages/SystemUI/src/com/android/systemui/accessibility/data/repository/ColorCorrectionRepository.kt
new file mode 100644
index 0000000..6483ae4
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/data/repository/ColorCorrectionRepository.kt
@@ -0,0 +1,73 @@
+/*
+ * 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.accessibility.data.repository
+
+import android.os.UserHandle
+import android.provider.Settings.Secure
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.util.settings.SecureSettings
+import com.android.systemui.util.settings.SettingsProxyExt.observerFlow
+import javax.inject.Inject
+import kotlin.coroutines.CoroutineContext
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.flowOn
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.onStart
+import kotlinx.coroutines.withContext
+
+/** Provides data related to color correction. */
+interface ColorCorrectionRepository {
+    /** Observable for whether color correction is enabled */
+    fun isEnabled(userHandle: UserHandle): Flow<Boolean>
+
+    /** Sets color correction enabled state. */
+    suspend fun setIsEnabled(isEnabled: Boolean, userHandle: UserHandle): Boolean
+}
+
+@SysUISingleton
+class ColorCorrectionRepositoryImpl
+@Inject
+constructor(
+    @Background private val bgCoroutineContext: CoroutineContext,
+    private val secureSettings: SecureSettings,
+) : ColorCorrectionRepository {
+
+    companion object {
+        const val SETTING_NAME = Secure.ACCESSIBILITY_DISPLAY_DALTONIZER_ENABLED
+        const val DISABLED = 0
+        const val ENABLED = 1
+    }
+
+    override fun isEnabled(userHandle: UserHandle): Flow<Boolean> =
+        secureSettings
+            .observerFlow(userHandle.identifier, SETTING_NAME)
+            .onStart { emit(Unit) }
+            .map { secureSettings.getIntForUser(SETTING_NAME, userHandle.identifier) == ENABLED }
+            .distinctUntilChanged()
+            .flowOn(bgCoroutineContext)
+
+    override suspend fun setIsEnabled(isEnabled: Boolean, userHandle: UserHandle): Boolean =
+        withContext(bgCoroutineContext) {
+            secureSettings.putIntForUser(
+                SETTING_NAME,
+                if (isEnabled) ENABLED else DISABLED,
+                userHandle.identifier
+            )
+        }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/qs/QSAccessibilityModule.kt b/packages/SystemUI/src/com/android/systemui/accessibility/qs/QSAccessibilityModule.kt
new file mode 100644
index 0000000..df7fdb8
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/qs/QSAccessibilityModule.kt
@@ -0,0 +1,124 @@
+/*
+ * 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.accessibility.qs
+
+import com.android.systemui.qs.QsEventLogger
+import com.android.systemui.qs.pipeline.shared.TileSpec
+import com.android.systemui.qs.tileimpl.QSTileImpl
+import com.android.systemui.qs.tiles.ColorCorrectionTile
+import com.android.systemui.qs.tiles.ColorInversionTile
+import com.android.systemui.qs.tiles.DreamTile
+import com.android.systemui.qs.tiles.FontScalingTile
+import com.android.systemui.qs.tiles.NightDisplayTile
+import com.android.systemui.qs.tiles.OneHandedModeTile
+import com.android.systemui.qs.tiles.ReduceBrightColorsTile
+import com.android.systemui.qs.tiles.base.viewmodel.QSTileViewModelFactory
+import com.android.systemui.qs.tiles.impl.colorcorrection.domain.ColorCorrectionTileMapper
+import com.android.systemui.qs.tiles.impl.colorcorrection.domain.interactor.ColorCorrectionTileDataInteractor
+import com.android.systemui.qs.tiles.impl.colorcorrection.domain.interactor.ColorCorrectionUserActionInteractor
+import com.android.systemui.qs.tiles.impl.colorcorrection.domain.model.ColorCorrectionTileModel
+import com.android.systemui.qs.tiles.viewmodel.QSTileConfig
+import com.android.systemui.qs.tiles.viewmodel.QSTileUIConfig
+import com.android.systemui.qs.tiles.viewmodel.QSTileViewModel
+import com.android.systemui.res.R
+import dagger.Binds
+import dagger.Module
+import dagger.Provides
+import dagger.multibindings.IntoMap
+import dagger.multibindings.StringKey
+
+@Module
+interface QSAccessibilityModule {
+
+    /** Inject ColorInversionTile into tileMap in QSModule */
+    @Binds
+    @IntoMap
+    @StringKey(ColorInversionTile.TILE_SPEC)
+    fun bindColorInversionTile(colorInversionTile: ColorInversionTile): QSTileImpl<*>
+
+    /** Inject NightDisplayTile into tileMap in QSModule */
+    @Binds
+    @IntoMap
+    @StringKey(NightDisplayTile.TILE_SPEC)
+    fun bindNightDisplayTile(nightDisplayTile: NightDisplayTile): QSTileImpl<*>
+
+    /** Inject ReduceBrightColorsTile into tileMap in QSModule */
+    @Binds
+    @IntoMap
+    @StringKey(ReduceBrightColorsTile.TILE_SPEC)
+    fun bindReduceBrightColorsTile(reduceBrightColorsTile: ReduceBrightColorsTile): QSTileImpl<*>
+
+    /** Inject OneHandedModeTile into tileMap in QSModule */
+    @Binds
+    @IntoMap
+    @StringKey(OneHandedModeTile.TILE_SPEC)
+    fun bindOneHandedModeTile(oneHandedModeTile: OneHandedModeTile): QSTileImpl<*>
+
+    /** Inject ColorCorrectionTile into tileMap in QSModule */
+    @Binds
+    @IntoMap
+    @StringKey(ColorCorrectionTile.TILE_SPEC)
+    fun bindColorCorrectionTile(colorCorrectionTile: ColorCorrectionTile): QSTileImpl<*>
+
+    /** Inject DreamTile into tileMap in QSModule */
+    @Binds
+    @IntoMap
+    @StringKey(DreamTile.TILE_SPEC)
+    fun bindDreamTile(dreamTile: DreamTile): QSTileImpl<*>
+
+    /** Inject FontScalingTile into tileMap in QSModule */
+    @Binds
+    @IntoMap
+    @StringKey(FontScalingTile.TILE_SPEC)
+    fun bindFontScalingTile(fontScalingTile: FontScalingTile): QSTileImpl<*>
+
+    companion object {
+
+        const val COLOR_CORRECTION_TILE_SPEC = "color_correction"
+
+        @Provides
+        @IntoMap
+        @StringKey(COLOR_CORRECTION_TILE_SPEC)
+        fun provideColorCorrectionTileConfig(uiEventLogger: QsEventLogger): QSTileConfig =
+            QSTileConfig(
+                tileSpec = TileSpec.create(COLOR_CORRECTION_TILE_SPEC),
+                uiConfig =
+                    QSTileUIConfig.Resource(
+                        iconRes = R.drawable.ic_qs_color_correction,
+                        labelRes = R.string.quick_settings_color_correction_label,
+                    ),
+                instanceId = uiEventLogger.getNewInstanceId(),
+            )
+
+        /** Inject ColorCorrectionTile into tileViewModelMap in QSModule */
+        @Provides
+        @IntoMap
+        @StringKey(COLOR_CORRECTION_TILE_SPEC)
+        fun provideColorCorrectionTileViewModel(
+            factory: QSTileViewModelFactory.Static<ColorCorrectionTileModel>,
+            mapper: ColorCorrectionTileMapper,
+            stateInteractor: ColorCorrectionTileDataInteractor,
+            userActionInteractor: ColorCorrectionUserActionInteractor
+        ): QSTileViewModel =
+            factory.create(
+                TileSpec.create(COLOR_CORRECTION_TILE_SPEC),
+                userActionInteractor,
+                stateInteractor,
+                mapper,
+            )
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java b/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java
index 877afce..5fba761 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/AuthController.java
@@ -70,6 +70,7 @@
 import com.android.systemui.biometrics.domain.interactor.LogContextInteractor;
 import com.android.systemui.biometrics.domain.interactor.PromptCredentialInteractor;
 import com.android.systemui.biometrics.domain.interactor.PromptSelectorInteractor;
+import com.android.systemui.biometrics.shared.SideFpsControllerRefactor;
 import com.android.systemui.biometrics.shared.model.UdfpsOverlayParams;
 import com.android.systemui.biometrics.ui.viewmodel.CredentialViewModel;
 import com.android.systemui.biometrics.ui.viewmodel.PromptViewModel;
@@ -88,6 +89,8 @@
 
 import dagger.Lazy;
 
+import kotlin.Unit;
+
 import java.io.PrintWriter;
 import java.util.ArrayList;
 import java.util.Arrays;
@@ -101,7 +104,6 @@
 import javax.inject.Inject;
 import javax.inject.Provider;
 
-import kotlin.Unit;
 import kotlinx.coroutines.CoroutineScope;
 
 /**
@@ -317,7 +319,9 @@
 
         mSidefpsProps = !sidefpsProps.isEmpty() ? sidefpsProps : null;
         if (mSidefpsProps != null) {
-            mSideFpsController = mSidefpsControllerFactory.get();
+            if (!SideFpsControllerRefactor.isEnabled()) {
+                mSideFpsController = mSidefpsControllerFactory.get();
+            }
         }
 
         mFingerprintManager.registerBiometricStateListener(new BiometricStateListener() {
@@ -1194,7 +1198,7 @@
      * Whether the passed userId has enrolled SFPS.
      */
     public boolean isSfpsEnrolled(int userId) {
-        if (mSideFpsController == null) {
+        if (mSidefpsProps == null) {
             return false;
         }
 
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/SideFpsController.kt b/packages/SystemUI/src/com/android/systemui/biometrics/SideFpsController.kt
index 91cee9e..ac99fc6 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/SideFpsController.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/SideFpsController.kt
@@ -23,9 +23,9 @@
 import android.graphics.PorterDuff
 import android.graphics.PorterDuffColorFilter
 import android.graphics.Rect
-import android.hardware.biometrics.BiometricOverlayConstants
-import android.hardware.biometrics.BiometricOverlayConstants.REASON_AUTH_KEYGUARD
-import android.hardware.biometrics.BiometricOverlayConstants.REASON_AUTH_SETTINGS
+import android.hardware.biometrics.BiometricRequestConstants
+import android.hardware.biometrics.BiometricRequestConstants.REASON_AUTH_KEYGUARD
+import android.hardware.biometrics.BiometricRequestConstants.REASON_AUTH_SETTINGS
 import android.hardware.biometrics.SensorLocationInternal
 import android.hardware.display.DisplayManager
 import android.hardware.fingerprint.FingerprintManager
@@ -58,6 +58,7 @@
 import com.android.keyguard.KeyguardPINView
 import com.android.systemui.Dumpable
 import com.android.systemui.biometrics.domain.interactor.DisplayStateInteractor
+import com.android.systemui.biometrics.shared.SideFpsControllerRefactor
 import com.android.systemui.bouncer.domain.interactor.AlternateBouncerInteractor
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Application
@@ -91,7 +92,7 @@
     @Main private val mainExecutor: DelayableExecutor,
     @Main private val handler: Handler,
     private val alternateBouncerInteractor: AlternateBouncerInteractor,
-    @Application private val scope: CoroutineScope,
+    @Application private val applicationScope: CoroutineScope,
     dumpManager: DumpManager,
     fpsUnlockTracker: FpsUnlockTracker
 ) : Dumpable {
@@ -110,7 +111,7 @@
             handler,
             sensorProps,
             { reason -> onOrientationChanged(reason) },
-            BiometricOverlayConstants.REASON_UNKNOWN
+            BiometricRequestConstants.REASON_UNKNOWN
         )
 
     @VisibleForTesting val orientationListener = orientationReasonListener.orientationListener
@@ -169,25 +170,27 @@
             }
 
     init {
-        fpsUnlockTracker.startTracking()
-        fingerprintManager?.setSidefpsController(
-            object : ISidefpsController.Stub() {
-                override fun show(
-                    sensorId: Int,
-                    @BiometricOverlayConstants.ShowReason reason: Int
-                ) =
-                    if (reason.isReasonToAutoShow(activityTaskManager)) {
-                        show(SideFpsUiRequestSource.AUTO_SHOW, reason)
-                    } else {
-                        hide(SideFpsUiRequestSource.AUTO_SHOW)
-                    }
+        if (!SideFpsControllerRefactor.isEnabled) {
+            fpsUnlockTracker.startTracking()
+            fingerprintManager?.setSidefpsController(
+                object : ISidefpsController.Stub() {
+                    override fun show(
+                        sensorId: Int,
+                        @BiometricRequestConstants.RequestReason reason: Int
+                    ) =
+                        if (reason.isReasonToAutoShow(activityTaskManager)) {
+                            show(SideFpsUiRequestSource.AUTO_SHOW, reason)
+                        } else {
+                            hide(SideFpsUiRequestSource.AUTO_SHOW)
+                        }
 
-                override fun hide(sensorId: Int) = hide(SideFpsUiRequestSource.AUTO_SHOW)
-            }
-        )
-        listenForAlternateBouncerVisibility()
+                    override fun hide(sensorId: Int) = hide(SideFpsUiRequestSource.AUTO_SHOW)
+                }
+            )
+            listenForAlternateBouncerVisibility()
 
-        dumpManager.registerDumpable(this)
+            dumpManager.registerDumpable(this)
+        }
     }
 
     private fun listenForAlternateBouncerVisibility() {
@@ -195,7 +198,7 @@
             alternateBouncerInteractor.setAlternateBouncerUIAvailable(true, "SideFpsController")
         }
 
-        scope.launch {
+        applicationScope.launch {
             alternateBouncerInteractor.isVisible.collect { isVisible: Boolean ->
                 if (isVisible) {
                     show(SideFpsUiRequestSource.ALTERNATE_BOUNCER, REASON_AUTH_KEYGUARD)
@@ -209,8 +212,10 @@
     /** Shows the side fps overlay if not already shown. */
     fun show(
         request: SideFpsUiRequestSource,
-        @BiometricOverlayConstants.ShowReason reason: Int = BiometricOverlayConstants.REASON_UNKNOWN
+        @BiometricRequestConstants.RequestReason
+        reason: Int = BiometricRequestConstants.REASON_UNKNOWN
     ) {
+        SideFpsControllerRefactor.assertInLegacyMode()
         if (!displayStateInteractor.isInRearDisplayMode.value) {
             requests.add(request)
             mainExecutor.execute {
@@ -229,6 +234,7 @@
 
     /** Hides the fps overlay if shown. */
     fun hide(request: SideFpsUiRequestSource) {
+        SideFpsControllerRefactor.assertInLegacyMode()
         requests.remove(request)
         mainExecutor.execute {
             if (requests.isEmpty()) {
@@ -239,6 +245,7 @@
 
     /** Hide the arrow indicator. */
     fun hideIndicator() {
+        SideFpsControllerRefactor.assertInLegacyMode()
         val lottieAnimationView =
             overlayView?.findViewById(R.id.sidefps_animation) as LottieAnimationView?
         lottieAnimationView?.visibility = INVISIBLE
@@ -246,6 +253,7 @@
 
     /** Show the arrow indicator. */
     fun showIndicator() {
+        SideFpsControllerRefactor.assertInLegacyMode()
         val lottieAnimationView =
             overlayView?.findViewById(R.id.sidefps_animation) as LottieAnimationView?
         lottieAnimationView?.visibility = VISIBLE
@@ -279,13 +287,13 @@
         pw.println("currentRotation=${displayInfo.rotation}")
     }
 
-    private fun onOrientationChanged(@BiometricOverlayConstants.ShowReason reason: Int) {
+    private fun onOrientationChanged(@BiometricRequestConstants.RequestReason reason: Int) {
         if (overlayView != null) {
             createOverlayForDisplay(reason)
         }
     }
 
-    private fun createOverlayForDisplay(@BiometricOverlayConstants.ShowReason reason: Int) {
+    private fun createOverlayForDisplay(@BiometricRequestConstants.RequestReason reason: Int) {
         val view = layoutInflater.inflate(R.layout.sidefps_view, null, false)
         overlayView = view
         val display = context.display!!
@@ -395,7 +403,7 @@
 /** Returns [True] when the device has a side fingerprint sensor. */
 fun FingerprintManager?.hasSideFpsSensor(): Boolean = this?.sideFpsSensorProperties != null
 
-@BiometricOverlayConstants.ShowReason
+@BiometricRequestConstants.RequestReason
 private fun Int.isReasonToAutoShow(activityTaskManager: ActivityTaskManager): Boolean =
     when (this) {
         REASON_AUTH_KEYGUARD -> false
@@ -434,7 +442,7 @@
 
 private fun LottieAnimationView.addOverlayDynamicColor(
     context: Context,
-    @BiometricOverlayConstants.ShowReason reason: Int
+    @BiometricRequestConstants.RequestReason reason: Int
 ) {
     fun update() {
         val isKeyguard = reason == REASON_AUTH_KEYGUARD
@@ -501,7 +509,7 @@
     handler: Handler,
     sensorProps: FingerprintSensorPropertiesInternal,
     onOrientationChanged: (reason: Int) -> Unit,
-    @BiometricOverlayConstants.ShowReason var reason: Int
+    @BiometricRequestConstants.RequestReason var reason: Int
 ) {
     val orientationListener =
         BiometricDisplayListener(
@@ -516,7 +524,7 @@
 
 /**
  * The source of a request to show the side fps visual indicator. This is distinct from
- * [BiometricOverlayConstants] which corrresponds with the reason fingerprint authentication is
+ * [BiometricRequestConstants] which corresponds with the reason fingerprint authentication is
  * requested.
  */
 enum class SideFpsUiRequestSource {
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/SideFpsLottieViewWrapper.kt b/packages/SystemUI/src/com/android/systemui/biometrics/SideFpsIndicatorView.kt
similarity index 96%
rename from packages/SystemUI/src/com/android/systemui/biometrics/SideFpsLottieViewWrapper.kt
rename to packages/SystemUI/src/com/android/systemui/biometrics/SideFpsIndicatorView.kt
index e98f6db..d5e25ac 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/SideFpsLottieViewWrapper.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/SideFpsIndicatorView.kt
@@ -19,6 +19,6 @@
 import android.util.AttributeSet
 import com.android.systemui.util.wrapper.LottieViewWrapper
 
-class SideFpsLottieViewWrapper
+class SideFpsIndicatorView
 @JvmOverloads
 constructor(context: Context, attrs: AttributeSet? = null) : LottieViewWrapper(context, attrs)
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java
index bb6ef41..65668b5 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsController.java
@@ -19,10 +19,10 @@
 import static android.app.StatusBarManager.SESSION_BIOMETRIC_PROMPT;
 import static android.app.StatusBarManager.SESSION_KEYGUARD;
 import static android.hardware.biometrics.BiometricFingerprintConstants.FINGERPRINT_ACQUIRED_GOOD;
-import static android.hardware.biometrics.BiometricOverlayConstants.REASON_AUTH_BP;
-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 android.hardware.biometrics.BiometricRequestConstants.REASON_AUTH_BP;
+import static android.hardware.biometrics.BiometricRequestConstants.REASON_AUTH_KEYGUARD;
+import static android.hardware.biometrics.BiometricRequestConstants.REASON_ENROLL_ENROLLING;
+import static android.hardware.biometrics.BiometricRequestConstants.REASON_ENROLL_FIND_SENSOR;
 
 import static com.android.internal.util.LatencyTracker.ACTION_UDFPS_ILLUMINATE;
 import static com.android.internal.util.Preconditions.checkNotNull;
@@ -106,6 +106,8 @@
 
 import dagger.Lazy;
 
+import kotlin.Unit;
+
 import java.io.PrintWriter;
 import java.util.ArrayList;
 import java.util.HashSet;
@@ -115,8 +117,6 @@
 import javax.inject.Inject;
 import javax.inject.Provider;
 
-import kotlin.Unit;
-
 import kotlinx.coroutines.ExperimentalCoroutinesApi;
 
 /**
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsControllerOverlay.kt b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsControllerOverlay.kt
index 452cd6a..dae6d08 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsControllerOverlay.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsControllerOverlay.kt
@@ -21,13 +21,13 @@
 import android.content.Context
 import android.graphics.PixelFormat
 import android.graphics.Rect
-import android.hardware.biometrics.BiometricOverlayConstants.REASON_AUTH_BP
-import android.hardware.biometrics.BiometricOverlayConstants.REASON_AUTH_KEYGUARD
-import android.hardware.biometrics.BiometricOverlayConstants.REASON_AUTH_OTHER
-import android.hardware.biometrics.BiometricOverlayConstants.REASON_AUTH_SETTINGS
-import android.hardware.biometrics.BiometricOverlayConstants.REASON_ENROLL_ENROLLING
-import android.hardware.biometrics.BiometricOverlayConstants.REASON_ENROLL_FIND_SENSOR
-import android.hardware.biometrics.BiometricOverlayConstants.ShowReason
+import android.hardware.biometrics.BiometricRequestConstants.REASON_AUTH_BP
+import android.hardware.biometrics.BiometricRequestConstants.REASON_AUTH_KEYGUARD
+import android.hardware.biometrics.BiometricRequestConstants.REASON_AUTH_OTHER
+import android.hardware.biometrics.BiometricRequestConstants.REASON_AUTH_SETTINGS
+import android.hardware.biometrics.BiometricRequestConstants.REASON_ENROLL_ENROLLING
+import android.hardware.biometrics.BiometricRequestConstants.REASON_ENROLL_FIND_SENSOR
+import android.hardware.biometrics.BiometricRequestConstants.RequestReason
 import android.hardware.fingerprint.IUdfpsOverlayControllerCallback
 import android.os.Build
 import android.os.RemoteException
@@ -96,7 +96,7 @@
     private val unlockedScreenOffAnimationController: UnlockedScreenOffAnimationController,
     private var udfpsDisplayModeProvider: UdfpsDisplayModeProvider,
     val requestId: Long,
-    @ShowReason val requestReason: Int,
+    @RequestReason val requestReason: Int,
     private val controllerCallback: IUdfpsOverlayControllerCallback,
     private val onTouch: (View, MotionEvent, Boolean) -> Boolean,
     private val activityLaunchAnimator: ActivityLaunchAnimator,
@@ -461,7 +461,7 @@
     }
 }
 
-@ShowReason
+@RequestReason
 private fun Int.isImportantForAccessibility() =
     this == REASON_ENROLL_FIND_SENSOR ||
             this == REASON_ENROLL_ENROLLING ||
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsShell.kt b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsShell.kt
index e3dbcb5..88b9e1b 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsShell.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/UdfpsShell.kt
@@ -18,13 +18,13 @@
 
 import android.content.Context
 import android.graphics.Rect
-import android.hardware.biometrics.BiometricOverlayConstants.REASON_AUTH_BP
-import android.hardware.biometrics.BiometricOverlayConstants.REASON_AUTH_KEYGUARD
-import android.hardware.biometrics.BiometricOverlayConstants.REASON_AUTH_OTHER
-import android.hardware.biometrics.BiometricOverlayConstants.REASON_AUTH_SETTINGS
-import android.hardware.biometrics.BiometricOverlayConstants.REASON_ENROLL_ENROLLING
-import android.hardware.biometrics.BiometricOverlayConstants.REASON_ENROLL_FIND_SENSOR
-import android.hardware.biometrics.BiometricOverlayConstants.REASON_UNKNOWN
+import android.hardware.biometrics.BiometricRequestConstants.REASON_AUTH_BP
+import android.hardware.biometrics.BiometricRequestConstants.REASON_AUTH_KEYGUARD
+import android.hardware.biometrics.BiometricRequestConstants.REASON_AUTH_OTHER
+import android.hardware.biometrics.BiometricRequestConstants.REASON_AUTH_SETTINGS
+import android.hardware.biometrics.BiometricRequestConstants.REASON_ENROLL_ENROLLING
+import android.hardware.biometrics.BiometricRequestConstants.REASON_ENROLL_FIND_SENSOR
+import android.hardware.biometrics.BiometricRequestConstants.REASON_UNKNOWN
 import android.hardware.fingerprint.IUdfpsOverlayControllerCallback
 import android.util.Log
 import android.view.LayoutInflater
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/dagger/BiometricsModule.kt b/packages/SystemUI/src/com/android/systemui/biometrics/dagger/BiometricsModule.kt
index 72fcfe7..8ae6f87 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/dagger/BiometricsModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/dagger/BiometricsModule.kt
@@ -18,8 +18,11 @@
 
 import android.content.res.Resources
 import com.android.internal.R
+import com.android.systemui.CoreStartable
 import com.android.systemui.biometrics.EllipseOverlapDetectorParams
 import com.android.systemui.biometrics.UdfpsUtils
+import com.android.systemui.biometrics.data.repository.BiometricStatusRepository
+import com.android.systemui.biometrics.data.repository.BiometricStatusRepositoryImpl
 import com.android.systemui.biometrics.data.repository.DisplayStateRepository
 import com.android.systemui.biometrics.data.repository.DisplayStateRepositoryImpl
 import com.android.systemui.biometrics.data.repository.FacePropertyRepository
@@ -33,11 +36,14 @@
 import com.android.systemui.biometrics.udfps.BoundingBoxOverlapDetector
 import com.android.systemui.biometrics.udfps.EllipseOverlapDetector
 import com.android.systemui.biometrics.udfps.OverlapDetector
+import com.android.systemui.biometrics.ui.binder.SideFpsOverlayViewBinder
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.util.concurrency.ThreadFactory
 import dagger.Binds
 import dagger.Module
 import dagger.Provides
+import dagger.multibindings.ClassKey
+import dagger.multibindings.IntoMap
 import java.util.concurrent.Executor
 import javax.inject.Qualifier
 
@@ -46,6 +52,11 @@
 interface BiometricsModule {
 
     @Binds
+    @IntoMap
+    @ClassKey(SideFpsOverlayViewBinder::class)
+    fun bindsSideFpsOverlayViewBinder(viewBinder: SideFpsOverlayViewBinder): CoreStartable
+
+    @Binds
     @SysUISingleton
     fun faceSettings(impl: FaceSettingsRepositoryImpl): FaceSettingsRepository
 
@@ -57,6 +68,10 @@
 
     @Binds
     @SysUISingleton
+    fun biometricStatusRepository(impl: BiometricStatusRepositoryImpl): BiometricStatusRepository
+
+    @Binds
+    @SysUISingleton
     fun fingerprintRepository(
         impl: FingerprintPropertyRepositoryImpl
     ): FingerprintPropertyRepository
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/data/repository/BiometricStatusRepository.kt b/packages/SystemUI/src/com/android/systemui/biometrics/data/repository/BiometricStatusRepository.kt
new file mode 100644
index 0000000..ad2136a
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/data/repository/BiometricStatusRepository.kt
@@ -0,0 +1,112 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.biometrics.data.repository
+
+import android.hardware.biometrics.AuthenticationStateListener
+import android.hardware.biometrics.BiometricManager
+import android.hardware.biometrics.BiometricRequestConstants.REASON_AUTH_BP
+import android.hardware.biometrics.BiometricRequestConstants.REASON_AUTH_KEYGUARD
+import android.hardware.biometrics.BiometricRequestConstants.REASON_AUTH_OTHER
+import android.hardware.biometrics.BiometricRequestConstants.REASON_AUTH_SETTINGS
+import android.hardware.biometrics.BiometricRequestConstants.REASON_ENROLL_ENROLLING
+import android.hardware.biometrics.BiometricRequestConstants.REASON_ENROLL_FIND_SENSOR
+import com.android.systemui.biometrics.shared.model.AuthenticationReason
+import com.android.systemui.biometrics.shared.model.AuthenticationReason.SettingsOperations
+import com.android.systemui.common.coroutine.ChannelExt.trySendWithFailureLogging
+import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.channels.awaitClose
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.shareIn
+
+/** A repository for the state of biometric authentication. */
+interface BiometricStatusRepository {
+    /**
+     * The logical reason for the current fingerprint auth operation if one is on-going, otherwise
+     * [NotRunning].
+     */
+    val fingerprintAuthenticationReason: Flow<AuthenticationReason>
+}
+
+@SysUISingleton
+class BiometricStatusRepositoryImpl
+@Inject
+constructor(
+    @Application private val applicationScope: CoroutineScope,
+    private val biometricManager: BiometricManager?
+) : BiometricStatusRepository {
+
+    override val fingerprintAuthenticationReason: Flow<AuthenticationReason> =
+        conflatedCallbackFlow {
+                val updateFingerprintAuthenticateReason = { reason: AuthenticationReason ->
+                    trySendWithFailureLogging(
+                        reason,
+                        TAG,
+                        "Error sending fingerprintAuthenticateReason reason"
+                    )
+                }
+
+                val authenticationStateListener =
+                    object : AuthenticationStateListener.Stub() {
+                        override fun onAuthenticationStarted(requestReason: Int) {
+                            val authenticationReason =
+                                when (requestReason) {
+                                    REASON_AUTH_BP ->
+                                        AuthenticationReason.BiometricPromptAuthentication
+                                    REASON_AUTH_KEYGUARD ->
+                                        AuthenticationReason.DeviceEntryAuthentication
+                                    REASON_AUTH_OTHER -> AuthenticationReason.OtherAuthentication
+                                    REASON_AUTH_SETTINGS ->
+                                        AuthenticationReason.SettingsAuthentication(
+                                            SettingsOperations.OTHER
+                                        )
+                                    REASON_ENROLL_ENROLLING ->
+                                        AuthenticationReason.SettingsAuthentication(
+                                            SettingsOperations.ENROLL_ENROLLING
+                                        )
+                                    REASON_ENROLL_FIND_SENSOR ->
+                                        AuthenticationReason.SettingsAuthentication(
+                                            SettingsOperations.ENROLL_FIND_SENSOR
+                                        )
+                                    else -> AuthenticationReason.Unknown
+                                }
+                            updateFingerprintAuthenticateReason(authenticationReason)
+                        }
+
+                        override fun onAuthenticationStopped() {
+                            updateFingerprintAuthenticateReason(AuthenticationReason.NotRunning)
+                        }
+                    }
+
+                updateFingerprintAuthenticateReason(AuthenticationReason.NotRunning)
+                biometricManager?.registerAuthenticationStateListener(authenticationStateListener)
+                awaitClose {
+                    biometricManager?.unregisterAuthenticationStateListener(
+                        authenticationStateListener
+                    )
+                }
+            }
+            .shareIn(applicationScope, started = SharingStarted.Eagerly, replay = 1)
+
+    companion object {
+        private const val TAG = "BiometricStatusRepositoryImpl"
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/domain/BiometricsDomainLayerModule.kt b/packages/SystemUI/src/com/android/systemui/biometrics/domain/BiometricsDomainLayerModule.kt
index b9b2fd8..ec3fd9f 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/domain/BiometricsDomainLayerModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/domain/BiometricsDomainLayerModule.kt
@@ -15,6 +15,8 @@
  */
 package com.android.systemui.biometrics.domain
 
+import com.android.systemui.biometrics.domain.interactor.BiometricStatusInteractor
+import com.android.systemui.biometrics.domain.interactor.BiometricStatusInteractorImpl
 import com.android.systemui.biometrics.domain.interactor.CredentialInteractor
 import com.android.systemui.biometrics.domain.interactor.CredentialInteractorImpl
 import com.android.systemui.biometrics.domain.interactor.DisplayStateInteractor
@@ -38,6 +40,12 @@
 
     @Binds
     @SysUISingleton
+    fun providesBiometricStatusInteractor(
+        impl: BiometricStatusInteractorImpl
+    ): BiometricStatusInteractor
+
+    @Binds
+    @SysUISingleton
     fun providesCredentialInteractor(impl: CredentialInteractorImpl): CredentialInteractor
 
     @Binds
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/BiometricStatusInteractor.kt b/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/BiometricStatusInteractor.kt
new file mode 100644
index 0000000..55a2d3d
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/BiometricStatusInteractor.kt
@@ -0,0 +1,74 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.biometrics.domain.interactor
+
+import android.app.ActivityTaskManager
+import com.android.systemui.biometrics.data.repository.BiometricStatusRepository
+import com.android.systemui.biometrics.shared.model.AuthenticationReason
+import com.android.systemui.biometrics.shared.model.AuthenticationReason.SettingsOperations
+import javax.inject.Inject
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.map
+
+/** Encapsulates business logic for interacting with biometric authentication state. */
+interface BiometricStatusInteractor {
+    /**
+     * The logical reason for the current side fingerprint sensor auth operation if one is on-going,
+     * filtered for when the overlay should be shown, otherwise [NotRunning].
+     */
+    val sfpsAuthenticationReason: Flow<AuthenticationReason>
+}
+
+class BiometricStatusInteractorImpl
+@Inject
+constructor(
+    private val activityTaskManager: ActivityTaskManager,
+    biometricStatusRepository: BiometricStatusRepository,
+) : BiometricStatusInteractor {
+
+    override val sfpsAuthenticationReason: Flow<AuthenticationReason> =
+        biometricStatusRepository.fingerprintAuthenticationReason.map { reason: AuthenticationReason
+            ->
+            if (reason.isReasonToAlwaysUpdateSfpsOverlay(activityTaskManager)) {
+                reason
+            } else {
+                AuthenticationReason.NotRunning
+            }
+        }
+
+    companion object {
+        private const val TAG = "BiometricStatusInteractor"
+    }
+}
+
+/** True if the sfps overlay should always be updated for this request source, false otherwise. */
+private fun AuthenticationReason.isReasonToAlwaysUpdateSfpsOverlay(
+    activityTaskManager: ActivityTaskManager
+): Boolean =
+    when (this) {
+        AuthenticationReason.DeviceEntryAuthentication -> false
+        AuthenticationReason.SettingsAuthentication(SettingsOperations.OTHER) ->
+            when (activityTaskManager.topClass()) {
+                // TODO(b/186176653): exclude fingerprint overlays from this list view
+                "com.android.settings.biometrics.fingerprint.FingerprintSettings" -> false
+                else -> true
+            }
+        else -> true
+    }
+
+internal fun ActivityTaskManager.topClass(): String =
+    getTasks(1).firstOrNull()?.topActivity?.className ?: ""
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/SideFpsSensorInteractor.kt b/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/SideFpsSensorInteractor.kt
index f513799..f4231ac 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/SideFpsSensorInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/domain/interactor/SideFpsSensorInteractor.kt
@@ -141,7 +141,6 @@
                             }
                         }
                     }
-
                 SideFpsSensorLocation(
                     left = sensorLeft,
                     top = sensorTop,
@@ -149,7 +148,15 @@
                     isSensorVerticalInDefaultOrientation = isSensorVerticalInDefaultOrientation
                 )
             }
-            .distinctUntilChanged()
+            .distinctUntilChanged(
+                areEquivalent = { old: SideFpsSensorLocation, new: SideFpsSensorLocation ->
+                    old.left == new.left &&
+                        old.top == new.top &&
+                        old.length == new.length &&
+                        old.isSensorVerticalInDefaultOrientation ==
+                            new.isSensorVerticalInDefaultOrientation
+                }
+            )
             .onEach {
                 logger.sensorLocationStateChanged(
                     it.left,
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/shared/SideFpsControllerRefactor.kt b/packages/SystemUI/src/com/android/systemui/biometrics/shared/SideFpsControllerRefactor.kt
new file mode 100644
index 0000000..899b07e
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/shared/SideFpsControllerRefactor.kt
@@ -0,0 +1,53 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.biometrics.shared
+
+import com.android.systemui.flags.FlagToken
+import com.android.systemui.flags.RefactorFlagUtils
+import com.android.systemui.shared.Flags
+
+/** Helper for reading or using the sidefps controller refactor flag state. */
+@Suppress("NOTHING_TO_INLINE")
+object SideFpsControllerRefactor {
+    /** The aconfig flag name */
+    const val FLAG_NAME = Flags.FLAG_SIDEFPS_CONTROLLER_REFACTOR
+
+    /** A token used for dependency declaration */
+    val token: FlagToken
+        get() = FlagToken(FLAG_NAME, isEnabled)
+
+    /** Is the refactor enabled */
+    @JvmStatic
+    inline val isEnabled
+        get() = Flags.sidefpsControllerRefactor()
+
+    /**
+     * Called to ensure code is only run when the flag is enabled. This protects users from the
+     * unintended behaviors caused by accidentally running new logic, while also crashing on an eng
+     * build to ensure that the refactor author catches issues in testing.
+     */
+    @JvmStatic
+    inline fun isUnexpectedlyInLegacyMode() =
+        RefactorFlagUtils.isUnexpectedlyInLegacyMode(isEnabled, FLAG_NAME)
+
+    /**
+     * Called to ensure code is only run when the flag is disabled. This will throw an exception if
+     * the flag is enabled to ensure that the refactor author catches issues in testing.
+     */
+    @JvmStatic
+    inline fun assertInLegacyMode() = RefactorFlagUtils.assertInLegacyMode(isEnabled, FLAG_NAME)
+}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/shared/model/AuthenticationReason.kt b/packages/SystemUI/src/com/android/systemui/biometrics/shared/model/AuthenticationReason.kt
new file mode 100644
index 0000000..0c3debbe
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/shared/model/AuthenticationReason.kt
@@ -0,0 +1,48 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.biometrics.shared.model
+
+/**
+ * The logical reason for a fingerprint auth operation if one is on-going, otherwise [NotRunning].
+ */
+sealed interface AuthenticationReason {
+    /** Device entry requested authentication */
+    data object DeviceEntryAuthentication : AuthenticationReason
+
+    /** Settings requested authentication */
+    data class SettingsAuthentication(val settingsOperation: SettingsOperations) :
+        AuthenticationReason
+
+    /** App requested authentication */
+    data object BiometricPromptAuthentication : AuthenticationReason
+
+    /** Authentication requested for other reason */
+    data object OtherAuthentication : AuthenticationReason
+
+    /** Authentication requested for unknown reason */
+    data object Unknown : AuthenticationReason
+
+    /** Authentication is not running */
+    data object NotRunning : AuthenticationReason
+
+    /** Settings operations that request biometric authentication */
+    enum class SettingsOperations {
+        ENROLL_ENROLLING,
+        ENROLL_FIND_SENSOR,
+        OTHER
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/SideFpsLottieViewWrapper.kt b/packages/SystemUI/src/com/android/systemui/biometrics/shared/model/LottieCallback.kt
similarity index 66%
copy from packages/SystemUI/src/com/android/systemui/biometrics/SideFpsLottieViewWrapper.kt
copy to packages/SystemUI/src/com/android/systemui/biometrics/shared/model/LottieCallback.kt
index e98f6db..0b30055 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/SideFpsLottieViewWrapper.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/shared/model/LottieCallback.kt
@@ -13,12 +13,10 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package com.android.systemui.biometrics
 
-import android.content.Context
-import android.util.AttributeSet
-import com.android.systemui.util.wrapper.LottieViewWrapper
+package com.android.systemui.biometrics.shared.model
 
-class SideFpsLottieViewWrapper
-@JvmOverloads
-constructor(context: Context, attrs: AttributeSet? = null) : LottieViewWrapper(context, attrs)
+import com.airbnb.lottie.model.KeyPath
+
+/** Represents properties of a LottieAnimationView callback */
+data class LottieCallback(val keypath: KeyPath, val color: Int)
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewBinder.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewBinder.kt
index 32d9067..90e4a38 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/BiometricViewBinder.kt
@@ -376,6 +376,22 @@
                     }
                 }
 
+                // Talkback directional guidance
+                backgroundView.setOnHoverListener { _, event ->
+                    launch {
+                        viewModel.onAnnounceAccessibilityHint(
+                            event,
+                            accessibilityManager.isTouchExplorationEnabled
+                        )
+                    }
+                    false
+                }
+                launch {
+                    viewModel.accessibilityHint.collect { message ->
+                        if (message.isNotBlank()) view.announceForAccessibility(message)
+                    }
+                }
+
                 // Play haptics
                 launch {
                     viewModel.hapticsToPlay.collect { hapticFeedbackConstant ->
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/SideFpsOverlayViewBinder.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/SideFpsOverlayViewBinder.kt
new file mode 100644
index 0000000..a8c9446
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/binder/SideFpsOverlayViewBinder.kt
@@ -0,0 +1,224 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package com.android.systemui.biometrics.ui.binder
+
+import android.content.Context
+import android.graphics.PorterDuff
+import android.graphics.PorterDuffColorFilter
+import android.view.LayoutInflater
+import android.view.View
+import android.view.WindowManager
+import android.view.accessibility.AccessibilityEvent
+import androidx.lifecycle.Lifecycle
+import androidx.lifecycle.repeatOnLifecycle
+import com.airbnb.lottie.LottieAnimationView
+import com.airbnb.lottie.LottieComposition
+import com.airbnb.lottie.LottieProperty
+import com.android.app.animation.Interpolators
+import com.android.keyguard.KeyguardPINView
+import com.android.systemui.CoreStartable
+import com.android.systemui.biometrics.FpsUnlockTracker
+import com.android.systemui.biometrics.domain.interactor.BiometricStatusInteractor
+import com.android.systemui.biometrics.domain.interactor.DisplayStateInteractor
+import com.android.systemui.biometrics.domain.interactor.SideFpsSensorInteractor
+import com.android.systemui.biometrics.shared.SideFpsControllerRefactor
+import com.android.systemui.biometrics.shared.model.AuthenticationReason.NotRunning
+import com.android.systemui.biometrics.shared.model.LottieCallback
+import com.android.systemui.biometrics.ui.viewmodel.SideFpsOverlayViewModel
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.keyguard.domain.interactor.DeviceEntrySideFpsOverlayInteractor
+import com.android.systemui.keyguard.ui.viewmodel.SideFpsProgressBarViewModel
+import com.android.systemui.lifecycle.repeatWhenAttached
+import com.android.systemui.res.R
+import com.android.systemui.util.kotlin.sample
+import javax.inject.Inject
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.launch
+
+/** Binds the side fingerprint sensor indicator view to [SideFpsOverlayViewModel]. */
+@SysUISingleton
+class SideFpsOverlayViewBinder
+@Inject
+constructor(
+    @Application private val applicationScope: CoroutineScope,
+    @Application private val applicationContext: Context,
+    private val biometricStatusInteractor: BiometricStatusInteractor,
+    private val displayStateInteractor: DisplayStateInteractor,
+    private val deviceEntrySideFpsOverlayInteractor: DeviceEntrySideFpsOverlayInteractor,
+    private val fpsUnlockTracker: FpsUnlockTracker,
+    private val layoutInflater: LayoutInflater,
+    private val sideFpsProgressBarViewModel: SideFpsProgressBarViewModel,
+    private val sfpsSensorInteractor: SideFpsSensorInteractor,
+    private val windowManager: WindowManager
+) : CoreStartable {
+
+    override fun start() {
+        if (!SideFpsControllerRefactor.isEnabled) {
+            return
+        }
+        applicationScope
+            .launch {
+                combine(
+                        biometricStatusInteractor.sfpsAuthenticationReason,
+                        deviceEntrySideFpsOverlayInteractor.showIndicatorForDeviceEntry,
+                        sideFpsProgressBarViewModel.isVisible,
+                        ::Triple
+                    )
+                    .sample(displayStateInteractor.isInRearDisplayMode, ::Pair)
+                    .collect { (combinedFlows, isInRearDisplayMode: Boolean) ->
+                        val (
+                            systemServerAuthReason,
+                            showIndicatorForDeviceEntry,
+                            progressBarIsVisible) =
+                            combinedFlows
+                        if (!isInRearDisplayMode) {
+                            if (progressBarIsVisible) {
+                                hide()
+                            } else if (systemServerAuthReason != NotRunning) {
+                                show()
+                            } else if (showIndicatorForDeviceEntry) {
+                                show()
+                            } else {
+                                hide()
+                            }
+                        }
+                    }
+            }
+            .invokeOnCompletion { fpsUnlockTracker.stopTracking() }
+    }
+
+    private var overlayView: View? = null
+    private var lottie: LottieAnimationView? = null
+
+    /** Show the side fingerprint sensor indicator */
+    private fun show() {
+        overlayView?.let {
+            if (it.isAttachedToWindow) {
+                lottie = it.requireViewById<LottieAnimationView>(R.id.sidefps_animation)
+                lottie?.pauseAnimation()
+                windowManager.removeView(it)
+            }
+        }
+
+        overlayView = layoutInflater.inflate(R.layout.sidefps_view, null, false)
+        val overlayViewModel =
+            SideFpsOverlayViewModel(
+                applicationContext,
+                biometricStatusInteractor,
+                deviceEntrySideFpsOverlayInteractor,
+                displayStateInteractor,
+                sfpsSensorInteractor,
+                sideFpsProgressBarViewModel
+            )
+        bind(overlayView!!, overlayViewModel, fpsUnlockTracker, windowManager)
+        overlayView!!.visibility = View.INVISIBLE
+        windowManager.addView(overlayView, overlayViewModel.defaultOverlayViewParams)
+    }
+
+    /** Hide the side fingerprint sensor indicator */
+    private fun hide() {
+        if (overlayView != null) {
+            windowManager.removeView(overlayView)
+            overlayView = null
+        }
+    }
+
+    companion object {
+        private const val TAG = "SideFpsOverlayViewBinder"
+
+        /** Binds overlayView (side fingerprint sensor indicator view) to SideFpsOverlayViewModel */
+        fun bind(
+            overlayView: View,
+            viewModel: SideFpsOverlayViewModel,
+            fpsUnlockTracker: FpsUnlockTracker,
+            windowManager: WindowManager
+        ) {
+            overlayView.repeatWhenAttached {
+                fpsUnlockTracker.startTracking()
+
+                val lottie = it.requireViewById<LottieAnimationView>(R.id.sidefps_animation)
+                lottie.addLottieOnCompositionLoadedListener { composition: LottieComposition ->
+                    viewModel.setLottieBounds(composition.bounds)
+                    overlayView.visibility = View.VISIBLE
+                }
+                it.alpha = 0f
+                val overlayShowAnimator =
+                    it.animate()
+                        .alpha(1f)
+                        .setDuration(KeyguardPINView.ANIMATION_DURATION)
+                        .setInterpolator(Interpolators.ALPHA_IN)
+
+                overlayShowAnimator.start()
+
+                it.setAccessibilityDelegate(
+                    object : View.AccessibilityDelegate() {
+                        override fun dispatchPopulateAccessibilityEvent(
+                            host: View,
+                            event: AccessibilityEvent
+                        ): Boolean {
+                            return if (
+                                event.getEventType() ===
+                                    android.view.accessibility.AccessibilityEvent
+                                        .TYPE_WINDOW_STATE_CHANGED
+                            ) {
+                                true
+                            } else {
+                                super.dispatchPopulateAccessibilityEvent(host, event)
+                            }
+                        }
+                    }
+                )
+
+                repeatOnLifecycle(Lifecycle.State.STARTED) {
+                    launch {
+                        viewModel.lottieCallbacks.collect { callbacks ->
+                            lottie.addOverlayDynamicColor(callbacks)
+                        }
+                    }
+
+                    launch {
+                        viewModel.overlayViewParams.collect { params ->
+                            windowManager.updateViewLayout(it, params)
+                            lottie.resumeAnimation()
+                        }
+                    }
+
+                    launch {
+                        viewModel.overlayViewProperties.collect { properties ->
+                            it.rotation = properties.overlayViewRotation
+                            lottie.setAnimation(properties.indicatorAsset)
+                        }
+                    }
+                }
+            }
+        }
+    }
+}
+
+private fun LottieAnimationView.addOverlayDynamicColor(colorCallbacks: List<LottieCallback>) {
+    addLottieOnCompositionLoadedListener {
+        for (callback in colorCallbacks) {
+            addValueCallback(callback.keypath, LottieProperty.COLOR_FILTER) {
+                PorterDuffColorFilter(callback.color, PorterDuff.Mode.SRC_ATOP)
+            }
+        }
+        resumeAnimation()
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModel.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModel.kt
index 647aaf3..6d0a58e 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/PromptViewModel.kt
@@ -21,9 +21,12 @@
 import android.util.Log
 import android.view.HapticFeedbackConstants
 import android.view.MotionEvent
+import com.android.systemui.Flags.bpTalkback
+import com.android.systemui.biometrics.UdfpsUtils
 import com.android.systemui.biometrics.Utils
 import com.android.systemui.biometrics.domain.interactor.DisplayStateInteractor
 import com.android.systemui.biometrics.domain.interactor.PromptSelectorInteractor
+import com.android.systemui.biometrics.domain.interactor.UdfpsOverlayInteractor
 import com.android.systemui.biometrics.shared.model.BiometricModalities
 import com.android.systemui.biometrics.shared.model.BiometricModality
 import com.android.systemui.biometrics.shared.model.DisplayRotation
@@ -35,7 +38,9 @@
 import kotlinx.coroutines.coroutineScope
 import kotlinx.coroutines.delay
 import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableSharedFlow
 import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.asSharedFlow
 import kotlinx.coroutines.flow.asStateFlow
 import kotlinx.coroutines.flow.combine
 import kotlinx.coroutines.flow.distinctUntilChanged
@@ -49,7 +54,9 @@
 constructor(
     displayStateInteractor: DisplayStateInteractor,
     promptSelectorInteractor: PromptSelectorInteractor,
-    @Application context: Context,
+    @Application private val context: Context,
+    private val udfpsOverlayInteractor: UdfpsOverlayInteractor,
+    private val udfpsUtils: UdfpsUtils
 ) {
     /** The set of modalities available for this prompt */
     val modalities: Flow<BiometricModalities> =
@@ -69,6 +76,11 @@
     val faceIconHeight: Int =
         context.resources.getDimensionPixelSize(R.dimen.biometric_dialog_face_icon_size)
 
+    private val _accessibilityHint = MutableSharedFlow<String>()
+
+    /** Hint for talkback directional guidance */
+    val accessibilityHint: Flow<String> = _accessibilityHint.asSharedFlow()
+
     private val _isAuthenticating: MutableStateFlow<Boolean> = MutableStateFlow(false)
 
     /** If the user is currently authenticating (i.e. at least one biometric is scanning). */
@@ -516,6 +528,40 @@
         return false
     }
 
+    /** Sets the message used for UDFPS directional guidance */
+    suspend fun onAnnounceAccessibilityHint(
+        event: MotionEvent,
+        touchExplorationEnabled: Boolean,
+    ): Boolean {
+        if (bpTalkback() && modalities.first().hasUdfps && touchExplorationEnabled) {
+            // TODO(b/315184924): Remove uses of UdfpsUtils
+            val scaledTouch =
+                udfpsUtils.getTouchInNativeCoordinates(
+                    event.getPointerId(0),
+                    event,
+                    udfpsOverlayInteractor.udfpsOverlayParams.value
+                )
+            if (
+                !udfpsUtils.isWithinSensorArea(
+                    event.getPointerId(0),
+                    event,
+                    udfpsOverlayInteractor.udfpsOverlayParams.value
+                )
+            ) {
+                _accessibilityHint.emit(
+                    udfpsUtils.onTouchOutsideOfSensorArea(
+                        touchExplorationEnabled,
+                        context,
+                        scaledTouch.x,
+                        scaledTouch.y,
+                        udfpsOverlayInteractor.udfpsOverlayParams.value
+                    )
+                )
+            }
+        }
+        return false
+    }
+
     /**
      * Switch to the credential view.
      *
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/SideFpsOverlayViewModel.kt b/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/SideFpsOverlayViewModel.kt
new file mode 100644
index 0000000..ce72603
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/biometrics/ui/viewmodel/SideFpsOverlayViewModel.kt
@@ -0,0 +1,229 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package com.android.systemui.biometrics.ui.viewmodel
+
+import android.content.Context
+import android.content.res.Configuration
+import android.graphics.Color
+import android.graphics.PixelFormat
+import android.graphics.Point
+import android.graphics.Rect
+import android.view.Gravity
+import android.view.WindowManager
+import android.view.WindowManager.LayoutParams.PRIVATE_FLAG_NO_MOVE_ANIMATION
+import android.view.WindowManager.LayoutParams.PRIVATE_FLAG_TRUSTED_OVERLAY
+import com.airbnb.lottie.model.KeyPath
+import com.android.systemui.biometrics.Utils
+import com.android.systemui.biometrics.domain.interactor.BiometricStatusInteractor
+import com.android.systemui.biometrics.domain.interactor.DisplayStateInteractor
+import com.android.systemui.biometrics.domain.interactor.SideFpsSensorInteractor
+import com.android.systemui.biometrics.domain.model.SideFpsSensorLocation
+import com.android.systemui.biometrics.shared.model.AuthenticationReason
+import com.android.systemui.biometrics.shared.model.DisplayRotation
+import com.android.systemui.biometrics.shared.model.LottieCallback
+import com.android.systemui.dagger.qualifiers.Application
+import com.android.systemui.keyguard.domain.interactor.DeviceEntrySideFpsOverlayInteractor
+import com.android.systemui.keyguard.ui.viewmodel.SideFpsProgressBarViewModel
+import com.android.systemui.res.R
+import javax.inject.Inject
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.distinctUntilChanged
+
+/** Models UI of the side fingerprint sensor indicator view. */
+class SideFpsOverlayViewModel
+@Inject
+constructor(
+    @Application private val applicationContext: Context,
+    biometricStatusInteractor: BiometricStatusInteractor,
+    deviceEntrySideFpsOverlayInteractor: DeviceEntrySideFpsOverlayInteractor,
+    displayStateInteractor: DisplayStateInteractor,
+    sfpsSensorInteractor: SideFpsSensorInteractor,
+    sideFpsProgressBarViewModel: SideFpsProgressBarViewModel
+) {
+    /** Contains properties of the side fingerprint sensor indicator */
+    data class OverlayViewProperties(
+        /** The raw asset for the indicator animation */
+        val indicatorAsset: Int,
+        /** Rotation of the overlayView */
+        val overlayViewRotation: Float,
+    )
+
+    private val _lottieBounds: MutableStateFlow<Rect?> = MutableStateFlow(null)
+
+    /** Used for setting lottie bounds once the composition has loaded. */
+    fun setLottieBounds(bounds: Rect) {
+        _lottieBounds.value = bounds
+    }
+
+    private val displayRotation = displayStateInteractor.currentRotation
+    private val sensorLocation = sfpsSensorInteractor.sensorLocation
+
+    /** Default LayoutParams for the overlayView */
+    val defaultOverlayViewParams: WindowManager.LayoutParams
+        get() =
+            WindowManager.LayoutParams(
+                    WindowManager.LayoutParams.WRAP_CONTENT,
+                    WindowManager.LayoutParams.WRAP_CONTENT,
+                    WindowManager.LayoutParams.TYPE_NAVIGATION_BAR_PANEL,
+                    Utils.FINGERPRINT_OVERLAY_LAYOUT_PARAM_FLAGS,
+                    PixelFormat.TRANSLUCENT
+                )
+                .apply {
+                    title = TAG
+                    fitInsetsTypes = 0 // overrides default, avoiding status bars during layout
+                    gravity = Gravity.TOP or Gravity.LEFT
+                    layoutInDisplayCutoutMode =
+                        WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS
+                    privateFlags = PRIVATE_FLAG_TRUSTED_OVERLAY or PRIVATE_FLAG_NO_MOVE_ANIMATION
+                }
+
+    private val indicatorAsset: Flow<Int> =
+        combine(displayRotation, sensorLocation) { rotation: DisplayRotation, sensorLocation ->
+                val yAligned = sensorLocation.isSensorVerticalInDefaultOrientation
+                val newAsset: Int =
+                    when (rotation) {
+                        DisplayRotation.ROTATION_0 ->
+                            if (yAligned) {
+                                R.raw.sfps_pulse
+                            } else {
+                                R.raw.sfps_pulse_landscape
+                            }
+                        DisplayRotation.ROTATION_180 ->
+                            if (yAligned) {
+                                R.raw.sfps_pulse
+                            } else {
+                                R.raw.sfps_pulse_landscape
+                            }
+                        else ->
+                            if (yAligned) {
+                                R.raw.sfps_pulse_landscape
+                            } else {
+                                R.raw.sfps_pulse
+                            }
+                    }
+                newAsset
+            }
+            .distinctUntilChanged()
+
+    private val overlayViewRotation: Flow<Float> =
+        combine(
+                displayRotation,
+                sensorLocation,
+            ) { rotation: DisplayRotation, sensorLocation ->
+                val yAligned = sensorLocation.isSensorVerticalInDefaultOrientation
+                when (rotation) {
+                    DisplayRotation.ROTATION_90 -> if (yAligned) 0f else 180f
+                    DisplayRotation.ROTATION_180 -> 180f
+                    DisplayRotation.ROTATION_270 -> if (yAligned) 180f else 0f
+                    else -> 0f
+                }
+            }
+            .distinctUntilChanged()
+
+    /** Contains properties (animation asset and view rotation) for overlayView */
+    val overlayViewProperties: Flow<OverlayViewProperties> =
+        combine(indicatorAsset, overlayViewRotation) { asset: Int, rotation: Float ->
+            OverlayViewProperties(asset, rotation)
+        }
+
+    /** LayoutParams for placement of overlayView (the side fingerprint sensor indicator view) */
+    val overlayViewParams: Flow<WindowManager.LayoutParams> =
+        combine(
+            _lottieBounds,
+            sensorLocation,
+            displayRotation,
+        ) { bounds: Rect?, sensorLocation: SideFpsSensorLocation, displayRotation: DisplayRotation
+            ->
+            val topLeft = Point(sensorLocation.left, sensorLocation.top)
+
+            if (sensorLocation.isSensorVerticalInDefaultOrientation) {
+                if (displayRotation == DisplayRotation.ROTATION_0) {
+                    topLeft.x -= bounds!!.width()
+                } else if (displayRotation == DisplayRotation.ROTATION_270) {
+                    topLeft.y -= bounds!!.height()
+                }
+            } else {
+                if (displayRotation == DisplayRotation.ROTATION_180) {
+                    topLeft.y -= bounds!!.height()
+                } else if (displayRotation == DisplayRotation.ROTATION_270) {
+                    topLeft.x -= bounds!!.width()
+                }
+            }
+            defaultOverlayViewParams.apply {
+                x = topLeft.x
+                y = topLeft.y
+            }
+        }
+
+    /** List of LottieCallbacks use for adding dynamic color to the overlayView */
+    val lottieCallbacks: Flow<List<LottieCallback>> =
+        combine(
+            biometricStatusInteractor.sfpsAuthenticationReason,
+            deviceEntrySideFpsOverlayInteractor.showIndicatorForDeviceEntry.distinctUntilChanged(),
+            sideFpsProgressBarViewModel.isVisible,
+        ) { reason: AuthenticationReason, showIndicatorForDeviceEntry: Boolean, progressBarIsVisible
+            ->
+            val callbacks = mutableListOf<LottieCallback>()
+            if (showIndicatorForDeviceEntry) {
+                val indicatorColor =
+                    com.android.settingslib.Utils.getColorAttrDefaultColor(
+                        applicationContext,
+                        com.android.internal.R.attr.materialColorPrimaryFixed
+                    )
+                val outerRimColor =
+                    com.android.settingslib.Utils.getColorAttrDefaultColor(
+                        applicationContext,
+                        com.android.internal.R.attr.materialColorPrimaryFixedDim
+                    )
+                val chevronFill =
+                    com.android.settingslib.Utils.getColorAttrDefaultColor(
+                        applicationContext,
+                        com.android.internal.R.attr.materialColorOnPrimaryFixed
+                    )
+                callbacks.add(LottieCallback(KeyPath(".blue600", "**"), indicatorColor))
+                callbacks.add(LottieCallback(KeyPath(".blue400", "**"), outerRimColor))
+                callbacks.add(LottieCallback(KeyPath(".black", "**"), chevronFill))
+            } else {
+                if (!isDarkMode(applicationContext)) {
+                    callbacks.add(LottieCallback(KeyPath(".black", "**"), Color.WHITE))
+                }
+                for (key in listOf(".blue600", ".blue400")) {
+                    callbacks.add(
+                        LottieCallback(
+                            KeyPath(key, "**"),
+                            applicationContext.getColor(
+                                com.android.settingslib.color.R.color.settingslib_color_blue400
+                            ),
+                        )
+                    )
+                }
+            }
+            callbacks
+        }
+
+    companion object {
+        private const val TAG = "SideFpsOverlayViewModel"
+    }
+}
+
+private fun isDarkMode(context: Context): Boolean {
+    val darkMode = context.resources.configuration.uiMode and Configuration.UI_MODE_NIGHT_MASK
+    return darkMode == Configuration.UI_MODE_NIGHT_YES
+}
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 1e0e16c..c2a1d8f 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
@@ -18,6 +18,7 @@
 
 import android.os.Build
 import android.util.Log
+import com.android.systemui.biometrics.shared.SideFpsControllerRefactor
 import com.android.systemui.bouncer.shared.constants.KeyguardBouncerConstants.EXPANSION_HIDDEN
 import com.android.systemui.bouncer.shared.model.BouncerShowMessageModel
 import com.android.systemui.dagger.SysUISingleton
@@ -121,6 +122,7 @@
 
     fun setAlternateBouncerUIAvailable(isAvailable: Boolean)
 
+    // TODO(b/288175061): remove with Flags.FLAG_SIDEFPS_CONTROLLER_REFACTOR
     fun setSideFpsShowing(isShowing: Boolean)
 }
 
@@ -261,7 +263,9 @@
         _isBackButtonEnabled.value = isBackButtonEnabled
     }
 
+    // TODO(b/288175061): remove with Flags.FLAG_SIDEFPS_CONTROLLER_REFACTOR
     override fun setSideFpsShowing(isShowing: Boolean) {
+        SideFpsControllerRefactor.assertInLegacyMode()
         _sideFpsShowing.value = isShowing
     }
 
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 aa7758f..621ca5d 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
@@ -28,6 +28,7 @@
 import com.android.keyguard.KeyguardUpdateMonitor
 import com.android.keyguard.KeyguardUpdateMonitorCallback
 import com.android.systemui.DejankUtils
+import com.android.systemui.biometrics.shared.SideFpsControllerRefactor
 import com.android.systemui.bouncer.data.repository.KeyguardBouncerRepository
 import com.android.systemui.bouncer.shared.constants.KeyguardBouncerConstants
 import com.android.systemui.bouncer.shared.constants.KeyguardBouncerConstants.EXPANSION_HIDDEN
@@ -116,9 +117,11 @@
 
     /** Allow for interaction when just about fully visible */
     val isInteractable: Flow<Boolean> = bouncerExpansion.map { it > 0.9 }
+    // TODO(b/288175061): remove with Flags.FLAG_SIDEFPS_CONTROLLER_REFACTOR
     val sideFpsShowing: Flow<Boolean> = repository.sideFpsShowing
     private var currentUserActiveUnlockRunning = false
 
+    // TODO(b/288175061): remove with Flags.FLAG_SIDEFPS_CONTROLLER_REFACTOR
     /** This callback needs to be a class field so it does not get garbage collected. */
     val keyguardUpdateMonitorCallback =
         object : KeyguardUpdateMonitorCallback() {
@@ -135,7 +138,10 @@
         }
 
     init {
-        keyguardUpdateMonitor.registerCallback(keyguardUpdateMonitorCallback)
+        // TODO(b/288175061): remove with Flags.FLAG_SIDEFPS_CONTROLLER_REFACTOR
+        if (!SideFpsControllerRefactor.isEnabled) {
+            keyguardUpdateMonitor.registerCallback(keyguardUpdateMonitorCallback)
+        }
         applicationScope.launch {
             trustRepository.isCurrentUserActiveUnlockRunning.collect {
                 currentUserActiveUnlockRunning = it
@@ -333,8 +339,10 @@
         repository.setPrimaryStartDisappearAnimation(runnable)
     }
 
+    // TODO(b/288175061): remove with Flags.FLAG_SIDEFPS_CONTROLLER_REFACTOR
     /** Determine whether to show the side fps animation. */
     fun updateSideFpsVisibility() {
+        SideFpsControllerRefactor.assertInLegacyMode()
         val sfpsEnabled: Boolean =
             context.resources.getBoolean(R.bool.config_show_sidefps_hint_on_bouncer)
         val fpsDetectionRunning: Boolean = keyguardUpdateMonitor.isFingerprintDetectionRunning
diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/ui/binder/KeyguardBouncerViewBinder.kt b/packages/SystemUI/src/com/android/systemui/bouncer/ui/binder/KeyguardBouncerViewBinder.kt
index ac3d4b6..5dcd661 100644
--- a/packages/SystemUI/src/com/android/systemui/bouncer/ui/binder/KeyguardBouncerViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/bouncer/ui/binder/KeyguardBouncerViewBinder.kt
@@ -27,6 +27,7 @@
 import com.android.keyguard.KeyguardSecurityModel
 import com.android.keyguard.KeyguardSecurityView
 import com.android.keyguard.dagger.KeyguardBouncerComponent
+import com.android.systemui.biometrics.shared.SideFpsControllerRefactor
 import com.android.systemui.bouncer.domain.interactor.BouncerMessageInteractor
 import com.android.systemui.bouncer.shared.constants.KeyguardBouncerConstants.EXPANSION_VISIBLE
 import com.android.systemui.bouncer.ui.BouncerViewDelegate
@@ -233,15 +234,21 @@
                             .collect { view.systemUiVisibility = it }
                     }
 
-                    launch {
-                        viewModel.shouldUpdateSideFps.collect {
-                            viewModel.updateSideFpsVisibility()
+                    // TODO(b/288175061): remove with Flags.FLAG_SIDEFPS_CONTROLLER_REFACTOR
+                    if (!SideFpsControllerRefactor.isEnabled) {
+                        launch {
+                            viewModel.shouldUpdateSideFps.collect {
+                                viewModel.updateSideFpsVisibility()
+                            }
                         }
                     }
 
-                    launch {
-                        viewModel.sideFpsShowing.collect {
-                            securityContainerController.updateSideFpsVisibility(it)
+                    // TODO(b/288175061): remove with Flags.FLAG_SIDEFPS_CONTROLLER_REFACTOR
+                    if (!SideFpsControllerRefactor.isEnabled) {
+                        launch {
+                            viewModel.sideFpsShowing.collect {
+                                securityContainerController.updateSideFpsVisibility(it)
+                            }
                         }
                     }
                     awaitCancellation()
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 649ae2f..1c9d1f0 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
@@ -17,6 +17,7 @@
 package com.android.systemui.bouncer.ui.viewmodel
 
 import android.view.View
+import com.android.systemui.biometrics.shared.SideFpsControllerRefactor
 import com.android.systemui.bouncer.domain.interactor.PrimaryBouncerInteractor
 import com.android.systemui.bouncer.shared.model.BouncerShowMessageModel
 import com.android.systemui.bouncer.ui.BouncerView
@@ -61,9 +62,11 @@
     /** Observe whether keyguard is authenticated already. */
     val keyguardAuthenticated: Flow<Boolean> = interactor.keyguardAuthenticatedBiometrics
 
+    // TODO(b/288175061): remove with Flags.FLAG_SIDEFPS_CONTROLLER_REFACTOR
     /** Observe whether the side fps is showing. */
     val sideFpsShowing: Flow<Boolean> = interactor.sideFpsShowing
 
+    // TODO(b/288175061): remove with Flags.FLAG_SIDEFPS_CONTROLLER_REFACTOR
     /** Observe whether we should update fps is showing. */
     val shouldUpdateSideFps: Flow<Unit> =
         merge(
@@ -87,7 +90,9 @@
         interactor.onMessageShown()
     }
 
+    // TODO(b/288175061): remove with Flags.FLAG_SIDEFPS_CONTROLLER_REFACTOR
     fun updateSideFpsVisibility() {
+        SideFpsControllerRefactor.assertInLegacyMode()
         interactor.updateSideFpsVisibility()
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt b/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt
index 1a2a425..e342c6b 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/domain/interactor/CommunalInteractor.kt
@@ -122,7 +122,7 @@
         if (!smartspaceRepository.isSmartspaceRemoteViewsEnabled) {
             flowOf(emptyList())
         } else {
-            smartspaceRepository.lockscreenSmartspaceTargets.map { targets ->
+            smartspaceRepository.communalSmartspaceTargets.map { targets ->
                 targets
                     .filter { target ->
                         target.featureType == SmartspaceTarget.FEATURE_TIMER &&
diff --git a/packages/SystemUI/src/com/android/systemui/communal/smartspace/CommunalSmartspaceController.kt b/packages/SystemUI/src/com/android/systemui/communal/smartspace/CommunalSmartspaceController.kt
new file mode 100644
index 0000000..c5610c8
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/communal/smartspace/CommunalSmartspaceController.kt
@@ -0,0 +1,195 @@
+/*
+ * 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.communal.smartspace
+
+import android.app.smartspace.SmartspaceConfig
+import android.app.smartspace.SmartspaceManager
+import android.app.smartspace.SmartspaceSession
+import android.app.smartspace.SmartspaceTarget
+import android.content.Context
+import android.util.Log
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.plugins.BcSmartspaceDataPlugin
+import com.android.systemui.plugins.BcSmartspaceDataPlugin.SmartspaceTargetListener
+import com.android.systemui.plugins.BcSmartspaceDataPlugin.SmartspaceView
+import com.android.systemui.plugins.BcSmartspaceDataPlugin.UI_SURFACE_GLANCEABLE_HUB
+import com.android.systemui.smartspace.SmartspacePrecondition
+import com.android.systemui.smartspace.SmartspaceTargetFilter
+import com.android.systemui.smartspace.dagger.SmartspaceModule.Companion.DREAM_SMARTSPACE_PRECONDITION
+import com.android.systemui.smartspace.dagger.SmartspaceModule.Companion.DREAM_SMARTSPACE_TARGET_FILTER
+import com.android.systemui.smartspace.dagger.SmartspaceModule.Companion.GLANCEABLE_HUB_SMARTSPACE_DATA_PLUGIN
+import com.android.systemui.util.concurrency.Execution
+import java.util.Optional
+import java.util.concurrent.Executor
+import javax.inject.Inject
+import javax.inject.Named
+
+/** Controller for managing the smartspace view on the dream */
+@SysUISingleton
+class CommunalSmartspaceController
+@Inject
+constructor(
+    private val context: Context,
+    private val smartspaceManager: SmartspaceManager?,
+    private val execution: Execution,
+    @Main private val uiExecutor: Executor,
+    @Named(DREAM_SMARTSPACE_PRECONDITION) private val precondition: SmartspacePrecondition,
+    @Named(DREAM_SMARTSPACE_TARGET_FILTER)
+    private val optionalTargetFilter: Optional<SmartspaceTargetFilter>,
+    @Named(GLANCEABLE_HUB_SMARTSPACE_DATA_PLUGIN) optionalPlugin: Optional<BcSmartspaceDataPlugin>,
+) {
+    companion object {
+        private const val TAG = "CommunalSmartspaceCtrlr"
+    }
+
+    private var session: SmartspaceSession? = null
+    private val plugin: BcSmartspaceDataPlugin? = optionalPlugin.orElse(null)
+    private var targetFilter: SmartspaceTargetFilter? = optionalTargetFilter.orElse(null)
+
+    // A shadow copy of listeners is maintained to track whether the session should remain open.
+    private var listeners = mutableSetOf<SmartspaceTargetListener>()
+
+    private var unfilteredListeners = mutableSetOf<SmartspaceTargetListener>()
+
+    // Smartspace can be used on multiple displays, such as when the user casts their screen
+    private var smartspaceViews = mutableSetOf<SmartspaceView>()
+
+    var preconditionListener =
+        object : SmartspacePrecondition.Listener {
+            override fun onCriteriaChanged() {
+                reloadSmartspace()
+            }
+        }
+
+    init {
+        precondition.addListener(preconditionListener)
+    }
+
+    var filterListener =
+        object : SmartspaceTargetFilter.Listener {
+            override fun onCriteriaChanged() {
+                reloadSmartspace()
+            }
+        }
+
+    init {
+        targetFilter?.addListener(filterListener)
+    }
+
+    private val sessionListener =
+        SmartspaceSession.OnTargetsAvailableListener { targets ->
+            execution.assertIsMainThread()
+
+            val filteredTargets =
+                targets.filter { targetFilter?.filterSmartspaceTarget(it) ?: true }
+            plugin?.onTargetsAvailable(filteredTargets)
+        }
+
+    private fun hasActiveSessionListeners(): Boolean {
+        return smartspaceViews.isNotEmpty() ||
+            listeners.isNotEmpty() ||
+            unfilteredListeners.isNotEmpty()
+    }
+
+    private fun connectSession() {
+        if (smartspaceManager == null) {
+            return
+        }
+        if (plugin == null) {
+            return
+        }
+        if (session != null || !hasActiveSessionListeners()) {
+            return
+        }
+
+        if (!precondition.conditionsMet()) {
+            return
+        }
+
+        val newSession =
+            smartspaceManager.createSmartspaceSession(
+                SmartspaceConfig.Builder(context, UI_SURFACE_GLANCEABLE_HUB).build()
+            )
+        Log.d(TAG, "Starting smartspace session for dream")
+        newSession.addOnTargetsAvailableListener(uiExecutor, sessionListener)
+        this.session = newSession
+
+        plugin?.registerSmartspaceEventNotifier { e -> session?.notifySmartspaceEvent(e) }
+
+        reloadSmartspace()
+    }
+
+    /** Disconnects the smartspace view from the smartspace service and cleans up any resources. */
+    private fun disconnect() {
+        if (hasActiveSessionListeners()) return
+
+        execution.assertIsMainThread()
+
+        if (session == null) {
+            return
+        }
+
+        session?.let {
+            it.removeOnTargetsAvailableListener(sessionListener)
+            it.close()
+        }
+
+        session = null
+
+        plugin?.registerSmartspaceEventNotifier(null)
+        plugin?.onTargetsAvailable(emptyList())
+        Log.d(TAG, "Ending smartspace session for dream")
+    }
+
+    fun addListener(listener: SmartspaceTargetListener) {
+        addAndRegisterListener(listener, plugin)
+    }
+
+    fun removeListener(listener: SmartspaceTargetListener) {
+        removeAndUnregisterListener(listener, plugin)
+    }
+
+    private fun addAndRegisterListener(
+        listener: SmartspaceTargetListener,
+        smartspaceDataPlugin: BcSmartspaceDataPlugin?
+    ) {
+        execution.assertIsMainThread()
+        smartspaceDataPlugin?.registerListener(listener)
+        listeners.add(listener)
+
+        connectSession()
+    }
+
+    private fun removeAndUnregisterListener(
+        listener: SmartspaceTargetListener,
+        smartspaceDataPlugin: BcSmartspaceDataPlugin?
+    ) {
+        execution.assertIsMainThread()
+        smartspaceDataPlugin?.unregisterListener(listener)
+        listeners.remove(listener)
+        disconnect()
+    }
+
+    private fun reloadSmartspace() {
+        session?.requestSmartspaceUpdate()
+    }
+
+    private fun onTargetsAvailableUnfiltered(targets: List<SmartspaceTarget>) {
+        unfilteredListeners.forEach { it.onSmartspaceTargetsUpdated(targets) }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/communal/widgets/EditWidgetsActivity.kt b/packages/SystemUI/src/com/android/systemui/communal/widgets/EditWidgetsActivity.kt
index 7b94fc1..573a748 100644
--- a/packages/SystemUI/src/com/android/systemui/communal/widgets/EditWidgetsActivity.kt
+++ b/packages/SystemUI/src/com/android/systemui/communal/widgets/EditWidgetsActivity.kt
@@ -76,6 +76,10 @@
                     Intent(applicationContext, WidgetPickerActivity::class.java)
                 )
             },
+            onEditDone = {
+                // TODO(b/315154364): in a separate change, lock the device and transition to GH
+                finish()
+            }
         )
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/complication/DreamHomeControlsComplication.java b/packages/SystemUI/src/com/android/systemui/complication/DreamHomeControlsComplication.java
index 7ac1cc7..9d4ed20 100644
--- a/packages/SystemUI/src/com/android/systemui/complication/DreamHomeControlsComplication.java
+++ b/packages/SystemUI/src/com/android/systemui/complication/DreamHomeControlsComplication.java
@@ -25,11 +25,15 @@
 import android.content.Context;
 import android.content.Intent;
 import android.content.res.Resources;
+import android.graphics.drawable.Drawable;
 import android.util.Log;
 import android.view.View;
 import android.widget.ImageView;
 
+import androidx.annotation.Nullable;
+
 import com.android.internal.logging.UiEventLogger;
+import com.android.settingslib.Utils;
 import com.android.systemui.CoreStartable;
 import com.android.systemui.animation.ActivityLaunchAnimator;
 import com.android.systemui.complication.dagger.DreamHomeControlsComplicationComponent;
@@ -43,6 +47,7 @@
 import com.android.systemui.dreams.DreamOverlayStateController;
 import com.android.systemui.plugins.ActivityStarter;
 import com.android.systemui.shared.condition.Monitor;
+import com.android.systemui.statusbar.policy.ConfigurationController;
 import com.android.systemui.util.ViewController;
 import com.android.systemui.util.condition.ConditionalCoreStartable;
 
@@ -195,34 +200,70 @@
 
         private final ActivityStarter mActivityStarter;
         private final Context mContext;
+        private final ConfigurationController mConfigurationController;
         private final ControlsComponent mControlsComponent;
 
         private final UiEventLogger mUiEventLogger;
 
+        private final ConfigurationController.ConfigurationListener mConfigurationListener =
+                new ConfigurationController.ConfigurationListener() {
+                    @Override
+                    public void onUiModeChanged() {
+                        reloadResources();
+                    }
+                };
+
         @Inject
         DreamHomeControlsChipViewController(
                 @Named(DREAM_HOME_CONTROLS_CHIP_VIEW) ImageView view,
                 ActivityStarter activityStarter,
                 Context context,
+                ConfigurationController configurationController,
                 ControlsComponent controlsComponent,
                 UiEventLogger uiEventLogger) {
             super(view);
 
             mActivityStarter = activityStarter;
             mContext = context;
+            mConfigurationController = configurationController;
             mControlsComponent = controlsComponent;
             mUiEventLogger = uiEventLogger;
         }
 
         @Override
         protected void onViewAttached() {
-            mView.setImageResource(mControlsComponent.getTileImageId());
-            mView.setContentDescription(mContext.getString(mControlsComponent.getTileTitleId()));
+            reloadResources();
             mView.setOnClickListener(this::onClickHomeControls);
+            mConfigurationController.addCallback(mConfigurationListener);
         }
 
         @Override
-        protected void onViewDetached() {}
+        protected void onViewDetached() {
+            mConfigurationController.removeCallback(mConfigurationListener);
+        }
+
+        private void reloadResources() {
+            final String title = getControlsTitle();
+            if (title != null) {
+                mView.setContentDescription(title);
+            }
+            mView.setImageResource(mControlsComponent.getTileImageId());
+            mView.setImageTintList(Utils.getColorAttr(mContext, android.R.attr.textColorPrimary));
+            final Drawable background = mView.getBackground();
+            if (background != null) {
+                background.setTintList(
+                        Utils.getColorAttr(mContext, com.android.internal.R.attr.colorSurface));
+            }
+        }
+
+        @Nullable
+        private String getControlsTitle() {
+            try {
+                return mContext.getString(mControlsComponent.getTileTitleId());
+            } catch (Resources.NotFoundException e) {
+                return null;
+            }
+        }
 
         private void onClickHomeControls(View v) {
             if (DEBUG) Log.d(TAG, "home controls complication tapped");
diff --git a/packages/SystemUI/src/com/android/systemui/complication/dagger/DreamHomeControlsComplicationComponent.java b/packages/SystemUI/src/com/android/systemui/complication/dagger/DreamHomeControlsComplicationComponent.java
index 08d0595..b6dcfcb 100644
--- a/packages/SystemUI/src/com/android/systemui/complication/dagger/DreamHomeControlsComplicationComponent.java
+++ b/packages/SystemUI/src/com/android/systemui/complication/dagger/DreamHomeControlsComplicationComponent.java
@@ -24,9 +24,8 @@
 import android.view.LayoutInflater;
 import android.widget.ImageView;
 
-import com.android.settingslib.Utils;
-import com.android.systemui.res.R;
 import com.android.systemui.complication.DreamHomeControlsComplication;
+import com.android.systemui.res.R;
 import com.android.systemui.shared.shadow.DoubleShadowIconDrawable;
 import com.android.systemui.shared.shadow.DoubleShadowTextHelper;
 
@@ -98,7 +97,7 @@
         @DreamHomeControlsComplicationScope
         @Named(DREAM_HOME_CONTROLS_BACKGROUND_DRAWABLE)
         static Drawable providesHomeControlsBackground(Context context, Resources resources) {
-            final Drawable background = new DoubleShadowIconDrawable(createShadowInfo(
+            return new DoubleShadowIconDrawable(createShadowInfo(
                             resources,
                             R.dimen.dream_overlay_bottom_affordance_key_text_shadow_radius,
                             R.dimen.dream_overlay_bottom_affordance_key_text_shadow_dx,
@@ -117,11 +116,6 @@
                             R.dimen.dream_overlay_bottom_affordance_width),
                     resources.getDimensionPixelSize(R.dimen.dream_overlay_bottom_affordance_inset)
             );
-
-            background.setTintList(
-                    Utils.getColorAttr(context, com.android.internal.R.attr.colorSurface));
-
-            return background;
         }
 
         private static DoubleShadowTextHelper.ShadowInfo createShadowInfo(Resources resources,
diff --git a/packages/SystemUI/src/com/android/systemui/compose/BaseComposeFacade.kt b/packages/SystemUI/src/com/android/systemui/compose/BaseComposeFacade.kt
index 65d4495..3a92739 100644
--- a/packages/SystemUI/src/com/android/systemui/compose/BaseComposeFacade.kt
+++ b/packages/SystemUI/src/com/android/systemui/compose/BaseComposeFacade.kt
@@ -63,6 +63,7 @@
         activity: ComponentActivity,
         viewModel: BaseCommunalViewModel,
         onOpenWidgetPicker: () -> Unit,
+        onEditDone: () -> Unit,
     )
 
     /** Create a [View] to represent [viewModel] on screen. */
diff --git a/packages/SystemUI/src/com/android/systemui/contrast/ContrastDialogDelegate.kt b/packages/SystemUI/src/com/android/systemui/contrast/ContrastDialogDelegate.kt
index 63b01ed..0daa058 100644
--- a/packages/SystemUI/src/com/android/systemui/contrast/ContrastDialogDelegate.kt
+++ b/packages/SystemUI/src/com/android/systemui/contrast/ContrastDialogDelegate.kt
@@ -35,23 +35,22 @@
 import javax.inject.Inject
 
 /** Dialog to select contrast options */
-class ContrastDialogDelegate @Inject constructor(
-    private val sysuiDialogFactory : SystemUIDialog.Factory,
+class ContrastDialogDelegate
+@Inject
+constructor(
+    private val sysuiDialogFactory: SystemUIDialog.Factory,
     @Main private val mainExecutor: Executor,
     private val uiModeManager: UiModeManager,
     private val userTracker: UserTracker,
     private val secureSettings: SecureSettings,
 ) : SystemUIDialog.Delegate, UiModeManager.ContrastChangeListener {
 
-    override fun createDialog(): SystemUIDialog {
-        return sysuiDialogFactory.create(this)
-    }
-
     @VisibleForTesting lateinit var contrastButtons: Map<Int, FrameLayout>
     lateinit var dialogView: View
     @VisibleForTesting var initialContrast: Float = fromContrastLevel(CONTRAST_LEVEL_STANDARD)
 
-    override fun onCreate(dialog: SystemUIDialog, savedInstanceState: Bundle?) {
+    override fun createDialog(): SystemUIDialog {
+        val dialog = sysuiDialogFactory.create(this)
         dialogView = dialog.layoutInflater.inflate(R.layout.contrast_dialog, null)
         with(dialog) {
             setView(dialogView)
@@ -67,12 +66,16 @@
             }
             setPositiveButton(com.android.settingslib.R.string.done) { _, _ -> dialog.dismiss() }
         }
+
+        return dialog
+    }
+
+    override fun onCreate(dialog: SystemUIDialog, savedInstanceState: Bundle?) {
         contrastButtons =
             mapOf(
-                CONTRAST_LEVEL_STANDARD to dialogView.requireViewById(
-                    R.id.contrast_button_standard),
-                CONTRAST_LEVEL_MEDIUM to dialogView.requireViewById(R.id.contrast_button_medium),
-                CONTRAST_LEVEL_HIGH to dialogView.requireViewById(R.id.contrast_button_high)
+                CONTRAST_LEVEL_STANDARD to dialog.requireViewById(R.id.contrast_button_standard),
+                CONTRAST_LEVEL_MEDIUM to dialog.requireViewById(R.id.contrast_button_medium),
+                CONTRAST_LEVEL_HIGH to dialog.requireViewById(R.id.contrast_button_high)
             )
 
         contrastButtons.forEach { (contrastLevel, contrastButton) ->
diff --git a/packages/SystemUI/src/com/android/systemui/flags/FlagDependencies.kt b/packages/SystemUI/src/com/android/systemui/flags/FlagDependencies.kt
index d5b95d67..5ec51f4 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/FlagDependencies.kt
+++ b/packages/SystemUI/src/com/android/systemui/flags/FlagDependencies.kt
@@ -16,6 +16,12 @@
 
 package com.android.systemui.flags
 
+import com.android.server.notification.Flags.FLAG_CROSS_APP_POLITE_NOTIFICATIONS
+import com.android.server.notification.Flags.FLAG_POLITE_NOTIFICATIONS
+import com.android.server.notification.Flags.FLAG_VIBRATE_WHILE_UNLOCKED
+import com.android.server.notification.Flags.crossAppPoliteNotifications
+import com.android.server.notification.Flags.politeNotifications
+import com.android.server.notification.Flags.vibrateWhileUnlocked
 import com.android.systemui.Flags.FLAG_KEYGUARD_BOTTOM_AREA_REFACTOR
 import com.android.systemui.Flags.keyguardBottomAreaRefactor
 import com.android.systemui.dagger.SysUISingleton
@@ -36,5 +42,14 @@
         val keyguardBottomAreaRefactor = FlagToken(
                 FLAG_KEYGUARD_BOTTOM_AREA_REFACTOR, keyguardBottomAreaRefactor())
         KeyguardShadeMigrationNssl.token dependsOn keyguardBottomAreaRefactor
+
+        val crossAppPoliteNotifToken =
+                FlagToken(FLAG_CROSS_APP_POLITE_NOTIFICATIONS, crossAppPoliteNotifications())
+        val politeNotifToken = FlagToken(FLAG_POLITE_NOTIFICATIONS, politeNotifications())
+        crossAppPoliteNotifToken dependsOn politeNotifToken
+
+        val vibrateWhileUnlockedToken =
+                FlagToken(FLAG_VIBRATE_WHILE_UNLOCKED, vibrateWhileUnlocked())
+        vibrateWhileUnlockedToken dependsOn politeNotifToken
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
index b753ff7..b1d4587 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
+++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
@@ -111,7 +111,7 @@
     // TODO(b/301955929)
     @JvmField
     val NOTIF_LS_BACKGROUND_THREAD =
-            unreleasedFlag("notification_lockscreen_mgr_bg_thread", teamfood = true)
+            releasedFlag("notification_lockscreen_mgr_bg_thread")
 
     // 200 - keyguard/lockscreen
     // ** Flag retired **
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
index b7260f2..57f3b70 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewMediator.java
@@ -141,6 +141,7 @@
 import com.android.systemui.flags.Flags;
 import com.android.systemui.flags.SystemPropertiesHelper;
 import com.android.systemui.keyguard.dagger.KeyguardModule;
+import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor;
 import com.android.systemui.keyguard.shared.model.TransitionStep;
 import com.android.systemui.keyguard.ui.viewmodel.DreamingToLockscreenTransitionViewModel;
 import com.android.systemui.log.SessionTracker;
@@ -1319,6 +1320,7 @@
     private DeviceConfigProxy mDeviceConfig;
     private DozeParameters mDozeParameters;
     private SelectedUserInteractor mSelectedUserInteractor;
+    private KeyguardInteractor mKeyguardInteractor;
 
     private final KeyguardStateController mKeyguardStateController;
     private final KeyguardStateController.Callback mKeyguardStateControllerCallback =
@@ -1400,7 +1402,8 @@
             Lazy<DreamingToLockscreenTransitionViewModel> dreamingToLockscreenTransitionViewModel,
             SystemPropertiesHelper systemPropertiesHelper,
             Lazy<WindowManagerLockscreenVisibilityManager> wmLockscreenVisibilityManager,
-            SelectedUserInteractor selectedUserInteractor) {
+            SelectedUserInteractor selectedUserInteractor,
+            KeyguardInteractor keyguardInteractor) {
         mContext = context;
         mUserTracker = userTracker;
         mFalsingCollector = falsingCollector;
@@ -1441,6 +1444,7 @@
                 }));
         mDozeParameters = dozeParameters;
         mSelectedUserInteractor = selectedUserInteractor;
+        mKeyguardInteractor = keyguardInteractor;
 
         mStatusBarStateController = statusBarStateController;
         statusBarStateController.addCallback(this);
@@ -2618,6 +2622,7 @@
         setPendingLock(false); // user may have authenticated during the screen off animation
 
         handleHide();
+        mKeyguardInteractor.keyguardDoneAnimationsFinished();
         mUpdateMonitor.clearFingerprintRecognizedWhenKeyguardDone(currentUser);
         Trace.endSection();
     }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java b/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java
index 331d892..3925dd1 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/dagger/KeyguardModule.java
@@ -52,6 +52,7 @@
 import com.android.systemui.keyguard.data.quickaffordance.KeyguardDataQuickAffordanceModule;
 import com.android.systemui.keyguard.data.repository.KeyguardFaceAuthModule;
 import com.android.systemui.keyguard.data.repository.KeyguardRepositoryModule;
+import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor;
 import com.android.systemui.keyguard.domain.interactor.StartKeyguardTransitionModule;
 import com.android.systemui.keyguard.shared.quickaffordance.KeyguardQuickAffordancesMetricsLogger;
 import com.android.systemui.keyguard.shared.quickaffordance.KeyguardQuickAffordancesMetricsLoggerImpl;
@@ -154,7 +155,8 @@
             Lazy<DreamingToLockscreenTransitionViewModel> dreamingToLockscreenTransitionViewModel,
             SystemPropertiesHelper systemPropertiesHelper,
             Lazy<WindowManagerLockscreenVisibilityManager> wmLockscreenVisibilityManager,
-            SelectedUserInteractor selectedUserInteractor) {
+            SelectedUserInteractor selectedUserInteractor,
+            KeyguardInteractor keyguardInteractor) {
         return new KeyguardViewMediator(
                 context,
                 uiEventLogger,
@@ -199,7 +201,8 @@
                 dreamingToLockscreenTransitionViewModel,
                 systemPropertiesHelper,
                 wmLockscreenVisibilityManager,
-                selectedUserInteractor);
+                selectedUserInteractor,
+                keyguardInteractor);
     }
 
     /** */
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 eceaf6c..4d60dd0 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
@@ -187,7 +187,8 @@
         faceManager?.sensorPropertiesInternal?.firstOrNull()?.supportsFaceDetection ?: false
 
     private val _isAuthRunning = MutableStateFlow(false)
-    override val isAuthRunning: StateFlow<Boolean> = _isAuthRunning
+    override val isAuthRunning: StateFlow<Boolean>
+        get() = _isAuthRunning
 
     private val keyguardSessionId: InstanceId?
         get() = sessionTracker.getSessionId(StatusBarManager.SESSION_KEYGUARD)
@@ -253,13 +254,6 @@
                 )
                 .andAllFlows("canFaceAuthRun", faceAuthLog)
                 .flowOn(backgroundDispatcher)
-                .onEach {
-                    faceAuthLogger.canFaceAuthRunChanged(it)
-                    if (!it) {
-                        // Cancel currently running auth if any of the gating checks are false.
-                        cancel()
-                    }
-                }
                 .stateIn(applicationScope, SharingStarted.Eagerly, false)
 
         // Face detection can run only when lockscreen bypass is enabled
@@ -287,12 +281,9 @@
                 )
                 .andAllFlows("canFaceDetectRun", faceDetectLog)
                 .flowOn(backgroundDispatcher)
-                .onEach {
-                    if (!it) {
-                        cancelDetection()
-                    }
-                }
                 .stateIn(applicationScope, SharingStarted.Eagerly, false)
+        observeFaceAuthGatingChecks()
+        observeFaceDetectGatingChecks()
         observeFaceAuthResettingConditions()
         listenForSchedulingWatchdog()
         processPendingAuthRequests()
@@ -313,14 +304,14 @@
     }
 
     private fun observeFaceAuthResettingConditions() {
-        // Clear auth status when keyguard is going away or when the user is switching or device
-        // starts going to sleep.
+        // Clear auth status when keyguard done animations finished or when the user is switching
+        // or device starts going to sleep.
         merge(
                 powerInteractor.isAsleep,
                 if (featureFlags.isEnabled(Flags.KEYGUARD_WM_STATE_REFACTOR)) {
                     keyguardTransitionInteractor.isInTransitionToState(KeyguardState.GONE)
                 } else {
-                    keyguardRepository.isKeyguardGoingAway
+                    keyguardRepository.keyguardDoneAnimationsFinished.map { true }
                 },
                 userRepository.selectedUser.map {
                     it.selectionStatus == SelectionStatus.SELECTION_IN_PROGRESS
@@ -347,6 +338,17 @@
         pendingAuthenticateRequest.value = null
     }
 
+    private fun observeFaceDetectGatingChecks() {
+        canRunDetection
+            .onEach {
+                if (!it) {
+                    cancelDetection()
+                }
+            }
+            .flowOn(mainDispatcher)
+            .launchIn(applicationScope)
+    }
+
     private fun isUdfps() =
         deviceEntryFingerprintAuthRepository.availableFpSensorType.map {
             it == BiometricType.UNDER_DISPLAY_FINGERPRINT
@@ -405,6 +407,20 @@
         )
     }
 
+    private fun observeFaceAuthGatingChecks() {
+        canRunFaceAuth
+            .onEach {
+                faceAuthLogger.canFaceAuthRunChanged(it)
+                if (!it) {
+                    // Cancel currently running auth if any of the gating checks are false.
+                    faceAuthLogger.cancellingFaceAuth()
+                    cancel()
+                }
+            }
+            .flowOn(mainDispatcher)
+            .launchIn(applicationScope)
+    }
+
     private val faceAuthCallback =
         object : FaceManager.AuthenticationCallback() {
             override fun onAuthenticationFailed() {
@@ -539,7 +555,7 @@
                     authenticate(it.uiEvent, it.fallbackToDetection)
                 }
             }
-            .flowOn(backgroundDispatcher)
+            .flowOn(mainDispatcher)
             .launchIn(applicationScope)
     }
 
@@ -635,7 +651,6 @@
     override fun cancel() {
         if (authCancellationSignal == null) return
 
-        faceAuthLogger.cancellingFaceAuth()
         authCancellationSignal?.cancel()
         cancelNotReceivedHandlerJob?.cancel()
         cancelNotReceivedHandlerJob =
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/DeviceEntryFingerprintAuthRepository.kt b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/DeviceEntryFingerprintAuthRepository.kt
index 96386f3..9a13558d 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/DeviceEntryFingerprintAuthRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/data/repository/DeviceEntryFingerprintAuthRepository.kt
@@ -41,6 +41,7 @@
 import kotlinx.coroutines.flow.SharingStarted
 import kotlinx.coroutines.flow.flowOf
 import kotlinx.coroutines.flow.flowOn
+import kotlinx.coroutines.flow.shareIn
 import kotlinx.coroutines.flow.stateIn
 
 /** Encapsulates state about device entry fingerprint auth mechanism. */
@@ -61,6 +62,9 @@
 
     /** Provide the current status of fingerprint authentication. */
     val authenticationStatus: Flow<FingerprintAuthenticationStatus>
+
+    /** Indicates whether to update the side fingerprint sensor indicator visibility. */
+    val shouldUpdateIndicatorVisibility: Flow<Boolean>
 }
 
 /**
@@ -256,6 +260,37 @@
             awaitClose { keyguardUpdateMonitor.removeCallback(callback) }
         }
 
+    override val shouldUpdateIndicatorVisibility: Flow<Boolean> =
+        conflatedCallbackFlow {
+                val sendShouldUpdateIndicatorVisibility =
+                    { shouldUpdateIndicatorVisibility: Boolean ->
+                        trySendWithFailureLogging(
+                            shouldUpdateIndicatorVisibility,
+                            TAG,
+                            "Error sending shouldUpdateIndicatorVisibility " +
+                                "$shouldUpdateIndicatorVisibility"
+                        )
+                    }
+
+                val callback =
+                    object : KeyguardUpdateMonitorCallback() {
+                        override fun onBiometricRunningStateChanged(
+                            running: Boolean,
+                            biometricSourceType: BiometricSourceType?
+                        ) {
+                            sendShouldUpdateIndicatorVisibility(true)
+                        }
+                        override fun onStrongAuthStateChanged(userId: Int) {
+                            sendShouldUpdateIndicatorVisibility(true)
+                        }
+                    }
+                sendShouldUpdateIndicatorVisibility(false)
+                keyguardUpdateMonitor.registerCallback(callback)
+                awaitClose { keyguardUpdateMonitor.removeCallback(callback) }
+            }
+            .flowOn(mainDispatcher)
+            .shareIn(scope, started = SharingStarted.WhileSubscribed(), replay = 1)
+
     companion object {
         const val TAG = "DeviceEntryFingerprintAuthRepositoryImpl"
     }
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 b51edab6..31ef100 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
@@ -160,6 +160,9 @@
     /** Last point that [KeyguardRootView] was tapped */
     val lastRootViewTapPosition: MutableStateFlow<Point?>
 
+    /** Is the ambient indication area visible? */
+    val ambientIndicationVisible: MutableStateFlow<Boolean>
+
     /** Observable for the [StatusBarState] */
     val statusBarState: StateFlow<StatusBarState>
 
@@ -189,6 +192,17 @@
     /** Observable updated when keyguardDone should be called either now or soon. */
     val keyguardDone: Flow<KeyguardDone>
 
+    /**
+     * Emits after the keyguard is done animating away.
+     *
+     * TODO(b/278086361): Remove once KEYGUARD_WM_STATE_REFACTOR flag is removed.
+     */
+    @Deprecated(
+        "Use KeyguardTransitionInteractor flows instead. The closest match for " +
+            "'keyguardDoneAnimationsFinished' is when the GONE transition is finished."
+    )
+    val keyguardDoneAnimationsFinished: Flow<Unit>
+
     /** Receive whether clock should be centered on lockscreen. */
     val clockShouldBeCentered: Flow<Boolean>
 
@@ -236,6 +250,17 @@
     suspend fun setKeyguardDone(keyguardDoneType: KeyguardDone)
 
     fun setClockShouldBeCentered(shouldBeCentered: Boolean)
+
+    /**
+     * Updates signal that the keyguard done animations are finished
+     *
+     * TODO(b/278086361): Remove once KEYGUARD_WM_STATE_REFACTOR flag is removed.
+     */
+    @Deprecated(
+        "Use KeyguardTransitionInteractor flows instead. The closest match for " +
+            "'keyguardDoneAnimationsFinished' is when the GONE transition is finished."
+    )
+    fun keyguardDoneAnimationsFinished()
 }
 
 /** Encapsulates application state for the keyguard. */
@@ -266,6 +291,11 @@
         _keyguardDone.emit(keyguardDoneType)
     }
 
+    override val keyguardDoneAnimationsFinished: MutableSharedFlow<Unit> = MutableSharedFlow()
+    override fun keyguardDoneAnimationsFinished() {
+        keyguardDoneAnimationsFinished.tryEmit(Unit)
+    }
+
     private val _animateBottomAreaDozingTransitions = MutableStateFlow(false)
     override val animateBottomAreaDozingTransitions =
         _animateBottomAreaDozingTransitions.asStateFlow()
@@ -423,6 +453,8 @@
 
     override val lastRootViewTapPosition: MutableStateFlow<Point?> = MutableStateFlow(null)
 
+    override val ambientIndicationVisible: MutableStateFlow<Boolean> = MutableStateFlow(false)
+
     override val isDreamingWithOverlay: Flow<Boolean> =
         conflatedCallbackFlow {
                 val callback =
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/DeviceEntrySideFpsOverlayInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/DeviceEntrySideFpsOverlayInteractor.kt
new file mode 100644
index 0000000..de15fd6
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/DeviceEntrySideFpsOverlayInteractor.kt
@@ -0,0 +1,92 @@
+/*
+ *   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.Context
+import com.android.keyguard.KeyguardUpdateMonitor
+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.dagger.qualifiers.Application
+import com.android.systemui.keyguard.data.repository.DeviceEntryFingerprintAuthRepository
+import com.android.systemui.res.R
+import javax.inject.Inject
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.filter
+import kotlinx.coroutines.flow.filterNotNull
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.merge
+
+/**
+ * Encapsulates business logic for device entry events that impact the side fingerprint sensor
+ * overlay.
+ */
+@SysUISingleton
+class DeviceEntrySideFpsOverlayInteractor
+@Inject
+constructor(
+    @Application private val context: Context,
+    deviceEntryFingerprintAuthRepository: DeviceEntryFingerprintAuthRepository,
+    private val primaryBouncerInteractor: PrimaryBouncerInteractor,
+    alternateBouncerInteractor: AlternateBouncerInteractor,
+    private val keyguardUpdateMonitor: KeyguardUpdateMonitor,
+) {
+
+    init {
+        alternateBouncerInteractor.setAlternateBouncerUIAvailable(true, TAG)
+    }
+
+    private val showIndicatorForPrimaryBouncer: Flow<Boolean> =
+        merge(
+                primaryBouncerInteractor.isShowing,
+                primaryBouncerInteractor.startingToHide,
+                primaryBouncerInteractor.startingDisappearAnimation.filterNotNull(),
+                deviceEntryFingerprintAuthRepository.shouldUpdateIndicatorVisibility.filter { it }
+            )
+            .map { shouldShowIndicatorForPrimaryBouncer() }
+
+    private val showIndicatorForAlternateBouncer: Flow<Boolean> =
+        alternateBouncerInteractor.isVisible
+
+    /**
+     * Indicates whether the primary or alternate bouncers request showing the side fingerprint
+     * sensor indicator.
+     */
+    val showIndicatorForDeviceEntry: Flow<Boolean> =
+        combine(showIndicatorForPrimaryBouncer, showIndicatorForAlternateBouncer) {
+            showForPrimaryBouncer,
+            showForAlternateBouncer ->
+            showForPrimaryBouncer || showForAlternateBouncer
+        }
+
+    private fun shouldShowIndicatorForPrimaryBouncer(): Boolean {
+        val sfpsEnabled: Boolean =
+            context.resources.getBoolean(R.bool.config_show_sidefps_hint_on_bouncer)
+        val sfpsDetectionRunning = keyguardUpdateMonitor.isFingerprintDetectionRunning
+        val isUnlockingWithFpAllowed = keyguardUpdateMonitor.isUnlockingWithFingerprintAllowed
+        return primaryBouncerInteractor.isBouncerShowing() &&
+            sfpsEnabled &&
+            sfpsDetectionRunning &&
+            isUnlockingWithFpAllowed &&
+            !primaryBouncerInteractor.isAnimatingAway()
+    }
+
+    companion object {
+        private const val TAG = "DeviceEntrySideFpsOverlayInteractor"
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt
index c12efe8..21651ba2 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/KeyguardInteractor.kt
@@ -174,6 +174,9 @@
     /** Last point that [KeyguardRootView] view was tapped */
     val lastRootViewTapPosition: Flow<Point?> = repository.lastRootViewTapPosition.asStateFlow()
 
+    /** Is the ambient indication area visible? */
+    val ambientIndicationVisible: Flow<Boolean> = repository.ambientIndicationVisible.asStateFlow()
+
     /** Whether the primary bouncer is showing or not. */
     val primaryBouncerShowing: Flow<Boolean> = bouncerRepository.primaryBouncerShow
 
@@ -311,6 +314,14 @@
         repository.lastRootViewTapPosition.value = point
     }
 
+    fun setAmbientIndicationVisible(isVisible: Boolean) {
+        repository.ambientIndicationVisible.value = isVisible
+    }
+
+    fun keyguardDoneAnimationsFinished() {
+        repository.keyguardDoneAnimationsFinished()
+    }
+
     companion object {
         private const val TAG = "KeyguardInteractor"
     }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/SystemUIKeyguardFaceAuthInteractor.kt b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/SystemUIKeyguardFaceAuthInteractor.kt
index fb20000..e3f4739 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/SystemUIKeyguardFaceAuthInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/domain/interactor/SystemUIKeyguardFaceAuthInteractor.kt
@@ -31,7 +31,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.dagger.qualifiers.Background
 import com.android.systemui.dagger.qualifiers.Main
 import com.android.systemui.keyguard.data.repository.BiometricSettingsRepository
 import com.android.systemui.keyguard.data.repository.DeviceEntryFaceAuthRepository
@@ -72,7 +71,6 @@
     private val context: Context,
     @Application private val applicationScope: CoroutineScope,
     @Main private val mainDispatcher: CoroutineDispatcher,
-    @Background private val backgroundDispatcher: CoroutineDispatcher,
     private val repository: DeviceEntryFaceAuthRepository,
     private val primaryBouncerInteractor: Lazy<PrimaryBouncerInteractor>,
     private val alternateBouncerInteractor: AlternateBouncerInteractor,
@@ -109,7 +107,6 @@
                     fallbackToDetect = false
                 )
             }
-            .flowOn(backgroundDispatcher)
             .launchIn(applicationScope)
 
         alternateBouncerInteractor.isVisible
@@ -121,7 +118,6 @@
                     fallbackToDetect = false
                 )
             }
-            .flowOn(backgroundDispatcher)
             .launchIn(applicationScope)
 
         merge(
@@ -150,7 +146,6 @@
                     fallbackToDetect = true
                 )
             }
-            .flowOn(backgroundDispatcher)
             .launchIn(applicationScope)
 
         deviceEntryFingerprintAuthRepository.isLockedOut
@@ -163,7 +158,6 @@
                     }
                 }
             }
-            .flowOn(backgroundDispatcher)
             .launchIn(applicationScope)
 
         // User switching should stop face auth and then when it is complete we should trigger face
@@ -187,7 +181,6 @@
                     )
                 }
             }
-            .flowOn(backgroundDispatcher)
             .launchIn(applicationScope)
     }
 
@@ -302,7 +295,6 @@
                     trustManager.clearAllBiometricRecognized(BiometricSourceType.FACE, userInfo.id)
                 }
             }
-            .flowOn(backgroundDispatcher)
             .onEach { (isAuthenticated, _) ->
                 listeners.forEach { it.onAuthenticatedChanged(isAuthenticated) }
             }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/SideFpsProgressBarViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/SideFpsProgressBarViewBinder.kt
index 560e4ad..9a6dca3 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/SideFpsProgressBarViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/SideFpsProgressBarViewBinder.kt
@@ -21,6 +21,7 @@
 import com.android.systemui.CoreStartable
 import com.android.systemui.Flags
 import com.android.systemui.biometrics.SideFpsController
+import com.android.systemui.biometrics.shared.SideFpsControllerRefactor
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.keyguard.ui.view.SideFpsProgressBar
@@ -46,6 +47,7 @@
     private val viewModel: SideFpsProgressBarViewModel,
     private val view: SideFpsProgressBar,
     @Application private val applicationScope: CoroutineScope,
+    // TODO(b/288175061): remove with Flags.FLAG_SIDEFPS_CONTROLLER_REFACTOR
     private val sfpsController: dagger.Lazy<SideFpsController>,
     private val logger: SideFpsLogger,
     private val commandRegistry: CommandRegistry,
@@ -109,12 +111,14 @@
         view.updateView(visible, location, length, thickness, rotation)
         // We have to hide the SFPS indicator as the progress bar will
         // be shown at the same location
-        if (visible) {
-            logger.hidingSfpsIndicator()
-            sfpsController.get().hideIndicator()
-        } else if (fpDetectRunning) {
-            logger.showingSfpsIndicator()
-            sfpsController.get().showIndicator()
+        if (!SideFpsControllerRefactor.isEnabled) {
+            if (visible) {
+                logger.hidingSfpsIndicator()
+                sfpsController.get().hideIndicator()
+            } else if (fpDetectRunning) {
+                logger.showingSfpsIndicator()
+                sfpsController.get().showIndicator()
+            }
         }
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardRemotePreviewManager.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardRemotePreviewManager.kt
index acfd3b0..24240df 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardRemotePreviewManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardRemotePreviewManager.kt
@@ -30,6 +30,7 @@
 import com.android.systemui.dagger.qualifiers.Background
 import com.android.systemui.dagger.qualifiers.Main
 import com.android.systemui.shared.quickaffordance.shared.model.KeyguardPreviewConstants
+import com.android.systemui.util.kotlin.logD
 import javax.inject.Inject
 import kotlinx.coroutines.CoroutineDispatcher
 import kotlinx.coroutines.CoroutineScope
@@ -58,11 +59,14 @@
 
             observer =
                 PreviewLifecycleObserver(
-                    renderer,
                     applicationScope,
                     mainDispatcher,
+                    renderer,
                     ::destroyObserver,
                 )
+
+            logD(TAG) { "Created observer $observer" }
+
             // Destroy any previous renderer associated with this token.
             activePreviews[renderer.id]?.let { destroyObserver(it) }
             activePreviews[renderer.id] = observer
@@ -80,6 +84,8 @@
                         observer,
                     )
                 )
+            // NOTE: The process on the other side can retain messenger indefinitely.
+            // (e.g. GC might not trigger and cleanup the reference)
             val msg = Message.obtain()
             msg.replyTo = messenger
             result.putParcelable(KEY_PREVIEW_CALLBACK, msg)
@@ -99,57 +105,84 @@
         }
     }
 
-    private class PreviewLifecycleObserver(
-        private val renderer: KeyguardPreviewRenderer,
-        private val scope: CoroutineScope,
-        private val mainDispatcher: CoroutineDispatcher,
-        private val requestDestruction: (PreviewLifecycleObserver) -> Unit,
-    ) : Handler.Callback, IBinder.DeathRecipient {
+    companion object {
+        internal const val TAG = "KeyguardRemotePreviewManager"
+        @VisibleForTesting const val KEY_PREVIEW_SURFACE_PACKAGE = "surface_package"
+        @VisibleForTesting const val KEY_PREVIEW_CALLBACK = "callback"
+    }
+}
 
-        private var isDestroyedOrDestroying = false
+/**
+ * Handles messages from the other process and handles cleanup.
+ *
+ * NOTE: The other process might hold on to reference of this class indefinitely. It's entirely
+ * possible that GC won't trigger and we'll leak this for all times even if [onDestroy] was called.
+ * This helps make sure no non-Singleton objects are retained beyond destruction to prevent leaks.
+ */
+@VisibleForTesting(VisibleForTesting.PRIVATE)
+class PreviewLifecycleObserver(
+    private val scope: CoroutineScope,
+    private val mainDispatcher: CoroutineDispatcher,
+    renderer: KeyguardPreviewRenderer,
+    onDestroy: (PreviewLifecycleObserver) -> Unit,
+) : Handler.Callback, IBinder.DeathRecipient {
 
-        override fun handleMessage(message: Message): Boolean {
-            if (isDestroyedOrDestroying) {
-                return true
-            }
+    private var isDestroyedOrDestroying = false
+    // These two are null after destruction
+    @VisibleForTesting var renderer: KeyguardPreviewRenderer?
+    @VisibleForTesting var onDestroy: ((PreviewLifecycleObserver) -> Unit)?
 
-            when (message.what) {
-                KeyguardPreviewConstants.MESSAGE_ID_SLOT_SELECTED -> {
-                    message.data.getString(KeyguardPreviewConstants.KEY_SLOT_ID)?.let { slotId ->
-                        renderer.onSlotSelected(slotId = slotId)
-                    }
-                }
-                KeyguardPreviewConstants.MESSAGE_ID_HIDE_SMART_SPACE -> {
-                    renderer.hideSmartspace(
-                        message.data.getBoolean(KeyguardPreviewConstants.KEY_HIDE_SMART_SPACE)
-                    )
-                }
-                else -> requestDestruction(this)
-            }
+    init {
+        this.renderer = renderer
+        this.onDestroy = onDestroy
+    }
 
+    override fun handleMessage(message: Message): Boolean {
+        if (isDestroyedOrDestroying) {
             return true
         }
 
-        override fun binderDied() {
-            requestDestruction(this)
+        when (message.what) {
+            KeyguardPreviewConstants.MESSAGE_ID_SLOT_SELECTED -> {
+                message.data.getString(KeyguardPreviewConstants.KEY_SLOT_ID)?.let { slotId ->
+                    checkNotNull(renderer).onSlotSelected(slotId = slotId)
+                }
+            }
+            KeyguardPreviewConstants.MESSAGE_ID_HIDE_SMART_SPACE -> {
+                checkNotNull(renderer)
+                    .hideSmartspace(
+                        message.data.getBoolean(KeyguardPreviewConstants.KEY_HIDE_SMART_SPACE)
+                    )
+            }
+            else -> checkNotNull(onDestroy).invoke(this)
         }
 
-        fun onDestroy(): Pair<IBinder?, Int>? {
-            if (isDestroyedOrDestroying) {
-                return null
-            }
+        return true
+    }
 
-            isDestroyedOrDestroying = true
-            val hostToken = renderer.hostToken
+    override fun binderDied() {
+        onDestroy?.invoke(this)
+    }
+
+    fun onDestroy(): Pair<IBinder?, Int>? {
+        if (isDestroyedOrDestroying) {
+            return null
+        }
+
+        logD(TAG) { "Destroying $this" }
+
+        isDestroyedOrDestroying = true
+        return renderer?.let { rendererToDestroy ->
+            this.renderer = null
+            this.onDestroy = null
+            val hostToken = rendererToDestroy.hostToken
             hostToken?.unlinkToDeath(this, 0)
-            scope.launch(mainDispatcher) { renderer.destroy() }
-            return renderer.id
+            scope.launch(mainDispatcher) { rendererToDestroy.destroy() }
+            rendererToDestroy.id
         }
     }
 
     companion object {
         private const val TAG = "KeyguardRemotePreviewManager"
-        @VisibleForTesting const val KEY_PREVIEW_SURFACE_PACKAGE = "surface_package"
-        @VisibleForTesting const val KEY_PREVIEW_CALLBACK = "callback"
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModel.kt
index 1d4520f..26dace0 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModel.kt
@@ -180,7 +180,9 @@
                     goneToAodTransitionViewModel
                         .enterFromTopTranslationY(enterFromTopAmount)
                         .onStart { emit(0f) },
-                    occludedToLockscreenTransitionViewModel.lockscreenTranslationY,
+                    occludedToLockscreenTransitionViewModel.lockscreenTranslationY.onStart {
+                        emit(0f)
+                    },
                 ) {
                     keyguardTransitionY,
                     burnInTranslationY,
@@ -193,6 +195,7 @@
                         occludedToLockscreenTransitionTranslationY
                 }
             }
+            .distinctUntilChanged()
 
     val translationX: Flow<Float> = burnIn().map { it.translationX.toFloat() }
 
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/SideFpsProgressBarViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/SideFpsProgressBarViewModel.kt
index 2d0712c..1dbf1f1 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/SideFpsProgressBarViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/SideFpsProgressBarViewModel.kt
@@ -19,6 +19,7 @@
 import android.animation.ValueAnimator
 import android.content.Context
 import android.graphics.Point
+import androidx.annotation.VisibleForTesting
 import androidx.core.animation.doOnEnd
 import com.android.systemui.Flags
 import com.android.systemui.biometrics.domain.interactor.DisplayStateInteractor
@@ -213,4 +214,9 @@
             }
         }
     }
+
+    @VisibleForTesting
+    fun setVisible(isVisible: Boolean) {
+        _visible.value = isVisible
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/media/controls/ui/ColorSchemeTransition.kt b/packages/SystemUI/src/com/android/systemui/media/controls/ui/ColorSchemeTransition.kt
index 14d4b68..c87fd14 100644
--- a/packages/SystemUI/src/com/android/systemui/media/controls/ui/ColorSchemeTransition.kt
+++ b/packages/SystemUI/src/com/android/systemui/media/controls/ui/ColorSchemeTransition.kt
@@ -119,7 +119,7 @@
         ::AnimatingColorTransition
     )
 
-    val bgColor = context.getColor(com.google.android.material.R.color.material_dynamic_secondary95)
+    val bgColor = context.getColor(com.google.android.material.R.color.material_dynamic_neutral20)
     val surfaceColor =
         animatingColorTransitionFactory(bgColor, ::surfaceFromScheme) { surfaceColor ->
             val colorList = ColorStateList.valueOf(surfaceColor)
diff --git a/packages/SystemUI/src/com/android/systemui/notetask/quickaffordance/NoteTaskQuickAffordanceConfig.kt b/packages/SystemUI/src/com/android/systemui/notetask/quickaffordance/NoteTaskQuickAffordanceConfig.kt
index 6d55d05..de490a5 100644
--- a/packages/SystemUI/src/com/android/systemui/notetask/quickaffordance/NoteTaskQuickAffordanceConfig.kt
+++ b/packages/SystemUI/src/com/android/systemui/notetask/quickaffordance/NoteTaskQuickAffordanceConfig.kt
@@ -27,7 +27,6 @@
 import android.util.Log
 import com.android.keyguard.KeyguardUpdateMonitor
 import com.android.keyguard.KeyguardUpdateMonitorCallback
-import com.android.systemui.res.R
 import com.android.systemui.animation.Expandable
 import com.android.systemui.common.shared.model.ContentDescription
 import com.android.systemui.common.shared.model.Icon
@@ -43,6 +42,7 @@
 import com.android.systemui.notetask.NoteTaskEnabledKey
 import com.android.systemui.notetask.NoteTaskEntryPoint.QUICK_AFFORDANCE
 import com.android.systemui.notetask.NoteTaskInfoResolver
+import com.android.systemui.res.R
 import com.android.systemui.stylus.StylusManager
 import dagger.Lazy
 import java.util.concurrent.Executor
@@ -100,7 +100,8 @@
                     isEnabled &&
                         isUserUnlocked &&
                         isDefaultNotesAppSet &&
-                        (isConfigSelected || isStylusEverUsed)
+                        isConfigSelected &&
+                        isStylusEverUsed
                 ) {
                     val contentDescription = ContentDescription.Resource(pickerNameResourceId)
                     val icon = Icon.Resource(pickerIconResourceId, contentDescription)
diff --git a/packages/SystemUI/src/com/android/systemui/privacy/PrivacyDialogControllerV2.kt b/packages/SystemUI/src/com/android/systemui/privacy/PrivacyDialogControllerV2.kt
index fdc70a8..76ef8a2 100644
--- a/packages/SystemUI/src/com/android/systemui/privacy/PrivacyDialogControllerV2.kt
+++ b/packages/SystemUI/src/com/android/systemui/privacy/PrivacyDialogControllerV2.kt
@@ -278,7 +278,12 @@
                     d.setShowForAllUsers(true)
                     d.addOnDismissListener(onDialogDismissed)
                     if (view != null) {
-                        dialogLaunchAnimator.showFromView(d, view)
+                        val controller = getPrivacyDialogController(view)
+                        if (controller == null) {
+                            d.show()
+                        } else {
+                            dialogLaunchAnimator.show(d, controller)
+                        }
                     } else {
                         d.show()
                     }
@@ -291,6 +296,13 @@
         }
     }
 
+    private fun getPrivacyDialogController(source: View): DialogLaunchAnimator.Controller? {
+        val delegate = DialogLaunchAnimator.Controller.fromView(source) ?: return null
+        return object : DialogLaunchAnimator.Controller by delegate {
+            override fun shouldAnimateExit() = false
+        }
+    }
+
     /** Dismisses the dialog */
     fun dismissDialog() {
         dialog?.dismiss()
diff --git a/packages/SystemUI/src/com/android/systemui/qs/pipeline/dagger/QSPipelineModule.kt b/packages/SystemUI/src/com/android/systemui/qs/pipeline/dagger/QSPipelineModule.kt
index b50798e..4bad45f 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/pipeline/dagger/QSPipelineModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/pipeline/dagger/QSPipelineModule.kt
@@ -20,6 +20,7 @@
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.log.LogBuffer
 import com.android.systemui.log.LogBufferFactory
+import com.android.systemui.qs.pipeline.data.model.RestoreProcessor
 import com.android.systemui.qs.pipeline.data.repository.DefaultTilesQSHostRepository
 import com.android.systemui.qs.pipeline.data.repository.DefaultTilesRepository
 import com.android.systemui.qs.pipeline.data.repository.InstalledTilesComponentRepository
@@ -39,14 +40,17 @@
 import dagger.Provides
 import dagger.multibindings.ClassKey
 import dagger.multibindings.IntoMap
+import dagger.multibindings.Multibinds
 
-@Module(includes = [QSAutoAddModule::class])
+@Module(includes = [QSAutoAddModule::class, RestoreProcessorsModule::class])
 abstract class QSPipelineModule {
 
     /** Implementation for [TileSpecRepository] */
     @Binds
     abstract fun provideTileSpecRepository(impl: TileSpecSettingsRepository): TileSpecRepository
 
+    @Multibinds abstract fun provideRestoreProcessors(): Set<RestoreProcessor>
+
     @Binds
     abstract fun provideDefaultTilesRepository(
         impl: DefaultTilesQSHostRepository
diff --git a/packages/SystemUI/src/com/android/systemui/qs/pipeline/dagger/RestoreProcessorsModule.kt b/packages/SystemUI/src/com/android/systemui/qs/pipeline/dagger/RestoreProcessorsModule.kt
new file mode 100644
index 0000000..e970c84
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/pipeline/dagger/RestoreProcessorsModule.kt
@@ -0,0 +1,31 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.pipeline.dagger
+
+import com.android.systemui.qs.pipeline.data.model.RestoreProcessor
+import com.android.systemui.qs.pipeline.data.restoreprocessors.WorkTileRestoreProcessor
+import dagger.Binds
+import dagger.Module
+import dagger.multibindings.IntoSet
+
+@Module
+interface RestoreProcessorsModule {
+
+    @Binds
+    @IntoSet
+    fun bindWorkTileRestoreProcessor(impl: WorkTileRestoreProcessor): RestoreProcessor
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/pipeline/data/model/RestoreProcessor.kt b/packages/SystemUI/src/com/android/systemui/qs/pipeline/data/model/RestoreProcessor.kt
new file mode 100644
index 0000000..8f7de19
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/pipeline/data/model/RestoreProcessor.kt
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.pipeline.data.model
+
+/**
+ * Perform processing of the [RestoreData] before or after it's applied to repositories.
+ *
+ * The order in which the restore processors are applied in not deterministic.
+ *
+ * In order to declare a restore processor, add it in [RestoreProcessingModule] using
+ *
+ * ```
+ * @Binds
+ * @IntoSet
+ * ``
+ */
+interface RestoreProcessor {
+
+    /** Should be called before applying the restore to the necessary repositories */
+    suspend fun preProcessRestore(restoreData: RestoreData) {}
+
+    /** Should be called after requesting the repositories to update. */
+    suspend fun postProcessRestore(restoreData: RestoreData) {}
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/pipeline/data/repository/AutoAddRepository.kt b/packages/SystemUI/src/com/android/systemui/qs/pipeline/data/repository/AutoAddRepository.kt
index 7998dfb..d40f3f4 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/pipeline/data/repository/AutoAddRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/pipeline/data/repository/AutoAddRepository.kt
@@ -20,9 +20,8 @@
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.qs.pipeline.data.model.RestoreData
 import com.android.systemui.qs.pipeline.shared.TileSpec
-import kotlinx.coroutines.flow.Flow
 import javax.inject.Inject
-import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.Flow
 
 /** Repository to track what QS tiles have been auto-added */
 interface AutoAddRepository {
@@ -49,8 +48,9 @@
 @SysUISingleton
 class AutoAddSettingRepository
 @Inject
-constructor(private val userAutoAddRepositoryFactory: UserAutoAddRepository.Factory) :
-    AutoAddRepository {
+constructor(
+    private val userAutoAddRepositoryFactory: UserAutoAddRepository.Factory,
+) : AutoAddRepository {
 
     private val userAutoAddRepositories = SparseArray<UserAutoAddRepository>()
 
diff --git a/packages/SystemUI/src/com/android/systemui/qs/pipeline/data/repository/QSSettingsRestoredRepository.kt b/packages/SystemUI/src/com/android/systemui/qs/pipeline/data/repository/QSSettingsRestoredRepository.kt
index 6cee116..e718eea 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/pipeline/data/repository/QSSettingsRestoredRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/pipeline/data/repository/QSSettingsRestoredRepository.kt
@@ -10,6 +10,7 @@
 import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.dagger.qualifiers.Background
 import com.android.systemui.qs.pipeline.data.model.RestoreData
+import com.android.systemui.qs.pipeline.data.repository.QSSettingsRestoredRepository.Companion.BUFFER_CAPACITY
 import com.android.systemui.qs.pipeline.data.repository.TilesSettingConverter.toTilesList
 import com.android.systemui.qs.pipeline.shared.logging.QSPipelineLogger
 import javax.inject.Inject
@@ -28,6 +29,14 @@
 /** Provides restored data (from Backup and Restore) for Quick Settings pipeline */
 interface QSSettingsRestoredRepository {
     val restoreData: Flow<RestoreData>
+
+    companion object {
+        // This capacity is the number of restore data that we will keep buffered in the shared
+        // flow. It is unlikely that at any given time there would be this many restores being
+        // processed by consumers, but just in case that a couple of users are restored at the
+        // same time and they need to be replayed for the consumers of the flow.
+        const val BUFFER_CAPACITY = 10
+    }
 }
 
 @SysUISingleton
@@ -86,11 +95,6 @@
 
     private companion object {
         private const val TAG = "QSSettingsRestoredBroadcastRepository"
-        // This capacity is the number of restore data that we will keep buffered in the shared
-        // flow. It is unlikely that at any given time there would be this many restores being
-        // processed by consumers, but just in case that a couple of users are restored at the
-        // same time and they need to be replayed for the consumers of the flow.
-        private const val BUFFER_CAPACITY = 10
 
         private val INTENT_FILTER = IntentFilter(Intent.ACTION_SETTING_RESTORED)
         private const val TILES_SETTING = Settings.Secure.QS_TILES
diff --git a/packages/SystemUI/src/com/android/systemui/qs/pipeline/data/restoreprocessors/WorkTileRestoreProcessor.kt b/packages/SystemUI/src/com/android/systemui/qs/pipeline/data/restoreprocessors/WorkTileRestoreProcessor.kt
new file mode 100644
index 0000000..7376aa9
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/pipeline/data/restoreprocessors/WorkTileRestoreProcessor.kt
@@ -0,0 +1,82 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.pipeline.data.restoreprocessors
+
+import android.os.UserHandle
+import android.util.SparseIntArray
+import androidx.annotation.GuardedBy
+import androidx.core.util.getOrDefault
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.qs.pipeline.data.model.RestoreData
+import com.android.systemui.qs.pipeline.data.model.RestoreProcessor
+import com.android.systemui.qs.pipeline.data.repository.QSSettingsRestoredRepository
+import com.android.systemui.qs.pipeline.data.repository.TileSpecRepository
+import com.android.systemui.qs.pipeline.shared.TileSpec
+import com.android.systemui.qs.tiles.WorkModeTile
+import javax.inject.Inject
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableSharedFlow
+import kotlinx.coroutines.flow.filter
+import kotlinx.coroutines.flow.map
+
+/**
+ * Processor for restore data for work tile.
+ *
+ * It will indicate when auto-add tracking may be removed for a user. This may be necessary if the
+ * tile will be destroyed due to being not available, but needs to be added once work profile is
+ * enabled (after restore), in the same position as it was in the restored data.
+ */
+@SysUISingleton
+class WorkTileRestoreProcessor @Inject constructor() : RestoreProcessor {
+
+    @GuardedBy("lastRestorePosition") private val lastRestorePosition = SparseIntArray()
+
+    private val _removeTrackingForUser =
+        MutableSharedFlow<Int>(extraBufferCapacity = QSSettingsRestoredRepository.BUFFER_CAPACITY)
+
+    /**
+     * Flow indicating that we may need to remove auto-add tracking for the work tile for a given
+     * user.
+     */
+    fun removeTrackingForUser(userHandle: UserHandle): Flow<Unit> {
+        return _removeTrackingForUser.filter { it == userHandle.identifier }.map {}
+    }
+
+    override suspend fun postProcessRestore(restoreData: RestoreData) {
+        if (TILE_SPEC in restoreData.restoredTiles) {
+            synchronized(lastRestorePosition) {
+                lastRestorePosition.put(
+                    restoreData.userId,
+                    restoreData.restoredTiles.indexOf(TILE_SPEC)
+                )
+            }
+            _removeTrackingForUser.emit(restoreData.userId)
+        }
+    }
+
+    fun pollLastPosition(userId: Int): Int {
+        return synchronized(lastRestorePosition) {
+            lastRestorePosition.getOrDefault(userId, TileSpecRepository.POSITION_AT_END).also {
+                lastRestorePosition.delete(userId)
+            }
+        }
+    }
+
+    companion object {
+        private val TILE_SPEC = TileSpec.create(WorkModeTile.TILE_SPEC)
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/autoaddable/WorkTileAutoAddable.kt b/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/autoaddable/WorkTileAutoAddable.kt
index 5e3c348..b221199 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/autoaddable/WorkTileAutoAddable.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/autoaddable/WorkTileAutoAddable.kt
@@ -17,8 +17,10 @@
 package com.android.systemui.qs.pipeline.domain.autoaddable
 
 import android.content.pm.UserInfo
+import android.os.UserHandle
 import com.android.systemui.common.coroutine.ConflatedCallbackFlow.conflatedCallbackFlow
 import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.qs.pipeline.data.restoreprocessors.WorkTileRestoreProcessor
 import com.android.systemui.qs.pipeline.domain.model.AutoAddSignal
 import com.android.systemui.qs.pipeline.domain.model.AutoAddTracking
 import com.android.systemui.qs.pipeline.domain.model.AutoAddable
@@ -28,6 +30,8 @@
 import javax.inject.Inject
 import kotlinx.coroutines.channels.awaitClose
 import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.mapNotNull
+import kotlinx.coroutines.flow.merge
 
 /**
  * [AutoAddable] for [WorkModeTile.TILE_SPEC].
@@ -36,17 +40,37 @@
  * signal to remove it if there is not.
  */
 @SysUISingleton
-class WorkTileAutoAddable @Inject constructor(private val userTracker: UserTracker) : AutoAddable {
+class WorkTileAutoAddable
+@Inject
+constructor(
+    private val userTracker: UserTracker,
+    private val workTileRestoreProcessor: WorkTileRestoreProcessor,
+) : AutoAddable {
 
     private val spec = TileSpec.create(WorkModeTile.TILE_SPEC)
 
     override fun autoAddSignal(userId: Int): Flow<AutoAddSignal> {
-        return conflatedCallbackFlow {
+        val removeTrackingDueToRestore: Flow<AutoAddSignal> =
+            workTileRestoreProcessor.removeTrackingForUser(UserHandle.of(userId)).mapNotNull {
+                val profiles = userTracker.userProfiles
+                if (profiles.any { it.id == userId } && !profiles.any { it.isManagedProfile }) {
+                    // Only remove auto-added if there are no managed profiles for this user
+                    AutoAddSignal.RemoveTracking(spec)
+                } else {
+                    null
+                }
+            }
+        val signalsFromCallback = conflatedCallbackFlow {
             fun maybeSend(profiles: List<UserInfo>) {
                 if (profiles.any { it.id == userId }) {
                     // We are looking at the profiles of the correct user.
                     if (profiles.any { it.isManagedProfile }) {
-                        trySend(AutoAddSignal.Add(spec))
+                        trySend(
+                            AutoAddSignal.Add(
+                                spec,
+                                workTileRestoreProcessor.pollLastPosition(userId),
+                            )
+                        )
                     } else {
                         trySend(AutoAddSignal.Remove(spec))
                     }
@@ -65,6 +89,7 @@
 
             awaitClose { userTracker.removeCallback(callback) }
         }
+        return merge(removeTrackingDueToRestore, signalsFromCallback)
     }
 
     override val autoAddTracking = AutoAddTracking.Always
diff --git a/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/interactor/AutoAddInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/interactor/AutoAddInteractor.kt
index b747393..cde3835 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/interactor/AutoAddInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/interactor/AutoAddInteractor.kt
@@ -103,6 +103,10 @@
                                     qsPipelineLogger.logTileAutoRemoved(userId, signal.spec)
                                     repository.unmarkTileAdded(userId, signal.spec)
                                 }
+                                is AutoAddSignal.RemoveTracking -> {
+                                    qsPipelineLogger.logTileUnmarked(userId, signal.spec)
+                                    repository.unmarkTileAdded(userId, signal.spec)
+                                }
                             }
                         }
                 }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/interactor/RestoreReconciliationInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/interactor/RestoreReconciliationInteractor.kt
index 9844903..a5be14e 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/interactor/RestoreReconciliationInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/interactor/RestoreReconciliationInteractor.kt
@@ -3,14 +3,15 @@
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.qs.pipeline.data.model.RestoreProcessor
 import com.android.systemui.qs.pipeline.data.repository.AutoAddRepository
 import com.android.systemui.qs.pipeline.data.repository.QSSettingsRestoredRepository
 import com.android.systemui.qs.pipeline.data.repository.TileSpecRepository
+import com.android.systemui.qs.pipeline.shared.logging.QSPipelineLogger
 import javax.inject.Inject
 import kotlinx.coroutines.CoroutineDispatcher
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.flow.collect
 import kotlinx.coroutines.flow.flatMapConcat
 import kotlinx.coroutines.flow.map
 import kotlinx.coroutines.flow.take
@@ -33,6 +34,8 @@
     private val tileSpecRepository: TileSpecRepository,
     private val autoAddRepository: AutoAddRepository,
     private val qsSettingsRestoredRepository: QSSettingsRestoredRepository,
+    private val restoreProcessors: Set<@JvmSuppressWildcards RestoreProcessor>,
+    private val qsPipelineLogger: QSPipelineLogger,
     @Application private val applicationScope: CoroutineScope,
     @Background private val backgroundDispatcher: CoroutineDispatcher,
 ) {
@@ -40,14 +43,30 @@
     @OptIn(ExperimentalCoroutinesApi::class)
     fun start() {
         applicationScope.launch(backgroundDispatcher) {
-            qsSettingsRestoredRepository.restoreData.flatMapConcat { data ->
-                autoAddRepository.autoAddedTiles(data.userId)
-                        .take(1)
-                        .map { tiles -> data to tiles }
-            }.collect { (restoreData, autoAdded) ->
-                tileSpecRepository.reconcileRestore(restoreData, autoAdded)
-                autoAddRepository.reconcileRestore(restoreData)
-            }
+            qsSettingsRestoredRepository.restoreData
+                .flatMapConcat { data ->
+                    autoAddRepository.autoAddedTiles(data.userId).take(1).map { tiles ->
+                        data to tiles
+                    }
+                }
+                .collect { (restoreData, autoAdded) ->
+                    restoreProcessors.forEach {
+                        it.preProcessRestore(restoreData)
+                        qsPipelineLogger.logRestoreProcessorApplied(
+                            it::class.simpleName,
+                            QSPipelineLogger.RestorePreprocessorStep.PREPROCESSING,
+                        )
+                    }
+                    tileSpecRepository.reconcileRestore(restoreData, autoAdded)
+                    autoAddRepository.reconcileRestore(restoreData)
+                    restoreProcessors.forEach {
+                        it.postProcessRestore(restoreData)
+                        qsPipelineLogger.logRestoreProcessorApplied(
+                            it::class.simpleName,
+                            QSPipelineLogger.RestorePreprocessorStep.POSTPROCESSING,
+                        )
+                    }
+                }
         }
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/model/AutoAddSignal.kt b/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/model/AutoAddSignal.kt
index ed7b8bd..8263680 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/model/AutoAddSignal.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/pipeline/domain/model/AutoAddSignal.kt
@@ -34,4 +34,9 @@
     data class Remove(
         override val spec: TileSpec,
     ) : AutoAddSignal
+
+    /** Signal for remove the auto-add marker from the tile, but not remove the tile */
+    data class RemoveTracking(
+        override val spec: TileSpec,
+    ) : AutoAddSignal
 }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/pipeline/shared/logging/QSPipelineLogger.kt b/packages/SystemUI/src/com/android/systemui/qs/pipeline/shared/logging/QSPipelineLogger.kt
index bca86c9..7d2c6c8 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/pipeline/shared/logging/QSPipelineLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/pipeline/shared/logging/QSPipelineLogger.kt
@@ -209,6 +209,18 @@
         )
     }
 
+    fun logTileUnmarked(userId: Int, spec: TileSpec) {
+        tileAutoAddLogBuffer.log(
+            AUTO_ADD_TAG,
+            LogLevel.DEBUG,
+            {
+                int1 = userId
+                str1 = spec.toString()
+            },
+            { "Tile $str1 unmarked as auto-added for user $int1" }
+        )
+    }
+
     fun logSettingsRestored(restoreData: RestoreData) {
         restoreLogBuffer.log(
             RESTORE_TAG,
@@ -226,6 +238,21 @@
         )
     }
 
+    fun logRestoreProcessorApplied(
+        restoreProcessorClassName: String?,
+        step: RestorePreprocessorStep,
+    ) {
+        restoreLogBuffer.log(
+            RESTORE_TAG,
+            LogLevel.DEBUG,
+            {
+                str1 = restoreProcessorClassName
+                str2 = step.name
+            },
+            { "Restore $str2 processed by $str1" }
+        )
+    }
+
     /** Reasons for destroying an existing tile. */
     enum class TileDestroyedReason(val readable: String) {
         TILE_REMOVED("Tile removed from  current set"),
@@ -234,4 +261,9 @@
         EXISTING_TILE_NOT_AVAILABLE("Existing tile not available"),
         TILE_NOT_PRESENT_IN_NEW_USER("Tile not present in new user"),
     }
+
+    enum class RestorePreprocessorStep {
+        PREPROCESSING,
+        POSTPROCESSING
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSFactoryImpl.java b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSFactoryImpl.java
index 38bbce4..17e6375 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSFactoryImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/tileimpl/QSFactoryImpl.java
@@ -19,6 +19,7 @@
 
 import androidx.annotation.Nullable;
 
+import com.android.systemui.accessibility.qs.QSAccessibilityModule;
 import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.plugins.qs.QSFactory;
 import com.android.systemui.plugins.qs.QSTile;
@@ -41,7 +42,7 @@
  * com.android.systemui.qs.tiles.DreamTile#TILE_SPEC})
  *
  * After, create or find an existing Module class to house the tile's binding method (e.g. {@link
- * com.android.systemui.accessibility.AccessibilityModule}). If creating a new module, add your
+ * QSAccessibilityModule}). If creating a new module, add your
  * module to the SystemUI dagger graph by including it in an appropriate module.
  */
 @SysUISingleton
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/RecordIssueTile.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/RecordIssueTile.kt
index a4088f8..0434b2d 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/RecordIssueTile.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/RecordIssueTile.kt
@@ -16,6 +16,7 @@
 
 package com.android.systemui.qs.tiles
 
+import android.app.AlertDialog
 import android.content.Intent
 import android.os.Handler
 import android.os.Looper
@@ -24,8 +25,11 @@
 import android.view.View
 import android.widget.Switch
 import androidx.annotation.VisibleForTesting
+import com.android.internal.jank.InteractionJankMonitor.CUJ_SHADE_DIALOG_OPEN
 import com.android.internal.logging.MetricsLogger
 import com.android.systemui.Flags.recordIssueQsTile
+import com.android.systemui.animation.DialogCuj
+import com.android.systemui.animation.DialogLaunchAnimator
 import com.android.systemui.dagger.qualifiers.Background
 import com.android.systemui.dagger.qualifiers.Main
 import com.android.systemui.plugins.ActivityStarter
@@ -36,7 +40,11 @@
 import com.android.systemui.qs.QsEventLogger
 import com.android.systemui.qs.logging.QSLogger
 import com.android.systemui.qs.tileimpl.QSTileImpl
+import com.android.systemui.recordissue.RecordIssueDialogDelegate
 import com.android.systemui.res.R
+import com.android.systemui.statusbar.phone.KeyguardDismissUtil
+import com.android.systemui.statusbar.phone.SystemUIDialog
+import com.android.systemui.statusbar.policy.KeyguardStateController
 import javax.inject.Inject
 
 class RecordIssueTile
@@ -50,7 +58,11 @@
     metricsLogger: MetricsLogger,
     statusBarStateController: StatusBarStateController,
     activityStarter: ActivityStarter,
-    qsLogger: QSLogger
+    qsLogger: QSLogger,
+    private val keyguardDismissUtil: KeyguardDismissUtil,
+    private val keyguardStateController: KeyguardStateController,
+    private val dialogLaunchAnimator: DialogLaunchAnimator,
+    private val sysuiDialogFactory: SystemUIDialog.Factory,
 ) :
     QSTileImpl<QSTile.BooleanState>(
         host,
@@ -76,11 +88,41 @@
             handlesLongClick = false
         }
 
-    override fun handleClick(view: View?) {
-        isRecording = !isRecording
+    @VisibleForTesting
+    public override fun handleClick(view: View?) {
+        if (isRecording) {
+            isRecording = false
+        } else {
+            mUiHandler.post { showPrompt(view) }
+        }
         refreshState()
     }
 
+    private fun showPrompt(view: View?) {
+        val dialog: AlertDialog =
+            RecordIssueDialogDelegate(sysuiDialogFactory) {
+                    isRecording = true
+                    refreshState()
+                }
+                .createDialog()
+        val dismissAction =
+            ActivityStarter.OnDismissAction {
+                // We animate from the touched view only if we are not on the keyguard, given
+                // that if we are we will dismiss it which will also collapse the shade.
+                if (view != null && !keyguardStateController.isShowing) {
+                    dialogLaunchAnimator.showFromView(
+                        dialog,
+                        view,
+                        DialogCuj(CUJ_SHADE_DIALOG_OPEN, TILE_SPEC)
+                    )
+                } else {
+                    dialog.show()
+                }
+                false
+            }
+        keyguardDismissUtil.executeWhenUnlocked(dismissAction, false, true)
+    }
+
     override fun getLongClickIntent(): Intent? = null
 
     @VisibleForTesting
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/base/interactor/QSTileUserActionInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/base/interactor/QSTileUserActionInteractor.kt
index 09d7a1f..17b78eb 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/base/interactor/QSTileUserActionInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/base/interactor/QSTileUserActionInteractor.kt
@@ -24,7 +24,7 @@
      * [QSTileInput.data]. It's guaranteed that [QSTileInput.userId] is the same as the id passed to
      * [QSTileDataInteractor] to get [QSTileInput.data].
      *
-     * It's safe to run long running computations inside this function in this.
+     * It's safe to run long running computations inside this function.
      */
     @WorkerThread suspend fun handleInput(input: QSTileInput<DATA_TYPE>)
 }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/di/QSTilesModule.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/di/QSTilesModule.kt
index 4a34276..b325b4d 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/di/QSTilesModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/di/QSTilesModule.kt
@@ -16,6 +16,8 @@
 
 package com.android.systemui.qs.tiles.di
 
+import android.content.Context
+import android.content.res.Resources.Theme
 import com.android.systemui.qs.external.CustomTileStatePersister
 import com.android.systemui.qs.external.CustomTileStatePersisterImpl
 import com.android.systemui.qs.tiles.base.actions.QSTileIntentUserInputHandler
@@ -27,6 +29,7 @@
 import com.android.systemui.qs.tiles.viewmodel.QSTileViewModel
 import dagger.Binds
 import dagger.Module
+import dagger.Provides
 import dagger.multibindings.Multibinds
 
 /** Module listing subcomponents */
@@ -57,4 +60,9 @@
 
     @Binds
     fun bindCustomTileStatePersister(impl: CustomTileStatePersisterImpl): CustomTileStatePersister
+
+    companion object {
+
+        @Provides fun provideTilesTheme(context: Context): Theme = context.theme
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/airplane/domain/AirplaneModeMapper.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/airplane/domain/AirplaneModeMapper.kt
index cfb5442..9b8dba1 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/airplane/domain/AirplaneModeMapper.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/airplane/domain/AirplaneModeMapper.kt
@@ -17,6 +17,7 @@
 package com.android.systemui.qs.tiles.impl.airplane.domain
 
 import android.content.res.Resources
+import android.content.res.Resources.Theme
 import com.android.systemui.common.shared.model.Icon
 import com.android.systemui.dagger.qualifiers.Main
 import com.android.systemui.qs.tiles.base.interactor.QSTileDataToStateMapper
@@ -27,18 +28,25 @@
 import javax.inject.Inject
 
 /** Maps [AirplaneModeTileModel] to [QSTileState]. */
-class AirplaneModeMapper @Inject constructor(@Main private val resources: Resources) :
-    QSTileDataToStateMapper<AirplaneModeTileModel> {
+class AirplaneModeMapper
+@Inject
+constructor(
+    @Main private val resources: Resources,
+    val theme: Theme,
+) : QSTileDataToStateMapper<AirplaneModeTileModel> {
 
     override fun map(config: QSTileConfig, data: AirplaneModeTileModel): QSTileState =
-        QSTileState.build(resources, config.uiConfig) {
+        QSTileState.build(resources, theme, config.uiConfig) {
             val icon =
-                Icon.Resource(
-                    if (data.isEnabled) {
-                        R.drawable.qs_airplane_icon_on
-                    } else {
-                        R.drawable.qs_airplane_icon_off
-                    },
+                Icon.Loaded(
+                    resources.getDrawable(
+                        if (data.isEnabled) {
+                            R.drawable.qs_airplane_icon_on
+                        } else {
+                            R.drawable.qs_airplane_icon_off
+                        },
+                        theme,
+                    ),
                     contentDescription = null
                 )
             this.icon = { icon }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/alarm/domain/AlarmTileMapper.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/alarm/domain/AlarmTileMapper.kt
index 6386577..e075e76 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/alarm/domain/AlarmTileMapper.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/alarm/domain/AlarmTileMapper.kt
@@ -17,6 +17,7 @@
 package com.android.systemui.qs.tiles.impl.alarm.domain
 
 import android.content.res.Resources
+import android.content.res.Resources.Theme
 import com.android.systemui.dagger.qualifiers.Main
 import com.android.systemui.qs.tiles.base.interactor.QSTileDataToStateMapper
 import com.android.systemui.qs.tiles.impl.alarm.domain.model.AlarmTileModel
@@ -30,14 +31,18 @@
 import javax.inject.Inject
 
 /** Maps [AlarmTileModel] to [QSTileState]. */
-class AlarmTileMapper @Inject constructor(@Main private val resources: Resources) :
-    QSTileDataToStateMapper<AlarmTileModel> {
+class AlarmTileMapper
+@Inject
+constructor(
+    @Main private val resources: Resources,
+    private val theme: Theme,
+) : QSTileDataToStateMapper<AlarmTileModel> {
     companion object {
         val formatter12Hour: DateTimeFormatter = DateTimeFormatter.ofPattern("E hh:mm a")
         val formatter24Hour: DateTimeFormatter = DateTimeFormatter.ofPattern("E HH:mm")
     }
     override fun map(config: QSTileConfig, data: AlarmTileModel): QSTileState =
-        QSTileState.build(resources, config.uiConfig) {
+        QSTileState.build(resources, theme, config.uiConfig) {
             when (data) {
                 is AlarmTileModel.NextAlarmSet -> {
                     activationState = QSTileState.ActivationState.ACTIVE
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/colorcorrection/domain/ColorCorrectionTileMapper.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/colorcorrection/domain/ColorCorrectionTileMapper.kt
new file mode 100644
index 0000000..1efbfd7
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/colorcorrection/domain/ColorCorrectionTileMapper.kt
@@ -0,0 +1,51 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.tiles.impl.colorcorrection.domain
+
+import android.content.res.Resources
+import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.qs.tiles.base.interactor.QSTileDataToStateMapper
+import com.android.systemui.qs.tiles.impl.colorcorrection.domain.model.ColorCorrectionTileModel
+import com.android.systemui.qs.tiles.viewmodel.QSTileConfig
+import com.android.systemui.qs.tiles.viewmodel.QSTileState
+import com.android.systemui.res.R
+import javax.inject.Inject
+
+/** Maps [ColorCorrectionTileModel] to [QSTileState]. */
+class ColorCorrectionTileMapper
+@Inject
+constructor(
+    @Main private val resources: Resources,
+    private val theme: Resources.Theme,
+) : QSTileDataToStateMapper<ColorCorrectionTileModel> {
+
+    override fun map(config: QSTileConfig, data: ColorCorrectionTileModel): QSTileState =
+        QSTileState.build(resources, theme, config.uiConfig) {
+            val subtitleArray = resources.getStringArray(R.array.tile_states_color_correction)
+
+            if (data.isEnabled) {
+                activationState = QSTileState.ActivationState.ACTIVE
+                secondaryLabel = subtitleArray[2]
+            } else {
+                activationState = QSTileState.ActivationState.INACTIVE
+                secondaryLabel = subtitleArray[1]
+            }
+            contentDescription = label
+            supportedActions =
+                setOf(QSTileState.UserAction.CLICK, QSTileState.UserAction.LONG_CLICK)
+        }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/colorcorrection/domain/interactor/ColorCorrectionTileDataInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/colorcorrection/domain/interactor/ColorCorrectionTileDataInteractor.kt
new file mode 100644
index 0000000..cd33d45
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/colorcorrection/domain/interactor/ColorCorrectionTileDataInteractor.kt
@@ -0,0 +1,43 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.tiles.impl.colorcorrection.domain.interactor
+
+import android.os.UserHandle
+import com.android.systemui.accessibility.data.repository.ColorCorrectionRepository
+import com.android.systemui.qs.tiles.base.interactor.DataUpdateTrigger
+import com.android.systemui.qs.tiles.base.interactor.QSTileDataInteractor
+import com.android.systemui.qs.tiles.impl.colorcorrection.domain.model.ColorCorrectionTileModel
+import javax.inject.Inject
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.flowOf
+import kotlinx.coroutines.flow.map
+
+/** Observes color correction state changes providing the [ColorCorrectionTileModel]. */
+class ColorCorrectionTileDataInteractor
+@Inject
+constructor(
+    private val colorCorrectionRepository: ColorCorrectionRepository,
+) : QSTileDataInteractor<ColorCorrectionTileModel> {
+
+    override fun tileData(
+        user: UserHandle,
+        triggers: Flow<DataUpdateTrigger>
+    ): Flow<ColorCorrectionTileModel> {
+        return colorCorrectionRepository.isEnabled(user).map { ColorCorrectionTileModel(it) }
+    }
+    override fun availability(user: UserHandle): Flow<Boolean> = flowOf(true)
+}
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/colorcorrection/domain/interactor/ColorCorrectionUserActionInteractor.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/colorcorrection/domain/interactor/ColorCorrectionUserActionInteractor.kt
new file mode 100644
index 0000000..d183802
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/colorcorrection/domain/interactor/ColorCorrectionUserActionInteractor.kt
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.tiles.impl.colorcorrection.domain.interactor
+
+import android.content.Intent
+import android.provider.Settings
+import com.android.systemui.accessibility.data.repository.ColorCorrectionRepository
+import com.android.systemui.qs.tiles.base.actions.QSTileIntentUserInputHandler
+import com.android.systemui.qs.tiles.base.interactor.QSTileInput
+import com.android.systemui.qs.tiles.base.interactor.QSTileUserActionInteractor
+import com.android.systemui.qs.tiles.impl.colorcorrection.domain.model.ColorCorrectionTileModel
+import com.android.systemui.qs.tiles.viewmodel.QSTileUserAction
+import javax.inject.Inject
+
+/** Handles color correction tile clicks. */
+class ColorCorrectionUserActionInteractor
+@Inject
+constructor(
+    private val colorCorrectionRepository: ColorCorrectionRepository,
+    private val qsTileIntentUserActionHandler: QSTileIntentUserInputHandler,
+) : QSTileUserActionInteractor<ColorCorrectionTileModel> {
+
+    override suspend fun handleInput(input: QSTileInput<ColorCorrectionTileModel>): Unit =
+        with(input) {
+            when (action) {
+                is QSTileUserAction.Click -> {
+                    colorCorrectionRepository.setIsEnabled(
+                        !data.isEnabled,
+                        user,
+                    )
+                }
+                is QSTileUserAction.LongClick -> {
+                    qsTileIntentUserActionHandler.handle(
+                        action.view,
+                        Intent(Settings.ACTION_COLOR_CORRECTION_SETTINGS)
+                    )
+                }
+            }
+        }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/SideFpsLottieViewWrapper.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/colorcorrection/domain/model/ColorCorrectionTileModel.kt
similarity index 66%
copy from packages/SystemUI/src/com/android/systemui/biometrics/SideFpsLottieViewWrapper.kt
copy to packages/SystemUI/src/com/android/systemui/qs/tiles/impl/colorcorrection/domain/model/ColorCorrectionTileModel.kt
index e98f6db..66487e1 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/SideFpsLottieViewWrapper.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/colorcorrection/domain/model/ColorCorrectionTileModel.kt
@@ -13,12 +13,12 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package com.android.systemui.biometrics
 
-import android.content.Context
-import android.util.AttributeSet
-import com.android.systemui.util.wrapper.LottieViewWrapper
+package com.android.systemui.qs.tiles.impl.colorcorrection.domain.model
 
-class SideFpsLottieViewWrapper
-@JvmOverloads
-constructor(context: Context, attrs: AttributeSet? = null) : LottieViewWrapper(context, attrs)
+/**
+ * Color correction tile model.
+ *
+ * @param isEnabled is true when the color correction is enabled;
+ */
+@JvmInline value class ColorCorrectionTileModel(val isEnabled: Boolean)
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/flashlight/domain/FlashlightMapper.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/flashlight/domain/FlashlightMapper.kt
index 881a6bd..1b3b584 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/flashlight/domain/FlashlightMapper.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/flashlight/domain/FlashlightMapper.kt
@@ -17,6 +17,7 @@
 package com.android.systemui.qs.tiles.impl.flashlight.domain
 
 import android.content.res.Resources
+import android.content.res.Resources.Theme
 import com.android.systemui.common.shared.model.Icon
 import com.android.systemui.dagger.qualifiers.Main
 import com.android.systemui.qs.tiles.base.interactor.QSTileDataToStateMapper
@@ -27,18 +28,25 @@
 import javax.inject.Inject
 
 /** Maps [FlashlightTileModel] to [QSTileState]. */
-class FlashlightMapper @Inject constructor(@Main private val resources: Resources) :
-    QSTileDataToStateMapper<FlashlightTileModel> {
+class FlashlightMapper
+@Inject
+constructor(
+    @Main private val resources: Resources,
+    private val theme: Theme,
+) : QSTileDataToStateMapper<FlashlightTileModel> {
 
     override fun map(config: QSTileConfig, data: FlashlightTileModel): QSTileState =
-        QSTileState.build(resources, config.uiConfig) {
+        QSTileState.build(resources, theme, config.uiConfig) {
             val icon =
-                Icon.Resource(
-                    if (data.isEnabled) {
-                        R.drawable.qs_flashlight_icon_on
-                    } else {
-                        R.drawable.qs_flashlight_icon_off
-                    },
+                Icon.Loaded(
+                    resources.getDrawable(
+                        if (data.isEnabled) {
+                            R.drawable.qs_flashlight_icon_on
+                        } else {
+                            R.drawable.qs_flashlight_icon_off
+                        },
+                        theme,
+                    ),
                     contentDescription = null
                 )
             this.icon = { icon }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/location/domain/LocationTileMapper.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/location/domain/LocationTileMapper.kt
index 7e7034d..fe5445d 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/location/domain/LocationTileMapper.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/location/domain/LocationTileMapper.kt
@@ -17,6 +17,7 @@
 package com.android.systemui.qs.tiles.impl.location.domain
 
 import android.content.res.Resources
+import android.content.res.Resources.Theme
 import com.android.systemui.common.shared.model.Icon
 import com.android.systemui.dagger.qualifiers.Main
 import com.android.systemui.qs.tiles.base.interactor.QSTileDataToStateMapper
@@ -27,18 +28,25 @@
 import javax.inject.Inject
 
 /** Maps [LocationTileModel] to [QSTileState]. */
-class LocationTileMapper @Inject constructor(@Main private val resources: Resources) :
-    QSTileDataToStateMapper<LocationTileModel> {
+class LocationTileMapper
+@Inject
+constructor(
+    @Main private val resources: Resources,
+    private val theme: Theme,
+) : QSTileDataToStateMapper<LocationTileModel> {
 
     override fun map(config: QSTileConfig, data: LocationTileModel): QSTileState =
-        QSTileState.build(resources, config.uiConfig) {
+        QSTileState.build(resources, theme, config.uiConfig) {
             val icon =
-                Icon.Resource(
-                    if (data.isEnabled) {
-                        R.drawable.qs_location_icon_on
-                    } else {
-                        R.drawable.qs_location_icon_off
-                    },
+                Icon.Loaded(
+                    resources.getDrawable(
+                        if (data.isEnabled) {
+                            R.drawable.qs_location_icon_on
+                        } else {
+                            R.drawable.qs_location_icon_off
+                        },
+                        theme,
+                    ),
                     contentDescription = null
                 )
             this.icon = { icon }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/saver/domain/DataSaverTileMapper.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/saver/domain/DataSaverTileMapper.kt
index 25b0913..df25600 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/saver/domain/DataSaverTileMapper.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/saver/domain/DataSaverTileMapper.kt
@@ -27,20 +27,28 @@
 import javax.inject.Inject
 
 /** Maps [DataSaverTileModel] to [QSTileState]. */
-class DataSaverTileMapper @Inject constructor(@Main private val resources: Resources) :
-    QSTileDataToStateMapper<DataSaverTileModel> {
+class DataSaverTileMapper
+@Inject
+constructor(
+    @Main private val resources: Resources,
+    private val theme: Resources.Theme,
+) : QSTileDataToStateMapper<DataSaverTileModel> {
     override fun map(config: QSTileConfig, data: DataSaverTileModel): QSTileState =
-        QSTileState.build(resources, config.uiConfig) {
+        QSTileState.build(resources, theme, config.uiConfig) {
             with(data) {
+                val iconRes: Int
                 if (isEnabled) {
                     activationState = QSTileState.ActivationState.ACTIVE
-                    icon = { Icon.Resource(R.drawable.qs_data_saver_icon_on, null) }
+                    iconRes = R.drawable.qs_data_saver_icon_on
                     secondaryLabel = resources.getStringArray(R.array.tile_states_saver)[2]
                 } else {
                     activationState = QSTileState.ActivationState.INACTIVE
-                    icon = { Icon.Resource(R.drawable.qs_data_saver_icon_off, null) }
+                    iconRes = R.drawable.qs_data_saver_icon_off
                     secondaryLabel = resources.getStringArray(R.array.tile_states_saver)[1]
                 }
+                val loadedIcon =
+                    Icon.Loaded(resources.getDrawable(iconRes, theme), contentDescription = null)
+                icon = { loadedIcon }
                 contentDescription = label
                 supportedActions =
                     setOf(QSTileState.UserAction.CLICK, QSTileState.UserAction.LONG_CLICK)
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/uimodenight/domain/UiModeNightTileMapper.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/uimodenight/domain/UiModeNightTileMapper.kt
index 3f30c75..ffef2b6 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/uimodenight/domain/UiModeNightTileMapper.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/impl/uimodenight/domain/UiModeNightTileMapper.kt
@@ -18,6 +18,7 @@
 
 import android.app.UiModeManager
 import android.content.res.Resources
+import android.content.res.Resources.Theme
 import android.text.TextUtils
 import com.android.systemui.common.shared.model.Icon
 import com.android.systemui.dagger.qualifiers.Main
@@ -31,15 +32,19 @@
 import javax.inject.Inject
 
 /** Maps [UiModeNightTileModel] to [QSTileState]. */
-class UiModeNightTileMapper @Inject constructor(@Main private val resources: Resources) :
-    QSTileDataToStateMapper<UiModeNightTileModel> {
+class UiModeNightTileMapper
+@Inject
+constructor(
+    @Main private val resources: Resources,
+    private val theme: Theme,
+) : QSTileDataToStateMapper<UiModeNightTileModel> {
     companion object {
         val formatter12Hour: DateTimeFormatter = DateTimeFormatter.ofPattern("hh:mm a")
         val formatter24Hour: DateTimeFormatter = DateTimeFormatter.ofPattern("HH:mm")
     }
     override fun map(config: QSTileConfig, data: UiModeNightTileModel): QSTileState =
         with(data) {
-            QSTileState.build(resources, config.uiConfig) {
+            QSTileState.build(resources, theme, config.uiConfig) {
                 var shouldSetSecondaryLabel = false
 
                 if (isPowerSave) {
@@ -116,8 +121,9 @@
                     if (activationState == QSTileState.ActivationState.ACTIVE)
                         R.drawable.qs_light_dark_theme_icon_on
                     else R.drawable.qs_light_dark_theme_icon_off
-                val iconResource = Icon.Resource(iconRes, null)
-                icon = { iconResource }
+                val loadedIcon =
+                    Icon.Loaded(resources.getDrawable(iconRes, theme), contentDescription = null)
+                icon = { loadedIcon }
 
                 supportedActions =
                     if (activationState == QSTileState.ActivationState.UNAVAILABLE)
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileState.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileState.kt
index 23e0cb6..be1b740 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileState.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/viewmodel/QSTileState.kt
@@ -17,6 +17,7 @@
 package com.android.systemui.qs.tiles.viewmodel
 
 import android.content.res.Resources
+import android.content.res.Resources.Theme
 import android.service.quicksettings.Tile
 import android.view.View
 import android.widget.Switch
@@ -47,14 +48,17 @@
 
         fun build(
             resources: Resources,
+            theme: Theme,
             config: QSTileUIConfig,
             build: Builder.() -> Unit
-        ): QSTileState =
-            build(
-                { Icon.Resource(config.iconRes, null) },
+        ): QSTileState {
+            val iconDrawable = resources.getDrawable(config.iconRes, theme)
+            return build(
+                { Icon.Loaded(iconDrawable, null) },
                 resources.getString(config.labelRes),
                 build,
             )
+        }
 
         fun build(icon: () -> Icon, label: CharSequence, build: Builder.() -> Unit): QSTileState =
             Builder(icon, label).apply(build).build()
diff --git a/packages/SystemUI/src/com/android/systemui/recordissue/RecordIssueDialogDelegate.kt b/packages/SystemUI/src/com/android/systemui/recordissue/RecordIssueDialogDelegate.kt
new file mode 100644
index 0000000..8221c63
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/recordissue/RecordIssueDialogDelegate.kt
@@ -0,0 +1,88 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.recordissue
+
+import android.annotation.SuppressLint
+import android.content.Context
+import android.content.res.ColorStateList
+import android.graphics.Color
+import android.os.Bundle
+import android.view.Gravity
+import android.view.LayoutInflater
+import android.view.WindowManager
+import android.widget.Button
+import android.widget.PopupMenu
+import android.widget.Switch
+import com.android.systemui.res.R
+import com.android.systemui.statusbar.phone.SystemUIDialog
+
+class RecordIssueDialogDelegate(
+    private val factory: SystemUIDialog.Factory,
+    private val onStarted: Runnable
+) : SystemUIDialog.Delegate {
+
+    @SuppressLint("UseSwitchCompatOrMaterialCode") private lateinit var screenRecordSwitch: Switch
+    private lateinit var issueTypeButton: Button
+
+    override fun beforeCreate(dialog: SystemUIDialog, savedInstanceState: Bundle?) {
+        dialog.apply {
+            setView(LayoutInflater.from(context).inflate(R.layout.record_issue_dialog, null))
+            setTitle(context.getString(R.string.qs_record_issue_label))
+            setIcon(R.drawable.qs_record_issue_icon_off)
+            setNegativeButton(R.string.cancel) { _, _ -> dismiss() }
+            setPositiveButton(R.string.qs_record_issue_start) { _, _ ->
+                onStarted.run()
+                dismiss()
+            }
+        }
+    }
+
+    override fun createDialog(): SystemUIDialog = factory.create(this)
+
+    override fun onCreate(dialog: SystemUIDialog, savedInstanceState: Bundle?) {
+        dialog.apply {
+            window?.addPrivateFlags(WindowManager.LayoutParams.SYSTEM_FLAG_SHOW_FOR_ALL_USERS)
+            window?.setGravity(Gravity.CENTER)
+
+            screenRecordSwitch = requireViewById(R.id.screenrecord_switch)
+            issueTypeButton = requireViewById(R.id.issue_type_button)
+            issueTypeButton.setOnClickListener { onIssueTypeClicked(context) }
+        }
+    }
+
+    private fun onIssueTypeClicked(context: Context) {
+        val selectedCategory = issueTypeButton.text.toString()
+        val popupMenu = PopupMenu(context, issueTypeButton)
+
+        context.resources.getStringArray(R.array.qs_record_issue_types).forEachIndexed { i, cat ->
+            popupMenu.menu.add(0, 0, i, cat).apply {
+                setIcon(R.drawable.arrow_pointing_down)
+                if (selectedCategory != cat) {
+                    iconTintList = ColorStateList.valueOf(Color.TRANSPARENT)
+                }
+            }
+        }
+        popupMenu.apply {
+            setOnMenuItemClickListener {
+                issueTypeButton.text = it.title
+                true
+            }
+            setForceShowIcon(true)
+            show()
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/ShadeExpansionStateManager.kt b/packages/SystemUI/src/com/android/systemui/shade/ShadeExpansionStateManager.kt
index 8a93ef6..d3459b1 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/ShadeExpansionStateManager.kt
+++ b/packages/SystemUI/src/com/android/systemui/shade/ShadeExpansionStateManager.kt
@@ -32,6 +32,7 @@
  * TODO(b/200063118): Make this class the one source of truth for the state of panel expansion.
  */
 @SysUISingleton
+@Deprecated("Use ShadeInteractor instead")
 class ShadeExpansionStateManager @Inject constructor() {
 
     private val expansionListeners = CopyOnWriteArrayList<ShadeExpansionListener>()
@@ -49,6 +50,7 @@
      *
      * @see #addExpansionListener
      */
+    @Deprecated("Use ShadeInteractor instead")
     fun addExpansionListener(listener: ShadeExpansionListener): ShadeExpansionChangeEvent {
         expansionListeners.add(listener)
         return ShadeExpansionChangeEvent(fraction, expanded, tracking, dragDownPxAmount)
@@ -60,6 +62,7 @@
     }
 
     /** Adds a listener that will be notified when the panel state has changed. */
+    @Deprecated("Use ShadeInteractor instead")
     fun addStateListener(listener: ShadeStateListener) {
         stateListeners.add(listener)
     }
diff --git a/packages/SystemUI/src/com/android/systemui/smartspace/dagger/SmartspaceModule.kt b/packages/SystemUI/src/com/android/systemui/smartspace/dagger/SmartspaceModule.kt
index c59ef26..d26fded1 100644
--- a/packages/SystemUI/src/com/android/systemui/smartspace/dagger/SmartspaceModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/smartspace/dagger/SmartspaceModule.kt
@@ -59,6 +59,11 @@
          * The BcSmartspaceDataPlugin for the standalone weather.
          */
         const val WEATHER_SMARTSPACE_DATA_PLUGIN = "weather_smartspace_data_plugin"
+
+        /**
+         * The BcSmartspaceDataProvider for the glanceable hub.
+         */
+        const val GLANCEABLE_HUB_SMARTSPACE_DATA_PLUGIN = "glanceable_hub_smartspace_data_plugin"
     }
 
     @BindsOptionalOf
@@ -78,4 +83,8 @@
     abstract fun bindSmartspacePrecondition(
         lockscreenPrecondition: LockscreenPrecondition?
     ): SmartspacePrecondition?
+
+    @BindsOptionalOf
+    @Named(GLANCEABLE_HUB_SMARTSPACE_DATA_PLUGIN)
+    abstract fun optionalBcSmartspaceDataPlugin(): BcSmartspaceDataPlugin?
 }
diff --git a/packages/SystemUI/src/com/android/systemui/smartspace/data/repository/SmartspaceRepository.kt b/packages/SystemUI/src/com/android/systemui/smartspace/data/repository/SmartspaceRepository.kt
index 2fc0ec2..095d30e 100644
--- a/packages/SystemUI/src/com/android/systemui/smartspace/data/repository/SmartspaceRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/smartspace/data/repository/SmartspaceRepository.kt
@@ -19,9 +19,9 @@
 import android.app.smartspace.SmartspaceTarget
 import android.os.Parcelable
 import android.widget.RemoteViews
+import com.android.systemui.communal.smartspace.CommunalSmartspaceController
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.plugins.BcSmartspaceDataPlugin
-import com.android.systemui.statusbar.lockscreen.LockscreenSmartspaceController
 import javax.inject.Inject
 import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.MutableStateFlow
@@ -32,37 +32,37 @@
     /** Whether [RemoteViews] are passed through smartspace targets. */
     val isSmartspaceRemoteViewsEnabled: Boolean
 
-    /** Smartspace targets for the lockscreen surface. */
-    val lockscreenSmartspaceTargets: Flow<List<SmartspaceTarget>>
+    /** Smartspace targets for the communal surface. */
+    val communalSmartspaceTargets: Flow<List<SmartspaceTarget>>
 }
 
 @SysUISingleton
 class SmartspaceRepositoryImpl
 @Inject
 constructor(
-    private val lockscreenSmartspaceController: LockscreenSmartspaceController,
+    private val communalSmartspaceController: CommunalSmartspaceController,
 ) : SmartspaceRepository, BcSmartspaceDataPlugin.SmartspaceTargetListener {
 
     override val isSmartspaceRemoteViewsEnabled: Boolean
         get() = android.app.smartspace.flags.Flags.remoteViews()
 
-    private val _lockscreenSmartspaceTargets: MutableStateFlow<List<SmartspaceTarget>> =
+    private val _communalSmartspaceTargets: MutableStateFlow<List<SmartspaceTarget>> =
         MutableStateFlow(emptyList())
-    override val lockscreenSmartspaceTargets: Flow<List<SmartspaceTarget>> =
-        _lockscreenSmartspaceTargets
+    override val communalSmartspaceTargets: Flow<List<SmartspaceTarget>> =
+        _communalSmartspaceTargets
             .onStart {
-                lockscreenSmartspaceController.addListener(listener = this@SmartspaceRepositoryImpl)
+                communalSmartspaceController.addListener(listener = this@SmartspaceRepositoryImpl)
             }
             .onCompletion {
-                lockscreenSmartspaceController.removeListener(
+                communalSmartspaceController.removeListener(
                     listener = this@SmartspaceRepositoryImpl
                 )
             }
 
     override fun onSmartspaceTargetsUpdated(targetsNullable: MutableList<out Parcelable>?) {
         targetsNullable?.let { targets ->
-            _lockscreenSmartspaceTargets.value = targets.filterIsInstance<SmartspaceTarget>()
+            _communalSmartspaceTargets.value = targets.filterIsInstance<SmartspaceTarget>()
         }
-            ?: run { _lockscreenSmartspaceTargets.value = emptyList() }
+            ?: run { _communalSmartspaceTargets.value = emptyList() }
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewbinder/NotificationIconContainerViewBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewbinder/NotificationIconContainerViewBinder.kt
index 92391e7..e1e30e1 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewbinder/NotificationIconContainerViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewbinder/NotificationIconContainerViewBinder.kt
@@ -17,7 +17,9 @@
 
 import android.graphics.Color
 import android.graphics.Rect
+import android.util.Log
 import android.view.View
+import android.view.ViewGroup
 import android.widget.FrameLayout
 import androidx.annotation.ColorInt
 import androidx.collection.ArrayMap
@@ -220,7 +222,7 @@
         notifyBindingFailures: (Collection<String>) -> Unit,
         viewStore: IconViewStore,
         bindIcon: suspend (iconKey: String, view: StatusBarIconView) -> Unit = { _, _ -> },
-    ): Unit = coroutineScope {
+    ) {
         val iconSizeFlow: Flow<Int> =
             configuration.getDimensionPixelSize(
                 com.android.internal.R.dimen.status_bar_icon_size_sp,
@@ -235,6 +237,21 @@
                 ->
                 FrameLayout.LayoutParams(iconSize + 2 * iconHPadding, statusBarHeight)
             }
+        try {
+            bindIcons(view, layoutParams, notifyBindingFailures, viewStore, bindIcon)
+        } finally {
+            // Detach everything so that child SBIVs don't hold onto a reference to the container.
+            view.detachAllIcons()
+        }
+    }
+
+    private suspend fun Flow<NotificationIconsViewData>.bindIcons(
+        view: NotificationIconContainer,
+        layoutParams: Flow<FrameLayout.LayoutParams>,
+        notifyBindingFailures: (Collection<String>) -> Unit,
+        viewStore: IconViewStore,
+        bindIcon: suspend (iconKey: String, view: StatusBarIconView) -> Unit,
+    ): Unit = coroutineScope {
         val failedBindings = mutableSetOf<String>()
         val boundViewsByNotifKey = ArrayMap<String, Pair<StatusBarIconView, Job>>()
         var prevIcons = NotificationIconsViewData()
@@ -266,9 +283,17 @@
                         continue
                     }
                     failedBindings.remove(notifKey)
-                    // The view might still be transiently added if it was just removed and added
-                    // again
-                    view.removeTransientView(sbiv)
+                    (sbiv.parent as? ViewGroup)?.run {
+                        if (this !== view) {
+                            Log.wtf(TAG, "StatusBarIconView($notifKey) has an unexpected parent")
+                        }
+                        // If the container was re-inflated and re-bound, then SBIVs might still be
+                        // attached to the prior view.
+                        removeView(sbiv)
+                        // The view might still be transiently added if it was just removed and
+                        // added again.
+                        removeTransientView(sbiv)
+                    }
                     view.addView(sbiv, idx)
                     boundViewsByNotifKey.remove(notifKey)?.second?.cancel()
                     boundViewsByNotifKey[notifKey] =
@@ -351,7 +376,8 @@
         fun iconView(key: String): StatusBarIconView?
     }
 
-    @ColorInt private val DEFAULT_AOD_ICON_COLOR = Color.WHITE
+    @ColorInt private const val DEFAULT_AOD_ICON_COLOR = Color.WHITE
+    private const val TAG =  "NotifIconContainerViewBinder"
 }
 
 /** [IconViewStore] for the [com.android.systemui.statusbar.NotificationShelf] */
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/domain/interactor/SharedNotificationContainerInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/domain/interactor/SharedNotificationContainerInteractor.kt
index adf6cca..625fdc1 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/domain/interactor/SharedNotificationContainerInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/domain/interactor/SharedNotificationContainerInteractor.kt
@@ -20,6 +20,8 @@
 import android.content.Context
 import com.android.systemui.common.ui.data.repository.ConfigurationRepository
 import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.deviceentry.domain.interactor.DeviceEntryUdfpsInteractor
+import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor
 import com.android.systemui.res.R
 import com.android.systemui.statusbar.policy.SplitShadeStateController
 import javax.inject.Inject
@@ -28,6 +30,7 @@
 import kotlinx.coroutines.flow.MutableStateFlow
 import kotlinx.coroutines.flow.asSharedFlow
 import kotlinx.coroutines.flow.asStateFlow
+import kotlinx.coroutines.flow.combine
 import kotlinx.coroutines.flow.distinctUntilChanged
 import kotlinx.coroutines.flow.map
 import kotlinx.coroutines.flow.onStart
@@ -39,7 +42,9 @@
 constructor(
     configurationRepository: ConfigurationRepository,
     private val context: Context,
-    private val splitShadeStateController: SplitShadeStateController
+    private val splitShadeStateController: SplitShadeStateController,
+    keyguardInteractor: KeyguardInteractor,
+    deviceEntryUdfpsInteractor: DeviceEntryUdfpsInteractor,
 ) {
 
     private val _topPosition = MutableStateFlow(0f)
@@ -75,6 +80,19 @@
             }
             .distinctUntilChanged()
 
+    /**
+     * The notification shelf can extend over the lock icon area if:
+     * * UDFPS supported. Ambient indication will always appear below
+     * * UDFPS not supported and ambient indication not visible, which will appear above lock icon
+     */
+    val useExtraShelfSpace: Flow<Boolean> =
+        combine(
+            keyguardInteractor.ambientIndicationVisible,
+            deviceEntryUdfpsInteractor.isUdfpsSupported,
+        ) { ambientIndicationVisible, isUdfpsSupported ->
+            isUdfpsSupported || !ambientIndicationVisible
+        }
+
     val isSplitShadeEnabled: Flow<Boolean> =
         configurationBasedDimensions
             .map { dimens: ConfigurationBasedDimensions -> dimens.useSplitShade }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/SharedNotificationContainerBinder.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/SharedNotificationContainerBinder.kt
index af56a3f..12927b8 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/SharedNotificationContainerBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/ui/viewbinder/SharedNotificationContainerBinder.kt
@@ -101,12 +101,13 @@
 
                     launch {
                         viewModel
-                            .getMaxNotifications { space ->
+                            .getMaxNotifications { space, extraShelfSpace ->
+                                val shelfHeight = controller.getShelfHeight().toFloat()
                                 notificationStackSizeCalculator.computeMaxKeyguardNotifications(
                                     controller.getView(),
                                     space,
-                                    0f, // Vertical space for shelf is already accounted for
-                                    controller.getShelfHeight().toFloat(),
+                                    if (extraShelfSpace) shelfHeight else 0f,
+                                    shelfHeight,
                                 )
                             }
                             .collect { controller.setMaxDisplayedNotifications(it) }
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 9594bc3..eff91e5 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
@@ -229,7 +229,7 @@
      * When expanding or when the user is interacting with the shade, keep the count stable; do not
      * emit a value.
      */
-    fun getMaxNotifications(calculateSpace: (Float) -> Int): Flow<Int> {
+    fun getMaxNotifications(calculateSpace: (Float, Boolean) -> Int): Flow<Int> {
         val showLimitedNotifications = isOnLockscreenWithoutShade
         val showUnlimitedNotifications =
             combine(
@@ -245,11 +245,17 @@
                 shadeInteractor.isUserInteracting,
                 bounds,
                 interactor.notificationStackChanged.onStart { emit(Unit) },
-            ) { showLimitedNotifications, showUnlimitedNotifications, isUserInteracting, bounds, _
-                ->
+                interactor.useExtraShelfSpace,
+            ) { flows ->
+                val showLimitedNotifications = flows[0] as Boolean
+                val showUnlimitedNotifications = flows[1] as Boolean
+                val isUserInteracting = flows[2] as Boolean
+                val bounds = flows[3] as NotificationContainerBounds
+                val useExtraShelfSpace = flows[5] as Boolean
+
                 if (!isUserInteracting) {
                     if (showLimitedNotifications) {
-                        emit(calculateSpace(bounds.bottom - bounds.top))
+                        emit(calculateSpace(bounds.bottom - bounds.top, useExtraShelfSpace))
                     } else if (showUnlimitedNotifications) {
                         emit(-1)
                     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconContainer.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconContainer.java
index 00e78a4..0dabafb 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconContainer.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/NotificationIconContainer.java
@@ -400,6 +400,21 @@
         }
     }
 
+    /**
+     * Removes all child {@link StatusBarIconView} instances from this container, immediately and
+     * without animation. This should be called when tearing down this container so that external
+     * icon views are not holding onto a reference thru {@link View#getParent()}.
+     */
+    public void detachAllIcons() {
+        boolean animsWereEnabled = mAnimationsEnabled;
+        boolean wasChangingPositions = mChangingViewPositions;
+        mAnimationsEnabled = false;
+        mChangingViewPositions = true;
+        removeAllViews();
+        mChangingViewPositions = wasChangingPositions;
+        mAnimationsEnabled = animsWereEnabled;
+    }
+
     public boolean areIconsOverflowing() {
         return mIsShowingOverflowDot;
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardStateControllerImpl.java b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardStateControllerImpl.java
index 756c440..0c5472f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardStateControllerImpl.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/policy/KeyguardStateControllerImpl.java
@@ -41,6 +41,7 @@
 import com.android.systemui.dump.DumpManager;
 import com.android.systemui.flags.FeatureFlags;
 import com.android.systemui.keyguard.KeyguardUnlockAnimationController;
+import com.android.systemui.log.core.LogLevel;
 import com.android.systemui.res.R;
 import com.android.systemui.user.domain.interactor.SelectedUserInteractor;
 
@@ -49,6 +50,7 @@
 import java.io.PrintWriter;
 import java.util.ArrayList;
 import java.util.Objects;
+import java.util.function.Consumer;
 
 import javax.inject.Inject;
 
@@ -210,23 +212,33 @@
     private void notifyKeyguardChanged() {
         Trace.beginSection("KeyguardStateController#notifyKeyguardChanged");
         // Copy the list to allow removal during callback.
-        new ArrayList<>(mCallbacks).forEach(Callback::onKeyguardShowingChanged);
+        invokeForEachCallback(Callback::onKeyguardShowingChanged);
         Trace.endSection();
     }
 
     private void notifyKeyguardFaceAuthEnabledChanged() {
+        invokeForEachCallback(Callback::onFaceEnrolledChanged);
+    }
+
+    private void invokeForEachCallback(Consumer<Callback> consumer) {
         // Copy the list to allow removal during callback.
-        new ArrayList<>(mCallbacks).forEach(callback -> {
+        ArrayList<Callback> copyOfCallbacks = new ArrayList<>(mCallbacks);
+        for (int i = 0; i < copyOfCallbacks.size(); i++) {
+            Callback callback = copyOfCallbacks.get(i);
+            // Temporary fix for b/315731775, callback is null even though only non-null callbacks
+            // are added to the list by addCallback
             if (callback != null) {
-                callback.onFaceEnrolledChanged();
+                consumer.accept(callback);
+            } else {
+                mLogger.log("KeyguardStateController callback is null", LogLevel.DEBUG);
             }
-        });
+        }
     }
 
     private void notifyUnlockedChanged() {
         Trace.beginSection("KeyguardStateController#notifyUnlockedChanged");
         // Copy the list to allow removal during callback.
-        new ArrayList<>(mCallbacks).forEach(Callback::onUnlockedChanged);
+        invokeForEachCallback(Callback::onUnlockedChanged);
         Trace.endSection();
     }
 
@@ -242,10 +254,7 @@
             Trace.traceCounter(Trace.TRACE_TAG_APP, "keyguardFadingAway",
                     keyguardFadingAway ? 1 : 0);
             mKeyguardFadingAway = keyguardFadingAway;
-            ArrayList<Callback> callbacks = new ArrayList<>(mCallbacks);
-            for (int i = 0; i < callbacks.size(); i++) {
-                callbacks.get(i).onKeyguardFadingAwayChanged();
-            }
+            invokeForEachCallback(Callback::onKeyguardFadingAwayChanged);
         }
     }
 
@@ -359,7 +368,7 @@
             Trace.traceCounter(Trace.TRACE_TAG_APP, "keyguardGoingAway",
                     keyguardGoingAway ? 1 : 0);
             mKeyguardGoingAway = keyguardGoingAway;
-            new ArrayList<>(mCallbacks).forEach(Callback::onKeyguardGoingAwayChanged);
+            invokeForEachCallback(Callback::onKeyguardGoingAwayChanged);
         }
     }
 
@@ -368,7 +377,7 @@
         if (mPrimaryBouncerShowing != showing) {
             mPrimaryBouncerShowing = showing;
 
-            new ArrayList<>(mCallbacks).forEach(Callback::onPrimaryBouncerShowingChanged);
+            invokeForEachCallback(Callback::onPrimaryBouncerShowingChanged);
         }
     }
 
@@ -392,13 +401,13 @@
             boolean dismissingFromTouch) {
         mDismissAmount = dismissAmount;
         mDismissingFromTouch = dismissingFromTouch;
-        new ArrayList<>(mCallbacks).forEach(Callback::onKeyguardDismissAmountChanged);
+        invokeForEachCallback(Callback::onKeyguardDismissAmountChanged);
     }
 
     @Override
     public void setLaunchTransitionFadingAway(boolean fadingAway) {
         mLaunchTransitionFadingAway = fadingAway;
-        new ArrayList<>(mCallbacks).forEach(Callback::onLaunchTransitionFadingAwayChanged);
+        invokeForEachCallback(Callback::onLaunchTransitionFadingAwayChanged);
     }
 
     @Override
diff --git a/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java b/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java
index 886fa70..2b9ad50 100644
--- a/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java
+++ b/packages/SystemUI/src/com/android/systemui/theme/ThemeOverlayController.java
@@ -18,8 +18,8 @@
 
 import static android.util.TypedValue.TYPE_INT_COLOR_ARGB8;
 
-import static com.android.systemui.keyguard.WakefulnessLifecycle.WAKEFULNESS_ASLEEP;
 import static com.android.systemui.Flags.themeOverlayControllerWakefulnessDeprecation;
+import static com.android.systemui.keyguard.WakefulnessLifecycle.WAKEFULNESS_ASLEEP;
 import static com.android.systemui.theme.ThemeOverlayApplier.COLOR_SOURCE_HOME;
 import static com.android.systemui.theme.ThemeOverlayApplier.COLOR_SOURCE_LOCK;
 import static com.android.systemui.theme.ThemeOverlayApplier.COLOR_SOURCE_PRESET;
@@ -364,15 +364,23 @@
     private final BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() {
         @Override
         public void onReceive(Context context, Intent intent) {
-            boolean newWorkProfile = Intent.ACTION_MANAGED_PROFILE_ADDED.equals(intent.getAction());
-            boolean isManagedProfile = mUserManager.isManagedProfile(
-                    intent.getIntExtra(Intent.EXTRA_USER_HANDLE, 0));
-            if (newWorkProfile) {
-                if (!mDeviceProvisionedController.isCurrentUserSetup() && isManagedProfile) {
+            boolean newProfile = Intent.ACTION_PROFILE_ADDED.equals(intent.getAction());
+            if (newProfile) {
+                UserHandle newUserHandle = intent.getParcelableExtra(Intent.EXTRA_USER,
+                        android.os.UserHandle.class);
+                boolean isManagedProfile =
+                        mUserManager.isManagedProfile(newUserHandle.getIdentifier());
+                if (!mDeviceProvisionedController.isUserSetup(newUserHandle.getIdentifier())
+                        && isManagedProfile) {
                     Log.i(TAG, "User setup not finished when " + intent.getAction()
                             + " was received. Deferring... Managed profile? " + isManagedProfile);
                     return;
                 }
+                if (android.os.Flags.allowPrivateProfile() && isPrivateProfile(newUserHandle)) {
+                    mDeferredThemeEvaluation = true;
+                    Log.i(TAG, "Deferring theme for private profile till user setup is complete");
+                    return;
+                }
                 if (DEBUG) Log.d(TAG, "Updating overlays for user switch / profile added.");
                 reevaluateSystemTheme(true /* forceReload */);
             } else if (Intent.ACTION_WALLPAPER_CHANGED.equals(intent.getAction())) {
@@ -432,7 +440,7 @@
     public void start() {
         if (DEBUG) Log.d(TAG, "Start");
         final IntentFilter filter = new IntentFilter();
-        filter.addAction(Intent.ACTION_MANAGED_PROFILE_ADDED);
+        filter.addAction(Intent.ACTION_PROFILE_ADDED);
         filter.addAction(Intent.ACTION_WALLPAPER_CHANGED);
         mBroadcastDispatcher.registerReceiver(mBroadcastReceiver, filter, mMainExecutor,
                 UserHandle.ALL);
@@ -608,6 +616,15 @@
         return new FabricatedOverlay.Builder("com.android.systemui", name, "android").build();
     }
 
+    @VisibleForTesting
+    protected boolean isPrivateProfile(UserHandle userHandle) {
+        Context usercontext = mContext.createContextAsUser(userHandle,0);
+        if (usercontext.getSystemService(UserManager.class).isPrivateProfile()) {
+            return true;
+        }
+        return false;
+    }
+
     private void createOverlays(int color) {
         boolean nightMode = isNightMode();
         mColorScheme = new ColorScheme(color, nightMode, mThemeStyle);
@@ -784,7 +801,7 @@
 
         Set<UserHandle> managedProfiles = new HashSet<>();
         for (UserInfo userInfo : mUserManager.getEnabledProfiles(currentUser)) {
-            if (userInfo.isManagedProfile()) {
+            if (userInfo.isProfile()) {
                 managedProfiles.add(userInfo.getUserHandle());
             }
         }
diff --git a/packages/SystemUI/src/com/android/systemui/util/kotlin/Log.kt b/packages/SystemUI/src/com/android/systemui/util/kotlin/Log.kt
new file mode 100644
index 0000000..2f6c450
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/util/kotlin/Log.kt
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.util.kotlin
+
+import android.util.Log
+
+/** Logs message at [Log.DEBUG] level. Won't call the lambda if [DEBUG] is not loggable. */
+inline fun logD(tag: String, messageLambda: () -> String) {
+    if (Log.isLoggable(tag, Log.DEBUG)) {
+        Log.d(tag, messageLambda.invoke())
+    }
+}
+
+/** Logs message at [Log.VERBOSE] level. Won't call the lambda if [VERBOSE] is not loggable. */
+inline fun logV(tag: String, messageLambda: () -> String) {
+    if (Log.isLoggable(tag, Log.VERBOSE)) {
+        Log.v(tag, messageLambda.invoke())
+    }
+}
+
+/** Logs message at [Log.INFO] level. Won't call the lambda if [INFO] is not loggable. */
+inline fun logI(tag: String, messageLambda: () -> String) {
+    if (Log.isLoggable(tag, Log.INFO)) {
+        Log.i(tag, messageLambda.invoke())
+    }
+}
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 ea20d29..91219f0 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthContainerViewTest.kt
@@ -47,12 +47,14 @@
 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.domain.interactor.UdfpsOverlayInteractor
 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.keyguard.WakefulnessLifecycle
 import com.android.systemui.statusbar.VibratorHelper
 import com.android.systemui.statusbar.events.ANIMATING_OUT
+import com.android.systemui.user.domain.interactor.SelectedUserInteractor
 import com.android.systemui.util.concurrency.FakeExecutor
 import com.android.systemui.util.time.FakeSystemClock
 import com.google.common.truth.Truth.assertThat
@@ -101,6 +103,12 @@
     lateinit var interactionJankMonitor: InteractionJankMonitor
     @Mock
     lateinit var vibrator: VibratorHelper
+    @Mock
+    lateinit var udfpsUtils: UdfpsUtils
+    @Mock
+    lateinit var authController: AuthController
+    @Mock
+    lateinit var selectedUserInteractor: SelectedUserInteractor
 
     private val testScope = TestScope(StandardTestDispatcher())
     private val fakeExecutor = FakeExecutor(FakeSystemClock())
@@ -123,6 +131,7 @@
 
     private lateinit var displayRepository: FakeDisplayRepository
     private lateinit var displayStateInteractor: DisplayStateInteractor
+    private lateinit var udfpsOverlayInteractor: UdfpsOverlayInteractor
 
     private val credentialViewModel = CredentialViewModel(mContext, bpCredentialInteractor)
 
@@ -140,6 +149,12 @@
                     displayStateRepository,
                     displayRepository,
             )
+        udfpsOverlayInteractor =
+                UdfpsOverlayInteractor(
+                        authController,
+                        selectedUserInteractor,
+                        testScope.backgroundScope,
+                )
     }
 
     @After
@@ -532,6 +547,8 @@
             displayStateInteractor,
             promptSelectorInteractor,
             context,
+            udfpsOverlayInteractor,
+            udfpsUtils
         ),
         { credentialViewModel },
         Handler(TestableLooper.get(this).looper),
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthRippleControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthRippleControllerTest.kt
index 8e54eb7..4c510ee 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthRippleControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/AuthRippleControllerTest.kt
@@ -27,6 +27,7 @@
 import com.android.keyguard.KeyguardUpdateMonitor
 import com.android.keyguard.KeyguardUpdateMonitorCallback
 import com.android.keyguard.logging.KeyguardLogger
+import com.android.systemui.Flags.FLAG_LIGHT_REVEAL_MIGRATION
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.dump.logcatLogBuffer
 import com.android.systemui.flags.FeatureFlags
@@ -40,6 +41,7 @@
 import com.android.systemui.statusbar.policy.KeyguardStateController
 import com.android.systemui.util.leak.RotationUtils
 import com.android.systemui.util.mockito.any
+import javax.inject.Provider
 import org.junit.After
 import org.junit.Assert.assertFalse
 import org.junit.Assert.assertTrue
@@ -51,14 +53,14 @@
 import org.mockito.ArgumentMatchers.eq
 import org.mockito.Captor
 import org.mockito.Mock
-import org.mockito.Mockito.`when`
 import org.mockito.Mockito.never
 import org.mockito.Mockito.reset
 import org.mockito.Mockito.verify
+import org.mockito.Mockito.`when`
 import org.mockito.MockitoAnnotations
 import org.mockito.MockitoSession
 import org.mockito.quality.Strictness
-import javax.inject.Provider
+
 
 @SmallTest
 @RunWith(AndroidTestingRunner::class)
@@ -246,6 +248,7 @@
     @Test
     @RunWithLooper(setAsMainLooper = true)
     fun testAnimatorRunWhenWakeAndUnlock_fingerprint() {
+        mSetFlagsRule.disableFlags(FLAG_LIGHT_REVEAL_MIGRATION)
         val fpsLocation = Point(5, 5)
         `when`(authController.fingerprintSensorLocation).thenReturn(fpsLocation)
         controller.onViewAttached()
@@ -266,6 +269,7 @@
     @Test
     @RunWithLooper(setAsMainLooper = true)
     fun testAnimatorRunWhenWakeAndUnlock_faceUdfpsFingerDown() {
+        mSetFlagsRule.disableFlags(FLAG_LIGHT_REVEAL_MIGRATION)
         val faceLocation = Point(5, 5)
         `when`(authController.faceSensorLocation).thenReturn(faceLocation)
         controller.onViewAttached()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/data/repository/BiometricStatusRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/data/repository/BiometricStatusRepositoryTest.kt
new file mode 100644
index 0000000..27d93eb
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/data/repository/BiometricStatusRepositoryTest.kt
@@ -0,0 +1,176 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.biometrics.data.repository
+
+import android.hardware.biometrics.AuthenticationStateListener
+import android.hardware.biometrics.BiometricManager
+import android.hardware.biometrics.BiometricRequestConstants.REASON_AUTH_BP
+import android.hardware.biometrics.BiometricRequestConstants.REASON_AUTH_KEYGUARD
+import android.hardware.biometrics.BiometricRequestConstants.REASON_AUTH_OTHER
+import android.hardware.biometrics.BiometricRequestConstants.REASON_AUTH_SETTINGS
+import android.hardware.biometrics.BiometricRequestConstants.REASON_ENROLL_ENROLLING
+import android.hardware.biometrics.BiometricRequestConstants.REASON_ENROLL_FIND_SENSOR
+import android.platform.test.annotations.RequiresFlagsEnabled
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.biometrics.shared.model.AuthenticationReason
+import com.android.systemui.biometrics.shared.model.AuthenticationReason.SettingsOperations
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.shared.Flags.FLAG_SIDEFPS_CONTROLLER_REFACTOR
+import com.android.systemui.util.mockito.withArgCaptor
+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
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+import org.mockito.Mock
+import org.mockito.Mockito.verify
+import org.mockito.junit.MockitoJUnit
+import org.mockito.junit.MockitoRule
+
+@RequiresFlagsEnabled(FLAG_SIDEFPS_CONTROLLER_REFACTOR)
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+@RunWith(JUnit4::class)
+class BiometricStatusRepositoryTest : SysuiTestCase() {
+    @JvmField @Rule var mockitoRule: MockitoRule = MockitoJUnit.rule()
+    @Mock private lateinit var biometricManager: BiometricManager
+
+    private lateinit var underTest: BiometricStatusRepository
+
+    private val testScope = TestScope(StandardTestDispatcher())
+
+    @Before
+    fun setUp() {
+        underTest = BiometricStatusRepositoryImpl(testScope.backgroundScope, biometricManager)
+    }
+
+    @Test
+    fun updatesFingerprintAuthenticationReason_whenBiometricPromptAuthenticationStarted() =
+        testScope.runTest {
+            val fingerprintAuthenticationReason by
+                collectLastValue(underTest.fingerprintAuthenticationReason)
+            runCurrent()
+
+            val listener = biometricManager.captureListener()
+
+            assertThat(fingerprintAuthenticationReason).isEqualTo(AuthenticationReason.NotRunning)
+
+            listener.onAuthenticationStarted(REASON_AUTH_BP)
+            assertThat(fingerprintAuthenticationReason)
+                .isEqualTo(AuthenticationReason.BiometricPromptAuthentication)
+        }
+
+    @Test
+    fun updatesFingerprintAuthenticationReason_whenDeviceEntryAuthenticationStarted() =
+        testScope.runTest {
+            val fingerprintAuthenticationReason by
+                collectLastValue(underTest.fingerprintAuthenticationReason)
+            runCurrent()
+
+            val listener = biometricManager.captureListener()
+
+            assertThat(fingerprintAuthenticationReason).isEqualTo(AuthenticationReason.NotRunning)
+
+            listener.onAuthenticationStarted(REASON_AUTH_KEYGUARD)
+            assertThat(fingerprintAuthenticationReason)
+                .isEqualTo(AuthenticationReason.DeviceEntryAuthentication)
+        }
+
+    @Test
+    fun updatesFingerprintAuthenticationReason_whenOtherAuthenticationStarted() =
+        testScope.runTest {
+            val fingerprintAuthenticationReason by
+                collectLastValue(underTest.fingerprintAuthenticationReason)
+            runCurrent()
+
+            val listener = biometricManager.captureListener()
+
+            assertThat(fingerprintAuthenticationReason).isEqualTo(AuthenticationReason.NotRunning)
+
+            listener.onAuthenticationStarted(REASON_AUTH_OTHER)
+            assertThat(fingerprintAuthenticationReason)
+                .isEqualTo(AuthenticationReason.OtherAuthentication)
+        }
+
+    @Test
+    fun updatesFingerprintAuthenticationReason_whenSettingsAuthenticationStarted() =
+        testScope.runTest {
+            val fingerprintAuthenticationReason by
+                collectLastValue(underTest.fingerprintAuthenticationReason)
+            runCurrent()
+
+            val listener = biometricManager.captureListener()
+
+            assertThat(fingerprintAuthenticationReason).isEqualTo(AuthenticationReason.NotRunning)
+
+            listener.onAuthenticationStarted(REASON_AUTH_SETTINGS)
+            assertThat(fingerprintAuthenticationReason)
+                .isEqualTo(AuthenticationReason.SettingsAuthentication(SettingsOperations.OTHER))
+        }
+
+    @Test
+    fun updatesFingerprintAuthenticationReason_whenEnrollmentAuthenticationStarted() =
+        testScope.runTest {
+            val fingerprintAuthenticationReason by
+                collectLastValue(underTest.fingerprintAuthenticationReason)
+            runCurrent()
+
+            val listener = biometricManager.captureListener()
+
+            assertThat(fingerprintAuthenticationReason).isEqualTo(AuthenticationReason.NotRunning)
+
+            listener.onAuthenticationStarted(REASON_ENROLL_FIND_SENSOR)
+            assertThat(fingerprintAuthenticationReason)
+                .isEqualTo(
+                    AuthenticationReason.SettingsAuthentication(
+                        SettingsOperations.ENROLL_FIND_SENSOR
+                    )
+                )
+
+            listener.onAuthenticationStarted(REASON_ENROLL_ENROLLING)
+            assertThat(fingerprintAuthenticationReason)
+                .isEqualTo(
+                    AuthenticationReason.SettingsAuthentication(SettingsOperations.ENROLL_ENROLLING)
+                )
+        }
+
+    @Test
+    fun updatesFingerprintAuthenticationReason_whenAuthenticationStopped() =
+        testScope.runTest {
+            val fingerprintAuthenticationReason by
+                collectLastValue(underTest.fingerprintAuthenticationReason)
+            runCurrent()
+
+            val listener = biometricManager.captureListener()
+
+            listener.onAuthenticationStarted(REASON_AUTH_BP)
+            listener.onAuthenticationStopped()
+            assertThat(fingerprintAuthenticationReason).isEqualTo(AuthenticationReason.NotRunning)
+        }
+}
+
+private fun BiometricManager.captureListener() =
+    withArgCaptor<AuthenticationStateListener> {
+        verify(this@captureListener).registerAuthenticationStateListener(capture())
+    }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/BiometricStatusInteractorImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/BiometricStatusInteractorImplTest.kt
new file mode 100644
index 0000000..6978923
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/BiometricStatusInteractorImplTest.kt
@@ -0,0 +1,173 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.biometrics.domain.interactor
+
+import android.app.ActivityManager
+import android.app.ActivityTaskManager
+import android.content.ComponentName
+import android.platform.test.annotations.RequiresFlagsEnabled
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.biometrics.data.repository.FakeBiometricStatusRepository
+import com.android.systemui.biometrics.shared.model.AuthenticationReason
+import com.android.systemui.biometrics.shared.model.AuthenticationReason.SettingsOperations
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.shared.Flags.FLAG_SIDEFPS_CONTROLLER_REFACTOR
+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
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+import org.mockito.Mock
+import org.mockito.Mockito
+import org.mockito.Mockito.`when`
+import org.mockito.junit.MockitoJUnit
+import org.mockito.junit.MockitoRule
+
+@RequiresFlagsEnabled(FLAG_SIDEFPS_CONTROLLER_REFACTOR)
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+@RunWith(JUnit4::class)
+class BiometricStatusInteractorImplTest : SysuiTestCase() {
+    @JvmField @Rule var mockitoRule: MockitoRule = MockitoJUnit.rule()
+    @Mock private lateinit var activityTaskManager: ActivityTaskManager
+
+    private lateinit var biometricStatusRepository: FakeBiometricStatusRepository
+    private lateinit var underTest: BiometricStatusInteractorImpl
+
+    private val testScope = TestScope(StandardTestDispatcher())
+
+    @Before
+    fun setup() {
+        biometricStatusRepository = FakeBiometricStatusRepository()
+        underTest = BiometricStatusInteractorImpl(activityTaskManager, biometricStatusRepository)
+    }
+
+    @Test
+    fun updatesSfpsAuthenticationReason_whenBiometricPromptAuthenticationStarted() =
+        testScope.runTest {
+            val sfpsAuthenticationReason by collectLastValue(underTest.sfpsAuthenticationReason)
+            runCurrent()
+
+            assertThat(sfpsAuthenticationReason).isEqualTo(AuthenticationReason.NotRunning)
+
+            biometricStatusRepository.setFingerprintAuthenticationReason(
+                AuthenticationReason.BiometricPromptAuthentication
+            )
+            assertThat(sfpsAuthenticationReason)
+                .isEqualTo(AuthenticationReason.BiometricPromptAuthentication)
+        }
+
+    @Test
+    fun doesNotUpdateSfpsAuthenticationReason_whenDeviceEntryAuthenticationStarted() =
+        testScope.runTest {
+            val sfpsAuthenticationReason by collectLastValue(underTest.sfpsAuthenticationReason)
+            runCurrent()
+
+            assertThat(sfpsAuthenticationReason).isEqualTo(AuthenticationReason.NotRunning)
+
+            biometricStatusRepository.setFingerprintAuthenticationReason(
+                AuthenticationReason.DeviceEntryAuthentication
+            )
+            assertThat(sfpsAuthenticationReason).isEqualTo(AuthenticationReason.NotRunning)
+        }
+
+    @Test
+    fun updatesSfpsAuthenticationReason_whenOtherAuthenticationStarted() =
+        testScope.runTest {
+            val sfpsAuthenticationReason by collectLastValue(underTest.sfpsAuthenticationReason)
+            runCurrent()
+
+            assertThat(sfpsAuthenticationReason).isEqualTo(AuthenticationReason.NotRunning)
+
+            biometricStatusRepository.setFingerprintAuthenticationReason(
+                AuthenticationReason.OtherAuthentication
+            )
+            assertThat(sfpsAuthenticationReason).isEqualTo(AuthenticationReason.OtherAuthentication)
+        }
+
+    @Test
+    fun doesNotUpdateSfpsAuthenticationReason_whenOtherSettingsAuthenticationStarted() =
+        testScope.runTest {
+            val sfpsAuthenticationReason by collectLastValue(underTest.sfpsAuthenticationReason)
+            runCurrent()
+
+            assertThat(sfpsAuthenticationReason).isEqualTo(AuthenticationReason.NotRunning)
+
+            `when`(activityTaskManager.getTasks(Mockito.anyInt()))
+                .thenReturn(listOf(fpSettingsTask()))
+            biometricStatusRepository.setFingerprintAuthenticationReason(
+                AuthenticationReason.SettingsAuthentication(SettingsOperations.OTHER)
+            )
+            assertThat(sfpsAuthenticationReason).isEqualTo(AuthenticationReason.NotRunning)
+        }
+
+    @Test
+    fun updatesSfpsAuthenticationReason_whenEnrollmentAuthenticationStarted() =
+        testScope.runTest {
+            val sfpsAuthenticationReason by collectLastValue(underTest.sfpsAuthenticationReason)
+            runCurrent()
+
+            assertThat(sfpsAuthenticationReason).isEqualTo(AuthenticationReason.NotRunning)
+
+            biometricStatusRepository.setFingerprintAuthenticationReason(
+                AuthenticationReason.SettingsAuthentication(SettingsOperations.ENROLL_FIND_SENSOR)
+            )
+            assertThat(sfpsAuthenticationReason)
+                .isEqualTo(
+                    AuthenticationReason.SettingsAuthentication(
+                        SettingsOperations.ENROLL_FIND_SENSOR
+                    )
+                )
+
+            biometricStatusRepository.setFingerprintAuthenticationReason(
+                AuthenticationReason.SettingsAuthentication(SettingsOperations.ENROLL_ENROLLING)
+            )
+            assertThat(sfpsAuthenticationReason)
+                .isEqualTo(
+                    AuthenticationReason.SettingsAuthentication(SettingsOperations.ENROLL_ENROLLING)
+                )
+        }
+
+    @Test
+    fun updatesFingerprintAuthenticationReason_whenAuthenticationStopped() =
+        testScope.runTest {
+            val sfpsAuthenticationReason by collectLastValue(underTest.sfpsAuthenticationReason)
+            runCurrent()
+
+            biometricStatusRepository.setFingerprintAuthenticationReason(
+                AuthenticationReason.BiometricPromptAuthentication
+            )
+            biometricStatusRepository.setFingerprintAuthenticationReason(
+                AuthenticationReason.NotRunning
+            )
+            assertThat(sfpsAuthenticationReason).isEqualTo(AuthenticationReason.NotRunning)
+        }
+}
+
+private fun fpSettingsTask() = settingsTask(".biometrics.fingerprint.FingerprintSettings")
+
+private fun settingsTask(cls: String) =
+    ActivityManager.RunningTaskInfo().apply {
+        topActivity = ComponentName.createRelative("com.android.settings", cls)
+    }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/shared/model/BiometricModalitiesTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/shared/model/BiometricModalitiesTest.kt
index 22e3e7f..74c4313 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/shared/model/BiometricModalitiesTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/shared/model/BiometricModalitiesTest.kt
@@ -16,6 +16,7 @@
 
 package com.android.systemui.biometrics.shared.model
 
+import android.hardware.fingerprint.FingerprintSensorProperties
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.biometrics.faceSensorPropertiesInternal
@@ -35,6 +36,46 @@
     }
 
     @Test
+    fun hasUdfps() {
+        with(
+            BiometricModalities(
+                fingerprintProperties = fingerprintSensorPropertiesInternal(
+                    sensorType = FingerprintSensorProperties.TYPE_UDFPS_OPTICAL
+                ).first(),
+            )
+        ) {
+            assertThat(isEmpty).isFalse()
+            assertThat(hasUdfps).isTrue()
+            assertThat(hasSfps).isFalse()
+            assertThat(hasFace).isFalse()
+            assertThat(hasFaceOnly).isFalse()
+            assertThat(hasFingerprint).isTrue()
+            assertThat(hasFingerprintOnly).isTrue()
+            assertThat(hasFaceAndFingerprint).isFalse()
+        }
+    }
+
+    @Test
+    fun hasSfps() {
+        with(
+            BiometricModalities(
+                fingerprintProperties = fingerprintSensorPropertiesInternal(
+                    sensorType = FingerprintSensorProperties.TYPE_POWER_BUTTON
+                ).first(),
+            )
+        ) {
+            assertThat(isEmpty).isFalse()
+            assertThat(hasUdfps).isFalse()
+            assertThat(hasSfps).isTrue()
+            assertThat(hasFace).isFalse()
+            assertThat(hasFaceOnly).isFalse()
+            assertThat(hasFingerprint).isTrue()
+            assertThat(hasFingerprintOnly).isTrue()
+            assertThat(hasFaceAndFingerprint).isFalse()
+        }
+    }
+
+    @Test
     fun fingerprintOnly() {
         with(
             BiometricModalities(
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/binder/SideFpsOverlayViewBinderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/binder/SideFpsOverlayViewBinderTest.kt
new file mode 100644
index 0000000..b4ae00d
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/binder/SideFpsOverlayViewBinderTest.kt
@@ -0,0 +1,484 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.biometrics.ui.binder
+
+import android.animation.Animator
+import android.app.ActivityTaskManager
+import android.graphics.Rect
+import android.hardware.biometrics.SensorLocationInternal
+import android.hardware.display.DisplayManager
+import android.hardware.display.DisplayManagerGlobal
+import android.os.Handler
+import android.testing.TestableLooper
+import android.view.Display
+import android.view.DisplayInfo
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewPropertyAnimator
+import android.view.WindowInsets
+import android.view.WindowManager
+import android.view.WindowMetrics
+import androidx.test.filters.SmallTest
+import com.airbnb.lottie.LottieAnimationView
+import com.android.keyguard.KeyguardSecurityModel
+import com.android.keyguard.KeyguardUpdateMonitor
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.biometrics.FingerprintInteractiveToAuthProvider
+import com.android.systemui.biometrics.FpsUnlockTracker
+import com.android.systemui.biometrics.data.repository.FakeBiometricStatusRepository
+import com.android.systemui.biometrics.data.repository.FakeDisplayStateRepository
+import com.android.systemui.biometrics.data.repository.FakeFingerprintPropertyRepository
+import com.android.systemui.biometrics.domain.interactor.BiometricStatusInteractor
+import com.android.systemui.biometrics.domain.interactor.BiometricStatusInteractorImpl
+import com.android.systemui.biometrics.domain.interactor.DisplayStateInteractorImpl
+import com.android.systemui.biometrics.domain.interactor.SideFpsSensorInteractor
+import com.android.systemui.biometrics.shared.model.AuthenticationReason
+import com.android.systemui.biometrics.shared.model.DisplayRotation
+import com.android.systemui.biometrics.shared.model.FingerprintSensorType
+import com.android.systemui.biometrics.shared.model.SensorStrength
+import com.android.systemui.biometrics.ui.viewmodel.SideFpsOverlayViewModel
+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.classifier.FalsingCollector
+import com.android.systemui.display.data.repository.FakeDisplayRepository
+import com.android.systemui.dump.logcatLogBuffer
+import com.android.systemui.keyguard.DismissCallbackRegistry
+import com.android.systemui.keyguard.data.repository.FakeBiometricSettingsRepository
+import com.android.systemui.keyguard.data.repository.FakeDeviceEntryFingerprintAuthRepository
+import com.android.systemui.keyguard.data.repository.FakeTrustRepository
+import com.android.systemui.keyguard.domain.interactor.DeviceEntrySideFpsOverlayInteractor
+import com.android.systemui.keyguard.domain.interactor.KeyguardFaceAuthInteractor
+import com.android.systemui.keyguard.ui.viewmodel.SideFpsProgressBarViewModel
+import com.android.systemui.log.SideFpsLogger
+import com.android.systemui.plugins.statusbar.StatusBarStateController
+import com.android.systemui.res.R
+import com.android.systemui.shared.Flags.FLAG_SIDEFPS_CONTROLLER_REFACTOR
+import com.android.systemui.statusbar.policy.KeyguardStateController
+import com.android.systemui.unfold.compat.ScreenSizeFoldProvider
+import com.android.systemui.user.domain.interactor.SelectedUserInteractor
+import com.android.systemui.util.concurrency.FakeExecutor
+import com.android.systemui.util.mockito.eq
+import com.android.systemui.util.mockito.whenever
+import com.android.systemui.util.time.FakeSystemClock
+import java.util.Optional
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.test.StandardTestDispatcher
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+import org.mockito.Mock
+import org.mockito.Mockito
+import org.mockito.Mockito.any
+import org.mockito.Mockito.mock
+import org.mockito.Mockito.never
+import org.mockito.Mockito.spy
+import org.mockito.Mockito.verify
+import org.mockito.Mockito.`when`
+import org.mockito.junit.MockitoJUnit
+import org.mockito.junit.MockitoRule
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+@RunWith(JUnit4::class)
+@TestableLooper.RunWithLooper(setAsMainLooper = true)
+class SideFpsOverlayViewBinderTest : SysuiTestCase() {
+    @JvmField @Rule var mockitoRule: MockitoRule = MockitoJUnit.rule()
+    @Mock private lateinit var activityTaskManager: ActivityTaskManager
+    @Mock private lateinit var displayManager: DisplayManager
+    @Mock private lateinit var faceAuthInteractor: KeyguardFaceAuthInteractor
+    @Mock
+    private lateinit var fingerprintInteractiveToAuthProvider: FingerprintInteractiveToAuthProvider
+    @Mock private lateinit var fpsUnlockTracker: FpsUnlockTracker
+    @Mock private lateinit var keyguardUpdateMonitor: KeyguardUpdateMonitor
+    @Mock private lateinit var layoutInflater: LayoutInflater
+    @Mock private lateinit var screenSizeFoldProvider: ScreenSizeFoldProvider
+    @Mock private lateinit var selectedUserInteractor: SelectedUserInteractor
+    @Mock private lateinit var sideFpsView: View
+    @Mock private lateinit var windowManager: WindowManager
+
+    private val contextDisplayInfo = DisplayInfo()
+
+    private val bouncerRepository = FakeKeyguardBouncerRepository()
+    private val biometricSettingsRepository = FakeBiometricSettingsRepository()
+    private val biometricStatusRepository = FakeBiometricStatusRepository()
+    private val deviceEntryFingerprintAuthRepository = FakeDeviceEntryFingerprintAuthRepository()
+    private val displayRepository = FakeDisplayRepository()
+    private val displayStateRepository = FakeDisplayStateRepository()
+    private val fingerprintPropertyRepository = FakeFingerprintPropertyRepository()
+
+    private lateinit var underTest: SideFpsOverlayViewBinder
+
+    private lateinit var alternateBouncerInteractor: AlternateBouncerInteractor
+    private lateinit var biometricStatusInteractor: BiometricStatusInteractor
+    private lateinit var deviceEntrySideFpsOverlayInteractor: DeviceEntrySideFpsOverlayInteractor
+    private lateinit var displayStateInteractor: DisplayStateInteractorImpl
+    private lateinit var primaryBouncerInteractor: PrimaryBouncerInteractor
+    private lateinit var sfpsSensorInteractor: SideFpsSensorInteractor
+
+    private lateinit var sideFpsProgressBarViewModel: SideFpsProgressBarViewModel
+
+    private lateinit var viewModel: SideFpsOverlayViewModel
+
+    private var displayWidth: Int = 0
+    private var displayHeight: Int = 0
+    private var boundsWidth: Int = 0
+    private var boundsHeight: Int = 0
+
+    private lateinit var deviceConfig: DeviceConfig
+    private lateinit var sensorLocation: SensorLocationInternal
+
+    private val testScope = TestScope(StandardTestDispatcher())
+    private val fakeExecutor = FakeExecutor(FakeSystemClock())
+
+    enum class DeviceConfig {
+        X_ALIGNED,
+        Y_ALIGNED,
+    }
+
+    @Before
+    fun setup() {
+        mSetFlagsRule.enableFlags(FLAG_SIDEFPS_CONTROLLER_REFACTOR)
+
+        allowTestableLooperAsMainThread() // repeatWhenAttached requires the main thread
+
+        mContext = spy(mContext)
+
+        val resources = mContext.resources
+        whenever(mContext.display)
+            .thenReturn(
+                Display(mock(DisplayManagerGlobal::class.java), 1, contextDisplayInfo, resources)
+            )
+
+        alternateBouncerInteractor =
+            AlternateBouncerInteractor(
+                mock(StatusBarStateController::class.java),
+                mock(KeyguardStateController::class.java),
+                bouncerRepository,
+                fingerprintPropertyRepository,
+                biometricSettingsRepository,
+                FakeSystemClock(),
+                keyguardUpdateMonitor,
+                testScope.backgroundScope,
+            )
+
+        biometricStatusInteractor =
+            BiometricStatusInteractorImpl(activityTaskManager, biometricStatusRepository)
+
+        displayStateInteractor =
+            DisplayStateInteractorImpl(
+                testScope.backgroundScope,
+                mContext,
+                fakeExecutor,
+                displayStateRepository,
+                displayRepository,
+            )
+        displayStateInteractor.setScreenSizeFoldProvider(screenSizeFoldProvider)
+
+        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),
+                mContext,
+                keyguardUpdateMonitor,
+                FakeTrustRepository(),
+                testScope.backgroundScope,
+                selectedUserInteractor,
+                faceAuthInteractor
+            )
+
+        deviceEntrySideFpsOverlayInteractor =
+            DeviceEntrySideFpsOverlayInteractor(
+                mContext,
+                deviceEntryFingerprintAuthRepository,
+                primaryBouncerInteractor,
+                alternateBouncerInteractor,
+                keyguardUpdateMonitor
+            )
+
+        whenever(fingerprintInteractiveToAuthProvider.enabledForCurrentUser)
+            .thenReturn(MutableStateFlow(false))
+
+        sfpsSensorInteractor =
+            SideFpsSensorInteractor(
+                mContext,
+                fingerprintPropertyRepository,
+                windowManager,
+                displayStateInteractor,
+                Optional.of(fingerprintInteractiveToAuthProvider),
+                SideFpsLogger(logcatLogBuffer("SfpsLogger"))
+            )
+
+        sideFpsProgressBarViewModel =
+            SideFpsProgressBarViewModel(
+                mContext,
+                deviceEntryFingerprintAuthRepository,
+                sfpsSensorInteractor,
+                displayStateInteractor,
+                testScope.backgroundScope,
+            )
+
+        viewModel =
+            SideFpsOverlayViewModel(
+                mContext,
+                biometricStatusInteractor,
+                deviceEntrySideFpsOverlayInteractor,
+                displayStateInteractor,
+                sfpsSensorInteractor,
+                sideFpsProgressBarViewModel
+            )
+
+        underTest =
+            SideFpsOverlayViewBinder(
+                testScope.backgroundScope,
+                mContext,
+                biometricStatusInteractor,
+                displayStateInteractor,
+                deviceEntrySideFpsOverlayInteractor,
+                fpsUnlockTracker,
+                layoutInflater,
+                sideFpsProgressBarViewModel,
+                sfpsSensorInteractor,
+                windowManager
+            )
+
+        context.addMockSystemService(DisplayManager::class.java, displayManager)
+        context.addMockSystemService(WindowManager::class.java, windowManager)
+
+        `when`(layoutInflater.inflate(R.layout.sidefps_view, null, false)).thenReturn(sideFpsView)
+        `when`(sideFpsView.requireViewById<LottieAnimationView>(eq(R.id.sidefps_animation)))
+            .thenReturn(mock(LottieAnimationView::class.java))
+        with(mock(ViewPropertyAnimator::class.java)) {
+            `when`(sideFpsView.animate()).thenReturn(this)
+            `when`(alpha(Mockito.anyFloat())).thenReturn(this)
+            `when`(setStartDelay(Mockito.anyLong())).thenReturn(this)
+            `when`(setDuration(Mockito.anyLong())).thenReturn(this)
+            `when`(setListener(any())).thenAnswer {
+                (it.arguments[0] as Animator.AnimatorListener).onAnimationEnd(
+                    mock(Animator::class.java)
+                )
+                this
+            }
+        }
+    }
+
+    @Test
+    fun verifyIndicatorNotAdded_whenInRearDisplayMode() {
+        testScope.runTest {
+            setupTestConfiguration(
+                DeviceConfig.X_ALIGNED,
+                rotation = DisplayRotation.ROTATION_0,
+                isInRearDisplayMode = true
+            )
+            biometricStatusRepository.setFingerprintAuthenticationReason(
+                AuthenticationReason.NotRunning
+            )
+            sideFpsProgressBarViewModel.setVisible(false)
+            updatePrimaryBouncer(
+                isShowing = true,
+                isAnimatingAway = false,
+                fpsDetectionRunning = true,
+                isUnlockingWithFpAllowed = true
+            )
+            runCurrent()
+
+            verify(windowManager, never()).addView(any(), any())
+        }
+    }
+
+    @Test
+    fun verifyIndicatorShowAndHide_onPrimaryBouncerShowAndHide() {
+        testScope.runTest {
+            setupTestConfiguration(
+                DeviceConfig.X_ALIGNED,
+                rotation = DisplayRotation.ROTATION_0,
+                isInRearDisplayMode = false
+            )
+            biometricStatusRepository.setFingerprintAuthenticationReason(
+                AuthenticationReason.NotRunning
+            )
+            sideFpsProgressBarViewModel.setVisible(false)
+            // Show primary bouncer
+            updatePrimaryBouncer(
+                isShowing = true,
+                isAnimatingAway = false,
+                fpsDetectionRunning = true,
+                isUnlockingWithFpAllowed = true
+            )
+            runCurrent()
+
+            verify(windowManager).addView(any(), any())
+
+            // Hide primary bouncer
+            updatePrimaryBouncer(
+                isShowing = false,
+                isAnimatingAway = false,
+                fpsDetectionRunning = true,
+                isUnlockingWithFpAllowed = true
+            )
+            runCurrent()
+
+            verify(windowManager).removeView(any())
+        }
+    }
+
+    @Test
+    fun verifyIndicatorShowAndHide_onAlternateBouncerShowAndHide() {
+        testScope.runTest {
+            setupTestConfiguration(
+                DeviceConfig.X_ALIGNED,
+                rotation = DisplayRotation.ROTATION_0,
+                isInRearDisplayMode = false
+            )
+            biometricStatusRepository.setFingerprintAuthenticationReason(
+                AuthenticationReason.NotRunning
+            )
+            sideFpsProgressBarViewModel.setVisible(false)
+            // Show alternate bouncer
+            bouncerRepository.setAlternateVisible(true)
+            runCurrent()
+
+            verify(windowManager).addView(any(), any())
+
+            // Hide alternate bouncer
+            bouncerRepository.setAlternateVisible(false)
+            runCurrent()
+
+            verify(windowManager).removeView(any())
+        }
+    }
+
+    @Test
+    fun verifyIndicatorShownAndHidden_onSystemServerAuthenticationStartedAndStopped() {
+        testScope.runTest {
+            setupTestConfiguration(
+                DeviceConfig.X_ALIGNED,
+                rotation = DisplayRotation.ROTATION_0,
+                isInRearDisplayMode = false
+            )
+            sideFpsProgressBarViewModel.setVisible(false)
+            updatePrimaryBouncer(
+                isShowing = false,
+                isAnimatingAway = false,
+                fpsDetectionRunning = true,
+                isUnlockingWithFpAllowed = true
+            )
+            // System server authentication started
+            biometricStatusRepository.setFingerprintAuthenticationReason(
+                AuthenticationReason.BiometricPromptAuthentication
+            )
+            runCurrent()
+
+            verify(windowManager).addView(any(), any())
+
+            // System server authentication stopped
+            biometricStatusRepository.setFingerprintAuthenticationReason(
+                AuthenticationReason.NotRunning
+            )
+            runCurrent()
+
+            verify(windowManager).removeView(any())
+        }
+    }
+
+    private fun updatePrimaryBouncer(
+        isShowing: Boolean,
+        isAnimatingAway: Boolean,
+        fpsDetectionRunning: Boolean,
+        isUnlockingWithFpAllowed: Boolean,
+    ) {
+        bouncerRepository.setPrimaryShow(isShowing)
+        bouncerRepository.setPrimaryStartingToHide(false)
+        val primaryStartDisappearAnimation = if (isAnimatingAway) Runnable {} else null
+        bouncerRepository.setPrimaryStartDisappearAnimation(primaryStartDisappearAnimation)
+
+        whenever(keyguardUpdateMonitor.isFingerprintDetectionRunning)
+            .thenReturn(fpsDetectionRunning)
+        whenever(keyguardUpdateMonitor.isUnlockingWithFingerprintAllowed)
+            .thenReturn(isUnlockingWithFpAllowed)
+        mContext.orCreateTestableResources.addOverride(
+            R.bool.config_show_sidefps_hint_on_bouncer,
+            true
+        )
+    }
+
+    private suspend fun TestScope.setupTestConfiguration(
+        deviceConfig: DeviceConfig,
+        rotation: DisplayRotation = DisplayRotation.ROTATION_0,
+        isInRearDisplayMode: Boolean,
+    ) {
+        this@SideFpsOverlayViewBinderTest.deviceConfig = deviceConfig
+
+        when (deviceConfig) {
+            DeviceConfig.X_ALIGNED -> {
+                displayWidth = 3000
+                displayHeight = 1500
+                boundsWidth = 200
+                boundsHeight = 100
+                sensorLocation = SensorLocationInternal("", 2500, 0, boundsWidth / 2)
+            }
+            DeviceConfig.Y_ALIGNED -> {
+                displayWidth = 2500
+                displayHeight = 2000
+                boundsWidth = 100
+                boundsHeight = 200
+                sensorLocation = SensorLocationInternal("", displayWidth, 300, boundsHeight / 2)
+            }
+        }
+
+        whenever(windowManager.maximumWindowMetrics)
+            .thenReturn(
+                WindowMetrics(
+                    Rect(0, 0, displayWidth, displayHeight),
+                    mock(WindowInsets::class.java),
+                )
+            )
+
+        contextDisplayInfo.uniqueId = DISPLAY_ID
+
+        fingerprintPropertyRepository.setProperties(
+            sensorId = 1,
+            strength = SensorStrength.STRONG,
+            sensorType = FingerprintSensorType.POWER_BUTTON,
+            sensorLocations = mapOf(DISPLAY_ID to sensorLocation)
+        )
+
+        displayStateRepository.setIsInRearDisplayMode(isInRearDisplayMode)
+        displayStateRepository.setCurrentRotation(rotation)
+        displayRepository.emitDisplayChangeEvent(0)
+        underTest.start()
+        runCurrent()
+    }
+
+    companion object {
+        private const val DISPLAY_ID = "displayId"
+    }
+}
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 d06cbbb..7475235 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
@@ -17,6 +17,7 @@
 package com.android.systemui.biometrics.ui.viewmodel
 
 import android.content.res.Configuration
+import android.graphics.Point
 import android.hardware.biometrics.PromptInfo
 import android.hardware.face.FaceSensorPropertiesInternal
 import android.hardware.fingerprint.FingerprintSensorProperties
@@ -25,7 +26,10 @@
 import android.view.MotionEvent
 import androidx.test.filters.SmallTest
 import com.android.internal.widget.LockPatternUtils
+import com.android.systemui.Flags.FLAG_BP_TALKBACK
 import com.android.systemui.SysuiTestCase
+import com.android.systemui.biometrics.AuthController
+import com.android.systemui.biometrics.UdfpsUtils
 import com.android.systemui.biometrics.data.repository.FakeDisplayStateRepository
 import com.android.systemui.biometrics.data.repository.FakeFingerprintPropertyRepository
 import com.android.systemui.biometrics.data.repository.FakePromptRepository
@@ -33,6 +37,7 @@
 import com.android.systemui.biometrics.domain.interactor.DisplayStateInteractorImpl
 import com.android.systemui.biometrics.domain.interactor.PromptSelectorInteractor
 import com.android.systemui.biometrics.domain.interactor.PromptSelectorInteractorImpl
+import com.android.systemui.biometrics.domain.interactor.UdfpsOverlayInteractor
 import com.android.systemui.biometrics.extractAuthenticatorTypes
 import com.android.systemui.biometrics.faceSensorPropertiesInternal
 import com.android.systemui.biometrics.fingerprintSensorPropertiesInternal
@@ -45,8 +50,10 @@
 import com.android.systemui.coroutines.collectValues
 import com.android.systemui.display.data.repository.FakeDisplayRepository
 import com.android.systemui.res.R
-import com.android.systemui.statusbar.VibratorHelper
+import com.android.systemui.user.domain.interactor.SelectedUserInteractor
 import com.android.systemui.util.concurrency.FakeExecutor
+import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.whenever
 import com.android.systemui.util.time.FakeSystemClock
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.ExperimentalCoroutinesApi
@@ -77,7 +84,9 @@
     @JvmField @Rule var mockitoRule = MockitoJUnit.rule()
 
     @Mock private lateinit var lockPatternUtils: LockPatternUtils
-    @Mock private lateinit var vibrator: VibratorHelper
+    @Mock private lateinit var authController: AuthController
+    @Mock private lateinit var selectedUserInteractor: SelectedUserInteractor
+    @Mock private lateinit var udfpsUtils: UdfpsUtils
 
     private val fakeExecutor = FakeExecutor(FakeSystemClock())
     private val testScope = TestScope()
@@ -87,6 +96,7 @@
     private lateinit var displayStateRepository: FakeDisplayStateRepository
     private lateinit var displayRepository: FakeDisplayRepository
     private lateinit var displayStateInteractor: DisplayStateInteractor
+    private lateinit var udfpsOverlayInteractor: UdfpsOverlayInteractor
 
     private lateinit var selector: PromptSelectorInteractor
     private lateinit var viewModel: PromptViewModel
@@ -116,11 +126,24 @@
                 displayStateRepository,
                 displayRepository,
             )
+        udfpsOverlayInteractor =
+            UdfpsOverlayInteractor(
+                authController,
+                selectedUserInteractor,
+                testScope.backgroundScope
+            )
         selector =
             PromptSelectorInteractorImpl(fingerprintRepository, promptRepository, lockPatternUtils)
         selector.resetPrompt()
 
-        viewModel = PromptViewModel(displayStateInteractor, selector, mContext)
+        viewModel =
+            PromptViewModel(
+                displayStateInteractor,
+                selector,
+                mContext,
+                udfpsOverlayInteractor,
+                udfpsUtils
+            )
         iconViewModel = viewModel.iconViewModel
     }
 
@@ -1153,6 +1176,29 @@
         assertThat(size).isEqualTo(PromptSize.LARGE)
     }
 
+    @Test
+    fun hint_for_talkback_guidance() = runGenericTest {
+        mSetFlagsRule.enableFlags(FLAG_BP_TALKBACK)
+        val hint by collectLastValue(viewModel.accessibilityHint)
+
+        // Touches should fall outside of sensor area
+        whenever(udfpsUtils.getTouchInNativeCoordinates(any(), any(), any()))
+            .thenReturn(Point(0, 0))
+        whenever(udfpsUtils.onTouchOutsideOfSensorArea(any(), any(), any(), any(), any()))
+            .thenReturn("Direction")
+
+        viewModel.onAnnounceAccessibilityHint(
+            obtainMotionEvent(MotionEvent.ACTION_HOVER_ENTER),
+            true
+        )
+
+        if (testCase.modalities.hasUdfps) {
+            assertThat(hint?.isNotBlank()).isTrue()
+        } else {
+            assertThat(hint.isNullOrBlank()).isTrue()
+        }
+    }
+
     /** Asserts that the selected buttons are visible now. */
     private suspend fun TestScope.assertButtonsVisible(
         tryAgain: Boolean = false,
@@ -1220,14 +1266,19 @@
                     authenticatedModality = BiometricModality.Face,
                 ),
                 TestCase(
-                    fingerprint = fingerprintSensorPropertiesInternal(strong = true).first(),
+                    fingerprint =
+                        fingerprintSensorPropertiesInternal(
+                                strong = true,
+                                sensorType = FingerprintSensorProperties.TYPE_POWER_BUTTON
+                            )
+                            .first(),
                     authenticatedModality = BiometricModality.Fingerprint,
                 ),
                 TestCase(
                     fingerprint =
                         fingerprintSensorPropertiesInternal(
                                 strong = true,
-                                sensorType = FingerprintSensorProperties.TYPE_POWER_BUTTON
+                                sensorType = FingerprintSensorProperties.TYPE_UDFPS_OPTICAL
                             )
                             .first(),
                     authenticatedModality = BiometricModality.Fingerprint,
@@ -1264,20 +1315,30 @@
                 TestCase(
                     face = faceSensorPropertiesInternal(strong = true).first(),
                     fingerprint = fingerprintSensorPropertiesInternal(strong = true).first(),
-                    authenticatedModality = BiometricModality.Fingerprint,
-                ),
-                TestCase(
-                    face = faceSensorPropertiesInternal(strong = true).first(),
-                    fingerprint = fingerprintSensorPropertiesInternal(strong = true).first(),
                     authenticatedModality = BiometricModality.Face,
                     confirmationRequested = true,
                 ),
                 TestCase(
                     face = faceSensorPropertiesInternal(strong = true).first(),
-                    fingerprint = fingerprintSensorPropertiesInternal(strong = true).first(),
+                    fingerprint =
+                        fingerprintSensorPropertiesInternal(
+                                strong = true,
+                                sensorType = FingerprintSensorProperties.TYPE_POWER_BUTTON
+                            )
+                            .first(),
                     authenticatedModality = BiometricModality.Fingerprint,
                     confirmationRequested = true,
                 ),
+                TestCase(
+                    face = faceSensorPropertiesInternal(strong = true).first(),
+                    fingerprint =
+                        fingerprintSensorPropertiesInternal(
+                                strong = true,
+                                sensorType = FingerprintSensorProperties.TYPE_UDFPS_OPTICAL
+                            )
+                            .first(),
+                    authenticatedModality = BiometricModality.Fingerprint,
+                ),
             )
     }
 }
@@ -1309,6 +1370,9 @@
             else -> false
         }
 
+    val modalities: BiometricModalities
+        get() = BiometricModalities(fingerprint, face)
+
     val authenticatedByFingerprint: Boolean
         get() = authenticatedModality == BiometricModality.Fingerprint
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/SideFpsOverlayViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/SideFpsOverlayViewModelTest.kt
new file mode 100644
index 0000000..2267bdc
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/ui/viewmodel/SideFpsOverlayViewModelTest.kt
@@ -0,0 +1,576 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.biometrics.ui.viewmodel
+
+import android.app.ActivityTaskManager
+import android.content.res.Configuration.UI_MODE_NIGHT_NO
+import android.content.res.Configuration.UI_MODE_NIGHT_YES
+import android.graphics.Color
+import android.graphics.Rect
+import android.hardware.biometrics.SensorLocationInternal
+import android.hardware.display.DisplayManagerGlobal
+import android.os.Handler
+import android.view.Display
+import android.view.DisplayInfo
+import android.view.WindowInsets
+import android.view.WindowManager
+import android.view.WindowMetrics
+import androidx.test.filters.SmallTest
+import com.airbnb.lottie.model.KeyPath
+import com.android.keyguard.KeyguardSecurityModel
+import com.android.keyguard.KeyguardUpdateMonitor
+import com.android.settingslib.Utils
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.biometrics.FingerprintInteractiveToAuthProvider
+import com.android.systemui.biometrics.data.repository.FakeBiometricStatusRepository
+import com.android.systemui.biometrics.data.repository.FakeDisplayStateRepository
+import com.android.systemui.biometrics.data.repository.FakeFingerprintPropertyRepository
+import com.android.systemui.biometrics.domain.interactor.BiometricStatusInteractor
+import com.android.systemui.biometrics.domain.interactor.BiometricStatusInteractorImpl
+import com.android.systemui.biometrics.domain.interactor.DisplayStateInteractorImpl
+import com.android.systemui.biometrics.domain.interactor.SideFpsSensorInteractor
+import com.android.systemui.biometrics.shared.model.AuthenticationReason
+import com.android.systemui.biometrics.shared.model.DisplayRotation
+import com.android.systemui.biometrics.shared.model.FingerprintSensorType
+import com.android.systemui.biometrics.shared.model.LottieCallback
+import com.android.systemui.biometrics.shared.model.SensorStrength
+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.classifier.FalsingCollector
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.display.data.repository.FakeDisplayRepository
+import com.android.systemui.dump.logcatLogBuffer
+import com.android.systemui.keyguard.DismissCallbackRegistry
+import com.android.systemui.keyguard.data.repository.FakeBiometricSettingsRepository
+import com.android.systemui.keyguard.data.repository.FakeDeviceEntryFingerprintAuthRepository
+import com.android.systemui.keyguard.data.repository.FakeTrustRepository
+import com.android.systemui.keyguard.domain.interactor.DeviceEntrySideFpsOverlayInteractor
+import com.android.systemui.keyguard.domain.interactor.KeyguardFaceAuthInteractor
+import com.android.systemui.keyguard.ui.viewmodel.SideFpsProgressBarViewModel
+import com.android.systemui.log.SideFpsLogger
+import com.android.systemui.plugins.statusbar.StatusBarStateController
+import com.android.systemui.res.R
+import com.android.systemui.shared.Flags.FLAG_SIDEFPS_CONTROLLER_REFACTOR
+import com.android.systemui.statusbar.policy.KeyguardStateController
+import com.android.systemui.unfold.compat.ScreenSizeFoldProvider
+import com.android.systemui.user.domain.interactor.SelectedUserInteractor
+import com.android.systemui.util.concurrency.FakeExecutor
+import com.android.systemui.util.mockito.whenever
+import com.android.systemui.util.time.FakeSystemClock
+import com.google.common.truth.Truth.assertThat
+import java.util.Optional
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.test.StandardTestDispatcher
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+import org.mockito.Mock
+import org.mockito.Mockito.mock
+import org.mockito.Mockito.spy
+import org.mockito.junit.MockitoJUnit
+import org.mockito.junit.MockitoRule
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+@RunWith(JUnit4::class)
+class SideFpsOverlayViewModelTest : SysuiTestCase() {
+    @JvmField @Rule var mockitoRule: MockitoRule = MockitoJUnit.rule()
+
+    @Mock private lateinit var activityTaskManager: ActivityTaskManager
+    @Mock private lateinit var faceAuthInteractor: KeyguardFaceAuthInteractor
+    @Mock
+    private lateinit var fingerprintInteractiveToAuthProvider: FingerprintInteractiveToAuthProvider
+    @Mock private lateinit var keyguardUpdateMonitor: KeyguardUpdateMonitor
+    @Mock private lateinit var screenSizeFoldProvider: ScreenSizeFoldProvider
+    @Mock private lateinit var selectedUserInteractor: SelectedUserInteractor
+    @Mock private lateinit var windowManager: WindowManager
+
+    private val contextDisplayInfo = DisplayInfo()
+
+    private val bouncerRepository = FakeKeyguardBouncerRepository()
+    private val biometricSettingsRepository = FakeBiometricSettingsRepository()
+    private val biometricStatusRepository = FakeBiometricStatusRepository()
+    private val deviceEntryFingerprintAuthRepository = FakeDeviceEntryFingerprintAuthRepository()
+    private val displayRepository = FakeDisplayRepository()
+    private val displayStateRepository = FakeDisplayStateRepository()
+    private val fingerprintPropertyRepository = FakeFingerprintPropertyRepository()
+
+    private val indicatorColor =
+        Utils.getColorAttrDefaultColor(
+            context,
+            com.android.internal.R.attr.materialColorPrimaryFixed
+        )
+    private val outerRimColor =
+        Utils.getColorAttrDefaultColor(
+            context,
+            com.android.internal.R.attr.materialColorPrimaryFixedDim
+        )
+    private val chevronFill =
+        Utils.getColorAttrDefaultColor(
+            context,
+            com.android.internal.R.attr.materialColorOnPrimaryFixed
+        )
+    private val color_blue400 =
+        context.getColor(com.android.settingslib.color.R.color.settingslib_color_blue400)
+
+    private lateinit var alternateBouncerInteractor: AlternateBouncerInteractor
+    private lateinit var biometricStatusInteractor: BiometricStatusInteractor
+    private lateinit var deviceEntrySideFpsOverlayInteractor: DeviceEntrySideFpsOverlayInteractor
+    private lateinit var displayStateInteractor: DisplayStateInteractorImpl
+    private lateinit var primaryBouncerInteractor: PrimaryBouncerInteractor
+    private lateinit var sfpsSensorInteractor: SideFpsSensorInteractor
+
+    private lateinit var sideFpsProgressBarViewModel: SideFpsProgressBarViewModel
+
+    private lateinit var underTest: SideFpsOverlayViewModel
+
+    private var displayWidth: Int = 0
+    private var displayHeight: Int = 0
+    private var boundsWidth: Int = 0
+    private var boundsHeight: Int = 0
+
+    private lateinit var deviceConfig: DeviceConfig
+    private lateinit var sensorLocation: SensorLocationInternal
+
+    private val testScope = TestScope(StandardTestDispatcher())
+    private val fakeExecutor = FakeExecutor(FakeSystemClock())
+
+    enum class DeviceConfig {
+        X_ALIGNED,
+        Y_ALIGNED,
+    }
+
+    @Before
+    fun setup() {
+        mSetFlagsRule.enableFlags(FLAG_SIDEFPS_CONTROLLER_REFACTOR)
+
+        mContext = spy(mContext)
+
+        val resources = mContext.resources
+        whenever(mContext.display)
+            .thenReturn(
+                Display(mock(DisplayManagerGlobal::class.java), 1, contextDisplayInfo, resources)
+            )
+
+        alternateBouncerInteractor =
+            AlternateBouncerInteractor(
+                mock(StatusBarStateController::class.java),
+                mock(KeyguardStateController::class.java),
+                bouncerRepository,
+                fingerprintPropertyRepository,
+                biometricSettingsRepository,
+                FakeSystemClock(),
+                keyguardUpdateMonitor,
+                testScope.backgroundScope,
+            )
+
+        biometricStatusInteractor =
+            BiometricStatusInteractorImpl(activityTaskManager, biometricStatusRepository)
+
+        displayStateInteractor =
+            DisplayStateInteractorImpl(
+                testScope.backgroundScope,
+                mContext,
+                fakeExecutor,
+                displayStateRepository,
+                displayRepository,
+            )
+        displayStateInteractor.setScreenSizeFoldProvider(screenSizeFoldProvider)
+
+        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),
+                mContext,
+                keyguardUpdateMonitor,
+                FakeTrustRepository(),
+                testScope.backgroundScope,
+                selectedUserInteractor,
+                faceAuthInteractor
+            )
+
+        deviceEntrySideFpsOverlayInteractor =
+            DeviceEntrySideFpsOverlayInteractor(
+                mContext,
+                deviceEntryFingerprintAuthRepository,
+                primaryBouncerInteractor,
+                alternateBouncerInteractor,
+                keyguardUpdateMonitor
+            )
+
+        whenever(fingerprintInteractiveToAuthProvider.enabledForCurrentUser)
+            .thenReturn(MutableStateFlow(false))
+
+        sfpsSensorInteractor =
+            SideFpsSensorInteractor(
+                mContext,
+                fingerprintPropertyRepository,
+                windowManager,
+                displayStateInteractor,
+                Optional.of(fingerprintInteractiveToAuthProvider),
+                SideFpsLogger(logcatLogBuffer("SfpsLogger"))
+            )
+
+        sideFpsProgressBarViewModel =
+            SideFpsProgressBarViewModel(
+                mContext,
+                deviceEntryFingerprintAuthRepository,
+                sfpsSensorInteractor,
+                displayStateInteractor,
+                testScope.backgroundScope,
+            )
+
+        underTest =
+            SideFpsOverlayViewModel(
+                mContext,
+                biometricStatusInteractor,
+                deviceEntrySideFpsOverlayInteractor,
+                displayStateInteractor,
+                sfpsSensorInteractor,
+                sideFpsProgressBarViewModel,
+            )
+    }
+
+    @Test
+    fun updatesOverlayViewProperties_onDisplayRotationChange_xAlignedSensor() {
+        testScope.runTest {
+            setupTestConfiguration(
+                DeviceConfig.X_ALIGNED,
+                rotation = DisplayRotation.ROTATION_0,
+                isInRearDisplayMode = false
+            )
+
+            val overlayViewProperties by collectLastValue(underTest.overlayViewProperties)
+
+            runCurrent()
+
+            assertThat(overlayViewProperties?.indicatorAsset).isEqualTo(R.raw.sfps_pulse_landscape)
+            assertThat(overlayViewProperties?.overlayViewRotation).isEqualTo(0f)
+
+            displayStateRepository.setCurrentRotation(DisplayRotation.ROTATION_90)
+
+            assertThat(overlayViewProperties?.indicatorAsset).isEqualTo(R.raw.sfps_pulse)
+            assertThat(overlayViewProperties?.overlayViewRotation).isEqualTo(180f)
+
+            displayStateRepository.setCurrentRotation(DisplayRotation.ROTATION_180)
+
+            assertThat(overlayViewProperties?.indicatorAsset).isEqualTo(R.raw.sfps_pulse_landscape)
+            assertThat(overlayViewProperties?.overlayViewRotation).isEqualTo(180f)
+
+            displayStateRepository.setCurrentRotation(DisplayRotation.ROTATION_270)
+
+            assertThat(overlayViewProperties?.indicatorAsset).isEqualTo(R.raw.sfps_pulse)
+            assertThat(overlayViewProperties?.overlayViewRotation).isEqualTo(0f)
+        }
+    }
+
+    @Test
+    fun updatesOverlayViewProperties_onDisplayRotationChange_yAlignedSensor() {
+        testScope.runTest {
+            setupTestConfiguration(
+                DeviceConfig.Y_ALIGNED,
+                rotation = DisplayRotation.ROTATION_0,
+                isInRearDisplayMode = false
+            )
+
+            val overlayViewProperties by collectLastValue(underTest.overlayViewProperties)
+
+            runCurrent()
+
+            displayStateRepository.setCurrentRotation(DisplayRotation.ROTATION_0)
+            assertThat(overlayViewProperties?.indicatorAsset).isEqualTo(R.raw.sfps_pulse)
+            assertThat(overlayViewProperties?.overlayViewRotation).isEqualTo(0f)
+
+            displayStateRepository.setCurrentRotation(DisplayRotation.ROTATION_90)
+
+            assertThat(overlayViewProperties?.indicatorAsset).isEqualTo(R.raw.sfps_pulse_landscape)
+            assertThat(overlayViewProperties?.overlayViewRotation).isEqualTo(0f)
+
+            displayStateRepository.setCurrentRotation(DisplayRotation.ROTATION_180)
+            assertThat(overlayViewProperties?.indicatorAsset).isEqualTo(R.raw.sfps_pulse)
+            assertThat(overlayViewProperties?.overlayViewRotation).isEqualTo(180f)
+
+            displayStateRepository.setCurrentRotation(DisplayRotation.ROTATION_270)
+
+            assertThat(overlayViewProperties?.indicatorAsset).isEqualTo(R.raw.sfps_pulse_landscape)
+            assertThat(overlayViewProperties?.overlayViewRotation).isEqualTo(180f)
+        }
+    }
+
+    @Test
+    fun updatesOverlayViewParams_onDisplayRotationChange_xAlignedSensor() {
+        testScope.runTest {
+            setupTestConfiguration(
+                DeviceConfig.X_ALIGNED,
+                rotation = DisplayRotation.ROTATION_0,
+                isInRearDisplayMode = false
+            )
+
+            val overlayViewParams by collectLastValue(underTest.overlayViewParams)
+
+            underTest.setLottieBounds(Rect(0, 0, boundsWidth, boundsHeight))
+            runCurrent()
+
+            assertThat(overlayViewParams).isNotNull()
+            assertThat(overlayViewParams!!.x).isEqualTo(sensorLocation.sensorLocationX)
+            assertThat(overlayViewParams!!.y).isEqualTo(0)
+
+            displayStateRepository.setCurrentRotation(DisplayRotation.ROTATION_90)
+            assertThat(overlayViewParams).isNotNull()
+            assertThat(overlayViewParams!!.x).isEqualTo(0)
+            assertThat(overlayViewParams!!.y)
+                .isEqualTo(
+                    displayHeight - sensorLocation.sensorLocationX - sensorLocation.sensorRadius * 2
+                )
+
+            displayStateRepository.setCurrentRotation(DisplayRotation.ROTATION_180)
+            assertThat(overlayViewParams).isNotNull()
+            assertThat(overlayViewParams!!.x)
+                .isEqualTo(
+                    displayWidth - sensorLocation.sensorLocationX - sensorLocation.sensorRadius * 2
+                )
+            assertThat(overlayViewParams!!.y).isEqualTo(displayHeight - boundsHeight)
+
+            displayStateRepository.setCurrentRotation(DisplayRotation.ROTATION_270)
+            assertThat(overlayViewParams).isNotNull()
+            assertThat(overlayViewParams!!.x).isEqualTo(displayWidth - boundsWidth)
+            assertThat(overlayViewParams!!.y).isEqualTo(sensorLocation.sensorLocationX)
+        }
+    }
+
+    @Test
+    fun updatesOverlayViewParams_onDisplayRotationChange_yAlignedSensor() {
+        testScope.runTest {
+            setupTestConfiguration(
+                DeviceConfig.Y_ALIGNED,
+                rotation = DisplayRotation.ROTATION_0,
+                isInRearDisplayMode = false
+            )
+
+            val overlayViewParams by collectLastValue(underTest.overlayViewParams)
+
+            underTest.setLottieBounds(Rect(0, 0, boundsWidth, boundsHeight))
+            runCurrent()
+
+            assertThat(overlayViewParams).isNotNull()
+            assertThat(overlayViewParams!!.x).isEqualTo(displayWidth - boundsWidth)
+            assertThat(overlayViewParams!!.y).isEqualTo(sensorLocation.sensorLocationY)
+
+            displayStateRepository.setCurrentRotation(DisplayRotation.ROTATION_90)
+            assertThat(overlayViewParams).isNotNull()
+            assertThat(overlayViewParams!!.x).isEqualTo(sensorLocation.sensorLocationY)
+            assertThat(overlayViewParams!!.y).isEqualTo(0)
+
+            displayStateRepository.setCurrentRotation(DisplayRotation.ROTATION_180)
+            assertThat(overlayViewParams).isNotNull()
+            assertThat(overlayViewParams!!.x).isEqualTo(0)
+            assertThat(overlayViewParams!!.y)
+                .isEqualTo(
+                    displayHeight - sensorLocation.sensorLocationY - sensorLocation.sensorRadius * 2
+                )
+
+            displayStateRepository.setCurrentRotation(DisplayRotation.ROTATION_270)
+            assertThat(overlayViewParams).isNotNull()
+            assertThat(overlayViewParams!!.x)
+                .isEqualTo(
+                    displayWidth - sensorLocation.sensorLocationY - sensorLocation.sensorRadius * 2
+                )
+            assertThat(overlayViewParams!!.y).isEqualTo(displayHeight - boundsHeight)
+        }
+    }
+
+    @Test
+    fun updatesLottieCallbacks_onShowIndicatorForDeviceEntry() {
+        testScope.runTest {
+            val lottieCallbacks by collectLastValue(underTest.lottieCallbacks)
+
+            biometricStatusRepository.setFingerprintAuthenticationReason(
+                AuthenticationReason.NotRunning
+            )
+            sideFpsProgressBarViewModel.setVisible(false)
+
+            updatePrimaryBouncer(
+                isShowing = true,
+                isAnimatingAway = false,
+                fpsDetectionRunning = true,
+                isUnlockingWithFpAllowed = true
+            )
+            runCurrent()
+
+            assertThat(lottieCallbacks)
+                .contains(LottieCallback(KeyPath(".blue600", "**"), indicatorColor))
+            assertThat(lottieCallbacks)
+                .contains(LottieCallback(KeyPath(".blue400", "**"), outerRimColor))
+            assertThat(lottieCallbacks)
+                .contains(LottieCallback(KeyPath(".black", "**"), chevronFill))
+        }
+    }
+
+    @Test
+    fun updatesLottieCallbacks_onShowIndicatorForSystemServer_inDarkMode() {
+        testScope.runTest {
+            val lottieCallbacks by collectLastValue(underTest.lottieCallbacks)
+            setDarkMode(true)
+
+            biometricStatusRepository.setFingerprintAuthenticationReason(
+                AuthenticationReason.BiometricPromptAuthentication
+            )
+            sideFpsProgressBarViewModel.setVisible(false)
+
+            updatePrimaryBouncer(
+                isShowing = false,
+                isAnimatingAway = false,
+                fpsDetectionRunning = true,
+                isUnlockingWithFpAllowed = true
+            )
+            runCurrent()
+
+            assertThat(lottieCallbacks)
+                .contains(LottieCallback(KeyPath(".blue600", "**"), color_blue400))
+            assertThat(lottieCallbacks)
+                .contains(LottieCallback(KeyPath(".blue400", "**"), color_blue400))
+        }
+    }
+
+    @Test
+    fun updatesLottieCallbacks_onShowIndicatorForSystemServer_inLightMode() {
+        testScope.runTest {
+            val lottieCallbacks by collectLastValue(underTest.lottieCallbacks)
+            setDarkMode(false)
+
+            biometricStatusRepository.setFingerprintAuthenticationReason(
+                AuthenticationReason.BiometricPromptAuthentication
+            )
+            sideFpsProgressBarViewModel.setVisible(false)
+
+            updatePrimaryBouncer(
+                isShowing = false,
+                isAnimatingAway = false,
+                fpsDetectionRunning = true,
+                isUnlockingWithFpAllowed = true
+            )
+            runCurrent()
+
+            assertThat(lottieCallbacks)
+                .contains(LottieCallback(KeyPath(".black", "**"), Color.WHITE))
+            assertThat(lottieCallbacks)
+                .contains(LottieCallback(KeyPath(".blue600", "**"), color_blue400))
+            assertThat(lottieCallbacks)
+                .contains(LottieCallback(KeyPath(".blue400", "**"), color_blue400))
+        }
+    }
+
+    private fun setDarkMode(inDarkMode: Boolean) {
+        val uiMode =
+            if (inDarkMode) {
+                UI_MODE_NIGHT_YES
+            } else {
+                UI_MODE_NIGHT_NO
+            }
+
+        mContext.resources.configuration.uiMode = uiMode
+    }
+
+    private fun updatePrimaryBouncer(
+        isShowing: Boolean,
+        isAnimatingAway: Boolean,
+        fpsDetectionRunning: Boolean,
+        isUnlockingWithFpAllowed: Boolean,
+    ) {
+        bouncerRepository.setPrimaryShow(isShowing)
+        bouncerRepository.setPrimaryStartingToHide(false)
+        val primaryStartDisappearAnimation = if (isAnimatingAway) Runnable {} else null
+        bouncerRepository.setPrimaryStartDisappearAnimation(primaryStartDisappearAnimation)
+
+        whenever(keyguardUpdateMonitor.isFingerprintDetectionRunning)
+            .thenReturn(fpsDetectionRunning)
+        whenever(keyguardUpdateMonitor.isUnlockingWithFingerprintAllowed)
+            .thenReturn(isUnlockingWithFpAllowed)
+        mContext.orCreateTestableResources.addOverride(
+            R.bool.config_show_sidefps_hint_on_bouncer,
+            true
+        )
+    }
+
+    private suspend fun TestScope.setupTestConfiguration(
+        deviceConfig: DeviceConfig,
+        rotation: DisplayRotation = DisplayRotation.ROTATION_0,
+        isInRearDisplayMode: Boolean,
+    ) {
+        this@SideFpsOverlayViewModelTest.deviceConfig = deviceConfig
+
+        when (deviceConfig) {
+            DeviceConfig.X_ALIGNED -> {
+                displayWidth = 3000
+                displayHeight = 1500
+                boundsWidth = 200
+                boundsHeight = 100
+                sensorLocation = SensorLocationInternal("", 2500, 0, boundsWidth / 2)
+            }
+            DeviceConfig.Y_ALIGNED -> {
+                displayWidth = 2500
+                displayHeight = 2000
+                boundsWidth = 100
+                boundsHeight = 200
+                sensorLocation = SensorLocationInternal("", displayWidth, 300, boundsHeight / 2)
+            }
+        }
+
+        whenever(windowManager.maximumWindowMetrics)
+            .thenReturn(
+                WindowMetrics(
+                    Rect(0, 0, displayWidth, displayHeight),
+                    mock(WindowInsets::class.java),
+                )
+            )
+
+        contextDisplayInfo.uniqueId = DISPLAY_ID
+
+        fingerprintPropertyRepository.setProperties(
+            sensorId = 1,
+            strength = SensorStrength.STRONG,
+            sensorType = FingerprintSensorType.POWER_BUTTON,
+            sensorLocations = mapOf(DISPLAY_ID to sensorLocation)
+        )
+
+        displayStateRepository.setIsInRearDisplayMode(isInRearDisplayMode)
+
+        displayStateRepository.setCurrentRotation(rotation)
+
+        displayRepository.emitDisplayChangeEvent(0)
+        runCurrent()
+    }
+
+    companion object {
+        private const val DISPLAY_ID = "displayId"
+    }
+}
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 37a093e..dacf23a 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
@@ -37,6 +37,7 @@
 import com.android.systemui.keyguard.domain.interactor.KeyguardFaceAuthInteractor
 import com.android.systemui.plugins.ActivityStarter
 import com.android.systemui.res.R
+import com.android.systemui.shared.Flags.FLAG_SIDEFPS_CONTROLLER_REFACTOR
 import com.android.systemui.statusbar.policy.KeyguardStateController
 import com.android.systemui.user.domain.interactor.SelectedUserInteractor
 import com.android.systemui.util.mockito.any
@@ -342,6 +343,7 @@
         assertThat(underTest.willDismissWithAction()).isFalse()
     }
 
+    // TODO(b/288175061): remove with Flags.FLAG_SIDEFPS_CONTROLLER_REFACTOR
     @Test
     fun testSideFpsVisibility() {
         updateSideFpsVisibilityParameters(
@@ -355,6 +357,7 @@
         verify(repository).setSideFpsShowing(true)
     }
 
+    // TODO(b/288175061): remove with Flags.FLAG_SIDEFPS_CONTROLLER_REFACTOR
     @Test
     fun testSideFpsVisibility_notVisible() {
         updateSideFpsVisibilityParameters(
@@ -368,6 +371,7 @@
         verify(repository).setSideFpsShowing(false)
     }
 
+    // TODO(b/288175061): remove with Flags.FLAG_SIDEFPS_CONTROLLER_REFACTOR
     @Test
     fun testSideFpsVisibility_sfpsNotEnabled() {
         updateSideFpsVisibilityParameters(
@@ -381,6 +385,7 @@
         verify(repository).setSideFpsShowing(false)
     }
 
+    // TODO(b/288175061): remove with Flags.FLAG_SIDEFPS_CONTROLLER_REFACTOR
     @Test
     fun testSideFpsVisibility_fpsDetectionNotRunning() {
         updateSideFpsVisibilityParameters(
@@ -394,6 +399,7 @@
         verify(repository).setSideFpsShowing(false)
     }
 
+    // TODO(b/288175061): remove with Flags.FLAG_SIDEFPS_CONTROLLER_REFACTOR
     @Test
     fun testSideFpsVisibility_UnlockingWithFpNotAllowed() {
         updateSideFpsVisibilityParameters(
@@ -407,6 +413,7 @@
         verify(repository).setSideFpsShowing(false)
     }
 
+    // TODO(b/288175061): remove with Flags.FLAG_SIDEFPS_CONTROLLER_REFACTOR
     @Test
     fun testSideFpsVisibility_AnimatingAway() {
         updateSideFpsVisibilityParameters(
@@ -492,6 +499,7 @@
         isUnlockingWithFpAllowed: Boolean,
         isAnimatingAway: Boolean
     ) {
+        mSetFlagsRule.disableFlags(FLAG_SIDEFPS_CONTROLLER_REFACTOR)
         whenever(repository.primaryBouncerShow.value).thenReturn(isVisible)
         resources.addOverride(R.bool.config_show_sidefps_hint_on_bouncer, sfpsEnabled)
         whenever(keyguardUpdateMonitor.isFingerprintDetectionRunning)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/complication/DreamHomeControlsComplicationTest.java b/packages/SystemUI/tests/src/com/android/systemui/complication/DreamHomeControlsComplicationTest.java
index 2207180..07c980b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/complication/DreamHomeControlsComplicationTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/complication/DreamHomeControlsComplicationTest.java
@@ -21,14 +21,13 @@
 
 import static com.google.common.truth.Truth.assertThat;
 
-import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
 import android.content.ComponentName;
-import android.content.Context;
 import android.content.res.Resources;
 import android.testing.AndroidTestingRunner;
 import android.view.View;
@@ -48,6 +47,7 @@
 import com.android.systemui.dreams.DreamOverlayStateController;
 import com.android.systemui.plugins.ActivityStarter;
 import com.android.systemui.shared.condition.Monitor;
+import com.android.systemui.statusbar.policy.ConfigurationController;
 
 import org.junit.Before;
 import org.junit.Test;
@@ -71,9 +71,6 @@
     private DreamOverlayStateController mDreamOverlayStateController;
 
     @Mock
-    private Context mContext;
-
-    @Mock
     private Resources mResources;
 
     @Mock
@@ -100,6 +97,9 @@
     @Mock
     private UiEventLogger mUiEventLogger;
 
+    @Mock
+    private ConfigurationController mConfigurationController;
+
     @Captor
     private ArgumentCaptor<DreamOverlayStateController.Callback> mStateCallbackCaptor;
 
@@ -109,7 +109,8 @@
     public void setup() {
         MockitoAnnotations.initMocks(this);
 
-        when(mContext.getString(anyInt())).thenReturn("");
+        mContext.ensureTestableResources();
+
         when(mControlsComponent.getControlsController()).thenReturn(
                 Optional.of(mControlsController));
         when(mControlsComponent.getControlsListingController()).thenReturn(
@@ -225,6 +226,7 @@
                         mHomeControlsView,
                         mActivityStarter,
                         mContext,
+                        mConfigurationController,
                         mControlsComponent,
                         mUiEventLogger);
         viewController.onViewAttached();
@@ -237,6 +239,24 @@
         verify(mUiEventLogger).log(DreamOverlayUiEvent.DREAM_HOME_CONTROLS_TAPPED);
     }
 
+    @Test
+    public void testUnregistersConfigurationCallback() {
+        final DreamHomeControlsComplication.DreamHomeControlsChipViewController viewController =
+                new DreamHomeControlsComplication.DreamHomeControlsChipViewController(
+                        mHomeControlsView,
+                        mActivityStarter,
+                        mContext,
+                        mConfigurationController,
+                        mControlsComponent,
+                        mUiEventLogger);
+        viewController.onViewAttached();
+        verify(mConfigurationController).addCallback(any());
+        verify(mConfigurationController, never()).removeCallback(any());
+
+        viewController.onViewDetached();
+        verify(mConfigurationController).removeCallback(any());
+    }
+
     private void setHaveFavorites(boolean value) {
         final List<StructureInfo> favorites = mock(List.class);
         when(favorites.isEmpty()).thenReturn(!value);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/contrast/ContrastDialogDelegateTest.kt b/packages/SystemUI/tests/src/com/android/systemui/contrast/ContrastDialogDelegateTest.kt
index e931384..65f68f9 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/contrast/ContrastDialogDelegateTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/contrast/ContrastDialogDelegateTest.kt
@@ -22,6 +22,7 @@
 import android.testing.AndroidTestingRunner
 import android.testing.TestableLooper.RunWithLooper
 import android.view.LayoutInflater
+import android.view.View
 import android.widget.FrameLayout
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
@@ -39,6 +40,8 @@
 import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
+import org.mockito.ArgumentCaptor
+import org.mockito.ArgumentMatchers.anyInt
 import org.mockito.Mock
 import org.mockito.Mockito.eq
 import org.mockito.Mockito.verify
@@ -73,13 +76,20 @@
         if (Looper.myLooper() == null) Looper.prepare()
 
         mContrastDialogDelegate =
-                ContrastDialogDelegate(
-                        sysuiDialogFactory,
-                        mainExecutor,
-                        mockUiModeManager,
-                        mockUserTracker,
-                        mockSecureSettings
-                )
+            ContrastDialogDelegate(
+                sysuiDialogFactory,
+                mainExecutor,
+                mockUiModeManager,
+                mockUserTracker,
+                mockSecureSettings
+            )
+
+        mContrastDialogDelegate.createDialog()
+        val viewCaptor = ArgumentCaptor.forClass(View::class.java)
+        verify(sysuiDialog).setView(viewCaptor.capture())
+        whenever(sysuiDialog.requireViewById(anyInt()) as View?).then {
+            viewCaptor.value.requireViewById(it.getArgument(0))
+        }
     }
 
     @Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java
index ae5f625..4ef18cf 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/KeyguardViewMediatorTest.java
@@ -93,6 +93,7 @@
 import com.android.systemui.flags.FakeFeatureFlags;
 import com.android.systemui.flags.Flags;
 import com.android.systemui.flags.SystemPropertiesHelper;
+import com.android.systemui.keyguard.domain.interactor.KeyguardInteractor;
 import com.android.systemui.keyguard.ui.viewmodel.DreamingToLockscreenTransitionViewModel;
 import com.android.systemui.log.SessionTracker;
 import com.android.systemui.navigationbar.NavigationModeController;
@@ -190,6 +191,7 @@
     private @Mock ShadeInteractor mShadeInteractor;
     private @Mock ShadeWindowLogger mShadeWindowLogger;
     private @Mock SelectedUserInteractor mSelectedUserInteractor;
+    private @Mock KeyguardInteractor mKeyguardInteractor;
     private @Captor ArgumentCaptor<KeyguardStateController.Callback>
             mKeyguardStateControllerCallback;
     private @Captor ArgumentCaptor<KeyguardUpdateMonitorCallback>
@@ -1131,7 +1133,8 @@
                 () -> mDreamingToLockscreenTransitionViewModel,
                 mSystemPropertiesHelper,
                 () -> mock(WindowManagerLockscreenVisibilityManager.class),
-                mSelectedUserInteractor);
+                mSelectedUserInteractor,
+                mKeyguardInteractor);
         mViewMediator.start();
 
         mViewMediator.registerCentralSurfaces(mCentralSurfaces, null, null, null, null, null);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/DeviceEntrySideFpsOverlayInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/DeviceEntrySideFpsOverlayInteractorTest.kt
new file mode 100644
index 0000000..70d3f81
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/DeviceEntrySideFpsOverlayInteractorTest.kt
@@ -0,0 +1,238 @@
+/*
+ * 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.os.Handler
+import android.platform.test.annotations.RequiresFlagsEnabled
+import androidx.test.filters.SmallTest
+import com.android.keyguard.KeyguardSecurityModel
+import com.android.keyguard.KeyguardUpdateMonitor
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.biometrics.data.repository.FakeFingerprintPropertyRepository
+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.classifier.FalsingCollector
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.keyguard.DismissCallbackRegistry
+import com.android.systemui.keyguard.data.repository.FakeBiometricSettingsRepository
+import com.android.systemui.keyguard.data.repository.FakeDeviceEntryFingerprintAuthRepository
+import com.android.systemui.keyguard.data.repository.FakeTrustRepository
+import com.android.systemui.plugins.statusbar.StatusBarStateController
+import com.android.systemui.res.R
+import com.android.systemui.shared.Flags.FLAG_SIDEFPS_CONTROLLER_REFACTOR
+import com.android.systemui.statusbar.policy.KeyguardStateController
+import com.android.systemui.user.domain.interactor.SelectedUserInteractor
+import com.android.systemui.util.mockito.whenever
+import com.android.systemui.util.time.FakeSystemClock
+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
+import kotlinx.coroutines.test.runTest
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+import org.mockito.Mock
+import org.mockito.Mockito.mock
+import org.mockito.junit.MockitoJUnit
+import org.mockito.junit.MockitoRule
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@RequiresFlagsEnabled(FLAG_SIDEFPS_CONTROLLER_REFACTOR)
+@SmallTest
+@RunWith(JUnit4::class)
+class DeviceEntrySideFpsOverlayInteractorTest : SysuiTestCase() {
+    @JvmField @Rule var mockitoRule: MockitoRule = MockitoJUnit.rule()
+
+    @Mock private lateinit var faceAuthInteractor: KeyguardFaceAuthInteractor
+    @Mock private lateinit var keyguardUpdateMonitor: KeyguardUpdateMonitor
+    @Mock private lateinit var mSelectedUserInteractor: SelectedUserInteractor
+
+    private val bouncerRepository = FakeKeyguardBouncerRepository()
+    private val biometricSettingsRepository = FakeBiometricSettingsRepository()
+
+    private lateinit var primaryBouncerInteractor: PrimaryBouncerInteractor
+    private lateinit var alternateBouncerInteractor: AlternateBouncerInteractor
+
+    private lateinit var underTest: DeviceEntrySideFpsOverlayInteractor
+
+    private val testScope = TestScope(StandardTestDispatcher())
+
+    @Before
+    fun setup() {
+        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),
+                mContext,
+                keyguardUpdateMonitor,
+                FakeTrustRepository(),
+                testScope.backgroundScope,
+                mSelectedUserInteractor,
+                faceAuthInteractor
+            )
+        alternateBouncerInteractor =
+            AlternateBouncerInteractor(
+                mock(StatusBarStateController::class.java),
+                mock(KeyguardStateController::class.java),
+                bouncerRepository,
+                FakeFingerprintPropertyRepository(),
+                biometricSettingsRepository,
+                FakeSystemClock(),
+                keyguardUpdateMonitor,
+                testScope.backgroundScope,
+            )
+        underTest =
+            DeviceEntrySideFpsOverlayInteractor(
+                mContext,
+                FakeDeviceEntryFingerprintAuthRepository(),
+                primaryBouncerInteractor,
+                alternateBouncerInteractor,
+                keyguardUpdateMonitor
+            )
+    }
+
+    @Test
+    fun updatesShowIndicatorForDeviceEntry_onPrimaryBouncerShowing() =
+        testScope.runTest {
+            val showIndicatorForDeviceEntry by
+                collectLastValue(underTest.showIndicatorForDeviceEntry)
+            runCurrent()
+
+            updatePrimaryBouncer(
+                isShowing = true,
+                isAnimatingAway = false,
+                fpsDetectionRunning = true,
+                isUnlockingWithFpAllowed = true
+            )
+            assertThat(showIndicatorForDeviceEntry).isEqualTo(true)
+        }
+
+    @Test
+    fun updatesShowIndicatorForDeviceEntry_onPrimaryBouncerHidden() =
+        testScope.runTest {
+            val showIndicatorForDeviceEntry by
+                collectLastValue(underTest.showIndicatorForDeviceEntry)
+            runCurrent()
+
+            updatePrimaryBouncer(
+                isShowing = false,
+                isAnimatingAway = false,
+                fpsDetectionRunning = true,
+                isUnlockingWithFpAllowed = true
+            )
+            assertThat(showIndicatorForDeviceEntry).isEqualTo(false)
+        }
+
+    @Test
+    fun updatesShowIndicatorForDeviceEntry_fromPrimaryBouncer_whenFpsDetectionNotRunning() {
+        testScope.runTest {
+            val showIndicatorForDeviceEntry by
+                collectLastValue(underTest.showIndicatorForDeviceEntry)
+            runCurrent()
+
+            updatePrimaryBouncer(
+                isShowing = true,
+                isAnimatingAway = false,
+                fpsDetectionRunning = false,
+                isUnlockingWithFpAllowed = true
+            )
+            assertThat(showIndicatorForDeviceEntry).isEqualTo(false)
+        }
+    }
+
+    @Test
+    fun updatesShowIndicatorForDeviceEntry_fromPrimaryBouncer_onUnlockingWithFpDisallowed() {
+        testScope.runTest {
+            val showIndicatorForDeviceEntry by
+                collectLastValue(underTest.showIndicatorForDeviceEntry)
+            runCurrent()
+
+            updatePrimaryBouncer(
+                isShowing = true,
+                isAnimatingAway = false,
+                fpsDetectionRunning = true,
+                isUnlockingWithFpAllowed = false
+            )
+            assertThat(showIndicatorForDeviceEntry).isEqualTo(false)
+        }
+    }
+
+    @Test
+    fun updatesShowIndicatorForDeviceEntry_onPrimaryBouncerAnimatingAway() {
+        testScope.runTest {
+            val showIndicatorForDeviceEntry by
+                collectLastValue(underTest.showIndicatorForDeviceEntry)
+            runCurrent()
+
+            updatePrimaryBouncer(
+                isShowing = true,
+                isAnimatingAway = true,
+                fpsDetectionRunning = true,
+                isUnlockingWithFpAllowed = true
+            )
+            assertThat(showIndicatorForDeviceEntry).isEqualTo(false)
+        }
+    }
+
+    @Test
+    fun updatesShowIndicatorForDeviceEntry_onAlternateBouncerRequest() =
+        testScope.runTest {
+            val showIndicatorForDeviceEntry by
+                collectLastValue(underTest.showIndicatorForDeviceEntry)
+            runCurrent()
+
+            bouncerRepository.setAlternateVisible(true)
+            assertThat(showIndicatorForDeviceEntry).isEqualTo(true)
+
+            bouncerRepository.setAlternateVisible(false)
+            assertThat(showIndicatorForDeviceEntry).isEqualTo(false)
+        }
+
+    private fun updatePrimaryBouncer(
+        isShowing: Boolean,
+        isAnimatingAway: Boolean,
+        fpsDetectionRunning: Boolean,
+        isUnlockingWithFpAllowed: Boolean,
+    ) {
+        bouncerRepository.setPrimaryShow(isShowing)
+        bouncerRepository.setPrimaryStartingToHide(false)
+        val primaryStartDisappearAnimation = if (isAnimatingAway) Runnable {} else null
+        bouncerRepository.setPrimaryStartDisappearAnimation(primaryStartDisappearAnimation)
+
+        whenever(keyguardUpdateMonitor.isFingerprintDetectionRunning)
+            .thenReturn(fpsDetectionRunning)
+        whenever(keyguardUpdateMonitor.isUnlockingWithFingerprintAllowed)
+            .thenReturn(isUnlockingWithFpAllowed)
+        mContext.orCreateTestableResources.addOverride(
+            R.bool.config_show_sidefps_hint_on_bouncer,
+            true
+        )
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardFaceAuthInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardFaceAuthInteractorTest.kt
index 4ab8e28..6eb95bd 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardFaceAuthInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/KeyguardFaceAuthInteractorTest.kt
@@ -137,7 +137,6 @@
                 mContext,
                 testScope.backgroundScope,
                 dispatcher,
-                dispatcher,
                 faceAuthRepository,
                 {
                     PrimaryBouncerInteractor(
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelTest.kt
index 6878007..459a74c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelTest.kt
@@ -184,6 +184,13 @@
         }
 
     @Test
+    fun translationYInitialValueIsZero() =
+        testScope.runTest {
+            val translationY by collectLastValue(underTest.translationY)
+            assertThat(translationY).isEqualTo(0)
+        }
+
+    @Test
     fun translationAndScaleFromBurnInNotDozing() =
         testScope.runTest {
             val translationX by collectLastValue(underTest.translationX)
diff --git a/packages/SystemUI/tests/src/com/android/systemui/notetask/quickaffordance/NoteTaskQuickAffordanceConfigTest.kt b/packages/SystemUI/tests/src/com/android/systemui/notetask/quickaffordance/NoteTaskQuickAffordanceConfigTest.kt
index 119ffd2..ebd34de 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/notetask/quickaffordance/NoteTaskQuickAffordanceConfigTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/notetask/quickaffordance/NoteTaskQuickAffordanceConfigTest.kt
@@ -28,7 +28,6 @@
 import android.test.suitebuilder.annotation.SmallTest
 import android.testing.AndroidTestingRunner
 import com.android.dx.mockito.inline.extended.ExtendedMockito
-import com.android.systemui.res.R
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.common.shared.model.ContentDescription
 import com.android.systemui.common.shared.model.Icon
@@ -40,6 +39,7 @@
 import com.android.systemui.notetask.NoteTaskController
 import com.android.systemui.notetask.NoteTaskEntryPoint
 import com.android.systemui.notetask.NoteTaskInfoResolver
+import com.android.systemui.res.R
 import com.android.systemui.stylus.StylusManager
 import com.android.systemui.util.concurrency.FakeExecutor
 import com.android.systemui.util.mockito.any
@@ -134,12 +134,9 @@
     // region lockScreenState
     @Test
     fun lockScreenState_stylusUsed_userUnlocked_isSelected_shouldEmitVisible() = runTest {
-        TestConfig()
-            .setStylusEverUsed(true)
-            .setUserUnlocked(true)
-            .setConfigSelections(mock<NoteTaskQuickAffordanceConfig>())
-
         val underTest = createUnderTest()
+        TestConfig().setStylusEverUsed(true).setUserUnlocked(true).setConfigSelections(underTest)
+
         val actual by collectLastValue(underTest.lockScreenState)
 
         assertThat(actual).isEqualTo(createLockScreenStateVisible())
@@ -148,10 +145,11 @@
     @Test
     fun lockScreenState_stylusUsed_userUnlocked_isSelected_noDefaultNotesAppSet_shouldEmitHidden() =
         runTest {
+            val underTest = createUnderTest()
             TestConfig()
                 .setStylusEverUsed(true)
                 .setUserUnlocked(true)
-                .setConfigSelections(mock<NoteTaskQuickAffordanceConfig>())
+                .setConfigSelections(underTest)
             whenever(
                     roleManager.getRoleHoldersAsUser(
                         eq(RoleManager.ROLE_NOTES),
@@ -160,7 +158,6 @@
                 )
                 .thenReturn(emptyList())
 
-            val underTest = createUnderTest()
             val actual by collectLastValue(underTest.lockScreenState)
 
             assertThat(actual).isEqualTo(LockScreenState.Hidden)
@@ -168,12 +165,9 @@
 
     @Test
     fun lockScreenState_stylusUnused_userUnlocked_isSelected_shouldEmitHidden() = runTest {
-        TestConfig()
-            .setStylusEverUsed(false)
-            .setUserUnlocked(true)
-            .setConfigSelections(mock<NoteTaskQuickAffordanceConfig>())
-
         val underTest = createUnderTest()
+        TestConfig().setStylusEverUsed(false).setUserUnlocked(true).setConfigSelections(underTest)
+
         val actual by collectLastValue(underTest.lockScreenState)
 
         assertThat(actual).isEqualTo(LockScreenState.Hidden)
@@ -181,25 +175,22 @@
 
     @Test
     fun lockScreenState_stylusUsed_userLocked_isSelected_shouldEmitHidden() = runTest {
-        TestConfig()
-            .setStylusEverUsed(true)
-            .setUserUnlocked(false)
-            .setConfigSelections(mock<NoteTaskQuickAffordanceConfig>())
-
         val underTest = createUnderTest()
+        TestConfig().setStylusEverUsed(true).setUserUnlocked(false).setConfigSelections(underTest)
+
         val actual by collectLastValue(underTest.lockScreenState)
 
         assertThat(actual).isEqualTo(LockScreenState.Hidden)
     }
 
     @Test
-    fun lockScreenState_stylusUsed_userUnlocked_noSelected_shouldEmitVisible() = runTest {
+    fun lockScreenState_stylusUsed_userUnlocked_noSelected_shouldEmitHidden() = runTest {
         TestConfig().setStylusEverUsed(true).setUserUnlocked(true).setConfigSelections()
 
         val underTest = createUnderTest()
         val actual by collectLastValue(underTest.lockScreenState)
 
-        assertThat(actual).isEqualTo(createLockScreenStateVisible())
+        assertThat(actual).isEqualTo(LockScreenState.Hidden)
     }
 
     @Test
@@ -223,13 +214,13 @@
     }
 
     @Test
-    fun lockScreenState_stylusUsed_userUnlocked_customSelections_shouldEmitVisible() = runTest {
+    fun lockScreenState_stylusUsed_userUnlocked_customSelections_shouldEmitHidden() = runTest {
         TestConfig().setStylusEverUsed(true).setUserUnlocked(true).setConfigSelections(mock())
 
         val underTest = createUnderTest()
         val actual by collectLastValue(underTest.lockScreenState)
 
-        assertThat(actual).isEqualTo(createLockScreenStateVisible())
+        assertThat(actual).isEqualTo(LockScreenState.Hidden)
     }
 
     @Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/privacy/PrivacyDialogControllerV2Test.kt b/packages/SystemUI/tests/src/com/android/systemui/privacy/PrivacyDialogControllerV2Test.kt
index 0a8c0ab..e4432f3 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/privacy/PrivacyDialogControllerV2Test.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/privacy/PrivacyDialogControllerV2Test.kt
@@ -31,18 +31,20 @@
 import android.permission.PermissionManager
 import android.testing.AndroidTestingRunner
 import android.view.View
+import android.widget.LinearLayout
 import androidx.test.filters.SmallTest
 import com.android.internal.logging.UiEventLogger
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.animation.DialogLaunchAnimator
+import com.android.systemui.animation.LaunchableView
 import com.android.systemui.appops.AppOpsController
 import com.android.systemui.plugins.ActivityStarter
 import com.android.systemui.privacy.logging.PrivacyLogger
 import com.android.systemui.settings.UserTracker
 import com.android.systemui.statusbar.policy.KeyguardStateController
 import com.android.systemui.util.concurrency.FakeExecutor
-import com.android.systemui.util.mockito.capture
 import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.capture
 import com.android.systemui.util.mockito.eq
 import com.android.systemui.util.time.FakeSystemClock
 import com.google.common.truth.Truth.assertThat
@@ -56,12 +58,12 @@
 import org.mockito.ArgumentMatchers.anyString
 import org.mockito.Captor
 import org.mockito.Mock
-import org.mockito.Mockito.`when`
 import org.mockito.Mockito.atLeastOnce
 import org.mockito.Mockito.mock
 import org.mockito.Mockito.never
 import org.mockito.Mockito.times
 import org.mockito.Mockito.verify
+import org.mockito.Mockito.`when`
 import org.mockito.MockitoAnnotations
 
 @SmallTest
@@ -83,60 +85,48 @@
         private val TEST_INTENT = Intent("test_intent_action")
     }
 
-    @Mock
-    private lateinit var dialog: PrivacyDialogV2
-    @Mock
-    private lateinit var permissionManager: PermissionManager
-    @Mock
-    private lateinit var packageManager: PackageManager
-    @Mock
-    private lateinit var privacyItemController: PrivacyItemController
-    @Mock
-    private lateinit var userTracker: UserTracker
-    @Mock
-    private lateinit var activityStarter: ActivityStarter
-    @Mock
-    private lateinit var privacyLogger: PrivacyLogger
-    @Mock
-    private lateinit var keyguardStateController: KeyguardStateController
-    @Mock
-    private lateinit var appOpsController: AppOpsController
+    @Mock private lateinit var dialog: PrivacyDialogV2
+    @Mock private lateinit var permissionManager: PermissionManager
+    @Mock private lateinit var packageManager: PackageManager
+    @Mock private lateinit var privacyItemController: PrivacyItemController
+    @Mock private lateinit var userTracker: UserTracker
+    @Mock private lateinit var activityStarter: ActivityStarter
+    @Mock private lateinit var privacyLogger: PrivacyLogger
+    @Mock private lateinit var keyguardStateController: KeyguardStateController
+    @Mock private lateinit var appOpsController: AppOpsController
     @Captor
     private lateinit var dialogDismissedCaptor: ArgumentCaptor<PrivacyDialogV2.OnDialogDismissed>
-    @Captor
-    private lateinit var activityStartedCaptor: ArgumentCaptor<ActivityStarter.Callback>
-    @Captor
-    private lateinit var intentCaptor: ArgumentCaptor<Intent>
-    @Mock
-    private lateinit var uiEventLogger: UiEventLogger
-    @Mock
-    private lateinit var dialogLaunchAnimator: DialogLaunchAnimator
+    @Captor private lateinit var activityStartedCaptor: ArgumentCaptor<ActivityStarter.Callback>
+    @Captor private lateinit var intentCaptor: ArgumentCaptor<Intent>
+    @Mock private lateinit var uiEventLogger: UiEventLogger
+    @Mock private lateinit var dialogLaunchAnimator: DialogLaunchAnimator
 
     private val backgroundExecutor = FakeExecutor(FakeSystemClock())
     private val uiExecutor = FakeExecutor(FakeSystemClock())
     private lateinit var controller: PrivacyDialogControllerV2
     private var nextUid: Int = 0
 
-    private val dialogProvider = object : PrivacyDialogControllerV2.DialogProvider {
-        var list: List<PrivacyDialogV2.PrivacyElement>? = null
-        var manageApp: ((String, Int, Intent) -> Unit)? = null
-        var closeApp: ((String, Int) -> Unit)? = null
-        var openPrivacyDashboard: (() -> Unit)? = null
+    private val dialogProvider =
+        object : PrivacyDialogControllerV2.DialogProvider {
+            var list: List<PrivacyDialogV2.PrivacyElement>? = null
+            var manageApp: ((String, Int, Intent) -> Unit)? = null
+            var closeApp: ((String, Int) -> Unit)? = null
+            var openPrivacyDashboard: (() -> Unit)? = null
 
-        override fun makeDialog(
-            context: Context,
-            list: List<PrivacyDialogV2.PrivacyElement>,
-            manageApp: (String, Int, Intent) -> Unit,
-            closeApp: (String, Int) -> Unit,
-            openPrivacyDashboard: () -> Unit
-        ): PrivacyDialogV2 {
-            this.list = list
-            this.manageApp = manageApp
-            this.closeApp = closeApp
-            this.openPrivacyDashboard = openPrivacyDashboard
-            return dialog
+            override fun makeDialog(
+                context: Context,
+                list: List<PrivacyDialogV2.PrivacyElement>,
+                manageApp: (String, Int, Intent) -> Unit,
+                closeApp: (String, Int) -> Unit,
+                openPrivacyDashboard: () -> Unit
+            ): PrivacyDialogV2 {
+                this.list = list
+                this.manageApp = manageApp
+                this.closeApp = closeApp
+                this.openPrivacyDashboard = openPrivacyDashboard
+                return dialog
+            }
         }
-    }
 
     @Before
     fun setUp() {
@@ -144,7 +134,8 @@
         nextUid = 0
         setUpDefaultMockResponses()
 
-        controller = PrivacyDialogControllerV2(
+        controller =
+            PrivacyDialogControllerV2(
                 permissionManager,
                 packageManager,
                 privacyItemController,
@@ -158,7 +149,7 @@
                 uiEventLogger,
                 dialogLaunchAnimator,
                 dialogProvider
-        )
+            )
     }
 
     @After
@@ -197,7 +188,7 @@
         verify(packageManager, never()).getApplicationInfoAsUser(anyString(), anyInt(), anyInt())
         backgroundExecutor.runAllReady()
         verify(packageManager, atLeastOnce())
-                .getApplicationInfoAsUser(anyString(), anyInt(), anyInt())
+            .getApplicationInfoAsUser(anyString(), anyInt(), anyInt())
     }
 
     @Test
@@ -208,20 +199,25 @@
         controller.showDialog(context)
         exhaustExecutors()
 
-        verify(dialogLaunchAnimator, never()).showFromView(any(), any(), any(), anyBoolean())
+        verify(dialogLaunchAnimator, never()).show(any(), any(), anyBoolean())
         verify(dialog).show()
     }
 
     @Test
     fun testShowDialogShowsDialogWithView() {
-        val view = View(context)
+        val parent = LinearLayout(context)
+        val view =
+            object : View(context), LaunchableView {
+                override fun setShouldBlockVisibilityChanges(block: Boolean) {}
+            }
+        parent.addView(view)
         val usage = createMockPermGroupUsage()
         `when`(permissionManager.getIndicatorAppOpUsageData(anyBoolean())).thenReturn(listOf(usage))
 
         controller.showDialog(context, view)
         exhaustExecutors()
 
-        verify(dialogLaunchAnimator).showFromView(dialog, view)
+        verify(dialogLaunchAnimator).show(eq(dialog), any(), anyBoolean())
         verify(dialog, never()).show()
     }
 
@@ -276,7 +272,8 @@
 
     @Test
     fun testSingleElementInList() {
-        val usage = createMockPermGroupUsage(
+        val usage =
+            createMockPermGroupUsage(
                 packageName = TEST_PACKAGE_NAME,
                 uid = generateUidForUser(USER_ID),
                 permissionGroupName = PERM_CAMERA,
@@ -285,7 +282,7 @@
                 isPhoneCall = false,
                 attributionTag = null,
                 proxyLabel = TEST_PROXY_LABEL
-        )
+            )
         `when`(permissionManager.getIndicatorAppOpUsageData(anyBoolean())).thenReturn(listOf(usage))
 
         controller.showDialog(context)
@@ -304,33 +301,38 @@
             assertThat(list.get(0).isPhoneCall).isFalse()
             assertThat(list.get(0).isService).isFalse()
             assertThat(list.get(0).permGroupName).isEqualTo(PERM_CAMERA)
-            assertThat(isIntentEqual(list.get(0).navigationIntent!!,
-                    controller.getDefaultManageAppPermissionsIntent(TEST_PACKAGE_NAME, USER_ID)))
-                    .isTrue()
+            assertThat(
+                    isIntentEqual(
+                        list.get(0).navigationIntent!!,
+                        controller.getDefaultManageAppPermissionsIntent(TEST_PACKAGE_NAME, USER_ID)
+                    )
+                )
+                .isTrue()
         }
     }
 
     private fun isIntentEqual(actual: Intent, expected: Intent): Boolean {
         return actual.action == expected.action &&
-                actual.getStringExtra(Intent.EXTRA_PACKAGE_NAME) ==
+            actual.getStringExtra(Intent.EXTRA_PACKAGE_NAME) ==
                 expected.getStringExtra(Intent.EXTRA_PACKAGE_NAME) &&
-                actual.getParcelableExtra(Intent.EXTRA_USER) as? UserHandle ==
+            actual.getParcelableExtra(Intent.EXTRA_USER) as? UserHandle ==
                 expected.getParcelableExtra(Intent.EXTRA_USER) as? UserHandle
     }
 
     @Test
     fun testTwoElementsDifferentType_sorted() {
-        val usage_camera = createMockPermGroupUsage(
+        val usage_camera =
+            createMockPermGroupUsage(
                 packageName = "${TEST_PACKAGE_NAME}_camera",
                 permissionGroupName = PERM_CAMERA
-        )
-        val usage_microphone = createMockPermGroupUsage(
+            )
+        val usage_microphone =
+            createMockPermGroupUsage(
                 packageName = "${TEST_PACKAGE_NAME}_microphone",
                 permissionGroupName = PERM_MICROPHONE
-        )
-        `when`(permissionManager.getIndicatorAppOpUsageData(anyBoolean())).thenReturn(
-                listOf(usage_microphone, usage_camera)
-        )
+            )
+        `when`(permissionManager.getIndicatorAppOpUsageData(anyBoolean()))
+            .thenReturn(listOf(usage_microphone, usage_camera))
 
         controller.showDialog(context)
         exhaustExecutors()
@@ -343,17 +345,12 @@
 
     @Test
     fun testTwoElementsSameType_oneActive() {
-        val usage_active = createMockPermGroupUsage(
-                packageName = "${TEST_PACKAGE_NAME}_active",
-                isActive = true
-        )
-        val usage_recent = createMockPermGroupUsage(
-                packageName = "${TEST_PACKAGE_NAME}_recent",
-                isActive = false
-        )
-        `when`(permissionManager.getIndicatorAppOpUsageData(anyBoolean())).thenReturn(
-                listOf(usage_recent, usage_active)
-        )
+        val usage_active =
+            createMockPermGroupUsage(packageName = "${TEST_PACKAGE_NAME}_active", isActive = true)
+        val usage_recent =
+            createMockPermGroupUsage(packageName = "${TEST_PACKAGE_NAME}_recent", isActive = false)
+        `when`(permissionManager.getIndicatorAppOpUsageData(anyBoolean()))
+            .thenReturn(listOf(usage_recent, usage_active))
 
         controller.showDialog(context)
         exhaustExecutors()
@@ -364,19 +361,20 @@
 
     @Test
     fun testTwoElementsSameType_twoActive() {
-        val usage_active = createMockPermGroupUsage(
+        val usage_active =
+            createMockPermGroupUsage(
                 packageName = "${TEST_PACKAGE_NAME}_active",
                 isActive = true,
                 lastAccessTimeMillis = 0L
-        )
-        val usage_active_moreRecent = createMockPermGroupUsage(
+            )
+        val usage_active_moreRecent =
+            createMockPermGroupUsage(
                 packageName = "${TEST_PACKAGE_NAME}_active_recent",
                 isActive = true,
                 lastAccessTimeMillis = 1L
-        )
-        `when`(permissionManager.getIndicatorAppOpUsageData(anyBoolean())).thenReturn(
-                listOf(usage_active, usage_active_moreRecent)
-        )
+            )
+        `when`(permissionManager.getIndicatorAppOpUsageData(anyBoolean()))
+            .thenReturn(listOf(usage_active, usage_active_moreRecent))
         controller.showDialog(context)
         exhaustExecutors()
         assertThat(dialogProvider.list).hasSize(2)
@@ -386,24 +384,26 @@
 
     @Test
     fun testManyElementsSameType_bothRecent() {
-        val usage_recent = createMockPermGroupUsage(
+        val usage_recent =
+            createMockPermGroupUsage(
                 packageName = "${TEST_PACKAGE_NAME}_recent",
                 isActive = false,
                 lastAccessTimeMillis = 0L
-        )
-        val usage_moreRecent = createMockPermGroupUsage(
+            )
+        val usage_moreRecent =
+            createMockPermGroupUsage(
                 packageName = "${TEST_PACKAGE_NAME}_moreRecent",
                 isActive = false,
                 lastAccessTimeMillis = 1L
-        )
-        val usage_mostRecent = createMockPermGroupUsage(
+            )
+        val usage_mostRecent =
+            createMockPermGroupUsage(
                 packageName = "${TEST_PACKAGE_NAME}_mostRecent",
                 isActive = false,
                 lastAccessTimeMillis = 2L
-        )
-        `when`(permissionManager.getIndicatorAppOpUsageData(anyBoolean())).thenReturn(
-                listOf(usage_recent, usage_mostRecent, usage_moreRecent)
-        )
+            )
+        `when`(permissionManager.getIndicatorAppOpUsageData(anyBoolean()))
+            .thenReturn(listOf(usage_recent, usage_mostRecent, usage_moreRecent))
 
         controller.showDialog(context)
         exhaustExecutors()
@@ -414,19 +414,12 @@
 
     @Test
     fun testMicAndCameraDisabled() {
-        val usage_camera = createMockPermGroupUsage(
-                permissionGroupName = PERM_CAMERA
-        )
-        val usage_microphone = createMockPermGroupUsage(
-                permissionGroupName = PERM_MICROPHONE
-        )
-        val usage_location = createMockPermGroupUsage(
-                permissionGroupName = PERM_LOCATION
-        )
+        val usage_camera = createMockPermGroupUsage(permissionGroupName = PERM_CAMERA)
+        val usage_microphone = createMockPermGroupUsage(permissionGroupName = PERM_MICROPHONE)
+        val usage_location = createMockPermGroupUsage(permissionGroupName = PERM_LOCATION)
 
-        `when`(permissionManager.getIndicatorAppOpUsageData(anyBoolean())).thenReturn(
-                listOf(usage_camera, usage_location, usage_microphone)
-        )
+        `when`(permissionManager.getIndicatorAppOpUsageData(anyBoolean()))
+            .thenReturn(listOf(usage_camera, usage_location, usage_microphone))
         `when`(privacyItemController.micCameraAvailable).thenReturn(false)
 
         controller.showDialog(context)
@@ -438,45 +431,29 @@
 
     @Test
     fun testLocationDisabled() {
-        val usage_camera = createMockPermGroupUsage(
-                permissionGroupName = PERM_CAMERA
-        )
-        val usage_microphone = createMockPermGroupUsage(
-                permissionGroupName = PERM_MICROPHONE
-        )
-        val usage_location = createMockPermGroupUsage(
-                permissionGroupName = PERM_LOCATION
-        )
+        val usage_camera = createMockPermGroupUsage(permissionGroupName = PERM_CAMERA)
+        val usage_microphone = createMockPermGroupUsage(permissionGroupName = PERM_MICROPHONE)
+        val usage_location = createMockPermGroupUsage(permissionGroupName = PERM_LOCATION)
 
-        `when`(permissionManager.getIndicatorAppOpUsageData(anyBoolean())).thenReturn(
-                listOf(usage_camera, usage_location, usage_microphone)
-        )
+        `when`(permissionManager.getIndicatorAppOpUsageData(anyBoolean()))
+            .thenReturn(listOf(usage_camera, usage_location, usage_microphone))
         `when`(privacyItemController.locationAvailable).thenReturn(false)
 
         controller.showDialog(context)
         exhaustExecutors()
 
         assertThat(dialogProvider.list).hasSize(2)
-        dialogProvider.list?.forEach {
-            assertThat(it.type).isNotEqualTo(PrivacyType.TYPE_LOCATION)
-        }
+        dialogProvider.list?.forEach { assertThat(it.type).isNotEqualTo(PrivacyType.TYPE_LOCATION) }
     }
 
     @Test
     fun testAllIndicatorsAvailable() {
-        val usage_camera = createMockPermGroupUsage(
-                permissionGroupName = PERM_CAMERA
-        )
-        val usage_microphone = createMockPermGroupUsage(
-                permissionGroupName = PERM_MICROPHONE
-        )
-        val usage_location = createMockPermGroupUsage(
-                permissionGroupName = PERM_LOCATION
-        )
+        val usage_camera = createMockPermGroupUsage(permissionGroupName = PERM_CAMERA)
+        val usage_microphone = createMockPermGroupUsage(permissionGroupName = PERM_MICROPHONE)
+        val usage_location = createMockPermGroupUsage(permissionGroupName = PERM_LOCATION)
 
-        `when`(permissionManager.getIndicatorAppOpUsageData(anyBoolean())).thenReturn(
-                listOf(usage_camera, usage_location, usage_microphone)
-        )
+        `when`(permissionManager.getIndicatorAppOpUsageData(anyBoolean()))
+            .thenReturn(listOf(usage_camera, usage_location, usage_microphone))
         `when`(privacyItemController.micCameraAvailable).thenReturn(true)
         `when`(privacyItemController.locationAvailable).thenReturn(true)
 
@@ -488,19 +465,12 @@
 
     @Test
     fun testNoIndicatorsAvailable() {
-        val usage_camera = createMockPermGroupUsage(
-                permissionGroupName = PERM_CAMERA
-        )
-        val usage_microphone = createMockPermGroupUsage(
-                permissionGroupName = PERM_MICROPHONE
-        )
-        val usage_location = createMockPermGroupUsage(
-                permissionGroupName = PERM_LOCATION
-        )
+        val usage_camera = createMockPermGroupUsage(permissionGroupName = PERM_CAMERA)
+        val usage_microphone = createMockPermGroupUsage(permissionGroupName = PERM_MICROPHONE)
+        val usage_location = createMockPermGroupUsage(permissionGroupName = PERM_LOCATION)
 
-        `when`(permissionManager.getIndicatorAppOpUsageData(anyBoolean())).thenReturn(
-                listOf(usage_camera, usage_location, usage_microphone)
-        )
+        `when`(permissionManager.getIndicatorAppOpUsageData(anyBoolean()))
+            .thenReturn(listOf(usage_camera, usage_location, usage_microphone))
         `when`(privacyItemController.micCameraAvailable).thenReturn(false)
         `when`(privacyItemController.locationAvailable).thenReturn(false)
 
@@ -512,11 +482,9 @@
 
     @Test
     fun testNotCurrentUser() {
-        val usage_other = createMockPermGroupUsage(
-                uid = generateUidForUser(ENT_USER_ID + 1)
-        )
+        val usage_other = createMockPermGroupUsage(uid = generateUidForUser(ENT_USER_ID + 1))
         `when`(permissionManager.getIndicatorAppOpUsageData(anyBoolean()))
-                .thenReturn(listOf(usage_other))
+            .thenReturn(listOf(usage_other))
 
         controller.showDialog(context)
         exhaustExecutors()
@@ -559,9 +527,7 @@
         // Calls happen in
         val usage = createMockPermGroupUsage(uid = SYSTEM_UID, isPhoneCall = true)
         `when`(permissionManager.getIndicatorAppOpUsageData(anyBoolean())).thenReturn(listOf(usage))
-        `when`(userTracker.userProfiles).thenReturn(listOf(
-                UserInfo(ENT_USER_ID, "", 0)
-        ))
+        `when`(userTracker.userProfiles).thenReturn(listOf(UserInfo(ENT_USER_ID, "", 0)))
 
         controller.showDialog(context)
         exhaustExecutors()
@@ -577,8 +543,12 @@
         exhaustExecutors()
 
         dialogProvider.manageApp?.invoke(TEST_PACKAGE_NAME, USER_ID, TEST_INTENT)
-        verify(uiEventLogger).log(PrivacyDialogEvent.PRIVACY_DIALOG_ITEM_CLICKED_TO_APP_SETTINGS,
-                USER_ID, TEST_PACKAGE_NAME)
+        verify(uiEventLogger)
+            .log(
+                PrivacyDialogEvent.PRIVACY_DIALOG_ITEM_CLICKED_TO_APP_SETTINGS,
+                USER_ID,
+                TEST_PACKAGE_NAME
+            )
     }
 
     @Test
@@ -589,8 +559,12 @@
         exhaustExecutors()
 
         dialogProvider.closeApp?.invoke(TEST_PACKAGE_NAME, USER_ID)
-        verify(uiEventLogger).log(PrivacyDialogEvent.PRIVACY_DIALOG_ITEM_CLICKED_TO_CLOSE_APP,
-                USER_ID, TEST_PACKAGE_NAME)
+        verify(uiEventLogger)
+            .log(
+                PrivacyDialogEvent.PRIVACY_DIALOG_ITEM_CLICKED_TO_CLOSE_APP,
+                USER_ID,
+                TEST_PACKAGE_NAME
+            )
     }
 
     @Test
@@ -629,9 +603,13 @@
         exhaustExecutors()
 
         dialogProvider.list?.let { list ->
-            assertThat(isIntentEqual(list.get(0).navigationIntent!!,
-                    controller.getDefaultManageAppPermissionsIntent(TEST_PACKAGE_NAME, USER_ID)))
-                    .isTrue()
+            assertThat(
+                    isIntentEqual(
+                        list.get(0).navigationIntent!!,
+                        controller.getDefaultManageAppPermissionsIntent(TEST_PACKAGE_NAME, USER_ID)
+                    )
+                )
+                .isTrue()
             assertThat(list.get(0).isService).isFalse()
         }
     }
@@ -648,45 +626,58 @@
         exhaustExecutors()
 
         dialogProvider.list?.let { list ->
-            assertThat(isIntentEqual(list.get(0).navigationIntent!!,
-                    controller.getDefaultManageAppPermissionsIntent(
-                        TEST_PACKAGE_NAME, ENT_USER_ID)))
-                        .isTrue()
+            assertThat(
+                    isIntentEqual(
+                        list.get(0).navigationIntent!!,
+                        controller.getDefaultManageAppPermissionsIntent(
+                            TEST_PACKAGE_NAME,
+                            ENT_USER_ID
+                        )
+                    )
+                )
+                .isTrue()
             assertThat(list.get(0).isService).isFalse()
         }
     }
 
     @Test
     fun testDefaultIntentOnInvalidAttributionTag() {
-        val usage = createMockPermGroupUsage(
+        val usage =
+            createMockPermGroupUsage(
                 attributionTag = "INVALID_ATTRIBUTION_TAG",
                 proxyLabel = TEST_PROXY_LABEL
-        )
+            )
         `when`(permissionManager.getIndicatorAppOpUsageData(anyBoolean())).thenReturn(listOf(usage))
 
         controller.showDialog(context)
         exhaustExecutors()
 
         dialogProvider.list?.let { list ->
-            assertThat(isIntentEqual(list.get(0).navigationIntent!!,
-                    controller.getDefaultManageAppPermissionsIntent(TEST_PACKAGE_NAME, USER_ID)))
-                    .isTrue()
+            assertThat(
+                    isIntentEqual(
+                        list.get(0).navigationIntent!!,
+                        controller.getDefaultManageAppPermissionsIntent(TEST_PACKAGE_NAME, USER_ID)
+                    )
+                )
+                .isTrue()
             assertThat(list.get(0).isService).isFalse()
         }
     }
 
     @Test
     fun testServiceIntentOnCorrectSubAttribution() {
-        val usage = createMockPermGroupUsage(
+        val usage =
+            createMockPermGroupUsage(
                 attributionTag = TEST_ATTRIBUTION_TAG,
                 attributionLabel = "TEST_LABEL"
-        )
+            )
 
         val activityInfo = createMockActivityInfo()
         val resolveInfo = createMockResolveInfo(activityInfo)
         `when`(permissionManager.getIndicatorAppOpUsageData(anyBoolean())).thenReturn(listOf(usage))
-        `when`(packageManager.resolveActivity(any(), any<ResolveInfoFlags>()))
-                .thenAnswer { resolveInfo }
+        `when`(packageManager.resolveActivity(any(), any<ResolveInfoFlags>())).thenAnswer {
+            resolveInfo
+        }
         controller.showDialog(context)
         exhaustExecutors()
 
@@ -694,57 +685,61 @@
             val navigationIntent = list.get(0).navigationIntent!!
             assertThat(navigationIntent.action).isEqualTo(Intent.ACTION_MANAGE_PERMISSION_USAGE)
             assertThat(navigationIntent.getStringExtra(Intent.EXTRA_PERMISSION_GROUP_NAME))
-                    .isEqualTo(PERM_CAMERA)
+                .isEqualTo(PERM_CAMERA)
             assertThat(navigationIntent.getStringArrayExtra(Intent.EXTRA_ATTRIBUTION_TAGS))
-                    .isEqualTo(arrayOf(TEST_ATTRIBUTION_TAG.toString()))
+                .isEqualTo(arrayOf(TEST_ATTRIBUTION_TAG.toString()))
             assertThat(navigationIntent.getBooleanExtra(Intent.EXTRA_SHOWING_ATTRIBUTION, false))
-                    .isTrue()
+                .isTrue()
             assertThat(list.get(0).isService).isTrue()
         }
     }
 
     @Test
     fun testDefaultIntentOnMissingAttributionLabel() {
-        val usage = createMockPermGroupUsage(
-                attributionTag = TEST_ATTRIBUTION_TAG
-        )
+        val usage = createMockPermGroupUsage(attributionTag = TEST_ATTRIBUTION_TAG)
 
         val activityInfo = createMockActivityInfo()
         val resolveInfo = createMockResolveInfo(activityInfo)
         `when`(permissionManager.getIndicatorAppOpUsageData(anyBoolean())).thenReturn(listOf(usage))
-        `when`(packageManager.resolveActivity(any(), any<ResolveInfoFlags>()))
-                .thenAnswer { resolveInfo }
+        `when`(packageManager.resolveActivity(any(), any<ResolveInfoFlags>())).thenAnswer {
+            resolveInfo
+        }
         controller.showDialog(context)
         exhaustExecutors()
 
         dialogProvider.list?.let { list ->
-            assertThat(isIntentEqual(list.get(0).navigationIntent!!,
-                    controller.getDefaultManageAppPermissionsIntent(TEST_PACKAGE_NAME, USER_ID)))
-                    .isTrue()
+            assertThat(
+                    isIntentEqual(
+                        list.get(0).navigationIntent!!,
+                        controller.getDefaultManageAppPermissionsIntent(TEST_PACKAGE_NAME, USER_ID)
+                    )
+                )
+                .isTrue()
             assertThat(list.get(0).isService).isFalse()
         }
     }
 
     @Test
     fun testDefaultIntentOnIncorrectPermission() {
-        val usage = createMockPermGroupUsage(
-                attributionTag = TEST_ATTRIBUTION_TAG
-        )
+        val usage = createMockPermGroupUsage(attributionTag = TEST_ATTRIBUTION_TAG)
 
-        val activityInfo = createMockActivityInfo(
-                permission = "INCORRECT_PERMISSION"
-        )
+        val activityInfo = createMockActivityInfo(permission = "INCORRECT_PERMISSION")
         val resolveInfo = createMockResolveInfo(activityInfo)
         `when`(permissionManager.getIndicatorAppOpUsageData(anyBoolean())).thenReturn(listOf(usage))
-        `when`(packageManager.resolveActivity(any(), any<ResolveInfoFlags>()))
-                .thenAnswer { resolveInfo }
+        `when`(packageManager.resolveActivity(any(), any<ResolveInfoFlags>())).thenAnswer {
+            resolveInfo
+        }
         controller.showDialog(context)
         exhaustExecutors()
 
         dialogProvider.list?.let { list ->
-            assertThat(isIntentEqual(list.get(0).navigationIntent!!,
-                    controller.getDefaultManageAppPermissionsIntent(TEST_PACKAGE_NAME, USER_ID)))
-                    .isTrue()
+            assertThat(
+                    isIntentEqual(
+                        list.get(0).navigationIntent!!,
+                        controller.getDefaultManageAppPermissionsIntent(TEST_PACKAGE_NAME, USER_ID)
+                    )
+                )
+                .isTrue()
             assertThat(list.get(0).isService).isFalse()
         }
     }
@@ -758,15 +753,18 @@
         `when`(appOpsController.isMicMuted).thenReturn(false)
 
         `when`(packageManager.getApplicationInfoAsUser(anyString(), anyInt(), anyInt()))
-                .thenAnswer { FakeApplicationInfo(it.getArgument(0)) }
+            .thenAnswer { FakeApplicationInfo(it.getArgument(0)) }
 
         `when`(privacyItemController.locationAvailable).thenReturn(true)
         `when`(privacyItemController.micCameraAvailable).thenReturn(true)
 
-        `when`(userTracker.userProfiles).thenReturn(listOf(
-                UserInfo(USER_ID, "", 0),
-                UserInfo(ENT_USER_ID, "", UserInfo.FLAG_MANAGED_PROFILE)
-        ))
+        `when`(userTracker.userProfiles)
+            .thenReturn(
+                listOf(
+                    UserInfo(USER_ID, "", 0),
+                    UserInfo(ENT_USER_ID, "", UserInfo.FLAG_MANAGED_PROFILE)
+                )
+            )
 
         `when`(keyguardStateController.isUnlocked).thenReturn(true)
     }
@@ -781,9 +779,7 @@
         return user * UserHandle.PER_USER_RANGE + nextUid++
     }
 
-    private fun createMockResolveInfo(
-        activityInfo: ActivityInfo? = null
-    ): ResolveInfo {
+    private fun createMockResolveInfo(activityInfo: ActivityInfo? = null): ResolveInfo {
         val resolveInfo = mock(ResolveInfo::class.java)
         resolveInfo.activityInfo = activityInfo
         return resolveInfo
@@ -822,4 +818,4 @@
         `when`(usage.proxyLabel).thenReturn(proxyLabel)
         return usage
     }
-}
\ No newline at end of file
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/RecordIssueTileTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/RecordIssueTileTest.kt
index d8199c5..e9714dc 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/RecordIssueTileTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/RecordIssueTileTest.kt
@@ -23,6 +23,7 @@
 import androidx.test.filters.SmallTest
 import com.android.internal.logging.MetricsLogger
 import com.android.systemui.SysuiTestCase
+import com.android.systemui.animation.DialogLaunchAnimator
 import com.android.systemui.classifier.FalsingManagerFake
 import com.android.systemui.plugins.ActivityStarter
 import com.android.systemui.plugins.statusbar.StatusBarStateController
@@ -30,12 +31,19 @@
 import com.android.systemui.qs.QsEventLogger
 import com.android.systemui.qs.logging.QSLogger
 import com.android.systemui.res.R
+import com.android.systemui.statusbar.phone.KeyguardDismissUtil
+import com.android.systemui.statusbar.phone.SystemUIDialog
+import com.android.systemui.statusbar.policy.KeyguardStateController
+import com.android.systemui.util.mockito.any
 import com.android.systemui.util.mockito.whenever
 import com.google.common.truth.Truth.assertThat
 import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
+import org.mockito.ArgumentMatchers.eq
+import org.mockito.ArgumentMatchers.isA
 import org.mockito.Mock
+import org.mockito.Mockito.verify
 import org.mockito.MockitoAnnotations
 
 /**
@@ -53,15 +61,22 @@
     @Mock private lateinit var statusBarStateController: StatusBarStateController
     @Mock private lateinit var activityStarter: ActivityStarter
     @Mock private lateinit var qsLogger: QSLogger
+    @Mock private lateinit var keyguardDismissUtil: KeyguardDismissUtil
+    @Mock private lateinit var keyguardStateController: KeyguardStateController
+    @Mock private lateinit var dialogLauncherAnimator: DialogLaunchAnimator
+    @Mock private lateinit var dialogFactory: SystemUIDialog.Factory
+    @Mock private lateinit var dialog: SystemUIDialog
 
+    private lateinit var testableLooper: TestableLooper
     private lateinit var tile: RecordIssueTile
 
     @Before
     fun setUp() {
         MockitoAnnotations.initMocks(this)
         whenever(host.context).thenReturn(mContext)
+        whenever(dialogFactory.create(any())).thenReturn(dialog)
 
-        val testableLooper = TestableLooper.get(this)
+        testableLooper = TestableLooper.get(this)
         tile =
             RecordIssueTile(
                 host,
@@ -72,7 +87,11 @@
                 metricsLogger,
                 statusBarStateController,
                 activityStarter,
-                qsLogger
+                qsLogger,
+                keyguardDismissUtil,
+                keyguardStateController,
+                dialogLauncherAnimator,
+                dialogFactory
             )
     }
 
@@ -119,4 +138,18 @@
 
         assertThat(testState.state).isEqualTo(Tile.STATE_ACTIVE)
     }
+
+    @Test
+    fun showPrompt_shouldUseKeyguardDismissUtil_ToShowDialog() {
+        tile.isRecording = false
+        tile.handleClick(null)
+        testableLooper.processAllMessages()
+
+        verify(keyguardDismissUtil)
+            .executeWhenUnlocked(
+                isA(ActivityStarter.OnDismissAction::class.java),
+                eq(false),
+                eq(true)
+            )
+    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/recordissue/RecordIssueDialogDelegateTest.kt b/packages/SystemUI/tests/src/com/android/systemui/recordissue/RecordIssueDialogDelegateTest.kt
new file mode 100644
index 0000000..bbc59d0
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/recordissue/RecordIssueDialogDelegateTest.kt
@@ -0,0 +1,93 @@
+/*
+ * 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.recordissue
+
+import android.app.Dialog
+import android.testing.AndroidTestingRunner
+import android.testing.TestableLooper
+import android.widget.Button
+import android.widget.Switch
+import androidx.test.filters.SmallTest
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.animation.DialogLaunchAnimator
+import com.android.systemui.broadcast.BroadcastDispatcher
+import com.android.systemui.flags.FeatureFlags
+import com.android.systemui.model.SysUiState
+import com.android.systemui.res.R
+import com.android.systemui.statusbar.phone.SystemUIDialog
+import com.android.systemui.statusbar.phone.SystemUIDialogManager
+import com.android.systemui.util.mockito.mock
+import com.android.systemui.util.mockito.whenever
+import com.google.common.truth.Truth.assertThat
+import java.util.concurrent.CountDownLatch
+import java.util.concurrent.TimeUnit
+import org.junit.After
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.ArgumentMatchers.anyBoolean
+import org.mockito.ArgumentMatchers.anyInt
+
+@SmallTest
+@RunWith(AndroidTestingRunner::class)
+@TestableLooper.RunWithLooper(setAsMainLooper = true)
+class RecordIssueDialogDelegateTest : SysuiTestCase() {
+
+    private lateinit var dialog: SystemUIDialog
+    private lateinit var latch: CountDownLatch
+
+    @Before
+    fun setup() {
+        val dialogFactory =
+            SystemUIDialog.Factory(
+                context,
+                mock<FeatureFlags>(),
+                mock<SystemUIDialogManager>(),
+                mock<SysUiState>().apply {
+                    whenever(setFlag(anyInt(), anyBoolean())).thenReturn(this)
+                },
+                mock<BroadcastDispatcher>(),
+                mock<DialogLaunchAnimator>()
+            )
+
+        latch = CountDownLatch(1)
+        dialog = RecordIssueDialogDelegate(dialogFactory) { latch.countDown() }.createDialog()
+        dialog.show()
+    }
+
+    @After
+    fun teardown() {
+        dialog.dismiss()
+    }
+
+    @Test
+    fun dialog_hasCorrectUiElements_afterCreation() {
+        dialog.requireViewById<Switch>(R.id.screenrecord_switch)
+        dialog.requireViewById<Button>(R.id.issue_type_button)
+
+        assertThat(dialog.getButton(Dialog.BUTTON_POSITIVE).text)
+            .isEqualTo(context.getString(R.string.qs_record_issue_start))
+        assertThat(dialog.getButton(Dialog.BUTTON_NEGATIVE).text)
+            .isEqualTo(context.getString(R.string.cancel))
+    }
+
+    @Test
+    fun onStarted_isCalled_afterStartButtonIsClicked() {
+        dialog.getButton(Dialog.BUTTON_POSITIVE).callOnClick()
+        latch.await(1L, TimeUnit.MILLISECONDS)
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenshot/appclips/AppClipsActivityTest.java b/packages/SystemUI/tests/src/com/android/systemui/screenshot/appclips/AppClipsActivityTest.java
index 0f33aaf..2c7b606 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/screenshot/appclips/AppClipsActivityTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/screenshot/appclips/AppClipsActivityTest.java
@@ -56,6 +56,7 @@
 
 import org.junit.After;
 import org.junit.Before;
+import org.junit.Ignore;
 import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -138,6 +139,7 @@
     }
 
     @Test
+    @Ignore("b/315848285")
     public void screenshotDisplayed_userConsented_screenshotExportedSuccessfully() {
         ResultReceiver resultReceiver = createResultReceiver((resultCode, data) -> {
             assertThat(resultCode).isEqualTo(RESULT_OK);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java
index 657f912..e572dcc 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationPanelViewControllerBaseTest.java
@@ -23,6 +23,8 @@
 
 import static com.google.common.truth.Truth.assertThat;
 
+import static kotlinx.coroutines.flow.FlowKt.emptyFlow;
+
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyBoolean;
 import static org.mockito.ArgumentMatchers.anyFloat;
@@ -37,8 +39,6 @@
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
-import static kotlinx.coroutines.flow.FlowKt.emptyFlow;
-
 import android.annotation.IdRes;
 import android.content.ContentResolver;
 import android.content.res.Configuration;
@@ -86,6 +86,7 @@
 import com.android.systemui.classifier.FalsingManagerFake;
 import com.android.systemui.common.ui.data.repository.FakeConfigurationRepository;
 import com.android.systemui.common.ui.view.LongPressHandlingView;
+import com.android.systemui.deviceentry.domain.interactor.DeviceEntryUdfpsInteractor;
 import com.android.systemui.doze.DozeLog;
 import com.android.systemui.dump.DumpManager;
 import com.android.systemui.flags.FakeFeatureFlagsClassic;
@@ -402,6 +403,10 @@
         mPowerInteractor = keyguardInteractorDeps.getPowerInteractor();
         when(mKeyguardTransitionInteractor.isInTransitionToStateWhere(any())).thenReturn(
                 StateFlowKt.MutableStateFlow(false));
+        DeviceEntryUdfpsInteractor deviceEntryUdfpsInteractor =
+                mock(DeviceEntryUdfpsInteractor.class);
+        when(deviceEntryUdfpsInteractor.isUdfpsSupported()).thenReturn(emptyFlow());
+
         mShadeInteractor = new ShadeInteractorImpl(
                 mTestScope.getBackgroundScope(),
                 new FakeDeviceProvisioningRepository(),
@@ -418,7 +423,9 @@
                         new SharedNotificationContainerInteractor(
                                 new FakeConfigurationRepository(),
                                 mContext,
-                                new ResourcesSplitShadeStateController()
+                                new ResourcesSplitShadeStateController(),
+                                mKeyguardInteractor,
+                                deviceEntryUdfpsInteractor
                         ),
                         mShadeRepository
                 )
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowControllerImplTest.java b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowControllerImplTest.java
index 5ffbe65..9d8b214 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowControllerImplTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/NotificationShadeWindowControllerImplTest.java
@@ -23,6 +23,8 @@
 
 import static com.google.common.truth.Truth.assertThat;
 
+import static kotlinx.coroutines.flow.FlowKt.emptyFlow;
+
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyInt;
 import static org.mockito.ArgumentMatchers.eq;
@@ -54,6 +56,7 @@
 import com.android.systemui.colorextraction.SysuiColorExtractor;
 import com.android.systemui.common.ui.data.repository.FakeConfigurationRepository;
 import com.android.systemui.common.ui.domain.interactor.ConfigurationInteractor;
+import com.android.systemui.deviceentry.domain.interactor.DeviceEntryUdfpsInteractor;
 import com.android.systemui.dump.DumpManager;
 import com.android.systemui.flags.FakeFeatureFlagsClassic;
 import com.android.systemui.keyguard.KeyguardViewMediator;
@@ -235,6 +238,11 @@
                 mKeyguardSecurityModel,
                 mSelectedUserInteractor,
                 powerInteractor);
+
+        DeviceEntryUdfpsInteractor deviceEntryUdfpsInteractor =
+                mock(DeviceEntryUdfpsInteractor.class);
+        when(deviceEntryUdfpsInteractor.isUdfpsSupported()).thenReturn(emptyFlow());
+
         mShadeInteractor = new ShadeInteractorImpl(
                 mTestScope.getBackgroundScope(),
                 new FakeDeviceProvisioningRepository(),
@@ -251,7 +259,9 @@
                         new SharedNotificationContainerInteractor(
                                 configurationRepository,
                                 mContext,
-                                new ResourcesSplitShadeStateController()),
+                                new ResourcesSplitShadeStateController(),
+                                keyguardInteractor,
+                                deviceEntryUdfpsInteractor),
                         shadeRepository
                 )
         );
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 e723d7d..eb5633b 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerBaseTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/shade/QuickSettingsControllerBaseTest.java
@@ -22,6 +22,7 @@
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.when;
 
+import static kotlinx.coroutines.flow.FlowKt.emptyFlow;
 import static kotlinx.coroutines.test.TestCoroutineDispatchersKt.StandardTestDispatcher;
 
 import android.content.res.Resources;
@@ -41,6 +42,7 @@
 import com.android.systemui.bouncer.data.repository.FakeKeyguardBouncerRepository;
 import com.android.systemui.common.ui.data.repository.FakeConfigurationRepository;
 import com.android.systemui.common.ui.domain.interactor.ConfigurationInteractor;
+import com.android.systemui.deviceentry.domain.interactor.DeviceEntryUdfpsInteractor;
 import com.android.systemui.dump.DumpManager;
 import com.android.systemui.flags.FakeFeatureFlagsClassic;
 import com.android.systemui.flags.FeatureFlags;
@@ -275,6 +277,10 @@
         ResourcesSplitShadeStateController splitShadeStateController =
                 new ResourcesSplitShadeStateController();
 
+        DeviceEntryUdfpsInteractor deviceEntryUdfpsInteractor =
+                mock(DeviceEntryUdfpsInteractor.class);
+        when(deviceEntryUdfpsInteractor.isUdfpsSupported()).thenReturn(emptyFlow());
+
         mShadeInteractor = new ShadeInteractorImpl(
                 mTestScope.getBackgroundScope(),
                 deviceProvisioningRepository,
@@ -291,7 +297,9 @@
                         new SharedNotificationContainerInteractor(
                                 configurationRepository,
                                 mContext,
-                                splitShadeStateController),
+                                splitShadeStateController,
+                                keyguardInteractor,
+                                deviceEntryUdfpsInteractor),
                         mShadeRepository
                 )
         );
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/StatusBarStateControllerImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/StatusBarStateControllerImplTest.kt
index dff91dd..f25ce0a 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/StatusBarStateControllerImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/StatusBarStateControllerImplTest.kt
@@ -27,6 +27,7 @@
 import com.android.systemui.classifier.FalsingCollectorFake
 import com.android.systemui.common.ui.data.repository.FakeConfigurationRepository
 import com.android.systemui.common.ui.domain.interactor.ConfigurationInteractor
+import com.android.systemui.deviceentry.domain.interactor.DeviceEntryUdfpsInteractor;
 import com.android.systemui.flags.FakeFeatureFlagsClassic
 import com.android.systemui.keyguard.data.repository.FakeCommandQueue
 import com.android.systemui.keyguard.data.repository.FakeKeyguardRepository
@@ -53,6 +54,7 @@
 import com.android.systemui.statusbar.policy.ResourcesSplitShadeStateController
 import com.android.systemui.statusbar.policy.data.repository.FakeDeviceProvisioningRepository
 import com.android.systemui.util.mockito.mock
+import kotlinx.coroutines.flow.emptyFlow
 import org.junit.Assert.assertEquals
 import org.junit.Assert.assertFalse
 import org.junit.Assert.assertTrue
@@ -83,6 +85,7 @@
         FromPrimaryBouncerTransitionInteractor
     @Mock lateinit var interactionJankMonitor: InteractionJankMonitor
     @Mock lateinit var mockDarkAnimator: ObjectAnimator
+    @Mock lateinit var deviceEntryUdfpsInteractor: DeviceEntryUdfpsInteractor
 
     private lateinit var controller: StatusBarStateControllerImpl
     private lateinit var uiEventLogger: UiEventLoggerFake
@@ -164,6 +167,8 @@
                 mock(),
                 powerInteractor
             )
+
+        whenever(deviceEntryUdfpsInteractor.isUdfpsSupported).thenReturn(emptyFlow())
         shadeInteractor =
             ShadeInteractorImpl(
                 testScope.backgroundScope,
@@ -181,7 +186,9 @@
                     SharedNotificationContainerInteractor(
                         configurationRepository,
                         mContext,
-                        ResourcesSplitShadeStateController()
+                        ResourcesSplitShadeStateController(),
+                        keyguardInteractor,
+                        deviceEntryUdfpsInteractor,
                     ),
                     shadeRepository,
                 )
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilderTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilderTest.java
index bfa03ee..8cf64a5 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilderTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/collection/ShadeListBuilderTest.java
@@ -48,7 +48,6 @@
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper;
 import android.util.ArrayMap;
-import android.util.Log;
 
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
@@ -56,6 +55,7 @@
 
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.dump.DumpManager;
+import com.android.systemui.log.LogAssertKt;
 import com.android.systemui.statusbar.NotificationInteractionTracker;
 import com.android.systemui.statusbar.RankingBuilder;
 import com.android.systemui.statusbar.notification.NotifPipelineFlags;
@@ -76,6 +76,7 @@
 import com.android.systemui.statusbar.notification.collection.notifcollection.CollectionReadyForBuildListener;
 import com.android.systemui.util.time.FakeSystemClock;
 
+import org.junit.Assert;
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -129,10 +130,6 @@
     private Map<String, Integer> mNextIdMap = new ArrayMap<>();
     private int mNextRank = 0;
 
-    private Log.TerribleFailureHandler mOldWtfHandler = null;
-    private Log.TerribleFailure mLastWtf = null;
-    private int mWtfCount = 0;
-
     @Before
     public void setUp() {
         MockitoAnnotations.initMocks(this);
@@ -1756,20 +1753,19 @@
         mListBuilder.addPreGroupFilter(filter);
         mListBuilder.addOnBeforeTransformGroupsListener(listener);
 
-        interceptWtfs();
-
         // WHEN we try to run the pipeline and the filter is invalidated exactly
         // MAX_CONSECUTIVE_REENTRANT_REBUILDS times,
         addNotif(0, PACKAGE_2);
         invalidator.setInvalidationCount(MAX_CONSECUTIVE_REENTRANT_REBUILDS);
-        dispatchBuild();
-        runWhileScheduledUpTo(MAX_CONSECUTIVE_REENTRANT_REBUILDS + 2);
 
         // THEN an exception is NOT thrown directly, but a WTF IS logged.
-        expectWtfs(MAX_CONSECUTIVE_REENTRANT_REBUILDS);
+        LogAssertKt.assertLogsWtfs(() -> {
+            dispatchBuild();
+            runWhileScheduledUpTo(MAX_CONSECUTIVE_REENTRANT_REBUILDS + 2);
+        });
     }
 
-    @Test(expected = IllegalStateException.class)
+    @Test
     public void testOutOfOrderPreGroupFilterInvalidationThrowsAfterTooManyRuns() {
         // GIVEN a PreGroupNotifFilter that gets invalidated during the grouping stage,
         NotifFilter filter = new PackageFilter(PACKAGE_1);
@@ -1778,20 +1774,20 @@
         mListBuilder.addPreGroupFilter(filter);
         mListBuilder.addOnBeforeTransformGroupsListener(listener);
 
-        interceptWtfs();
-
         // WHEN we try to run the pipeline and the filter is invalidated more than
         // MAX_CONSECUTIVE_REENTRANT_REBUILDS times,
-        addNotif(0, PACKAGE_2);
-        invalidator.setInvalidationCount(MAX_CONSECUTIVE_REENTRANT_REBUILDS + 1);
-        dispatchBuild();
-        try {
-            runWhileScheduledUpTo(MAX_CONSECUTIVE_REENTRANT_REBUILDS + 2);
-        } finally {
-            expectWtfs(MAX_CONSECUTIVE_REENTRANT_REBUILDS);
-        }
 
         // THEN an exception IS thrown.
+
+        addNotif(0, PACKAGE_2);
+        invalidator.setInvalidationCount(MAX_CONSECUTIVE_REENTRANT_REBUILDS + 1);
+
+        LogAssertKt.assertLogsWtfs(() -> {
+            Assert.assertThrows(IllegalStateException.class, () -> {
+                dispatchBuild();
+                runWhileScheduledUpTo(MAX_CONSECUTIVE_REENTRANT_REBUILDS + 2);
+            });
+        });
     }
 
     @Test
@@ -1803,26 +1799,30 @@
         mListBuilder.addPreGroupFilter(filter);
         mListBuilder.addOnBeforeTransformGroupsListener(listener);
 
-        interceptWtfs();
-
         // WHEN we try to run the pipeline and the filter is invalidated
         // MAX_CONSECUTIVE_REENTRANT_REBUILDS times, the pipeline runs for a non-reentrant reason,
         // and then the filter is invalidated MAX_CONSECUTIVE_REENTRANT_REBUILDS times again,
-        addNotif(0, PACKAGE_2);
-        invalidator.setInvalidationCount(MAX_CONSECUTIVE_REENTRANT_REBUILDS);
-        dispatchBuild();
-        runWhileScheduledUpTo(MAX_CONSECUTIVE_REENTRANT_REBUILDS + 2);
-        invalidator.setInvalidationCount(MAX_CONSECUTIVE_REENTRANT_REBUILDS);
-        // Note: dispatchBuild itself triggers a non-reentrant pipeline run.
-        dispatchBuild();
-        runWhileScheduledUpTo(MAX_CONSECUTIVE_REENTRANT_REBUILDS + 2);
 
         // THEN an exception is NOT thrown, but WTFs ARE logged.
-        expectWtfs(MAX_CONSECUTIVE_REENTRANT_REBUILDS * 2);
+
+        addNotif(0, PACKAGE_2);
+
+        invalidator.setInvalidationCount(MAX_CONSECUTIVE_REENTRANT_REBUILDS);
+        LogAssertKt.assertLogsWtfs(() -> {
+            dispatchBuild();
+            runWhileScheduledUpTo(MAX_CONSECUTIVE_REENTRANT_REBUILDS + 2);
+        });
+
+        invalidator.setInvalidationCount(MAX_CONSECUTIVE_REENTRANT_REBUILDS);
+        LogAssertKt.assertLogsWtfs(() -> {
+            // Note: dispatchBuild itself triggers a non-reentrant pipeline run.
+            dispatchBuild();
+            runWhileScheduledUpTo(MAX_CONSECUTIVE_REENTRANT_REBUILDS + 2);
+        });
     }
 
     @Test
-    public void testOutOfOrderPrompterInvalidationDoesNotThrowBeforeTooManyRuns() {
+    public void testOutOfOrderPromoterInvalidationDoesNotThrowBeforeTooManyRuns() {
         // GIVEN a NotifPromoter that gets invalidated during the sorting stage,
         NotifPromoter promoter = new IdPromoter(47);
         CountingInvalidator invalidator = new CountingInvalidator(promoter);
@@ -1830,22 +1830,22 @@
         mListBuilder.addPromoter(promoter);
         mListBuilder.addOnBeforeSortListener(listener);
 
-        interceptWtfs();
-
         // WHEN we try to run the pipeline and the promoter is invalidated exactly
         // MAX_CONSECUTIVE_REENTRANT_REBUILDS times,
-        addNotif(0, PACKAGE_1);
-        invalidator.setInvalidationCount(MAX_CONSECUTIVE_REENTRANT_REBUILDS);
-        dispatchBuild();
-        runWhileScheduledUpTo(MAX_CONSECUTIVE_REENTRANT_REBUILDS + 2);
 
         // THEN an exception is NOT thrown directly, but a WTF IS logged.
-        expectWtfs(MAX_CONSECUTIVE_REENTRANT_REBUILDS);
 
+        addNotif(0, PACKAGE_1);
+        invalidator.setInvalidationCount(MAX_CONSECUTIVE_REENTRANT_REBUILDS);
+
+        LogAssertKt.assertLogsWtfs(() -> {
+            dispatchBuild();
+            runWhileScheduledUpTo(MAX_CONSECUTIVE_REENTRANT_REBUILDS + 2);
+        });
     }
 
-    @Test(expected = IllegalStateException.class)
-    public void testOutOfOrderPrompterInvalidationThrowsAfterTooManyRuns() {
+    @Test
+    public void testOutOfOrderPromoterInvalidationThrowsAfterTooManyRuns() {
         // GIVEN a NotifPromoter that gets invalidated during the sorting stage,
         NotifPromoter promoter = new IdPromoter(47);
         CountingInvalidator invalidator = new CountingInvalidator(promoter);
@@ -1853,20 +1853,20 @@
         mListBuilder.addPromoter(promoter);
         mListBuilder.addOnBeforeSortListener(listener);
 
-        interceptWtfs();
-
         // WHEN we try to run the pipeline and the promoter is invalidated more than
         // MAX_CONSECUTIVE_REENTRANT_REBUILDS times,
-        addNotif(0, PACKAGE_1);
-        invalidator.setInvalidationCount(MAX_CONSECUTIVE_REENTRANT_REBUILDS + 1);
-        dispatchBuild();
-        try {
-            runWhileScheduledUpTo(MAX_CONSECUTIVE_REENTRANT_REBUILDS + 2);
-        } finally {
-            expectWtfs(MAX_CONSECUTIVE_REENTRANT_REBUILDS);
-        }
 
         // THEN an exception IS thrown.
+
+        addNotif(0, PACKAGE_1);
+        invalidator.setInvalidationCount(MAX_CONSECUTIVE_REENTRANT_REBUILDS + 1);
+
+        LogAssertKt.assertLogsWtfs(() -> {
+            Assert.assertThrows(IllegalStateException.class, () -> {
+                dispatchBuild();
+                runWhileScheduledUpTo(MAX_CONSECUTIVE_REENTRANT_REBUILDS + 2);
+            });
+        });
     }
 
     @Test
@@ -1878,20 +1878,21 @@
         mListBuilder.setComparators(singletonList(comparator));
         mListBuilder.addOnBeforeRenderListListener(listener);
 
-        interceptWtfs();
-
         // WHEN we try to run the pipeline and the comparator is invalidated exactly
         // MAX_CONSECUTIVE_REENTRANT_REBUILDS times,
-        addNotif(0, PACKAGE_2);
-        invalidator.setInvalidationCount(MAX_CONSECUTIVE_REENTRANT_REBUILDS);
-        dispatchBuild();
-        runWhileScheduledUpTo(MAX_CONSECUTIVE_REENTRANT_REBUILDS + 2);
 
         // THEN an exception is NOT thrown directly, but a WTF IS logged.
-        expectWtfs(MAX_CONSECUTIVE_REENTRANT_REBUILDS);
+
+        addNotif(0, PACKAGE_2);
+        invalidator.setInvalidationCount(MAX_CONSECUTIVE_REENTRANT_REBUILDS);
+
+        LogAssertKt.assertLogsWtfs(() -> {
+            dispatchBuild();
+            runWhileScheduledUpTo(MAX_CONSECUTIVE_REENTRANT_REBUILDS + 2);
+        });
     }
 
-    @Test(expected = IllegalStateException.class)
+    @Test
     public void testOutOfOrderComparatorInvalidationThrowsAfterTooManyRuns() {
         // GIVEN a NotifComparator that gets invalidated during the finalizing stage,
         NotifComparator comparator = new HypeComparator(PACKAGE_1);
@@ -1900,16 +1901,20 @@
         mListBuilder.setComparators(singletonList(comparator));
         mListBuilder.addOnBeforeRenderListListener(listener);
 
-        interceptWtfs();
-
         // WHEN we try to run the pipeline and the comparator is invalidated more than
         // MAX_CONSECUTIVE_REENTRANT_REBUILDS times,
-        addNotif(0, PACKAGE_2);
-        invalidator.setInvalidationCount(MAX_CONSECUTIVE_REENTRANT_REBUILDS + 1);
-        dispatchBuild();
-        runWhileScheduledUpTo(MAX_CONSECUTIVE_REENTRANT_REBUILDS + 2);
 
         // THEN an exception IS thrown.
+
+        addNotif(0, PACKAGE_2);
+        invalidator.setInvalidationCount(MAX_CONSECUTIVE_REENTRANT_REBUILDS + 1);
+
+        LogAssertKt.assertLogsWtfs(() -> {
+            Assert.assertThrows(IllegalStateException.class, () -> {
+                dispatchBuild();
+                runWhileScheduledUpTo(MAX_CONSECUTIVE_REENTRANT_REBUILDS + 2);
+            });
+        });
     }
 
     @Test
@@ -1921,20 +1926,21 @@
         mListBuilder.addFinalizeFilter(filter);
         mListBuilder.addOnBeforeRenderListListener(listener);
 
-        interceptWtfs();
-
         // WHEN we try to run the pipeline and the PreRenderFilter is invalidated exactly
         // MAX_CONSECUTIVE_REENTRANT_REBUILDS times,
-        addNotif(0, PACKAGE_2);
-        invalidator.setInvalidationCount(MAX_CONSECUTIVE_REENTRANT_REBUILDS);
-        dispatchBuild();
-        runWhileScheduledUpTo(MAX_CONSECUTIVE_REENTRANT_REBUILDS + 2);
 
         // THEN an exception is NOT thrown directly, but a WTF IS logged.
-        expectWtfs(MAX_CONSECUTIVE_REENTRANT_REBUILDS);
+
+        addNotif(0, PACKAGE_2);
+        invalidator.setInvalidationCount(MAX_CONSECUTIVE_REENTRANT_REBUILDS);
+
+        LogAssertKt.assertLogsWtfs(() -> {
+            dispatchBuild();
+            runWhileScheduledUpTo(MAX_CONSECUTIVE_REENTRANT_REBUILDS + 2);
+        });
     }
 
-    @Test(expected = IllegalStateException.class)
+    @Test
     public void testOutOfOrderPreRenderFilterInvalidationThrowsAfterTooManyRuns() {
         // GIVEN a PreRenderNotifFilter that gets invalidated during the finalizing stage,
         NotifFilter filter = new PackageFilter(PACKAGE_1);
@@ -1943,59 +1949,22 @@
         mListBuilder.addFinalizeFilter(filter);
         mListBuilder.addOnBeforeRenderListListener(listener);
 
-        interceptWtfs();
-
         // WHEN we try to run the pipeline and the PreRenderFilter is invalidated more than
         // MAX_CONSECUTIVE_REENTRANT_REBUILDS times,
-        addNotif(0, PACKAGE_2);
-        invalidator.setInvalidationCount(MAX_CONSECUTIVE_REENTRANT_REBUILDS + 1);
-        dispatchBuild();
-        try {
-            runWhileScheduledUpTo(MAX_CONSECUTIVE_REENTRANT_REBUILDS + 2);
-        } finally {
-            expectWtfs(MAX_CONSECUTIVE_REENTRANT_REBUILDS);
-        }
 
         // THEN an exception IS thrown.
-    }
 
-    private void interceptWtfs() {
-        assertNull(mOldWtfHandler);
+        addNotif(0, PACKAGE_2);
+        invalidator.setInvalidationCount(MAX_CONSECUTIVE_REENTRANT_REBUILDS + 1);
 
-        mLastWtf = null;
-        mWtfCount = 0;
-
-        mOldWtfHandler = Log.setWtfHandler((tag, e, system) -> {
-            Log.e("ShadeListBuilderTest", "Observed WTF: " + e);
-            mLastWtf = e;
-            mWtfCount++;
+        LogAssertKt.assertLogsWtfs(() -> {
+            Assert.assertThrows(IllegalStateException.class, () -> {
+                dispatchBuild();
+                runWhileScheduledUpTo(MAX_CONSECUTIVE_REENTRANT_REBUILDS + 2);
+            });
         });
     }
 
-    private void expectNoWtfs() {
-        assertNull(expectWtfs(0));
-    }
-
-    private Log.TerribleFailure expectWtf() {
-        return expectWtfs(1);
-    }
-
-    private Log.TerribleFailure expectWtfs(int expectedWtfCount) {
-        assertNotNull(mOldWtfHandler);
-
-        Log.setWtfHandler(mOldWtfHandler);
-        mOldWtfHandler = null;
-
-        Log.TerribleFailure wtf = mLastWtf;
-        int wtfCount = mWtfCount;
-
-        mLastWtf = null;
-        mWtfCount = 0;
-
-        assertEquals(expectedWtfCount, wtfCount);
-        return wtf;
-    }
-
     @Test
     public void testStableOrdering() {
         mStabilityManager.setAllowEntryReordering(false);
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/domain/interactor/SharedNotificationContainerInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/domain/interactor/SharedNotificationContainerInteractorTest.kt
index a07b570..327a07d6 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/domain/interactor/SharedNotificationContainerInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/stack/domain/interactor/SharedNotificationContainerInteractorTest.kt
@@ -20,57 +20,86 @@
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
-import com.android.systemui.common.ui.data.repository.FakeConfigurationRepository
+import com.android.systemui.biometrics.data.repository.fingerprintPropertyRepository
+import com.android.systemui.common.ui.data.repository.fakeConfigurationRepository
 import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.keyguard.data.repository.fakeKeyguardRepository
+import com.android.systemui.kosmos.testScope
 import com.android.systemui.res.R
-import com.android.systemui.statusbar.policy.ResourcesSplitShadeStateController
+import com.android.systemui.testKosmos
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.test.runCurrent
 import kotlinx.coroutines.test.runTest
-import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
 
 @SmallTest
 @RunWith(AndroidJUnit4::class)
 class SharedNotificationContainerInteractorTest : SysuiTestCase() {
-    private lateinit var configurationRepository: FakeConfigurationRepository
-    private lateinit var underTest: SharedNotificationContainerInteractor
-
-    @Before
-    fun setUp() {
-        configurationRepository = FakeConfigurationRepository()
-        underTest =
-            SharedNotificationContainerInteractor(
-                configurationRepository,
-                mContext,
-                ResourcesSplitShadeStateController()
-            )
-    }
+    private val kosmos = testKosmos()
+    private val testScope = kosmos.testScope
+    private val keyguardRepository = kosmos.fakeKeyguardRepository
+    private val configurationRepository = kosmos.fakeConfigurationRepository
+    private val fingerprintPropertyRepository = kosmos.fingerprintPropertyRepository
+    private val underTest = kosmos.sharedNotificationContainerInteractor
 
     @Test
-    fun validateConfigValues() = runTest {
-        overrideResource(R.bool.config_use_split_notification_shade, true)
-        overrideResource(R.bool.config_use_large_screen_shade_header, false)
-        overrideResource(R.dimen.notification_panel_margin_horizontal, 0)
-        overrideResource(R.dimen.notification_panel_margin_bottom, 10)
-        overrideResource(R.dimen.notification_panel_margin_top, 10)
-        overrideResource(R.dimen.large_screen_shade_header_height, 0)
-        overrideResource(R.dimen.keyguard_split_shade_top_margin, 55)
+    fun validateConfigValues() =
+        testScope.runTest {
+            overrideResource(R.bool.config_use_split_notification_shade, true)
+            overrideResource(R.bool.config_use_large_screen_shade_header, false)
+            overrideResource(R.dimen.notification_panel_margin_horizontal, 0)
+            overrideResource(R.dimen.notification_panel_margin_bottom, 10)
+            overrideResource(R.dimen.notification_panel_margin_top, 10)
+            overrideResource(R.dimen.large_screen_shade_header_height, 0)
+            overrideResource(R.dimen.keyguard_split_shade_top_margin, 55)
 
-        val dimens = collectLastValue(underTest.configurationBasedDimensions)
+            val dimens = collectLastValue(underTest.configurationBasedDimensions)
 
-        configurationRepository.onAnyConfigurationChange()
-        runCurrent()
+            configurationRepository.onAnyConfigurationChange()
+            runCurrent()
 
-        val lastDimens = dimens()!!
+            val lastDimens = dimens()!!
 
-        assertThat(lastDimens.useSplitShade).isTrue()
-        assertThat(lastDimens.useLargeScreenHeader).isFalse()
-        assertThat(lastDimens.marginHorizontal).isEqualTo(0)
-        assertThat(lastDimens.marginBottom).isGreaterThan(0)
-        assertThat(lastDimens.marginTop).isGreaterThan(0)
-        assertThat(lastDimens.marginTopLargeScreen).isEqualTo(0)
-        assertThat(lastDimens.keyguardSplitShadeTopMargin).isEqualTo(55)
-    }
+            assertThat(lastDimens.useSplitShade).isTrue()
+            assertThat(lastDimens.useLargeScreenHeader).isFalse()
+            assertThat(lastDimens.marginHorizontal).isEqualTo(0)
+            assertThat(lastDimens.marginBottom).isGreaterThan(0)
+            assertThat(lastDimens.marginTop).isGreaterThan(0)
+            assertThat(lastDimens.marginTopLargeScreen).isEqualTo(0)
+            assertThat(lastDimens.keyguardSplitShadeTopMargin).isEqualTo(55)
+        }
+
+    @Test
+    fun useExtraShelfSpaceIsTrueWithUdfps() =
+        testScope.runTest {
+            val useExtraShelfSpace by collectLastValue(underTest.useExtraShelfSpace)
+
+            keyguardRepository.ambientIndicationVisible.value = true
+            fingerprintPropertyRepository.supportsUdfps()
+
+            assertThat(useExtraShelfSpace).isEqualTo(true)
+        }
+
+    @Test
+    fun useExtraShelfSpaceIsTrueWithRearFpsAndNoAmbientIndicationArea() =
+        testScope.runTest {
+            val useExtraShelfSpace by collectLastValue(underTest.useExtraShelfSpace)
+
+            keyguardRepository.ambientIndicationVisible.value = false
+            fingerprintPropertyRepository.supportsRearFps()
+
+            assertThat(useExtraShelfSpace).isEqualTo(true)
+        }
+
+    @Test
+    fun useExtraShelfSpaceIsFalseWithRearFpsAndAmbientIndicationArea() =
+        testScope.runTest {
+            val useExtraShelfSpace by collectLastValue(underTest.useExtraShelfSpace)
+
+            keyguardRepository.ambientIndicationVisible.value = true
+            fingerprintPropertyRepository.supportsRearFps()
+
+            assertThat(useExtraShelfSpace).isEqualTo(false)
+        }
 }
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 f0205b3..36a4712 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
@@ -332,8 +332,8 @@
     fun maxNotificationsOnLockscreen() =
         testScope.runTest {
             var notificationCount = 10
-            val maxNotifications by
-                collectLastValue(underTest.getMaxNotifications { notificationCount })
+            val calculateSpace = { space: Float, useExtraShelfSpace: Boolean -> notificationCount }
+            val maxNotifications by collectLastValue(underTest.getMaxNotifications(calculateSpace))
 
             showLockscreen()
 
@@ -355,8 +355,8 @@
     fun maxNotificationsOnLockscreen_DoesNotUpdateWhenUserInteracting() =
         testScope.runTest {
             var notificationCount = 10
-            val maxNotifications by
-                collectLastValue(underTest.getMaxNotifications { notificationCount })
+            val calculateSpace = { space: Float, useExtraShelfSpace: Boolean -> notificationCount }
+            val maxNotifications by collectLastValue(underTest.getMaxNotifications(calculateSpace))
 
             showLockscreen()
 
@@ -390,7 +390,8 @@
     @Test
     fun maxNotificationsOnShade() =
         testScope.runTest {
-            val maxNotifications by collectLastValue(underTest.getMaxNotifications { 10 })
+            val calculateSpace = { space: Float, useExtraShelfSpace: Boolean -> 10 }
+            val maxNotifications by collectLastValue(underTest.getMaxNotifications(calculateSpace))
 
             // Show lockscreen with shade expanded
             showLockscreenWithShadeExpanded()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/theme/ThemeOverlayControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/theme/ThemeOverlayControllerTest.java
index 1123688..b58a41c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/theme/ThemeOverlayControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/theme/ThemeOverlayControllerTest.java
@@ -16,6 +16,7 @@
 
 package com.android.systemui.theme;
 
+import static android.os.Flags.FLAG_ALLOW_PRIVATE_PROFILE;
 import static android.util.TypedValue.TYPE_INT_COLOR_ARGB8;
 
 import static com.android.systemui.keyguard.WakefulnessLifecycle.WAKEFULNESS_AWAKE;
@@ -90,6 +91,9 @@
 
     private static final int USER_SYSTEM = UserHandle.USER_SYSTEM;
     private static final int USER_SECONDARY = 10;
+    private static final UserHandle MANAGED_USER_HANDLE = UserHandle.of(100);
+    private static final UserHandle PRIVATE_USER_HANDLE = UserHandle.of(101);
+
     @Mock
     private JavaAdapter mJavaAdapter;
     @Mock
@@ -174,6 +178,14 @@
                                 Integer.toHexString(mColorScheme.getSeed() | 0xff000000)));
                 return overlay;
             }
+
+            @VisibleForTesting
+            protected boolean isPrivateProfile(UserHandle userHandle) {
+                if (userHandle.getIdentifier() == PRIVATE_USER_HANDLE.getIdentifier()) {
+                    return true;
+                }
+                return false;
+            }
         };
 
         mWakefulnessLifecycle.dispatchFinishedWakingUp();
@@ -675,7 +687,8 @@
     @Test
     public void onProfileAdded_setsTheme() {
         mBroadcastReceiver.getValue().onReceive(null,
-                new Intent(Intent.ACTION_MANAGED_PROFILE_ADDED));
+                new Intent(Intent.ACTION_PROFILE_ADDED)
+                        .putExtra(Intent.EXTRA_USER, MANAGED_USER_HANDLE));
         verify(mThemeOverlayApplier).applyCurrentUserOverlays(any(), any(), anyInt(), any());
     }
 
@@ -684,7 +697,8 @@
         reset(mDeviceProvisionedController);
         when(mUserManager.isManagedProfile(anyInt())).thenReturn(false);
         mBroadcastReceiver.getValue().onReceive(null,
-                new Intent(Intent.ACTION_MANAGED_PROFILE_ADDED));
+                new Intent(Intent.ACTION_PROFILE_ADDED)
+                        .putExtra(Intent.EXTRA_USER, MANAGED_USER_HANDLE));
         verify(mThemeOverlayApplier)
                 .applyCurrentUserOverlays(any(), any(), anyInt(), any());
     }
@@ -694,12 +708,26 @@
         reset(mDeviceProvisionedController);
         when(mUserManager.isManagedProfile(anyInt())).thenReturn(true);
         mBroadcastReceiver.getValue().onReceive(null,
-                new Intent(Intent.ACTION_MANAGED_PROFILE_ADDED));
+                new Intent(Intent.ACTION_PROFILE_ADDED)
+                        .putExtra(Intent.EXTRA_USER, MANAGED_USER_HANDLE));
         verify(mThemeOverlayApplier, never())
                 .applyCurrentUserOverlays(any(), any(), anyInt(), any());
     }
 
     @Test
+    public void onPrivateProfileAdded_ignoresUntilStartComplete() {
+        mSetFlagsRule.enableFlags(FLAG_ALLOW_PRIVATE_PROFILE);
+        reset(mDeviceProvisionedController);
+        when(mUserManager.isManagedProfile(anyInt())).thenReturn(false);
+        mBroadcastReceiver.getValue().onReceive(null,
+                (new Intent(Intent.ACTION_PROFILE_ADDED))
+                        .putExtra(Intent.EXTRA_USER, PRIVATE_USER_HANDLE));
+        verify(mThemeOverlayApplier, never())
+                .applyCurrentUserOverlays(any(), any(), anyInt(), any());
+    }
+
+
+    @Test
     public void onWallpaperColorsChanged_firstEventBeforeUserSetup_shouldBeAccepted() {
         // By default, on setup() we make this controller return that the user finished setup
         // wizard. This test on the other hand, is testing the setup flow.
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 5b9b390..b217195 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/wmshell/BubblesTest.java
@@ -29,6 +29,8 @@
 
 import static com.google.common.truth.Truth.assertThat;
 
+import static kotlinx.coroutines.flow.FlowKt.emptyFlow;
+
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotNull;
@@ -97,6 +99,7 @@
 import com.android.systemui.colorextraction.SysuiColorExtractor;
 import com.android.systemui.common.ui.data.repository.FakeConfigurationRepository;
 import com.android.systemui.common.ui.domain.interactor.ConfigurationInteractor;
+import com.android.systemui.deviceentry.domain.interactor.DeviceEntryUdfpsInteractor;
 import com.android.systemui.dump.DumpManager;
 import com.android.systemui.flags.FakeFeatureFlags;
 import com.android.systemui.flags.FakeFeatureFlagsClassic;
@@ -465,6 +468,10 @@
         ResourcesSplitShadeStateController splitShadeStateController =
                 new ResourcesSplitShadeStateController();
 
+        DeviceEntryUdfpsInteractor deviceEntryUdfpsInteractor =
+                mock(DeviceEntryUdfpsInteractor.class);
+        when(deviceEntryUdfpsInteractor.isUdfpsSupported()).thenReturn(emptyFlow());
+
         mShadeInteractor =
                 new ShadeInteractorImpl(
                         mTestScope.getBackgroundScope(),
@@ -481,7 +488,9 @@
                                 new SharedNotificationContainerInteractor(
                                         configurationRepository,
                                         mContext,
-                                        splitShadeStateController),
+                                        splitShadeStateController,
+                                        keyguardInteractor,
+                                        deviceEntryUdfpsInteractor),
                                 shadeRepository
                         )
                 );
diff --git a/packages/SystemUI/tests/utils/src/android/graphics/drawable/TestStubDrawable.kt b/packages/SystemUI/tests/utils/src/android/graphics/drawable/TestStubDrawable.kt
new file mode 100644
index 0000000..b88f302
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/android/graphics/drawable/TestStubDrawable.kt
@@ -0,0 +1,35 @@
+/*
+ * 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.graphics.drawable
+
+import android.graphics.Canvas
+import android.graphics.ColorFilter
+import android.graphics.PixelFormat
+
+/**
+ * Stub drawable that does nothing. It's to be used in tests as a mock drawable and checked for the
+ * same instance
+ */
+class TestStubDrawable : Drawable() {
+
+    override fun draw(canvas: Canvas) = Unit
+    override fun setAlpha(alpha: Int) = Unit
+    override fun setColorFilter(colorFilter: ColorFilter?) = Unit
+    override fun getOpacity(): Int = PixelFormat.UNKNOWN
+
+    override fun equals(other: Any?): Boolean = this === other
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/accessibility/data/repository/FakeColorCorrectionRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/accessibility/data/repository/FakeColorCorrectionRepository.kt
new file mode 100644
index 0000000..607a4f3
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/accessibility/data/repository/FakeColorCorrectionRepository.kt
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.accessibility.data.repository
+
+import android.os.UserHandle
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.StateFlow
+
+class FakeColorCorrectionRepository : ColorCorrectionRepository {
+    private val userMap = mutableMapOf<Int, MutableStateFlow<Boolean>>()
+
+    override fun isEnabled(userHandle: UserHandle): StateFlow<Boolean> {
+        return getFlow(userHandle.identifier)
+    }
+
+    override suspend fun setIsEnabled(isEnabled: Boolean, userHandle: UserHandle): Boolean {
+        getFlow(userHandle.identifier).value = isEnabled
+        return true
+    }
+
+    /** initializes the flow if already not */
+    private fun getFlow(userId: Int): MutableStateFlow<Boolean> {
+        return userMap.getOrPut(userId) { MutableStateFlow(false) }
+    }
+}
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/data/repository/FakeBiometricStatusRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/data/repository/FakeBiometricStatusRepository.kt
new file mode 100644
index 0000000..1c8bd3b
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/biometrics/data/repository/FakeBiometricStatusRepository.kt
@@ -0,0 +1,34 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ *
+ */
+
+package com.android.systemui.biometrics.data.repository
+
+import com.android.systemui.biometrics.shared.model.AuthenticationReason
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.asStateFlow
+
+class FakeBiometricStatusRepository : BiometricStatusRepository {
+    private val _fingerprintAuthenticationReason =
+        MutableStateFlow<AuthenticationReason>(AuthenticationReason.NotRunning)
+    override val fingerprintAuthenticationReason: StateFlow<AuthenticationReason> =
+        _fingerprintAuthenticationReason.asStateFlow()
+
+    fun setFingerprintAuthenticationReason(reason: AuthenticationReason) {
+        _fingerprintAuthenticationReason.value = reason
+    }
+}
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 f84481c..ff5179a 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
@@ -1,5 +1,6 @@
 package com.android.systemui.bouncer.data.repository
 
+import com.android.systemui.biometrics.shared.SideFpsControllerRefactor
 import com.android.systemui.bouncer.shared.constants.KeyguardBouncerConstants
 import com.android.systemui.bouncer.shared.model.BouncerShowMessageModel
 import com.android.systemui.dagger.SysUISingleton
@@ -113,7 +114,9 @@
         _isBackButtonEnabled.value = isBackButtonEnabled
     }
 
+    // TODO(b/288175061): remove with Flags.FLAG_SIDEFPS_CONTROLLER_REFACTOR
     override fun setSideFpsShowing(isShowing: Boolean) {
+        SideFpsControllerRefactor.assertInLegacyMode()
         _sideFpsShowing.value = isShowing
     }
 }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeDeviceEntryFingerprintAuthRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeDeviceEntryFingerprintAuthRepository.kt
index c9160ef..1d44929 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeDeviceEntryFingerprintAuthRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/keyguard/data/repository/FakeDeviceEntryFingerprintAuthRepository.kt
@@ -54,6 +54,11 @@
     private var _authenticationStatus = MutableStateFlow<FingerprintAuthenticationStatus?>(null)
     override val authenticationStatus: Flow<FingerprintAuthenticationStatus>
         get() = _authenticationStatus.filterNotNull()
+
+    private var _shouldUpdateIndicatorVisibility = MutableStateFlow(false)
+    override val shouldUpdateIndicatorVisibility: Flow<Boolean>
+        get() = _shouldUpdateIndicatorVisibility
+
     fun setAuthenticationStatus(status: FingerprintAuthenticationStatus) {
         _authenticationStatus.value = status
     }
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 0e7c662..4200f05 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
@@ -41,6 +41,10 @@
     private val _deferKeyguardDone: MutableSharedFlow<KeyguardDone> = MutableSharedFlow()
     override val keyguardDone: Flow<KeyguardDone> = _deferKeyguardDone
 
+    private val _keyguardDoneAnimationsFinished: MutableSharedFlow<Unit> =
+        MutableSharedFlow(extraBufferCapacity = 1)
+    override val keyguardDoneAnimationsFinished: Flow<Unit> = _keyguardDoneAnimationsFinished
+
     private val _clockShouldBeCentered = MutableStateFlow<Boolean>(true)
     override val clockShouldBeCentered: Flow<Boolean> = _clockShouldBeCentered
 
@@ -121,6 +125,8 @@
 
     override val lastRootViewTapPosition: MutableStateFlow<Point?> = MutableStateFlow(null)
 
+    override val ambientIndicationVisible: MutableStateFlow<Boolean> = MutableStateFlow(false)
+
     override fun setQuickSettingsVisible(isVisible: Boolean) {
         _isQuickSettingsVisible.value = isVisible
     }
@@ -174,6 +180,10 @@
         _deferKeyguardDone.emit(timing)
     }
 
+    override fun keyguardDoneAnimationsFinished() {
+        _keyguardDoneAnimationsFinished.tryEmit(Unit)
+    }
+
     override fun setClockShouldBeCentered(shouldBeCentered: Boolean) {
         _clockShouldBeCentered.value = shouldBeCentered
     }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/log/LogAssert.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/log/LogAssert.kt
index 10f9346..6ccb3bc 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/log/LogAssert.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/log/LogAssert.kt
@@ -59,6 +59,17 @@
 ): TerribleFailureLog =
     assertLogsWtf(message = message, allowMultiple = allowMultiple) { loggingRunnable.run() }
 
+fun assertLogsWtfs(
+    message: String = "Expected Log.wtf to be called once or more",
+    loggingBlock: () -> Unit,
+): TerribleFailureLog = assertLogsWtf(message, allowMultiple = true, loggingBlock)
+
+@JvmOverloads
+fun assertLogsWtfs(
+    message: String = "Expected Log.wtf to be called once or more",
+    loggingRunnable: Runnable,
+): TerribleFailureLog = assertLogsWtfs(message) { loggingRunnable.run() }
+
 /** The data passed to [TerribleFailureHandler.onTerribleFailure] */
 data class TerribleFailureLog(
     val tag: String,
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/QuickSettingsKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/QuickSettingsKosmos.kt
index 1cb2587..6332c1a 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/QuickSettingsKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/QuickSettingsKosmos.kt
@@ -19,9 +19,14 @@
 import com.android.internal.logging.testing.UiEventLoggerFake
 import com.android.systemui.InstanceIdSequenceFake
 import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.plugins.qs.QSFactory
+import com.android.systemui.qs.tiles.di.NewQSTileFactory
 
 val Kosmos.instanceIdSequenceFake: InstanceIdSequenceFake by
     Kosmos.Fixture { InstanceIdSequenceFake(0) }
 val Kosmos.uiEventLogger: UiEventLoggerFake by Kosmos.Fixture { UiEventLoggerFake() }
 val Kosmos.qsEventLogger: QsEventLoggerFake by
     Kosmos.Fixture { QsEventLoggerFake(uiEventLogger, instanceIdSequenceFake) }
+
+var Kosmos.newQSTileFactory by Kosmos.Fixture<NewQSTileFactory>()
+var Kosmos.qsTileFactory by Kosmos.Fixture<QSFactory>()
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/SideFpsLottieViewWrapper.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/external/CustomTileStatePersisterKosmos.kt
similarity index 66%
copy from packages/SystemUI/src/com/android/systemui/biometrics/SideFpsLottieViewWrapper.kt
copy to packages/SystemUI/tests/utils/src/com/android/systemui/qs/external/CustomTileStatePersisterKosmos.kt
index e98f6db..f01e3aa 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/SideFpsLottieViewWrapper.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/external/CustomTileStatePersisterKosmos.kt
@@ -13,12 +13,11 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package com.android.systemui.biometrics
 
-import android.content.Context
-import android.util.AttributeSet
-import com.android.systemui.util.wrapper.LottieViewWrapper
+package com.android.systemui.qs.external
 
-class SideFpsLottieViewWrapper
-@JvmOverloads
-constructor(context: Context, attrs: AttributeSet? = null) : LottieViewWrapper(context, attrs)
+import com.android.systemui.kosmos.Kosmos
+
+var Kosmos.customTileStatePersister: CustomTileStatePersister by
+    Kosmos.Fixture { fakeCustomTileStatePersister }
+val Kosmos.fakeCustomTileStatePersister by Kosmos.Fixture { FakeCustomTileStatePersister() }
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/SideFpsLottieViewWrapper.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/external/TileLifecycleManagerFactoryKosmos.kt
similarity index 65%
copy from packages/SystemUI/src/com/android/systemui/biometrics/SideFpsLottieViewWrapper.kt
copy to packages/SystemUI/tests/utils/src/com/android/systemui/qs/external/TileLifecycleManagerFactoryKosmos.kt
index e98f6db..f8ce707 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/SideFpsLottieViewWrapper.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/external/TileLifecycleManagerFactoryKosmos.kt
@@ -13,12 +13,12 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package com.android.systemui.biometrics
 
-import android.content.Context
-import android.util.AttributeSet
-import com.android.systemui.util.wrapper.LottieViewWrapper
+package com.android.systemui.qs.external
 
-class SideFpsLottieViewWrapper
-@JvmOverloads
-constructor(context: Context, attrs: AttributeSet? = null) : LottieViewWrapper(context, attrs)
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.util.mockito.mock
+
+/** Returns mocks */
+var Kosmos.tileLifecycleManagerFactory: TileLifecycleManager.Factory by
+    Kosmos.Fixture { TileLifecycleManager.Factory { _, _ -> mock<TileLifecycleManager>() } }
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/SideFpsLottieViewWrapper.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/pipeline/data/model/RestoreProcessorsKosmos.kt
similarity index 60%
copy from packages/SystemUI/src/com/android/systemui/biometrics/SideFpsLottieViewWrapper.kt
copy to packages/SystemUI/tests/utils/src/com/android/systemui/qs/pipeline/data/model/RestoreProcessorsKosmos.kt
index e98f6db..d93dd8d 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/SideFpsLottieViewWrapper.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/pipeline/data/model/RestoreProcessorsKosmos.kt
@@ -13,12 +13,17 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package com.android.systemui.biometrics
 
-import android.content.Context
-import android.util.AttributeSet
-import com.android.systemui.util.wrapper.LottieViewWrapper
+package com.android.systemui.qs.pipeline.data.model
 
-class SideFpsLottieViewWrapper
-@JvmOverloads
-constructor(context: Context, attrs: AttributeSet? = null) : LottieViewWrapper(context, attrs)
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.qs.pipeline.data.restoreprocessors.WorkTileRestoreProcessor
+
+val Kosmos.workTileRestoreProcessor by Kosmos.Fixture { WorkTileRestoreProcessor() }
+
+var Kosmos.restoreProcessors by
+    Kosmos.Fixture {
+        setOf(
+            workTileRestoreProcessor,
+        )
+    }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/pipeline/data/repository/QSPipelineRepositoryKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/pipeline/data/repository/QSPipelineRepositoryKosmos.kt
new file mode 100644
index 0000000..0091482
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/pipeline/data/repository/QSPipelineRepositoryKosmos.kt
@@ -0,0 +1,38 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.pipeline.data.repository
+
+import com.android.systemui.kosmos.Kosmos
+
+val Kosmos.fakeTileSpecRepository by Kosmos.Fixture { FakeTileSpecRepository() }
+var Kosmos.tileSpecRepository: TileSpecRepository by Kosmos.Fixture { fakeTileSpecRepository }
+
+val Kosmos.fakeAutoAddRepository by Kosmos.Fixture { FakeAutoAddRepository() }
+var Kosmos.autoAddRepository: AutoAddRepository by Kosmos.Fixture { fakeAutoAddRepository }
+
+val Kosmos.fakeRestoreRepository by Kosmos.Fixture { FakeQSSettingsRestoredRepository() }
+var Kosmos.restoreRepository: QSSettingsRestoredRepository by
+    Kosmos.Fixture { fakeRestoreRepository }
+
+val Kosmos.fakeInstalledTilesRepository by
+    Kosmos.Fixture { FakeInstalledTilesComponentRepository() }
+var Kosmos.installedTilesRepository: InstalledTilesComponentRepository by
+    Kosmos.Fixture { fakeInstalledTilesRepository }
+
+val Kosmos.fakeCustomTileAddedRepository by Kosmos.Fixture { FakeCustomTileAddedRepository() }
+var Kosmos.customTileAddedRepository: CustomTileAddedRepository by
+    Kosmos.Fixture { fakeCustomTileAddedRepository }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/pipeline/domain/autoaddable/AutoAddablesKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/pipeline/domain/autoaddable/AutoAddablesKosmos.kt
new file mode 100644
index 0000000..35f178b
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/pipeline/domain/autoaddable/AutoAddablesKosmos.kt
@@ -0,0 +1,26 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.pipeline.domain.autoaddable
+
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.qs.pipeline.data.model.workTileRestoreProcessor
+import com.android.systemui.settings.userTracker
+
+val Kosmos.workTileAutoAddable by
+    Kosmos.Fixture { WorkTileAutoAddable(userTracker, workTileRestoreProcessor) }
+
+var Kosmos.autoAddables by Kosmos.Fixture { setOf(workTileAutoAddable) }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/pipeline/domain/autoaddable/FakeAutoAddable.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/pipeline/domain/autoaddable/FakeAutoAddable.kt
index ebdd6fd..bf8f4da 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/pipeline/domain/autoaddable/FakeAutoAddable.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/pipeline/domain/autoaddable/FakeAutoAddable.kt
@@ -39,14 +39,18 @@
         return getFlow(userId).asStateFlow().filterNotNull()
     }
 
-    suspend fun sendRemoveSignal(userId: Int) {
+    fun sendRemoveSignal(userId: Int) {
         getFlow(userId).value = AutoAddSignal.Remove(spec)
     }
 
-    suspend fun sendAddSignal(userId: Int, position: Int = POSITION_AT_END) {
+    fun sendAddSignal(userId: Int, position: Int = POSITION_AT_END) {
         getFlow(userId).value = AutoAddSignal.Add(spec, position)
     }
 
+    fun sendRemoveTrackingSignal(userId: Int) {
+        getFlow(userId).value = AutoAddSignal.RemoveTracking(spec)
+    }
+
     override val description: String
         get() = "FakeAutoAddable($spec)"
 }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/pipeline/domain/interactor/AutoAddInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/pipeline/domain/interactor/AutoAddInteractorKosmos.kt
new file mode 100644
index 0000000..5e8471c
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/pipeline/domain/interactor/AutoAddInteractorKosmos.kt
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.pipeline.domain.interactor
+
+import com.android.systemui.dump.dumpManager
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.applicationCoroutineScope
+import com.android.systemui.qs.pipeline.data.repository.autoAddRepository
+import com.android.systemui.qs.pipeline.domain.autoaddable.autoAddables
+import com.android.systemui.qs.pipeline.shared.logging.qsLogger
+
+val Kosmos.autoAddInteractor by
+    Kosmos.Fixture {
+        AutoAddInteractor(
+            autoAddables,
+            autoAddRepository,
+            dumpManager,
+            qsLogger,
+            applicationCoroutineScope,
+        )
+    }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/pipeline/domain/interactor/CurrentTilesInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/pipeline/domain/interactor/CurrentTilesInteractorKosmos.kt
new file mode 100644
index 0000000..67df563
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/pipeline/domain/interactor/CurrentTilesInteractorKosmos.kt
@@ -0,0 +1,52 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.pipeline.domain.interactor
+
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.applicationCoroutineScope
+import com.android.systemui.kosmos.testDispatcher
+import com.android.systemui.qs.external.customTileStatePersister
+import com.android.systemui.qs.external.tileLifecycleManagerFactory
+import com.android.systemui.qs.newQSTileFactory
+import com.android.systemui.qs.pipeline.data.repository.customTileAddedRepository
+import com.android.systemui.qs.pipeline.data.repository.installedTilesRepository
+import com.android.systemui.qs.pipeline.data.repository.tileSpecRepository
+import com.android.systemui.qs.pipeline.shared.logging.qsLogger
+import com.android.systemui.qs.pipeline.shared.pipelineFlagsRepository
+import com.android.systemui.qs.qsTileFactory
+import com.android.systemui.settings.userTracker
+import com.android.systemui.user.data.repository.userRepository
+
+val Kosmos.currentTilesInteractor: CurrentTilesInteractor by
+    Kosmos.Fixture {
+        CurrentTilesInteractorImpl(
+            tileSpecRepository,
+            installedTilesRepository,
+            userRepository,
+            customTileStatePersister,
+            { newQSTileFactory },
+            qsTileFactory,
+            customTileAddedRepository,
+            tileLifecycleManagerFactory,
+            userTracker,
+            testDispatcher,
+            testDispatcher,
+            applicationCoroutineScope,
+            qsLogger,
+            pipelineFlagsRepository,
+        )
+    }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/qs/pipeline/domain/interactor/RestoreReconciliationInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/pipeline/domain/interactor/RestoreReconciliationInteractorKosmos.kt
new file mode 100644
index 0000000..55c23d4
--- /dev/null
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/pipeline/domain/interactor/RestoreReconciliationInteractorKosmos.kt
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.qs.pipeline.domain.interactor
+
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.kosmos.applicationCoroutineScope
+import com.android.systemui.kosmos.testDispatcher
+import com.android.systemui.qs.pipeline.data.model.restoreProcessors
+import com.android.systemui.qs.pipeline.data.repository.autoAddRepository
+import com.android.systemui.qs.pipeline.data.repository.restoreRepository
+import com.android.systemui.qs.pipeline.data.repository.tileSpecRepository
+import com.android.systemui.qs.pipeline.shared.logging.qsLogger
+
+val Kosmos.restoreReconciliationInteractor by
+    Kosmos.Fixture {
+        RestoreReconciliationInteractor(
+            tileSpecRepository,
+            autoAddRepository,
+            restoreRepository,
+            restoreProcessors,
+            qsLogger,
+            applicationCoroutineScope,
+            testDispatcher,
+        )
+    }
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/SideFpsLottieViewWrapper.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/pipeline/shared/QSPipelineFlagRepositoryKosmos.kt
similarity index 66%
copy from packages/SystemUI/src/com/android/systemui/biometrics/SideFpsLottieViewWrapper.kt
copy to packages/SystemUI/tests/utils/src/com/android/systemui/qs/pipeline/shared/QSPipelineFlagRepositoryKosmos.kt
index e98f6db..961545a 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/SideFpsLottieViewWrapper.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/pipeline/shared/QSPipelineFlagRepositoryKosmos.kt
@@ -13,12 +13,9 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package com.android.systemui.biometrics
 
-import android.content.Context
-import android.util.AttributeSet
-import com.android.systemui.util.wrapper.LottieViewWrapper
+package com.android.systemui.qs.pipeline.shared
 
-class SideFpsLottieViewWrapper
-@JvmOverloads
-constructor(context: Context, attrs: AttributeSet? = null) : LottieViewWrapper(context, attrs)
+import com.android.systemui.kosmos.Kosmos
+
+val Kosmos.pipelineFlagsRepository by Kosmos.Fixture { QSPipelineFlagsRepository() }
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/SideFpsLottieViewWrapper.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/pipeline/shared/logging/QSPipelineLoggerKosmos.kt
similarity index 66%
copy from packages/SystemUI/src/com/android/systemui/biometrics/SideFpsLottieViewWrapper.kt
copy to packages/SystemUI/tests/utils/src/com/android/systemui/qs/pipeline/shared/logging/QSPipelineLoggerKosmos.kt
index e98f6db..7d52f5d 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/SideFpsLottieViewWrapper.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/pipeline/shared/logging/QSPipelineLoggerKosmos.kt
@@ -13,12 +13,11 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package com.android.systemui.biometrics
 
-import android.content.Context
-import android.util.AttributeSet
-import com.android.systemui.util.wrapper.LottieViewWrapper
+package com.android.systemui.qs.pipeline.shared.logging
 
-class SideFpsLottieViewWrapper
-@JvmOverloads
-constructor(context: Context, attrs: AttributeSet? = null) : LottieViewWrapper(context, attrs)
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.util.mockito.mock
+
+/** mock */
+var Kosmos.qsLogger: QSPipelineLogger by Kosmos.Fixture { mock<QSPipelineLogger>() }
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/SideFpsLottieViewWrapper.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/impl/colorcorrection/ColorCorrectionTileKosmos.kt
similarity index 63%
copy from packages/SystemUI/src/com/android/systemui/biometrics/SideFpsLottieViewWrapper.kt
copy to packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/impl/colorcorrection/ColorCorrectionTileKosmos.kt
index e98f6db..0357036 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/SideFpsLottieViewWrapper.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/qs/tiles/impl/colorcorrection/ColorCorrectionTileKosmos.kt
@@ -13,12 +13,12 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package com.android.systemui.biometrics
 
-import android.content.Context
-import android.util.AttributeSet
-import com.android.systemui.util.wrapper.LottieViewWrapper
+package com.android.systemui.qs.tiles.impl.colorcorrection
 
-class SideFpsLottieViewWrapper
-@JvmOverloads
-constructor(context: Context, attrs: AttributeSet? = null) : LottieViewWrapper(context, attrs)
+import com.android.systemui.accessibility.qs.QSAccessibilityModule
+import com.android.systemui.kosmos.Kosmos
+import com.android.systemui.qs.qsEventLogger
+
+val Kosmos.qsColorCorrectionTileConfig by
+    Kosmos.Fixture { QSAccessibilityModule.provideColorCorrectionTileConfig(qsEventLogger) }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/settings/FakeUserTracker.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/settings/FakeUserTracker.kt
index 7494ccf..2ca338a 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/settings/FakeUserTracker.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/settings/FakeUserTracker.kt
@@ -72,6 +72,7 @@
         onBeforeUserSwitching()
         onUserChanging()
         onUserChanged()
+        onProfileChanged()
     }
 
     fun onBeforeUserSwitching(userId: Int = _userId) {
diff --git a/packages/SystemUI/src/com/android/systemui/biometrics/SideFpsLottieViewWrapper.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/settings/UserTrackerKosmos.kt
similarity index 66%
copy from packages/SystemUI/src/com/android/systemui/biometrics/SideFpsLottieViewWrapper.kt
copy to packages/SystemUI/tests/utils/src/com/android/systemui/settings/UserTrackerKosmos.kt
index e98f6db..ffa86ff 100644
--- a/packages/SystemUI/src/com/android/systemui/biometrics/SideFpsLottieViewWrapper.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/settings/UserTrackerKosmos.kt
@@ -13,12 +13,10 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package com.android.systemui.biometrics
 
-import android.content.Context
-import android.util.AttributeSet
-import com.android.systemui.util.wrapper.LottieViewWrapper
+package com.android.systemui.settings
 
-class SideFpsLottieViewWrapper
-@JvmOverloads
-constructor(context: Context, attrs: AttributeSet? = null) : LottieViewWrapper(context, attrs)
+import com.android.systemui.kosmos.Kosmos
+
+val Kosmos.fakeUserTracker by Kosmos.Fixture { FakeUserTracker() }
+var Kosmos.userTracker: UserTracker by Kosmos.Fixture { fakeUserTracker }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/smartspace/data/repository/FakeSmartspaceRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/smartspace/data/repository/FakeSmartspaceRepository.kt
index c8013ef..862e52d 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/smartspace/data/repository/FakeSmartspaceRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/smartspace/data/repository/FakeSmartspaceRepository.kt
@@ -10,12 +10,12 @@
 
     override val isSmartspaceRemoteViewsEnabled = smartspaceRemoteViewsEnabled
 
-    private val _lockscreenSmartspaceTargets: MutableStateFlow<List<SmartspaceTarget>> =
+    private val _communalSmartspaceTargets: MutableStateFlow<List<SmartspaceTarget>> =
         MutableStateFlow(emptyList())
-    override val lockscreenSmartspaceTargets: Flow<List<SmartspaceTarget>> =
-        _lockscreenSmartspaceTargets
+    override val communalSmartspaceTargets: Flow<List<SmartspaceTarget>> =
+        _communalSmartspaceTargets
 
-    fun setLockscreenSmartspaceTargets(targets: List<SmartspaceTarget>) {
-        _lockscreenSmartspaceTargets.value = targets
+    fun setCommunalSmartspaceTargets(targets: List<SmartspaceTarget>) {
+        _communalSmartspaceTargets.value = targets
     }
 }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/domain/interactor/SharedNotificationContainerInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/domain/interactor/SharedNotificationContainerInteractorKosmos.kt
index 3403227..13d577b 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/domain/interactor/SharedNotificationContainerInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/stack/domain/interactor/SharedNotificationContainerInteractorKosmos.kt
@@ -18,6 +18,8 @@
 
 import android.content.applicationContext
 import com.android.systemui.common.ui.data.repository.configurationRepository
+import com.android.systemui.deviceentry.domain.interactor.deviceEntryUdfpsInteractor
+import com.android.systemui.keyguard.domain.interactor.keyguardInteractor
 import com.android.systemui.kosmos.Kosmos
 import com.android.systemui.statusbar.policy.splitShadeStateController
 
@@ -27,5 +29,7 @@
             configurationRepository = configurationRepository,
             context = applicationContext,
             splitShadeStateController = splitShadeStateController,
+            keyguardInteractor = keyguardInteractor,
+            deviceEntryUdfpsInteractor = deviceEntryUdfpsInteractor,
         )
     }
diff --git a/packages/services/CameraExtensionsProxy/src/com/android/cameraextensions/CameraExtensionsProxyService.java b/packages/services/CameraExtensionsProxy/src/com/android/cameraextensions/CameraExtensionsProxyService.java
index b315f4a..7fcef9c 100644
--- a/packages/services/CameraExtensionsProxy/src/com/android/cameraextensions/CameraExtensionsProxyService.java
+++ b/packages/services/CameraExtensionsProxy/src/com/android/cameraextensions/CameraExtensionsProxyService.java
@@ -76,6 +76,7 @@
 import android.view.Surface;
 
 import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
 import androidx.camera.extensions.impl.AutoImageCaptureExtenderImpl;
 import androidx.camera.extensions.impl.AutoPreviewExtenderImpl;
 import androidx.camera.extensions.impl.BeautyImageCaptureExtenderImpl;
@@ -94,6 +95,7 @@
 import androidx.camera.extensions.impl.PreviewExtenderImpl.ProcessorType;
 import androidx.camera.extensions.impl.PreviewImageProcessorImpl;
 import androidx.camera.extensions.impl.ProcessResultImpl;
+import androidx.camera.extensions.impl.ProcessorImpl;
 import androidx.camera.extensions.impl.RequestUpdateProcessorImpl;
 import androidx.camera.extensions.impl.advanced.AdvancedExtenderImpl;
 import androidx.camera.extensions.impl.advanced.AutoAdvancedExtenderImpl;
@@ -101,6 +103,7 @@
 import androidx.camera.extensions.impl.advanced.BokehAdvancedExtenderImpl;
 import androidx.camera.extensions.impl.advanced.Camera2OutputConfigImpl;
 import androidx.camera.extensions.impl.advanced.Camera2SessionConfigImpl;
+import androidx.camera.extensions.impl.advanced.EyesFreeVideographyAdvancedExtenderImpl;
 import androidx.camera.extensions.impl.advanced.HdrAdvancedExtenderImpl;
 import androidx.camera.extensions.impl.advanced.ImageProcessorImpl;
 import androidx.camera.extensions.impl.advanced.ImageReaderOutputConfigImpl;
@@ -112,6 +115,8 @@
 import androidx.camera.extensions.impl.advanced.SessionProcessorImpl;
 import androidx.camera.extensions.impl.advanced.SurfaceOutputConfigImpl;
 
+import com.android.internal.camera.flags.Flags;
+
 import java.io.IOException;
 import java.util.ArrayList;
 import java.util.HashMap;
@@ -135,22 +140,28 @@
     private static final String RESULTS_VERSION_PREFIX = "1.3";
     // Support for various latency improvements
     private static final String LATENCY_VERSION_PREFIX = "1.4";
-    private static final String[] ADVANCED_VERSION_PREFIXES = {LATENCY_VERSION_PREFIX,
-            ADVANCED_VERSION_PREFIX, RESULTS_VERSION_PREFIX };
-    private static final String[] SUPPORTED_VERSION_PREFIXES = {LATENCY_VERSION_PREFIX,
-            RESULTS_VERSION_PREFIX, ADVANCED_VERSION_PREFIX, "1.1", NON_INIT_VERSION_PREFIX};
+    private static final String EFV_VERSION_PREFIX = "1.5";
+    private static final String[] ADVANCED_VERSION_PREFIXES = {EFV_VERSION_PREFIX,
+            LATENCY_VERSION_PREFIX, ADVANCED_VERSION_PREFIX, RESULTS_VERSION_PREFIX };
+    private static final String[] SUPPORTED_VERSION_PREFIXES = {EFV_VERSION_PREFIX,
+            LATENCY_VERSION_PREFIX, RESULTS_VERSION_PREFIX, ADVANCED_VERSION_PREFIX, "1.1",
+            NON_INIT_VERSION_PREFIX};
     private static final boolean EXTENSIONS_PRESENT = checkForExtensions();
     private static final String EXTENSIONS_VERSION = EXTENSIONS_PRESENT ?
             (new ExtensionVersionImpl()).checkApiVersion(LATEST_VERSION) : null;
     private static final boolean ESTIMATED_LATENCY_API_SUPPORTED = checkForLatencyAPI();
     private static final boolean LATENCY_IMPROVEMENTS_SUPPORTED = EXTENSIONS_PRESENT &&
-            (EXTENSIONS_VERSION.startsWith(LATENCY_VERSION_PREFIX));
+            (EXTENSIONS_VERSION.startsWith(LATENCY_VERSION_PREFIX) ||
+                    (EXTENSIONS_VERSION.startsWith(EFV_VERSION_PREFIX)));
+    private static final boolean EFV_SUPPORTED = EXTENSIONS_PRESENT &&
+            (EXTENSIONS_VERSION.startsWith(EFV_VERSION_PREFIX));
     private static final boolean ADVANCED_API_SUPPORTED = checkForAdvancedAPI();
     private static final boolean INIT_API_SUPPORTED = EXTENSIONS_PRESENT &&
             (!EXTENSIONS_VERSION.startsWith(NON_INIT_VERSION_PREFIX));
     private static final boolean RESULT_API_SUPPORTED = EXTENSIONS_PRESENT &&
             (EXTENSIONS_VERSION.startsWith(RESULTS_VERSION_PREFIX) ||
-            EXTENSIONS_VERSION.startsWith(LATENCY_VERSION_PREFIX));
+            EXTENSIONS_VERSION.startsWith(LATENCY_VERSION_PREFIX) ||
+            EXTENSIONS_VERSION.startsWith(EFV_VERSION_PREFIX));
 
     private HashMap<String, Long> mMetadataVendorIdMap = new HashMap<>();
     private CameraManager mCameraManager;
@@ -509,6 +520,167 @@
      */
     public static Pair<PreviewExtenderImpl, ImageCaptureExtenderImpl> initializeExtension(
             int extensionType) {
+        if (Flags.concertMode()) {
+            if (extensionType == CameraExtensionCharacteristics.EXTENSION_EYES_FREE_VIDEOGRAPHY) {
+                // Basic extensions are deprecated starting with extension version 1.5
+                return new Pair<>(new PreviewExtenderImpl() {
+                    @Override
+                    public boolean isExtensionAvailable(String cameraId,
+                            CameraCharacteristics cameraCharacteristics) {
+                        return false;
+                    }
+
+                    @Override
+                    public void init(String cameraId, CameraCharacteristics cameraCharacteristics) {
+
+                    }
+
+                    @Override
+                    public androidx.camera.extensions.impl.CaptureStageImpl getCaptureStage() {
+                        return null;
+                    }
+
+                    @Override
+                    public ProcessorType getProcessorType() {
+                        return null;
+                    }
+
+                    @Override
+                    public ProcessorImpl getProcessor() {
+                        return null;
+                    }
+
+                    @Nullable
+                    @Override
+                    public List<Pair<Integer, Size[]>> getSupportedResolutions() {
+                        return null;
+                    }
+
+                    @Override
+                    public void onInit(String cameraId, CameraCharacteristics cameraCharacteristics,
+                            Context context) { }
+
+                    @Override
+                    public void onDeInit() { }
+
+                    @Override
+                    public androidx.camera.extensions.impl.CaptureStageImpl onPresetSession() {
+                        return null;
+                    }
+
+                    @Override
+                    public androidx.camera.extensions.impl.CaptureStageImpl onEnableSession() {
+                        return null;
+                    }
+
+                    @Override
+                    public androidx.camera.extensions.impl.CaptureStageImpl onDisableSession() {
+                        return null;
+                    }
+
+                    @Override
+                    public int onSessionType() {
+                        return 0;
+                    }
+                }, new ImageCaptureExtenderImpl() {
+                    @Override
+                    public boolean isExtensionAvailable(String cameraId,
+                            CameraCharacteristics cameraCharacteristics) {
+                        return false;
+                    }
+
+                    @Override
+                    public void init(String cameraId,
+                            CameraCharacteristics cameraCharacteristics) { }
+
+                    @Override
+                    public CaptureProcessorImpl getCaptureProcessor() {
+                        return null;
+                    }
+
+                    @Override
+                    public
+                    List<androidx.camera.extensions.impl.CaptureStageImpl> getCaptureStages() {
+                        return null;
+                    }
+
+                    @Override
+                    public int getMaxCaptureStage() {
+                        return 0;
+                    }
+
+                    @Override
+                    public List<Pair<Integer, Size[]>> getSupportedResolutions() {
+                        return null;
+                    }
+
+                    @Override
+                    public List<Pair<Integer, Size[]>> getSupportedPostviewResolutions(
+                            Size captureSize) {
+                        return null;
+                    }
+
+                    @Override
+                    public Range<Long> getEstimatedCaptureLatencyRange(
+                            Size captureOutputSize) {
+                        return null;
+                    }
+
+                    @Override
+                    public List<CaptureRequest.Key> getAvailableCaptureRequestKeys() {
+                        return null;
+                    }
+
+                    @Override
+                    public List<CaptureResult.Key> getAvailableCaptureResultKeys() {
+                        return null;
+                    }
+
+                    @Override
+                    public boolean isCaptureProcessProgressAvailable() {
+                        return false;
+                    }
+
+                    @Override
+                    public Pair<Long, Long> getRealtimeCaptureLatency() {
+                        return null;
+                    }
+
+                    @Override
+                    public boolean isPostviewAvailable() {
+                        return false;
+                    }
+
+                    @Override
+                    public void onInit(String cameraId,
+                            CameraCharacteristics cameraCharacteristics, Context context) { }
+
+                    @Override
+                    public void onDeInit() { }
+
+                    @Override
+                    public androidx.camera.extensions.impl.CaptureStageImpl onPresetSession() {
+                        return null;
+                    }
+
+                    @Override
+                    public androidx.camera.extensions.impl.CaptureStageImpl onEnableSession() {
+                        return null;
+                    }
+
+                    @Override
+                    public androidx.camera.extensions.impl.CaptureStageImpl onDisableSession() {
+                        return null;
+                    }
+
+                    @Override
+                    public int onSessionType() {
+                        return 0;
+                    }
+                });
+            }
+        }
+
         switch (extensionType) {
             case CameraExtensionCharacteristics.EXTENSION_AUTOMATIC:
                 return new Pair<>(new AutoPreviewExtenderImpl(),
@@ -533,6 +705,82 @@
      * @hide
      */
     public static AdvancedExtenderImpl initializeAdvancedExtensionImpl(int extensionType) {
+        if (Flags.concertMode()) {
+            if (extensionType == CameraExtensionCharacteristics.EXTENSION_EYES_FREE_VIDEOGRAPHY) {
+                if (EFV_SUPPORTED) {
+                    return new EyesFreeVideographyAdvancedExtenderImpl();
+                } else {
+                    return new AdvancedExtenderImpl() {
+                        @Override
+                        public boolean isExtensionAvailable(String cameraId,
+                                Map<String, CameraCharacteristics> characteristicsMap) {
+                            return false;
+                        }
+
+                        @Override
+                        public void init(String cameraId,
+                                Map<String, CameraCharacteristics> characteristicsMap) {
+
+                        }
+
+                        @Override
+                        public Range<Long> getEstimatedCaptureLatencyRange(String cameraId,
+                                Size captureOutputSize, int imageFormat) {
+                            return null;
+                        }
+
+                        @Override
+                        public Map<Integer, List<Size>> getSupportedPreviewOutputResolutions(
+                                String cameraId) {
+                            return null;
+                        }
+
+                        @Override
+                        public Map<Integer, List<Size>> getSupportedCaptureOutputResolutions(
+                                String cameraId) {
+                            return null;
+                        }
+
+                        @Override
+                        public Map<Integer, List<Size>> getSupportedPostviewResolutions(
+                                Size captureSize) {
+                            return null;
+                        }
+
+                        @Override
+                        public List<Size> getSupportedYuvAnalysisResolutions(String cameraId) {
+                            return null;
+                        }
+
+                        @Override
+                        public SessionProcessorImpl createSessionProcessor() {
+                            return null;
+                        }
+
+                        @Override
+                        public List<CaptureRequest.Key> getAvailableCaptureRequestKeys() {
+                            return null;
+                        }
+
+                        @Override
+                        public List<CaptureResult.Key> getAvailableCaptureResultKeys() {
+                            return null;
+                        }
+
+                        @Override
+                        public boolean isCaptureProcessProgressAvailable() {
+                            return false;
+                        }
+
+                        @Override
+                        public boolean isPostviewAvailable() {
+                            return false;
+                        }
+                    };
+                }
+            }
+        }
+
         switch (extensionType) {
             case CameraExtensionCharacteristics.EXTENSION_AUTOMATIC:
                 return new AutoAdvancedExtenderImpl();
diff --git a/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodRule.java b/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodRule.java
index d175713..513c095 100644
--- a/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodRule.java
+++ b/ravenwood/junit-src/android/platform/test/ravenwood/RavenwoodRule.java
@@ -16,6 +16,8 @@
 
 package android.platform.test.ravenwood;
 
+import static org.junit.Assert.fail;
+
 import android.platform.test.annotations.IgnoreUnderRavenwood;
 
 import org.junit.Assume;
@@ -36,6 +38,15 @@
 
     private static final boolean IS_UNDER_RAVENWOOD = RavenwoodRuleImpl.isUnderRavenwood();
 
+    /**
+     * When probing is enabled, all tests will be unconditionally run under Ravenwood to detect
+     * cases where a test is able to pass despite being marked as {@code IgnoreUnderRavenwood}.
+     *
+     * This is typically helpful for internal maintainers discovering tests that had previously
+     * been ignored, but now have enough Ravenwood-supported functionality to be enabled.
+     */
+    private static final boolean ENABLE_PROBE_IGNORED = false; // DO NOT SUBMIT WITH TRUE
+
     private static final int SYSTEM_UID = 1000;
     private static final int NOBODY_UID = 9999;
     private static final int FIRST_APPLICATION_UID = 10000;
@@ -97,26 +108,76 @@
         return IS_UNDER_RAVENWOOD;
     }
 
+    /**
+     * Test if the given {@link Description} has been marked with an {@link IgnoreUnderRavenwood}
+     * annotation, either at the method or class level.
+     */
+    private static boolean hasIgnoreUnderRavenwoodAnnotation(Description description) {
+        if (description.getTestClass().getAnnotation(IgnoreUnderRavenwood.class) != null) {
+            return true;
+        } else if (description.getAnnotation(IgnoreUnderRavenwood.class) != null) {
+            return true;
+        } else {
+            return false;
+        }
+    }
+
     @Override
     public Statement apply(Statement base, Description description) {
+        if (ENABLE_PROBE_IGNORED) {
+            return applyProbeIgnored(base, description);
+        } else {
+            return applyDefault(base, description);
+        }
+    }
+
+    /**
+     * Run the given {@link Statement} with no special treatment.
+     */
+    private Statement applyDefault(Statement base, Description description) {
         return new Statement() {
             @Override
             public void evaluate() throws Throwable {
-                if (description.getTestClass().getAnnotation(IgnoreUnderRavenwood.class) != null) {
+                if (hasIgnoreUnderRavenwoodAnnotation(description)) {
                     Assume.assumeFalse(IS_UNDER_RAVENWOOD);
                 }
-                if (description.getAnnotation(IgnoreUnderRavenwood.class) != null) {
-                    Assume.assumeFalse(IS_UNDER_RAVENWOOD);
-                }
-                if (IS_UNDER_RAVENWOOD) {
-                    RavenwoodRuleImpl.init(RavenwoodRule.this);
-                }
+
+                RavenwoodRuleImpl.init(RavenwoodRule.this);
                 try {
                     base.evaluate();
                 } finally {
-                    if (IS_UNDER_RAVENWOOD) {
-                        RavenwoodRuleImpl.reset(RavenwoodRule.this);
+                    RavenwoodRuleImpl.reset(RavenwoodRule.this);
+                }
+            }
+        };
+    }
+
+    /**
+     * Run the given {@link Statement} with probing enabled. All tests will be unconditionally
+     * run under Ravenwood to detect cases where a test is able to pass despite being marked as
+     * {@code IgnoreUnderRavenwood}.
+     */
+    private Statement applyProbeIgnored(Statement base, Description description) {
+        return new Statement() {
+            @Override
+            public void evaluate() throws Throwable {
+                RavenwoodRuleImpl.init(RavenwoodRule.this);
+                try {
+                    base.evaluate();
+                } catch (Throwable t) {
+                    if (hasIgnoreUnderRavenwoodAnnotation(description)) {
+                        // This failure is expected, so eat the exception and report the
+                        // assumption failure that test authors expect
+                        Assume.assumeFalse(IS_UNDER_RAVENWOOD);
                     }
+                    throw t;
+                } finally {
+                    RavenwoodRuleImpl.reset(RavenwoodRule.this);
+                }
+
+                if (hasIgnoreUnderRavenwoodAnnotation(description) && IS_UNDER_RAVENWOOD) {
+                    fail("Test was annotated with IgnoreUnderRavenwood, but it actually "
+                            + "passed under Ravenwood; consider removing the annotation");
                 }
             }
         };
diff --git a/ravenwood/junit-stub-src/android/platform/test/ravenwood/RavenwoodRuleImpl.java b/ravenwood/junit-stub-src/android/platform/test/ravenwood/RavenwoodRuleImpl.java
index fb71e9d..0ff6a1a 100644
--- a/ravenwood/junit-stub-src/android/platform/test/ravenwood/RavenwoodRuleImpl.java
+++ b/ravenwood/junit-stub-src/android/platform/test/ravenwood/RavenwoodRuleImpl.java
@@ -22,12 +22,10 @@
     }
 
     public static void init(RavenwoodRule rule) {
-        // Must be provided by impl to reference runtime internals
-        throw new UnsupportedOperationException();
+        // No-op when running on a real device
     }
 
     public static void reset(RavenwoodRule rule) {
-        // Must be provided by impl to reference runtime internals
-        throw new UnsupportedOperationException();
+        // No-op when running on a real device
     }
 }
diff --git a/ravenwood/ravenwood-annotation-allowed-classes.txt b/ravenwood/ravenwood-annotation-allowed-classes.txt
index 9fcabd6..13908f1 100644
--- a/ravenwood/ravenwood-annotation-allowed-classes.txt
+++ b/ravenwood/ravenwood-annotation-allowed-classes.txt
@@ -1,8 +1,16 @@
 # Only classes listed here can use the Ravenwood annotations.
 
 com.android.internal.util.ArrayUtils
+com.android.internal.os.BatteryStatsHistory
+com.android.internal.os.BatteryStatsHistory$TraceDelegate
+com.android.internal.os.BatteryStatsHistory$VarintParceler
+com.android.internal.os.BatteryStatsHistoryIterator
+com.android.internal.os.Clock
 com.android.internal.os.LongArrayMultiStateCounter
 com.android.internal.os.LongArrayMultiStateCounter$LongArrayContainer
+com.android.internal.os.MonotonicClock
+com.android.internal.os.PowerStats
+com.android.internal.os.PowerStats$Descriptor
 
 android.util.AtomicFile
 android.util.DataUnit
@@ -25,10 +33,15 @@
 android.util.Xml
 
 android.os.BatteryConsumer
+android.os.BatteryStats$HistoryItem
+android.os.BatteryStats$HistoryStepDetails
+android.os.BatteryStats$HistoryTag
+android.os.BatteryStats$ProcessStateChange
 android.os.Binder
 android.os.Binder$IdentitySupplier
 android.os.Broadcaster
 android.os.BundleMerger
+android.os.ConditionVariable
 android.os.FileUtils
 android.os.FileUtils$MemoryPipe
 android.os.Handler
diff --git a/services/accessibility/Android.bp b/services/accessibility/Android.bp
index b8cf13b..e2488a5 100644
--- a/services/accessibility/Android.bp
+++ b/services/accessibility/Android.bp
@@ -19,6 +19,9 @@
     defaults: [
         "platform_service_defaults",
     ],
+    lint: {
+        error_checks: ["MissingPermissionAnnotation"],
+    },
     srcs: [
         ":services.accessibility-sources",
         "//frameworks/base/packages/SettingsLib/RestrictedLockUtils:SettingsLibRestrictedLockUtilsSrc",
diff --git a/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java b/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java
index 0696807..1d73843 100644
--- a/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java
+++ b/services/accessibility/java/com/android/server/accessibility/AbstractAccessibilityServiceConnection.java
@@ -118,6 +118,7 @@
  * This class represents an accessibility client - either an AccessibilityService or a UiAutomation.
  * It is responsible for behavior common to both types of clients.
  */
+@SuppressWarnings("MissingPermissionAnnotation")
 abstract class AbstractAccessibilityServiceConnection extends IAccessibilityServiceConnection.Stub
         implements ServiceConnection, IBinder.DeathRecipient, KeyEventDispatcher.KeyEventFilter,
         FingerprintGestureDispatcher.FingerprintGestureClient {
@@ -209,6 +210,7 @@
     final ComponentName mComponentName;
 
     int mGenericMotionEventSources;
+    int mObservedMotionEventSources;
 
     // the events pending events to be dispatched to this service
     final SparseArray<AccessibilityEvent> mPendingEvents = new SparseArray<>();
@@ -397,6 +399,19 @@
         mNotificationTimeout = info.notificationTimeout;
         mIsDefault = (info.flags & DEFAULT) != 0;
         mGenericMotionEventSources = info.getMotionEventSources();
+        if (android.view.accessibility.Flags.motionEventObserving()) {
+            if (mContext.checkCallingOrSelfPermission(
+                            android.Manifest.permission.ACCESSIBILITY_MOTION_EVENT_OBSERVING)
+                    == PackageManager.PERMISSION_GRANTED) {
+                mObservedMotionEventSources = info.getObservedMotionEventSources();
+            } else {
+                Slog.e(
+                        LOG_TAG,
+                        "Observing motion events requires"
+                            + " android.Manifest.permission.ACCESSIBILITY_MOTION_EVENT_OBSERVING.");
+                mObservedMotionEventSources = 0;
+            }
+        }
 
         if (supportsFlagForNotImportantViews(info)) {
             if ((info.flags & AccessibilityServiceInfo.FLAG_INCLUDE_NOT_IMPORTANT_VIEWS) != 0) {
@@ -1599,7 +1614,7 @@
             final int displayId = displays[i].getDisplayId();
             onDisplayRemoved(displayId);
         }
-        if (Flags.cleanupA11yOverlays()) {
+        if (com.android.server.accessibility.Flags.cleanupA11yOverlays()) {
             detachAllOverlays();
         }
     }
@@ -1919,6 +1934,7 @@
         return (mGenericMotionEventSources & eventSourceWithoutClass) != 0;
     }
 
+
     /**
      * Called by the invocation handler to notify the service that the
      * state of magnification has changed.
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityInputFilter.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityInputFilter.java
index 6cac6a4..abcd8e2 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityInputFilter.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityInputFilter.java
@@ -57,6 +57,7 @@
  *
  * NOTE: This class has to be created and poked only from the main thread.
  */
+@SuppressWarnings("MissingPermissionAnnotation")
 class AccessibilityInputFilter extends InputFilter implements EventStreamTransformation {
 
     private static final String TAG = AccessibilityInputFilter.class.getSimpleName();
@@ -198,6 +199,7 @@
     // State tracking for generic MotionEvents is display-agnostic so we only need one.
     private GenericMotionEventStreamState mGenericMotionEventStreamState;
     private int mCombinedGenericMotionEventSources = 0;
+    private int mCombinedMotionEventObservedSources = 0;
 
     private EventStreamState mKeyboardStreamState;
 
@@ -525,16 +527,33 @@
         }
 
         if ((mEnabledFeatures & FLAG_FEATURE_INTERCEPT_GENERIC_MOTION_EVENTS) != 0) {
-            addFirstEventHandler(displayId, new BaseEventStreamTransformation() {
-                @Override
-                public void onMotionEvent(MotionEvent event, MotionEvent rawEvent,
-                        int policyFlags) {
-                    if (!anyServiceWantsGenericMotionEvent(rawEvent)
-                            || !mAms.sendMotionEventToListeningServices(rawEvent)) {
-                        super.onMotionEvent(event, rawEvent, policyFlags);
-                    }
-                }
-            });
+            addFirstEventHandler(
+                    displayId,
+                    new BaseEventStreamTransformation() {
+                        @Override
+                        public void onMotionEvent(
+                                MotionEvent event, MotionEvent rawEvent, int policyFlags) {
+                            boolean passAlongEvent = true;
+                            if (anyServiceWantsGenericMotionEvent(event)) {
+                                // Some service wants this event, so try to deliver it to at least
+                                // one service.
+                                if (mAms.sendMotionEventToListeningServices(event)) {
+                                    // A service accepted this event, so prevent it from passing
+                                    // down the stream by default.
+                                    passAlongEvent = false;
+                                }
+                                // However, if a service is observing these events instead of
+                                // consuming them then ensure
+                                // it is always passed along to the next stage of the event stream.
+                                if (anyServiceWantsToObserveMotionEvent(event)) {
+                                    passAlongEvent = true;
+                                }
+                            }
+                            if (passAlongEvent) {
+                                super.onMotionEvent(event, rawEvent, policyFlags);
+                            }
+                        }
+                    });
         }
 
         if ((mEnabledFeatures & FLAG_FEATURE_CONTROL_SCREEN_MAGNIFIER) != 0
@@ -542,15 +561,14 @@
                 || ((mEnabledFeatures & FLAG_FEATURE_MAGNIFICATION_TWO_FINGER_TRIPLE_TAP) != 0)
                 || ((mEnabledFeatures & FLAG_FEATURE_TRIGGERED_SCREEN_MAGNIFIER) != 0)) {
             final MagnificationGestureHandler magnificationGestureHandler =
-                    createMagnificationGestureHandler(displayId,
-                            displayContext);
+                    createMagnificationGestureHandler(displayId, displayContext);
             addFirstEventHandler(displayId, magnificationGestureHandler);
             mMagnificationGestureHandler.put(displayId, magnificationGestureHandler);
         }
 
         if ((mEnabledFeatures & FLAG_FEATURE_INJECT_MOTION_EVENTS) != 0) {
-            MotionEventInjector injector = new MotionEventInjector(
-                    mContext.getMainLooper(), mAms.getTraceManager());
+            MotionEventInjector injector =
+                    new MotionEventInjector(mContext.getMainLooper(), mAms.getTraceManager());
             addFirstEventHandler(displayId, injector);
             mMotionEventInjectors.put(displayId, injector);
         }
@@ -923,6 +941,20 @@
         }
     }
 
+    private boolean anyServiceWantsToObserveMotionEvent(MotionEvent event) {
+        // Disable SOURCE_TOUCHSCREEN generic event interception if any service is performing
+        // touch exploration.
+        if (event.isFromSource(InputDevice.SOURCE_TOUCHSCREEN)
+                && (mEnabledFeatures & FLAG_FEATURE_TOUCH_EXPLORATION) != 0) {
+            return false;
+        }
+        final int eventSourceWithoutClass = event.getSource() & ~InputDevice.SOURCE_CLASS_MASK;
+        return (mCombinedGenericMotionEventSources
+                        & mCombinedMotionEventObservedSources
+                        & eventSourceWithoutClass)
+                != 0;
+    }
+
     private boolean anyServiceWantsGenericMotionEvent(MotionEvent event) {
         // Disable SOURCE_TOUCHSCREEN generic event interception if any service is performing
         // touch exploration.
@@ -938,6 +970,10 @@
         mCombinedGenericMotionEventSources = sources;
     }
 
+    public void setCombinedMotionEventObservedSources(int sources) {
+        mCombinedMotionEventObservedSources = sources;
+    }
+
     /**
      * Keeps state of streams of events from all keyboard devices.
      */
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
index 440e996..3d8d7b7 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityManagerService.java
@@ -195,6 +195,7 @@
  * event dispatch for {@link AccessibilityEvent}s generated across all processes
  * on the device. Events are dispatched to {@link AccessibilityService}s.
  */
+@SuppressWarnings("MissingPermissionAnnotation")
 public class AccessibilityManagerService extends IAccessibilityManager.Stub
         implements AbstractAccessibilityServiceConnection.SystemSupport,
         AccessibilityUserState.ServiceInfoChangeListener,
@@ -2825,8 +2826,10 @@
                 flags |= AccessibilityInputFilter.FLAG_FEATURE_INJECT_MOTION_EVENTS;
             }
             int combinedGenericMotionEventSources = 0;
+            int combinedMotionEventObservedSources = 0;
             for (AccessibilityServiceConnection connection : userState.mBoundServices) {
                 combinedGenericMotionEventSources |= connection.mGenericMotionEventSources;
+                combinedMotionEventObservedSources |= connection.mObservedMotionEventSources;
             }
             if (combinedGenericMotionEventSources != 0) {
                 flags |= AccessibilityInputFilter.FLAG_FEATURE_INTERCEPT_GENERIC_MOTION_EVENTS;
@@ -2845,6 +2848,8 @@
                 mInputFilter.setUserAndEnabledFeatures(userState.mUserId, flags);
                 mInputFilter.setCombinedGenericMotionEventSources(
                         combinedGenericMotionEventSources);
+                mInputFilter.setCombinedMotionEventObservedSources(
+                        combinedMotionEventObservedSources);
             } else {
                 if (mHasInputFilter) {
                     mHasInputFilter = false;
diff --git a/services/accessibility/java/com/android/server/accessibility/AccessibilityServiceConnection.java b/services/accessibility/java/com/android/server/accessibility/AccessibilityServiceConnection.java
index 40ca694..5ebe161 100644
--- a/services/accessibility/java/com/android/server/accessibility/AccessibilityServiceConnection.java
+++ b/services/accessibility/java/com/android/server/accessibility/AccessibilityServiceConnection.java
@@ -65,6 +65,7 @@
  * passed to the service it represents as soon it is bound. It also serves as the
  * connection for the service.
  */
+@SuppressWarnings("MissingPermissionAnnotation")
 class AccessibilityServiceConnection extends AbstractAccessibilityServiceConnection {
     private static final String LOG_TAG = "AccessibilityServiceConnection";
 
diff --git a/services/accessibility/java/com/android/server/accessibility/ActionReplacingCallback.java b/services/accessibility/java/com/android/server/accessibility/ActionReplacingCallback.java
index a525e7c..b119d7d 100644
--- a/services/accessibility/java/com/android/server/accessibility/ActionReplacingCallback.java
+++ b/services/accessibility/java/com/android/server/accessibility/ActionReplacingCallback.java
@@ -34,6 +34,7 @@
  * If we are stripping and/or replacing the actions from a window, we need to intercept the
  * nodes heading back to the service and swap out the actions.
  */
+@SuppressWarnings("MissingPermissionAnnotation")
 public class ActionReplacingCallback extends IAccessibilityInteractionConnectionCallback.Stub {
     private static final boolean DEBUG = false;
     private static final String LOG_TAG = "ActionReplacingCallback";
diff --git a/services/accessibility/java/com/android/server/accessibility/FingerprintGestureDispatcher.java b/services/accessibility/java/com/android/server/accessibility/FingerprintGestureDispatcher.java
index c9ec16e..e10e87c 100644
--- a/services/accessibility/java/com/android/server/accessibility/FingerprintGestureDispatcher.java
+++ b/services/accessibility/java/com/android/server/accessibility/FingerprintGestureDispatcher.java
@@ -33,6 +33,7 @@
 /**
  * Encapsulate fingerprint gesture logic
  */
+@SuppressWarnings("MissingPermissionAnnotation")
 public class FingerprintGestureDispatcher extends IFingerprintClientActiveCallback.Stub
         implements Handler.Callback{
     private static final int MSG_REGISTER = 1;
diff --git a/services/accessibility/java/com/android/server/accessibility/ProxyAccessibilityServiceConnection.java b/services/accessibility/java/com/android/server/accessibility/ProxyAccessibilityServiceConnection.java
index ab01fc3..6aa4702 100644
--- a/services/accessibility/java/com/android/server/accessibility/ProxyAccessibilityServiceConnection.java
+++ b/services/accessibility/java/com/android/server/accessibility/ProxyAccessibilityServiceConnection.java
@@ -64,6 +64,7 @@
  *
  * TODO(241429275): Initialize this when a proxy is registered.
  */
+@SuppressWarnings("MissingPermissionAnnotation")
 public class ProxyAccessibilityServiceConnection extends AccessibilityServiceConnection {
     private static final String LOG_TAG = "ProxyAccessibilityServiceConnection";
 
diff --git a/services/accessibility/java/com/android/server/accessibility/UiAutomationManager.java b/services/accessibility/java/com/android/server/accessibility/UiAutomationManager.java
index 53c629a..f69104d 100644
--- a/services/accessibility/java/com/android/server/accessibility/UiAutomationManager.java
+++ b/services/accessibility/java/com/android/server/accessibility/UiAutomationManager.java
@@ -245,6 +245,7 @@
         }
     }
 
+    @SuppressWarnings("MissingPermissionAnnotation")
     private class UiAutomationService extends AbstractAccessibilityServiceConnection {
         private final Handler mMainHandler;
 
diff --git a/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationConnectionManager.java b/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationConnectionManager.java
index e6af54b..e11c36a 100644
--- a/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationConnectionManager.java
+++ b/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationConnectionManager.java
@@ -922,6 +922,7 @@
         disableWindowMagnification(displayId, true);
     }
 
+    @SuppressWarnings("MissingPermissionAnnotation")
     private class ConnectionCallback extends IMagnificationConnectionCallback.Stub implements
             IBinder.DeathRecipient {
         private boolean mExpiredDeathRecipient = false;
diff --git a/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationConnectionWrapper.java b/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationConnectionWrapper.java
index c63784a..db5b313 100644
--- a/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationConnectionWrapper.java
+++ b/services/accessibility/java/com/android/server/accessibility/magnification/MagnificationConnectionWrapper.java
@@ -246,6 +246,7 @@
         return new RemoteAnimationCallback(callback, trace);
     }
 
+    @SuppressWarnings("MissingPermissionAnnotation")
     private static class RemoteAnimationCallback extends
             IRemoteMagnificationAnimationCallback.Stub {
         private final MagnificationAnimationCallback mCallback;
diff --git a/services/autofill/bugfixes.aconfig b/services/autofill/bugfixes.aconfig
index ab678d9..b5130a1 100644
--- a/services/autofill/bugfixes.aconfig
+++ b/services/autofill/bugfixes.aconfig
@@ -27,3 +27,10 @@
   description: "Mitigation for view state reset to empty causing no save dialog to show issue"
   bug: "297976948"
 }
+
+flag {
+  name: "include_invisible_view_group_in_assist_structure"
+  namespace: "autofill"
+  description: "Mitigation for autofill providers miscalculating view visibility"
+  bug: "291795358"
+}
diff --git a/services/companion/java/com/android/server/companion/CompanionDeviceShellCommand.java b/services/companion/java/com/android/server/companion/CompanionDeviceShellCommand.java
index 23e7ce6..9fdf5c2 100644
--- a/services/companion/java/com/android/server/companion/CompanionDeviceShellCommand.java
+++ b/services/companion/java/com/android/server/companion/CompanionDeviceShellCommand.java
@@ -92,9 +92,10 @@
                     int userId = getNextIntArgRequired();
                     String packageName = getNextArgRequired();
                     String address = getNextArgRequired();
+                    String deviceProfile = getNextArg();
                     final MacAddress macAddress = MacAddress.fromString(address);
                     mService.createNewAssociation(userId, packageName, macAddress,
-                            null, null, false);
+                            null, deviceProfile, false);
                 }
                 break;
 
@@ -350,7 +351,7 @@
         pw.println("      Print this help text.");
         pw.println("  list USER_ID");
         pw.println("      List all Associations for a user.");
-        pw.println("  associate USER_ID PACKAGE MAC_ADDRESS");
+        pw.println("  associate USER_ID PACKAGE MAC_ADDRESS [DEVICE_PROFILE]");
         pw.println("      Create a new Association.");
         pw.println("  disassociate USER_ID PACKAGE MAC_ADDRESS");
         pw.println("      Remove an existing Association.");
diff --git a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java
index 8dc6537..0d5cdcb 100644
--- a/services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java
+++ b/services/companion/java/com/android/server/companion/virtual/VirtualDeviceManagerService.java
@@ -439,6 +439,12 @@
             if (associationInfo == null) {
                 throw new IllegalArgumentException("No association with ID " + associationId);
             }
+            if (!VIRTUAL_DEVICE_COMPANION_DEVICE_PROFILES
+                    .contains(associationInfo.getDeviceProfile())
+                    && Flags.persistentDeviceIdApi()) {
+                throw new IllegalArgumentException("Unsupported CDM Association device profile "
+                        + associationInfo.getDeviceProfile() + " for virtual device creation.");
+            }
             Objects.requireNonNull(params);
             Objects.requireNonNull(activityListener);
             Objects.requireNonNull(soundEffectListener);
diff --git a/services/contentcapture/java/com/android/server/contentcapture/ContentCapturePerUserService.java b/services/contentcapture/java/com/android/server/contentcapture/ContentCapturePerUserService.java
index 34787a3..145303d 100644
--- a/services/contentcapture/java/com/android/server/contentcapture/ContentCapturePerUserService.java
+++ b/services/contentcapture/java/com/android/server/contentcapture/ContentCapturePerUserService.java
@@ -554,6 +554,10 @@
             if (mMaster.debug) Slog.d(mTag, "onActivityEvent(): no remote service");
             return;
         }
+        if (mRemoteService.getServiceInterface() == null) {
+            if (mMaster.debug) Slog.d(mTag, "onActivityEvent(): remote service is dead or unbound");
+            return;
+        }
         final ActivityEvent event = new ActivityEvent(activityId, componentName, type);
 
         if (mMaster.verbose) Slog.v(mTag, "onActivityEvent(): " + event);
diff --git a/services/core/Android.bp b/services/core/Android.bp
index 5a120a7..f5a80d8 100644
--- a/services/core/Android.bp
+++ b/services/core/Android.bp
@@ -154,6 +154,7 @@
 
     static_libs: [
         "android.frameworks.location.altitude-V1-java", // AIDL
+        "android.frameworks.vibrator-V1-java", // AIDL
         "android.hardware.authsecret-V1.0-java",
         "android.hardware.authsecret-V1-java",
         "android.hardware.boot-V1.0-java", // HIDL
@@ -199,6 +200,7 @@
         "notification_flags_lib",
         "biometrics_flags_lib",
         "am_flags_lib",
+        "com_android_systemui_shared_flags_lib",
         "com_android_wm_shell_flags_lib",
         "com.android.server.utils_aconfig-java",
         "service-jobscheduler-deviceidle.flags-aconfig-java",
diff --git a/services/core/java/android/content/pm/PackageManagerInternal.java b/services/core/java/android/content/pm/PackageManagerInternal.java
index 09e7986..136692e 100644
--- a/services/core/java/android/content/pm/PackageManagerInternal.java
+++ b/services/core/java/android/content/pm/PackageManagerInternal.java
@@ -1426,14 +1426,19 @@
 
     /**
      * Checks if package is quarantined for a specific user.
+     *
+     * @throws PackageManager.NameNotFoundException if the package is not found
      */
-    public abstract boolean isPackageQuarantined(@NonNull String packageName,
-            @UserIdInt int userId);
+    public abstract boolean isPackageQuarantined(@NonNull String packageName, @UserIdInt int userId)
+            throws PackageManager.NameNotFoundException;
 
     /**
      * Checks if package is stopped for a specific user.
+     *
+     * @throws PackageManager.NameNotFoundException if the package is not found
      */
-    public abstract boolean isPackageStopped(@NonNull String packageName, @UserIdInt int userId);
+    public abstract boolean isPackageStopped(@NonNull String packageName, @UserIdInt int userId)
+            throws PackageManager.NameNotFoundException;
 
     /**
      * Sends the PACKAGE_RESTARTED broadcast.
diff --git a/services/core/java/com/android/server/BatteryService.java b/services/core/java/com/android/server/BatteryService.java
index 33726d1..5a44ac8 100644
--- a/services/core/java/com/android/server/BatteryService.java
+++ b/services/core/java/com/android/server/BatteryService.java
@@ -974,6 +974,7 @@
                 unplugBattery(/* forceUpdate= */ (opts & OPTION_FORCE_UPDATE) != 0, pw);
             } break;
             case "get": {
+                final int opts = parseOptions(shell);
                 final String key = shell.getNextArg();
                 if (key == null) {
                     pw.println("No property specified");
@@ -1007,11 +1008,17 @@
                         break;
                     case "current_now":
                         if (batteryServiceSupportCurrentAdbCommand()) {
+                            if ((opts & OPTION_FORCE_UPDATE) != 0) {
+                                updateHealthInfo();
+                            }
                             pw.println(mHealthInfo.batteryCurrentMicroamps);
                         }
                         break;
                     case "current_average":
                         if (batteryServiceSupportCurrentAdbCommand()) {
+                            if ((opts & OPTION_FORCE_UPDATE) != 0) {
+                                updateHealthInfo();
+                            }
                             pw.println(mHealthInfo.batteryCurrentAverageMicroamps);
                         }
                         break;
@@ -1125,6 +1132,14 @@
         return 0;
     }
 
+    private void updateHealthInfo() {
+        try {
+            mHealthServiceWrapper.scheduleUpdate();
+        } catch (RemoteException e) {
+            Slog.e(TAG, "Unable to update health service data.", e);
+        }
+    }
+
     private void setChargerAcOnline(boolean online, boolean forceUpdate) {
         if (!mUpdatesStopped) {
             copyV1Battery(mLastHealthInfo, mHealthInfo);
diff --git a/services/core/java/com/android/server/am/ActiveServices.java b/services/core/java/com/android/server/am/ActiveServices.java
index 7191684..df8f17a 100644
--- a/services/core/java/com/android/server/am/ActiveServices.java
+++ b/services/core/java/com/android/server/am/ActiveServices.java
@@ -5175,6 +5175,8 @@
             return null;
         }
 
+        final long startTimeNs = SystemClock.elapsedRealtimeNanos();
+
         if (DEBUG_SERVICE) {
             Slog.v(TAG_SERVICE, "Bringing up " + r + " " + r.intent + " fg=" + r.fgRequired);
         }
@@ -5333,9 +5335,14 @@
                 bringDownServiceLocked(r, enqueueOomAdj);
                 return msg;
             }
+            mAm.mProcessList.getAppStartInfoTracker().handleProcessServiceStart(startTimeNs, app, r,
+                    hostingRecord, true);
             if (isolated) {
                 r.isolationHostProc = app;
             }
+        } else {
+            mAm.mProcessList.getAppStartInfoTracker().handleProcessServiceStart(startTimeNs, app, r,
+                    hostingRecord, false);
         }
 
         if (r.fgRequired) {
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index ac173f3..2ee39c57 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -421,6 +421,7 @@
 import com.android.internal.os.TimeoutRecord;
 import com.android.internal.os.TransferPipe;
 import com.android.internal.os.Zygote;
+import com.android.internal.pm.pkg.parsing.ParsingPackageUtils;
 import com.android.internal.policy.AttributeCache;
 import com.android.internal.protolog.common.ProtoLog;
 import com.android.internal.util.ArrayUtils;
@@ -468,7 +469,6 @@
 import com.android.server.pm.permission.PermissionManagerServiceInternal;
 import com.android.server.pm.pkg.AndroidPackage;
 import com.android.server.pm.pkg.SELinuxUtil;
-import com.android.server.pm.pkg.parsing.ParsingPackageUtils;
 import com.android.server.pm.snapshot.PackageDataSnapshot;
 import com.android.server.power.stats.BatteryStatsImpl;
 import com.android.server.sdksandbox.SdkSandboxManagerLocal;
@@ -1103,9 +1103,51 @@
 
     private final ActivityMetricsLaunchObserver mActivityLaunchObserver =
             new ActivityMetricsLaunchObserver() {
+
         @Override
-        public void onActivityLaunched(long id, ComponentName name, int temperature) {
+        public void onIntentStarted(@NonNull Intent intent, long timestampNanos) {
+            synchronized (this) {
+                mProcessList.getAppStartInfoTracker().onIntentStarted(intent, timestampNanos);
+            }
+        }
+
+        @Override
+        public void onIntentFailed(long id) {
+            mProcessList.getAppStartInfoTracker().onIntentFailed(id);
+        }
+
+        @Override
+        public void onActivityLaunched(long id, ComponentName name, int temperature, int userId) {
             mAppProfiler.onActivityLaunched();
+            synchronized (ActivityManagerService.this) {
+                ProcessRecord record = null;
+                try {
+                    record = getProcessRecordLocked(name.getPackageName(), mContext
+                            .getPackageManager().getPackageUidAsUser(name.getPackageName(), 0,
+                            userId));
+                } catch (NameNotFoundException nnfe) {
+                    // Ignore, record will be lost.
+                }
+                mProcessList.getAppStartInfoTracker().onActivityLaunched(id, name, temperature,
+                        record);
+            }
+        }
+
+        @Override
+        public void onActivityLaunchCancelled(long id) {
+            mProcessList.getAppStartInfoTracker().onActivityLaunchCancelled(id);
+        }
+
+        @Override
+        public void onActivityLaunchFinished(long id, ComponentName name, long timestampNanos,
+                int launchMode) {
+            mProcessList.getAppStartInfoTracker().onActivityLaunchFinished(id, name,
+                    timestampNanos, launchMode);
+        }
+
+        @Override
+        public void onReportFullyDrawn(long id, long timestampNanos) {
+            mProcessList.getAppStartInfoTracker().onReportFullyDrawn(id, timestampNanos);
         }
     };
 
@@ -4488,13 +4530,13 @@
     @GuardedBy("this")
     private void attachApplicationLocked(@NonNull IApplicationThread thread,
             int pid, int callingUid, long startSeq) {
-
         // Find the application record that is being attached...  either via
         // the pid if we are running in multiple processes, or just pull the
         // next app record if we are emulating process with anonymous threads.
         ProcessRecord app;
         long startTime = SystemClock.uptimeMillis();
         long bindApplicationTimeMillis;
+        long bindApplicationTimeNanos;
         if (pid != MY_PID && pid >= 0) {
             synchronized (mPidsSelfLocked) {
                 app = mPidsSelfLocked.get(pid);
@@ -4698,6 +4740,7 @@
 
             checkTime(startTime, "attachApplicationLocked: immediately before bindApplication");
             bindApplicationTimeMillis = SystemClock.uptimeMillis();
+            bindApplicationTimeNanos = SystemClock.elapsedRealtimeNanos();
             mAtmInternal.preBindApplication(app.getWindowProcessController());
             final ActiveInstrumentation instr2 = app.getActiveInstrumentation();
             if (mPlatformCompat != null) {
@@ -4754,6 +4797,8 @@
             }
 
             app.setBindApplicationTime(bindApplicationTimeMillis);
+            mProcessList.getAppStartInfoTracker()
+                    .reportBindApplicationTimeNanos(app, bindApplicationTimeNanos);
 
             // Make app active after binding application or client may be running requests (e.g
             // starting activities) before it is ready.
@@ -9799,12 +9844,12 @@
             final int uid = enforceDumpPermissionForPackage(packageName, userId, callingUid,
                         "getHistoricalProcessStartReasons");
             if (uid != INVALID_UID) {
-                mProcessList.mAppStartInfoTracker.getStartInfo(
+                mProcessList.getAppStartInfoTracker().getStartInfo(
                         packageName, userId, callingPid, maxNum, results);
             }
         } else {
             // If no package name is given, use the caller's uid as the filter uid.
-            mProcessList.mAppStartInfoTracker.getStartInfo(
+            mProcessList.getAppStartInfoTracker().getStartInfo(
                     packageName, callingUid, callingPid, maxNum, results);
         }
         return new ParceledListSlice<ApplicationStartInfo>(results);
@@ -9822,7 +9867,7 @@
         }
 
         final int callingUid = Binder.getCallingUid();
-        mProcessList.mAppStartInfoTracker.addStartInfoCompleteListener(listener, callingUid);
+        mProcessList.getAppStartInfoTracker().addStartInfoCompleteListener(listener, callingUid);
     }
 
 
@@ -9836,7 +9881,7 @@
         }
 
         final int callingUid = Binder.getCallingUid();
-        mProcessList.mAppStartInfoTracker.clearStartInfoCompleteListener(callingUid, true);
+        mProcessList.getAppStartInfoTracker().clearStartInfoCompleteListener(callingUid, true);
     }
 
     @Override
@@ -10138,7 +10183,7 @@
             pw.println();
             if (dumpAll) {
                 pw.println("-------------------------------------------------------------------------------");
-                mProcessList.mAppStartInfoTracker.dumpHistoryProcessStartInfo(pw, dumpPackage);
+                mProcessList.getAppStartInfoTracker().dumpHistoryProcessStartInfo(pw, dumpPackage);
                 pw.println("-------------------------------------------------------------------------------");
                 mProcessList.mAppExitInfoTracker.dumpHistoryProcessExitInfo(pw, dumpPackage);
             }
@@ -10541,7 +10586,7 @@
                     dumpPackage = args[opti];
                     opti++;
                 }
-                mProcessList.mAppStartInfoTracker.dumpHistoryProcessStartInfo(pw, dumpPackage);
+                mProcessList.getAppStartInfoTracker().dumpHistoryProcessStartInfo(pw, dumpPackage);
             } else if ("exit-info".equals(cmd)) {
                 if (opti < args.length) {
                     dumpPackage = args[opti];
@@ -13831,6 +13876,7 @@
     // activity manager to announce its creation.
     public boolean bindBackupAgent(String packageName, int backupMode, int targetUserId,
             @BackupDestination int backupDestination) {
+        long startTimeNs = SystemClock.elapsedRealtimeNanos();
         if (DEBUG_BACKUP) {
             Slog.v(TAG, "bindBackupAgent: app=" + packageName + " mode=" + backupMode
                     + " targetUserId=" + targetUserId + " callingUid = " + Binder.getCallingUid()
@@ -13906,15 +13952,20 @@
                             ? new ComponentName(app.packageName, app.backupAgentName)
                             : new ComponentName("android", "FullBackupAgent");
 
-            // startProcessLocked() returns existing proc's record if it's already running
-            ProcessRecord proc = startProcessLocked(app.processName, app,
-                    false, 0,
-                    new HostingRecord(HostingRecord.HOSTING_TYPE_BACKUP, hostingName),
-                    ZYGOTE_POLICY_FLAG_SYSTEM_PROCESS, false, false);
+            ProcessRecord proc = getProcessRecordLocked(app.processName, app.uid);
+            boolean isProcessStarted = proc != null;
+            if (!isProcessStarted) {
+                proc = startProcessLocked(app.processName, app,
+                  false, 0,
+                  new HostingRecord(HostingRecord.HOSTING_TYPE_BACKUP, hostingName),
+                  ZYGOTE_POLICY_FLAG_SYSTEM_PROCESS, false, false);
+            }
             if (proc == null) {
                 Slog.e(TAG, "Unable to start backup agent process " + r);
                 return false;
             }
+            mProcessList.getAppStartInfoTracker().handleProcessBackupStart(startTimeNs, proc, r,
+                    !isProcessStarted);
 
             // If the app is a regular app (uid >= 10000) and not the system server or phone
             // process, etc, then mark it as being in full backup so that certain calls to the
@@ -18741,8 +18792,12 @@
                     // If the process is known as top app, set a hint so when the process is
                     // started, the top priority can be applied immediately to avoid cpu being
                     // preempted by other processes before attaching the process of top app.
-                    startProcessLocked(processName, info, knownToBeDead, 0 /* intentFlags */,
-                            new HostingRecord(hostingType, hostingName, isTop),
+                    final long startTimeNs = SystemClock.elapsedRealtimeNanos();
+                    HostingRecord hostingRecord =
+                            new HostingRecord(hostingType, hostingName, isTop);
+                    ProcessRecord rec = getProcessRecordLocked(processName, info.uid);
+                    ProcessRecord app = startProcessLocked(processName, info, knownToBeDead,
+                            0 /* intentFlags */, hostingRecord,
                             ZYGOTE_POLICY_FLAG_LATENCY_SENSITIVE, false /* allowWhileBooting */,
                             false /* isolated */);
                 }
diff --git a/services/core/java/com/android/server/am/ActivityManagerShellCommand.java b/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
index f3b2ef3..ae0cd65 100644
--- a/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
+++ b/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
@@ -1362,7 +1362,7 @@
             }
             userId = user.id;
         }
-        mInternal.mProcessList.mAppStartInfoTracker
+        mInternal.mProcessList.getAppStartInfoTracker()
                 .clearHistoryProcessStartInfo(packageName, userId);
         return 0;
     }
diff --git a/services/core/java/com/android/server/am/AppStartInfoTracker.java b/services/core/java/com/android/server/am/AppStartInfoTracker.java
index edca74f..82e554e 100644
--- a/services/core/java/com/android/server/am/AppStartInfoTracker.java
+++ b/services/core/java/com/android/server/am/AppStartInfoTracker.java
@@ -22,11 +22,12 @@
 import static com.android.server.am.ActivityManagerDebugConfig.TAG_AM;
 import static com.android.server.am.ActivityManagerDebugConfig.TAG_WITH_CLASS_NAME;
 
-import android.app.ActivityOptions;
+import android.annotation.NonNull;
 import android.app.ApplicationStartInfo;
 import android.app.Flags;
 import android.app.IApplicationStartInfoCompleteListener;
 import android.content.BroadcastReceiver;
+import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentFilter;
@@ -138,6 +139,15 @@
     /** The path to the historical proc start info file, persisted in the storage. */
     @VisibleForTesting File mProcStartInfoFile;
 
+
+    /**
+     * Temporary list of records that have not been completed.
+     *
+     * Key is timestamp of launch from {@link #ActivityMetricsLaunchObserver}.
+     */
+    @GuardedBy("mLock")
+    private ArrayMap<Long, ApplicationStartInfo> mInProgRecords = new ArrayMap<>();
+
     AppStartInfoTracker() {
         mCallbacks = new SparseArray<>();
         mData = new ProcessMap<AppStartInfoContainer>();
@@ -174,68 +184,99 @@
         });
     }
 
-    void handleProcessColdStarted(long startTimeNs, HostingRecord hostingRecord,
-            ProcessRecord app) {
-        synchronized (mLock) {
-            if (!mEnabled) {
-                return;
-            }
-            ApplicationStartInfo start = new ApplicationStartInfo();
-            addBaseFieldsFromProcessRecord(start, app);
-            start.setStartupState(ApplicationStartInfo.STARTUP_STATE_STARTED);
-            start.addStartupTimestamp(
-                    ApplicationStartInfo.START_TIMESTAMP_LAUNCH, startTimeNs);
-            start.addStartupTimestamp(
-                    ApplicationStartInfo.START_TIMESTAMP_FORK, app.getStartElapsedTime());
-            start.setStartType(ApplicationStartInfo.START_TYPE_COLD);
-            start.setReason(ApplicationStartInfo.START_REASON_OTHER);
-            addStartInfoLocked(start);
-        }
-    }
-
-    public void handleProcessActivityWarmOrHotStarted(long startTimeNs,
-            ActivityOptions activityOptions, Intent intent) {
+    void onIntentStarted(@NonNull Intent intent, long timestampNanos) {
         synchronized (mLock) {
             if (!mEnabled) {
                 return;
             }
             ApplicationStartInfo start = new ApplicationStartInfo();
             start.setStartupState(ApplicationStartInfo.STARTUP_STATE_STARTED);
-            start.addStartupTimestamp(
-                    ApplicationStartInfo.START_TIMESTAMP_LAUNCH, startTimeNs);
             start.setIntent(intent);
-            start.setReason(ApplicationStartInfo.START_REASON_LAUNCHER);
-            if (activityOptions != null) {
-                start.setProcessName(activityOptions.getPackageName());
-            }
-            start.setStartType(ApplicationStartInfo.START_TYPE_WARM);
+            start.setStartType(ApplicationStartInfo.START_TYPE_UNSET);
+            start.addStartupTimestamp(ApplicationStartInfo.START_TIMESTAMP_LAUNCH, timestampNanos);
             if (intent != null && intent.getCategories() != null
                     && intent.getCategories().contains(Intent.CATEGORY_LAUNCHER)) {
                 start.setReason(ApplicationStartInfo.START_REASON_LAUNCHER);
             } else {
                 start.setReason(ApplicationStartInfo.START_REASON_START_ACTIVITY);
             }
-            addStartInfoLocked(start);
+            mInProgRecords.put(timestampNanos, start);
         }
     }
 
-    public void handleProcessActivityStartedFromRecents(long startTimeNs,
-            ActivityOptions activityOptions) {
+    void onIntentFailed(long id) {
         synchronized (mLock) {
             if (!mEnabled) {
                 return;
             }
-            ApplicationStartInfo start = new ApplicationStartInfo();
-            start.setStartupState(ApplicationStartInfo.STARTUP_STATE_STARTED);
-            start.addStartupTimestamp(
-                    ApplicationStartInfo.START_TIMESTAMP_LAUNCH, startTimeNs);
-            if (activityOptions != null) {
-                start.setIntent(activityOptions.getResultData());
-                start.setProcessName(activityOptions.getPackageName());
+            if (!mInProgRecords.containsKey(id)) {
+                return;
             }
-            start.setReason(ApplicationStartInfo.START_REASON_LAUNCHER_RECENTS);
-            start.setStartType(ApplicationStartInfo.START_TYPE_WARM);
-            addStartInfoLocked(start);
+            mInProgRecords.get(id).setStartupState(ApplicationStartInfo.STARTUP_STATE_ERROR);
+            mInProgRecords.remove(id);
+        }
+    }
+
+    void onActivityLaunched(long id, ComponentName name, long temperature, ProcessRecord app) {
+        synchronized (mLock) {
+            if (!mEnabled) {
+                return;
+            }
+            if (!mInProgRecords.containsKey(id)) {
+                return;
+            }
+            if (app != null) {
+                ApplicationStartInfo info = mInProgRecords.get(id);
+                info.setStartType((int) temperature);
+                addBaseFieldsFromProcessRecord(info, app);
+                addStartInfoLocked(info);
+            } else {
+                mInProgRecords.remove(id);
+            }
+        }
+    }
+
+    void onActivityLaunchCancelled(long id) {
+        synchronized (mLock) {
+            if (!mEnabled) {
+                return;
+            }
+            if (!mInProgRecords.containsKey(id)) {
+                return;
+            }
+            ApplicationStartInfo info = mInProgRecords.get(id);
+            info.setStartupState(ApplicationStartInfo.STARTUP_STATE_ERROR);
+            mInProgRecords.remove(id);
+        }
+    }
+
+    void onActivityLaunchFinished(long id, ComponentName name, long timestampNanos,
+            int launchMode) {
+        synchronized (mLock) {
+            if (!mEnabled) {
+                return;
+            }
+            if (!mInProgRecords.containsKey(id)) {
+                return;
+            }
+            ApplicationStartInfo info = mInProgRecords.get(id);
+            info.setStartupState(ApplicationStartInfo.STARTUP_STATE_FIRST_FRAME_DRAWN);
+            info.setLaunchMode(launchMode);
+        }
+    }
+
+    void onReportFullyDrawn(long id, long timestampNanos) {
+        synchronized (mLock) {
+            if (!mEnabled) {
+                return;
+            }
+            if (!mInProgRecords.containsKey(id)) {
+                return;
+            }
+            ApplicationStartInfo info = mInProgRecords.get(id);
+            info.addStartupTimestamp(ApplicationStartInfo.START_TIMESTAMP_FULLY_DRAWN,
+                    timestampNanos);
+            mInProgRecords.remove(id);
         }
     }
 
@@ -347,7 +388,8 @@
                 ApplicationStartInfo.START_TIMESTAMP_APPLICATION_ONCREATE);
     }
 
-    void reportBindApplicationTimeNanos(ProcessRecord app, long timeNs) {
+    /** Report a bind application timestamp to add to {@link ApplicationStartInfo}. */
+    public void reportBindApplicationTimeNanos(ProcessRecord app, long timeNs) {
         addTimestampToStart(app, timeNs,
                 ApplicationStartInfo.START_TIMESTAMP_BIND_APPLICATION);
     }
diff --git a/services/core/java/com/android/server/am/BatteryStatsService.java b/services/core/java/com/android/server/am/BatteryStatsService.java
index f8f3d82..ace2cfd 100644
--- a/services/core/java/com/android/server/am/BatteryStatsService.java
+++ b/services/core/java/com/android/server/am/BatteryStatsService.java
@@ -33,6 +33,7 @@
 import android.annotation.NonNull;
 import android.annotation.RequiresNoPermission;
 import android.annotation.SuppressLint;
+import android.app.AlarmManager;
 import android.app.StatsManager;
 import android.app.usage.NetworkStatsManager;
 import android.bluetooth.BluetoothActivityEnergyInfo;
@@ -427,13 +428,7 @@
         mPowerStatsStore = new PowerStatsStore(systemDir, mHandler, aggregatedPowerStatsConfig);
         mPowerStatsAggregator = new PowerStatsAggregator(aggregatedPowerStatsConfig,
                 mStats.getHistory());
-        final long aggregatedPowerStatsSpanDuration = context.getResources().getInteger(
-                com.android.internal.R.integer.config_aggregatedPowerStatsSpanDuration);
-        final long powerStatsAggregationPeriod = context.getResources().getInteger(
-                com.android.internal.R.integer.config_powerStatsAggregationPeriod);
-        mPowerStatsScheduler = new PowerStatsScheduler(context, mPowerStatsAggregator,
-                aggregatedPowerStatsSpanDuration, powerStatsAggregationPeriod, mPowerStatsStore,
-                Clock.SYSTEM_CLOCK, mMonotonicClock, mHandler, mStats);
+        mPowerStatsScheduler = createPowerStatsScheduler(mContext);
         PowerStatsExporter powerStatsExporter =
                 new PowerStatsExporter(mPowerStatsStore, mPowerStatsAggregator);
         mBatteryUsageStatsProvider = new BatteryUsageStatsProvider(context,
@@ -445,6 +440,23 @@
         mConfigFile = new AtomicFile(new File(systemDir, "battery_usage_stats_config"));
     }
 
+    private PowerStatsScheduler createPowerStatsScheduler(Context context) {
+        final long aggregatedPowerStatsSpanDuration = context.getResources().getInteger(
+                com.android.internal.R.integer.config_aggregatedPowerStatsSpanDuration);
+        final long powerStatsAggregationPeriod = context.getResources().getInteger(
+                com.android.internal.R.integer.config_powerStatsAggregationPeriod);
+        PowerStatsScheduler.AlarmScheduler alarmScheduler =
+                (triggerAtMillis, tag, onAlarmListener, aHandler) -> {
+                    AlarmManager alarmManager = mContext.getSystemService(AlarmManager.class);
+                    alarmManager.set(AlarmManager.ELAPSED_REALTIME, triggerAtMillis, tag,
+                            onAlarmListener, aHandler);
+                };
+        return new PowerStatsScheduler(mStats::schedulePowerStatsSampleCollection,
+                mPowerStatsAggregator, aggregatedPowerStatsSpanDuration,
+                powerStatsAggregationPeriod, mPowerStatsStore, alarmScheduler, Clock.SYSTEM_CLOCK,
+                mMonotonicClock, () -> mStats.getHistory().getStartTime(), mHandler);
+    }
+
     private AggregatedPowerStatsConfig getAggregatedPowerStatsConfig() {
         AggregatedPowerStatsConfig config = new AggregatedPowerStatsConfig();
         config.trackPowerComponent(BatteryConsumer.POWER_COMPONENT_CPU)
diff --git a/services/core/java/com/android/server/am/CoreSettingsObserver.java b/services/core/java/com/android/server/am/CoreSettingsObserver.java
index 903cb7b..982076d 100644
--- a/services/core/java/com/android/server/am/CoreSettingsObserver.java
+++ b/services/core/java/com/android/server/am/CoreSettingsObserver.java
@@ -30,7 +30,6 @@
 
 import com.android.internal.R;
 import com.android.internal.annotations.VisibleForTesting;
-import com.android.internal.config.sysui.SystemUiDeviceConfigFlags;
 
 import java.util.ArrayList;
 import java.util.HashMap;
@@ -164,12 +163,6 @@
                 WidgetFlags.MAGNIFIER_ASPECT_RATIO_DEFAULT));
 
         sDeviceConfigEntries.add(new DeviceConfigEntry<Boolean>(
-                DeviceConfig.NAMESPACE_SYSTEMUI,
-                SystemUiDeviceConfigFlags.REMOTEVIEWS_ADAPTER_CONVERSION,
-                SystemUiDeviceConfigFlags.KEY_REMOTEVIEWS_ADAPTER_CONVERSION, boolean.class,
-                SystemUiDeviceConfigFlags.REMOTEVIEWS_ADAPTER_CONVERSION_DEFAULT));
-
-        sDeviceConfigEntries.add(new DeviceConfigEntry<Boolean>(
                 TextFlags.NAMESPACE, TextFlags.ENABLE_NEW_CONTEXT_MENU,
                 TextFlags.KEY_ENABLE_NEW_CONTEXT_MENU, boolean.class,
                 TextFlags.ENABLE_NEW_CONTEXT_MENU_DEFAULT));
diff --git a/services/core/java/com/android/server/am/ProcessList.java b/services/core/java/com/android/server/am/ProcessList.java
index 4ff34b1..3156e9d 100644
--- a/services/core/java/com/android/server/am/ProcessList.java
+++ b/services/core/java/com/android/server/am/ProcessList.java
@@ -498,7 +498,7 @@
 
     /** Manages the {@link android.app.ApplicationStartInfo} records. */
     @GuardedBy("mAppStartInfoTracker")
-    final AppStartInfoTracker mAppStartInfoTracker = new AppStartInfoTracker();
+    private final AppStartInfoTracker mAppStartInfoTracker = new AppStartInfoTracker();
 
     /**
      * The currently running SDK sandbox processes for a uid.
@@ -1523,6 +1523,10 @@
         return mCachedRestoreLevel;
     }
 
+    AppStartInfoTracker getAppStartInfoTracker() {
+        return mAppStartInfoTracker;
+    }
+
     /**
      * Set the out-of-memory badness adjustment for a process.
      * If {@code pid <= 0}, this method will be a no-op.
@@ -2572,6 +2576,7 @@
             boolean isSdkSandbox, int sdkSandboxUid, String sdkSandboxClientAppPackage,
             String abiOverride, String entryPoint, String[] entryPointArgs, Runnable crashHandler) {
         long startTime = SystemClock.uptimeMillis();
+        final long startTimeNs = SystemClock.elapsedRealtimeNanos();
         ProcessRecord app;
         if (!isolated) {
             app = getProcessRecordLocked(processName, info.uid);
diff --git a/services/core/java/com/android/server/app/GameManagerService.java b/services/core/java/com/android/server/app/GameManagerService.java
index b182538..32d5cf5 100644
--- a/services/core/java/com/android/server/app/GameManagerService.java
+++ b/services/core/java/com/android/server/app/GameManagerService.java
@@ -152,7 +152,6 @@
     private static final String GAME_MODE_INTERVENTION_LIST_FILE_NAME =
             "game_mode_intervention.list";
 
-
     private final Context mContext;
     private final Object mLock = new Object();
     private final Object mDeviceConfigLock = new Object();
@@ -184,6 +183,7 @@
     @GuardedBy("mUidObserverLock")
     private final Set<Integer> mForegroundGameUids = new HashSet<>();
     private final GameManagerServiceSystemPropertiesWrapper mSysProps;
+    private float mGameDefaultFrameRateValue;
 
     @VisibleForTesting
     static class Injector {
@@ -1559,6 +1559,10 @@
         mPowerManagerInternal.setPowerMode(Mode.GAME_LOADING, false);
         Slog.v(TAG, "Game power mode OFF (game manager service start/restart)");
         mPowerManagerInternal.setPowerMode(Mode.GAME, false);
+
+        mGameDefaultFrameRateValue = (float) mSysProps.getInt(
+                PROPERTY_RO_SURFACEFLINGER_GAME_DEFAULT_FRAME_RATE, 60);
+        Slog.v(TAG, "Game Default Frame Rate : " + mGameDefaultFrameRateValue);
     }
 
     private void sendUserMessage(int userId, int what, String eventForLog, int delayMillis) {
@@ -2217,8 +2221,7 @@
         }
         if (gameDefaultFrameRate()) {
             gameDefaultFrameRate = isGameDefaultFrameRateEnabled
-                    ? (float) mSysProps.getInt(
-                            PROPERTY_RO_SURFACEFLINGER_GAME_DEFAULT_FRAME_RATE, 0) : 0.0f;
+                    ? mGameDefaultFrameRateValue : 0.0f;
         }
         return gameDefaultFrameRate;
     }
diff --git a/services/core/java/com/android/server/audio/AudioService.java b/services/core/java/com/android/server/audio/AudioService.java
index 44cb136..290bb7e 100644
--- a/services/core/java/com/android/server/audio/AudioService.java
+++ b/services/core/java/com/android/server/audio/AudioService.java
@@ -5106,7 +5106,7 @@
     private void setMasterMuteInternalNoCallerCheck(
             boolean mute, int flags, int userId, String eventSource) {
         if (DEBUG_VOL) {
-            Log.d(TAG, TextUtils.formatSimple("Master mute %s, %d, user=%d from %s",
+            Log.d(TAG, TextUtils.formatSimple("Master mute %s, flags 0x%x, userId=%d from %s",
                     mute, flags, userId, eventSource));
         }
 
diff --git a/services/core/java/com/android/server/biometrics/AuthService.java b/services/core/java/com/android/server/biometrics/AuthService.java
index 0629e637..dafea9a 100644
--- a/services/core/java/com/android/server/biometrics/AuthService.java
+++ b/services/core/java/com/android/server/biometrics/AuthService.java
@@ -36,6 +36,7 @@
 import android.app.AppOpsManager;
 import android.content.Context;
 import android.content.pm.PackageManager;
+import android.hardware.biometrics.AuthenticationStateListener;
 import android.hardware.biometrics.BiometricAuthenticator;
 import android.hardware.biometrics.BiometricManager;
 import android.hardware.biometrics.ComponentInfoInternal;
@@ -385,6 +386,26 @@
         }
 
         @Override
+        public void registerAuthenticationStateListener(AuthenticationStateListener listener)
+                throws RemoteException {
+            checkInternalPermission();
+            final IFingerprintService fingerprintService = mInjector.getFingerprintService();
+            if (fingerprintService != null) {
+                fingerprintService.registerAuthenticationStateListener(listener);
+            }
+        }
+
+        @Override
+        public void unregisterAuthenticationStateListener(AuthenticationStateListener listener)
+                throws RemoteException {
+            checkInternalPermission();
+            final IFingerprintService fingerprintService = mInjector.getFingerprintService();
+            if (fingerprintService != null) {
+                fingerprintService.unregisterAuthenticationStateListener(listener);
+            }
+        }
+
+        @Override
         public void invalidateAuthenticatorIds(int userId, int fromSensorId,
                 IInvalidationCallback callback) throws RemoteException {
             checkInternalPermission();
diff --git a/services/core/java/com/android/server/biometrics/sensors/AuthenticationClient.java b/services/core/java/com/android/server/biometrics/sensors/AuthenticationClient.java
index a47135f..f9568ea 100644
--- a/services/core/java/com/android/server/biometrics/sensors/AuthenticationClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/AuthenticationClient.java
@@ -27,7 +27,7 @@
 import android.hardware.biometrics.BiometricAuthenticator;
 import android.hardware.biometrics.BiometricConstants;
 import android.hardware.biometrics.BiometricManager;
-import android.hardware.biometrics.BiometricOverlayConstants;
+import android.hardware.biometrics.BiometricRequestConstants;
 import android.os.IBinder;
 import android.os.RemoteException;
 import android.security.KeyStore;
@@ -430,19 +430,19 @@
         return mLockoutTracker;
     }
 
-    protected int getShowOverlayReason() {
+    protected int getRequestReason() {
         if (isKeyguard()) {
-            return BiometricOverlayConstants.REASON_AUTH_KEYGUARD;
+            return BiometricRequestConstants.REASON_AUTH_KEYGUARD;
         } else if (isBiometricPrompt()) {
             // BP reason always takes precedent over settings, since callers from within
             // settings can always invoke BP.
-            return BiometricOverlayConstants.REASON_AUTH_BP;
+            return BiometricRequestConstants.REASON_AUTH_BP;
         } else if (isSettings()) {
             // This is pretty much only for FingerprintManager#authenticate usage from
             // FingerprintSettings.
-            return BiometricOverlayConstants.REASON_AUTH_SETTINGS;
+            return BiometricRequestConstants.REASON_AUTH_SETTINGS;
         } else {
-            return BiometricOverlayConstants.REASON_AUTH_OTHER;
+            return BiometricRequestConstants.REASON_AUTH_OTHER;
         }
     }
 }
diff --git a/services/core/java/com/android/server/biometrics/sensors/AuthenticationStateListeners.java b/services/core/java/com/android/server/biometrics/sensors/AuthenticationStateListeners.java
new file mode 100644
index 0000000..5863535
--- /dev/null
+++ b/services/core/java/com/android/server/biometrics/sensors/AuthenticationStateListeners.java
@@ -0,0 +1,108 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.biometrics.sensors;
+
+import android.annotation.NonNull;
+import android.hardware.biometrics.AuthenticationStateListener;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.util.Slog;
+
+import java.util.concurrent.CopyOnWriteArrayList;
+
+/**
+ * Low-level callback interface between BiometricManager and AuthService. Allows core system
+ * services (e.g. SystemUI) to register and unregister listeners for updates about the current
+ * state of biometric authentication.
+ * @hide */
+public class AuthenticationStateListeners implements IBinder.DeathRecipient {
+
+    private static final String TAG = "AuthenticationStateListeners";
+
+    @NonNull
+    private final CopyOnWriteArrayList<AuthenticationStateListener> mAuthenticationStateListeners =
+            new CopyOnWriteArrayList<>();
+
+    /**
+     * Enables clients to register an AuthenticationStateListener for updates about the current
+     * state of biometric authentication.
+     * @param listener listener to register
+     */
+    public void registerAuthenticationStateListener(
+            @NonNull AuthenticationStateListener listener) {
+        mAuthenticationStateListeners.add(listener);
+        try {
+            listener.asBinder().linkToDeath(this, 0 /* flags */);
+        } catch (RemoteException e) {
+            Slog.e(TAG, "Failed to link to death", e);
+        }
+    }
+
+    /**
+     * Enables clients to unregister an AuthenticationStateListener.
+     * @param listener listener to register
+     */
+    public void unregisterAuthenticationStateListener(
+            @NonNull AuthenticationStateListener listener) {
+        mAuthenticationStateListeners.remove(listener);
+    }
+
+    /**
+     * Defines behavior in response to authentication starting
+     * @param requestReason reason from [BiometricRequestConstants.RequestReason] for requesting
+     * authentication starting
+     */
+    public void onAuthenticationStarted(int requestReason) {
+        for (AuthenticationStateListener listener: mAuthenticationStateListeners) {
+            try {
+                listener.onAuthenticationStarted(requestReason);
+            } catch (RemoteException e) {
+                Slog.e(TAG, "Remote exception in notifying listener that authentication "
+                        + "started", e);
+            }
+        }
+    }
+
+    /**
+     * Defines behavior in response to authentication stopping
+     */
+    public void onAuthenticationStopped() {
+        for (AuthenticationStateListener listener: mAuthenticationStateListeners) {
+            try {
+                listener.onAuthenticationStopped();
+            } catch (RemoteException e) {
+                Slog.e(TAG, "Remote exception in notifying listener that authentication "
+                        + "stopped", e);
+            }
+        }
+    }
+
+    @Override
+    public void binderDied() {
+        // Do nothing, handled below
+    }
+
+    @Override
+    public void binderDied(IBinder who) {
+        Slog.w(TAG, "Callback binder died: " + who);
+        if (mAuthenticationStateListeners.removeIf(listener -> listener.asBinder().equals(who))) {
+            Slog.w(TAG, "Removed dead listener for " + who);
+        } else {
+            Slog.w(TAG, "No dead listeners found");
+        }
+    }
+}
diff --git a/services/core/java/com/android/server/biometrics/sensors/EnrollClient.java b/services/core/java/com/android/server/biometrics/sensors/EnrollClient.java
index 483ce75..2c4d30b 100644
--- a/services/core/java/com/android/server/biometrics/sensors/EnrollClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/EnrollClient.java
@@ -19,7 +19,7 @@
 import android.annotation.NonNull;
 import android.content.Context;
 import android.hardware.biometrics.BiometricAuthenticator;
-import android.hardware.biometrics.BiometricOverlayConstants;
+import android.hardware.biometrics.BiometricRequestConstants;
 import android.hardware.fingerprint.FingerprintManager;
 import android.os.IBinder;
 import android.os.RemoteException;
@@ -135,14 +135,14 @@
         return true;
     }
 
-    protected int getOverlayReasonFromEnrollReason(@FingerprintManager.EnrollReason int reason) {
+    protected int getRequestReasonFromEnrollReason(@FingerprintManager.EnrollReason int reason) {
         switch (reason) {
             case FingerprintManager.ENROLL_FIND_SENSOR:
-                return BiometricOverlayConstants.REASON_ENROLL_FIND_SENSOR;
+                return BiometricRequestConstants.REASON_ENROLL_FIND_SENSOR;
             case FingerprintManager.ENROLL_ENROLL:
-                return BiometricOverlayConstants.REASON_ENROLL_ENROLLING;
+                return BiometricRequestConstants.REASON_ENROLL_ENROLLING;
             default:
-                return BiometricOverlayConstants.REASON_UNKNOWN;
+                return BiometricRequestConstants.REASON_UNKNOWN;
         }
     }
 }
diff --git a/services/core/java/com/android/server/biometrics/sensors/SensorOverlays.java b/services/core/java/com/android/server/biometrics/sensors/SensorOverlays.java
index aeb6b6e..3d20efc 100644
--- a/services/core/java/com/android/server/biometrics/sensors/SensorOverlays.java
+++ b/services/core/java/com/android/server/biometrics/sensors/SensorOverlays.java
@@ -16,9 +16,11 @@
 
 package com.android.server.biometrics.sensors;
 
+import static com.android.systemui.shared.Flags.sidefpsControllerRefactor;
+
 import android.annotation.NonNull;
 import android.annotation.Nullable;
-import android.hardware.biometrics.BiometricOverlayConstants;
+import android.hardware.biometrics.BiometricRequestConstants;
 import android.hardware.fingerprint.ISidefpsController;
 import android.hardware.fingerprint.IUdfpsOverlayController;
 import android.hardware.fingerprint.IUdfpsOverlayControllerCallback;
@@ -42,6 +44,8 @@
     private static final String TAG = "SensorOverlays";
 
     @NonNull private final Optional<IUdfpsOverlayController> mUdfpsOverlayController;
+
+    // TODO(b/288175061): remove with Flags.FLAG_SIDEFPS_CONTROLLER_REFACTOR
     @NonNull private final Optional<ISidefpsController> mSidefpsController;
 
     /**
@@ -58,19 +62,32 @@
     }
 
     /**
+     * Create an overlay controller for each modality.
+     *
+     * @param udfpsOverlayController under display fps or null if not present on device
+     */
+    public SensorOverlays(@Nullable IUdfpsOverlayController udfpsOverlayController) {
+        mUdfpsOverlayController = Optional.ofNullable(udfpsOverlayController);
+        // TODO(b/288175061): remove with Flags.FLAG_SIDEFPS_CONTROLLER_REFACTOR
+        mSidefpsController = Optional.empty();
+    }
+
+    /**
      * Show the overlay.
      *
      * @param sensorId sensor id
      * @param reason reason for showing
      * @param client client performing operation
      */
-    public void show(int sensorId, @BiometricOverlayConstants.ShowReason int reason,
+    public void show(int sensorId, @BiometricRequestConstants.RequestReason int reason,
             @NonNull AcquisitionClient<?> client) {
-        if (mSidefpsController.isPresent()) {
-            try {
-                mSidefpsController.get().show(sensorId, reason);
-            } catch (RemoteException e) {
-                Slog.e(TAG, "Remote exception when showing the side-fps overlay", e);
+        if (!sidefpsControllerRefactor()) {
+            if (mSidefpsController.isPresent()) {
+                try {
+                    mSidefpsController.get().show(sensorId, reason);
+                } catch (RemoteException e) {
+                    Slog.e(TAG, "Remote exception when showing the side-fps overlay", e);
+                }
             }
         }
 
@@ -98,11 +115,13 @@
      * @param sensorId sensor id
      */
     public void hide(int sensorId) {
-        if (mSidefpsController.isPresent()) {
-            try {
-                mSidefpsController.get().hide(sensorId);
-            } catch (RemoteException e) {
-                Slog.e(TAG, "Remote exception when hiding the side-fps overlay", e);
+        if (!sidefpsControllerRefactor()) {
+            if (mSidefpsController.isPresent()) {
+                try {
+                    mSidefpsController.get().hide(sensorId);
+                } catch (RemoteException e) {
+                    Slog.e(TAG, "Remote exception when hiding the side-fps overlay", e);
+                }
             }
         }
 
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 5084b60..578d9dc 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
@@ -722,6 +722,7 @@
             if (Flags.faceVhalFeature() && Utils.isVirtualEnabled(getContext())) {
                 if (virtualAt != -1) {
                     //only virtual instance should be returned
+                    Slog.i(TAG, "virtual hal is used");
                     return new Pair(new ArrayList<>(), List.of(aidlInstances.get(virtualAt)));
                 } else {
                     Slog.e(TAG, "Could not find virtual interface while it is enabled");
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintService.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintService.java
index 5ce0c8b..83b306b 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintService.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/FingerprintService.java
@@ -33,6 +33,7 @@
 import android.app.AppOpsManager;
 import android.content.Context;
 import android.content.pm.PackageManager;
+import android.hardware.biometrics.AuthenticationStateListener;
 import android.hardware.biometrics.BiometricPrompt;
 import android.hardware.biometrics.BiometricsProtoEnums;
 import android.hardware.biometrics.IBiometricSensorReceiver;
@@ -81,6 +82,7 @@
 import com.android.server.SystemService;
 import com.android.server.biometrics.Utils;
 import com.android.server.biometrics.log.BiometricContext;
+import com.android.server.biometrics.sensors.AuthenticationStateListeners;
 import com.android.server.biometrics.sensors.BaseClientMonitor;
 import com.android.server.biometrics.sensors.BiometricStateCallback;
 import com.android.server.biometrics.sensors.ClientMonitorCallback;
@@ -128,6 +130,8 @@
     private final BiometricStateCallback<ServiceProvider, FingerprintSensorPropertiesInternal>
             mBiometricStateCallback;
     @NonNull
+    private final AuthenticationStateListeners mAuthenticationStateListeners;
+    @NonNull
     private final Handler mHandler;
     @NonNull
     private final FingerprintServiceRegistry mRegistry;
@@ -891,6 +895,7 @@
                 return providers;
             });
         }
+
         @android.annotation.EnforcePermission(android.Manifest.permission.USE_BIOMETRIC_INTERNAL)
         @Override
         public void addAuthenticatorsRegisteredCallback(
@@ -902,6 +907,24 @@
 
         @android.annotation.EnforcePermission(android.Manifest.permission.USE_BIOMETRIC_INTERNAL)
         @Override
+        public void registerAuthenticationStateListener(
+                @NonNull AuthenticationStateListener listener) {
+            super.registerAuthenticationStateListener_enforcePermission();
+
+            mAuthenticationStateListeners.registerAuthenticationStateListener(listener);
+        }
+
+        @android.annotation.EnforcePermission(android.Manifest.permission.USE_BIOMETRIC_INTERNAL)
+        @Override
+        public void unregisterAuthenticationStateListener(
+                @NonNull AuthenticationStateListener listener) {
+            super.unregisterAuthenticationStateListener_enforcePermission();
+
+            mAuthenticationStateListeners.unregisterAuthenticationStateListener(listener);
+        }
+
+        @android.annotation.EnforcePermission(android.Manifest.permission.USE_BIOMETRIC_INTERNAL)
+        @Override
         public void registerBiometricStateListener(@NonNull IBiometricStateListener listener) {
             super.registerBiometricStateListener_enforcePermission();
 
@@ -957,6 +980,7 @@
             }
         }
 
+        // TODO(b/288175061): remove with Flags.FLAG_SIDEFPS_CONTROLLER_REFACTOR
         @android.annotation.EnforcePermission(android.Manifest.permission.USE_BIOMETRIC_INTERNAL)
         @Override
         public void setSidefpsController(@NonNull ISidefpsController controller) {
@@ -1014,6 +1038,7 @@
         mLockoutResetDispatcher = new LockoutResetDispatcher(context);
         mLockPatternUtils = new LockPatternUtils(context);
         mBiometricStateCallback = new BiometricStateCallback<>(UserManager.get(context));
+        mAuthenticationStateListeners = new AuthenticationStateListeners();
         mFingerprintProvider = fingerprintProvider != null ? fingerprintProvider :
                 (name) -> {
                     final String fqName = IFingerprint.DESCRIPTOR + "/" + name;
@@ -1022,9 +1047,9 @@
                     if (fp != null) {
                         try {
                             return new FingerprintProvider(getContext(),
-                                    mBiometricStateCallback, fp.getSensorProps(), name,
-                                    mLockoutResetDispatcher, mGestureAvailabilityDispatcher,
-                                    mBiometricContext);
+                                    mBiometricStateCallback, mAuthenticationStateListeners,
+                                    fp.getSensorProps(), name, mLockoutResetDispatcher,
+                                    mGestureAvailabilityDispatcher, mBiometricContext);
                         } catch (RemoteException e) {
                             Slog.e(TAG, "Remote exception in getSensorProps: " + fqName);
                         }
@@ -1057,6 +1082,7 @@
         if (Utils.isVirtualEnabled(getContext())) {
             if (virtualAt != -1) {
                 //only virtual instance should be returned
+                Slog.i(TAG, "virtual hal is used");
                 return new Pair(new ArrayList<>(), List.of(aidlInstances.get(virtualAt)));
             } else {
                 Slog.e(TAG, "Could not find virtual interface while it is enabled");
@@ -1085,13 +1111,13 @@
                     Fingerprint21UdfpsMock.CONFIG_ENABLE_TEST_UDFPS, 0 /* default */,
                     UserHandle.USER_CURRENT) != 0) {
                 fingerprint21 = Fingerprint21UdfpsMock.newInstance(getContext(),
-                        mBiometricStateCallback, hidlSensor,
-                        mLockoutResetDispatcher, mGestureAvailabilityDispatcher,
+                        mBiometricStateCallback, mAuthenticationStateListeners,
+                        hidlSensor, mLockoutResetDispatcher, mGestureAvailabilityDispatcher,
                         BiometricContext.getInstance(getContext()));
             } else {
                 fingerprint21 = Fingerprint21.newInstance(getContext(),
-                        mBiometricStateCallback, hidlSensor, mHandler,
-                        mLockoutResetDispatcher, mGestureAvailabilityDispatcher);
+                        mBiometricStateCallback, mAuthenticationStateListeners, hidlSensor,
+                        mHandler, mLockoutResetDispatcher, mGestureAvailabilityDispatcher);
             }
             providers.add(fingerprint21);
         }
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/ServiceProvider.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/ServiceProvider.java
index a15d1a4..fc37d70 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/ServiceProvider.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/ServiceProvider.java
@@ -135,6 +135,7 @@
 
     void onPowerPressed();
 
+    // TODO(b/288175061): remove with Flags.FLAG_SIDEFPS_CONTROLLER_REFACTOR
     /**
      * Sets side-fps controller
      * @param controller side-fps controller
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClient.java
index 337c3c2..29c5a3d 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClient.java
@@ -16,6 +16,8 @@
 
 package com.android.server.biometrics.sensors.fingerprint.aidl;
 
+import static com.android.systemui.shared.Flags.sidefpsControllerRefactor;
+
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.app.TaskStackListener;
@@ -48,6 +50,7 @@
 import com.android.server.biometrics.log.Probe;
 import com.android.server.biometrics.sensors.AuthSessionCoordinator;
 import com.android.server.biometrics.sensors.AuthenticationClient;
+import com.android.server.biometrics.sensors.AuthenticationStateListeners;
 import com.android.server.biometrics.sensors.BiometricNotificationUtils;
 import com.android.server.biometrics.sensors.ClientMonitorCallback;
 import com.android.server.biometrics.sensors.ClientMonitorCallbackConverter;
@@ -84,6 +87,7 @@
     private final int mSkipWaitForPowerVendorAcquireMessage;
     private final long mFingerUpIgnoresPower = 500;
     private final AuthSessionCoordinator mAuthSessionCoordinator;
+    @NonNull private final AuthenticationStateListeners mAuthenticationStateListeners;
     @Nullable
     private ICancellationSignal mCancellationSignal;
     private boolean mIsPointerDown;
@@ -110,7 +114,9 @@
             boolean isStrongBiometric,
             @Nullable TaskStackListener taskStackListener,
             @Nullable IUdfpsOverlayController udfpsOverlayController,
+            // TODO(b/288175061): remove with Flags.FLAG_SIDEFPS_CONTROLLER_REFACTOR
             @Nullable ISidefpsController sidefpsController,
+            @NonNull AuthenticationStateListeners authenticationStateListeners,
             boolean allowBackgroundAuthentication,
             @NonNull FingerprintSensorPropertiesInternal sensorProps,
             @NonNull Handler handler,
@@ -136,7 +142,12 @@
                 false /* shouldVibrate */,
                 biometricStrength);
         setRequestId(requestId);
-        mSensorOverlays = new SensorOverlays(udfpsOverlayController, sidefpsController);
+        if (sidefpsControllerRefactor()) {
+            mSensorOverlays = new SensorOverlays(udfpsOverlayController);
+        } else {
+            mSensorOverlays = new SensorOverlays(udfpsOverlayController, sidefpsController);
+        }
+        mAuthenticationStateListeners = authenticationStateListeners;
         mSensorProps = sensorProps;
         mALSProbeCallback = getLogger().getAmbientLightProbe(false /* startWithClient */);
         mHandler = handler;
@@ -216,6 +227,9 @@
         if (authenticated) {
             mState = STATE_STOPPED;
             mSensorOverlays.hide(getSensorId());
+            if (sidefpsControllerRefactor()) {
+                mAuthenticationStateListeners.onAuthenticationStopped();
+            }
         } else {
             mState = STATE_STARTED_PAUSED_ATTEMPTED;
         }
@@ -241,6 +255,9 @@
                 // controlled by the HAL, the framework must stop the sensor before finishing the
                 // client.
                 mSensorOverlays.hide(getSensorId());
+                if (sidefpsControllerRefactor()) {
+                    mAuthenticationStateListeners.onAuthenticationStopped();
+                }
                 onErrorInternal(errorCode, 0 /* vendorCode */, false /* finish */);
                 cancel();
             }
@@ -266,11 +283,17 @@
         }
 
         mSensorOverlays.hide(getSensorId());
+        if (sidefpsControllerRefactor()) {
+            mAuthenticationStateListeners.onAuthenticationStopped();
+        }
     }
 
     @Override
     protected void startHalOperation() {
-        mSensorOverlays.show(getSensorId(), getShowOverlayReason(), this);
+        mSensorOverlays.show(getSensorId(), getRequestReason(), this);
+        if (sidefpsControllerRefactor()) {
+            mAuthenticationStateListeners.onAuthenticationStarted(getRequestReason());
+        }
 
         try {
             mCancellationSignal = doAuthenticate();
@@ -280,6 +303,9 @@
                     BiometricFingerprintConstants.FINGERPRINT_ERROR_HW_UNAVAILABLE,
                     0 /* vendorCode */);
             mSensorOverlays.hide(getSensorId());
+            if (sidefpsControllerRefactor()) {
+                mAuthenticationStateListeners.onAuthenticationStopped();
+            }
             mCallback.onClientFinished(this, false /* success */);
         }
     }
@@ -323,6 +349,9 @@
     @Override
     protected void stopHalOperation() {
         mSensorOverlays.hide(getSensorId());
+        if (sidefpsControllerRefactor()) {
+            mAuthenticationStateListeners.onAuthenticationStopped();
+        }
         unsubscribeBiometricContext();
 
         if (mCancellationSignal != null) {
@@ -423,6 +452,9 @@
         }
 
         mSensorOverlays.hide(getSensorId());
+        if (sidefpsControllerRefactor()) {
+            mAuthenticationStateListeners.onAuthenticationStopped();
+        }
         mCallback.onClientFinished(this, false /* success */);
     }
 
@@ -450,6 +482,9 @@
         }
 
         mSensorOverlays.hide(getSensorId());
+        if (sidefpsControllerRefactor()) {
+            mAuthenticationStateListeners.onAuthenticationStopped();
+        }
         mCallback.onClientFinished(this, false /* success */);
     }
 
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintDetectClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintDetectClient.java
index e2413ee..e58e5ae 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintDetectClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintDetectClient.java
@@ -16,10 +16,12 @@
 
 package com.android.server.biometrics.sensors.fingerprint.aidl;
 
+import static com.android.systemui.shared.Flags.sidefpsControllerRefactor;
+
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.content.Context;
-import android.hardware.biometrics.BiometricOverlayConstants;
+import android.hardware.biometrics.BiometricRequestConstants;
 import android.hardware.biometrics.common.ICancellationSignal;
 import android.hardware.fingerprint.FingerprintAuthenticateOptions;
 import android.hardware.fingerprint.IUdfpsOverlayController;
@@ -66,7 +68,12 @@
                 true /* shouldVibrate */, biometricLogger, biometricContext);
         setRequestId(requestId);
         mIsStrongBiometric = isStrongBiometric;
-        mSensorOverlays = new SensorOverlays(udfpsOverlayController, null /* sideFpsController*/);
+        if (sidefpsControllerRefactor()) {
+            mSensorOverlays = new SensorOverlays(udfpsOverlayController);
+        } else {
+            mSensorOverlays = new SensorOverlays(
+                    udfpsOverlayController, null /* sideFpsController */);
+        }
         mOptions = options;
     }
 
@@ -93,7 +100,7 @@
 
     @Override
     protected void startHalOperation() {
-        mSensorOverlays.show(getSensorId(), BiometricOverlayConstants.REASON_AUTH_KEYGUARD,
+        mSensorOverlays.show(getSensorId(), BiometricRequestConstants.REASON_AUTH_KEYGUARD,
                 this);
 
         try {
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintEnrollClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintEnrollClient.java
index 06550d8..c0761ed 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintEnrollClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintEnrollClient.java
@@ -16,6 +16,8 @@
 
 package com.android.server.biometrics.sensors.fingerprint.aidl;
 
+import static com.android.systemui.shared.Flags.sidefpsControllerRefactor;
+
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.content.Context;
@@ -42,6 +44,7 @@
 import com.android.server.biometrics.log.CallbackWithProbe;
 import com.android.server.biometrics.log.OperationContextExt;
 import com.android.server.biometrics.log.Probe;
+import com.android.server.biometrics.sensors.AuthenticationStateListeners;
 import com.android.server.biometrics.sensors.BiometricNotificationUtils;
 import com.android.server.biometrics.sensors.BiometricUtils;
 import com.android.server.biometrics.sensors.ClientMonitorCallback;
@@ -65,6 +68,7 @@
     @NonNull private final CallbackWithProbe<Probe> mALSProbeCallback;
 
     private final @FingerprintManager.EnrollReason int mEnrollReason;
+    @NonNull private final AuthenticationStateListeners mAuthenticationStateListeners;
     @Nullable private ICancellationSignal mCancellationSignal;
     private final int mMaxTemplatesPerUser;
     private boolean mIsPointerDown;
@@ -80,15 +84,18 @@
         }
     }
 
-    public FingerprintEnrollClient(@NonNull Context context,
-            @NonNull Supplier<AidlSession> lazyDaemon, @NonNull IBinder token, long requestId,
+    public FingerprintEnrollClient(
+            @NonNull Context context, @NonNull Supplier<AidlSession> lazyDaemon,
+            @NonNull IBinder token, long requestId,
             @NonNull ClientMonitorCallbackConverter listener, int userId,
             @NonNull byte[] hardwareAuthToken, @NonNull String owner,
             @NonNull BiometricUtils<Fingerprint> utils, int sensorId,
             @NonNull BiometricLogger logger, @NonNull BiometricContext biometricContext,
             @NonNull FingerprintSensorPropertiesInternal sensorProps,
             @Nullable IUdfpsOverlayController udfpsOverlayController,
+            // TODO(b/288175061): remove with Flags.FLAG_SIDEFPS_CONTROLLER_REFACTOR
             @Nullable ISidefpsController sidefpsController,
+            @NonNull AuthenticationStateListeners authenticationStateListeners,
             int maxTemplatesPerUser, @FingerprintManager.EnrollReason int enrollReason) {
         // UDFPS haptics occur when an image is acquired (instead of when the result is known)
         super(context, lazyDaemon, token, listener, userId, hardwareAuthToken, owner, utils,
@@ -96,7 +103,13 @@
                 logger, biometricContext);
         setRequestId(requestId);
         mSensorProps = sensorProps;
-        mSensorOverlays = new SensorOverlays(udfpsOverlayController, sidefpsController);
+        if (sidefpsControllerRefactor()) {
+            mSensorOverlays = new SensorOverlays(udfpsOverlayController);
+        } else {
+            mSensorOverlays = new SensorOverlays(udfpsOverlayController, sidefpsController);
+        }
+        mAuthenticationStateListeners = authenticationStateListeners;
+
         mMaxTemplatesPerUser = maxTemplatesPerUser;
 
         mALSProbeCallback = getLogger().getAmbientLightProbe(true /* startWithClient */);
@@ -130,7 +143,11 @@
 
         if (remaining == 0) {
             mSensorOverlays.hide(getSensorId());
+            if (sidefpsControllerRefactor()) {
+                mAuthenticationStateListeners.onAuthenticationStopped();
+            }
         }
+
     }
 
     @Override
@@ -159,8 +176,10 @@
     @Override
     public void onError(int errorCode, int vendorCode) {
         super.onError(errorCode, vendorCode);
-
         mSensorOverlays.hide(getSensorId());
+        if (sidefpsControllerRefactor()) {
+            mAuthenticationStateListeners.onAuthenticationStopped();
+        }
     }
 
     @Override
@@ -171,8 +190,12 @@
 
     @Override
     protected void startHalOperation() {
-        mSensorOverlays.show(getSensorId(), getOverlayReasonFromEnrollReason(mEnrollReason),
+        mSensorOverlays.show(getSensorId(), getRequestReasonFromEnrollReason(mEnrollReason),
                 this);
+        if (sidefpsControllerRefactor()) {
+            mAuthenticationStateListeners.onAuthenticationStarted(
+                    getRequestReasonFromEnrollReason(mEnrollReason));
+        }
 
         BiometricNotificationUtils.cancelBadCalibrationNotification(getContext());
         try {
@@ -210,6 +233,10 @@
     @Override
     protected void stopHalOperation() {
         mSensorOverlays.hide(getSensorId());
+        if (sidefpsControllerRefactor()) {
+            mAuthenticationStateListeners.onAuthenticationStopped();
+        }
+
         unsubscribeBiometricContext();
 
         if (mCancellationSignal != null) {
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProvider.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProvider.java
index 9985b06..032ab87 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProvider.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProvider.java
@@ -64,6 +64,7 @@
 import com.android.server.biometrics.log.BiometricLogger;
 import com.android.server.biometrics.sensors.AuthSessionCoordinator;
 import com.android.server.biometrics.sensors.AuthenticationClient;
+import com.android.server.biometrics.sensors.AuthenticationStateListeners;
 import com.android.server.biometrics.sensors.BaseClientMonitor;
 import com.android.server.biometrics.sensors.BiometricScheduler;
 import com.android.server.biometrics.sensors.BiometricStateCallback;
@@ -108,6 +109,8 @@
     @NonNull
     private final BiometricStateCallback mBiometricStateCallback;
     @NonNull
+    private final AuthenticationStateListeners mAuthenticationStateListeners;
+    @NonNull
     private final String mHalInstanceName;
     @NonNull
     private final Handler mHandler;
@@ -122,6 +125,7 @@
     @NonNull private final BiometricContext mBiometricContext;
     @Nullable private IFingerprint mDaemon;
     @Nullable private IUdfpsOverlayController mUdfpsOverlayController;
+    // TODO(b/288175061): remove with Flags.FLAG_SIDEFPS_CONTROLLER_REFACTOR
     @Nullable private ISidefpsController mSidefpsController;
     private AuthSessionCoordinator mAuthSessionCoordinator;
     @Nullable private AuthenticationStatsCollector mAuthenticationStatsCollector;
@@ -157,16 +161,19 @@
 
     public FingerprintProvider(@NonNull Context context,
             @NonNull BiometricStateCallback biometricStateCallback,
+            @NonNull AuthenticationStateListeners authenticationStateListeners,
             @NonNull SensorProps[] props, @NonNull String halInstanceName,
             @NonNull LockoutResetDispatcher lockoutResetDispatcher,
             @NonNull GestureAvailabilityDispatcher gestureAvailabilityDispatcher,
             @NonNull BiometricContext biometricContext) {
-        this(context, biometricStateCallback, props, halInstanceName, lockoutResetDispatcher,
-                gestureAvailabilityDispatcher, biometricContext, null /* daemon */);
+        this(context, biometricStateCallback, authenticationStateListeners, props, halInstanceName,
+                lockoutResetDispatcher, gestureAvailabilityDispatcher, biometricContext,
+                null /* daemon */);
     }
 
     @VisibleForTesting FingerprintProvider(@NonNull Context context,
             @NonNull BiometricStateCallback biometricStateCallback,
+            @NonNull AuthenticationStateListeners authenticationStateListeners,
             @NonNull SensorProps[] props, @NonNull String halInstanceName,
             @NonNull LockoutResetDispatcher lockoutResetDispatcher,
             @NonNull GestureAvailabilityDispatcher gestureAvailabilityDispatcher,
@@ -174,6 +181,7 @@
             IFingerprint daemon) {
         mContext = context;
         mBiometricStateCallback = biometricStateCallback;
+        mAuthenticationStateListeners = authenticationStateListeners;
         mHalInstanceName = halInstanceName;
         mFingerprintSensors = new SensorList<>(ActivityManager.getService());
         mHandler = new Handler(Looper.getMainLooper());
@@ -434,7 +442,7 @@
                     mBiometricContext,
                     mFingerprintSensors.get(sensorId).getSensorProperties(),
                     mUdfpsOverlayController, mSidefpsController,
-                    maxTemplatesPerUser, enrollReason);
+                    mAuthenticationStateListeners, maxTemplatesPerUser, enrollReason);
             scheduleForSensor(sensorId, client, new ClientMonitorCompositeCallback(
                     mBiometricStateCallback, new ClientMonitorCallback() {
                 @Override
@@ -498,7 +506,7 @@
                     mBiometricContext, isStrongBiometric,
                     mTaskStackListener,
                     mUdfpsOverlayController, mSidefpsController,
-                    allowBackgroundAuthentication,
+                    mAuthenticationStateListeners, allowBackgroundAuthentication,
                     mFingerprintSensors.get(sensorId).getSensorProperties(), mHandler,
                     Utils.getCurrentStrength(sensorId),
                     SystemClock.elapsedRealtimeClock(),
@@ -732,6 +740,7 @@
         }
     }
 
+    // TODO(b/288175061): remove with Flags.FLAG_SIDEFPS_CONTROLLER_REFACTOR
     @Override
     public void setSidefpsController(@NonNull ISidefpsController controller) {
         mSidefpsController = controller;
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21.java
index 8bfa560..d3cecd0 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21.java
@@ -68,6 +68,7 @@
 import com.android.server.biometrics.sensors.AuthSessionCoordinator;
 import com.android.server.biometrics.sensors.AuthenticationClient;
 import com.android.server.biometrics.sensors.AuthenticationConsumer;
+import com.android.server.biometrics.sensors.AuthenticationStateListeners;
 import com.android.server.biometrics.sensors.BaseClientMonitor;
 import com.android.server.biometrics.sensors.BiometricScheduler;
 import com.android.server.biometrics.sensors.BiometricStateCallback;
@@ -115,6 +116,7 @@
 
     final Context mContext;
     @NonNull private final BiometricStateCallback mBiometricStateCallback;
+    @NonNull private final AuthenticationStateListeners mAuthenticationStateListeners;
     private final ActivityTaskManager mActivityTaskManager;
     @NonNull private final FingerprintSensorPropertiesInternal mSensorProperties;
     private final BiometricScheduler mScheduler;
@@ -128,6 +130,8 @@
     @Nullable private IBiometricsFingerprint mDaemon;
     @NonNull private final HalResultController mHalResultController;
     @Nullable private IUdfpsOverlayController mUdfpsOverlayController;
+
+    // TODO(b/288175061): remove with Flags.FLAG_SIDEFPS_CONTROLLER_REFACTOR
     @Nullable private ISidefpsController mSidefpsController;
     @NonNull private final BiometricContext mBiometricContext;
     @Nullable private AuthenticationStatsCollector mAuthenticationStatsCollector;
@@ -330,6 +334,7 @@
     @VisibleForTesting
     Fingerprint21(@NonNull Context context,
             @NonNull BiometricStateCallback biometricStateCallback,
+            @NonNull AuthenticationStateListeners authenticationStateListeners,
             @NonNull FingerprintSensorPropertiesInternal sensorProps,
             @NonNull BiometricScheduler scheduler,
             @NonNull Handler handler,
@@ -338,6 +343,7 @@
             @NonNull BiometricContext biometricContext) {
         mContext = context;
         mBiometricStateCallback = biometricStateCallback;
+        mAuthenticationStateListeners = authenticationStateListeners;
         mBiometricContext = biometricContext;
 
         mSensorProperties = sensorProps;
@@ -378,6 +384,7 @@
 
     public static Fingerprint21 newInstance(@NonNull Context context,
             @NonNull BiometricStateCallback biometricStateCallback,
+            @NonNull AuthenticationStateListeners authenticationStateListeners,
             @NonNull FingerprintSensorPropertiesInternal sensorProps,
             @NonNull Handler handler,
             @NonNull LockoutResetDispatcher lockoutResetDispatcher,
@@ -388,8 +395,9 @@
                         gestureAvailabilityDispatcher);
         final HalResultController controller = new HalResultController(sensorProps.sensorId,
                 context, handler, scheduler);
-        return new Fingerprint21(context, biometricStateCallback, sensorProps, scheduler, handler,
-                lockoutResetDispatcher, controller, BiometricContext.getInstance(context));
+        return new Fingerprint21(context, biometricStateCallback, authenticationStateListeners,
+                sensorProps, scheduler, handler, lockoutResetDispatcher, controller,
+                BiometricContext.getInstance(context));
     }
 
     @Override
@@ -719,7 +727,10 @@
                 mSensorProperties.sensorId,
                 createLogger(BiometricsProtoEnums.ACTION_ENROLL,
                         BiometricsProtoEnums.CLIENT_UNKNOWN, mAuthenticationStatsCollector),
-                mBiometricContext, mUdfpsOverlayController, mSidefpsController, enrollReason);
+                mBiometricContext, mUdfpsOverlayController,
+                // TODO(b/288175061): remove with Flags.FLAG_SIDEFPS_CONTROLLER_REFACTOR
+                mSidefpsController,
+                mAuthenticationStateListeners, enrollReason);
         mScheduler.scheduleClientMonitor(client, new ClientMonitorCallback() {
             @Override
             public void onClientStarted(@NonNull BaseClientMonitor clientMonitor) {
@@ -761,10 +772,14 @@
                                 BiometricsProtoEnums.CLIENT_UNKNOWN,
                                 mAuthenticationStatsCollector),
                         mBiometricContext, null /* sensorProps */,
-                        mUdfpsOverlayController, mSidefpsController,
+                        mUdfpsOverlayController,
+                        // TODO(b/288175061): remove with Flags.FLAG_SIDEFPS_CONTROLLER_REFACTOR
+                        mSidefpsController,
+                        mAuthenticationStateListeners,
                         mContext.getResources().getInteger(
                                 com.android.internal.R.integer.config_fingerprintMaxTemplatesPerUser),
                         enrollReason);
+
         mScheduler.scheduleClientMonitor(client, new ClientMonitorCallback() {
             @Override
             public void onClientStarted(@NonNull BaseClientMonitor clientMonitor) {
@@ -880,6 +895,7 @@
                         mBiometricContext, isStrongBiometric,
                         mTaskStackListener,
                         mUdfpsOverlayController, mSidefpsController,
+                        mAuthenticationStateListeners,
                         allowBackgroundAuthentication, mSensorProperties, mHandler,
                         Utils.getCurrentStrength(mSensorId), null /* clock */, mLockoutTracker);
         mScheduler.scheduleClientMonitor(client, mBiometricStateCallback);
@@ -898,6 +914,7 @@
                 mBiometricContext, isStrongBiometric,
                 mTaskStackListener, mLockoutTracker,
                 mUdfpsOverlayController, mSidefpsController,
+                mAuthenticationStateListeners,
                 allowBackgroundAuthentication, mSensorProperties,
                 Utils.getCurrentStrength(mSensorId));
         mScheduler.scheduleClientMonitor(client, mBiometricStateCallback);
@@ -1123,6 +1140,7 @@
         mUdfpsOverlayController = controller;
     }
 
+    // TODO(b/288175061): remove with Flags.FLAG_SIDEFPS_CONTROLLER_REFACTOR
     @Override
     public void setSidefpsController(@NonNull ISidefpsController controller) {
         mSidefpsController = controller;
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21UdfpsMock.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21UdfpsMock.java
index c1a9370..88dae6f 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21UdfpsMock.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21UdfpsMock.java
@@ -41,6 +41,7 @@
 import com.android.internal.R;
 import com.android.server.biometrics.log.BiometricContext;
 import com.android.server.biometrics.sensors.AuthenticationConsumer;
+import com.android.server.biometrics.sensors.AuthenticationStateListeners;
 import com.android.server.biometrics.sensors.BaseClientMonitor;
 import com.android.server.biometrics.sensors.BiometricScheduler;
 import com.android.server.biometrics.sensors.BiometricStateCallback;
@@ -248,6 +249,7 @@
 
     public static Fingerprint21UdfpsMock newInstance(@NonNull Context context,
             @NonNull BiometricStateCallback biometricStateCallback,
+            @NonNull AuthenticationStateListeners authenticationStateListeners,
             @NonNull FingerprintSensorPropertiesInternal sensorProps,
             @NonNull LockoutResetDispatcher lockoutResetDispatcher,
             @NonNull GestureAvailabilityDispatcher gestureAvailabilityDispatcher,
@@ -259,8 +261,9 @@
                 new TestableBiometricScheduler(TAG, handler, gestureAvailabilityDispatcher);
         final MockHalResultController controller =
                 new MockHalResultController(sensorProps.sensorId, context, handler, scheduler);
-        return new Fingerprint21UdfpsMock(context, biometricStateCallback, sensorProps, scheduler,
-                handler, lockoutResetDispatcher, controller, biometricContext);
+        return new Fingerprint21UdfpsMock(context, biometricStateCallback,
+                authenticationStateListeners, sensorProps, scheduler, handler,
+                lockoutResetDispatcher, controller, biometricContext);
     }
 
     private static abstract class FakeFingerRunnable implements Runnable {
@@ -388,14 +391,15 @@
 
     private Fingerprint21UdfpsMock(@NonNull Context context,
             @NonNull BiometricStateCallback biometricStateCallback,
+            @NonNull AuthenticationStateListeners authenticationStateListeners,
             @NonNull FingerprintSensorPropertiesInternal sensorProps,
             @NonNull TestableBiometricScheduler scheduler,
             @NonNull Handler handler,
             @NonNull LockoutResetDispatcher lockoutResetDispatcher,
             @NonNull MockHalResultController controller,
             @NonNull BiometricContext biometricContext) {
-        super(context, biometricStateCallback, sensorProps, scheduler, handler,
-                lockoutResetDispatcher, controller, biometricContext);
+        super(context, biometricStateCallback, authenticationStateListeners, sensorProps, scheduler,
+                handler, lockoutResetDispatcher, controller, biometricContext);
         mScheduler = scheduler;
         mScheduler.init(this);
         mHandler = handler;
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintAuthenticationClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintAuthenticationClient.java
index 9966e91..4c1d4d6 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintAuthenticationClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintAuthenticationClient.java
@@ -16,6 +16,8 @@
 
 package com.android.server.biometrics.sensors.fingerprint.hidl;
 
+import static com.android.systemui.shared.Flags.sidefpsControllerRefactor;
+
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.app.TaskStackListener;
@@ -40,6 +42,7 @@
 import com.android.server.biometrics.log.CallbackWithProbe;
 import com.android.server.biometrics.log.Probe;
 import com.android.server.biometrics.sensors.AuthenticationClient;
+import com.android.server.biometrics.sensors.AuthenticationStateListeners;
 import com.android.server.biometrics.sensors.BiometricNotificationUtils;
 import com.android.server.biometrics.sensors.ClientMonitorCallback;
 import com.android.server.biometrics.sensors.ClientMonitorCallbackConverter;
@@ -68,6 +71,7 @@
     @NonNull private final SensorOverlays mSensorOverlays;
     @NonNull private final FingerprintSensorPropertiesInternal mSensorProps;
     @NonNull private final CallbackWithProbe<Probe> mALSProbeCallback;
+    @NonNull private final AuthenticationStateListeners mAuthenticationStateListeners;
 
     private boolean mIsPointerDown;
 
@@ -81,7 +85,9 @@
             @NonNull TaskStackListener taskStackListener,
             @NonNull LockoutFrameworkImpl lockoutTracker,
             @Nullable IUdfpsOverlayController udfpsOverlayController,
+            // TODO(b/288175061): remove with Flags.FLAG_SIDEFPS_CONTROLLER_REFACTOR
             @Nullable ISidefpsController sidefpsController,
+            @NonNull AuthenticationStateListeners authenticationStateListeners,
             boolean allowBackgroundAuthentication,
             @NonNull FingerprintSensorPropertiesInternal sensorProps,
             @Authenticators.Types int sensorStrength) {
@@ -91,7 +97,12 @@
                 false /* shouldVibrate */, sensorStrength);
         setRequestId(requestId);
         mLockoutFrameworkImpl = lockoutTracker;
-        mSensorOverlays = new SensorOverlays(udfpsOverlayController, sidefpsController);
+        if (sidefpsControllerRefactor()) {
+            mSensorOverlays = new SensorOverlays(udfpsOverlayController);
+        } else {
+            mSensorOverlays = new SensorOverlays(udfpsOverlayController, sidefpsController);
+        }
+        mAuthenticationStateListeners = authenticationStateListeners;
         mSensorProps = sensorProps;
         mALSProbeCallback = getLogger().getAmbientLightProbe(false /* startWithClient */);
     }
@@ -128,6 +139,9 @@
             mState = STATE_STOPPED;
             resetFailedAttempts(getTargetUserId());
             mSensorOverlays.hide(getSensorId());
+            if (sidefpsControllerRefactor()) {
+                mAuthenticationStateListeners.onAuthenticationStopped();
+            }
         } else {
             mState = STATE_STARTED_PAUSED_ATTEMPTED;
             final @LockoutTracker.LockoutMode int lockoutMode =
@@ -141,6 +155,9 @@
                 // controlled by the HAL, the framework must stop the sensor before finishing the
                 // client.
                 mSensorOverlays.hide(getSensorId());
+                if (sidefpsControllerRefactor()) {
+                    mAuthenticationStateListeners.onAuthenticationStopped();
+                }
                 onErrorInternal(errorCode, 0 /* vendorCode */, false /* finish */);
                 cancel();
             }
@@ -156,6 +173,9 @@
         }
 
         mSensorOverlays.hide(getSensorId());
+        if (sidefpsControllerRefactor()) {
+            mAuthenticationStateListeners.onAuthenticationStopped();
+        }
     }
 
     private void resetFailedAttempts(int userId) {
@@ -205,7 +225,10 @@
 
     @Override
     protected void startHalOperation() {
-        mSensorOverlays.show(getSensorId(), getShowOverlayReason(), this);
+        mSensorOverlays.show(getSensorId(), getRequestReason(), this);
+        if (sidefpsControllerRefactor()) {
+            mAuthenticationStateListeners.onAuthenticationStarted(getRequestReason());
+        }
 
         try {
             // GroupId was never used. In fact, groupId is always the same as userId.
@@ -215,6 +238,9 @@
             onError(BiometricFingerprintConstants.FINGERPRINT_ERROR_HW_UNAVAILABLE,
                     0 /* vendorCode */);
             mSensorOverlays.hide(getSensorId());
+            if (sidefpsControllerRefactor()) {
+                mAuthenticationStateListeners.onAuthenticationStopped();
+            }
             mCallback.onClientFinished(this, false /* success */);
         }
     }
@@ -222,6 +248,9 @@
     @Override
     protected void stopHalOperation() {
         mSensorOverlays.hide(getSensorId());
+        if (sidefpsControllerRefactor()) {
+            mAuthenticationStateListeners.onAuthenticationStopped();
+        }
 
         try {
             getFreshDaemon().cancel();
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintDetectClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintDetectClient.java
index 0d7f9f2..6e029d2 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintDetectClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintDetectClient.java
@@ -16,12 +16,14 @@
 
 package com.android.server.biometrics.sensors.fingerprint.hidl;
 
+import static com.android.systemui.shared.Flags.sidefpsControllerRefactor;
+
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.content.Context;
 import android.hardware.biometrics.BiometricAuthenticator;
 import android.hardware.biometrics.BiometricFingerprintConstants;
-import android.hardware.biometrics.BiometricOverlayConstants;
+import android.hardware.biometrics.BiometricRequestConstants;
 import android.hardware.biometrics.fingerprint.PointerContext;
 import android.hardware.biometrics.fingerprint.V2_1.IBiometricsFingerprint;
 import android.hardware.fingerprint.FingerprintAuthenticateOptions;
@@ -71,7 +73,12 @@
                 options.getOpPackageName(), 0 /* cookie */, options.getSensorId(),
                 true /* shouldVibrate */, biometricLogger, biometricContext);
         setRequestId(requestId);
-        mSensorOverlays = new SensorOverlays(udfpsOverlayController, null /* sideFpsController */);
+        if (sidefpsControllerRefactor()) {
+            mSensorOverlays = new SensorOverlays(udfpsOverlayController);
+        } else {
+            mSensorOverlays = new SensorOverlays(
+                    udfpsOverlayController, null /* sideFpsController */);
+        }
         mIsStrongBiometric = isStrongBiometric;
     }
 
@@ -97,7 +104,7 @@
 
     @Override
     protected void startHalOperation() {
-        mSensorOverlays.show(getSensorId(), BiometricOverlayConstants.REASON_AUTH_KEYGUARD,
+        mSensorOverlays.show(getSensorId(), BiometricRequestConstants.REASON_AUTH_KEYGUARD,
                 this);
 
         try {
diff --git a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintEnrollClient.java b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintEnrollClient.java
index 382e7e2..26332ff 100644
--- a/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintEnrollClient.java
+++ b/services/core/java/com/android/server/biometrics/sensors/fingerprint/hidl/FingerprintEnrollClient.java
@@ -16,6 +16,8 @@
 
 package com.android.server.biometrics.sensors.fingerprint.hidl;
 
+import static com.android.systemui.shared.Flags.sidefpsControllerRefactor;
+
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.content.Context;
@@ -34,6 +36,7 @@
 
 import com.android.server.biometrics.log.BiometricContext;
 import com.android.server.biometrics.log.BiometricLogger;
+import com.android.server.biometrics.sensors.AuthenticationStateListeners;
 import com.android.server.biometrics.sensors.BiometricNotificationUtils;
 import com.android.server.biometrics.sensors.BiometricUtils;
 import com.android.server.biometrics.sensors.ClientMonitorCallback;
@@ -58,22 +61,31 @@
 
     @NonNull private final SensorOverlays mSensorOverlays;
     private final @FingerprintManager.EnrollReason int mEnrollReason;
+    @NonNull private final AuthenticationStateListeners mAuthenticationStateListeners;
     private boolean mIsPointerDown;
 
-    FingerprintEnrollClient(@NonNull Context context,
-            @NonNull Supplier<IBiometricsFingerprint> lazyDaemon, @NonNull IBinder token,
-            long requestId, @NonNull ClientMonitorCallbackConverter listener, int userId,
+    FingerprintEnrollClient(
+            @NonNull Context context, @NonNull Supplier<IBiometricsFingerprint> lazyDaemon,
+            @NonNull IBinder token, long requestId,
+            @NonNull ClientMonitorCallbackConverter listener, int userId,
             @NonNull byte[] hardwareAuthToken, @NonNull String owner,
             @NonNull BiometricUtils<Fingerprint> utils, int timeoutSec, int sensorId,
             @NonNull BiometricLogger biometricLogger, @NonNull BiometricContext biometricContext,
             @Nullable IUdfpsOverlayController udfpsOverlayController,
+            // TODO(b/288175061): remove with Flags.FLAG_SIDEFPS_CONTROLLER_REFACTOR
             @Nullable ISidefpsController sidefpsController,
+            @NonNull AuthenticationStateListeners authenticationStateListeners,
             @FingerprintManager.EnrollReason int enrollReason) {
         super(context, lazyDaemon, token, listener, userId, hardwareAuthToken, owner, utils,
                 timeoutSec, sensorId, true /* shouldVibrate */, biometricLogger,
                 biometricContext);
         setRequestId(requestId);
-        mSensorOverlays = new SensorOverlays(udfpsOverlayController, sidefpsController);
+        if (sidefpsControllerRefactor()) {
+            mSensorOverlays = new SensorOverlays(udfpsOverlayController);
+        } else {
+            mSensorOverlays = new SensorOverlays(udfpsOverlayController, sidefpsController);
+        }
+        mAuthenticationStateListeners = authenticationStateListeners;
 
         mEnrollReason = enrollReason;
         if (enrollReason == FingerprintManager.ENROLL_FIND_SENSOR) {
@@ -110,8 +122,12 @@
 
     @Override
     protected void startHalOperation() {
-        mSensorOverlays.show(getSensorId(), getOverlayReasonFromEnrollReason(mEnrollReason),
+        mSensorOverlays.show(getSensorId(), getRequestReasonFromEnrollReason(mEnrollReason),
                 this);
+        if (sidefpsControllerRefactor()) {
+            mAuthenticationStateListeners.onAuthenticationStarted(
+                    getRequestReasonFromEnrollReason(mEnrollReason));
+        }
 
         BiometricNotificationUtils.cancelBadCalibrationNotification(getContext());
         try {
@@ -122,6 +138,9 @@
             onError(BiometricFingerprintConstants.FINGERPRINT_ERROR_HW_UNAVAILABLE,
                     0 /* vendorCode */);
             mSensorOverlays.hide(getSensorId());
+            if (sidefpsControllerRefactor()) {
+                mAuthenticationStateListeners.onAuthenticationStopped();
+            }
             mCallback.onClientFinished(this, false /* success */);
         }
     }
@@ -129,6 +148,9 @@
     @Override
     protected void stopHalOperation() {
         mSensorOverlays.hide(getSensorId());
+        if (sidefpsControllerRefactor()) {
+            mAuthenticationStateListeners.onAuthenticationStopped();
+        }
 
         try {
             getFreshDaemon().cancel();
@@ -149,6 +171,9 @@
 
         if (remaining == 0) {
             mSensorOverlays.hide(getSensorId());
+            if (sidefpsControllerRefactor()) {
+                mAuthenticationStateListeners.onAuthenticationStopped();
+            }
         }
     }
 
@@ -170,6 +195,9 @@
         super.onError(errorCode, vendorCode);
 
         mSensorOverlays.hide(getSensorId());
+        if (sidefpsControllerRefactor()) {
+            mAuthenticationStateListeners.onAuthenticationStopped();
+        }
     }
 
     @Override
diff --git a/services/core/java/com/android/server/content/SyncManager.java b/services/core/java/com/android/server/content/SyncManager.java
index 1e5e147..df179a9 100644
--- a/services/core/java/com/android/server/content/SyncManager.java
+++ b/services/core/java/com/android/server/content/SyncManager.java
@@ -1294,8 +1294,8 @@
         if (android.content.pm.Flags.stayStopped()) {
             try {
                 return mPackageManagerInternal.isPackageStopped(packageName, userId);
-            } catch (IllegalArgumentException e) {
-                Log.d(TAG, "Couldn't determine stopped state for unknown package: " + packageName);
+            } catch (NameNotFoundException e) {
+                Log.e(TAG, "Couldn't determine stopped state for unknown package: " + packageName);
             }
         }
         return false;
diff --git a/services/core/java/com/android/server/devicestate/DeviceStateManagerService.java b/services/core/java/com/android/server/devicestate/DeviceStateManagerService.java
index dff14b5..6ec6a12 100644
--- a/services/core/java/com/android/server/devicestate/DeviceStateManagerService.java
+++ b/services/core/java/com/android/server/devicestate/DeviceStateManagerService.java
@@ -327,7 +327,7 @@
     Optional<DeviceState> getOverrideState() {
         synchronized (mLock) {
             if (mActiveOverride.isPresent()) {
-                return getStateLocked(mActiveOverride.get().getRequestedState());
+                return getStateLocked(mActiveOverride.get().getRequestedStateIdentifier());
             }
             return Optional.empty();
         }
@@ -342,7 +342,7 @@
     Optional<DeviceState> getOverrideBaseState() {
         synchronized (mLock) {
             if (mActiveBaseStateOverride.isPresent()) {
-                return getStateLocked(mActiveBaseStateOverride.get().getRequestedState());
+                return getStateLocked(mActiveBaseStateOverride.get().getRequestedStateIdentifier());
             }
             return Optional.empty();
         }
@@ -499,6 +499,7 @@
      * @return {@code true} if the pending state has changed as a result of this call, {@code false}
      * otherwise.
      */
+    @GuardedBy("mLock")
     private boolean updatePendingStateLocked() {
         if (mPendingState.isPresent()) {
             // Have pending state, can not configure a new state until the state is committed.
@@ -507,7 +508,8 @@
 
         final DeviceState stateToConfigure;
         if (mActiveOverride.isPresent()) {
-            stateToConfigure = getStateLocked(mActiveOverride.get().getRequestedState()).get();
+            stateToConfigure = getStateLocked(
+                    mActiveOverride.get().getRequestedStateIdentifier()).get();
         } else if (mBaseState.isPresent()
                 && isSupportedStateLocked(mBaseState.get().getIdentifier())) {
             // Base state could have recently become unsupported after a change in supported states.
@@ -599,7 +601,7 @@
             // requested state is committed.
             OverrideRequest activeRequest = mActiveOverride.orElse(null);
             if (activeRequest != null
-                    && activeRequest.getRequestedState() == newState.getIdentifier()) {
+                    && activeRequest.getRequestedStateIdentifier() == newState.getIdentifier()) {
                 ProcessRecord processRecord = mProcessRecords.get(activeRequest.getPid());
                 if (processRecord != null) {
                     processRecord.notifyRequestActiveAsync(activeRequest.getToken());
@@ -666,21 +668,21 @@
                 case STATUS_ACTIVE:
                     mActiveOverride = Optional.of(request);
                     mDeviceStateNotificationController.showStateActiveNotificationIfNeeded(
-                            request.getRequestedState(), request.getUid());
+                            request.getRequestedStateIdentifier(), request.getUid());
                     break;
                 case STATUS_CANCELED:
                     if (mActiveOverride.isPresent() && mActiveOverride.get() == request) {
                         mActiveOverride = Optional.empty();
                         mDeviceStateNotificationController.cancelNotification(
-                                request.getRequestedState());
+                                request.getRequestedStateIdentifier());
                         if ((flags & FLAG_THERMAL_CRITICAL) == FLAG_THERMAL_CRITICAL) {
                             mDeviceStateNotificationController
                                     .showThermalCriticalNotificationIfNeeded(
-                                            request.getRequestedState());
+                                            request.getRequestedStateIdentifier());
                         } else if ((flags & FLAG_POWER_SAVE_ENABLED) == FLAG_POWER_SAVE_ENABLED) {
                             mDeviceStateNotificationController
                                     .showPowerSaveNotificationIfNeeded(
-                                            request.getRequestedState());
+                                            request.getRequestedStateIdentifier());
                         }
                     }
                     break;
@@ -723,7 +725,7 @@
      */
     @GuardedBy("mLock")
     private void enableBaseStateRequestLocked(OverrideRequest request) {
-        setBaseState(request.getRequestedState());
+        setBaseState(request.getRequestedStateIdentifier());
         mActiveBaseStateOverride = Optional.of(request);
         ProcessRecord processRecord = mProcessRecords.get(request.getPid());
         processRecord.notifyRequestActiveAsync(request.getToken());
@@ -762,6 +764,11 @@
         synchronized (mLock) {
             mProcessRecords.remove(processRecord.mPid);
             mOverrideRequestController.handleProcessDied(processRecord.mPid);
+
+            if (shouldCancelOverrideRequestWhenRequesterNotOnTop()) {
+                OverrideRequest request = mActiveOverride.get();
+                mOverrideRequestController.cancelRequest(request);
+            }
         }
     }
 
@@ -787,7 +794,7 @@
             }
 
             OverrideRequest request = new OverrideRequest(token, callingPid, callingUid,
-                    state, flags, OVERRIDE_REQUEST_TYPE_EMULATED_STATE);
+                    deviceState.get(), flags, OVERRIDE_REQUEST_TYPE_EMULATED_STATE);
 
             // If we don't have the CONTROL_DEVICE_STATE permission, we want to show the overlay
             if (!hasControlDeviceStatePermission && mRearDisplayState != null
@@ -848,7 +855,7 @@
             }
 
             OverrideRequest request = new OverrideRequest(token, callingPid, callingUid,
-                    state, flags, OVERRIDE_REQUEST_TYPE_BASE_STATE);
+                    deviceState.get(), flags, OVERRIDE_REQUEST_TYPE_BASE_STATE);
             mOverrideRequestController.addBaseStateRequest(request);
         }
     }
@@ -1318,7 +1325,7 @@
         if (mActiveOverride.isEmpty()) {
             return false;
         }
-        int identifier = mActiveOverride.get().getRequestedState();
+        int identifier = mActiveOverride.get().getRequestedStateIdentifier();
         DeviceState deviceState = mDeviceStates.get(identifier);
         return deviceState.hasFlag(DeviceState.FLAG_CANCEL_WHEN_REQUESTER_NOT_ON_TOP);
     }
diff --git a/services/core/java/com/android/server/devicestate/OverrideRequest.java b/services/core/java/com/android/server/devicestate/OverrideRequest.java
index 74cf184..20485c1 100644
--- a/services/core/java/com/android/server/devicestate/OverrideRequest.java
+++ b/services/core/java/com/android/server/devicestate/OverrideRequest.java
@@ -17,6 +17,7 @@
 package com.android.server.devicestate;
 
 import android.annotation.IntDef;
+import android.annotation.NonNull;
 import android.hardware.devicestate.DeviceStateRequest;
 import android.os.IBinder;
 
@@ -32,7 +33,8 @@
     private final IBinder mToken;
     private final int mPid;
     private final int mUid;
-    private final int mRequestedState;
+    @NonNull
+    private final DeviceState mRequestedState;
     @DeviceStateRequest.RequestFlags
     private final int mFlags;
     @OverrideRequestType
@@ -69,7 +71,7 @@
     @Retention(RetentionPolicy.SOURCE)
     public @interface OverrideRequestType {}
 
-    OverrideRequest(IBinder token, int pid, int uid, int requestedState,
+    OverrideRequest(IBinder token, int pid, int uid, @NonNull DeviceState requestedState,
             @DeviceStateRequest.RequestFlags int flags, @OverrideRequestType int requestType) {
         mToken = token;
         mPid = pid;
@@ -91,10 +93,15 @@
         return mUid;
     }
 
-    int getRequestedState() {
+    @NonNull
+    DeviceState getRequestedDeviceState() {
         return mRequestedState;
     }
 
+    int getRequestedStateIdentifier() {
+        return mRequestedState.getIdentifier();
+    }
+
     @DeviceStateRequest.RequestFlags
     int getFlags() {
         return mFlags;
diff --git a/services/core/java/com/android/server/devicestate/OverrideRequestController.java b/services/core/java/com/android/server/devicestate/OverrideRequestController.java
index 46f0bc0..f5f2fa8 100644
--- a/services/core/java/com/android/server/devicestate/OverrideRequestController.java
+++ b/services/core/java/com/android/server/devicestate/OverrideRequestController.java
@@ -204,6 +204,12 @@
         }
 
         if (mRequest != null && mRequest.getPid() == pid) {
+            if (mRequest.getRequestedDeviceState().hasFlag(
+                    DeviceState.FLAG_CANCEL_WHEN_REQUESTER_NOT_ON_TOP)) {
+                cancelCurrentRequestLocked();
+                return;
+            }
+
             if (mStickyRequestsAllowed) {
                 // Do not cancel the requests now because sticky requests are allowed. These
                 // requests will be cancelled on a call to cancelStickyRequests().
@@ -219,7 +225,7 @@
      * listener of all changes to request status as a result of this change.
      */
     void handleBaseStateChanged(int state) {
-        if (mBaseStateRequest != null && state != mBaseStateRequest.getRequestedState()) {
+        if (mBaseStateRequest != null && state != mBaseStateRequest.getRequestedStateIdentifier()) {
             cancelBaseStateOverrideRequest();
         }
         if (mRequest == null) {
@@ -246,11 +252,12 @@
         flags |= isThermalCritical ? FLAG_THERMAL_CRITICAL : 0;
         flags |= isPowerSaveEnabled ? FLAG_POWER_SAVE_ENABLED : 0;
         if (mBaseStateRequest != null && !contains(newSupportedStates,
-                mBaseStateRequest.getRequestedState())) {
+                mBaseStateRequest.getRequestedStateIdentifier())) {
             cancelCurrentBaseStateRequestLocked(flags);
         }
 
-        if (mRequest != null && !contains(newSupportedStates, mRequest.getRequestedState())) {
+        if (mRequest != null && !contains(newSupportedStates,
+                mRequest.getRequestedStateIdentifier())) {
             cancelCurrentRequestLocked(flags);
         }
     }
@@ -262,7 +269,7 @@
         pw.println("Override Request active: " + requestActive);
         if (requestActive) {
             pw.println("Request: mPid=" + overrideRequest.getPid()
-                    + ", mRequestedState=" + overrideRequest.getRequestedState()
+                    + ", mRequestedState=" + overrideRequest.getRequestedStateIdentifier()
                     + ", mFlags=" + overrideRequest.getFlags()
                     + ", mStatus=" + statusToString(STATUS_ACTIVE));
         }
diff --git a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java
index 1687157..5831b29 100644
--- a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java
+++ b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java
@@ -194,10 +194,14 @@
             addAndStartAction(new RequestActiveSourceAction(this, new IHdmiControlCallback.Stub() {
                 @Override
                 public void onComplete(int result) {
-                    if (result != HdmiControlManager.RESULT_SUCCESS) {
+                    if (!mService.getLocalActiveSource().isValid()
+                            && result != HdmiControlManager.RESULT_SUCCESS) {
                         mService.sendCecCommand(HdmiCecMessageBuilder.buildActiveSource(
                                 getDeviceInfo().getLogicalAddress(),
                                 getDeviceInfo().getPhysicalAddress()));
+                        updateActiveSource(getDeviceInfo().getLogicalAddress(),
+                                getDeviceInfo().getPhysicalAddress(),
+                                "RequestActiveSourceAction#finishWithCallback()");
                     }
                 }
             }));
@@ -257,6 +261,7 @@
         if (isAlreadyActiveSource(targetDevice, targetAddress, callback)) {
             return;
         }
+        removeAction(RequestActiveSourceAction.class);
         if (targetAddress == Constants.ADDR_INTERNAL) {
             handleSelectInternalSource();
             // Switching to internal source is always successful even when CEC control is disabled.
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerInternal.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerInternal.java
index f526dbe..4089a81 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodManagerInternal.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerInternal.java
@@ -215,10 +215,20 @@
     /**
      * Switch the keyboard layout in response to a keyboard shortcut.
      *
-     * @param direction {@code 1} to switch to the next subtype, {@code -1} to switch to the
-     *                  previous subtype
+     * @param direction         {@code 1} to switch to the next subtype, {@code -1} to switch to the
+     *                          previous subtype
+     * @param displayId         the display to which the keyboard layout switch shortcut is
+     *                          dispatched. Note that there is no guarantee that an IME is
+     *                          associated with this display. This is more or less than a hint for
+     *                          cases when no IME is running for the given targetWindowToken. There
+     *                          is a longstanding discussion whether we should allow users to
+     *                          rotate keyboard layout even when there is no edit field, and this
+     *                          displayID would be helpful for such a situation.
+     * @param targetWindowToken the window token to which other keys are being sent while handling
+     *                          this shortcut.
      */
-    public abstract void switchKeyboardLayout(int direction);
+    public abstract void onSwitchKeyboardLayoutShortcut(int direction, int displayId,
+            IBinder targetWindowToken);
 
     /**
      * Returns true if any InputConnection is currently active.
@@ -314,7 +324,8 @@
                 }
 
                 @Override
-                public void switchKeyboardLayout(int direction) {
+                public void onSwitchKeyboardLayoutShortcut(int direction, int displayId,
+                        IBinder targetWindowToken) {
                 }
 
                 @Override
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
index 30e9f5b..c440a64 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodManagerService.java
@@ -5763,7 +5763,8 @@
         }
 
         @Override
-        public void switchKeyboardLayout(int direction) {
+        public void onSwitchKeyboardLayoutShortcut(int direction, int displayId,
+                IBinder targetWindowToken) {
             synchronized (ImfLock.class) {
                 switchKeyboardLayoutLocked(direction);
             }
diff --git a/services/core/java/com/android/server/integrity/AppIntegrityManagerServiceImpl.java b/services/core/java/com/android/server/integrity/AppIntegrityManagerServiceImpl.java
index ec858ee2..f2dcba4 100644
--- a/services/core/java/com/android/server/integrity/AppIntegrityManagerServiceImpl.java
+++ b/services/core/java/com/android/server/integrity/AppIntegrityManagerServiceImpl.java
@@ -59,6 +59,7 @@
 
 import com.android.internal.R;
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.pm.pkg.parsing.ParsingPackageUtils;
 import com.android.internal.util.ArrayUtils;
 import com.android.internal.util.FrameworkStatsLog;
 import com.android.server.LocalServices;
@@ -67,7 +68,6 @@
 import com.android.server.integrity.model.RuleMetadata;
 import com.android.server.pm.PackageManagerServiceUtils;
 import com.android.server.pm.parsing.PackageParser2;
-import com.android.server.pm.pkg.parsing.ParsingPackageUtils;
 
 import java.io.ByteArrayInputStream;
 import java.io.File;
diff --git a/services/core/java/com/android/server/location/contexthub/IContextHubWrapper.java b/services/core/java/com/android/server/location/contexthub/IContextHubWrapper.java
index bab3cbe..9c27c22 100644
--- a/services/core/java/com/android/server/location/contexthub/IContextHubWrapper.java
+++ b/services/core/java/com/android/server/location/contexthub/IContextHubWrapper.java
@@ -18,6 +18,7 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.hardware.contexthub.HostEndpointInfo;
+import android.hardware.contexthub.MessageDeliveryStatus;
 import android.hardware.contexthub.NanSessionRequest;
 import android.hardware.contexthub.V1_0.ContextHub;
 import android.hardware.contexthub.V1_0.ContextHubMsg;
@@ -467,6 +468,11 @@
                 // TODO(271471342): Implement
             }
 
+            public void handleMessageDeliveryStatus(char hostEndPointId,
+                    MessageDeliveryStatus messageDeliveryStatus) {
+                // TODO(b/312417087): Implement reliable message support
+            }
+
             public byte[] getUuid() {
                 return UUID;
             }
diff --git a/services/core/java/com/android/server/locksettings/LockSettingsService.java b/services/core/java/com/android/server/locksettings/LockSettingsService.java
index 42c2548..0c2eee5 100644
--- a/services/core/java/com/android/server/locksettings/LockSettingsService.java
+++ b/services/core/java/com/android/server/locksettings/LockSettingsService.java
@@ -209,7 +209,7 @@
  *   <li>Protect each user's data using their SP.  For example, use the SP to encrypt/decrypt the
  *   user's credential-encrypted (CE) key for file-based encryption (FBE).</li>
  *
- *   <li>Generate, protect, and use profile passwords for managed profiles.</li>
+ *   <li>Generate, protect, and use unified profile passwords.</li>
  *
  *   <li>Support unlocking the SP by alternative means: resume-on-reboot (reboot escrow) for easier
  *   OTA updates, and escrow tokens when set up by the Device Policy Controller (DPC).</li>
@@ -287,7 +287,7 @@
 
     private final java.security.KeyStore mJavaKeyStore;
     private final RecoverableKeyStoreManager mRecoverableKeyStoreManager;
-    private ManagedProfilePasswordCache mManagedProfilePasswordCache;
+    private final UnifiedProfilePasswordCache mUnifiedProfilePasswordCache;
 
     private final RebootEscrowManager mRebootEscrowManager;
 
@@ -404,7 +404,8 @@
         for (int i = 0; i < newPasswordChars.length; i++) {
             newPassword[i] = (byte) newPasswordChars[i];
         }
-        LockscreenCredential credential = LockscreenCredential.createManagedPassword(newPassword);
+        LockscreenCredential credential =
+                LockscreenCredential.createUnifiedProfilePassword(newPassword);
         Arrays.fill(newPasswordChars, '\u0000');
         Arrays.fill(newPassword, (byte) 0);
         Arrays.fill(randomLockSeed, (byte) 0);
@@ -424,7 +425,7 @@
         if (!isCredentialSharableWithParent(profileUserId)) {
             return;
         }
-        // Do not tie profile when work challenge is enabled
+        // Do not tie profile when separate challenge is enabled
         if (getSeparateProfileChallengeEnabledInternal(profileUserId)) {
             return;
         }
@@ -462,7 +463,7 @@
             setLockCredentialInternal(unifiedProfilePassword, profileUserPassword, profileUserId,
                     /* isLockTiedToParent= */ true);
             tieProfileLockToParent(profileUserId, parent.id, unifiedProfilePassword);
-            mManagedProfilePasswordCache.storePassword(profileUserId, unifiedProfilePassword,
+            mUnifiedProfilePasswordCache.storePassword(profileUserId, unifiedProfilePassword,
                     parentSid);
         }
     }
@@ -620,9 +621,9 @@
             }
         }
 
-        public @NonNull ManagedProfilePasswordCache getManagedProfilePasswordCache(
+        public @NonNull UnifiedProfilePasswordCache getUnifiedProfilePasswordCache(
                 java.security.KeyStore ks) {
-            return new ManagedProfilePasswordCache(ks);
+            return new UnifiedProfilePasswordCache(ks);
         }
 
         public boolean isHeadlessSystemUserMode() {
@@ -665,7 +666,7 @@
         mGatekeeperPasswords = new LongSparseArray<>();
 
         mSpManager = injector.getSyntheticPasswordManager(mStorage);
-        mManagedProfilePasswordCache = injector.getManagedProfilePasswordCache(mJavaKeyStore);
+        mUnifiedProfilePasswordCache = injector.getUnifiedProfilePasswordCache(mJavaKeyStore);
         mBiometricDeferredQueue = new BiometricDeferredQueue(mSpManager, mHandler);
 
         mRebootEscrowManager = injector.getRebootEscrowManager(new RebootEscrowCallbacks(),
@@ -689,8 +690,8 @@
     }
 
     /**
-     * If the account is credential-encrypted, show notification requesting the user to unlock the
-     * device.
+     * If the user is a managed profile whose credential-encrypted storage is locked, show a
+     * notification requesting the user to unlock the device.
      */
     private void maybeShowEncryptionNotificationForUser(@UserIdInt int userId, String reason) {
         final UserInfo user = mUserManager.getUserInfo(userId);
@@ -846,7 +847,7 @@
         mHandler.post(new Runnable() {
             @Override
             public void run() {
-                // Hide notification first, as tie managed profile lock takes time
+                // Hide notification first, as tie profile lock takes time
                 hideEncryptionNotification(new UserHandle(userId));
 
                 if (isCredentialSharableWithParent(userId)) {
@@ -1458,13 +1459,13 @@
 
         cipher.init(Cipher.DECRYPT_MODE, decryptionKey, new GCMParameterSpec(128, iv));
         decryptionResult = cipher.doFinal(encryptedPassword);
-        LockscreenCredential credential = LockscreenCredential.createManagedPassword(
+        LockscreenCredential credential = LockscreenCredential.createUnifiedProfilePassword(
                 decryptionResult);
         Arrays.fill(decryptionResult, (byte) 0);
         try {
             long parentSid = getGateKeeperService().getSecureUserId(
                     mUserManager.getProfileParent(userId).id);
-            mManagedProfilePasswordCache.storePassword(userId, credential, parentSid);
+            mUnifiedProfilePasswordCache.storePassword(userId, credential, parentSid);
         } catch (RemoteException e) {
             Slogf.w(TAG, "Failed to talk to GateKeeper service", e);
         }
@@ -1550,7 +1551,7 @@
                         // so it goes into the cache
                         getDecryptedPasswordForTiedProfile(profile.id);
                     } catch (GeneralSecurityException | IOException e) {
-                        Slog.d(TAG, "Cache work profile password failed", e);
+                        Slog.d(TAG, "Cache unified profile password failed", e);
                     }
                 }
             }
@@ -1604,19 +1605,19 @@
     }
 
     /**
-     * Synchronize all profile's work challenge of the given user if it's unified: tie or clear them
+     * Synchronize all profile's challenge of the given user if it's unified: tie or clear them
      * depending on the parent user's secure state.
      *
-     * When clearing tied work challenges, a pre-computed password table for profiles are required,
-     * since changing password for profiles requires existing password, and existing passwords can
-     * only be computed before the parent user's password is cleared.
+     * When clearing tied challenges, a pre-computed password table for profiles are required, since
+     * changing password for profiles requires existing password, and existing passwords can only be
+     * computed before the parent user's password is cleared.
      *
      * Strictly this is a recursive function, since setLockCredentialInternal ends up calling this
      * method again on profiles. However the recursion is guaranteed to terminate as this method
      * terminates when the user is a profile that shares lock credentials with parent.
      * (e.g. managed and clone profile).
      */
-    private void synchronizeUnifiedWorkChallengeForProfiles(int userId,
+    private void synchronizeUnifiedChallengeForProfiles(int userId,
             Map<Integer, LockscreenCredential> profilePasswordMap) {
         if (isCredentialSharableWithParent(userId)) {
             return;
@@ -1635,7 +1636,7 @@
                     tieProfileLockIfNecessary(profileUserId,
                             LockscreenCredential.createNone());
                 } else {
-                    // We use cached work profile password computed before clearing the parent's
+                    // We use cached profile password computed before clearing the parent's
                     // credential, otherwise they get lost
                     if (profilePasswordMap != null
                             && profilePasswordMap.containsKey(profileUserId)) {
@@ -1777,7 +1778,7 @@
                 notifyPasswordChanged(credential, userId);
             }
             if (isCredentialSharableWithParent(userId)) {
-                // Make sure the profile doesn't get locked straight after setting work challenge.
+                // Make sure the profile doesn't get locked straight after setting challenge.
                 setDeviceUnlockedForUser(userId);
             }
             notifySeparateProfileChallengeChanged(userId);
@@ -2368,7 +2369,7 @@
         }
 
         try {
-            // Unlock work profile, and work profile with unified lock must use password only
+            // Unlock profile with unified lock
             return doVerifyCredential(getDecryptedPasswordForTiedProfile(userId),
                     userId, null /* progressCallback */, flags);
         } catch (UnrecoverableKeyException | InvalidKeyException | KeyStoreException
@@ -2492,7 +2493,7 @@
         mStrongAuth.removeUser(userId);
 
         AndroidKeyStoreMaintenance.onUserRemoved(userId);
-        mManagedProfilePasswordCache.removePassword(userId);
+        mUnifiedProfilePasswordCache.removePassword(userId);
 
         gateKeeperClearSecureUserId(userId);
         removeKeystoreProfileKey(userId);
@@ -2982,7 +2983,7 @@
                 credential, sp, userId);
         final Map<Integer, LockscreenCredential> profilePasswords;
         if (!credential.isNone()) {
-            // not needed by synchronizeUnifiedWorkChallengeForProfiles()
+            // not needed by synchronizeUnifiedChallengeForProfiles()
             profilePasswords = null;
 
             if (!mSpManager.hasSidForUser(userId)) {
@@ -2993,8 +2994,8 @@
                 }
             }
         } else {
-            // Cache all profile password if they use unified work challenge. This will later be
-            // used to clear the profile's password in synchronizeUnifiedWorkChallengeForProfiles()
+            // Cache all profile password if they use unified challenge. This will later be used to
+            // clear the profile's password in synchronizeUnifiedChallengeForProfiles().
             profilePasswords = getDecryptedPasswordsForAllTiedProfiles(userId);
 
             mSpManager.clearSidForUser(userId);
@@ -3010,10 +3011,10 @@
         }
         setCurrentLskfBasedProtectorId(newProtectorId, userId);
         LockPatternUtils.invalidateCredentialTypeCache();
-        synchronizeUnifiedWorkChallengeForProfiles(userId, profilePasswords);
+        synchronizeUnifiedChallengeForProfiles(userId, profilePasswords);
 
         setUserPasswordMetrics(credential, userId);
-        mManagedProfilePasswordCache.removePassword(userId);
+        mUnifiedProfilePasswordCache.removePassword(userId);
         if (savedCredentialType != CREDENTIAL_TYPE_NONE) {
             mSpManager.destroyAllWeakTokenBasedProtectors(userId);
         }
@@ -3114,7 +3115,7 @@
                 try {
                     currentCredential = getDecryptedPasswordForTiedProfile(userId);
                 } catch (Exception e) {
-                    Slog.e(TAG, "Failed to get work profile credential", e);
+                    Slog.e(TAG, "Failed to get unified profile password", e);
                     return null;
                 }
             }
@@ -3284,7 +3285,7 @@
     @Override
     public boolean tryUnlockWithCachedUnifiedChallenge(int userId) {
         checkPasswordReadPermission();
-        try (LockscreenCredential cred = mManagedProfilePasswordCache.retrievePassword(userId)) {
+        try (LockscreenCredential cred = mUnifiedProfilePasswordCache.retrievePassword(userId)) {
             if (cred == null) {
                 return false;
             }
@@ -3296,7 +3297,7 @@
     @Override
     public void removeCachedUnifiedChallenge(int userId) {
         checkWritePermission();
-        mManagedProfilePasswordCache.removePassword(userId);
+        mUnifiedProfilePasswordCache.removePassword(userId);
     }
 
     static String timestampToString(long timestamp) {
diff --git a/services/core/java/com/android/server/locksettings/LockSettingsStorage.java b/services/core/java/com/android/server/locksettings/LockSettingsStorage.java
index 1e8b387..6d123cc 100644
--- a/services/core/java/com/android/server/locksettings/LockSettingsStorage.java
+++ b/services/core/java/com/android/server/locksettings/LockSettingsStorage.java
@@ -501,10 +501,10 @@
         final UserInfo parentInfo = um.getProfileParent(userId);
 
         if (parentInfo == null) {
-            // This user owns its lock settings files - safe to delete them
+            // Delete files specific to non-profile users.
             deleteFile(getRebootEscrowFile(userId));
         } else {
-            // Managed profile
+            // Delete files specific to profile users.
             removeChildProfileLock(userId);
         }
 
diff --git a/services/core/java/com/android/server/locksettings/ManagedProfilePasswordCache.java b/services/core/java/com/android/server/locksettings/UnifiedProfilePasswordCache.java
similarity index 84%
rename from services/core/java/com/android/server/locksettings/ManagedProfilePasswordCache.java
rename to services/core/java/com/android/server/locksettings/UnifiedProfilePasswordCache.java
index 1298fe8f..21caf76 100644
--- a/services/core/java/com/android/server/locksettings/ManagedProfilePasswordCache.java
+++ b/services/core/java/com/android/server/locksettings/UnifiedProfilePasswordCache.java
@@ -43,30 +43,31 @@
 import javax.crypto.spec.GCMParameterSpec;
 
 /**
- * Caches *unified* work challenge for managed profiles. The cached credential is encrypted using
- * a keystore key auth-bound to the parent user's lockscreen credential, similar to how unified
- * work challenge is normally secured.
- *
- * <p> The cache is filled whenever the managed profile's unified challenge is created or derived
- * (as part of the parent user's credential verification flow). It's removed when the profile is
- * deleted or a (separate) lockscreen credential is explicitly set on the profile. There is also
- * an ADB command to evict the cache "cmd lock_settings remove-cache --user X", to assist
- * development and testing.
-
- * <p> The encrypted credential is stored in-memory only so the cache does not persist across
- * reboots.
+ * An in-memory cache for unified profile passwords.  A "unified profile password" is the random
+ * password that the system automatically generates and manages for each profile that uses a unified
+ * challenge and where the parent user has a secure lock screen.
+ * <p>
+ * Each password in this cache is encrypted by a Keystore key that is auth-bound to the parent user.
+ * This is very similar to how the password is protected on-disk, but the in-memory cache uses a
+ * much longer timeout on the keys: 7 days instead of 30 seconds.  This enables use cases like
+ * unpausing work apps without requiring authentication as frequently.
+ * <p>
+ * Unified profile passwords are cached when they are created, or when they are decrypted as part of
+ * the parent user's LSKF verification flow.  They are removed when the profile is deleted or when a
+ * separate challenge is explicitly set on the profile.  There is also an ADB command to evict a
+ * cached password, "locksettings remove-cache --user X", to assist development and testing.
  */
 @VisibleForTesting // public visibility is needed for Mockito
-public class ManagedProfilePasswordCache {
+public class UnifiedProfilePasswordCache {
 
-    private static final String TAG = "ManagedProfilePasswordCache";
+    private static final String TAG = "UnifiedProfilePasswordCache";
     private static final int KEY_LENGTH = 256;
     private static final int CACHE_TIMEOUT_SECONDS = (int) TimeUnit.DAYS.toSeconds(7);
 
     private final SparseArray<byte[]> mEncryptedPasswords = new SparseArray<>();
     private final KeyStore mKeyStore;
 
-    public ManagedProfilePasswordCache(KeyStore keyStore) {
+    public UnifiedProfilePasswordCache(KeyStore keyStore) {
         mKeyStore = keyStore;
     }
 
@@ -151,7 +152,8 @@
                 Slog.d(TAG, "Cannot decrypt", e);
                 return null;
             }
-            LockscreenCredential result = LockscreenCredential.createManagedPassword(credential);
+            LockscreenCredential result =
+                    LockscreenCredential.createUnifiedProfilePassword(credential);
             Arrays.fill(credential, (byte) 0);
             return result;
         }
diff --git a/services/core/java/com/android/server/media/AudioPoliciesDeviceRouteController.java b/services/core/java/com/android/server/media/AudioPoliciesDeviceRouteController.java
index 173c452..8504495 100644
--- a/services/core/java/com/android/server/media/AudioPoliciesDeviceRouteController.java
+++ b/services/core/java/com/android/server/media/AudioPoliciesDeviceRouteController.java
@@ -330,8 +330,11 @@
                 TextUtils.isEmpty(address)
                         ? null
                         : mBluetoothRouteController.getRouteIdForBluetoothAddress(address);
-        return createMediaRoute2Info(
-                routeId, audioDeviceInfo.getType(), audioDeviceInfo.getProductName(), address);
+        // We use the name from the port instead AudioDeviceInfo#getProductName because the latter
+        // replaces empty names with the name of the device (example: Pixel 8). In that case we want
+        // to derive a name ourselves from the type instead.
+        String deviceName = audioDeviceInfo.getPort().name();
+        return createMediaRoute2Info(routeId, audioDeviceInfo.getType(), deviceName, address);
     }
 
     /**
@@ -339,8 +342,8 @@
      *
      * @param routeId A route id, or null to use an id pre-defined for the given {@code type}.
      * @param audioDeviceInfoType The type as obtained from {@link AudioDeviceInfo#getType}.
-     * @param productName The product name as obtained from {@link
-     *     AudioDeviceInfo#getProductName()}, or null to use a predefined name for the given {@code
+     * @param deviceName A human readable name to populate the route's {@link
+     *     MediaRoute2Info#getName name}, or null to use a predefined name for the given {@code
      *     type}.
      * @param address The type as obtained from {@link AudioDeviceInfo#getAddress()} or {@link
      *     BluetoothDevice#getAddress()}.
@@ -350,7 +353,7 @@
     private MediaRoute2Info createMediaRoute2Info(
             @Nullable String routeId,
             int audioDeviceInfoType,
-            @Nullable CharSequence productName,
+            @Nullable CharSequence deviceName,
             @Nullable String address) {
         SystemRouteInfo systemRouteInfo =
                 AUDIO_DEVICE_INFO_TYPE_TO_ROUTE_INFO.get(audioDeviceInfoType);
@@ -359,7 +362,7 @@
             // earpiece.
             return null;
         }
-        CharSequence humanReadableName = productName;
+        CharSequence humanReadableName = deviceName;
         if (TextUtils.isEmpty(humanReadableName)) {
             humanReadableName = mContext.getResources().getText(systemRouteInfo.mNameResource);
         }
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index a62d8b8..66a9740 100755
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -5406,6 +5406,7 @@
         private void validateAutomaticZenRule(AutomaticZenRule rule) {
             Objects.requireNonNull(rule, "automaticZenRule is null");
             Objects.requireNonNull(rule.getName(), "Name is null");
+            rule.validate();
             if (rule.getOwner() == null
                     && rule.getConfigurationActivity() == null) {
                 throw new NullPointerException(
@@ -5463,6 +5464,7 @@
         public void setAutomaticZenRuleState(String id, Condition condition) {
             Objects.requireNonNull(id, "id is null");
             Objects.requireNonNull(condition, "Condition is null");
+            condition.validate();
 
             enforcePolicyAccess(Binder.getCallingUid(), "setAutomaticZenRuleState");
 
diff --git a/services/core/java/com/android/server/notification/ZenModeHelper.java b/services/core/java/com/android/server/notification/ZenModeHelper.java
index 7ec94c3..3f8b595 100644
--- a/services/core/java/com/android/server/notification/ZenModeHelper.java
+++ b/services/core/java/com/android/server/notification/ZenModeHelper.java
@@ -627,6 +627,7 @@
             try {
                 ApplicationInfo applicationInfo = mPm.getApplicationInfo(pkg, 0);
                 rule.name = applicationInfo.loadLabel(mPm).toString();
+                rule.iconResName = drawableResIdToResName(pkg, applicationInfo.icon);
             } catch (PackageManager.NameNotFoundException e) {
                 // Should not happen, since it's the app calling us (?)
                 Log.w(TAG, "Package not found for creating implicit zen rule");
@@ -634,6 +635,9 @@
             }
         });
 
+        rule.type = AutomaticZenRule.TYPE_OTHER;
+        rule.triggerDescription = mContext.getString(R.string.zen_mode_implicit_trigger_description,
+                rule.name);
         rule.condition = null;
         rule.conditionId = new Uri.Builder()
                 .scheme(Condition.SCHEME)
diff --git a/services/core/java/com/android/server/notification/flags.aconfig b/services/core/java/com/android/server/notification/flags.aconfig
index dcac8c9..da01745 100644
--- a/services/core/java/com/android/server/notification/flags.aconfig
+++ b/services/core/java/com/android/server/notification/flags.aconfig
@@ -20,3 +20,17 @@
   description: "This flag controls the refactoring of NMS to NotificationAttentionHelper"
   bug: "291907312"
 }
+
+flag {
+  name: "cross_app_polite_notifications"
+  namespace: "systemui"
+  description: "This flag controls the cross-app effect of polite notifications"
+  bug: "270456865"
+}
+
+flag {
+  name: "vibrate_while_unlocked"
+  namespace: "systemui"
+  description: "This flag controls the vibrate while unlocked setting of polite notifications"
+  bug: "270456865"
+}
\ No newline at end of file
diff --git a/services/core/java/com/android/server/pdb/PersistentDataBlockService.java b/services/core/java/com/android/server/pdb/PersistentDataBlockService.java
index f985b5b..b9b09fb 100644
--- a/services/core/java/com/android/server/pdb/PersistentDataBlockService.java
+++ b/services/core/java/com/android/server/pdb/PersistentDataBlockService.java
@@ -754,9 +754,9 @@
         }
     };
 
-    private PersistentDataBlockManagerInternal mInternalService =
-            new PersistentDataBlockManagerInternal() {
+    private InternalService mInternalService = new InternalService();
 
+    private class InternalService implements PersistentDataBlockManagerInternal {
         @Override
         public void setFrpCredentialHandle(byte[] handle) {
             writeInternal(handle, getFrpCredentialDataOffset(), MAX_FRP_CREDENTIAL_HANDLE_SIZE);
diff --git a/services/core/java/com/android/server/pm/BroadcastHelper.java b/services/core/java/com/android/server/pm/BroadcastHelper.java
index 7b35589..7f58e75e 100644
--- a/services/core/java/com/android/server/pm/BroadcastHelper.java
+++ b/services/core/java/com/android/server/pm/BroadcastHelper.java
@@ -837,13 +837,11 @@
         }
 
         final String removedPackage = packageRemovedInfo.mRemovedPackage;
-        final int removedAppId = packageRemovedInfo.mRemovedAppId;
-        final int uid = packageRemovedInfo.mUid;
         final String installerPackageName = packageRemovedInfo.mInstallerPackageName;
         final SparseArray<int[]> broadcastAllowList = packageRemovedInfo.mBroadcastAllowList;
 
         Bundle extras = new Bundle(2);
-        extras.putInt(Intent.EXTRA_UID, removedAppId >= 0 ? removedAppId : uid);
+        extras.putInt(Intent.EXTRA_UID, packageRemovedInfo.mUid);
         extras.putBoolean(Intent.EXTRA_REPLACING, true);
         sendPackageBroadcastAndNotify(Intent.ACTION_PACKAGE_ADDED, removedPackage, extras,
                 0, null /*targetPackage*/, null, null, null, broadcastAllowList, null);
@@ -888,8 +886,6 @@
             boolean removedBySystem,
             boolean isArchived) {
         final String removedPackage = packageRemovedInfo.mRemovedPackage;
-        final int removedAppId = packageRemovedInfo.mRemovedAppId;
-        final int uid = packageRemovedInfo.mUid;
         final String installerPackageName = packageRemovedInfo.mInstallerPackageName;
         final int[] broadcastUserIds = packageRemovedInfo.mBroadcastUsers;
         final int[] instantUserIds = packageRemovedInfo.mInstantUserIds;
@@ -902,8 +898,7 @@
         final boolean isStaticSharedLib = packageRemovedInfo.mIsStaticSharedLib;
 
         Bundle extras = new Bundle();
-        final int removedUid = removedAppId >= 0  ? removedAppId : uid;
-        extras.putInt(Intent.EXTRA_UID, removedUid);
+        extras.putInt(Intent.EXTRA_UID, packageRemovedInfo.mUid);
         extras.putBoolean(Intent.EXTRA_DATA_REMOVED, dataRemoved);
         extras.putBoolean(Intent.EXTRA_SYSTEM_UPDATE_UNINSTALL, isRemovedPackageSystemUpdate);
         extras.putBoolean(Intent.EXTRA_DONT_KILL_APP, !killApp);
@@ -940,10 +935,10 @@
                 sendPackageBroadcastAndNotify(Intent.ACTION_PACKAGE_FULLY_REMOVED,
                         removedPackage, extras, Intent.FLAG_RECEIVER_INCLUDE_BACKGROUND, null,
                         null, broadcastUserIds, instantUserIds, broadcastAllowList, null);
-                packageSender.notifyPackageRemoved(removedPackage, removedUid);
+                packageSender.notifyPackageRemoved(removedPackage, packageRemovedInfo.mUid);
             }
         }
-        if (removedAppId >= 0) {
+        if (packageRemovedInfo.mIsAppIdRemoved) {
             // If a system app's updates are uninstalled the UID is not actually removed. Some
             // services need to know the package name affected.
             if (isReplace) {
diff --git a/services/core/java/com/android/server/pm/Computer.java b/services/core/java/com/android/server/pm/Computer.java
index bd725ed..27f4e11 100644
--- a/services/core/java/com/android/server/pm/Computer.java
+++ b/services/core/java/com/android/server/pm/Computer.java
@@ -508,12 +508,15 @@
 
     boolean getApplicationHiddenSettingAsUser(@NonNull String packageName, @UserIdInt int userId);
 
-    boolean isPackageSuspendedForUser(@NonNull String packageName, @UserIdInt int userId);
+    boolean isPackageSuspendedForUser(@NonNull String packageName, @UserIdInt int userId)
+            throws PackageManager.NameNotFoundException;
 
-    boolean isPackageQuarantinedForUser(@NonNull String packageName, @UserIdInt int userId);
+    boolean isPackageQuarantinedForUser(@NonNull String packageName, @UserIdInt int userId)
+            throws PackageManager.NameNotFoundException;
 
     /** Check if the package is in a stopped state for a given user. */
-    boolean isPackageStoppedForUser(@NonNull String packageName, @UserIdInt int userId);
+    boolean isPackageStoppedForUser(@NonNull String packageName, @UserIdInt int userId)
+            throws PackageManager.NameNotFoundException;
 
     boolean isSuspendingAnyPackages(@NonNull String suspendingPackage, @UserIdInt int userId);
 
diff --git a/services/core/java/com/android/server/pm/ComputerEngine.java b/services/core/java/com/android/server/pm/ComputerEngine.java
index 2dc3fb7..abfd571 100644
--- a/services/core/java/com/android/server/pm/ComputerEngine.java
+++ b/services/core/java/com/android/server/pm/ComputerEngine.java
@@ -4980,31 +4980,34 @@
         }
     }
 
-    private PackageUserStateInternal getUserStageOrDefaultForUser(@NonNull String packageName,
-            int userId) {
+    private PackageUserStateInternal getUserStateOrDefaultForUser(@NonNull String packageName,
+            int userId) throws PackageManager.NameNotFoundException {
         final int callingUid = Binder.getCallingUid();
         enforceCrossUserPermission(callingUid, userId, true /* requireFullPermission */,
                 false /* checkShell */, "when asking about packages for user " + userId);
         final PackageStateInternal ps = mSettings.getPackage(packageName);
         if (ps == null || shouldFilterApplicationIncludingUninstalled(ps, callingUid, userId)) {
-            throw new IllegalArgumentException("Unknown target package: " + packageName);
+            throw new PackageManager.NameNotFoundException(packageName);
         }
         return ps.getUserStateOrDefault(userId);
     }
 
     @Override
-    public boolean isPackageSuspendedForUser(@NonNull String packageName, int userId) {
-        return getUserStageOrDefaultForUser(packageName, userId).isSuspended();
+    public boolean isPackageSuspendedForUser(@NonNull String packageName, int userId)
+            throws PackageManager.NameNotFoundException {
+        return getUserStateOrDefaultForUser(packageName, userId).isSuspended();
     }
 
     @Override
-    public boolean isPackageQuarantinedForUser(@NonNull String packageName, @UserIdInt int userId) {
-        return getUserStageOrDefaultForUser(packageName, userId).isQuarantined();
+    public boolean isPackageQuarantinedForUser(@NonNull String packageName, @UserIdInt int userId)
+            throws PackageManager.NameNotFoundException {
+        return getUserStateOrDefaultForUser(packageName, userId).isQuarantined();
     }
 
     @Override
-    public boolean isPackageStoppedForUser(@NonNull String packageName, @UserIdInt int userId) {
-        return getUserStageOrDefaultForUser(packageName, userId).isStopped();
+    public boolean isPackageStoppedForUser(@NonNull String packageName, @UserIdInt int userId)
+            throws PackageManager.NameNotFoundException {
+        return getUserStateOrDefaultForUser(packageName, userId).isStopped();
     }
 
     @Override
diff --git a/services/core/java/com/android/server/pm/DeletePackageAction.java b/services/core/java/com/android/server/pm/DeletePackageAction.java
index 8ef6601..31544d5 100644
--- a/services/core/java/com/android/server/pm/DeletePackageAction.java
+++ b/services/core/java/com/android/server/pm/DeletePackageAction.java
@@ -16,17 +16,19 @@
 
 package com.android.server.pm;
 
+import android.annotation.NonNull;
 import android.os.UserHandle;
 
 final class DeletePackageAction {
     public final PackageSetting mDeletingPs;
     public final PackageSetting mDisabledPs;
+    @NonNull
     public final PackageRemovedInfo mRemovedInfo;
     public final int mFlags;
     public final UserHandle mUser;
 
     DeletePackageAction(PackageSetting deletingPs, PackageSetting disabledPs,
-            PackageRemovedInfo removedInfo, int flags, UserHandle user) {
+            @NonNull PackageRemovedInfo removedInfo, int flags, UserHandle user) {
         mDeletingPs = deletingPs;
         mDisabledPs = disabledPs;
         mRemovedInfo = removedInfo;
diff --git a/services/core/java/com/android/server/pm/DeletePackageHelper.java b/services/core/java/com/android/server/pm/DeletePackageHelper.java
index 93836266..aa7f0d3 100644
--- a/services/core/java/com/android/server/pm/DeletePackageHelper.java
+++ b/services/core/java/com/android/server/pm/DeletePackageHelper.java
@@ -370,7 +370,7 @@
     @GuardedBy("mPm.mInstallLock")
     public boolean deletePackageLIF(@NonNull String packageName, UserHandle user,
             boolean deleteCodeAndResources, @NonNull int[] allUserHandles, int flags,
-            PackageRemovedInfo outInfo, boolean writeSettings) {
+            @NonNull PackageRemovedInfo outInfo, boolean writeSettings) {
         final DeletePackageAction action;
         synchronized (mPm.mLock) {
             final PackageSetting ps = mPm.mSettings.getPackageLPr(packageName);
@@ -410,8 +410,8 @@
      * deleted, {@code null} otherwise.
      */
     @Nullable
-    public static DeletePackageAction mayDeletePackageLocked(
-            PackageRemovedInfo outInfo, PackageSetting ps, @Nullable PackageSetting disabledPs,
+    public static DeletePackageAction mayDeletePackageLocked(@NonNull PackageRemovedInfo outInfo,
+            PackageSetting ps, @Nullable PackageSetting disabledPs,
             int flags, UserHandle user) {
         if (ps == null) {
             return null;
@@ -460,12 +460,18 @@
         }
 
         final int userId = user == null ? UserHandle.USER_ALL : user.getIdentifier();
-        if (outInfo != null) {
-            // Remember which users are affected, before the installed states are modified
-            outInfo.mRemovedUsers = (systemApp || userId == UserHandle.USER_ALL)
-                    ? ps.queryUsersInstalledOrHasData(allUserHandles)
-                    : new int[]{userId};
-        }
+        // Remember which users are affected, before the installed states are modified
+        outInfo.mRemovedUsers = (systemApp || userId == UserHandle.USER_ALL)
+                ? ps.queryUsersInstalledOrHasData(allUserHandles)
+                : new int[]{userId};
+        outInfo.populateBroadcastUsers(ps);
+        outInfo.mDataRemoved = (flags & PackageManager.DELETE_KEEP_DATA) == 0;
+        outInfo.mRemovedPackage = ps.getPackageName();
+        outInfo.mInstallerPackageName = ps.getInstallSource().mInstallerPackageName;
+        outInfo.mIsStaticSharedLib =
+                ps.getPkg() != null && ps.getPkg().getStaticSharedLibraryName() != null;
+        outInfo.mIsExternal = ps.isExternalStorage();
+        outInfo.mRemovedPackageVersionCode = ps.getVersionCode();
 
         if ((!systemApp || (flags & PackageManager.DELETE_SYSTEM_APP) != 0)
                 && userId != UserHandle.USER_ALL) {
@@ -503,7 +509,11 @@
                 }
             }
             if (clearPackageStateAndReturn) {
-                mRemovePackageHelper.clearPackageStateForUserLIF(ps, userId, outInfo, flags);
+                mRemovePackageHelper.clearPackageStateForUserLIF(ps, userId, flags);
+                // Legacy behavior to report appId as UID here.
+                // The final broadcasts will contain a per-user UID.
+                outInfo.mUid = ps.getAppId();
+                outInfo.mIsAppIdRemoved = true;
                 mPm.scheduleWritePackageRestrictions(user);
                 return;
             }
@@ -529,12 +539,8 @@
 
         // If the package removed had SUSPEND_APPS, unset any restrictions that might have been in
         // place for all affected users.
-        int[] affectedUserIds = (outInfo != null) ? outInfo.mRemovedUsers : null;
-        if (affectedUserIds == null) {
-            affectedUserIds = mPm.resolveUserIds(userId);
-        }
         final Computer snapshot = mPm.snapshotComputer();
-        for (final int affectedUserId : affectedUserIds) {
+        for (final int affectedUserId : outInfo.mRemovedUsers) {
             if (hadSuspendAppsPermission.get(affectedUserId)) {
                 mPm.unsuspendForSuspendingPackage(snapshot, packageName, affectedUserId);
                 mPm.removeAllDistractingPackageRestrictions(snapshot, affectedUserId);
@@ -542,24 +548,21 @@
         }
 
         // Take a note whether we deleted the package for all users
-        if (outInfo != null) {
-            synchronized (mPm.mLock) {
-                outInfo.mRemovedForAllUsers = mPm.mPackages.get(ps.getPackageName()) == null;
-            }
+        synchronized (mPm.mLock) {
+            outInfo.mRemovedForAllUsers = mPm.mPackages.get(ps.getPackageName()) == null;
         }
     }
 
     @GuardedBy("mPm.mInstallLock")
     private void deleteInstalledPackageLIF(PackageSetting ps,
             boolean deleteCodeAndResources, int flags, @NonNull int[] allUserHandles,
-            PackageRemovedInfo outInfo, boolean writeSettings) {
+            @NonNull PackageRemovedInfo outInfo, boolean writeSettings) {
         synchronized (mPm.mLock) {
-            if (outInfo != null) {
-                outInfo.mUid = ps.getAppId();
-                outInfo.mBroadcastAllowList = mPm.mAppsFilter.getVisibilityAllowList(
-                        mPm.snapshotComputer(), ps, allUserHandles,
-                        mPm.mSettings.getPackagesLocked());
-            }
+            // Since the package is being deleted in all users, report appId as the uid
+            outInfo.mUid = ps.getAppId();
+            outInfo.mBroadcastAllowList = mPm.mAppsFilter.getVisibilityAllowList(
+                    mPm.snapshotComputer(), ps, allUserHandles,
+                    mPm.mSettings.getPackagesLocked());
         }
 
         // Delete package data from internal structures and also remove data if flag is set
@@ -567,7 +570,7 @@
                 ps, allUserHandles, outInfo, flags, writeSettings);
 
         // Delete application code and resources only for parent packages
-        if (deleteCodeAndResources && (outInfo != null)) {
+        if (deleteCodeAndResources) {
             outInfo.mArgs = new InstallArgs(
                     ps.getPathString(), getAppDexInstructionSets(
                             ps.getPrimaryCpuAbiLegacy(), ps.getSecondaryCpuAbiLegacy()));
@@ -639,7 +642,7 @@
         int flags = action.mFlags;
         final PackageSetting deletedPs = action.mDeletingPs;
         final PackageRemovedInfo outInfo = action.mRemovedInfo;
-        final boolean applyUserRestrictions = outInfo != null && (outInfo.mOrigUsers != null);
+        final boolean applyUserRestrictions = outInfo.mOrigUsers != null;
         final AndroidPackage deletedPkg = deletedPs.getPkg();
         // Confirm if the system package has been updated
         // An updated system app can be deleted. This will also have to restore
@@ -662,10 +665,8 @@
             }
         }
 
-        if (outInfo != null) {
-            // Delete the updated package
-            outInfo.mIsRemovedPackageSystemUpdate = true;
-        }
+        // Delete the updated package
+        outInfo.mIsRemovedPackageSystemUpdate = true;
 
         if (disabledPs.getVersionCode() < deletedPs.getVersionCode()
                 || disabledPs.getAppId() != deletedPs.getAppId()) {
diff --git a/services/core/java/com/android/server/pm/IPackageManagerBase.java b/services/core/java/com/android/server/pm/IPackageManagerBase.java
index 24a33f1..e3bbd2d 100644
--- a/services/core/java/com/android/server/pm/IPackageManagerBase.java
+++ b/services/core/java/com/android/server/pm/IPackageManagerBase.java
@@ -951,20 +951,32 @@
     @Deprecated
     public final boolean isPackageSuspendedForUser(@NonNull String packageName,
             @UserIdInt int userId) {
-        return snapshot().isPackageSuspendedForUser(packageName, userId);
+        try {
+            return snapshot().isPackageSuspendedForUser(packageName, userId);
+        } catch (PackageManager.NameNotFoundException e) {
+            throw new IllegalArgumentException("Unknown target package: " + packageName);
+        }
     }
 
     @Override
     @Deprecated
     public final boolean isPackageQuarantinedForUser(@NonNull String packageName,
             @UserIdInt int userId) {
-        return snapshot().isPackageQuarantinedForUser(packageName, userId);
+        try {
+            return snapshot().isPackageQuarantinedForUser(packageName, userId);
+        } catch (PackageManager.NameNotFoundException e) {
+            throw new IllegalArgumentException("Unknown target package: " + packageName);
+        }
     }
 
     @Override
     public final boolean isPackageStoppedForUser(@NonNull String packageName,
             @UserIdInt int userId) {
-        return snapshot().isPackageStoppedForUser(packageName, userId);
+        try {
+            return snapshot().isPackageStoppedForUser(packageName, userId);
+        } catch (PackageManager.NameNotFoundException e) {
+            throw new IllegalArgumentException("Unknown target package: " + packageName);
+        }
     }
 
     @Override
diff --git a/services/core/java/com/android/server/pm/InitAppsHelper.java b/services/core/java/com/android/server/pm/InitAppsHelper.java
index 5c4447e..3b9f9c8 100644
--- a/services/core/java/com/android/server/pm/InitAppsHelper.java
+++ b/services/core/java/com/android/server/pm/InitAppsHelper.java
@@ -18,6 +18,7 @@
 
 import static android.os.Trace.TRACE_TAG_PACKAGE_MANAGER;
 
+import static com.android.internal.pm.pkg.parsing.ParsingPackageUtils.PARSE_APK_IN_APEX;
 import static com.android.internal.util.FrameworkStatsLog.BOOT_TIME_EVENT_DURATION__EVENT__OTA_PACKAGE_MANAGER_DATA_APP_AVG_SCAN_TIME;
 import static com.android.internal.util.FrameworkStatsLog.BOOT_TIME_EVENT_DURATION__EVENT__OTA_PACKAGE_MANAGER_SYSTEM_APP_AVG_SCAN_TIME;
 import static com.android.server.pm.PackageManagerService.SCAN_AS_APK_IN_APEX;
@@ -30,7 +31,6 @@
 import static com.android.server.pm.PackageManagerService.SCAN_REQUIRE_KNOWN;
 import static com.android.server.pm.PackageManagerService.SYSTEM_PARTITIONS;
 import static com.android.server.pm.PackageManagerService.TAG;
-import static com.android.server.pm.pkg.parsing.ParsingPackageUtils.PARSE_APK_IN_APEX;
 
 import android.annotation.NonNull;
 import android.annotation.Nullable;
@@ -46,12 +46,12 @@
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.content.om.OverlayConfig;
+import com.android.internal.pm.pkg.parsing.ParsingPackageUtils;
 import com.android.internal.util.FrameworkStatsLog;
 import com.android.server.EventLogTags;
 import com.android.server.pm.parsing.PackageCacher;
 import com.android.server.pm.parsing.PackageParser2;
 import com.android.server.pm.pkg.AndroidPackage;
-import com.android.server.pm.pkg.parsing.ParsingPackageUtils;
 import com.android.server.utils.WatchedArrayMap;
 
 import java.io.File;
diff --git a/services/core/java/com/android/server/pm/InstallPackageHelper.java b/services/core/java/com/android/server/pm/InstallPackageHelper.java
index 83a6f10..0fa7aa5 100644
--- a/services/core/java/com/android/server/pm/InstallPackageHelper.java
+++ b/services/core/java/com/android/server/pm/InstallPackageHelper.java
@@ -154,12 +154,16 @@
 
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.content.F2fsUtils;
+import com.android.internal.pm.parsing.PackageParserException;
+import com.android.internal.pm.parsing.pkg.AndroidPackageLegacyUtils;
 import com.android.internal.pm.parsing.pkg.ParsedPackage;
+import com.android.internal.pm.pkg.component.ComponentMutateUtils;
 import com.android.internal.pm.pkg.component.ParsedActivity;
 import com.android.internal.pm.pkg.component.ParsedInstrumentation;
 import com.android.internal.pm.pkg.component.ParsedIntentInfo;
 import com.android.internal.pm.pkg.component.ParsedPermission;
 import com.android.internal.pm.pkg.component.ParsedPermissionGroup;
+import com.android.internal.pm.pkg.parsing.ParsingPackageUtils;
 import com.android.internal.security.VerityUtils;
 import com.android.internal.util.ArrayUtils;
 import com.android.internal.util.CollectionUtils;
@@ -181,8 +185,6 @@
 import com.android.server.pm.pkg.AndroidPackage;
 import com.android.server.pm.pkg.PackageStateInternal;
 import com.android.server.pm.pkg.SharedLibraryWrapper;
-import com.android.server.pm.pkg.component.ComponentMutateUtils;
-import com.android.server.pm.pkg.parsing.ParsingPackageUtils;
 import com.android.server.rollback.RollbackManagerInternal;
 import com.android.server.security.FileIntegrityService;
 import com.android.server.utils.WatchedArrayMap;
@@ -1165,7 +1167,7 @@
                         parseFlags);
                 archivedPackage = request.getPackageLite().getArchivedPackage();
             }
-        } catch (PackageManagerException e) {
+        } catch (PackageManagerException | PackageParserException e) {
             throw new PrepareFailure("Failed parse during installPackageLI", e);
         } finally {
             Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
@@ -2450,9 +2452,9 @@
             // Hardcode previousAppId to 0 to disable any data migration (http://b/221088088)
             mAppDataHelper.prepareAppDataPostCommitLIF(ps, 0, installRequest.getNewUsers());
             if (installRequest.isClearCodeCache()) {
-                mAppDataHelper.clearAppDataLeafLIF(packageName, ps.getVolumeUuid(),
-                        UserHandle.USER_ALL, FLAG_STORAGE_DE | FLAG_STORAGE_CE
-                        | FLAG_STORAGE_EXTERNAL | Installer.FLAG_CLEAR_CODE_CACHE_ONLY);
+                mAppDataHelper.clearAppDataLIF(ps.getPkg(), UserHandle.USER_ALL,
+                        FLAG_STORAGE_DE | FLAG_STORAGE_CE | FLAG_STORAGE_EXTERNAL
+                                | Installer.FLAG_CLEAR_CODE_CACHE_ONLY);
             }
             if (installRequest.isInstallReplace() && pkg != null) {
                 mDexManager.notifyPackageUpdated(packageName,
@@ -2910,7 +2912,8 @@
                 info.mInstallerPackageName = request.getInstallerPackageName();
                 info.mRemovedUsers = firstUserIds;
                 info.mBroadcastUsers = firstUserIds;
-                info.mRemovedAppId = request.getAppId();
+                info.mUid = request.getAppId();
+                info.mIsAppIdRemoved = true;
                 info.mRemovedPackageVersionCode = request.getPkg().getLongVersionCode();
                 info.mRemovedForAllUsers = true;
 
@@ -3856,7 +3859,7 @@
 
         synchronized (mPm.mLock) {
             platformPackage = mPm.getPlatformPackage();
-            var isSystemApp = AndroidPackageUtils.isSystem(parsedPackage);
+            var isSystemApp = AndroidPackageLegacyUtils.isSystem(parsedPackage);
             final String renamedPkgName = mPm.mSettings.getRenamedPackageLPr(
                     AndroidPackageUtils.getRealPackageOrNull(parsedPackage, isSystemApp));
             realPkgName = ScanPackageUtils.getRealPackageName(parsedPackage, renamedPkgName,
@@ -4126,7 +4129,7 @@
                         null /* request */)) {
                     mDeletePackageHelper.deletePackageLIF(
                             parsedPackage.getPackageName(), null, true,
-                            mPm.mUserManager.getUserIds(), 0, null, false);
+                            mPm.mUserManager.getUserIds(), 0, new PackageRemovedInfo(), false);
                 }
             } else if (newPkgVersionGreater || newSharedUserSetting) {
                 // The application on /system is newer than the application on /data.
@@ -4574,7 +4577,7 @@
 
     private void assertPackageWithSharedUserIdIsPrivileged(AndroidPackage pkg)
             throws PackageManagerException {
-        if (!AndroidPackageUtils.isPrivileged(pkg) && (pkg.getSharedUserId() != null)) {
+        if (!AndroidPackageLegacyUtils.isPrivileged(pkg) && (pkg.getSharedUserId() != null)) {
             SharedUserSetting sharedUserSetting = null;
             try {
                 synchronized (mPm.mLock) {
@@ -4612,7 +4615,7 @@
         final boolean skipVendorPrivilegeScan = ((scanFlags & SCAN_AS_VENDOR) != 0)
                 && ScanPackageUtils.getVendorPartitionVersion() < 28;
         if (((scanFlags & SCAN_AS_PRIVILEGED) == 0)
-                && !AndroidPackageUtils.isPrivileged(pkg)
+                && !AndroidPackageLegacyUtils.isPrivileged(pkg)
                 && (pkg.getSharedUserId() != null)
                 && !skipVendorPrivilegeScan) {
             SharedUserSetting sharedUserSetting = null;
diff --git a/services/core/java/com/android/server/pm/InstallRequest.java b/services/core/java/com/android/server/pm/InstallRequest.java
index 5494bd9..ee780d9 100644
--- a/services/core/java/com/android/server/pm/InstallRequest.java
+++ b/services/core/java/com/android/server/pm/InstallRequest.java
@@ -51,12 +51,12 @@
 import android.util.Slog;
 
 import com.android.internal.pm.parsing.pkg.ParsedPackage;
+import com.android.internal.pm.pkg.parsing.ParsingPackageUtils;
 import com.android.internal.util.ArrayUtils;
 import com.android.server.art.model.DexoptResult;
 import com.android.server.pm.pkg.AndroidPackage;
 import com.android.server.pm.pkg.PackageState;
 import com.android.server.pm.pkg.PackageStateInternal;
-import com.android.server.pm.pkg.parsing.ParsingPackageUtils;
 
 import java.io.File;
 import java.util.ArrayList;
@@ -818,7 +818,8 @@
 
     public void setRemovedAppId(int appId) {
         if (mRemovedInfo != null) {
-            mRemovedInfo.mRemovedAppId = appId;
+            mRemovedInfo.mUid = appId;
+            mRemovedInfo.mIsAppIdRemoved = true;
         }
     }
 
diff --git a/services/core/java/com/android/server/pm/KnownPackages.java b/services/core/java/com/android/server/pm/KnownPackages.java
index 154709a..83831ca 100644
--- a/services/core/java/com/android/server/pm/KnownPackages.java
+++ b/services/core/java/com/android/server/pm/KnownPackages.java
@@ -77,6 +77,8 @@
     // Please note the numbers should be continuous.
     public static final int LAST_KNOWN_PACKAGE = PACKAGE_WEARABLE_SENSING;
 
+    static final String SYSTEM_PACKAGE_NAME = "android";
+
     private final DefaultAppProvider mDefaultAppProvider;
     private final String mRequiredInstallerPackage;
     private final String mRequiredUninstallerPackage;
@@ -186,7 +188,7 @@
             case PACKAGE_SETUP_WIZARD:
                 return snapshot.filterOnlySystemPackages(mSetupWizardPackage);
             case PACKAGE_SYSTEM:
-                return new String[]{"android"};
+                return new String[]{SYSTEM_PACKAGE_NAME};
             case PACKAGE_VERIFIER:
                 return snapshot.filterOnlySystemPackages(mRequiredVerifierPackages);
             case PACKAGE_SYSTEM_TEXT_CLASSIFIER:
diff --git a/services/core/java/com/android/server/pm/LauncherAppsService.java b/services/core/java/com/android/server/pm/LauncherAppsService.java
index 58c1c05..7f75e72 100644
--- a/services/core/java/com/android/server/pm/LauncherAppsService.java
+++ b/services/core/java/com/android/server/pm/LauncherAppsService.java
@@ -1575,6 +1575,57 @@
         }
 
         @Override
+        public List<String> getPreInstalledSystemPackages(UserHandle user) {
+            // Only system launchers, which have access to recents should have access to this API.
+            // TODO(b/303803157): Update access control for this API to default Launcher app.
+            if (!mActivityTaskManagerInternal.isCallerRecents(Binder.getCallingUid())) {
+                throw new SecurityException("Caller is not the recents app");
+            }
+            if (!canAccessProfile(user.getIdentifier(),
+                    "Can't access preinstalled packages for another user")) {
+                return null;
+            }
+            final long identity = Binder.clearCallingIdentity();
+            try {
+                String userType = mUm.getUserInfo(user.getIdentifier()).userType;
+                Set<String> preInstalledPackages = mUm.getPreInstallableSystemPackages(userType);
+                if (preInstalledPackages == null) {
+                    return new ArrayList<>();
+                }
+                return List.copyOf(preInstalledPackages);
+            } finally {
+                Binder.restoreCallingIdentity(identity);
+            }
+        }
+
+        @Override
+        public @Nullable IntentSender getAppMarketActivityIntent(@NonNull String callingPackage,
+                @Nullable String packageName, @NonNull UserHandle user) {
+            // Only system launchers, which have access to recents should have access to this API.
+            // TODO(b/303803157): Update access control for this API to default Launcher app.
+            if (!mActivityTaskManagerInternal.isCallerRecents(Binder.getCallingUid())) {
+                throw new SecurityException("Caller is not the recents app");
+            }
+            if (!canAccessProfile(user.getIdentifier(),
+                    "Can't access AppMarketActivity for another user")) {
+                return null;
+            }
+            final long identity = Binder.clearCallingIdentity();
+            try {
+                // TODO(b/316118005): Add code to launch the app installer for the packageName.
+                Intent appMarketIntent = new Intent(Intent.ACTION_MAIN);
+                appMarketIntent.addCategory(Intent.CATEGORY_APP_MARKET);
+                final PendingIntent pi = PendingIntent.getActivityAsUser(
+                        mContext, /* requestCode */ 0, appMarketIntent, PendingIntent.FLAG_ONE_SHOT
+                                | PendingIntent.FLAG_IMMUTABLE | PendingIntent.FLAG_CANCEL_CURRENT,
+                        /* options */ null, user);
+                return pi == null ? null : pi.getIntentSender();
+            } finally {
+                Binder.restoreCallingIdentity(identity);
+            }
+        }
+
+        @Override
         public void startActivityAsUser(IApplicationThread caller, String callingPackage,
                 String callingFeatureId, ComponentName component, Rect sourceBounds,
                 Bundle opts, UserHandle user) throws RemoteException {
diff --git a/services/core/java/com/android/server/pm/PackageArchiver.java b/services/core/java/com/android/server/pm/PackageArchiver.java
index e3bab3f..6b05edf 100644
--- a/services/core/java/com/android/server/pm/PackageArchiver.java
+++ b/services/core/java/com/android/server/pm/PackageArchiver.java
@@ -323,6 +323,7 @@
         PackageStateInternal ps = getPackageState(packageName, snapshot,
                 Binder.getCallingUid(), userId);
         verifyNotSystemApp(ps.getFlags());
+        verifyInstalled(ps, userId);
         String responsibleInstallerPackage = getResponsibleInstallerPackage(ps);
         verifyInstaller(responsibleInstallerPackage, userId);
         ApplicationInfo installerInfo = snapshot.getApplicationInfo(
@@ -476,6 +477,14 @@
         }
     }
 
+    private void verifyInstalled(PackageStateInternal ps, int userId)
+            throws PackageManager.NameNotFoundException {
+        if (!ps.getUserStateOrDefault(userId).isInstalled()) {
+            throw new PackageManager.NameNotFoundException(
+                    TextUtils.formatSimple("%s is not installed.", ps.getPackageName()));
+        }
+    }
+
     /**
      * Returns true if the app is archivable.
      */
@@ -519,11 +528,11 @@
     /**
      * Returns true if user has opted the app out of archiving through system settings.
      */
-    // TODO(b/304256918) Switch this to a separate OP code for archiving.
     private boolean isAppOptedOutOfArchiving(String packageName, int uid) {
         return Binder.withCleanCallingIdentity(() ->
-                getAppOpsManager().checkOp(AppOpsManager.OP_AUTO_REVOKE_PERMISSIONS_IF_UNUSED,
-                        uid, packageName) == MODE_IGNORED);
+                getAppOpsManager().checkOpNoThrow(
+                        AppOpsManager.OP_AUTO_REVOKE_PERMISSIONS_IF_UNUSED, uid, packageName)
+                        == MODE_IGNORED);
     }
 
     private void verifyOptOutStatus(String packageName, int uid)
@@ -676,23 +685,51 @@
         PackageStateInternal ps;
         try {
             ps = getPackageState(packageName, snapshot, callingUid, userId);
-            snapshot.enforceCrossUserPermission(callingUid, userId, true, false,
-                    "getArchivedAppIcon");
-            verifyArchived(ps, userId);
         } catch (PackageManager.NameNotFoundException e) {
-            throw new ParcelableException(e);
+            Slog.e(TAG, TextUtils.formatSimple("Package %s couldn't be found.", packageName), e);
+            return null;
         }
 
-        List<ArchiveActivityInfo> activityInfos = ps.getUserStateOrDefault(
-                userId).getArchiveState().getActivityInfos();
-        if (activityInfos.size() == 0) {
+        ArchiveState archiveState = getAnyArchiveState(ps, userId);
+        if (archiveState == null || archiveState.getActivityInfos().size() == 0) {
             return null;
         }
 
         // TODO(b/298452477) Handle monochrome icons.
         // In the rare case the archived app defined more than two launcher activities, we choose
         // the first one arbitrarily.
-        return includeCloudOverlay(decodeIcon(activityInfos.get(0)));
+        return includeCloudOverlay(decodeIcon(archiveState.getActivityInfos().get(0)));
+    }
+
+    /**
+     * This method first checks the ArchiveState for the provided userId and then tries to fallback
+     * to other users if the current user is not archived.
+     *
+     * <p> This fallback behaviour is required for archived apps to fit into the multi-user world
+     * where APKs are shared across users. E.g. current ways of fetching icons for apps that are
+     * only installed on the work profile also work when executed on the personal profile if you're
+     * using {@link PackageManager#MATCH_UNINSTALLED_PACKAGES}. Resource fetching from APKs is for
+     * the most part userId-agnostic, which we need to mimic here in order for existing methods
+     * like {@link PackageManager#getApplicationIcon} to continue working.
+     *
+     * @return {@link ArchiveState} for {@code userId} if present. If not present, false back to an
+     * arbitrary userId. If no user is archived, returns null.
+     */
+    @Nullable
+    private ArchiveState getAnyArchiveState(PackageStateInternal ps, int userId) {
+        PackageUserStateInternal userState = ps.getUserStateOrDefault(userId);
+        if (isArchived(userState)) {
+            return userState.getArchiveState();
+        }
+
+        for (int i = 0; i < ps.getUserStates().size(); i++) {
+            userState = ps.getUserStates().valueAt(i);
+            if (isArchived(userState)) {
+                return userState.getArchiveState();
+            }
+        }
+
+        return null;
     }
 
     @VisibleForTesting
diff --git a/services/core/java/com/android/server/pm/PackageInstallerSession.java b/services/core/java/com/android/server/pm/PackageInstallerSession.java
index 47d1df5..4adb60c 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerSession.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerSession.java
@@ -168,6 +168,7 @@
 import com.android.internal.content.NativeLibraryHelper;
 import com.android.internal.messages.nano.SystemMessageProto;
 import com.android.internal.os.SomeArgs;
+import com.android.internal.pm.pkg.parsing.ParsingPackageUtils;
 import com.android.internal.security.VerityUtils;
 import com.android.internal.util.ArrayUtils;
 import com.android.internal.util.CollectionUtils;
@@ -181,7 +182,6 @@
 import com.android.server.pm.dex.DexManager;
 import com.android.server.pm.pkg.AndroidPackage;
 import com.android.server.pm.pkg.PackageStateInternal;
-import com.android.server.pm.pkg.parsing.ParsingPackageUtils;
 
 import libcore.io.IoUtils;
 import libcore.util.EmptyArray;
diff --git a/services/core/java/com/android/server/pm/PackageManagerInternalBase.java b/services/core/java/com/android/server/pm/PackageManagerInternalBase.java
index 1e7d043..c737b45 100644
--- a/services/core/java/com/android/server/pm/PackageManagerInternalBase.java
+++ b/services/core/java/com/android/server/pm/PackageManagerInternalBase.java
@@ -762,13 +762,14 @@
     }
 
     @Override
-    public boolean isPackageQuarantined(@NonNull String packageName,
-            @UserIdInt int userId) {
+    public boolean isPackageQuarantined(@NonNull String packageName, @UserIdInt int userId)
+            throws PackageManager.NameNotFoundException {
         return snapshot().isPackageQuarantinedForUser(packageName, userId);
     }
 
     @Override
-    public boolean isPackageStopped(@NonNull String packageName, @UserIdInt int userId) {
+    public boolean isPackageStopped(@NonNull String packageName, @UserIdInt int userId)
+            throws PackageManager.NameNotFoundException {
         return snapshot().isPackageStoppedForUser(packageName, userId);
     }
 
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index 2880f84..56365b6 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -185,6 +185,7 @@
 import com.android.internal.pm.parsing.pkg.ParsedPackage;
 import com.android.internal.pm.pkg.component.ParsedInstrumentation;
 import com.android.internal.pm.pkg.component.ParsedMainComponent;
+import com.android.internal.pm.pkg.parsing.ParsingPackageUtils;
 import com.android.internal.telephony.CarrierAppUtils;
 import com.android.internal.util.ArrayUtils;
 import com.android.internal.util.CollectionUtils;
@@ -238,7 +239,6 @@
 import com.android.server.pm.pkg.mutate.PackageStateMutator;
 import com.android.server.pm.pkg.mutate.PackageStateWrite;
 import com.android.server.pm.pkg.mutate.PackageUserStateWrite;
-import com.android.server.pm.pkg.parsing.ParsingPackageUtils;
 import com.android.server.pm.resolution.ComponentResolver;
 import com.android.server.pm.resolution.ComponentResolverApi;
 import com.android.server.pm.verify.domain.DomainVerificationManagerInternal;
@@ -2013,6 +2013,16 @@
             public boolean hasFeature(String feature) {
                 return PackageManagerService.this.hasSystemFeature(feature, 0);
             }
+
+            @Override
+            public Set<String> getHiddenApiWhitelistedApps() {
+                return SystemConfig.getInstance().getHiddenApiWhitelistedApps();
+            }
+
+            @Override
+            public Set<String> getInstallConstraintsAllowlist() {
+                return SystemConfig.getInstance().getInstallConstraintsAllowlist();
+            }
         };
 
         // CHECKSTYLE:ON IndentationCheck
@@ -3044,6 +3054,7 @@
         }
     }
 
+    @NonNull
     int[] resolveUserIds(int userId) {
         return (userId == UserHandle.USER_ALL) ? mUserManager.getUserIds() : new int[] { userId };
     }
@@ -5241,14 +5252,18 @@
 
         @Override
         public String getSuspendingPackage(String packageName, int userId) {
-            final int callingUid = Binder.getCallingUid();
-            final Computer snapshot = snapshot();
-            // This will do visibility checks as well.
-            if (!snapshot.isPackageSuspendedForUser(packageName, userId)) {
+            try {
+                final int callingUid = Binder.getCallingUid();
+                final Computer snapshot = snapshot();
+                // This will do visibility checks as well.
+                if (!snapshot.isPackageSuspendedForUser(packageName, userId)) {
+                    return null;
+                }
+                return mSuspendPackageHelper.getSuspendingPackage(snapshot, packageName, userId,
+                        callingUid);
+            } catch (PackageManager.NameNotFoundException e) {
                 return null;
             }
-            return mSuspendPackageHelper.getSuspendingPackage(snapshot, packageName, userId,
-                    callingUid);
         }
 
         @Override
diff --git a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
index 215e952..322557b 100644
--- a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
+++ b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java
@@ -2872,16 +2872,16 @@
                 UserHandle.USER_NULL, "runGrantRevokePermission"));
 
         List<PackageInfo> packageInfos;
+        PackageManager pm = mContext.createContextAsUser(translatedUser, 0).getPackageManager();
         if (pkg == null) {
-            packageInfos = mContext.getPackageManager().getInstalledPackages(
-                    PackageManager.GET_PERMISSIONS);
+            packageInfos = pm.getInstalledPackages(PackageManager.GET_PERMISSIONS);
         } else {
             try {
-                packageInfos = Collections.singletonList(
-                        mContext.getPackageManager().getPackageInfo(pkg,
-                                PackageManager.GET_PERMISSIONS));
+                packageInfos = Collections.singletonList(pm.getPackageInfo(pkg,
+                        PackageManager.GET_PERMISSIONS));
             } catch (NameNotFoundException e) {
                 getErrPrintWriter().println("Error: package not found");
+                getOutPrintWriter().println("Failure [package not found]");
                 return 1;
             }
         }
diff --git a/services/core/java/com/android/server/pm/PackageRemovedInfo.java b/services/core/java/com/android/server/pm/PackageRemovedInfo.java
index 7ee1772..881b0b3 100644
--- a/services/core/java/com/android/server/pm/PackageRemovedInfo.java
+++ b/services/core/java/com/android/server/pm/PackageRemovedInfo.java
@@ -25,7 +25,7 @@
     String mRemovedPackage;
     String mInstallerPackageName;
     int mUid = -1;
-    int mRemovedAppId = -1;
+    boolean mIsAppIdRemoved = false;
     int[] mOrigUsers;
     int[] mRemovedUsers = null;
     int[] mBroadcastUsers = null;
diff --git a/services/core/java/com/android/server/pm/ReconcilePackageUtils.java b/services/core/java/com/android/server/pm/ReconcilePackageUtils.java
index bb0017c..b6de0e5 100644
--- a/services/core/java/com/android/server/pm/ReconcilePackageUtils.java
+++ b/services/core/java/com/android/server/pm/ReconcilePackageUtils.java
@@ -20,19 +20,23 @@
 import static android.content.pm.PackageManager.INSTALL_PARSE_FAILED_INCONSISTENT_CERTIFICATES;
 import static android.content.pm.SigningDetails.CapabilityMergeRule.MERGE_RESTRICTED_CAPABILITY;
 
+import static com.android.server.pm.PackageManagerService.SCAN_AS_APEX;
 import static com.android.server.pm.PackageManagerService.SCAN_BOOTING;
 import static com.android.server.pm.PackageManagerService.SCAN_DONT_KILL_APP;
+import static com.android.server.pm.PackageManagerService.TAG;
 
 import android.content.pm.PackageManager;
 import android.content.pm.SharedLibraryInfo;
 import android.content.pm.SigningDetails;
+import android.os.Build;
 import android.os.SystemProperties;
 import android.util.ArrayMap;
 import android.util.Log;
+import android.util.Slog;
 
 import com.android.internal.pm.parsing.pkg.ParsedPackage;
+import com.android.internal.pm.pkg.parsing.ParsingPackageUtils;
 import com.android.server.pm.pkg.AndroidPackage;
-import com.android.server.pm.pkg.parsing.ParsingPackageUtils;
 import com.android.server.utils.WatchedLongSparseArray;
 
 import java.util.ArrayList;
@@ -49,6 +53,8 @@
  * as install) led to the request.
  */
 final class ReconcilePackageUtils {
+    private static final boolean ALLOW_NON_PRELOADS_SYSTEM_SIGNATURE = Build.IS_DEBUGGABLE || true;
+
     public static List<ReconciledPackage> reconcilePackages(
             List<InstallRequest> installRequests,
             Map<String, AndroidPackage> allPackages,
@@ -90,6 +96,8 @@
             }
         }
 
+        final AndroidPackage systemPackage = allPackages.get(KnownPackages.SYSTEM_PACKAGE_NAME);
+
         for (InstallRequest installRequest : installRequests) {
             final String installPackageName = installRequest.getParsedPackage().getPackageName();
             final List<SharedLibraryInfo> allowedSharedLibInfos =
@@ -133,6 +141,9 @@
             if (parsedPackage != null) {
                 signingDetails = parsedPackage.getSigningDetails();
             }
+            final boolean isSystemPackage =
+                    ((parseFlags & ParsingPackageUtils.PARSE_IS_SYSTEM_DIR) != 0);
+            final boolean isApex = (scanFlags & SCAN_AS_APEX) != 0;
             SharedUserSetting sharedUserSetting = settings.getSharedUserSettingLPr(
                     signatureCheckPs);
             if (ksms.shouldCheckUpgradeKeySetLocked(
@@ -141,7 +152,7 @@
                     // We just determined the app is signed correctly, so bring
                     // over the latest parsed certs.
                 } else {
-                    if ((parseFlags & ParsingPackageUtils.PARSE_IS_SYSTEM_DIR) == 0) {
+                    if (!isSystemPackage) {
                         throw new ReconcileFailure(INSTALL_FAILED_UPDATE_INCOMPATIBLE,
                                 "Package " + parsedPackage.getPackageName()
                                         + " upgrade keys do not match the previously installed"
@@ -168,9 +179,23 @@
                         removeAppKeySetData = true;
                     }
 
+                    if (!isSystemPackage && !isApex && signingDetails != null
+                            && systemPackage != null && systemPackage.getSigningDetails() != null
+                            && systemPackage.getSigningDetails().checkCapability(
+                                    signingDetails,
+                                    SigningDetails.CertCapabilities.PERMISSION)) {
+                        Slog.d(TAG, "Non-preload app associated with system signature: "
+                                + signatureCheckPs.getPackageName());
+                        if (!ALLOW_NON_PRELOADS_SYSTEM_SIGNATURE) {
+                            throw new ReconcileFailure(
+                                    INSTALL_PARSE_FAILED_INCONSISTENT_CERTIFICATES,
+                                    "Non-preload app associated with system signature: "
+                                            + signatureCheckPs.getPackageName());
+                        }
+                    }
+
                     // if this is is a sharedUser, check to see if the new package is signed by a
-                    // newer
-                    // signing certificate than the existing one, and if so, copy over the new
+                    // newer signing certificate than the existing one, and if so, copy over the new
                     // details
                     if (sharedUserSetting != null) {
                         // Attempt to merge the existing lineage for the shared SigningDetails with
@@ -203,7 +228,7 @@
                         }
                     }
                 } catch (PackageManagerException e) {
-                    if ((parseFlags & ParsingPackageUtils.PARSE_IS_SYSTEM_DIR) == 0) {
+                    if (!isSystemPackage) {
                         throw new ReconcileFailure(e);
                     }
                     signingDetails = parsedPackage.getSigningDetails();
diff --git a/services/core/java/com/android/server/pm/RemovePackageHelper.java b/services/core/java/com/android/server/pm/RemovePackageHelper.java
index 52b3131..fefab3b 100644
--- a/services/core/java/com/android/server/pm/RemovePackageHelper.java
+++ b/services/core/java/com/android/server/pm/RemovePackageHelper.java
@@ -44,12 +44,12 @@
 import android.util.SparseBooleanArray;
 
 import com.android.internal.annotations.GuardedBy;
+import com.android.internal.pm.parsing.pkg.AndroidPackageLegacyUtils;
+import com.android.internal.pm.parsing.pkg.PackageImpl;
 import com.android.internal.pm.pkg.component.ParsedInstrumentation;
 import com.android.internal.util.ArrayUtils;
 import com.android.server.pm.Installer.LegacyDexoptDisabledException;
 import com.android.server.pm.parsing.PackageCacher;
-import com.android.server.pm.parsing.pkg.AndroidPackageUtils;
-import com.android.server.pm.parsing.pkg.PackageImpl;
 import com.android.server.pm.permission.PermissionManagerServiceInternal;
 import com.android.server.pm.pkg.AndroidPackage;
 import com.android.server.pm.pkg.PackageStateInternal;
@@ -167,7 +167,7 @@
             if (removedPackage != null) {
                 // TODO: Use PackageState for isSystem
                 cleanPackageDataStructuresLILPw(removedPackage,
-                        AndroidPackageUtils.isSystem(removedPackage), chatty);
+                        AndroidPackageLegacyUtils.isSystem(removedPackage), chatty);
             }
         }
     }
@@ -252,8 +252,7 @@
         }
     }
 
-    public void clearPackageStateForUserLIF(PackageSetting ps, int userId,
-            PackageRemovedInfo outInfo, int flags) {
+    public void clearPackageStateForUserLIF(PackageSetting ps, int userId, int flags) {
         final AndroidPackage pkg;
         final SharedUserSetting sus;
         synchronized (mPm.mLock) {
@@ -287,25 +286,12 @@
         }
         mPermissionManager.onPackageUninstalled(ps.getPackageName(), ps.getAppId(), ps, pkg,
                 sharedUserPkgs, userId);
-
-        if (outInfo != null) {
-            if ((flags & PackageManager.DELETE_KEEP_DATA) == 0) {
-                outInfo.mDataRemoved = true;
-            }
-            outInfo.mRemovedPackage = ps.getPackageName();
-            outInfo.mInstallerPackageName = ps.getInstallSource().mInstallerPackageName;
-            outInfo.mIsStaticSharedLib = pkg != null && pkg.getStaticSharedLibraryName() != null;
-            outInfo.mRemovedAppId = ps.getAppId();
-            outInfo.mBroadcastUsers = outInfo.mRemovedUsers;
-            outInfo.mIsExternal = ps.isExternalStorage();
-            outInfo.mRemovedPackageVersionCode = ps.getVersionCode();
-        }
     }
 
     // Called to clean up disabled system packages
     public void removePackageData(final PackageSetting deletedPs, @NonNull int[] allUserHandles) {
         synchronized (mPm.mInstallLock) {
-            removePackageDataLIF(deletedPs, allUserHandles, /* outInfo= */ null,
+            removePackageDataLIF(deletedPs, allUserHandles, new PackageRemovedInfo(),
                     /* flags= */ 0, /* writeSettings= */ false);
         }
     }
@@ -318,20 +304,11 @@
      */
     @GuardedBy("mPm.mInstallLock")
     public void removePackageDataLIF(final PackageSetting deletedPs, @NonNull int[] allUserHandles,
-            PackageRemovedInfo outInfo, int flags, boolean writeSettings) {
+            @NonNull PackageRemovedInfo outInfo, int flags, boolean writeSettings) {
         String packageName = deletedPs.getPackageName();
         if (DEBUG_REMOVE) Slog.d(TAG, "removePackageDataLI: " + deletedPs);
         // Retrieve object to delete permissions for shared user later on
         final AndroidPackage deletedPkg = deletedPs.getPkg();
-        if (outInfo != null) {
-            outInfo.mRemovedPackage = packageName;
-            outInfo.mInstallerPackageName = deletedPs.getInstallSource().mInstallerPackageName;
-            outInfo.mIsStaticSharedLib = deletedPkg != null
-                    && deletedPkg.getStaticSharedLibraryName() != null;
-            outInfo.populateBroadcastUsers(deletedPs);
-            outInfo.mIsExternal = deletedPs.isExternalStorage();
-            outInfo.mRemovedPackageVersionCode = deletedPs.getVersionCode();
-        }
 
         removePackageLI(deletedPs.getPackageName(), (flags & PackageManager.DELETE_CHATTY) != 0);
         if (!deletedPs.isSystem()) {
@@ -355,9 +332,6 @@
             mAppDataHelper.destroyAppDataLIF(resolvedPkg, UserHandle.USER_ALL,
                     FLAG_STORAGE_DE | FLAG_STORAGE_CE | FLAG_STORAGE_EXTERNAL);
             mAppDataHelper.destroyAppProfilesLIF(resolvedPkg.getPackageName());
-            if (outInfo != null) {
-                outInfo.mDataRemoved = true;
-            }
         }
 
         int removedAppId = -1;
@@ -373,9 +347,7 @@
                 mPm.mAppsFilter.removePackage(snapshot,
                         snapshot.getPackageStateInternal(packageName));
                 removedAppId = mPm.mSettings.removePackageLPw(packageName);
-                if (outInfo != null) {
-                    outInfo.mRemovedAppId = removedAppId;
-                }
+                outInfo.mIsAppIdRemoved = true;
                 if (!mPm.mSettings.isDisabledSystemPackageLPr(packageName)) {
                     // If we don't have a disabled system package to reinstall, the package is
                     // really gone and its permission state should be removed.
@@ -403,8 +375,8 @@
                     mBroadcastHelper.sendPreferredActivityChangedBroadcast(UserHandle.USER_ALL);
                 });
             }
-        } else if (!deletedPs.isSystem() && outInfo != null && !outInfo.mIsUpdate
-                && outInfo.mRemovedUsers != null && !outInfo.mIsExternal) {
+        } else if (!deletedPs.isSystem() && !outInfo.mIsUpdate
+                && outInfo.mRemovedUsers != null && !deletedPs.isExternalStorage()) {
             // For non-system uninstalls with DELETE_KEEP_DATA, set the installed state to false
             // for affected users. This does not apply to app updates where the old apk is replaced
             // but the old data remains.
@@ -424,7 +396,7 @@
         // make sure to preserve per-user installed state if this removal was just
         // a downgrade of a system app to the factory package
         boolean installedStateChanged = false;
-        if (outInfo != null && outInfo.mOrigUsers != null && deletedPs.isSystem()) {
+        if (outInfo.mOrigUsers != null && deletedPs.isSystem()) {
             if (DEBUG_REMOVE) {
                 Slog.d(TAG, "Propagating install state across downgrade");
             }
diff --git a/services/core/java/com/android/server/pm/ScanPackageUtils.java b/services/core/java/com/android/server/pm/ScanPackageUtils.java
index 31a63e0..5c6d61e 100644
--- a/services/core/java/com/android/server/pm/ScanPackageUtils.java
+++ b/services/core/java/com/android/server/pm/ScanPackageUtils.java
@@ -74,11 +74,13 @@
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.pm.parsing.pkg.ParsedPackage;
+import com.android.internal.pm.pkg.component.ComponentMutateUtils;
 import com.android.internal.pm.pkg.component.ParsedActivity;
 import com.android.internal.pm.pkg.component.ParsedMainComponent;
 import com.android.internal.pm.pkg.component.ParsedProcess;
 import com.android.internal.pm.pkg.component.ParsedProvider;
 import com.android.internal.pm.pkg.component.ParsedService;
+import com.android.internal.pm.pkg.parsing.ParsingPackageUtils;
 import com.android.internal.util.ArrayUtils;
 import com.android.server.SystemConfig;
 import com.android.server.pm.parsing.PackageInfoUtils;
@@ -86,8 +88,6 @@
 import com.android.server.pm.parsing.pkg.AndroidPackageUtils;
 import com.android.server.pm.pkg.AndroidPackage;
 import com.android.server.pm.pkg.PackageStateUtils;
-import com.android.server.pm.pkg.component.ComponentMutateUtils;
-import com.android.server.pm.pkg.parsing.ParsingPackageUtils;
 import com.android.server.utils.WatchedArraySet;
 
 import dalvik.system.VMRuntime;
diff --git a/services/core/java/com/android/server/pm/ScanRequest.java b/services/core/java/com/android/server/pm/ScanRequest.java
index 37cf30b..41e2a3f 100644
--- a/services/core/java/com/android/server/pm/ScanRequest.java
+++ b/services/core/java/com/android/server/pm/ScanRequest.java
@@ -22,8 +22,8 @@
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.pm.parsing.pkg.ParsedPackage;
+import com.android.internal.pm.pkg.parsing.ParsingPackageUtils;
 import com.android.server.pm.pkg.AndroidPackage;
-import com.android.server.pm.pkg.parsing.ParsingPackageUtils;
 
 /** A package to be scanned */
 @VisibleForTesting
diff --git a/services/core/java/com/android/server/pm/Settings.java b/services/core/java/com/android/server/pm/Settings.java
index 75d88da..cfbaae3 100644
--- a/services/core/java/com/android/server/pm/Settings.java
+++ b/services/core/java/com/android/server/pm/Settings.java
@@ -89,6 +89,7 @@
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.os.BackgroundThread;
+import com.android.internal.pm.parsing.pkg.PackageImpl;
 import com.android.internal.pm.pkg.component.ParsedComponent;
 import com.android.internal.pm.pkg.component.ParsedIntentInfo;
 import com.android.internal.pm.pkg.component.ParsedPermission;
@@ -106,7 +107,6 @@
 import com.android.server.backup.PreferredActivityBackupHelper;
 import com.android.server.pm.Installer.InstallerException;
 import com.android.server.pm.parsing.PackageInfoUtils;
-import com.android.server.pm.parsing.pkg.PackageImpl;
 import com.android.server.pm.permission.LegacyPermissionDataProvider;
 import com.android.server.pm.permission.LegacyPermissionSettings;
 import com.android.server.pm.permission.LegacyPermissionState;
diff --git a/services/core/java/com/android/server/pm/SharedLibrariesImpl.java b/services/core/java/com/android/server/pm/SharedLibrariesImpl.java
index 94495bf..ec8af2e 100644
--- a/services/core/java/com/android/server/pm/SharedLibrariesImpl.java
+++ b/services/core/java/com/android/server/pm/SharedLibrariesImpl.java
@@ -731,7 +731,7 @@
                                 ? PackageManager.DELETE_KEEP_DATA : 0;
                         synchronized (mPm.mInstallLock) {
                             mDeletePackageHelper.deletePackageLIF(pkg.getPackageName(), null, true,
-                                    mPm.mUserManager.getUserIds(), flags, null,
+                                    mPm.mUserManager.getUserIds(), flags, new PackageRemovedInfo(),
                                     true);
                         }
                     }
diff --git a/services/core/java/com/android/server/pm/SharedUserSetting.java b/services/core/java/com/android/server/pm/SharedUserSetting.java
index dddc6b0..5c0a15a 100644
--- a/services/core/java/com/android/server/pm/SharedUserSetting.java
+++ b/services/core/java/com/android/server/pm/SharedUserSetting.java
@@ -25,14 +25,14 @@
 import android.util.ArraySet;
 import android.util.proto.ProtoOutputStream;
 
+import com.android.internal.pm.pkg.component.ComponentMutateUtils;
 import com.android.internal.pm.pkg.component.ParsedProcess;
+import com.android.internal.pm.pkg.component.ParsedProcessImpl;
 import com.android.internal.util.ArrayUtils;
 import com.android.server.pm.permission.LegacyPermissionState;
 import com.android.server.pm.pkg.AndroidPackage;
 import com.android.server.pm.pkg.PackageStateInternal;
 import com.android.server.pm.pkg.SharedUserApi;
-import com.android.server.pm.pkg.component.ComponentMutateUtils;
-import com.android.server.pm.pkg.component.ParsedProcessImpl;
 import com.android.server.utils.SnapshotCache;
 import com.android.server.utils.Watchable;
 import com.android.server.utils.WatchedArraySet;
diff --git a/services/core/java/com/android/server/pm/StorageEventHelper.java b/services/core/java/com/android/server/pm/StorageEventHelper.java
index 70aa19a..7d87d1b 100644
--- a/services/core/java/com/android/server/pm/StorageEventHelper.java
+++ b/services/core/java/com/android/server/pm/StorageEventHelper.java
@@ -47,11 +47,11 @@
 import android.util.Slog;
 
 import com.android.internal.annotations.GuardedBy;
+import com.android.internal.pm.pkg.parsing.ParsingPackageUtils;
 import com.android.internal.policy.AttributeCache;
 import com.android.internal.util.IndentingPrintWriter;
 import com.android.server.pm.pkg.AndroidPackage;
 import com.android.server.pm.pkg.PackageStateInternal;
-import com.android.server.pm.pkg.parsing.ParsingPackageUtils;
 
 import java.io.File;
 import java.io.PrintWriter;
@@ -256,13 +256,12 @@
 
                     final AndroidPackage pkg = ps.getPkg();
                     final int deleteFlags = PackageManager.DELETE_KEEP_DATA;
-                    final PackageRemovedInfo outInfo = new PackageRemovedInfo();
 
                     try (PackageFreezer freezer = mPm.freezePackageForDelete(ps.getPackageName(),
                              UserHandle.USER_ALL, deleteFlags,
                             "unloadPrivatePackagesInner", ApplicationExitInfo.REASON_OTHER)) {
                         if (mDeletePackageHelper.deletePackageLIF(ps.getPackageName(), null, false,
-                                userIds, deleteFlags, outInfo, false)) {
+                                userIds, deleteFlags, new PackageRemovedInfo(), false)) {
                             unloaded.add(pkg);
                         } else {
                             Slog.w(TAG, "Failed to unload " + ps.getPath());
diff --git a/services/core/java/com/android/server/pm/SuspendPackageHelper.java b/services/core/java/com/android/server/pm/SuspendPackageHelper.java
index fe8c12c..c2a960a 100644
--- a/services/core/java/com/android/server/pm/SuspendPackageHelper.java
+++ b/services/core/java/com/android/server/pm/SuspendPackageHelper.java
@@ -418,11 +418,24 @@
         }
 
         String suspendingPackage = null;
+        String suspendedBySystem = null;
+        String qasPackage = null;
         for (int i = 0; i < userState.getSuspendParams().size(); i++) {
             suspendingPackage = userState.getSuspendParams().keyAt(i);
+            var suspendParams = userState.getSuspendParams().valueAt(i);
             if (PLATFORM_PACKAGE_NAME.equals(suspendingPackage)) {
-                return suspendingPackage;
+                suspendedBySystem = suspendingPackage;
             }
+            if (suspendParams.isQuarantined() && qasPackage == null) {
+                qasPackage = suspendingPackage;
+            }
+        }
+        // Precedence: quarantined, then system, then suspending.
+        if (qasPackage != null) {
+            return qasPackage;
+        }
+        if (suspendedBySystem != null) {
+            return suspendedBySystem;
         }
         return suspendingPackage;
     }
diff --git a/services/core/java/com/android/server/pm/UserManagerService.java b/services/core/java/com/android/server/pm/UserManagerService.java
index a7b52f4..c48eccf 100644
--- a/services/core/java/com/android/server/pm/UserManagerService.java
+++ b/services/core/java/com/android/server/pm/UserManagerService.java
@@ -532,9 +532,10 @@
             }
             final IntentSender target = intent.getParcelableExtra(Intent.EXTRA_INTENT, android.content.IntentSender.class);
             final int userId = intent.getIntExtra(Intent.EXTRA_USER_ID, UserHandle.USER_NULL);
+            final String callingPackage = intent.getStringExtra(Intent.EXTRA_PACKAGE_NAME);
             // Call setQuietModeEnabled on bg thread to avoid ANR
             BackgroundThread.getHandler().post(() ->
-                    setQuietModeEnabled(userId, false, target, /* callingPackage */ null));
+                    setQuietModeEnabled(userId, false, target, callingPackage));
         }
     };
 
@@ -1410,7 +1411,7 @@
                     if (onlyIfCredentialNotRequired) {
                         return false;
                     }
-                    showConfirmCredentialToDisableQuietMode(userId, target);
+                    showConfirmCredentialToDisableQuietMode(userId, target, callingPackage);
                     return false;
                 }
             }
@@ -1434,7 +1435,7 @@
                 if (onlyIfCredentialNotRequired) {
                     return false;
                 }
-                showConfirmCredentialToDisableQuietMode(userId, target);
+                showConfirmCredentialToDisableQuietMode(userId, target, callingPackage);
                 return false;
             }
             setQuietModeEnabled(userId, false /* enableQuietMode */, target, callingPackage);
@@ -1604,7 +1605,7 @@
      * Show confirm credential screen to unlock user in order to turn off quiet mode.
      */
     private void showConfirmCredentialToDisableQuietMode(
-            @UserIdInt int userId, @Nullable IntentSender target) {
+            @UserIdInt int userId, @Nullable IntentSender target, @Nullable String callingPackage) {
         if (android.app.admin.flags.Flags.quietModeCredentialBugFix()) {
             // TODO (b/308121702) It may be brittle to rely on user states to check profile state
             int state;
@@ -1635,6 +1636,7 @@
         }
         callBackIntent.putExtra(Intent.EXTRA_USER_ID, userId);
         callBackIntent.setPackage(mContext.getPackageName());
+        callBackIntent.putExtra(Intent.EXTRA_PACKAGE_NAME, callingPackage);
         callBackIntent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND);
         final PendingIntent pendingIntent = PendingIntent.getBroadcast(
                 mContext,
diff --git a/services/core/java/com/android/server/pm/UserTypeFactory.java b/services/core/java/com/android/server/pm/UserTypeFactory.java
index 7386301..14db70e 100644
--- a/services/core/java/com/android/server/pm/UserTypeFactory.java
+++ b/services/core/java/com/android/server/pm/UserTypeFactory.java
@@ -306,7 +306,6 @@
                 .setDarkThemeBadgeColors(
                         R.color.white)
                 .setDefaultRestrictions(getDefaultProfileRestrictions())
-                .setDefaultSecureSettings(getDefaultNonManagedProfileSecureSettings())
                 .setDefaultUserProperties(new UserProperties.Builder()
                         .setStartWithParent(true)
                         .setCredentialShareableWithParent(true)
diff --git a/services/core/java/com/android/server/pm/parsing/PackageCacher.java b/services/core/java/com/android/server/pm/parsing/PackageCacher.java
index 459e2cf..79c9c8e 100644
--- a/services/core/java/com/android/server/pm/parsing/PackageCacher.java
+++ b/services/core/java/com/android/server/pm/parsing/PackageCacher.java
@@ -28,9 +28,9 @@
 import android.util.Slog;
 
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.pm.parsing.pkg.PackageImpl;
 import com.android.internal.pm.parsing.pkg.ParsedPackage;
 import com.android.server.pm.ApexManager;
-import com.android.server.pm.parsing.pkg.PackageImpl;
 
 import libcore.io.IoUtils;
 
diff --git a/services/core/java/com/android/server/pm/parsing/PackageInfoUtils.java b/services/core/java/com/android/server/pm/parsing/PackageInfoUtils.java
index b23dbee..fa54f0b 100644
--- a/services/core/java/com/android/server/pm/parsing/PackageInfoUtils.java
+++ b/services/core/java/com/android/server/pm/parsing/PackageInfoUtils.java
@@ -52,6 +52,9 @@
 import android.util.Slog;
 
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.pm.parsing.pkg.AndroidPackageLegacyUtils;
+import com.android.internal.pm.parsing.pkg.PackageImpl;
+import com.android.internal.pm.pkg.component.ComponentParseUtils;
 import com.android.internal.pm.pkg.component.ParsedActivity;
 import com.android.internal.pm.pkg.component.ParsedAttribution;
 import com.android.internal.pm.pkg.component.ParsedComponent;
@@ -63,11 +66,12 @@
 import com.android.internal.pm.pkg.component.ParsedProvider;
 import com.android.internal.pm.pkg.component.ParsedService;
 import com.android.internal.pm.pkg.component.ParsedUsesPermission;
+import com.android.internal.pm.pkg.parsing.ParsingPackageUtils;
+import com.android.internal.pm.pkg.parsing.ParsingUtils;
 import com.android.internal.util.ArrayUtils;
 import com.android.server.SystemConfig;
 import com.android.server.pm.PackageArchiver;
 import com.android.server.pm.parsing.pkg.AndroidPackageUtils;
-import com.android.server.pm.parsing.pkg.PackageImpl;
 import com.android.server.pm.pkg.AndroidPackage;
 import com.android.server.pm.pkg.PackageStateInternal;
 import com.android.server.pm.pkg.PackageStateUnserialized;
@@ -75,9 +79,6 @@
 import com.android.server.pm.pkg.PackageUserStateInternal;
 import com.android.server.pm.pkg.PackageUserStateUtils;
 import com.android.server.pm.pkg.SELinuxUtil;
-import com.android.server.pm.pkg.component.ComponentParseUtils;
-import com.android.server.pm.pkg.parsing.ParsingPackageUtils;
-import com.android.server.pm.pkg.parsing.ParsingUtils;
 
 import java.io.File;
 import java.util.ArrayList;
@@ -273,8 +274,8 @@
                 final ActivityInfo[] res = new ActivityInfo[N];
                 for (int i = 0; i < N; i++) {
                     final ParsedActivity a = pkg.getActivities().get(i);
-                    if (ComponentParseUtils.isMatch(state, pkgSetting.isSystem(), pkg.isEnabled(), a,
-                            aflags)) {
+                    if (PackageUserStateUtils.isMatch(state, pkgSetting.isSystem(), pkg.isEnabled(),
+                            a.isEnabled(), a.isDirectBootAware(), a.getName(), aflags)) {
                         if (PackageManager.APP_DETAILS_ACTIVITY_CLASS_NAME.equals(
                                 a.getName())) {
                             continue;
@@ -293,8 +294,8 @@
                 final ActivityInfo[] res = new ActivityInfo[size];
                 for (int i = 0; i < size; i++) {
                     final ParsedActivity a = pkg.getReceivers().get(i);
-                    if (ComponentParseUtils.isMatch(state, pkgSetting.isSystem(), pkg.isEnabled(), a,
-                            flags)) {
+                    if (PackageUserStateUtils.isMatch(state, pkgSetting.isSystem(), pkg.isEnabled(),
+                            a.isEnabled(), a.isDirectBootAware(), a.getName(), flags)) {
                         res[num++] = generateActivityInfo(pkg, a, flags, state, applicationInfo,
                                 userId, pkgSetting);
                     }
@@ -309,8 +310,8 @@
                 final ServiceInfo[] res = new ServiceInfo[size];
                 for (int i = 0; i < size; i++) {
                     final ParsedService s = pkg.getServices().get(i);
-                    if (ComponentParseUtils.isMatch(state, pkgSetting.isSystem(), pkg.isEnabled(), s,
-                            flags)) {
+                    if (PackageUserStateUtils.isMatch(state, pkgSetting.isSystem(), pkg.isEnabled(),
+                            s.isEnabled(), s.isDirectBootAware(), s.getName(), flags)) {
                         res[num++] = generateServiceInfo(pkg, s, flags, state, applicationInfo,
                                 userId, pkgSetting);
                     }
@@ -326,8 +327,8 @@
                 for (int i = 0; i < size; i++) {
                     final ParsedProvider pr = pkg.getProviders()
                             .get(i);
-                    if (ComponentParseUtils.isMatch(state, pkgSetting.isSystem(), pkg.isEnabled(), pr,
-                            flags)) {
+                    if (PackageUserStateUtils.isMatch(state, pkgSetting.isSystem(), pkg.isEnabled(),
+                            pr.isEnabled(), pr.isDirectBootAware(), pr.getName(), flags)) {
                         res[num++] = generateProviderInfo(pkg, pr, flags, state, applicationInfo,
                                 userId, pkgSetting);
                     }
@@ -923,7 +924,7 @@
                 | flag(pkg.isExtraLargeScreensSupported(), ApplicationInfo.FLAG_SUPPORTS_XLARGE_SCREENS)
                 | flag(pkg.isResizeable(), ApplicationInfo.FLAG_RESIZEABLE_FOR_SCREENS)
                 | flag(pkg.isAnyDensity(), ApplicationInfo.FLAG_SUPPORTS_SCREEN_DENSITIES)
-                | flag(AndroidPackageUtils.isSystem(pkg), ApplicationInfo.FLAG_SYSTEM)
+                | flag(AndroidPackageLegacyUtils.isSystem(pkg), ApplicationInfo.FLAG_SYSTEM)
                 | flag(pkg.isFactoryTest(), ApplicationInfo.FLAG_FACTORY_TEST);
 
         return appInfoFlags(pkgWithoutStateFlags, pkgSetting);
@@ -964,12 +965,12 @@
                 | flag(pkg.isSaveStateDisallowed(), ApplicationInfo.PRIVATE_FLAG_CANT_SAVE_STATE)
                 | flag(pkg.isResizeableActivityViaSdkVersion(), ApplicationInfo.PRIVATE_FLAG_ACTIVITIES_RESIZE_MODE_RESIZEABLE_VIA_SDK_VERSION)
                 | flag(pkg.isAllowNativeHeapPointerTagging(), ApplicationInfo.PRIVATE_FLAG_ALLOW_NATIVE_HEAP_POINTER_TAGGING)
-                | flag(AndroidPackageUtils.isSystemExt(pkg), ApplicationInfo.PRIVATE_FLAG_SYSTEM_EXT)
-                | flag(AndroidPackageUtils.isPrivileged(pkg), ApplicationInfo.PRIVATE_FLAG_PRIVILEGED)
-                | flag(AndroidPackageUtils.isOem(pkg), ApplicationInfo.PRIVATE_FLAG_OEM)
-                | flag(AndroidPackageUtils.isVendor(pkg), ApplicationInfo.PRIVATE_FLAG_VENDOR)
-                | flag(AndroidPackageUtils.isProduct(pkg), ApplicationInfo.PRIVATE_FLAG_PRODUCT)
-                | flag(AndroidPackageUtils.isOdm(pkg), ApplicationInfo.PRIVATE_FLAG_ODM)
+                | flag(AndroidPackageLegacyUtils.isSystemExt(pkg), ApplicationInfo.PRIVATE_FLAG_SYSTEM_EXT)
+                | flag(AndroidPackageLegacyUtils.isPrivileged(pkg), ApplicationInfo.PRIVATE_FLAG_PRIVILEGED)
+                | flag(AndroidPackageLegacyUtils.isOem(pkg), ApplicationInfo.PRIVATE_FLAG_OEM)
+                | flag(AndroidPackageLegacyUtils.isVendor(pkg), ApplicationInfo.PRIVATE_FLAG_VENDOR)
+                | flag(AndroidPackageLegacyUtils.isProduct(pkg), ApplicationInfo.PRIVATE_FLAG_PRODUCT)
+                | flag(AndroidPackageLegacyUtils.isOdm(pkg), ApplicationInfo.PRIVATE_FLAG_ODM)
                 | flag(pkg.isSignedWithPlatformKey(), ApplicationInfo.PRIVATE_FLAG_SIGNED_WITH_PLATFORM_KEY);
 
         Boolean resizeableActivity = pkg.getResizeableActivity();
diff --git a/services/core/java/com/android/server/pm/parsing/PackageParser2.java b/services/core/java/com/android/server/pm/parsing/PackageParser2.java
index 1c751e0..b6a08a5 100644
--- a/services/core/java/com/android/server/pm/parsing/PackageParser2.java
+++ b/services/core/java/com/android/server/pm/parsing/PackageParser2.java
@@ -35,17 +35,19 @@
 import android.util.Slog;
 
 import com.android.internal.compat.IPlatformCompat;
+import com.android.internal.pm.parsing.pkg.PackageImpl;
 import com.android.internal.pm.parsing.pkg.ParsedPackage;
 import com.android.internal.pm.pkg.parsing.ParsingPackage;
+import com.android.internal.pm.pkg.parsing.ParsingPackageUtils;
+import com.android.internal.pm.pkg.parsing.ParsingUtils;
 import com.android.internal.util.ArrayUtils;
+import com.android.server.SystemConfig;
 import com.android.server.pm.PackageManagerException;
 import com.android.server.pm.PackageManagerService;
-import com.android.server.pm.parsing.pkg.PackageImpl;
-import com.android.server.pm.pkg.parsing.ParsingPackageUtils;
-import com.android.server.pm.pkg.parsing.ParsingUtils;
 
 import java.io.File;
 import java.util.List;
+import java.util.Set;
 
 /**
  * The v2 of package parsing for use when parsing is initiated in the server and must
@@ -88,6 +90,16 @@
                 // behavior.
                 return false;
             }
+
+            @Override
+            public Set<String> getHiddenApiWhitelistedApps() {
+                return SystemConfig.getInstance().getHiddenApiWhitelistedApps();
+            }
+
+            @Override
+            public Set<String> getInstallConstraintsAllowlist() {
+                return SystemConfig.getInstance().getInstallConstraintsAllowlist();
+            }
         });
     }
 
@@ -221,7 +233,7 @@
                 @NonNull String baseCodePath, @NonNull String codePath,
                 @NonNull TypedArray manifestArray, boolean isCoreApp) {
             return PackageImpl.forParsing(packageName, baseCodePath, codePath, manifestArray,
-                    isCoreApp);
+                    isCoreApp, Callback.this);
         }
 
         /**
diff --git a/services/core/java/com/android/server/pm/parsing/pkg/AndroidPackageUtils.java b/services/core/java/com/android/server/pm/parsing/pkg/AndroidPackageUtils.java
index 61be6e1..1b7c7ad 100644
--- a/services/core/java/com/android/server/pm/parsing/pkg/AndroidPackageUtils.java
+++ b/services/core/java/com/android/server/pm/parsing/pkg/AndroidPackageUtils.java
@@ -29,7 +29,9 @@
 import android.os.incremental.IncrementalManager;
 
 import com.android.internal.content.NativeLibraryHelper;
+import com.android.internal.pm.parsing.PackageParserException;
 import com.android.internal.pm.parsing.pkg.AndroidPackageHidden;
+import com.android.internal.pm.parsing.pkg.PackageImpl;
 import com.android.internal.pm.pkg.component.ParsedActivity;
 import com.android.internal.pm.pkg.component.ParsedInstrumentation;
 import com.android.internal.pm.pkg.component.ParsedProvider;
@@ -37,7 +39,6 @@
 import com.android.internal.pm.pkg.parsing.ParsingPackageHidden;
 import com.android.internal.util.ArrayUtils;
 import com.android.server.SystemConfig;
-import com.android.server.pm.PackageManagerException;
 import com.android.server.pm.pkg.AndroidPackage;
 import com.android.server.pm.pkg.PackageState;
 import com.android.server.pm.pkg.PackageStateInternal;
@@ -134,10 +135,10 @@
     /**
      * Validate the dex metadata files installed for the given package.
      *
-     * @throws PackageManagerException in case of errors.
+     * @throws PackageParserException in case of errors.
      */
     public static void validatePackageDexMetadata(AndroidPackage pkg)
-            throws PackageManagerException {
+            throws PackageParserException {
         Collection<String> apkToDexMetadataList = getPackageDexMetadata(pkg).values();
         String packageName = pkg.getPackageName();
         long versionCode = pkg.getLongVersionCode();
@@ -146,7 +147,7 @@
             final ParseResult result = DexMetadataHelper.validateDexMetadataFile(
                     input.reset(), dexMetadata, packageName, versionCode);
             if (result.isError()) {
-                throw new PackageManagerException(
+                throw new PackageParserException(
                         result.getErrorCode(), result.getErrorMessage(), result.getException());
             }
         }
@@ -314,60 +315,4 @@
         info.versionCode = ((ParsingPackageHidden) pkg).getVersionCode();
         info.versionCodeMajor = ((ParsingPackageHidden) pkg).getVersionCodeMajor();
     }
-
-    /**
-     * @deprecated Use {@link PackageState#isSystem}
-     */
-    @Deprecated
-    public static boolean isSystem(@NonNull AndroidPackage pkg) {
-        return ((AndroidPackageHidden) pkg).isSystem();
-    }
-
-    /**
-     * @deprecated Use {@link PackageState#isSystemExt}
-     */
-    @Deprecated
-    public static boolean isSystemExt(@NonNull AndroidPackage pkg) {
-        return ((AndroidPackageHidden) pkg).isSystemExt();
-    }
-
-    /**
-     * @deprecated Use {@link PackageState#isPrivileged}
-     */
-    @Deprecated
-    public static boolean isPrivileged(@NonNull AndroidPackage pkg) {
-        return ((AndroidPackageHidden) pkg).isPrivileged();
-    }
-
-    /**
-     * @deprecated Use {@link PackageState#isOem}
-     */
-    @Deprecated
-    public static boolean isOem(@NonNull AndroidPackage pkg) {
-        return ((AndroidPackageHidden) pkg).isOem();
-    }
-
-    /**
-     * @deprecated Use {@link PackageState#isVendor}
-     */
-    @Deprecated
-    public static boolean isVendor(@NonNull AndroidPackage pkg) {
-        return ((AndroidPackageHidden) pkg).isVendor();
-    }
-
-    /**
-     * @deprecated Use {@link PackageState#isProduct}
-     */
-    @Deprecated
-    public static boolean isProduct(@NonNull AndroidPackage pkg) {
-        return ((AndroidPackageHidden) pkg).isProduct();
-    }
-
-    /**
-     * @deprecated Use {@link PackageState#isOdm}
-     */
-    @Deprecated
-    public static boolean isOdm(@NonNull AndroidPackage pkg) {
-        return ((AndroidPackageHidden) pkg).isOdm();
-    }
 }
diff --git a/services/core/java/com/android/server/pm/permission/PermissionManagerServiceImpl.java b/services/core/java/com/android/server/pm/permission/PermissionManagerServiceImpl.java
index 8bd2d94..671e031 100644
--- a/services/core/java/com/android/server/pm/permission/PermissionManagerServiceImpl.java
+++ b/services/core/java/com/android/server/pm/permission/PermissionManagerServiceImpl.java
@@ -120,8 +120,11 @@
 import com.android.internal.logging.MetricsLogger;
 import com.android.internal.logging.nano.MetricsProto;
 import com.android.internal.os.RoSystemProperties;
+import com.android.internal.pm.permission.CompatibilityPermissionInfo;
+import com.android.internal.pm.pkg.component.ComponentMutateUtils;
 import com.android.internal.pm.pkg.component.ParsedPermission;
 import com.android.internal.pm.pkg.component.ParsedPermissionGroup;
+import com.android.internal.pm.pkg.component.ParsedPermissionUtils;
 import com.android.internal.util.ArrayUtils;
 import com.android.internal.util.CollectionUtils;
 import com.android.internal.util.IntPair;
@@ -144,8 +147,6 @@
 import com.android.server.pm.pkg.PackageState;
 import com.android.server.pm.pkg.PackageStateInternal;
 import com.android.server.pm.pkg.SharedUserApi;
-import com.android.server.pm.pkg.component.ComponentMutateUtils;
-import com.android.server.pm.pkg.component.ParsedPermissionUtils;
 import com.android.server.policy.PermissionPolicyInternal;
 import com.android.server.policy.SoftRestrictedPermissionPolicy;
 
diff --git a/services/core/java/com/android/server/pm/pkg/SELinuxUtil.java b/services/core/java/com/android/server/pm/pkg/SELinuxUtil.java
index 6cbc1de..6a15641 100644
--- a/services/core/java/com/android/server/pm/pkg/SELinuxUtil.java
+++ b/services/core/java/com/android/server/pm/pkg/SELinuxUtil.java
@@ -16,6 +16,8 @@
 
 package com.android.server.pm.pkg;
 
+import com.android.internal.pm.pkg.SEInfoUtil;
+
 /**
  * Utility methods that need to be used in application space.
  * @hide
@@ -23,10 +25,10 @@
 public final class SELinuxUtil {
 
     /** Append to existing seinfo label for instant apps @hide */
-    private static final String INSTANT_APP_STR = ":ephemeralapp";
+    private static final String INSTANT_APP_STR = SEInfoUtil.INSTANT_APP_STR;
 
     /** Append to existing seinfo when modifications are complete @hide */
-    public static final String COMPLETE_STR = ":complete";
+    public static final String COMPLETE_STR = SEInfoUtil.COMPLETE_STR;
 
     /** @hide */
     public static String getSeinfoUser(PackageUserState userState) {
diff --git a/services/core/java/com/android/server/pm/resolution/ComponentResolver.java b/services/core/java/com/android/server/pm/resolution/ComponentResolver.java
index 532a7f8..c9da99d 100644
--- a/services/core/java/com/android/server/pm/resolution/ComponentResolver.java
+++ b/services/core/java/com/android/server/pm/resolution/ComponentResolver.java
@@ -45,11 +45,13 @@
 import android.util.Slog;
 
 import com.android.internal.annotations.GuardedBy;
+import com.android.internal.pm.pkg.component.ComponentMutateUtils;
 import com.android.internal.pm.pkg.component.ParsedActivity;
 import com.android.internal.pm.pkg.component.ParsedComponent;
 import com.android.internal.pm.pkg.component.ParsedIntentInfo;
 import com.android.internal.pm.pkg.component.ParsedMainComponent;
 import com.android.internal.pm.pkg.component.ParsedProvider;
+import com.android.internal.pm.pkg.component.ParsedProviderImpl;
 import com.android.internal.pm.pkg.component.ParsedService;
 import com.android.internal.util.ArrayUtils;
 import com.android.server.IntentResolver;
@@ -62,8 +64,6 @@
 import com.android.server.pm.pkg.PackageStateInternal;
 import com.android.server.pm.pkg.PackageStateUtils;
 import com.android.server.pm.pkg.PackageUserStateInternal;
-import com.android.server.pm.pkg.component.ComponentMutateUtils;
-import com.android.server.pm.pkg.component.ParsedProviderImpl;
 import com.android.server.pm.snapshot.PackageDataSnapshot;
 import com.android.server.utils.Snappable;
 import com.android.server.utils.SnapshotCache;
diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java
index 4e5dc1d..938ed23 100644
--- a/services/core/java/com/android/server/policy/PhoneWindowManager.java
+++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java
@@ -76,7 +76,6 @@
 import static android.view.contentprotection.flags.Flags.createAccessibilityOverlayAppOpEnabled;
 
 import static com.android.internal.config.sysui.SystemUiDeviceConfigFlags.SCREENSHOT_KEYCHORD_DELAY;
-import static com.android.internal.util.FrameworkStatsLog.ACCESSIBILITY_SHORTCUT_REPORTED__SHORTCUT_TYPE__A11Y_WEAR_TRIPLE_PRESS_GESTURE;
 import static com.android.server.policy.WindowManagerPolicy.WindowManagerFuncs.CAMERA_LENS_COVERED;
 import static com.android.server.policy.WindowManagerPolicy.WindowManagerFuncs.CAMERA_LENS_COVER_ABSENT;
 import static com.android.server.policy.WindowManagerPolicy.WindowManagerFuncs.CAMERA_LENS_UNCOVERED;
@@ -97,7 +96,6 @@
 import static com.android.server.wm.WindowManagerPolicyProto.WINDOW_MANAGER_DRAW_COMPLETE;
 
 import android.accessibilityservice.AccessibilityService;
-import android.accessibilityservice.AccessibilityServiceInfo;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.SuppressLint;
@@ -124,7 +122,6 @@
 import android.content.pm.ApplicationInfo;
 import android.content.pm.PackageManager;
 import android.content.pm.ResolveInfo;
-import android.content.pm.ServiceInfo;
 import android.content.res.Configuration;
 import android.content.res.Resources;
 import android.database.ContentObserver;
@@ -204,8 +201,6 @@
 
 import com.android.internal.R;
 import com.android.internal.accessibility.AccessibilityShortcutController;
-import com.android.internal.accessibility.util.AccessibilityStatsLogUtils;
-import com.android.internal.accessibility.util.AccessibilityUtils;
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.app.AssistUtils;
 import com.android.internal.display.BrightnessUtils;
@@ -385,8 +380,6 @@
 
     public static final String TRACE_WAIT_FOR_ALL_WINDOWS_DRAWN_METHOD = "waitForAllWindowsDrawn";
 
-    private static final String TALKBACK_LABEL = "TalkBack";
-
     private static final int POWER_BUTTON_SUPPRESSION_DELAY_DEFAULT_MILLIS = 800;
 
     /**
@@ -477,6 +470,8 @@
     /** Controller that supports enabling an AccessibilityService by holding down the volume keys */
     private AccessibilityShortcutController mAccessibilityShortcutController;
 
+    private TalkbackShortcutController mTalkbackShortcutController;
+
     boolean mSafeMode;
 
     // Whether to allow dock apps with METADATA_DOCK_HOME to temporarily take over the Home key.
@@ -813,7 +808,10 @@
                     handleScreenShot(msg.arg1);
                     break;
                 case MSG_SWITCH_KEYBOARD_LAYOUT:
-                    handleSwitchKeyboardLayout(msg.arg1, msg.arg2);
+                    SwitchKeyboardLayoutMessageObject object =
+                            (SwitchKeyboardLayoutMessageObject) msg.obj;
+                    handleSwitchKeyboardLayout(object.keyEvent, object.direction,
+                            object.focusedToken);
                     break;
                 case MSG_LOG_KEYBOARD_SYSTEM_EVENT:
                     handleKeyboardSystemEvent(KeyboardLogEvent.from(msg.arg1), (KeyEvent) msg.obj);
@@ -934,6 +932,10 @@
         }
     }
 
+    private record SwitchKeyboardLayoutMessageObject(KeyEvent keyEvent, IBinder focusedToken,
+                                                     int direction) {
+    }
+
     final IPersistentVrStateCallbacks mPersistentVrModeListener =
             new IPersistentVrStateCallbacks.Stub() {
         @Override
@@ -1602,19 +1604,11 @@
                 if (DEBUG_INPUT) {
                     Slog.d(TAG, "Executing stem primary triple press action behavior.");
                 }
-
-                if (Settings.System.getIntForUser(mContext.getContentResolver(),
-                        Settings.System.WEAR_ACCESSIBILITY_GESTURE_ENABLED,
-                        /* def= */ 0, UserHandle.USER_CURRENT) == 1) {
-                    /** Toggle talkback begin */
-                    ComponentName componentName = getTalkbackComponent();
-                    if (componentName != null && toggleTalkBack(componentName)) {
-                        /** log stem triple press telemetry if it's a talkback enabled event */
-                        logStemTriplePressAccessibilityTelemetry(componentName);
-                    }
-                    performHapticFeedback(HapticFeedbackConstants.CONFIRM, /* always = */ false,
-                        /* reason = */ "Stem primary - Triple Press - Toggle Accessibility");
-                    /** Toggle talkback end */
+                mTalkbackShortcutController.toggleTalkback(mCurrentUserId);
+                if (mTalkbackShortcutController.isTalkBackShortcutGestureEnabled()) {
+                    performHapticFeedback(HapticFeedbackConstants.CONFIRM, /* always = */
+                            false, /* reason = */
+                            "Stem primary - Triple Press - Toggle Accessibility");
                 }
                 break;
         }
@@ -1640,61 +1634,6 @@
     }
 
     /**
-     * A function that toggles talkback service
-     *
-     * @return {@code true} if talkback is enabled, {@code false} if talkback is disabled
-     */
-    private boolean toggleTalkBack(ComponentName componentName) {
-        final Set<ComponentName> enabledServices =
-                AccessibilityUtils.getEnabledServicesFromSettings(mContext, mCurrentUserId);
-
-        boolean isTalkbackAlreadyEnabled = enabledServices.contains(componentName);
-        AccessibilityUtils.setAccessibilityServiceState(mContext, componentName,
-                !isTalkbackAlreadyEnabled);
-        /** if isTalkbackAlreadyEnabled is true, then it's a disabled event so return false
-         * and if isTalkbackAlreadyEnabled is false, return true as it's an enabled event */
-        return !isTalkbackAlreadyEnabled;
-    }
-
-    /**
-     * A function that logs stem triple press accessibility telemetry
-     * If the user setup (Oobe) is not completed, set the
-     * WEAR_ACCESSIBILITY_GESTURE_ENABLED_DURING_OOBE
-     * setting which will be later logged via Settings Snapshot
-     * else, log ACCESSIBILITY_SHORTCUT_REPORTED atom
-     */
-    private void logStemTriplePressAccessibilityTelemetry(ComponentName componentName) {
-        if (!AccessibilityUtils.isUserSetupCompleted(mContext)) {
-            Settings.Secure.putInt(mContext.getContentResolver(),
-                    Settings.System.WEAR_ACCESSIBILITY_GESTURE_ENABLED_DURING_OOBE, 1);
-        } else {
-            AccessibilityStatsLogUtils.logAccessibilityShortcutActivated(mContext, componentName,
-                    ACCESSIBILITY_SHORTCUT_REPORTED__SHORTCUT_TYPE__A11Y_WEAR_TRIPLE_PRESS_GESTURE,
-                    /* serviceEnabled= */ true);
-        }
-    }
-
-    private ComponentName getTalkbackComponent() {
-        AccessibilityManager accessibilityManager = mContext.getSystemService(
-                AccessibilityManager.class);
-        List<AccessibilityServiceInfo> serviceInfos =
-                accessibilityManager.getInstalledAccessibilityServiceList();
-
-        for (AccessibilityServiceInfo service : serviceInfos) {
-            final ServiceInfo serviceInfo = service.getResolveInfo().serviceInfo;
-            if (isTalkback(serviceInfo)) {
-                return new ComponentName(serviceInfo.packageName, serviceInfo.name);
-            }
-        }
-        return null;
-    }
-
-    private boolean isTalkback(ServiceInfo info) {
-        String label = info.loadLabel(mPackageManager).toString();
-        return label.equals(TALKBACK_LABEL);
-    }
-
-    /**
      * Load most recent task (expect current task) and bring it to the front.
      */
     void performStemPrimaryDoublePressSwitchToRecentTask() {
@@ -1731,12 +1670,7 @@
             case TRIPLE_PRESS_PRIMARY_NOTHING:
                 break;
             case TRIPLE_PRESS_PRIMARY_TOGGLE_ACCESSIBILITY:
-                if (Settings.System.getIntForUser(
-                                mContext.getContentResolver(),
-                                Settings.System.WEAR_ACCESSIBILITY_GESTURE_ENABLED,
-                                /* def= */ 0,
-                                UserHandle.USER_CURRENT)
-                        == 1) {
+                if (mTalkbackShortcutController.isTalkBackShortcutGestureEnabled()) {
                     return 3;
                 }
                 break;
@@ -2252,6 +2186,10 @@
         ButtonOverridePermissionChecker getButtonOverridePermissionChecker() {
             return new ButtonOverridePermissionChecker();
         }
+
+        TalkbackShortcutController getTalkbackShortcutController() {
+            return new TalkbackShortcutController(mContext);
+        }
     }
 
     /** {@inheritDoc} */
@@ -2515,6 +2453,7 @@
         mKeyguardDrawnTimeout = mContext.getResources().getInteger(
                 com.android.internal.R.integer.config_keyguardDrawnTimeout);
         mKeyguardDelegate = injector.getKeyguardServiceDelegate();
+        mTalkbackShortcutController = injector.getTalkbackShortcutController();
         initKeyCombinationRules();
         initSingleKeyGestureRules(injector.getLooper());
         mButtonOverridePermissionChecker = injector.getButtonOverridePermissionChecker();
@@ -3709,7 +3648,7 @@
             case KeyEvent.KEYCODE_LANGUAGE_SWITCH:
                 if (firstDown) {
                     int direction = (metaState & KeyEvent.META_SHIFT_MASK) != 0 ? -1 : 1;
-                    sendSwitchKeyboardLayout(event, direction);
+                    sendSwitchKeyboardLayout(event, focusedToken, direction);
                     logKeyboardSystemsEvent(event, KeyboardLogEvent.LANGUAGE_SWITCH);
                     return true;
                 }
@@ -3978,7 +3917,7 @@
                     + ", policyFlags=" + policyFlags);
         }
 
-        if (interceptUnhandledKey(event)) {
+        if (interceptUnhandledKey(event, focusedToken)) {
             return null;
         }
 
@@ -4036,7 +3975,7 @@
         return fallbackEvent;
     }
 
-    private boolean interceptUnhandledKey(KeyEvent event) {
+    private boolean interceptUnhandledKey(KeyEvent event, IBinder focusedToken) {
         final int keyCode = event.getKeyCode();
         final int repeatCount = event.getRepeatCount();
         final boolean down = event.getAction() == KeyEvent.ACTION_DOWN;
@@ -4049,7 +3988,7 @@
                     if (KeyEvent.metaStateHasModifiers(metaState & ~KeyEvent.META_SHIFT_MASK,
                             KeyEvent.META_CTRL_ON)) {
                         int direction = (metaState & KeyEvent.META_SHIFT_MASK) != 0 ? -1 : 1;
-                        sendSwitchKeyboardLayout(event, direction);
+                        sendSwitchKeyboardLayout(event, focusedToken, direction);
                         return true;
                     }
                 }
@@ -4105,16 +4044,22 @@
         }
     }
 
-    private void sendSwitchKeyboardLayout(@NonNull KeyEvent event, int direction) {
-        mHandler.obtainMessage(MSG_SWITCH_KEYBOARD_LAYOUT, event.getDeviceId(),
-                direction).sendToTarget();
+    private void sendSwitchKeyboardLayout(@NonNull KeyEvent event,
+            @Nullable IBinder focusedToken, int direction) {
+        SwitchKeyboardLayoutMessageObject object =
+                new SwitchKeyboardLayoutMessageObject(event, focusedToken, direction);
+        mHandler.obtainMessage(MSG_SWITCH_KEYBOARD_LAYOUT, object).sendToTarget();
     }
 
-    private void handleSwitchKeyboardLayout(int deviceId, int direction) {
+    private void handleSwitchKeyboardLayout(@NonNull KeyEvent event, int direction,
+            IBinder focusedToken) {
         if (FeatureFlagUtils.isEnabled(mContext, FeatureFlagUtils.SETTINGS_NEW_KEYBOARD_UI)) {
-            InputMethodManagerInternal.get().switchKeyboardLayout(direction);
+            IBinder targetWindowToken =
+                    mWindowManagerInternal.getTargetWindowTokenFromInputToken(focusedToken);
+            InputMethodManagerInternal.get().onSwitchKeyboardLayoutShortcut(direction,
+                    event.getDisplayId(), targetWindowToken);
         } else {
-            mWindowManagerFuncs.switchKeyboardLayout(deviceId, direction);
+            mWindowManagerFuncs.switchKeyboardLayout(event.getDeviceId(), direction);
         }
     }
 
@@ -4124,7 +4069,7 @@
         if ((actions & ACTION_PASS_TO_USER) != 0) {
             long delayMillis = interceptKeyBeforeDispatching(
                     focusedToken, fallbackEvent, policyFlags);
-            if (delayMillis == 0 && !interceptUnhandledKey(fallbackEvent)) {
+            if (delayMillis == 0 && !interceptUnhandledKey(fallbackEvent, focusedToken)) {
                 return true;
             }
         }
diff --git a/services/core/java/com/android/server/policy/TalkbackShortcutController.java b/services/core/java/com/android/server/policy/TalkbackShortcutController.java
new file mode 100644
index 0000000..906da2f
--- /dev/null
+++ b/services/core/java/com/android/server/policy/TalkbackShortcutController.java
@@ -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.server.policy;
+
+import static com.android.internal.util.FrameworkStatsLog.ACCESSIBILITY_SHORTCUT_REPORTED__SHORTCUT_TYPE__A11Y_WEAR_TRIPLE_PRESS_GESTURE;
+
+import android.accessibilityservice.AccessibilityServiceInfo;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.pm.PackageManager;
+import android.content.pm.ServiceInfo;
+import android.os.UserHandle;
+import android.provider.Settings;
+import android.view.accessibility.AccessibilityManager;
+
+import com.android.internal.accessibility.util.AccessibilityStatsLogUtils;
+import com.android.internal.accessibility.util.AccessibilityUtils;
+import com.android.internal.annotations.VisibleForTesting;
+
+import java.util.List;
+import java.util.Set;
+
+/**
+ * This class controls talkback shortcut related operations such as toggling, quering and
+ * logging.
+ */
+@VisibleForTesting
+class TalkbackShortcutController {
+    private static final String TALKBACK_LABEL = "TalkBack";
+    private final Context mContext;
+    private final PackageManager mPackageManager;
+
+    TalkbackShortcutController(Context context) {
+        mContext = context;
+        mPackageManager = mContext.getPackageManager();
+    }
+
+    /**
+     * A function that toggles talkback service.
+     *
+     * @return talkback state after toggle. {@code true} if talkback is enabled, {@code false} if
+     * talkback is disabled
+     */
+    boolean toggleTalkback(int userId) {
+        final Set<ComponentName> enabledServices =
+                AccessibilityUtils.getEnabledServicesFromSettings(mContext, userId);
+        ComponentName componentName = getTalkbackComponent();
+        boolean isTalkbackAlreadyEnabled = enabledServices.contains(componentName);
+
+        if (isTalkBackShortcutGestureEnabled()) {
+            isTalkbackAlreadyEnabled = !isTalkbackAlreadyEnabled;
+            AccessibilityUtils.setAccessibilityServiceState(mContext, componentName,
+                    isTalkbackAlreadyEnabled);
+
+            // log stem triple press telemetry if it's a talkback enabled event.
+            if (componentName != null && isTalkbackAlreadyEnabled) {
+                logStemTriplePressAccessibilityTelemetry(componentName);
+            }
+        }
+        return isTalkbackAlreadyEnabled;
+    }
+
+    private ComponentName getTalkbackComponent() {
+        AccessibilityManager accessibilityManager = mContext.getSystemService(
+                AccessibilityManager.class);
+        List<AccessibilityServiceInfo> serviceInfos =
+                accessibilityManager.getInstalledAccessibilityServiceList();
+
+        for (AccessibilityServiceInfo service : serviceInfos) {
+            final ServiceInfo serviceInfo = service.getResolveInfo().serviceInfo;
+            if (isTalkback(serviceInfo)) {
+                return new ComponentName(serviceInfo.packageName, serviceInfo.name);
+            }
+        }
+        return null;
+    }
+
+    boolean isTalkBackShortcutGestureEnabled() {
+        return Settings.System.getIntForUser(mContext.getContentResolver(),
+                Settings.System.WEAR_ACCESSIBILITY_GESTURE_ENABLED,
+                /* def= */ 0, UserHandle.USER_CURRENT) == 1;
+    }
+
+    /**
+     * A function that logs stem triple press accessibility telemetry. If the user setup (Oobe)
+     * is not completed, set the WEAR_ACCESSIBILITY_GESTURE_ENABLED_DURING_OOBE setting which
+     * will be later logged via Settings Snapshot  else, log ACCESSIBILITY_SHORTCUT_REPORTED atom
+     */
+    private void logStemTriplePressAccessibilityTelemetry(ComponentName componentName) {
+        if (!AccessibilityUtils.isUserSetupCompleted(mContext)) {
+            Settings.Secure.putInt(mContext.getContentResolver(),
+                    Settings.System.WEAR_ACCESSIBILITY_GESTURE_ENABLED_DURING_OOBE, 1);
+            return;
+        }
+        AccessibilityStatsLogUtils.logAccessibilityShortcutActivated(mContext,
+                componentName,
+                ACCESSIBILITY_SHORTCUT_REPORTED__SHORTCUT_TYPE__A11Y_WEAR_TRIPLE_PRESS_GESTURE,
+                /* serviceEnabled= */ true);
+    }
+
+    private boolean isTalkback(ServiceInfo info) {
+        return TALKBACK_LABEL.equals(info.loadLabel(mPackageManager).toString());
+    }
+}
diff --git a/services/core/java/com/android/server/power/stats/PowerStatsCollector.java b/services/core/java/com/android/server/power/stats/PowerStatsCollector.java
index abfe9de..e1eb8f0 100644
--- a/services/core/java/com/android/server/power/stats/PowerStatsCollector.java
+++ b/services/core/java/com/android/server/power/stats/PowerStatsCollector.java
@@ -20,7 +20,6 @@
 import android.os.ConditionVariable;
 import android.os.Handler;
 import android.os.PersistableBundle;
-import android.util.FastImmutableArraySet;
 import android.util.IndentingPrintWriter;
 import android.util.Slog;
 
@@ -30,8 +29,9 @@
 
 import java.io.PrintWriter;
 import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
 import java.util.function.Consumer;
-import java.util.stream.Stream;
 
 /**
  * Collects snapshots of power-related system statistics.
@@ -246,8 +246,7 @@
 
     @GuardedBy("this")
     @SuppressWarnings("unchecked")
-    private volatile FastImmutableArraySet<Consumer<PowerStats>> mConsumerList =
-            new FastImmutableArraySet<Consumer<PowerStats>>(new Consumer[0]);
+    private volatile List<Consumer<PowerStats>> mConsumerList = Collections.emptyList();
 
     public PowerStatsCollector(Handler handler, long throttlePeriodMs, Clock clock) {
         mHandler = handler;
@@ -262,9 +261,13 @@
     @SuppressWarnings("unchecked")
     public void addConsumer(Consumer<PowerStats> consumer) {
         synchronized (this) {
-            mConsumerList = new FastImmutableArraySet<Consumer<PowerStats>>(
-                    Stream.concat(mConsumerList.stream(), Stream.of(consumer))
-                            .toArray(Consumer[]::new));
+            if (mConsumerList.contains(consumer)) {
+                return;
+            }
+
+            List<Consumer<PowerStats>> newList = new ArrayList<>(mConsumerList);
+            newList.add(consumer);
+            mConsumerList = Collections.unmodifiableList(newList);
         }
     }
 
@@ -275,9 +278,9 @@
     @SuppressWarnings("unchecked")
     public void removeConsumer(Consumer<PowerStats> consumer) {
         synchronized (this) {
-            mConsumerList = new FastImmutableArraySet<Consumer<PowerStats>>(
-                    mConsumerList.stream().filter(c -> c != consumer)
-                            .toArray(Consumer[]::new));
+            List<Consumer<PowerStats>> newList = new ArrayList<>(mConsumerList);
+            newList.remove(consumer);
+            mConsumerList = Collections.unmodifiableList(newList);
         }
     }
 
@@ -302,8 +305,9 @@
         if (stats == null) {
             return;
         }
-        for (Consumer<PowerStats> consumer : mConsumerList) {
-            consumer.accept(stats);
+        List<Consumer<PowerStats>> consumerList = mConsumerList;
+        for (int i = consumerList.size() - 1; i >= 0; i--) {
+            consumerList.get(i).accept(stats);
         }
     }
 
diff --git a/services/core/java/com/android/server/power/stats/PowerStatsScheduler.java b/services/core/java/com/android/server/power/stats/PowerStatsScheduler.java
index 97d872a..121a98b 100644
--- a/services/core/java/com/android/server/power/stats/PowerStatsScheduler.java
+++ b/services/core/java/com/android/server/power/stats/PowerStatsScheduler.java
@@ -18,7 +18,6 @@
 
 import android.annotation.DurationMillisLong;
 import android.app.AlarmManager;
-import android.content.Context;
 import android.os.ConditionVariable;
 import android.os.Handler;
 import android.util.IndentingPrintWriter;
@@ -30,6 +29,7 @@
 import java.io.PrintWriter;
 import java.util.Calendar;
 import java.util.concurrent.TimeUnit;
+import java.util.function.Supplier;
 
 /**
  * Controls the frequency at which {@link PowerStatsSpan}'s are generated and stored in
@@ -39,7 +39,7 @@
     private static final long MINUTE_IN_MILLIS = TimeUnit.MINUTES.toMillis(1);
     private static final long HOUR_IN_MILLIS = TimeUnit.HOURS.toMillis(1);
 
-    private final Context mContext;
+    private final AlarmScheduler mAlarmScheduler;
     private boolean mEnablePeriodicPowerStatsCollection;
     @DurationMillisLong
     private final long mAggregatedPowerStatsSpanDuration;
@@ -49,24 +49,38 @@
     private final Clock mClock;
     private final MonotonicClock mMonotonicClock;
     private final Handler mHandler;
-    private final BatteryStatsImpl mBatteryStats;
+    private final Runnable mPowerStatsCollector;
+    private final Supplier<Long> mEarliestAvailableBatteryHistoryTimeMs;
     private final PowerStatsAggregator mPowerStatsAggregator;
     private long mLastSavedSpanEndMonotonicTime;
 
-    public PowerStatsScheduler(Context context, PowerStatsAggregator powerStatsAggregator,
+    /**
+     * External dependency on AlarmManager.
+     */
+    public interface AlarmScheduler {
+        /**
+         * Should use AlarmManager to schedule an inexact, non-wakeup alarm.
+         */
+        void scheduleAlarm(long triggerAtMillis, String tag,
+                AlarmManager.OnAlarmListener onAlarmListener, Handler handler);
+    }
+
+    public PowerStatsScheduler(Runnable powerStatsCollector,
+            PowerStatsAggregator powerStatsAggregator,
             @DurationMillisLong long aggregatedPowerStatsSpanDuration,
             @DurationMillisLong long powerStatsAggregationPeriod, PowerStatsStore powerStatsStore,
-            Clock clock, MonotonicClock monotonicClock, Handler handler,
-            BatteryStatsImpl batteryStats) {
-        mContext = context;
+            AlarmScheduler alarmScheduler, Clock clock, MonotonicClock monotonicClock,
+            Supplier<Long> earliestAvailableBatteryHistoryTimeMs, Handler handler) {
         mPowerStatsAggregator = powerStatsAggregator;
         mAggregatedPowerStatsSpanDuration = aggregatedPowerStatsSpanDuration;
         mPowerStatsAggregationPeriod = powerStatsAggregationPeriod;
         mPowerStatsStore = powerStatsStore;
+        mAlarmScheduler = alarmScheduler;
         mClock = clock;
         mMonotonicClock = monotonicClock;
         mHandler = handler;
-        mBatteryStats = batteryStats;
+        mPowerStatsCollector = powerStatsCollector;
+        mEarliestAvailableBatteryHistoryTimeMs = earliestAvailableBatteryHistoryTimeMs;
     }
 
     /**
@@ -81,9 +95,8 @@
     }
 
     private void scheduleNextPowerStatsAggregation() {
-        AlarmManager alarmManager = mContext.getSystemService(AlarmManager.class);
-        alarmManager.set(AlarmManager.ELAPSED_REALTIME,
-                mClock.elapsedRealtime() + mPowerStatsAggregationPeriod, "PowerStats",
+        mAlarmScheduler.scheduleAlarm(mClock.elapsedRealtime() + mPowerStatsAggregationPeriod,
+                "PowerStats",
                 () -> {
                     schedulePowerStatsAggregation();
                     mHandler.post(this::scheduleNextPowerStatsAggregation);
@@ -96,7 +109,7 @@
     @VisibleForTesting
     public void schedulePowerStatsAggregation() {
         // Catch up the power stats collectors
-        mBatteryStats.schedulePowerStatsSampleCollection();
+        mPowerStatsCollector.run();
         mHandler.post(this::aggregateAndStorePowerStats);
     }
 
@@ -105,7 +118,7 @@
         long currentMonotonicTime = mMonotonicClock.monotonicTime();
         long startTime = getLastSavedSpanEndMonotonicTime();
         if (startTime < 0) {
-            startTime = mBatteryStats.getHistory().getStartTime();
+            startTime = mEarliestAvailableBatteryHistoryTimeMs.get();
         }
         long endTimeMs = alignToWallClock(startTime + mAggregatedPowerStatsSpanDuration,
                 mAggregatedPowerStatsSpanDuration, currentMonotonicTime, currentTimeMillis);
diff --git a/services/core/java/com/android/server/trust/TrustManagerService.java b/services/core/java/com/android/server/trust/TrustManagerService.java
index eac4fc0..9a85c42 100644
--- a/services/core/java/com/android/server/trust/TrustManagerService.java
+++ b/services/core/java/com/android/server/trust/TrustManagerService.java
@@ -1608,14 +1608,12 @@
                     user.name, user.id, user.flags);
             if (!user.supportsSwitchToByUser()) {
                 final boolean locked;
-                if (user.isProfile()) {
-                    if (mLockPatternUtils.isSeparateProfileChallengeEnabled(user.id)) {
-                        fout.print(" (profile with separate challenge)");
-                        locked = isDeviceLockedInner(user.id);
-                    } else {
-                        fout.print(" (profile with unified challenge)");
-                        locked = isDeviceLockedInner(resolveProfileParent(user.id));
-                    }
+                if (mLockPatternUtils.isProfileWithUnifiedChallenge(user.id)) {
+                    fout.print(" (profile with unified challenge)");
+                    locked = isDeviceLockedInner(resolveProfileParent(user.id));
+                } else if (mLockPatternUtils.isSeparateProfileChallengeEnabled(user.id)) {
+                    fout.print(" (profile with separate challenge)");
+                    locked = isDeviceLockedInner(user.id);
                 } else {
                     fout.println(" (user that cannot be switched to)");
                     locked = isDeviceLockedInner(user.id);
diff --git a/services/core/java/com/android/server/tv/TvInputManagerService.java b/services/core/java/com/android/server/tv/TvInputManagerService.java
index 6f27507..d903ad4 100644
--- a/services/core/java/com/android/server/tv/TvInputManagerService.java
+++ b/services/core/java/com/android/server/tv/TvInputManagerService.java
@@ -41,7 +41,6 @@
 import android.content.pm.ResolveInfo;
 import android.content.pm.ServiceInfo;
 import android.content.pm.UserInfo;
-import android.content.res.Resources;
 import android.graphics.Rect;
 import android.hardware.hdmi.HdmiControlManager;
 import android.hardware.hdmi.HdmiDeviceInfo;
@@ -2081,6 +2080,45 @@
         }
 
         @Override
+        public void stopPlayback(IBinder sessionToken, int mode, int userId) {
+            final int callingUid = Binder.getCallingUid();
+            final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
+                    userId, "stopPlayback");
+            final long identity = Binder.clearCallingIdentity();
+            try {
+                synchronized (mLock) {
+                    try {
+                        getSessionLocked(sessionToken, callingUid, resolvedUserId).stopPlayback(
+                                mode);
+                    } catch (RemoteException | SessionNotFoundException e) {
+                        Slog.e(TAG, "error in stopPlayback(mode)", e);
+                    }
+                }
+            } finally {
+                Binder.restoreCallingIdentity(identity);
+            }
+        }
+
+        @Override
+        public void startPlayback(IBinder sessionToken, int userId) {
+            final int callingUid = Binder.getCallingUid();
+            final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
+                    userId, "stopPlayback");
+            final long identity = Binder.clearCallingIdentity();
+            try {
+                synchronized (mLock) {
+                    try {
+                        getSessionLocked(sessionToken, callingUid, resolvedUserId).startPlayback();
+                    } catch (RemoteException | SessionNotFoundException e) {
+                        Slog.e(TAG, "error in startPlayback()", e);
+                    }
+                }
+            } finally {
+                Binder.restoreCallingIdentity(identity);
+            }
+        }
+
+        @Override
         public void timeShiftPlay(IBinder sessionToken, final Uri recordedProgramUri, int userId) {
             final int callingUid = Binder.getCallingUid();
             final int resolvedUserId = resolveCallingUserId(Binder.getCallingPid(), callingUid,
diff --git a/services/core/java/com/android/server/vibrator/VibratorControlService.java b/services/core/java/com/android/server/vibrator/VibratorControlService.java
new file mode 100644
index 0000000..2eeb903
--- /dev/null
+++ b/services/core/java/com/android/server/vibrator/VibratorControlService.java
@@ -0,0 +1,105 @@
+/*
+ * 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.vibrator;
+
+import android.annotation.NonNull;
+import android.annotation.SuppressLint;
+import android.frameworks.vibrator.IVibratorControlService;
+import android.frameworks.vibrator.IVibratorController;
+import android.frameworks.vibrator.VibrationParam;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.util.Slog;
+
+import java.util.Objects;
+
+/**
+ * Implementation of {@link IVibratorControlService} which allows the registration of
+ * {@link IVibratorController} to set and receive vibration params.
+ *
+ * @hide
+ */
+public final class VibratorControlService extends IVibratorControlService.Stub {
+    private static final String TAG = "VibratorControlService";
+
+    private final VibratorControllerHolder mVibratorControllerHolder;
+    private final Object mLock;
+
+    public VibratorControlService(VibratorControllerHolder vibratorControllerHolder, Object lock) {
+        mVibratorControllerHolder = vibratorControllerHolder;
+        mLock = lock;
+    }
+
+    @Override
+    public void registerVibratorController(IVibratorController controller)
+            throws RemoteException {
+        synchronized (mLock) {
+            mVibratorControllerHolder.setVibratorController(controller);
+        }
+    }
+
+    @Override
+    public void unregisterVibratorController(@NonNull IVibratorController controller)
+            throws RemoteException {
+        Objects.requireNonNull(controller);
+
+        synchronized (mLock) {
+            if (mVibratorControllerHolder.getVibratorController() == null) {
+                Slog.w(TAG, "Received request to unregister IVibratorController = "
+                        + controller + ", but no controller was previously registered. Request "
+                        + "Ignored.");
+                return;
+            }
+            if (!Objects.equals(mVibratorControllerHolder.getVibratorController().asBinder(),
+                    controller.asBinder())) {
+                Slog.wtf(TAG, "Failed to unregister IVibratorController. The provided "
+                        + "controller doesn't match the registered one. " + this);
+                return;
+            }
+            mVibratorControllerHolder.setVibratorController(null);
+        }
+    }
+
+    @Override
+    public void setVibrationParams(
+            @SuppressLint("ArrayReturn") VibrationParam[] params, IVibratorController token)
+            throws RemoteException {
+        // TODO(b/305939964): Add set vibration implementation.
+    }
+
+    @Override
+    public void clearVibrationParams(int types, IVibratorController token) throws RemoteException {
+        // TODO(b/305939964): Add clear vibration implementation.
+    }
+
+    @Override
+    public void onRequestVibrationParamsComplete(
+            IBinder requestToken, @SuppressLint("ArrayReturn") VibrationParam[] result)
+            throws RemoteException {
+        // TODO(305942827): Cache the vibration params in VibrationScaler
+    }
+
+    @Override
+    public int getInterfaceVersion() throws RemoteException {
+        return this.VERSION;
+    }
+
+    @Override
+    public String getInterfaceHash() throws RemoteException {
+        return this.HASH;
+    }
+}
diff --git a/services/core/java/com/android/server/vibrator/VibratorControllerHolder.java b/services/core/java/com/android/server/vibrator/VibratorControllerHolder.java
new file mode 100644
index 0000000..63e69db
--- /dev/null
+++ b/services/core/java/com/android/server/vibrator/VibratorControllerHolder.java
@@ -0,0 +1,70 @@
+/*
+ * 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.vibrator;
+
+import android.annotation.NonNull;
+import android.frameworks.vibrator.IVibratorController;
+import android.os.IBinder;
+import android.os.RemoteException;
+import android.util.Slog;
+
+/**
+ * Holder class for {@link IVibratorController}.
+ *
+ * @hide
+ */
+public final class VibratorControllerHolder implements IBinder.DeathRecipient {
+    private static final String TAG = "VibratorControllerHolder";
+
+    private IVibratorController mVibratorController;
+
+    public IVibratorController getVibratorController() {
+        return mVibratorController;
+    }
+
+    /**
+     * Sets the {@link IVibratorController} in {@link VibratorControllerHolder} to the new
+     * controller. This will also take care of registering and unregistering death notifications
+     * for the cached {@link IVibratorController}.
+     */
+    public void setVibratorController(IVibratorController controller) {
+        try {
+            if (mVibratorController != null) {
+                mVibratorController.asBinder().unlinkToDeath(this, 0);
+            }
+            mVibratorController = controller;
+            if (mVibratorController != null) {
+                mVibratorController.asBinder().linkToDeath(this, 0);
+            }
+        } catch (RemoteException e) {
+            Slog.wtf(TAG, "Failed to set IVibratorController: " + this, e);
+        }
+    }
+
+    @Override
+    public void binderDied(@NonNull IBinder deadBinder) {
+        if (deadBinder == mVibratorController.asBinder()) {
+            setVibratorController(null);
+        }
+    }
+
+    @Override
+    public void binderDied() {
+        // Should not be used as binderDied(IBinder who) is overridden.
+        Slog.wtf(TAG, "binderDied() called unexpectedly.");
+    }
+}
diff --git a/services/core/java/com/android/server/vibrator/VibratorManagerService.java b/services/core/java/com/android/server/vibrator/VibratorManagerService.java
index 7d4bd3b..fc824ab 100644
--- a/services/core/java/com/android/server/vibrator/VibratorManagerService.java
+++ b/services/core/java/com/android/server/vibrator/VibratorManagerService.java
@@ -91,6 +91,8 @@
 public class VibratorManagerService extends IVibratorManagerService.Stub {
     private static final String TAG = "VibratorManagerService";
     private static final String EXTERNAL_VIBRATOR_SERVICE = "external_vibrator_service";
+    private static final String VIBRATOR_CONTROL_SERVICE =
+            "android.frameworks.vibrator.IVibratorControlService/default";
     private static final boolean DEBUG = false;
     private static final VibrationAttributes DEFAULT_ATTRIBUTES =
             new VibrationAttributes.Builder().build();
@@ -269,6 +271,11 @@
         context.registerReceiver(mIntentReceiver, filter, Context.RECEIVER_NOT_EXPORTED);
 
         injector.addService(EXTERNAL_VIBRATOR_SERVICE, new ExternalVibratorService());
+        if (ServiceManager.isDeclared(VIBRATOR_CONTROL_SERVICE)) {
+            injector.addService(VIBRATOR_CONTROL_SERVICE,
+                    new VibratorControlService(new VibratorControllerHolder(), mLock));
+        }
+
     }
 
     /** Finish initialization at boot phase {@link SystemService#PHASE_SYSTEM_SERVICES_READY}. */
diff --git a/services/core/java/com/android/server/wm/ActivityMetricsLaunchObserver.java b/services/core/java/com/android/server/wm/ActivityMetricsLaunchObserver.java
index 81e5fbd..769f01c 100644
--- a/services/core/java/com/android/server/wm/ActivityMetricsLaunchObserver.java
+++ b/services/core/java/com/android/server/wm/ActivityMetricsLaunchObserver.java
@@ -142,8 +142,11 @@
      *           if the launching activity is started from an existing launch sequence (trampoline)
      *           but cannot coalesce to the existing one, e.g. to a different display.
      * @param name The launching activity name.
+     * @param temperature The temperature at which a launch sequence had started.
+     * @param userId The id of the user the activity is being launched for.
      */
-    public void onActivityLaunched(long id, ComponentName name, @Temperature int temperature) {
+    public void onActivityLaunched(long id, ComponentName name, @Temperature int temperature,
+            int userId) {
     }
 
     /**
@@ -177,13 +180,15 @@
      * @param timestampNanos the timestamp of ActivityLaunchFinished event in nanoseconds.
      *        To compute the TotalTime duration, deduct the timestamp {@link #onIntentStarted}
      *        from {@code timestampNanos}.
+     * @param launchMode The activity launch mode.
      *
      * @apiNote The finishing activity isn't necessarily the same as the starting activity;
      *          in the case of a trampoline, multiple activities could've been started
      *          and only the latest activity that was top-most during first-frame drawn
      *          is reported here.
      */
-    public void onActivityLaunchFinished(long id, ComponentName name, long timestampNanos) {
+    public void onActivityLaunchFinished(long id, ComponentName name, long timestampNanos,
+            int launchMode) {
     }
 
     /**
diff --git a/services/core/java/com/android/server/wm/ActivityMetricsLogger.java b/services/core/java/com/android/server/wm/ActivityMetricsLogger.java
index 7b20529..78f501a 100644
--- a/services/core/java/com/android/server/wm/ActivityMetricsLogger.java
+++ b/services/core/java/com/android/server/wm/ActivityMetricsLogger.java
@@ -1737,7 +1737,8 @@
 
         // Beginning a launch is timing sensitive and so should be observed as soon as possible.
         mLaunchObserver.onActivityLaunched(info.mLaunchingState.mStartUptimeNs,
-                info.mLastLaunchedActivity.mActivityComponent, temperature);
+                info.mLastLaunchedActivity.mActivityComponent, temperature,
+                info.mLastLaunchedActivity.mUserId);
 
         Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
     }
@@ -1774,7 +1775,8 @@
                 "MetricsLogger:launchObserverNotifyActivityLaunchFinished");
 
         mLaunchObserver.onActivityLaunchFinished(info.mLaunchingState.mStartUptimeNs,
-                info.mLastLaunchedActivity.mActivityComponent, timestampNs);
+                info.mLastLaunchedActivity.mActivityComponent, timestampNs,
+                info.mLastLaunchedActivity.launchMode);
 
         Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
     }
diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
index 73edb4b..869bcc0 100644
--- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
+++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
@@ -1053,6 +1053,7 @@
             mWindowManager = wm;
             mRootWindowContainer = wm.mRoot;
             mWindowOrganizerController.mTransitionController.setWindowManager(wm);
+            mLifecycleManager.setWindowManager(wm);
             mTempConfig.setToDefaults();
             mTempConfig.setLocales(LocaleList.getDefault());
             mConfigurationSeq = mTempConfig.seq = 1;
@@ -1274,7 +1275,6 @@
             @Nullable String callingFeatureId, Intent intent, String resolvedType,
             IBinder resultTo, String resultWho, int requestCode, int startFlags,
             ProfilerInfo profilerInfo, Bundle bOptions, int userId, boolean validateIncomingUser) {
-
         final SafeActivityOptions opts = SafeActivityOptions.fromBundle(bOptions);
 
         assertPackageMatchesCallingUid(callingPackage);
@@ -1315,7 +1315,6 @@
                 .setActivityOptions(opts)
                 .setUserId(userId)
                 .execute();
-
     }
 
     @Override
diff --git a/services/core/java/com/android/server/wm/BackgroundActivityStartController.java b/services/core/java/com/android/server/wm/BackgroundActivityStartController.java
index 92665af..4929df80 100644
--- a/services/core/java/com/android/server/wm/BackgroundActivityStartController.java
+++ b/services/core/java/com/android/server/wm/BackgroundActivityStartController.java
@@ -20,6 +20,7 @@
 import static android.app.ActivityManager.PROCESS_STATE_NONEXISTENT;
 import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
 import static android.content.pm.PackageManager.PERMISSION_GRANTED;
+import static android.os.Build.VERSION_CODES.UPSIDE_DOWN_CAKE;
 import static android.os.Process.SYSTEM_UID;
 import static android.provider.DeviceConfig.NAMESPACE_WINDOW_MANAGER;
 
@@ -49,7 +50,6 @@
 import android.content.ComponentName;
 import android.content.Intent;
 import android.content.pm.PackageManager;
-import android.os.Build;
 import android.os.Process;
 import android.os.UserHandle;
 import android.provider.DeviceConfig;
@@ -86,7 +86,7 @@
     private static final int NO_PROCESS_UID = -1;
     /** If enabled the creator will not allow BAL on its behalf by default. */
     @ChangeId
-    @EnabledAfter(targetSdkVersion = Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
+    @EnabledAfter(targetSdkVersion = UPSIDE_DOWN_CAKE)
     private static final long DEFAULT_RESCIND_BAL_PRIVILEGES_FROM_PENDING_INTENT_CREATOR =
             296478951;
     public static final ActivityOptions ACTIVITY_OPTIONS_SYSTEM_DEFINED =
@@ -228,6 +228,7 @@
         private final Intent mIntent;
         private final WindowProcessController mCallerApp;
         private final WindowProcessController mRealCallerApp;
+        private final boolean mIsCallForResult;
 
         private BalState(int callingUid, int callingPid, final String callingPackage,
                  int realCallingUid, int realCallingPid,
@@ -247,8 +248,10 @@
             mOriginatingPendingIntent = originatingPendingIntent;
             mIntent = intent;
             mRealCallingPackage = mService.getPackageNameIfUnique(realCallingUid, realCallingPid);
-            if (originatingPendingIntent == null // not a PendingIntent
-                    || resultRecord != null // sent for result
+            mIsCallForResult = resultRecord != null;
+            if (balRequireOptInByPendingIntentCreator() // auto-opt in introduced with this feature
+                    && (originatingPendingIntent == null // not a PendingIntent
+                    || mIsCallForResult) // sent for result
             ) {
                 // grant BAL privileges unless explicitly opted out
                 mBalAllowedByPiCreatorWithHardening = mBalAllowedByPiCreator =
@@ -351,6 +354,19 @@
             return name + "[debugOnly]";
         }
 
+        /** @return valid targetSdk or <code>-1</code> */
+        private int getTargetSdk(String packageName) {
+            if (packageName == null) {
+                return -1;
+            }
+            try {
+                PackageManager pm = mService.mContext.getPackageManager();
+                return pm.getTargetSdkVersion(packageName);
+            } catch (Exception e) {
+                return -1;
+            }
+        }
+
         private boolean hasRealCaller() {
             return mRealCallingUid != NO_PROCESS_UID;
         }
@@ -368,6 +384,7 @@
             StringBuilder sb = new StringBuilder(2048);
             sb.append("[callingPackage: ")
                     .append(getDebugPackageName(mCallingPackage, mCallingUid));
+            sb.append("; callingPackageTargetSdk: ").append(getTargetSdk(mCallingPackage));
             sb.append("; callingUid: ").append(mCallingUid);
             sb.append("; callingPid: ").append(mCallingPid);
             sb.append("; appSwitchState: ").append(mAppSwitchState);
@@ -387,10 +404,13 @@
                     .append(mBalAllowedByPiCreatorWithHardening);
             sb.append("; resultIfPiCreatorAllowsBal: ").append(resultIfPiCreatorAllowsBal);
             sb.append("; hasRealCaller: ").append(hasRealCaller());
+            sb.append("; isCallForResult: ").append(mIsCallForResult);
             sb.append("; isPendingIntent: ").append(isPendingIntent());
             if (hasRealCaller()) {
                 sb.append("; realCallingPackage: ")
                         .append(getDebugPackageName(mRealCallingPackage, mRealCallingUid));
+                sb.append("; realCallingPackageTargetSdk: ")
+                        .append(getTargetSdk(mRealCallingPackage));
                 sb.append("; realCallingUid: ").append(mRealCallingUid);
                 sb.append("; realCallingPid: ").append(mRealCallingPid);
                 sb.append("; realCallingUidHasAnyVisibleWindow: ")
diff --git a/services/core/java/com/android/server/wm/ClientLifecycleManager.java b/services/core/java/com/android/server/wm/ClientLifecycleManager.java
index 8b282dd3..6b6388b 100644
--- a/services/core/java/com/android/server/wm/ClientLifecycleManager.java
+++ b/services/core/java/com/android/server/wm/ClientLifecycleManager.java
@@ -22,7 +22,13 @@
 import android.app.servertransaction.ClientTransaction;
 import android.app.servertransaction.ClientTransactionItem;
 import android.os.Binder;
+import android.os.IBinder;
 import android.os.RemoteException;
+import android.util.ArrayMap;
+import android.util.Slog;
+
+import com.android.internal.annotations.VisibleForTesting;
+import com.android.window.flags.Flags;
 
 /**
  * Class that is able to combine multiple client lifecycle transition requests and/or callbacks,
@@ -31,8 +37,18 @@
  * @see ClientTransaction
  */
 class ClientLifecycleManager {
-    // TODO(lifecycler): Implement building transactions or global transaction.
-    // TODO(lifecycler): Use object pools for transactions and transaction items.
+
+    private static final String TAG = "ClientLifecycleManager";
+
+    /** Mapping from client process binder to its pending transaction. */
+    @VisibleForTesting
+    final ArrayMap<IBinder, ClientTransaction> mPendingTransactions = new ArrayMap<>();
+
+    private WindowManagerService mWms;
+
+    void setWindowManager(@NonNull WindowManagerService wms) {
+        mWms = wms;
+    }
 
     /**
      * Schedules a transaction, which may consist of multiple callbacks and a lifecycle request.
@@ -82,14 +98,24 @@
      */
     void scheduleTransactionItem(@NonNull IApplicationThread client,
             @NonNull ClientTransactionItem transactionItem) throws RemoteException {
-        // TODO(b/260873529): queue the transaction items.
-        final ClientTransaction clientTransaction = ClientTransaction.obtain(client);
-        if (transactionItem.isActivityLifecycleItem()) {
-            clientTransaction.setLifecycleStateRequest((ActivityLifecycleItem) transactionItem);
+        // The behavior is different depending on the flag.
+        // When flag is on, we wait until RootWindowContainer#performSurfacePlacementNoTrace to
+        // dispatch all pending transactions at once.
+        if (Flags.bundleClientTransactionFlag()) {
+            final ClientTransaction clientTransaction = getOrCreatePendingTransaction(client);
+            clientTransaction.addTransactionItem(transactionItem);
+
+            onClientTransactionItemScheduledLocked(clientTransaction);
         } else {
-            clientTransaction.addCallback(transactionItem);
+            // TODO(b/260873529): cleanup after launch.
+            final ClientTransaction clientTransaction = ClientTransaction.obtain(client);
+            if (transactionItem.isActivityLifecycleItem()) {
+                clientTransaction.setLifecycleStateRequest((ActivityLifecycleItem) transactionItem);
+            } else {
+                clientTransaction.addCallback(transactionItem);
+            }
+            scheduleTransaction(clientTransaction);
         }
-        scheduleTransaction(clientTransaction);
     }
 
     /**
@@ -100,10 +126,67 @@
     void scheduleTransactionAndLifecycleItems(@NonNull IApplicationThread client,
             @NonNull ClientTransactionItem transactionItem,
             @NonNull ActivityLifecycleItem lifecycleItem) throws RemoteException {
-        // TODO(b/260873529): replace with #scheduleTransactionItem after launch for cleanup.
-        final ClientTransaction clientTransaction = ClientTransaction.obtain(client);
-        clientTransaction.addCallback(transactionItem);
-        clientTransaction.setLifecycleStateRequest(lifecycleItem);
+        // The behavior is different depending on the flag.
+        // When flag is on, we wait until RootWindowContainer#performSurfacePlacementNoTrace to
+        // dispatch all pending transactions at once.
+        if (Flags.bundleClientTransactionFlag()) {
+            final ClientTransaction clientTransaction = getOrCreatePendingTransaction(client);
+            clientTransaction.addTransactionItem(transactionItem);
+            clientTransaction.addTransactionItem(lifecycleItem);
+
+            onClientTransactionItemScheduledLocked(clientTransaction);
+        } else {
+            // TODO(b/260873529): cleanup after launch.
+            final ClientTransaction clientTransaction = ClientTransaction.obtain(client);
+            clientTransaction.addCallback(transactionItem);
+            clientTransaction.setLifecycleStateRequest(lifecycleItem);
+            scheduleTransaction(clientTransaction);
+        }
+    }
+
+    /** Executes all the pending transactions. */
+    void dispatchPendingTransactions() {
+        final int size = mPendingTransactions.size();
+        for (int i = 0; i < size; i++) {
+            final ClientTransaction transaction = mPendingTransactions.valueAt(i);
+            try {
+                scheduleTransaction(transaction);
+            } catch (RemoteException e) {
+                Slog.e(TAG, "Failed to deliver transaction for " + transaction.getClient());
+            }
+        }
+        mPendingTransactions.clear();
+    }
+
+    @NonNull
+    private ClientTransaction getOrCreatePendingTransaction(@NonNull IApplicationThread client) {
+        final IBinder clientBinder = client.asBinder();
+        final ClientTransaction pendingTransaction = mPendingTransactions.get(clientBinder);
+        if (pendingTransaction != null) {
+            return pendingTransaction;
+        }
+
+        // Create new transaction if there is no existing.
+        final ClientTransaction transaction = ClientTransaction.obtain(client);
+        mPendingTransactions.put(clientBinder, transaction);
+        return transaction;
+    }
+
+    /** Must only be called with WM lock. */
+    private void onClientTransactionItemScheduledLocked(
+            @NonNull ClientTransaction clientTransaction) throws RemoteException {
+        // TODO(b/260873529): make sure WindowSurfacePlacer#requestTraversal is called before
+        // ClientTransaction scheduled when needed.
+
+        if (mWms != null && (mWms.mWindowPlacerLocked.isInLayout()
+                || mWms.mWindowPlacerLocked.isTraversalScheduled())) {
+            // The pending transactions will be dispatched when
+            // RootWindowContainer#performSurfacePlacementNoTrace.
+            return;
+        }
+
+        // Dispatch the pending transaction immediately.
+        mPendingTransactions.remove(clientTransaction.getClient().asBinder());
         scheduleTransaction(clientTransaction);
     }
 }
diff --git a/services/core/java/com/android/server/wm/DisplayContent.java b/services/core/java/com/android/server/wm/DisplayContent.java
index 03d6c2c..ae10ce3 100644
--- a/services/core/java/com/android/server/wm/DisplayContent.java
+++ b/services/core/java/com/android/server/wm/DisplayContent.java
@@ -5149,8 +5149,13 @@
 
     /** @return the orientation of the display when it's rotation is ROTATION_0. */
     int getNaturalOrientation() {
-        return mBaseDisplayWidth < mBaseDisplayHeight
-                ? ORIENTATION_PORTRAIT : ORIENTATION_LANDSCAPE;
+        final Configuration config = getConfiguration();
+        if (config.windowConfiguration.getDisplayRotation() == ROTATION_0) {
+            return config.orientation;
+        }
+        final Rect frame = mDisplayPolicy.getDecorInsetsInfo(
+                ROTATION_0, mBaseDisplayWidth, mBaseDisplayHeight).mConfigFrame;
+        return frame.width() <= frame.height() ? ORIENTATION_PORTRAIT : ORIENTATION_LANDSCAPE;
     }
 
     void performLayout(boolean initial, boolean updateInputWindows) {
diff --git a/services/core/java/com/android/server/wm/DragDropController.java b/services/core/java/com/android/server/wm/DragDropController.java
index 4a3d0c1..32d60c5 100644
--- a/services/core/java/com/android/server/wm/DragDropController.java
+++ b/services/core/java/com/android/server/wm/DragDropController.java
@@ -16,6 +16,7 @@
 
 package com.android.server.wm;
 
+import static com.android.input.flags.Flags.enablePointerChoreographer;
 import static com.android.server.wm.WindowManagerDebugConfig.DEBUG_DRAG;
 import static com.android.server.wm.WindowManagerDebugConfig.SHOW_LIGHT_TRANSACTIONS;
 import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
@@ -23,6 +24,7 @@
 import android.annotation.NonNull;
 import android.content.ClipData;
 import android.content.Context;
+import android.hardware.input.InputManagerGlobal;
 import android.os.Binder;
 import android.os.Handler;
 import android.os.IBinder;
@@ -31,6 +33,8 @@
 import android.util.Slog;
 import android.view.Display;
 import android.view.IWindow;
+import android.view.InputDevice;
+import android.view.PointerIcon;
 import android.view.SurfaceControl;
 import android.view.View;
 import android.view.accessibility.AccessibilityManager;
@@ -97,8 +101,8 @@
     }
 
     IBinder performDrag(int callerPid, int callerUid, IWindow window, int flags,
-            SurfaceControl surface, int touchSource, float touchX, float touchY,
-            float thumbCenterX, float thumbCenterY, ClipData data) {
+            SurfaceControl surface, int touchSource, int touchDeviceId, int touchPointerId,
+            float touchX, float touchY, float thumbCenterX, float thumbCenterY, ClipData data) {
         if (DEBUG_DRAG) {
             Slog.d(TAG_WM, "perform drag: win=" + window + " surface=" + surface + " flags=" +
                     Integer.toHexString(flags) + " data=" + data + " touch(" + touchX + ","
@@ -208,7 +212,17 @@
 
                 final SurfaceControl surfaceControl = mDragState.mSurfaceControl;
                 mDragState.broadcastDragStartedLocked(touchX, touchY);
-                mDragState.overridePointerIconLocked(touchSource);
+                if (enablePointerChoreographer()) {
+                    if ((touchSource & InputDevice.SOURCE_MOUSE) == InputDevice.SOURCE_MOUSE) {
+                        InputManagerGlobal.getInstance().setPointerIcon(
+                                PointerIcon.getSystemIcon(
+                                        mService.mContext, PointerIcon.TYPE_GRABBING),
+                                mDragState.mDisplayContent.getDisplayId(), touchDeviceId,
+                                touchPointerId, mDragState.getInputToken());
+                    }
+                } else {
+                    mDragState.overridePointerIconLocked(touchSource);
+                }
                 // remember the thumb offsets for later
                 mDragState.mThumbOffsetX = thumbCenterX;
                 mDragState.mThumbOffsetY = thumbCenterY;
diff --git a/services/core/java/com/android/server/wm/DragState.java b/services/core/java/com/android/server/wm/DragState.java
index a888f84..adbe3bc 100644
--- a/services/core/java/com/android/server/wm/DragState.java
+++ b/services/core/java/com/android/server/wm/DragState.java
@@ -416,6 +416,13 @@
         return mInputInterceptor == null ? null : mInputInterceptor.mDragWindowHandle;
     }
 
+    IBinder getInputToken() {
+        if (mInputInterceptor == null || mInputInterceptor.mClientChannel == null) {
+            return null;
+        }
+        return mInputInterceptor.mClientChannel.getToken();
+    }
+
     /**
      * @param display The Display that the window being dragged is on.
      */
diff --git a/services/core/java/com/android/server/wm/LaunchObserverRegistryImpl.java b/services/core/java/com/android/server/wm/LaunchObserverRegistryImpl.java
index 9cbc1bd..4c73a41 100644
--- a/services/core/java/com/android/server/wm/LaunchObserverRegistryImpl.java
+++ b/services/core/java/com/android/server/wm/LaunchObserverRegistryImpl.java
@@ -84,10 +84,10 @@
     }
 
     @Override
-    public void onActivityLaunched(long id, ComponentName name, int temperature) {
+    public void onActivityLaunched(long id, ComponentName name, int temperature, int userId) {
         mHandler.sendMessage(PooledLambda.obtainMessage(
                 LaunchObserverRegistryImpl::handleOnActivityLaunched,
-                this, id, name, temperature));
+                this, id, name, temperature, userId));
     }
 
     @Override
@@ -97,10 +97,11 @@
     }
 
     @Override
-    public void onActivityLaunchFinished(long id, ComponentName name, long timestampNs) {
+    public void onActivityLaunchFinished(long id, ComponentName name, long timestampNs,
+            int launchMode) {
         mHandler.sendMessage(PooledLambda.obtainMessage(
                 LaunchObserverRegistryImpl::handleOnActivityLaunchFinished,
-                this, id, name, timestampNs));
+                this, id, name, timestampNs, launchMode));
     }
 
     @Override
@@ -137,10 +138,10 @@
     }
 
     private void handleOnActivityLaunched(long id, ComponentName name,
-            @Temperature int temperature) {
+            @Temperature int temperature, int userId) {
         // Traverse start-to-end to meet the registerLaunchObserver multi-cast order guarantee.
         for (int i = 0; i < mList.size(); i++) {
-            mList.get(i).onActivityLaunched(id, name, temperature);
+            mList.get(i).onActivityLaunched(id, name, temperature, userId);
         }
     }
 
@@ -151,10 +152,11 @@
         }
     }
 
-    private void handleOnActivityLaunchFinished(long id, ComponentName name, long timestampNs) {
+    private void handleOnActivityLaunchFinished(long id, ComponentName name, long timestampNs,
+            int launchMode) {
         // Traverse start-to-end to meet the registerLaunchObserver multi-cast order guarantee.
         for (int i = 0; i < mList.size(); i++) {
-            mList.get(i).onActivityLaunchFinished(id, name, timestampNs);
+            mList.get(i).onActivityLaunchFinished(id, name, timestampNs, launchMode);
         }
     }
 
diff --git a/services/core/java/com/android/server/wm/RootWindowContainer.java b/services/core/java/com/android/server/wm/RootWindowContainer.java
index d5aa276..9a75dae 100644
--- a/services/core/java/com/android/server/wm/RootWindowContainer.java
+++ b/services/core/java/com/android/server/wm/RootWindowContainer.java
@@ -842,6 +842,9 @@
         handleResizingWindows();
         clearFrameChangingWindows();
 
+        // Called after #handleResizingWindows to include WindowStateResizeItem if any.
+        mWmService.mAtmService.getLifecycleManager().dispatchPendingTransactions();
+
         if (mWmService.mDisplayFrozen) {
             ProtoLog.v(WM_DEBUG_ORIENTATION,
                     "With display frozen, orientationChangeComplete=%b",
diff --git a/services/core/java/com/android/server/wm/Session.java b/services/core/java/com/android/server/wm/Session.java
index 56f9aa4..57939bc 100644
--- a/services/core/java/com/android/server/wm/Session.java
+++ b/services/core/java/com/android/server/wm/Session.java
@@ -340,7 +340,8 @@
 
     @Override
     public IBinder performDrag(IWindow window, int flags, SurfaceControl surface, int touchSource,
-            float touchX, float touchY, float thumbCenterX, float thumbCenterY, ClipData data) {
+            int touchDeviceId, int touchPointerId, float touchX, float touchY, float thumbCenterX,
+            float thumbCenterY, ClipData data) {
         final int callingUid = Binder.getCallingUid();
         final int callingPid = Binder.getCallingPid();
         // Validate and resolve ClipDescription data before clearing the calling identity
@@ -349,7 +350,8 @@
         final long ident = Binder.clearCallingIdentity();
         try {
             return mDragDropController.performDrag(mPid, mUid, window, flags, surface, touchSource,
-                    touchX, touchY, thumbCenterX, thumbCenterY, data);
+                    touchDeviceId, touchPointerId, touchX, touchY, thumbCenterX, thumbCenterY,
+                    data);
         } finally {
             Binder.restoreCallingIdentity(ident);
         }
diff --git a/services/core/java/com/android/server/wm/TaskFragment.java b/services/core/java/com/android/server/wm/TaskFragment.java
index fc92755..5d01912 100644
--- a/services/core/java/com/android/server/wm/TaskFragment.java
+++ b/services/core/java/com/android/server/wm/TaskFragment.java
@@ -73,6 +73,7 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.app.ActivityOptions;
+import android.app.IApplicationThread;
 import android.app.ResultInfo;
 import android.app.WindowConfiguration;
 import android.app.servertransaction.ActivityResultItem;
@@ -103,6 +104,7 @@
 import com.android.internal.protolog.common.ProtoLog;
 import com.android.server.am.HostingRecord;
 import com.android.server.pm.pkg.AndroidPackage;
+import com.android.window.flags.Flags;
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
@@ -1509,23 +1511,38 @@
             }
 
             try {
-                final ClientTransaction transaction = ClientTransaction.obtain(
-                        next.app.getThread());
+                final IApplicationThread appThread = next.app.getThread();
+                final ClientTransaction transaction = Flags.bundleClientTransactionFlag()
+                        ? null
+                        : ClientTransaction.obtain(appThread);
                 // Deliver all pending results.
-                ArrayList<ResultInfo> a = next.results;
+                final ArrayList<ResultInfo> a = next.results;
                 if (a != null) {
                     final int size = a.size();
                     if (!next.finishing && size > 0) {
                         if (DEBUG_RESULTS) {
                             Slog.v(TAG_RESULTS, "Delivering results to " + next + ": " + a);
                         }
-                        transaction.addCallback(ActivityResultItem.obtain(next.token, a));
+                        final ActivityResultItem activityResultItem = ActivityResultItem.obtain(
+                                next.token, a);
+                        if (transaction == null) {
+                            mAtmService.getLifecycleManager().scheduleTransactionItem(
+                                    appThread, activityResultItem);
+                        } else {
+                            transaction.addCallback(activityResultItem);
+                        }
                     }
                 }
 
                 if (next.newIntents != null) {
-                    transaction.addCallback(
-                            NewIntentItem.obtain(next.token, next.newIntents, true /* resume */));
+                    final NewIntentItem newIntentItem = NewIntentItem.obtain(
+                            next.token, next.newIntents, true /* resume */);
+                    if (transaction == null) {
+                        mAtmService.getLifecycleManager().scheduleTransactionItem(
+                                appThread, newIntentItem);
+                    } else {
+                        transaction.addCallback(newIntentItem);
+                    }
                 }
 
                 // Well the app will no longer be stopped.
@@ -1539,10 +1556,16 @@
                 final int topProcessState = mAtmService.mTopProcessState;
                 next.app.setPendingUiCleanAndForceProcessStateUpTo(topProcessState);
                 next.abortAndClearOptionsAnimation();
-                transaction.setLifecycleStateRequest(
-                        ResumeActivityItem.obtain(next.token, topProcessState,
-                                dc.isNextTransitionForward(), next.shouldSendCompatFakeFocus()));
-                mAtmService.getLifecycleManager().scheduleTransaction(transaction);
+                final ResumeActivityItem resumeActivityItem = ResumeActivityItem.obtain(
+                        next.token, topProcessState, dc.isNextTransitionForward(),
+                        next.shouldSendCompatFakeFocus());
+                if (transaction == null) {
+                    mAtmService.getLifecycleManager().scheduleTransactionItem(
+                            appThread, resumeActivityItem);
+                } else {
+                    transaction.setLifecycleStateRequest(resumeActivityItem);
+                    mAtmService.getLifecycleManager().scheduleTransaction(transaction);
+                }
 
                 ProtoLog.d(WM_DEBUG_STATES, "resumeTopActivity: Resumed %s", next);
             } catch (Exception e) {
diff --git a/services/core/java/com/android/server/wm/WindowManagerInternal.java b/services/core/java/com/android/server/wm/WindowManagerInternal.java
index 808a11d..516d37c 100644
--- a/services/core/java/com/android/server/wm/WindowManagerInternal.java
+++ b/services/core/java/com/android/server/wm/WindowManagerInternal.java
@@ -866,6 +866,11 @@
     public abstract ImeTargetInfo onToggleImeRequested(boolean show,
             @NonNull IBinder focusedToken, @NonNull IBinder requestToken, int displayId);
 
+    /**
+     * Returns the token to identify the target window that the IME is associated with.
+     */
+    public abstract @Nullable IBinder getTargetWindowTokenFromInputToken(IBinder inputToken);
+
     /** The information of input method target when IME is requested to show or hide. */
     public static class ImeTargetInfo {
         public final String focusedWindowName;
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index 10dd334..2125c63 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -8552,6 +8552,12 @@
                         fromOrientations, toOrientations);
             }
         }
+
+        @Override
+        public @Nullable IBinder getTargetWindowTokenFromInputToken(IBinder inputToken) {
+            InputTarget inputTarget = WindowManagerService.this.getInputTargetFromToken(inputToken);
+            return inputTarget == null ? null : inputTarget.getWindowToken();
+        }
     }
 
     private final class ImeTargetVisibilityPolicyImpl extends ImeTargetVisibilityPolicy {
diff --git a/services/core/java/com/android/server/wm/WindowOrganizerController.java b/services/core/java/com/android/server/wm/WindowOrganizerController.java
index a872fd0..4b99432 100644
--- a/services/core/java/com/android/server/wm/WindowOrganizerController.java
+++ b/services/core/java/com/android/server/wm/WindowOrganizerController.java
@@ -63,6 +63,7 @@
 import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_START_SHORTCUT;
 
 import static com.android.internal.protolog.ProtoLogGroup.WM_DEBUG_WINDOW_ORGANIZER;
+import static com.android.server.wm.ActivityRecord.State.PAUSING;
 import static com.android.server.wm.ActivityTaskManagerService.enforceTaskPermission;
 import static com.android.server.wm.ActivityTaskSupervisor.PRESERVE_WINDOWS;
 import static com.android.server.wm.ActivityTaskSupervisor.REMOVE_FROM_RECENTS;
@@ -1178,6 +1179,14 @@
                 mService.mRootWindowContainer.moveActivityToPinnedRootTask(
                         pipActivity, null /* launchIntoPipHostActivity */,
                         "moveActivityToPinnedRootTask", null /* transition */, entryBounds);
+
+                // Continue the pausing process after potential task reparenting.
+                if (pipActivity.isState(PAUSING) && pipActivity.mPauseSchedulePendingForPip) {
+                    pipActivity.getTask().schedulePauseActivity(
+                            pipActivity, false /* userLeaving */,
+                            false /* pauseImmediately */, true /* autoEnteringPip */, "auto-pip");
+                }
+
                 effects |= TRANSACT_EFFECTS_LIFECYCLE;
                 break;
             }
diff --git a/services/core/java/com/android/server/wm/WindowSurfacePlacer.java b/services/core/java/com/android/server/wm/WindowSurfacePlacer.java
index aa58902..dff718a 100644
--- a/services/core/java/com/android/server/wm/WindowSurfacePlacer.java
+++ b/services/core/java/com/android/server/wm/WindowSurfacePlacer.java
@@ -210,6 +210,10 @@
         return mInLayout;
     }
 
+    boolean isTraversalScheduled() {
+        return mTraversalScheduled;
+    }
+
     void requestTraversal() {
         if (mTraversalScheduled) {
             return;
diff --git a/services/core/jni/com_android_server_pm_PackageManagerShellCommandDataLoader.cpp b/services/core/jni/com_android_server_pm_PackageManagerShellCommandDataLoader.cpp
index 3d4f866..2a0f1e2 100644
--- a/services/core/jni/com_android_server_pm_PackageManagerShellCommandDataLoader.cpp
+++ b/services/core/jni/com_android_server_pm_PackageManagerShellCommandDataLoader.cpp
@@ -182,7 +182,7 @@
 
     auto block_count = 1 + (fileSize - 1) / INCFS_DATA_FILE_BLOCK_SIZE;
     auto hash_block_count = block_count;
-    for (auto i = 0; hash_block_count > 1; i++) {
+    while (hash_block_count > 1) {
         hash_block_count = (hash_block_count + hash_per_block - 1) / hash_per_block;
         total_tree_block_count += hash_block_count;
     }
@@ -468,7 +468,6 @@
                      borrowed_fd incomingFd, bool waitOnEof, std::vector<char>* buffer,
                      std::vector<IncFsDataBlock>* blocks) {
         IncFsSize remaining = size;
-        IncFsSize totalSize = 0;
         IncFsBlockIndex blockIdx = 0;
         while (remaining > 0) {
             constexpr auto capacity = BUFFER_SIZE;
@@ -502,7 +501,6 @@
 
             buffer->resize(size + read);
             remaining -= read;
-            totalSize += read;
         }
         if (!buffer->empty() && !flashToIncFs(incfsFd, kind, true, &blockIdx, buffer, blocks)) {
             return false;
diff --git a/services/foldables/devicestateprovider/src/com/android/server/policy/feature/device_state_flags.aconfig b/services/foldables/devicestateprovider/src/com/android/server/policy/feature/device_state_flags.aconfig
index 47c2a1b..29e258c 100644
--- a/services/foldables/devicestateprovider/src/com/android/server/policy/feature/device_state_flags.aconfig
+++ b/services/foldables/devicestateprovider/src/com/android/server/policy/feature/device_state_flags.aconfig
@@ -5,4 +5,12 @@
     namespace: "display_manager"
     description: "Feature flag for dual display blocking"
     bug: "278667199"
+}
+
+flag {
+    name: "enable_foldables_posture_based_closed_state"
+    namespace: "windowing_frontend"
+    description: "Enables smarter closed device state state for foldable devices"
+    bug: "309792734"
+    is_fixed_read_only: true
 }
\ No newline at end of file
diff --git a/services/manifest_services.xml b/services/manifest_services.xml
index 7638915..eae159f 100644
--- a/services/manifest_services.xml
+++ b/services/manifest_services.xml
@@ -4,4 +4,9 @@
         <version>1</version>
         <fqname>IAltitudeService/default</fqname>
     </hal>
+    <hal format="aidl">
+        <name>android.frameworks.vibrator</name>
+        <version>1</version>
+        <fqname>IVibratorControlService/default</fqname>
+    </hal>
 </manifest>
diff --git a/services/permission/java/com/android/server/permission/access/permission/AppIdPermissionPolicy.kt b/services/permission/java/com/android/server/permission/access/permission/AppIdPermissionPolicy.kt
index 02032c7..f69f628 100644
--- a/services/permission/java/com/android/server/permission/access/permission/AppIdPermissionPolicy.kt
+++ b/services/permission/java/com/android/server/permission/access/permission/AppIdPermissionPolicy.kt
@@ -24,6 +24,7 @@
 import android.os.Build
 import android.util.Slog
 import com.android.internal.os.RoSystemProperties
+import com.android.internal.pm.permission.CompatibilityPermissionInfo
 import com.android.modules.utils.BinaryXmlPullParser
 import com.android.modules.utils.BinaryXmlSerializer
 import com.android.server.permission.access.AccessState
@@ -42,7 +43,6 @@
 import com.android.server.permission.access.util.isInternal
 import com.android.server.pm.KnownPackages
 import com.android.server.pm.parsing.PackageInfoUtils
-import com.android.server.pm.permission.CompatibilityPermissionInfo
 import com.android.server.pm.pkg.AndroidPackage
 import com.android.server.pm.pkg.PackageState
 
diff --git a/services/robotests/src/com/android/server/pm/CrossProfileAppsServiceImplRoboTest.java b/services/robotests/src/com/android/server/pm/CrossProfileAppsServiceImplRoboTest.java
index d62da1a..5b222c0 100644
--- a/services/robotests/src/com/android/server/pm/CrossProfileAppsServiceImplRoboTest.java
+++ b/services/robotests/src/com/android/server/pm/CrossProfileAppsServiceImplRoboTest.java
@@ -57,11 +57,11 @@
 
 import androidx.test.core.app.ApplicationProvider;
 
+import com.android.internal.pm.parsing.pkg.PackageImpl;
 import com.android.internal.pm.parsing.pkg.ParsedPackage;
 import com.android.internal.util.FunctionalUtils.ThrowingRunnable;
 import com.android.internal.util.FunctionalUtils.ThrowingSupplier;
 import com.android.server.LocalServices;
-import com.android.server.pm.parsing.pkg.PackageImpl;
 import com.android.server.pm.pkg.AndroidPackage;
 import com.android.server.testing.shadows.ShadowApplicationPackageManager;
 import com.android.server.testing.shadows.ShadowUserManager;
diff --git a/services/tests/PackageManagerComponentOverrideTests/src/com/android/server/pm/test/override/PackageManagerComponentLabelIconOverrideTest.kt b/services/tests/PackageManagerComponentOverrideTests/src/com/android/server/pm/test/override/PackageManagerComponentLabelIconOverrideTest.kt
index 3011fa1..5c4716d 100644
--- a/services/tests/PackageManagerComponentOverrideTests/src/com/android/server/pm/test/override/PackageManagerComponentLabelIconOverrideTest.kt
+++ b/services/tests/PackageManagerComponentOverrideTests/src/com/android/server/pm/test/override/PackageManagerComponentLabelIconOverrideTest.kt
@@ -25,6 +25,7 @@
 import android.os.UserHandle
 import android.util.ArrayMap
 import com.android.internal.pm.parsing.pkg.AndroidPackageInternal
+import com.android.internal.pm.parsing.pkg.PackageImpl
 import com.android.internal.pm.parsing.pkg.ParsedPackage
 import com.android.internal.pm.pkg.component.ParsedActivity
 import com.android.server.pm.AppsFilterImpl
@@ -38,7 +39,6 @@
 import com.android.server.pm.SharedLibrariesImpl
 import com.android.server.pm.UserManagerInternal
 import com.android.server.pm.UserManagerService
-import com.android.server.pm.parsing.pkg.PackageImpl
 import com.android.server.pm.pkg.AndroidPackage
 import com.android.server.pm.resolution.ComponentResolver
 import com.android.server.pm.snapshot.PackageDataSnapshot
@@ -49,6 +49,8 @@
 import com.android.server.testutils.whenever
 import com.android.server.wm.ActivityTaskManagerInternal
 import com.google.common.truth.Truth.assertThat
+import java.io.File
+import java.util.UUID
 import org.junit.After
 import org.junit.Before
 import org.junit.BeforeClass
@@ -61,8 +63,6 @@
 import org.mockito.Mockito.intThat
 import org.mockito.Mockito.same
 import org.testng.Assert.assertThrows
-import java.io.File
-import java.util.UUID
 
 @RunWith(Parameterized::class)
 class PackageManagerComponentLabelIconOverrideTest {
diff --git a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/AppsFilterImplTest.java b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/AppsFilterImplTest.java
index 3461bb6..7277fd7 100644
--- a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/AppsFilterImplTest.java
+++ b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/AppsFilterImplTest.java
@@ -49,20 +49,20 @@
 
 import androidx.annotation.NonNull;
 
+import com.android.internal.pm.parsing.pkg.PackageImpl;
 import com.android.internal.pm.parsing.pkg.ParsedPackage;
 import com.android.internal.pm.pkg.component.ParsedActivity;
+import com.android.internal.pm.pkg.component.ParsedActivityImpl;
+import com.android.internal.pm.pkg.component.ParsedComponentImpl;
+import com.android.internal.pm.pkg.component.ParsedInstrumentationImpl;
+import com.android.internal.pm.pkg.component.ParsedIntentInfoImpl;
 import com.android.internal.pm.pkg.component.ParsedPermission;
+import com.android.internal.pm.pkg.component.ParsedPermissionImpl;
+import com.android.internal.pm.pkg.component.ParsedProviderImpl;
+import com.android.internal.pm.pkg.component.ParsedUsesPermissionImpl;
 import com.android.internal.pm.pkg.parsing.ParsingPackage;
 import com.android.server.om.OverlayReferenceMapper;
-import com.android.server.pm.parsing.pkg.PackageImpl;
 import com.android.server.pm.pkg.AndroidPackage;
-import com.android.server.pm.pkg.component.ParsedActivityImpl;
-import com.android.server.pm.pkg.component.ParsedComponentImpl;
-import com.android.server.pm.pkg.component.ParsedInstrumentationImpl;
-import com.android.server.pm.pkg.component.ParsedIntentInfoImpl;
-import com.android.server.pm.pkg.component.ParsedPermissionImpl;
-import com.android.server.pm.pkg.component.ParsedProviderImpl;
-import com.android.server.pm.pkg.component.ParsedUsesPermissionImpl;
 import com.android.server.utils.WatchableTester;
 
 import org.junit.Before;
diff --git a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/CompatibilityModeTest.java b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/CompatibilityModeTest.java
index f0d389b..b1c3e94 100644
--- a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/CompatibilityModeTest.java
+++ b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/CompatibilityModeTest.java
@@ -32,11 +32,11 @@
 import android.os.Build;
 import android.platform.test.annotations.Presubmit;
 
+import com.android.internal.pm.parsing.pkg.PackageImpl;
+import com.android.internal.pm.pkg.parsing.ParsingPackageUtils;
 import com.android.server.pm.parsing.PackageInfoUtils;
-import com.android.server.pm.parsing.pkg.PackageImpl;
 import com.android.server.pm.pkg.PackageStateUnserialized;
 import com.android.server.pm.pkg.PackageUserStateImpl;
-import com.android.server.pm.pkg.parsing.ParsingPackageUtils;
 
 import org.junit.After;
 import org.junit.Before;
diff --git a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageManagerSettingsTests.java b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageManagerSettingsTests.java
index f07e820..b396cf4 100644
--- a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageManagerSettingsTests.java
+++ b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageManagerSettingsTests.java
@@ -63,10 +63,10 @@
 import androidx.test.filters.SmallTest;
 import androidx.test.runner.AndroidJUnit4;
 
+import com.android.internal.pm.parsing.pkg.PackageImpl;
 import com.android.internal.pm.parsing.pkg.ParsedPackage;
 import com.android.permission.persistence.RuntimePermissionsPersistence;
 import com.android.server.LocalServices;
-import com.android.server.pm.parsing.pkg.PackageImpl;
 import com.android.server.pm.permission.LegacyPermissionDataProvider;
 import com.android.server.pm.pkg.AndroidPackage;
 import com.android.server.pm.pkg.ArchiveState;
diff --git a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageManagerTests.java b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageManagerTests.java
index 9c48af8..285c059 100644
--- a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageManagerTests.java
+++ b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageManagerTests.java
@@ -73,7 +73,7 @@
 import com.android.compatibility.common.util.CddTest;
 import com.android.internal.content.InstallLocationUtils;
 import com.android.internal.pm.parsing.pkg.ParsedPackage;
-import com.android.server.pm.pkg.parsing.ParsingPackageUtils;
+import com.android.server.pm.parsing.ParsingUtils;
 import com.android.server.pm.test.service.server.R;
 
 import dalvik.system.VMRuntime;
@@ -346,7 +346,7 @@
 
     private ParsedPackage parsePackage(Uri packageURI) {
         final String archiveFilePath = packageURI.getPath();
-        ParseResult<ParsedPackage> result = ParsingPackageUtils.parseDefaultOneTime(
+        ParseResult<ParsedPackage> result = ParsingUtils.parseDefaultOneTime(
                 new File(archiveFilePath), 0 /*flags*/, Collections.emptyList(),
                 false /*collectCertificates*/);
         if (result.isError()) {
diff --git a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageParserTest.java b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageParserTest.java
index a62cd4f..03e45a2 100644
--- a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageParserTest.java
+++ b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageParserTest.java
@@ -15,7 +15,7 @@
  */
 package com.android.server.pm;
 
-import static com.android.server.pm.permission.CompatibilityPermissionInfo.COMPAT_PERMS;
+import static com.android.internal.pm.permission.CompatibilityPermissionInfo.COMPAT_PERMS;
 
 import static com.google.common.truth.Truth.assertThat;
 import static com.google.common.truth.Truth.assertWithMessage;
@@ -58,17 +58,28 @@
 import androidx.test.filters.SmallTest;
 import androidx.test.runner.AndroidJUnit4;
 
+import com.android.internal.pm.parsing.pkg.PackageImpl;
 import com.android.internal.pm.parsing.pkg.ParsedPackage;
+import com.android.internal.pm.permission.CompatibilityPermissionInfo;
 import com.android.internal.pm.pkg.component.ParsedActivity;
+import com.android.internal.pm.pkg.component.ParsedActivityImpl;
 import com.android.internal.pm.pkg.component.ParsedApexSystemService;
 import com.android.internal.pm.pkg.component.ParsedComponent;
 import com.android.internal.pm.pkg.component.ParsedInstrumentation;
+import com.android.internal.pm.pkg.component.ParsedInstrumentationImpl;
 import com.android.internal.pm.pkg.component.ParsedIntentInfo;
+import com.android.internal.pm.pkg.component.ParsedIntentInfoImpl;
 import com.android.internal.pm.pkg.component.ParsedPermission;
 import com.android.internal.pm.pkg.component.ParsedPermissionGroup;
+import com.android.internal.pm.pkg.component.ParsedPermissionGroupImpl;
+import com.android.internal.pm.pkg.component.ParsedPermissionImpl;
+import com.android.internal.pm.pkg.component.ParsedPermissionUtils;
 import com.android.internal.pm.pkg.component.ParsedProvider;
+import com.android.internal.pm.pkg.component.ParsedProviderImpl;
 import com.android.internal.pm.pkg.component.ParsedService;
+import com.android.internal.pm.pkg.component.ParsedServiceImpl;
 import com.android.internal.pm.pkg.component.ParsedUsesPermission;
+import com.android.internal.pm.pkg.component.ParsedUsesPermissionImpl;
 import com.android.internal.pm.pkg.parsing.ParsingPackage;
 import com.android.internal.util.ArrayUtils;
 import com.android.server.pm.parsing.PackageCacher;
@@ -76,19 +87,8 @@
 import com.android.server.pm.parsing.PackageParser2;
 import com.android.server.pm.parsing.TestPackageParser2;
 import com.android.server.pm.parsing.pkg.AndroidPackageUtils;
-import com.android.server.pm.parsing.pkg.PackageImpl;
-import com.android.server.pm.permission.CompatibilityPermissionInfo;
 import com.android.server.pm.pkg.AndroidPackage;
 import com.android.server.pm.pkg.PackageUserStateInternal;
-import com.android.server.pm.pkg.component.ParsedActivityImpl;
-import com.android.server.pm.pkg.component.ParsedInstrumentationImpl;
-import com.android.server.pm.pkg.component.ParsedIntentInfoImpl;
-import com.android.server.pm.pkg.component.ParsedPermissionGroupImpl;
-import com.android.server.pm.pkg.component.ParsedPermissionImpl;
-import com.android.server.pm.pkg.component.ParsedPermissionUtils;
-import com.android.server.pm.pkg.component.ParsedProviderImpl;
-import com.android.server.pm.pkg.component.ParsedServiceImpl;
-import com.android.server.pm.pkg.component.ParsedUsesPermissionImpl;
 
 import org.junit.Before;
 import org.junit.Rule;
@@ -725,6 +725,16 @@
                 public boolean hasFeature(String feature) {
                     return false;
                 }
+
+                @Override
+                public Set<String> getHiddenApiWhitelistedApps() {
+                    return new ArraySet<>();
+                }
+
+                @Override
+                public Set<String> getInstallConstraintsAllowlist() {
+                    return new ArraySet<>();
+                }
             });
             if (cacheDir != null) {
                 setCacheDir(cacheDir);
diff --git a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/ScanTests.java b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/ScanTests.java
index 6202908..c1271bb 100644
--- a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/ScanTests.java
+++ b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/ScanTests.java
@@ -50,13 +50,13 @@
 import android.platform.test.annotations.Presubmit;
 import android.util.Pair;
 
+import com.android.internal.pm.parsing.pkg.PackageImpl;
 import com.android.internal.pm.parsing.pkg.ParsedPackage;
+import com.android.internal.pm.pkg.component.ParsedUsesPermissionImpl;
 import com.android.internal.pm.pkg.parsing.ParsingPackage;
 import com.android.server.compat.PlatformCompat;
 import com.android.server.pm.parsing.PackageInfoUtils;
-import com.android.server.pm.parsing.pkg.PackageImpl;
 import com.android.server.pm.pkg.AndroidPackage;
-import com.android.server.pm.pkg.component.ParsedUsesPermissionImpl;
 import com.android.server.pm.verify.domain.DomainVerificationManagerInternal;
 
 import org.hamcrest.BaseMatcher;
@@ -376,7 +376,7 @@
         // Create the ParsedPackage for the apex
         final ParsedPackage basicPackage =
                 ((ParsedPackage) new PackageImpl(DUMMY_PACKAGE_NAME, codePath, codePath,
-                        mock(TypedArray.class), false)
+                        mock(TypedArray.class), false, null)
                         .setVolumeUuid(UUID_ONE.toString())
                         .hideAsParsed())
                         .setVersionCodeMajor(1)
@@ -595,7 +595,7 @@
         // TODO(b/135203078): Make this use PackageImpl.forParsing and separate the steps
         return (ParsingPackage) ((ParsedPackage) new PackageImpl(packageName,
                 "/data/tmp/randompath/base.apk", createCodePath(packageName),
-                mock(TypedArray.class), false)
+                mock(TypedArray.class), false, null)
                 .setVolumeUuid(UUID_ONE.toString())
                 .addUsesStaticLibrary("some.static.library", 234L, new String[]{"testCert1"})
                 .addUsesStaticLibrary("some.other.static.library", 456L, new String[]{"testCert2"})
diff --git a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/parsing/PackageParserLegacyCoreTest.java b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/parsing/PackageParserLegacyCoreTest.java
index b102ab4..b63950c 100644
--- a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/parsing/PackageParserLegacyCoreTest.java
+++ b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/parsing/PackageParserLegacyCoreTest.java
@@ -39,14 +39,14 @@
 import androidx.test.runner.AndroidJUnit4;
 
 import com.android.internal.pm.parsing.pkg.ParsedPackage;
+import com.android.internal.pm.pkg.component.ParsedActivityUtils;
 import com.android.internal.pm.pkg.component.ParsedComponent;
 import com.android.internal.pm.pkg.component.ParsedIntentInfo;
 import com.android.internal.pm.pkg.component.ParsedPermission;
+import com.android.internal.pm.pkg.component.ParsedPermissionUtils;
 import com.android.internal.util.ArrayUtils;
 import com.android.server.pm.PackageManagerException;
 import com.android.server.pm.pkg.AndroidPackage;
-import com.android.server.pm.pkg.component.ParsedActivityUtils;
-import com.android.server.pm.pkg.component.ParsedPermissionUtils;
 import com.android.server.pm.test.service.server.R;
 
 import com.google.common.truth.Expect;
diff --git a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/parsing/PackageParsingDeferErrorTest.kt b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/parsing/PackageParsingDeferErrorTest.kt
index 67b91d2..c435b94 100644
--- a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/parsing/PackageParsingDeferErrorTest.kt
+++ b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/parsing/PackageParsingDeferErrorTest.kt
@@ -22,7 +22,6 @@
 import android.platform.test.annotations.Presubmit
 import androidx.test.InstrumentationRegistry
 import com.android.internal.pm.parsing.pkg.ParsedPackage
-import com.android.server.pm.pkg.parsing.ParsingPackageUtils
 import com.android.server.pm.test.service.server.R
 import com.google.common.truth.Truth.assertThat
 import com.google.common.truth.Truth.assertWithMessage
@@ -121,7 +120,7 @@
                 input.copyTo(output)
             }
         }
-        return ParsingPackageUtils.parseDefaultOneTime(file, 0 /*flags*/, emptyList(),
+        return ParsingUtils.parseDefaultOneTime(file, 0 /*flags*/, emptyList(),
                 false /*collectCertificates*/)
     }
 }
diff --git a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/parsing/ParsingUtils.java b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/parsing/ParsingUtils.java
new file mode 100644
index 0000000..a9eac95
--- /dev/null
+++ b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/parsing/ParsingUtils.java
@@ -0,0 +1,81 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.pm.parsing;
+
+import android.annotation.NonNull;
+import android.content.pm.parsing.result.ParseInput;
+import android.content.pm.parsing.result.ParseResult;
+import android.content.pm.parsing.result.ParseTypeImpl;
+import android.content.res.TypedArray;
+import android.permission.PermissionManager;
+
+import com.android.internal.pm.parsing.pkg.PackageImpl;
+import com.android.internal.pm.parsing.pkg.ParsedPackage;
+import com.android.internal.pm.pkg.parsing.ParsingPackage;
+import com.android.internal.pm.pkg.parsing.ParsingPackageUtils;
+import com.android.server.SystemConfig;
+
+import java.io.File;
+import java.util.List;
+import java.util.Set;
+
+/** @hide **/
+public class ParsingUtils {
+
+    /**
+     * @see ParsingPackageUtils#parseDefault(ParseInput, File, int, List, boolean,
+     * ParsingPackageUtils.Callback)
+     */
+    @NonNull
+    public static ParseResult<ParsedPackage> parseDefaultOneTime(File file,
+            @ParsingPackageUtils.ParseFlags int parseFlags,
+            @NonNull List<PermissionManager.SplitPermissionInfo> splitPermissions,
+            boolean collectCertificates) {
+        ParseInput input = ParseTypeImpl.forDefaultParsing().reset();
+        return ParsingPackageUtils.parseDefault(input, file, parseFlags, splitPermissions, collectCertificates,
+                new ParsingPackageUtils.Callback() {
+            @Override
+            public boolean hasFeature(String feature) {
+                // Assume the device doesn't support anything. This will affect permission
+                // parsing and will force <uses-permission/> declarations to include all
+                // requiredNotFeature permissions and exclude all requiredFeature
+                // permissions. This mirrors the old behavior.
+                return false;
+            }
+
+            @Override
+            public ParsingPackage startParsingPackage(
+                    @NonNull String packageName,
+                    @NonNull String baseApkPath,
+                    @NonNull String path,
+                    @NonNull TypedArray manifestArray, boolean isCoreApp) {
+                return PackageImpl.forParsing(packageName, baseApkPath, path, manifestArray,
+                        isCoreApp, this);
+            }
+
+            @Override
+            public Set<String> getHiddenApiWhitelistedApps() {
+                return SystemConfig.getInstance().getHiddenApiWhitelistedApps();
+            }
+
+            @Override
+            public Set<String> getInstallConstraintsAllowlist() {
+                return SystemConfig.getInstance().getInstallConstraintsAllowlist();
+            }
+        });
+    }
+}
diff --git a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/parsing/SystemPartitionParseTest.kt b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/parsing/SystemPartitionParseTest.kt
index 1f57b6c..98af63c 100644
--- a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/parsing/SystemPartitionParseTest.kt
+++ b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/parsing/SystemPartitionParseTest.kt
@@ -17,15 +17,15 @@
 package com.android.server.pm.parsing
 
 import android.content.pm.PackageManager
-import com.android.server.pm.pkg.parsing.ParsingPackageUtils
 import android.platform.test.annotations.Postsubmit
+import com.android.internal.pm.pkg.parsing.ParsingPackageUtils
 import com.android.server.pm.PackageManagerException
 import com.android.server.pm.PackageManagerService
 import com.android.server.pm.PackageManagerServiceUtils
+import java.io.File
 import org.junit.Rule
 import org.junit.Test
 import org.junit.rules.TemporaryFolder
-import java.io.File
 
 /**
  * This test parses all the system APKs on the device image to ensure that they succeed.
diff --git a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/parsing/library/AndroidHidlUpdaterTest.java b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/parsing/library/AndroidHidlUpdaterTest.java
index 6cd7123..9517e49 100644
--- a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/parsing/library/AndroidHidlUpdaterTest.java
+++ b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/parsing/library/AndroidHidlUpdaterTest.java
@@ -24,8 +24,8 @@
 
 import androidx.test.filters.SmallTest;
 
+import com.android.internal.pm.parsing.pkg.PackageImpl;
 import com.android.internal.pm.parsing.pkg.ParsedPackage;
-import com.android.server.pm.parsing.pkg.PackageImpl;
 import com.android.server.pm.pkg.AndroidPackage;
 
 import org.junit.Test;
diff --git a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/parsing/library/AndroidNetIpSecIkeUpdaterTest.java b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/parsing/library/AndroidNetIpSecIkeUpdaterTest.java
index 27fd781..01fad8f 100644
--- a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/parsing/library/AndroidNetIpSecIkeUpdaterTest.java
+++ b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/parsing/library/AndroidNetIpSecIkeUpdaterTest.java
@@ -21,8 +21,8 @@
 
 import androidx.test.filters.SmallTest;
 
+import com.android.internal.pm.parsing.pkg.PackageImpl;
 import com.android.internal.pm.parsing.pkg.ParsedPackage;
-import com.android.server.pm.parsing.pkg.PackageImpl;
 import com.android.server.pm.pkg.AndroidPackage;
 
 import org.junit.Test;
diff --git a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/parsing/library/AndroidTestBaseUpdaterTest.java b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/parsing/library/AndroidTestBaseUpdaterTest.java
index b13d6de..b1f26c2 100644
--- a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/parsing/library/AndroidTestBaseUpdaterTest.java
+++ b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/parsing/library/AndroidTestBaseUpdaterTest.java
@@ -23,8 +23,8 @@
 
 import androidx.test.filters.SmallTest;
 
+import com.android.internal.pm.parsing.pkg.PackageImpl;
 import com.android.internal.pm.parsing.pkg.ParsedPackage;
-import com.android.server.pm.parsing.pkg.PackageImpl;
 import com.android.server.pm.pkg.AndroidPackage;
 
 import org.junit.Test;
diff --git a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/parsing/library/AndroidTestRunnerSplitUpdaterTest.java b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/parsing/library/AndroidTestRunnerSplitUpdaterTest.java
index fa69f84..349763a 100644
--- a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/parsing/library/AndroidTestRunnerSplitUpdaterTest.java
+++ b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/parsing/library/AndroidTestRunnerSplitUpdaterTest.java
@@ -24,9 +24,9 @@
 
 import androidx.test.filters.SmallTest;
 
+import com.android.internal.pm.parsing.pkg.PackageImpl;
 import com.android.internal.pm.parsing.pkg.ParsedPackage;
 import com.android.server.pm.parsing.library.PackageBackwardCompatibility.AndroidTestRunnerSplitUpdater;
-import com.android.server.pm.parsing.pkg.PackageImpl;
 import com.android.server.pm.pkg.AndroidPackage;
 
 import org.junit.Test;
diff --git a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/parsing/library/ApexSharedLibraryUpdaterTest.java b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/parsing/library/ApexSharedLibraryUpdaterTest.java
index 856013a..71bdacb 100644
--- a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/parsing/library/ApexSharedLibraryUpdaterTest.java
+++ b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/parsing/library/ApexSharedLibraryUpdaterTest.java
@@ -22,9 +22,9 @@
 
 import androidx.test.filters.SmallTest;
 
+import com.android.internal.pm.parsing.pkg.PackageImpl;
 import com.android.internal.pm.parsing.pkg.ParsedPackage;
 import com.android.server.SystemConfig;
-import com.android.server.pm.parsing.pkg.PackageImpl;
 import com.android.server.pm.pkg.AndroidPackage;
 
 import org.junit.Before;
diff --git a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/parsing/library/ComGoogleAndroidMapsUpdaterTest.java b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/parsing/library/ComGoogleAndroidMapsUpdaterTest.java
index ae5ea21..6aa0c2d 100644
--- a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/parsing/library/ComGoogleAndroidMapsUpdaterTest.java
+++ b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/parsing/library/ComGoogleAndroidMapsUpdaterTest.java
@@ -21,8 +21,8 @@
 
 import androidx.test.filters.SmallTest;
 
+import com.android.internal.pm.parsing.pkg.PackageImpl;
 import com.android.internal.pm.parsing.pkg.ParsedPackage;
-import com.android.server.pm.parsing.pkg.PackageImpl;
 import com.android.server.pm.pkg.AndroidPackage;
 
 import org.junit.Test;
diff --git a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/parsing/library/OrgApacheHttpLegacyUpdaterTest.java b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/parsing/library/OrgApacheHttpLegacyUpdaterTest.java
index e126ffc..44098d0 100644
--- a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/parsing/library/OrgApacheHttpLegacyUpdaterTest.java
+++ b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/parsing/library/OrgApacheHttpLegacyUpdaterTest.java
@@ -23,8 +23,8 @@
 
 import androidx.test.filters.SmallTest;
 
+import com.android.internal.pm.parsing.pkg.PackageImpl;
 import com.android.internal.pm.parsing.pkg.ParsedPackage;
-import com.android.server.pm.parsing.pkg.PackageImpl;
 import com.android.server.pm.pkg.AndroidPackage;
 
 import org.junit.Test;
diff --git a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/parsing/library/PackageBackwardCompatibilityTest.java b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/parsing/library/PackageBackwardCompatibilityTest.java
index d0b0cf8..9d5ce8a 100644
--- a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/parsing/library/PackageBackwardCompatibilityTest.java
+++ b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/parsing/library/PackageBackwardCompatibilityTest.java
@@ -28,10 +28,10 @@
 
 import androidx.test.filters.SmallTest;
 
+import com.android.internal.pm.parsing.pkg.PackageImpl;
 import com.android.internal.pm.parsing.pkg.ParsedPackage;
 import com.android.internal.pm.pkg.parsing.ParsingPackage;
 import com.android.server.pm.parsing.library.PackageBackwardCompatibility.RemoveUnnecessaryAndroidTestBaseLibrary;
-import com.android.server.pm.parsing.pkg.PackageImpl;
 import com.android.server.pm.pkg.AndroidPackage;
 
 import org.junit.Assume;
diff --git a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/parsing/library/RemoveUnnecessaryAndroidTestBaseLibraryTest.java b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/parsing/library/RemoveUnnecessaryAndroidTestBaseLibraryTest.java
index c141c03..bffeb72 100644
--- a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/parsing/library/RemoveUnnecessaryAndroidTestBaseLibraryTest.java
+++ b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/parsing/library/RemoveUnnecessaryAndroidTestBaseLibraryTest.java
@@ -23,9 +23,9 @@
 
 import androidx.test.filters.SmallTest;
 
+import com.android.internal.pm.parsing.pkg.PackageImpl;
 import com.android.internal.pm.parsing.pkg.ParsedPackage;
 import com.android.server.pm.parsing.library.PackageBackwardCompatibility.RemoveUnnecessaryAndroidTestBaseLibrary;
-import com.android.server.pm.parsing.pkg.PackageImpl;
 import com.android.server.pm.pkg.AndroidPackage;
 
 import org.junit.Test;
diff --git a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/parsing/library/RemoveUnnecessaryOrgApacheHttpLegacyLibraryTest.java b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/parsing/library/RemoveUnnecessaryOrgApacheHttpLegacyLibraryTest.java
index a58604b..b114cd3 100644
--- a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/parsing/library/RemoveUnnecessaryOrgApacheHttpLegacyLibraryTest.java
+++ b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/parsing/library/RemoveUnnecessaryOrgApacheHttpLegacyLibraryTest.java
@@ -23,9 +23,9 @@
 
 import androidx.test.filters.SmallTest;
 
+import com.android.internal.pm.parsing.pkg.PackageImpl;
 import com.android.internal.pm.parsing.pkg.ParsedPackage;
 import com.android.server.pm.parsing.library.PackageBackwardCompatibility.RemoveUnnecessaryOrgApacheHttpLegacyLibrary;
-import com.android.server.pm.parsing.pkg.PackageImpl;
 import com.android.server.pm.pkg.AndroidPackage;
 
 import org.junit.Test;
diff --git a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/AndroidPackageTest.kt b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/AndroidPackageTest.kt
index 09b66c1..ef9c62f 100644
--- a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/AndroidPackageTest.kt
+++ b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/AndroidPackageTest.kt
@@ -30,21 +30,21 @@
 import android.util.SparseArray
 import android.util.SparseIntArray
 import com.android.internal.R
-import com.android.server.pm.parsing.pkg.AndroidPackageUtils
-import com.android.server.pm.parsing.pkg.PackageImpl
+import com.android.internal.pm.parsing.pkg.AndroidPackageLegacyUtils
+import com.android.internal.pm.parsing.pkg.PackageImpl
+import com.android.internal.pm.pkg.component.ParsedActivityImpl
+import com.android.internal.pm.pkg.component.ParsedApexSystemServiceImpl
+import com.android.internal.pm.pkg.component.ParsedAttributionImpl
+import com.android.internal.pm.pkg.component.ParsedComponentImpl
+import com.android.internal.pm.pkg.component.ParsedInstrumentationImpl
+import com.android.internal.pm.pkg.component.ParsedIntentInfoImpl
+import com.android.internal.pm.pkg.component.ParsedPermissionGroupImpl
+import com.android.internal.pm.pkg.component.ParsedPermissionImpl
+import com.android.internal.pm.pkg.component.ParsedProcessImpl
+import com.android.internal.pm.pkg.component.ParsedProviderImpl
+import com.android.internal.pm.pkg.component.ParsedServiceImpl
+import com.android.internal.pm.pkg.component.ParsedUsesPermissionImpl
 import com.android.server.pm.pkg.AndroidPackage
-import com.android.server.pm.pkg.component.ParsedActivityImpl
-import com.android.server.pm.pkg.component.ParsedApexSystemServiceImpl
-import com.android.server.pm.pkg.component.ParsedAttributionImpl
-import com.android.server.pm.pkg.component.ParsedComponentImpl
-import com.android.server.pm.pkg.component.ParsedInstrumentationImpl
-import com.android.server.pm.pkg.component.ParsedIntentInfoImpl
-import com.android.server.pm.pkg.component.ParsedPermissionGroupImpl
-import com.android.server.pm.pkg.component.ParsedPermissionImpl
-import com.android.server.pm.pkg.component.ParsedProcessImpl
-import com.android.server.pm.pkg.component.ParsedProviderImpl
-import com.android.server.pm.pkg.component.ParsedServiceImpl
-import com.android.server.pm.pkg.component.ParsedUsesPermissionImpl
 import com.android.server.testutils.mockThrowOnUnmocked
 import com.android.server.testutils.whenever
 import java.security.KeyPairGenerator
@@ -534,34 +534,34 @@
             }
         ),
         getter(AndroidPackage::getKnownActivityEmbeddingCerts, setOf("TESTEMBEDDINGCERT")),
-        getSetByValue({ AndroidPackageUtils.isOdm(it) }, "isOdm", PackageImpl::setOdm, true),
-        getSetByValue({ AndroidPackageUtils.isOem(it) }, "isOem", PackageImpl::setOem, true),
+        getSetByValue({ AndroidPackageLegacyUtils.isOdm(it) }, "isOdm", PackageImpl::setOdm, true),
+        getSetByValue({ AndroidPackageLegacyUtils.isOem(it) }, "isOem", PackageImpl::setOem, true),
         getSetByValue(
-            { AndroidPackageUtils.isPrivileged(it) },
+            { AndroidPackageLegacyUtils.isPrivileged(it) },
             "isPrivileged",
             PackageImpl::setPrivileged,
             true
         ),
         getSetByValue(
-            { AndroidPackageUtils.isProduct(it) },
+            { AndroidPackageLegacyUtils.isProduct(it) },
             "isProduct",
             PackageImpl::setProduct,
             true
         ),
         getSetByValue(
-            { AndroidPackageUtils.isVendor(it) },
+            { AndroidPackageLegacyUtils.isVendor(it) },
             "isVendor",
             PackageImpl::setVendor,
             true
         ),
         getSetByValue(
-            { AndroidPackageUtils.isSystem(it) },
+            { AndroidPackageLegacyUtils.isSystem(it) },
             "isSystem",
             PackageImpl::setSystem,
             true
         ),
         getSetByValue(
-            { AndroidPackageUtils.isSystemExt(it) },
+            { AndroidPackageLegacyUtils.isSystemExt(it) },
             "isSystemExt",
             PackageImpl::setSystemExt,
             true
@@ -593,7 +593,7 @@
                 )
             ) { "" }
         },
-        true
+        true, null
     )
         .asSplit(
             arrayOf("testSplitNameZero", "testSplitNameOne"),
diff --git a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedActivityTest.kt b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedActivityTest.kt
index 2646854..2c8b1cd 100644
--- a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedActivityTest.kt
+++ b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedActivityTest.kt
@@ -18,7 +18,7 @@
 
 import android.content.pm.ActivityInfo
 import com.android.internal.pm.pkg.component.ParsedActivity
-import com.android.server.pm.pkg.component.ParsedActivityImpl
+import com.android.internal.pm.pkg.component.ParsedActivityImpl
 import kotlin.contracts.ExperimentalContracts
 
 @ExperimentalContracts
diff --git a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedAttributionTest.kt b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedAttributionTest.kt
index 52d5b3b..ad53746 100644
--- a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedAttributionTest.kt
+++ b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedAttributionTest.kt
@@ -17,7 +17,7 @@
 package com.android.server.pm.test.parsing.parcelling
 
 import com.android.internal.pm.pkg.component.ParsedAttribution
-import com.android.server.pm.pkg.component.ParsedAttributionImpl
+import com.android.internal.pm.pkg.component.ParsedAttributionImpl
 import kotlin.contracts.ExperimentalContracts
 
 @ExperimentalContracts
diff --git a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedComponentTest.kt b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedComponentTest.kt
index af0c0de..3ac4853 100644
--- a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedComponentTest.kt
+++ b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedComponentTest.kt
@@ -18,8 +18,8 @@
 
 import android.content.pm.PackageManager
 import com.android.internal.pm.pkg.component.ParsedComponent
-import com.android.server.pm.pkg.component.ParsedComponentImpl
-import com.android.server.pm.pkg.component.ParsedIntentInfoImpl
+import com.android.internal.pm.pkg.component.ParsedComponentImpl
+import com.android.internal.pm.pkg.component.ParsedIntentInfoImpl
 import android.os.Bundle
 import android.os.Parcelable
 import kotlin.contracts.ExperimentalContracts
diff --git a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedInstrumentationTest.kt b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedInstrumentationTest.kt
index dc0f194..2bd4f61 100644
--- a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedInstrumentationTest.kt
+++ b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedInstrumentationTest.kt
@@ -17,7 +17,7 @@
 package com.android.server.pm.test.parsing.parcelling
 
 import com.android.internal.pm.pkg.component.ParsedInstrumentation
-import com.android.server.pm.pkg.component.ParsedInstrumentationImpl
+import com.android.internal.pm.pkg.component.ParsedInstrumentationImpl
 import kotlin.contracts.ExperimentalContracts
 
 @ExperimentalContracts
diff --git a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedIntentInfoTest.kt b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedIntentInfoTest.kt
index 5224f23..af385e2 100644
--- a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedIntentInfoTest.kt
+++ b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedIntentInfoTest.kt
@@ -17,7 +17,7 @@
 package com.android.server.pm.test.parsing.parcelling
 
 import com.android.internal.pm.pkg.component.ParsedIntentInfo
-import com.android.server.pm.pkg.component.ParsedIntentInfoImpl
+import com.android.internal.pm.pkg.component.ParsedIntentInfoImpl
 import android.os.Parcelable
 import android.os.PatternMatcher
 import kotlin.contracts.ExperimentalContracts
diff --git a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedMainComponentTest.kt b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedMainComponentTest.kt
index dfff602..061e39d 100644
--- a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedMainComponentTest.kt
+++ b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedMainComponentTest.kt
@@ -17,7 +17,7 @@
 package com.android.server.pm.test.parsing.parcelling
 
 import com.android.internal.pm.pkg.component.ParsedMainComponent
-import com.android.server.pm.pkg.component.ParsedMainComponentImpl
+import com.android.internal.pm.pkg.component.ParsedMainComponentImpl
 import android.os.Parcelable
 import java.util.Arrays
 import kotlin.contracts.ExperimentalContracts
diff --git a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedPermissionGroupTest.kt b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedPermissionGroupTest.kt
index ccbf558..3a64188 100644
--- a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedPermissionGroupTest.kt
+++ b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedPermissionGroupTest.kt
@@ -17,7 +17,7 @@
 package com.android.server.pm.test.parsing.parcelling
 
 import com.android.internal.pm.pkg.component.ParsedPermissionGroup
-import com.android.server.pm.pkg.component.ParsedPermissionGroupImpl
+import com.android.internal.pm.pkg.component.ParsedPermissionGroupImpl
 import kotlin.contracts.ExperimentalContracts
 
 @ExperimentalContracts
diff --git a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedPermissionTest.kt b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedPermissionTest.kt
index 2814783..551f16d 100644
--- a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedPermissionTest.kt
+++ b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedPermissionTest.kt
@@ -18,8 +18,8 @@
 
 import com.android.internal.pm.pkg.component.ParsedPermission
 import com.android.internal.pm.pkg.component.ParsedPermissionGroup
-import com.android.server.pm.pkg.component.ParsedPermissionGroupImpl
-import com.android.server.pm.pkg.component.ParsedPermissionImpl
+import com.android.internal.pm.pkg.component.ParsedPermissionGroupImpl
+import com.android.internal.pm.pkg.component.ParsedPermissionImpl
 import kotlin.contracts.ExperimentalContracts
 
 @ExperimentalContracts
diff --git a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedProcessTest.kt b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedProcessTest.kt
index 2e96046..93bdeae 100644
--- a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedProcessTest.kt
+++ b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedProcessTest.kt
@@ -16,9 +16,9 @@
 
 package com.android.server.pm.test.parsing.parcelling
 
-import com.android.internal.pm.pkg.component.ParsedProcess
-import com.android.server.pm.pkg.component.ParsedProcessImpl
 import android.util.ArrayMap
+import com.android.internal.pm.pkg.component.ParsedProcess
+import com.android.internal.pm.pkg.component.ParsedProcessImpl
 import kotlin.contracts.ExperimentalContracts
 
 @ExperimentalContracts
diff --git a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedProviderTest.kt b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedProviderTest.kt
index 290dbd6..1e84470 100644
--- a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedProviderTest.kt
+++ b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedProviderTest.kt
@@ -17,9 +17,9 @@
 package com.android.server.pm.test.parsing.parcelling
 
 import android.content.pm.PathPermission
-import com.android.internal.pm.pkg.component.ParsedProvider
-import com.android.server.pm.pkg.component.ParsedProviderImpl
 import android.os.PatternMatcher
+import com.android.internal.pm.pkg.component.ParsedProvider
+import com.android.internal.pm.pkg.component.ParsedProviderImpl
 import kotlin.contracts.ExperimentalContracts
 
 @ExperimentalContracts
diff --git a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedServiceTest.kt b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedServiceTest.kt
index 3ae7e92..79d5a4f 100644
--- a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedServiceTest.kt
+++ b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedServiceTest.kt
@@ -17,7 +17,7 @@
 package com.android.server.pm.test.parsing.parcelling
 
 import com.android.internal.pm.pkg.component.ParsedService
-import com.android.server.pm.pkg.component.ParsedServiceImpl
+import com.android.internal.pm.pkg.component.ParsedServiceImpl
 import kotlin.contracts.ExperimentalContracts
 
 @ExperimentalContracts
diff --git a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedUsesPermissionTest.kt b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedUsesPermissionTest.kt
index 67dfc6d..d0ad09b 100644
--- a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedUsesPermissionTest.kt
+++ b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/parsing/parcelling/ParsedUsesPermissionTest.kt
@@ -17,7 +17,7 @@
 package com.android.server.pm.test.parsing.parcelling
 
 import com.android.internal.pm.pkg.component.ParsedUsesPermission
-import com.android.server.pm.pkg.component.ParsedUsesPermissionImpl
+import com.android.internal.pm.pkg.component.ParsedUsesPermissionImpl
 import kotlin.contracts.ExperimentalContracts
 
 @ExperimentalContracts
diff --git a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/pkg/PackageStateTest.kt b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/pkg/PackageStateTest.kt
index 1da3a22..b21c349 100644
--- a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/pkg/PackageStateTest.kt
+++ b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/pkg/PackageStateTest.kt
@@ -17,37 +17,34 @@
 package com.android.server.pm.test.pkg
 
 import android.content.Intent
-import android.content.pm.overlay.OverlayPaths
 import android.content.pm.PackageManager
 import android.content.pm.PathPermission
 import android.content.pm.SharedLibraryInfo
 import android.content.pm.VersionedPackage
+import android.content.pm.overlay.OverlayPaths
 import android.os.PatternMatcher
 import android.util.ArraySet
+import com.android.internal.pm.parsing.pkg.PackageImpl
 import com.android.internal.pm.pkg.component.ParsedActivity
+import com.android.internal.pm.pkg.component.ParsedActivityImpl
+import com.android.internal.pm.pkg.component.ParsedComponentImpl
 import com.android.internal.pm.pkg.component.ParsedInstrumentation
+import com.android.internal.pm.pkg.component.ParsedIntentInfoImpl
 import com.android.internal.pm.pkg.component.ParsedPermission
 import com.android.internal.pm.pkg.component.ParsedPermissionGroup
+import com.android.internal.pm.pkg.component.ParsedPermissionImpl
 import com.android.internal.pm.pkg.component.ParsedProcess
+import com.android.internal.pm.pkg.component.ParsedProcessImpl
 import com.android.internal.pm.pkg.component.ParsedProvider
+import com.android.internal.pm.pkg.component.ParsedProviderImpl
 import com.android.internal.pm.pkg.component.ParsedService
 import com.android.server.pm.PackageSetting
 import com.android.server.pm.PackageSettingBuilder
-import com.android.server.pm.parsing.pkg.PackageImpl
 import com.android.server.pm.pkg.AndroidPackage
 import com.android.server.pm.pkg.PackageState
 import com.android.server.pm.pkg.PackageUserState
-import com.android.server.pm.pkg.component.ParsedActivityImpl
-import com.android.server.pm.pkg.component.ParsedComponentImpl
-import com.android.server.pm.pkg.component.ParsedIntentInfoImpl
-import com.android.server.pm.pkg.component.ParsedPermissionImpl
-import com.android.server.pm.pkg.component.ParsedProcessImpl
-import com.android.server.pm.pkg.component.ParsedProviderImpl
 import com.android.server.pm.test.parsing.parcelling.AndroidPackageTest
 import com.google.common.truth.Expect
-import org.junit.Rule
-import org.junit.Test
-import org.junit.rules.TemporaryFolder
 import kotlin.contracts.ExperimentalContracts
 import kotlin.reflect.KClass
 import kotlin.reflect.KFunction
@@ -55,6 +52,9 @@
 import kotlin.reflect.full.isSubtypeOf
 import kotlin.reflect.full.memberFunctions
 import kotlin.reflect.full.starProjectedType
+import org.junit.Rule
+import org.junit.Test
+import org.junit.rules.TemporaryFolder
 
 class PackageStateTest {
 
diff --git a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationCollectorTest.kt b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationCollectorTest.kt
index 9341e9d..5e73d19 100644
--- a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationCollectorTest.kt
+++ b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationCollectorTest.kt
@@ -22,11 +22,11 @@
 import android.os.PatternMatcher
 import android.util.ArraySet
 import com.android.internal.pm.parsing.pkg.AndroidPackageInternal
+import com.android.internal.pm.pkg.component.ParsedActivityImpl
+import com.android.internal.pm.pkg.component.ParsedIntentInfoImpl
 import com.android.server.SystemConfig
 import com.android.server.compat.PlatformCompat
 import com.android.server.pm.pkg.AndroidPackage
-import com.android.server.pm.pkg.component.ParsedActivityImpl
-import com.android.server.pm.pkg.component.ParsedIntentInfoImpl
 import com.android.server.pm.verify.domain.DomainVerificationCollector
 import com.android.server.testutils.mockThrowOnUnmocked
 import com.android.server.testutils.whenever
diff --git a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationEnforcerTest.kt b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationEnforcerTest.kt
index a737b90..d307608 100644
--- a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationEnforcerTest.kt
+++ b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationEnforcerTest.kt
@@ -29,17 +29,22 @@
 import android.util.SparseArray
 import androidx.test.platform.app.InstrumentationRegistry
 import com.android.internal.pm.parsing.pkg.AndroidPackageInternal
+import com.android.internal.pm.pkg.component.ParsedActivityImpl
+import com.android.internal.pm.pkg.component.ParsedIntentInfoImpl
 import com.android.server.pm.Computer
 import com.android.server.pm.pkg.PackageStateInternal
 import com.android.server.pm.pkg.PackageUserStateInternal
-import com.android.server.pm.pkg.component.ParsedActivityImpl
-import com.android.server.pm.pkg.component.ParsedIntentInfoImpl
 import com.android.server.pm.verify.domain.DomainVerificationEnforcer
 import com.android.server.pm.verify.domain.DomainVerificationManagerInternal
 import com.android.server.pm.verify.domain.DomainVerificationService
 import com.android.server.pm.verify.domain.proxy.DomainVerificationProxy
 import com.android.server.testutils.mockThrowOnUnmocked
 import com.android.server.testutils.whenever
+import java.util.UUID
+import java.util.concurrent.atomic.AtomicBoolean
+import java.util.concurrent.atomic.AtomicInteger
+import kotlin.test.assertFailsWith
+import kotlin.test.fail
 import org.junit.Test
 import org.junit.runner.RunWith
 import org.junit.runners.Parameterized
@@ -51,11 +56,6 @@
 import org.mockito.Mockito.eq
 import org.mockito.Mockito.mock
 import org.mockito.Mockito.verifyNoMoreInteractions
-import java.util.UUID
-import java.util.concurrent.atomic.AtomicBoolean
-import java.util.concurrent.atomic.AtomicInteger
-import kotlin.test.assertFailsWith
-import kotlin.test.fail
 
 @RunWith(Parameterized::class)
 class DomainVerificationEnforcerTest {
diff --git a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationManagerApiTest.kt b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationManagerApiTest.kt
index f38df22..5edf30a3 100644
--- a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationManagerApiTest.kt
+++ b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationManagerApiTest.kt
@@ -30,24 +30,24 @@
 import android.util.ArraySet
 import android.util.SparseArray
 import com.android.internal.pm.parsing.pkg.AndroidPackageInternal
+import com.android.internal.pm.pkg.component.ParsedActivityImpl
+import com.android.internal.pm.pkg.component.ParsedIntentInfoImpl
 import com.android.server.pm.pkg.PackageStateInternal
 import com.android.server.pm.pkg.PackageUserStateInternal
-import com.android.server.pm.pkg.component.ParsedActivityImpl
-import com.android.server.pm.pkg.component.ParsedIntentInfoImpl
 import com.android.server.pm.verify.domain.DomainVerificationManagerStub
 import com.android.server.pm.verify.domain.DomainVerificationService
 import com.android.server.testutils.mockThrowOnUnmocked
 import com.android.server.testutils.whenever
 import com.google.common.truth.Truth.assertThat
+import java.util.UUID
+import java.util.concurrent.atomic.AtomicBoolean
+import kotlin.test.assertFailsWith
 import org.junit.Test
 import org.mockito.ArgumentMatchers.any
 import org.mockito.ArgumentMatchers.anyInt
 import org.mockito.ArgumentMatchers.anyLong
 import org.mockito.ArgumentMatchers.anyString
 import org.mockito.Mockito.doReturn
-import java.util.UUID
-import java.util.concurrent.atomic.AtomicBoolean
-import kotlin.test.assertFailsWith
 
 class DomainVerificationManagerApiTest {
 
diff --git a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationPackageTest.kt b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationPackageTest.kt
index 874e0d2..85f0125 100644
--- a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationPackageTest.kt
+++ b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationPackageTest.kt
@@ -37,27 +37,27 @@
 import android.util.SparseArray
 import android.util.Xml
 import com.android.internal.pm.parsing.pkg.AndroidPackageInternal
+import com.android.internal.pm.pkg.component.ParsedActivityImpl
+import com.android.internal.pm.pkg.component.ParsedIntentInfoImpl
 import com.android.server.pm.Computer
 import com.android.server.pm.pkg.PackageStateInternal
 import com.android.server.pm.pkg.PackageUserStateInternal
-import com.android.server.pm.pkg.component.ParsedActivityImpl
-import com.android.server.pm.pkg.component.ParsedIntentInfoImpl
 import com.android.server.pm.verify.domain.DomainVerificationService
 import com.android.server.testutils.mock
 import com.android.server.testutils.mockThrowOnUnmocked
 import com.android.server.testutils.spy
 import com.android.server.testutils.whenever
 import com.google.common.truth.Truth.assertThat
+import java.io.ByteArrayInputStream
+import java.io.ByteArrayOutputStream
+import java.security.PublicKey
+import java.util.UUID
 import org.junit.Test
 import org.mockito.ArgumentMatchers.any
 import org.mockito.ArgumentMatchers.anyInt
 import org.mockito.ArgumentMatchers.anyLong
 import org.mockito.ArgumentMatchers.anyString
 import org.mockito.Mockito.doReturn
-import java.io.ByteArrayInputStream
-import java.io.ByteArrayOutputStream
-import java.security.PublicKey
-import java.util.UUID
 
 class DomainVerificationPackageTest {
 
diff --git a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationSettingsMutationTest.kt b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationSettingsMutationTest.kt
index 3207e6c..a5c4f6c 100644
--- a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationSettingsMutationTest.kt
+++ b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationSettingsMutationTest.kt
@@ -25,15 +25,16 @@
 import android.util.ArraySet
 import android.util.SparseArray
 import com.android.internal.pm.parsing.pkg.AndroidPackageInternal
+import com.android.internal.pm.pkg.component.ParsedActivityImpl
+import com.android.internal.pm.pkg.component.ParsedIntentInfoImpl
 import com.android.server.pm.pkg.PackageStateInternal
 import com.android.server.pm.pkg.PackageUserStateInternal
-import com.android.server.pm.pkg.component.ParsedActivityImpl
-import com.android.server.pm.pkg.component.ParsedIntentInfoImpl
 import com.android.server.pm.verify.domain.DomainVerificationManagerInternal
 import com.android.server.pm.verify.domain.DomainVerificationService
 import com.android.server.pm.verify.domain.proxy.DomainVerificationProxy
 import com.android.server.testutils.mockThrowOnUnmocked
 import com.android.server.testutils.whenever
+import java.util.UUID
 import org.junit.Test
 import org.junit.runner.RunWith
 import org.junit.runners.Parameterized
@@ -44,7 +45,6 @@
 import org.mockito.Mockito.doReturn
 import org.mockito.Mockito.eq
 import org.mockito.Mockito.verify
-import java.util.UUID
 
 @RunWith(Parameterized::class)
 class DomainVerificationSettingsMutationTest {
diff --git a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationUserSelectionOverrideTest.kt b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationUserSelectionOverrideTest.kt
index a90b7d5..ae570a3 100644
--- a/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationUserSelectionOverrideTest.kt
+++ b/services/tests/PackageManagerServiceTests/unit/src/com/android/server/pm/test/verify/domain/DomainVerificationUserSelectionOverrideTest.kt
@@ -27,14 +27,15 @@
 import android.util.ArraySet
 import android.util.SparseArray
 import com.android.internal.pm.parsing.pkg.AndroidPackageInternal
+import com.android.internal.pm.pkg.component.ParsedActivityImpl
+import com.android.internal.pm.pkg.component.ParsedIntentInfoImpl
 import com.android.server.pm.pkg.PackageStateInternal
 import com.android.server.pm.pkg.PackageUserStateInternal
-import com.android.server.pm.pkg.component.ParsedActivityImpl
-import com.android.server.pm.pkg.component.ParsedIntentInfoImpl
 import com.android.server.pm.verify.domain.DomainVerificationService
 import com.android.server.testutils.mockThrowOnUnmocked
 import com.android.server.testutils.whenever
 import com.google.common.truth.Truth.assertThat
+import java.util.UUID
 import org.junit.Test
 import org.mockito.ArgumentMatchers.any
 import org.mockito.ArgumentMatchers.anyInt
@@ -42,8 +43,6 @@
 import org.mockito.ArgumentMatchers.anyString
 import org.mockito.Mockito.doReturn
 
-import java.util.UUID
-
 class DomainVerificationUserStateOverrideTest {
 
     companion object {
diff --git a/services/tests/mockingservicestests/src/com/android/server/app/GameManagerServiceTests.java b/services/tests/mockingservicestests/src/com/android/server/app/GameManagerServiceTests.java
index 76d4d55..9739e4b 100644
--- a/services/tests/mockingservicestests/src/com/android/server/app/GameManagerServiceTests.java
+++ b/services/tests/mockingservicestests/src/com/android/server/app/GameManagerServiceTests.java
@@ -2423,6 +2423,14 @@
                             }
                         }));
 
+        when(mSysPropsMock.getInt(
+                ArgumentMatchers.eq(PROPERTY_RO_SURFACEFLINGER_GAME_DEFAULT_FRAME_RATE),
+                anyInt())).thenReturn(60);
+        when(mSysPropsMock.getBoolean(
+                ArgumentMatchers.eq(PROPERTY_PERSISTENT_GFX_GAME_DEFAULT_FRAME_RATE_ENABLED),
+                ArgumentMatchers.eq(true))).thenReturn(true);
+        gameManagerService.onBootCompleted();
+
         // Set up a game in the foreground.
         String[] packages = {mPackageName};
         when(mMockPackageManager.getPackagesForUid(DEFAULT_PACKAGE_UID)).thenReturn(packages);
@@ -2430,12 +2438,6 @@
                 DEFAULT_PACKAGE_UID, ActivityManager.PROCESS_STATE_TOP, 0, 0);
 
         // Toggle game default frame rate on.
-        when(mSysPropsMock.getInt(
-                ArgumentMatchers.eq(PROPERTY_RO_SURFACEFLINGER_GAME_DEFAULT_FRAME_RATE),
-                anyInt())).thenReturn(60);
-        when(mSysPropsMock.getBoolean(
-                ArgumentMatchers.eq(PROPERTY_PERSISTENT_GFX_GAME_DEFAULT_FRAME_RATE_ENABLED),
-                ArgumentMatchers.eq(true))).thenReturn(true);
         gameManagerService.toggleGameDefaultFrameRate(true);
 
         // Verify that:
diff --git a/services/tests/mockingservicestests/src/com/android/server/job/controllers/BackgroundJobsControllerTest.java b/services/tests/mockingservicestests/src/com/android/server/job/controllers/BackgroundJobsControllerTest.java
index 23886a1..00fe3d9 100644
--- a/services/tests/mockingservicestests/src/com/android/server/job/controllers/BackgroundJobsControllerTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/job/controllers/BackgroundJobsControllerTest.java
@@ -25,6 +25,7 @@
 
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyBoolean;
 import static org.mockito.ArgumentMatchers.anyInt;
@@ -167,8 +168,12 @@
     }
 
     private void setStoppedState(int uid, String pkgName, boolean stopped) {
-        doReturn(stopped).when(mPackageManagerInternal).isPackageStopped(pkgName, uid);
-        sendPackageStoppedBroadcast(uid, pkgName, stopped);
+        try {
+            doReturn(stopped).when(mPackageManagerInternal).isPackageStopped(pkgName, uid);
+            sendPackageStoppedBroadcast(uid, pkgName, stopped);
+        } catch (PackageManager.NameNotFoundException e) {
+            fail("Unable to set stopped state for unknown package: " + pkgName);
+        }
     }
 
     private void sendPackageStoppedBroadcast(int uid, String pkgName, boolean stopped) {
diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/ApexManagerTest.java b/services/tests/mockingservicestests/src/com/android/server/pm/ApexManagerTest.java
index de8b308..c2b52b4 100644
--- a/services/tests/mockingservicestests/src/com/android/server/pm/ApexManagerTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/pm/ApexManagerTest.java
@@ -45,14 +45,15 @@
 import android.os.RemoteException;
 import android.os.ServiceSpecificException;
 import android.platform.test.annotations.Presubmit;
+import android.util.ArraySet;
 
 import androidx.test.filters.SmallTest;
 import androidx.test.runner.AndroidJUnit4;
 
+import com.android.internal.pm.parsing.pkg.AndroidPackageLegacyUtils;
+import com.android.internal.pm.pkg.parsing.ParsingPackageUtils;
 import com.android.server.pm.parsing.PackageParser2;
-import com.android.server.pm.parsing.pkg.AndroidPackageUtils;
 import com.android.server.pm.pkg.AndroidPackage;
-import com.android.server.pm.pkg.parsing.ParsingPackageUtils;
 
 import org.junit.Before;
 import org.junit.Rule;
@@ -67,6 +68,7 @@
 import java.io.OutputStream;
 import java.util.List;
 import java.util.Objects;
+import java.util.Set;
 
 @SmallTest
 @Presubmit
@@ -106,6 +108,18 @@
             public boolean hasFeature(String feature) {
                 return true;
             }
+
+            @androidx.annotation.NonNull
+            @Override
+            public Set<String> getHiddenApiWhitelistedApps() {
+                return new ArraySet<>();
+            }
+
+            @androidx.annotation.NonNull
+            @Override
+            public Set<String> getInstallConstraintsAllowlist() {
+                return new ArraySet<>();
+            }
         });
 
         mMockSystem.system().stageNominalSystemState();
@@ -385,7 +399,7 @@
                 findFactory(results, "test.apex.rebootless").apexInfo);
         assertThat(factoryPkg.getBaseApkPath()).isEqualTo(activeApexInfo.modulePath);
         assertThat(factoryPkg.getLongVersionCode()).isEqualTo(1);
-        assertThat(AndroidPackageUtils.isSystem(factoryPkg)).isTrue();
+        assertThat(AndroidPackageLegacyUtils.isSystem(factoryPkg)).isTrue();
     }
 
     @Test
@@ -416,7 +430,7 @@
                 findFactory(results, "test.apex.rebootless").apexInfo);
         assertThat(factoryPkg.getBaseApkPath()).isEqualTo(factoryApexInfo.modulePath);
         assertThat(factoryPkg.getLongVersionCode()).isEqualTo(1);
-        assertThat(AndroidPackageUtils.isSystem(factoryPkg)).isTrue();
+        assertThat(AndroidPackageLegacyUtils.isSystem(factoryPkg)).isTrue();
     }
 
     @Test
diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/MockSystem.kt b/services/tests/mockingservicestests/src/com/android/server/pm/MockSystem.kt
index 28bd987..7b29e2a 100644
--- a/services/tests/mockingservicestests/src/com/android/server/pm/MockSystem.kt
+++ b/services/tests/mockingservicestests/src/com/android/server/pm/MockSystem.kt
@@ -56,8 +56,10 @@
 import com.android.dx.mockito.inline.extended.StaticMockitoSession
 import com.android.dx.mockito.inline.extended.StaticMockitoSessionBuilder
 import com.android.internal.R
+import com.android.internal.pm.parsing.pkg.PackageImpl
 import com.android.internal.pm.parsing.pkg.ParsedPackage
 import com.android.internal.pm.pkg.parsing.ParsingPackage
+import com.android.internal.pm.pkg.parsing.ParsingPackageUtils
 import com.android.server.LocalManagerRegistry
 import com.android.server.LocalServices
 import com.android.server.LockGuard
@@ -68,10 +70,8 @@
 import com.android.server.pm.dex.DexManager
 import com.android.server.pm.dex.DynamicCodeLogger
 import com.android.server.pm.parsing.PackageParser2
-import com.android.server.pm.parsing.pkg.PackageImpl
 import com.android.server.pm.permission.PermissionManagerServiceInternal
 import com.android.server.pm.pkg.AndroidPackage
-import com.android.server.pm.pkg.parsing.ParsingPackageUtils
 import com.android.server.pm.resolution.ComponentResolver
 import com.android.server.pm.snapshot.PackageDataSnapshot
 import com.android.server.pm.verify.domain.DomainVerificationManagerInternal
@@ -81,14 +81,6 @@
 import com.android.server.testutils.nullable
 import com.android.server.testutils.whenever
 import com.android.server.utils.WatchedArrayMap
-import libcore.util.HexEncoding
-import org.junit.Assert
-import org.junit.rules.TestRule
-import org.junit.runner.Description
-import org.junit.runners.model.Statement
-import org.mockito.AdditionalMatchers.or
-import org.mockito.Mockito
-import org.mockito.quality.Strictness
 import java.io.File
 import java.io.IOException
 import java.nio.file.Files
@@ -97,6 +89,14 @@
 import java.util.Arrays
 import java.util.Random
 import java.util.concurrent.FutureTask
+import libcore.util.HexEncoding
+import org.junit.Assert
+import org.junit.rules.TestRule
+import org.junit.runner.Description
+import org.junit.runners.model.Statement
+import org.mockito.AdditionalMatchers.or
+import org.mockito.Mockito
+import org.mockito.quality.Strictness
 
 /**
  * A utility for mocking behavior of the system and dependencies when testing PackageManagerService
diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/PackageArchiverTest.java b/services/tests/mockingservicestests/src/com/android/server/pm/PackageArchiverTest.java
index 2332988..e5ecdc4 100644
--- a/services/tests/mockingservicestests/src/com/android/server/pm/PackageArchiverTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/pm/PackageArchiverTest.java
@@ -62,6 +62,7 @@
 import android.os.UserHandle;
 import android.platform.test.annotations.Presubmit;
 import android.text.TextUtils;
+import android.util.SparseArray;
 
 import androidx.test.filters.SmallTest;
 import androidx.test.runner.AndroidJUnit4;
@@ -174,6 +175,7 @@
         mUserState = new PackageUserStateImpl().setInstalled(true);
         mPackageSetting.setUserState(mUserId, mUserState);
         when(mPackageState.getUserStateOrDefault(eq(mUserId))).thenReturn(mUserState);
+        when(mPackageState.getUserStates()).thenReturn(new SparseArray<>());
 
         when(mContext.getSystemService(LauncherApps.class)).thenReturn(mLauncherApps);
         when(mContext.getSystemService(AppOpsManager.class)).thenReturn(
@@ -343,7 +345,7 @@
 
     @Test
     public void archiveApp_appOptedOutOfArchiving() {
-        when(mAppOpsManager.checkOp(
+        when(mAppOpsManager.checkOpNoThrow(
                 eq(AppOpsManager.OP_AUTO_REVOKE_PERMISSIONS_IF_UNUSED),
                 anyInt(), eq(PACKAGE))).thenReturn(MODE_IGNORED);
 
@@ -430,7 +432,7 @@
     @Test
     public void isAppArchivable_appOptedOutOfArchiving()
             throws PackageManager.NameNotFoundException {
-        when(mAppOpsManager.checkOp(
+        when(mAppOpsManager.checkOpNoThrow(
                 eq(AppOpsManager.OP_AUTO_REVOKE_PERMISSIONS_IF_UNUSED),
                 anyInt(), eq(PACKAGE))).thenReturn(MODE_IGNORED);
 
@@ -551,22 +553,12 @@
         when(mComputer.getPackageStateFiltered(eq(PACKAGE), anyInt(), anyInt())).thenReturn(
                 null);
 
-        Exception e = assertThrows(
-                ParcelableException.class,
-                () -> mArchiveManager.getArchivedAppIcon(PACKAGE, UserHandle.CURRENT));
-        assertThat(e.getCause()).isInstanceOf(PackageManager.NameNotFoundException.class);
-        assertThat(e.getCause()).hasMessageThat().isEqualTo(
-                String.format("Package %s not found.", PACKAGE));
+        assertThat(mArchiveManager.getArchivedAppIcon(PACKAGE, UserHandle.CURRENT)).isNull();
     }
 
     @Test
     public void getArchivedAppIcon_notArchived() {
-        Exception e = assertThrows(
-                ParcelableException.class,
-                () -> mArchiveManager.getArchivedAppIcon(PACKAGE, UserHandle.CURRENT));
-        assertThat(e.getCause()).isInstanceOf(PackageManager.NameNotFoundException.class);
-        assertThat(e.getCause()).hasMessageThat().isEqualTo(
-                String.format("Package %s is not currently archived.", PACKAGE));
+        assertThat(mArchiveManager.getArchivedAppIcon(PACKAGE, UserHandle.CURRENT)).isNull();
     }
 
     @Test
diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/PackageHelperTestBase.kt b/services/tests/mockingservicestests/src/com/android/server/pm/PackageHelperTestBase.kt
index a6ba5d4..7b80aea 100644
--- a/services/tests/mockingservicestests/src/com/android/server/pm/PackageHelperTestBase.kt
+++ b/services/tests/mockingservicestests/src/com/android/server/pm/PackageHelperTestBase.kt
@@ -36,6 +36,7 @@
 open class PackageHelperTestBase {
 
     companion object {
+        const val PLATFORM_PACKAGE_NAME = "android"
         const val TEST_PACKAGE_1 = "com.android.test.package1"
         const val TEST_PACKAGE_2 = "com.android.test.package2"
         const val DEVICE_OWNER_PACKAGE = "com.android.test.owner"
diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/SharedLibrariesImplTest.kt b/services/tests/mockingservicestests/src/com/android/server/pm/SharedLibrariesImplTest.kt
index e685c3f..944b1aa 100644
--- a/services/tests/mockingservicestests/src/com/android/server/pm/SharedLibrariesImplTest.kt
+++ b/services/tests/mockingservicestests/src/com/android/server/pm/SharedLibrariesImplTest.kt
@@ -21,17 +21,17 @@
 import android.content.pm.SharedLibraryInfo
 import android.content.pm.VersionedPackage
 import android.os.Build
-import android.os.storage.StorageManager
 import android.os.UserHandle
+import android.os.storage.StorageManager
 import android.util.ArrayMap
 import android.util.PackageUtils
+import com.android.internal.pm.parsing.pkg.PackageImpl
 import com.android.internal.pm.parsing.pkg.ParsedPackage
 import com.android.server.SystemConfig.SharedLibraryEntry
 import com.android.server.compat.PlatformCompat
 import com.android.server.extendedtestutils.wheneverStatic
 import com.android.server.pm.PackageManagerService.PLATFORM_PACKAGE_NAME
 import com.android.server.pm.pkg.AndroidPackage
-import com.android.server.pm.parsing.pkg.PackageImpl
 import com.android.server.testutils.any
 import com.android.server.testutils.eq
 import com.android.server.testutils.mock
diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/SuspendPackageHelperTest.kt b/services/tests/mockingservicestests/src/com/android/server/pm/SuspendPackageHelperTest.kt
index 1473033..ae53e70 100644
--- a/services/tests/mockingservicestests/src/com/android/server/pm/SuspendPackageHelperTest.kt
+++ b/services/tests/mockingservicestests/src/com/android/server/pm/SuspendPackageHelperTest.kt
@@ -283,6 +283,72 @@
     }
 
     @Test
+    fun getSuspendingPackagePrecedence() {
+        val launcherExtras = PersistableBundle()
+        launcherExtras.putString(TEST_PACKAGE_2, TEST_PACKAGE_2)
+        val targetPackages = arrayOf(TEST_PACKAGE_2)
+        // Suspend.
+        var failedNames = suspendPackageHelper.setPackagesSuspended(pms.snapshotComputer(),
+                targetPackages, true /* suspended */, null /* appExtras */, launcherExtras,
+                null /* dialogInfo */, DEVICE_OWNER_PACKAGE, TEST_USER_ID, deviceOwnerUid,
+                false /* quarantined */)
+        assertThat(failedNames).isEmpty()
+        testHandler.flush()
+
+        assertThat(suspendPackageHelper.getSuspendingPackage(pms.snapshotComputer(),
+                TEST_PACKAGE_2, TEST_USER_ID, deviceOwnerUid)).isEqualTo(DEVICE_OWNER_PACKAGE)
+
+        // Suspend by system.
+        failedNames = suspendPackageHelper.setPackagesSuspended(pms.snapshotComputer(),
+                targetPackages, true /* suspended */, null /* appExtras */, launcherExtras,
+                null /* dialogInfo */, PLATFORM_PACKAGE_NAME, TEST_USER_ID, deviceOwnerUid,
+                false /* quarantined */)
+        assertThat(failedNames).isEmpty()
+        testHandler.flush()
+
+        assertThat(suspendPackageHelper.getSuspendingPackage(pms.snapshotComputer(),
+                TEST_PACKAGE_2, TEST_USER_ID, deviceOwnerUid)).isEqualTo(PLATFORM_PACKAGE_NAME)
+
+        // QAS by package1.
+        failedNames = suspendPackageHelper.setPackagesSuspended(pms.snapshotComputer(),
+                targetPackages, true /* suspended */, null /* appExtras */, launcherExtras,
+                null /* dialogInfo */, TEST_PACKAGE_1, TEST_USER_ID, deviceOwnerUid,
+                true /* quarantined */)
+        assertThat(failedNames).isEmpty()
+        testHandler.flush()
+
+        assertThat(suspendPackageHelper.getSuspendingPackage(pms.snapshotComputer(),
+                TEST_PACKAGE_2, TEST_USER_ID, deviceOwnerUid)).isEqualTo(TEST_PACKAGE_1)
+
+        // Un-QAS by package1.
+        suspendPackageHelper.removeSuspensionsBySuspendingPackage(pms.snapshotComputer(),
+                targetPackages, { suspendingPackage -> suspendingPackage == TEST_PACKAGE_1 },
+                TEST_USER_ID)
+        testHandler.flush()
+
+        assertThat(suspendPackageHelper.getSuspendingPackage(pms.snapshotComputer(),
+                TEST_PACKAGE_2, TEST_USER_ID, deviceOwnerUid)).isEqualTo(PLATFORM_PACKAGE_NAME)
+
+        // Un-suspend by system.
+        suspendPackageHelper.removeSuspensionsBySuspendingPackage(pms.snapshotComputer(),
+                targetPackages, { suspendingPackage -> suspendingPackage == PLATFORM_PACKAGE_NAME },
+                TEST_USER_ID)
+        testHandler.flush()
+
+        assertThat(suspendPackageHelper.getSuspendingPackage(pms.snapshotComputer(),
+                TEST_PACKAGE_2, TEST_USER_ID, deviceOwnerUid)).isEqualTo(DEVICE_OWNER_PACKAGE)
+
+        // Unsuspend.
+        suspendPackageHelper.removeSuspensionsBySuspendingPackage(pms.snapshotComputer(),
+                targetPackages, { suspendingPackage -> suspendingPackage == DEVICE_OWNER_PACKAGE },
+                TEST_USER_ID)
+        testHandler.flush()
+
+        assertThat(suspendPackageHelper.getSuspendingPackage(pms.snapshotComputer(),
+                TEST_PACKAGE_2, TEST_USER_ID, deviceOwnerUid)).isNull()
+    }
+
+    @Test
     fun getSuspendedDialogInfo() {
         val dialogInfo = SuspendDialogInfo.Builder()
             .setTitle(TEST_PACKAGE_1).build()
diff --git a/services/tests/powerstatstests/Android.bp b/services/tests/powerstatstests/Android.bp
index 07197b1..05e0e8f 100644
--- a/services/tests/powerstatstests/Android.bp
+++ b/services/tests/powerstatstests/Android.bp
@@ -3,6 +3,20 @@
     default_applicable_licenses: ["frameworks_base_license"],
 }
 
+filegroup {
+    name: "power_stats_ravenwood_tests",
+    srcs: [
+        "src/com/android/server/power/stats/AggregatedPowerStatsProcessorTest.java",
+        "src/com/android/server/power/stats/AggregatedPowerStatsTest.java",
+        "src/com/android/server/power/stats/MultiStateStatsTest.java",
+        "src/com/android/server/power/stats/PowerStatsAggregatorTest.java",
+        "src/com/android/server/power/stats/PowerStatsCollectorTest.java",
+        "src/com/android/server/power/stats/PowerStatsSchedulerTest.java",
+        "src/com/android/server/power/stats/PowerStatsStoreTest.java",
+        "src/com/android/server/power/stats/PowerStatsUidResolverTest.java",
+    ],
+}
+
 android_test {
     name: "PowerStatsTests",
 
@@ -12,8 +26,7 @@
     ],
 
     exclude_srcs: [
-        "src/com/android/server/power/stats/MultiStateStatsTest.java",
-        "src/com/android/server/power/stats/PowerStatsStoreTest.java",
+        ":power_stats_ravenwood_tests",
     ],
 
     static_libs: [
@@ -65,10 +78,12 @@
         "modules-utils-binary-xml",
         "androidx.annotation_annotation",
         "androidx.test.rules",
+        "truth",
+        "mockito_ravenwood",
     ],
     srcs: [
-        "src/com/android/server/power/stats/MultiStateStatsTest.java",
-        "src/com/android/server/power/stats/PowerStatsStoreTest.java",
+        ":power_stats_ravenwood_tests",
+        "src/com/android/server/power/stats/MockClock.java",
     ],
     auto_gen_config: true,
 }
diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/AggregatePowerStatsProcessorTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/AggregatedPowerStatsProcessorTest.java
similarity index 99%
rename from services/tests/powerstatstests/src/com/android/server/power/stats/AggregatePowerStatsProcessorTest.java
rename to services/tests/powerstatstests/src/com/android/server/power/stats/AggregatedPowerStatsProcessorTest.java
index 6d61dc8..af83be0 100644
--- a/services/tests/powerstatstests/src/com/android/server/power/stats/AggregatePowerStatsProcessorTest.java
+++ b/services/tests/powerstatstests/src/com/android/server/power/stats/AggregatedPowerStatsProcessorTest.java
@@ -35,7 +35,7 @@
 
 @RunWith(AndroidJUnit4.class)
 @SmallTest
-public class AggregatePowerStatsProcessorTest {
+public class AggregatedPowerStatsProcessorTest {
 
     @Test
     public void createPowerEstimationPlan_allDeviceStatesPresentInUidStates() {
diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/MultiStateStatsTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/MultiStateStatsTest.java
index e8f46b3..1b045c5 100644
--- a/services/tests/powerstatstests/src/com/android/server/power/stats/MultiStateStatsTest.java
+++ b/services/tests/powerstatstests/src/com/android/server/power/stats/MultiStateStatsTest.java
@@ -22,12 +22,10 @@
 import static org.junit.Assert.assertThrows;
 
 import android.os.BatteryConsumer;
-import android.platform.test.ravenwood.RavenwoodRule;
 
 import androidx.test.filters.SmallTest;
 import androidx.test.runner.AndroidJUnit4;
 
-import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
@@ -38,11 +36,6 @@
 @RunWith(AndroidJUnit4.class)
 @SmallTest
 public class MultiStateStatsTest {
-
-    @Rule
-    public final RavenwoodRule mRavenwood = new RavenwoodRule.Builder()
-            .build();
-
     public static final int DIMENSION_COUNT = 2;
 
     @Test
diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/PowerStatsAggregatorTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/PowerStatsAggregatorTest.java
index 6704987..2456636 100644
--- a/services/tests/powerstatstests/src/com/android/server/power/stats/PowerStatsAggregatorTest.java
+++ b/services/tests/powerstatstests/src/com/android/server/power/stats/PowerStatsAggregatorTest.java
@@ -24,7 +24,6 @@
 import android.os.BatteryConsumer;
 import android.os.BatteryStats;
 import android.os.PersistableBundle;
-import android.text.format.DateFormat;
 
 import androidx.annotation.NonNull;
 import androidx.test.filters.SmallTest;
@@ -39,7 +38,8 @@
 import org.junit.runner.RunWith;
 
 import java.text.ParseException;
-import java.util.Calendar;
+import java.text.SimpleDateFormat;
+import java.util.Date;
 import java.util.List;
 import java.util.TimeZone;
 
@@ -60,7 +60,7 @@
     public void setup() throws ParseException {
         mHistory = new BatteryStatsHistory(32, 1024,
                 mock(BatteryStatsHistory.HistoryStepDetailsCalculator.class), mClock,
-                mMonotonicClock);
+                mMonotonicClock, mock(BatteryStatsHistory.TraceDelegate.class));
 
         AggregatedPowerStatsConfig config = new AggregatedPowerStatsConfig();
         config.trackPowerComponent(TEST_POWER_COMPONENT)
@@ -179,9 +179,9 @@
 
     @NonNull
     private static CharSequence formatDateTime(long timeInMillis) {
-        Calendar cal = Calendar.getInstance(TimeZone.getTimeZone("GMT"));
-        cal.setTimeInMillis(timeInMillis);
-        return DateFormat.format("yyyy-MM-dd hh:mm:ss", cal);
+        SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss");
+        format.getCalendar().setTimeZone(TimeZone.getTimeZone("GMT"));
+        return format.format(new Date(timeInMillis));
     }
 
     @Test
diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/PowerStatsCollectorTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/PowerStatsCollectorTest.java
index 330f698..17a7d3e 100644
--- a/services/tests/powerstatstests/src/com/android/server/power/stats/PowerStatsCollectorTest.java
+++ b/services/tests/powerstatstests/src/com/android/server/power/stats/PowerStatsCollectorTest.java
@@ -22,6 +22,7 @@
 import android.os.Handler;
 import android.os.HandlerThread;
 import android.os.PersistableBundle;
+import android.platform.test.ravenwood.RavenwoodRule;
 
 import androidx.test.filters.SmallTest;
 import androidx.test.runner.AndroidJUnit4;
@@ -29,12 +30,18 @@
 import com.android.internal.os.PowerStats;
 
 import org.junit.Before;
+import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
 @RunWith(AndroidJUnit4.class)
 @SmallTest
 public class PowerStatsCollectorTest {
+    @Rule
+    public final RavenwoodRule mRavenwood = new RavenwoodRule.Builder()
+            .setProvideMainThread(true)
+            .build();
+
     private final MockClock mMockClock = new MockClock();
     private final HandlerThread mHandlerThread = new HandlerThread("test");
     private Handler mHandler;
diff --git a/services/tests/powerstatstests/src/com/android/server/power/stats/PowerStatsSchedulerTest.java b/services/tests/powerstatstests/src/com/android/server/power/stats/PowerStatsSchedulerTest.java
index 7257a94..beec661 100644
--- a/services/tests/powerstatstests/src/com/android/server/power/stats/PowerStatsSchedulerTest.java
+++ b/services/tests/powerstatstests/src/com/android/server/power/stats/PowerStatsSchedulerTest.java
@@ -24,26 +24,26 @@
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.when;
 
-import android.content.Context;
 import android.os.ConditionVariable;
 import android.os.Handler;
 import android.os.HandlerThread;
+import android.platform.test.ravenwood.RavenwoodRule;
 
-import androidx.test.InstrumentationRegistry;
 import androidx.test.runner.AndroidJUnit4;
 
 import com.android.internal.os.MonotonicClock;
-import com.android.internal.os.PowerProfile;
 
 import org.junit.Before;
+import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
-import java.io.File;
+import java.io.IOException;
+import java.nio.file.Files;
 import java.time.Duration;
 import java.time.Instant;
+import java.util.ArrayList;
 import java.util.List;
 import java.util.TimeZone;
 import java.util.concurrent.TimeUnit;
@@ -51,39 +51,46 @@
 
 @RunWith(AndroidJUnit4.class)
 public class PowerStatsSchedulerTest {
+    @Rule
+    public final RavenwoodRule mRavenwood = new RavenwoodRule.Builder()
+            .setProvideMainThread(true)
+            .build();
+
     private PowerStatsStore mPowerStatsStore;
     private Handler mHandler;
     private MockClock mClock = new MockClock();
     private MonotonicClock mMonotonicClock = new MonotonicClock(0, mClock);
-    private MockBatteryStatsImpl mBatteryStats;
     private PowerStatsScheduler mPowerStatsScheduler;
-    private PowerProfile mPowerProfile;
     private PowerStatsAggregator mPowerStatsAggregator;
     private AggregatedPowerStatsConfig mAggregatedPowerStatsConfig;
+    private List<Long> mScheduledAlarms = new ArrayList<>();
+    private boolean mPowerStatsCollectionOccurred;
+
+    private static final int START_REALTIME = 7654321;
 
     @Before
     @SuppressWarnings("GuardedBy")
-    public void setup() {
-        final Context context = InstrumentationRegistry.getContext();
-
+    public void setup() throws IOException {
         TimeZone.setDefault(TimeZone.getTimeZone("UTC"));
 
         mClock.currentTime = Instant.parse("2023-01-02T03:04:05.00Z").toEpochMilli();
-        mClock.realtime = 7654321;
+        mClock.realtime = START_REALTIME;
 
         HandlerThread bgThread = new HandlerThread("bg thread");
         bgThread.start();
-        File systemDir = context.getCacheDir();
         mHandler = new Handler(bgThread.getLooper());
         mAggregatedPowerStatsConfig = new AggregatedPowerStatsConfig();
-        mPowerStatsStore = new PowerStatsStore(systemDir, mHandler, mAggregatedPowerStatsConfig);
-        mPowerProfile = mock(PowerProfile.class);
-        when(mPowerProfile.getAveragePower(PowerProfile.POWER_FLASHLIGHT)).thenReturn(1000000.0);
-        mBatteryStats = new MockBatteryStatsImpl(mClock).setPowerProfile(mPowerProfile);
+        mPowerStatsStore = new PowerStatsStore(
+                Files.createTempDirectory("PowerStatsSchedulerTest").toFile(),
+                mHandler, mAggregatedPowerStatsConfig);
         mPowerStatsAggregator = mock(PowerStatsAggregator.class);
-        mPowerStatsScheduler = new PowerStatsScheduler(context, mPowerStatsAggregator,
-                TimeUnit.MINUTES.toMillis(30), TimeUnit.HOURS.toMillis(1), mPowerStatsStore, mClock,
-                mMonotonicClock, mHandler, mBatteryStats);
+        mPowerStatsScheduler = new PowerStatsScheduler(
+                () -> mPowerStatsCollectionOccurred = true,
+                mPowerStatsAggregator, TimeUnit.MINUTES.toMillis(30), TimeUnit.HOURS.toMillis(1),
+                mPowerStatsStore,
+                ((triggerAtMillis, tag, onAlarmListener, handler) ->
+                        mScheduledAlarms.add(triggerAtMillis)),
+                mClock, mMonotonicClock, () -> 12345L, mHandler);
     }
 
     @Test
@@ -113,7 +120,7 @@
             long endTimeWallClock =
                     mClock.currentTime - (mMonotonicClock.monotonicTime() - endTime);
 
-            assertThat(startTime).isEqualTo(7654321 + 123);
+            assertThat(startTime).isEqualTo(START_REALTIME + 123);
             assertThat(endTime - startTime).isAtLeast(TimeUnit.MINUTES.toMillis(30));
             assertThat(Instant.ofEpochMilli(endTimeWallClock))
                     .isEqualTo(Instant.parse("2023-01-02T04:00:00Z"));
@@ -142,11 +149,15 @@
         }).when(mPowerStatsAggregator).aggregatePowerStats(anyLong(), anyLong(),
                 any(Consumer.class));
 
-        mPowerStatsScheduler.schedulePowerStatsAggregation();
+        mPowerStatsScheduler.start(/*enabled*/ true);
         ConditionVariable done = new ConditionVariable();
         mHandler.post(done::open);
         done.block();
 
+        assertThat(mPowerStatsCollectionOccurred).isTrue();
+        assertThat(mScheduledAlarms).containsExactly(
+                START_REALTIME + TimeUnit.MINUTES.toMillis(90) + TimeUnit.HOURS.toMillis(1));
+
         verify(mPowerStatsAggregator, times(2))
                 .aggregatePowerStats(anyLong(), anyLong(), any(Consumer.class));
 
@@ -155,7 +166,7 @@
         // Skip the first entry, which was placed in the store at the beginning of this test
         PowerStatsSpan.TimeFrame timeFrame1 = contents.get(1).getTimeFrames().get(0);
         PowerStatsSpan.TimeFrame timeFrame2 = contents.get(2).getTimeFrames().get(0);
-        assertThat(timeFrame1.startMonotonicTime).isEqualTo(7654321 + 123);
+        assertThat(timeFrame1.startMonotonicTime).isEqualTo(START_REALTIME + 123);
         assertThat(timeFrame2.startMonotonicTime)
                 .isEqualTo(timeFrame1.startMonotonicTime + timeFrame1.duration);
         assertThat(Instant.ofEpochMilli(timeFrame2.startTime))
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/AuthServiceTest.java b/services/tests/servicestests/src/com/android/server/biometrics/AuthServiceTest.java
index a78f2dc..3b5cae3 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/AuthServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/AuthServiceTest.java
@@ -23,6 +23,8 @@
 import static android.hardware.biometrics.BiometricConstants.BIOMETRIC_ERROR_CANCELED;
 import static android.hardware.biometrics.BiometricConstants.BIOMETRIC_SUCCESS;
 
+import static com.android.systemui.shared.Flags.FLAG_SIDEFPS_CONTROLLER_REFACTOR;
+
 import static junit.framework.Assert.assertEquals;
 
 import static org.junit.Assert.assertThrows;
@@ -41,6 +43,7 @@
 import android.content.Context;
 import android.content.pm.PackageManager;
 import android.content.res.Resources;
+import android.hardware.biometrics.AuthenticationStateListener;
 import android.hardware.biometrics.BiometricManager;
 import android.hardware.biometrics.Flags;
 import android.hardware.biometrics.IBiometricEnabledOnKeyguardCallback;
@@ -108,6 +111,7 @@
     @Mock
     IFaceService mFaceService;
     @Mock
+
     AppOpsManager mAppOpsManager;
     @Mock
     private VirtualDeviceManagerInternal mVdmInternal;
@@ -404,6 +408,23 @@
                 eq(TEST_OP_PACKAGE_NAME));
     }
 
+    @Test
+    public void testRegisterAuthenticationStateListener_callsFingerprintService()
+            throws Exception {
+        mSetFlagsRule.enableFlags(FLAG_SIDEFPS_CONTROLLER_REFACTOR);
+        setInternalAndTestBiometricPermissions(mContext, true /* hasPermission */);
+
+        mAuthService = new AuthService(mContext, mInjector);
+        mAuthService.onStart();
+
+        final AuthenticationStateListener listener = mock(AuthenticationStateListener.class);
+
+        mAuthService.mImpl.registerAuthenticationStateListener(listener);
+
+        waitForIdle();
+        verify(mFingerprintService).registerAuthenticationStateListener(
+                eq(listener));
+    }
 
     @Test
     public void testRegisterKeyguardCallback_callsBiometricServiceRegisterKeyguardCallback()
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/SensorOverlaysTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/SensorOverlaysTest.java
index 5012335..94cb860 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/SensorOverlaysTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/SensorOverlaysTest.java
@@ -16,6 +16,8 @@
 
 package com.android.server.biometrics.sensors;
 
+import static com.android.systemui.shared.Flags.FLAG_SIDEFPS_CONTROLLER_REFACTOR;
+
 import static com.google.common.truth.Truth.assertThat;
 
 import static org.mockito.ArgumentMatchers.any;
@@ -23,10 +25,11 @@
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
-import android.hardware.biometrics.BiometricOverlayConstants;
+import android.hardware.biometrics.BiometricRequestConstants;
 import android.hardware.fingerprint.ISidefpsController;
 import android.hardware.fingerprint.IUdfpsOverlayController;
 import android.platform.test.annotations.Presubmit;
+import android.platform.test.annotations.RequiresFlagsDisabled;
 
 import androidx.test.filters.SmallTest;
 
@@ -40,6 +43,7 @@
 import java.util.ArrayList;
 import java.util.List;
 
+@RequiresFlagsDisabled(FLAG_SIDEFPS_CONTROLLER_REFACTOR)
 @Presubmit
 @SmallTest
 public class SensorOverlaysTest {
@@ -97,7 +101,7 @@
     private void testShow(IUdfpsOverlayController udfps, ISidefpsController sidefps)
             throws Exception {
         final SensorOverlays sensorOverlays = new SensorOverlays(udfps, sidefps);
-        final int reason = BiometricOverlayConstants.REASON_UNKNOWN;
+        final int reason = BiometricRequestConstants.REASON_UNKNOWN;
         sensorOverlays.show(SENSOR_ID, reason, mAcquisitionClient);
 
         if (udfps != null) {
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClientTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClientTest.java
index 79a528c..c24227f 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClientTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClientTest.java
@@ -18,6 +18,8 @@
 
 import static android.hardware.biometrics.BiometricConstants.BIOMETRIC_ERROR_CANCELED;
 
+import static com.android.systemui.shared.Flags.FLAG_SIDEFPS_CONTROLLER_REFACTOR;
+
 import static com.google.common.truth.Truth.assertThat;
 
 import static org.mockito.ArgumentMatchers.anyBoolean;
@@ -56,6 +58,7 @@
 import android.platform.test.annotations.RequiresFlagsEnabled;
 import android.platform.test.flag.junit.CheckFlagsRule;
 import android.platform.test.flag.junit.DeviceFlagsValueProvider;
+import android.platform.test.flag.junit.SetFlagsRule;
 import android.testing.TestableContext;
 
 import androidx.test.filters.SmallTest;
@@ -68,6 +71,7 @@
 import com.android.server.biometrics.log.OperationContextExt;
 import com.android.server.biometrics.log.Probe;
 import com.android.server.biometrics.sensors.AuthSessionCoordinator;
+import com.android.server.biometrics.sensors.AuthenticationStateListeners;
 import com.android.server.biometrics.sensors.ClientMonitorCallback;
 import com.android.server.biometrics.sensors.ClientMonitorCallbackConverter;
 import com.android.server.biometrics.sensors.LockoutTracker;
@@ -91,6 +95,8 @@
 @SmallTest
 public class FingerprintAuthenticationClientTest {
 
+    @Rule public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
+
     private static final int SENSOR_ID = 4;
     private static final int USER_ID = 8;
     private static final long OP_ID = 7;
@@ -128,6 +134,8 @@
     @Mock
     private ISidefpsController mSideFpsController;
     @Mock
+    private AuthenticationStateListeners mAuthenticationStateListeners;
+    @Mock
     private FingerprintSensorPropertiesInternal mSensorProps;
     @Mock
     private ClientMonitorCallback mCallback;
@@ -384,6 +392,7 @@
 
     private void showHideOverlay(Consumer<FingerprintAuthenticationClient> block)
             throws RemoteException {
+        mSetFlagsRule.disableFlags(FLAG_SIDEFPS_CONTROLLER_REFACTOR);
         final FingerprintAuthenticationClient client = createClient();
 
         client.start(mCallback);
@@ -398,6 +407,49 @@
     }
 
     @Test
+    public void showHideOverlay_cancel_sidefpsControllerRemovalRefactor() throws RemoteException {
+        showHideOverlay_sidefpsControllerRemovalRefactor(c -> c.cancel());
+    }
+
+    @Test
+    public void showHideOverlay_stop_sidefpsControllerRemovalRefactor() throws RemoteException {
+        showHideOverlay_sidefpsControllerRemovalRefactor(c -> c.stopHalOperation());
+    }
+
+    @Test
+    public void showHideOverlay_error_sidefpsControllerRemovalRefactor() throws RemoteException {
+        showHideOverlay_sidefpsControllerRemovalRefactor(c -> c.onError(0, 0));
+        verify(mCallback).onClientFinished(any(), eq(false));
+    }
+
+    @Test
+    public void showHideOverlay_lockout_sidefpsControllerRemovalRefactor() throws RemoteException {
+        showHideOverlay_sidefpsControllerRemovalRefactor(c -> c.onLockoutTimed(5000));
+    }
+
+    @Test
+    public void showHideOverlay_lockoutPerm_sidefpsControllerRemovalRefactor()
+            throws RemoteException {
+        showHideOverlay_sidefpsControllerRemovalRefactor(c -> c.onLockoutPermanent());
+    }
+
+    private void showHideOverlay_sidefpsControllerRemovalRefactor(
+            Consumer<FingerprintAuthenticationClient> block) throws RemoteException {
+        mSetFlagsRule.enableFlags(FLAG_SIDEFPS_CONTROLLER_REFACTOR);
+        final FingerprintAuthenticationClient client = createClient();
+
+        client.start(mCallback);
+
+        verify(mUdfpsOverlayController).showUdfpsOverlay(eq(REQUEST_ID), anyInt(), anyInt(), any());
+        verify(mAuthenticationStateListeners).onAuthenticationStarted(anyInt());
+
+        block.accept(client);
+
+        verify(mUdfpsOverlayController).hideUdfpsOverlay(anyInt());
+        verify(mAuthenticationStateListeners).onAuthenticationStopped();
+    }
+
+    @Test
     public void cancelsAuthWhenNotInForeground() throws Exception {
         final ActivityManager.RunningTaskInfo topTask = new ActivityManager.RunningTaskInfo();
         topTask.topActivity = new ComponentName("other", "thing");
@@ -502,7 +554,8 @@
                 mBiometricLogger, mBiometricContext,
                 true /* isStrongBiometric */,
                 null /* taskStackListener */,
-                mUdfpsOverlayController, mSideFpsController, allowBackgroundAuthentication,
+                mUdfpsOverlayController, mSideFpsController, mAuthenticationStateListeners,
+                allowBackgroundAuthentication,
                 mSensorProps,
                 new Handler(mLooper.getLooper()), 0 /* biometricStrength */, mClock,
                 lockoutTracker) {
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintEnrollClientTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintEnrollClientTest.java
index c7eb1db..e7d4a2e 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintEnrollClientTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintEnrollClientTest.java
@@ -16,6 +16,8 @@
 
 package com.android.server.biometrics.sensors.fingerprint.aidl;
 
+import static com.android.systemui.shared.Flags.FLAG_SIDEFPS_CONTROLLER_REFACTOR;
+
 import static org.mockito.ArgumentMatchers.anyBoolean;
 import static org.mockito.ArgumentMatchers.anyFloat;
 import static org.mockito.ArgumentMatchers.anyInt;
@@ -38,6 +40,7 @@
 import android.os.IBinder;
 import android.os.RemoteException;
 import android.platform.test.annotations.Presubmit;
+import android.platform.test.flag.junit.SetFlagsRule;
 import android.testing.TestableContext;
 
 import androidx.test.filters.SmallTest;
@@ -48,6 +51,7 @@
 import com.android.server.biometrics.log.CallbackWithProbe;
 import com.android.server.biometrics.log.OperationContextExt;
 import com.android.server.biometrics.log.Probe;
+import com.android.server.biometrics.sensors.AuthenticationStateListeners;
 import com.android.server.biometrics.sensors.BiometricUtils;
 import com.android.server.biometrics.sensors.ClientMonitorCallback;
 import com.android.server.biometrics.sensors.ClientMonitorCallbackConverter;
@@ -68,6 +72,8 @@
 @SmallTest
 public class FingerprintEnrollClientTest {
 
+    @Rule public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
+
     private static final byte[] HAT = new byte[69];
     private static final int USER_ID = 8;
     private static final long REQUEST_ID = 9;
@@ -98,6 +104,8 @@
     @Mock
     private ISidefpsController mSideFpsController;
     @Mock
+    private AuthenticationStateListeners mAuthenticationStateListeners;
+    @Mock
     private FingerprintSensorPropertiesInternal mSensorProps;
     @Mock
     private ClientMonitorCallback mCallback;
@@ -271,6 +279,7 @@
 
     private void showHideOverlay(Consumer<FingerprintEnrollClient> block)
             throws RemoteException {
+        mSetFlagsRule.disableFlags(FLAG_SIDEFPS_CONTROLLER_REFACTOR);
         final FingerprintEnrollClient client = createClient();
 
         client.start(mCallback);
@@ -284,6 +293,44 @@
         verify(mSideFpsController).hide(anyInt());
     }
 
+    @Test
+    public void showHideOverlay_cancel_sidefpsControllerRemovalRefactor() throws RemoteException {
+        showHideOverlay_sidefpsControllerRemovalRefactor(c -> c.cancel());
+    }
+
+    @Test
+    public void showHideOverlay_stop_sidefpsControllerRemovalRefactor() throws RemoteException {
+        showHideOverlay_sidefpsControllerRemovalRefactor(c -> c.stopHalOperation());
+    }
+
+    @Test
+    public void showHideOverlay_error_sidefpsControllerRemovalRefactor() throws RemoteException {
+        showHideOverlay_sidefpsControllerRemovalRefactor(c -> c.onError(0, 0));
+        verify(mCallback).onClientFinished(any(), eq(false));
+    }
+
+    @Test
+    public void showHideOverlay_result_sidefpsControllerRemovalRefactor() throws RemoteException {
+        showHideOverlay_sidefpsControllerRemovalRefactor(
+                c -> c.onEnrollResult(new Fingerprint("", 1, 1), 0));
+    }
+
+    private void showHideOverlay_sidefpsControllerRemovalRefactor(
+            Consumer<FingerprintEnrollClient> block) throws RemoteException {
+        mSetFlagsRule.enableFlags(FLAG_SIDEFPS_CONTROLLER_REFACTOR);
+        final FingerprintEnrollClient client = createClient();
+
+        client.start(mCallback);
+
+        verify(mUdfpsOverlayController).showUdfpsOverlay(eq(REQUEST_ID), anyInt(), anyInt(), any());
+        verify(mAuthenticationStateListeners).onAuthenticationStarted(anyInt());
+
+        block.accept(client);
+
+        verify(mUdfpsOverlayController).hideUdfpsOverlay(anyInt());
+        verify(mAuthenticationStateListeners).onAuthenticationStopped();
+    }
+
     private FingerprintEnrollClient createClient() throws RemoteException {
         return createClient(500);
     }
@@ -296,6 +343,7 @@
         mClientMonitorCallbackConverter, 0 /* userId */,
         HAT, "owner", mBiometricUtils, 8 /* sensorId */,
         mBiometricLogger, mBiometricContext, mSensorProps, mUdfpsOverlayController,
-        mSideFpsController, 6 /* maxTemplatesPerUser */, FingerprintManager.ENROLL_ENROLL);
+        mSideFpsController, mAuthenticationStateListeners, 6 /* maxTemplatesPerUser */,
+        FingerprintManager.ENROLL_ENROLL);
     }
 }
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProviderTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProviderTest.java
index 8f6efff..4cfb83f 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProviderTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintProviderTest.java
@@ -43,6 +43,7 @@
 import androidx.test.filters.SmallTest;
 
 import com.android.server.biometrics.log.BiometricContext;
+import com.android.server.biometrics.sensors.AuthenticationStateListeners;
 import com.android.server.biometrics.sensors.BaseClientMonitor;
 import com.android.server.biometrics.sensors.BiometricScheduler;
 import com.android.server.biometrics.sensors.BiometricStateCallback;
@@ -74,6 +75,8 @@
     @Mock
     private GestureAvailabilityDispatcher mGestureAvailabilityDispatcher;
     @Mock
+    private AuthenticationStateListeners mAuthenticationStateListeners;
+    @Mock
     private BiometricStateCallback mBiometricStateCallback;
     @Mock
     private BiometricContext mBiometricContext;
@@ -110,8 +113,9 @@
         mLockoutResetDispatcher = new LockoutResetDispatcher(mContext);
 
         mFingerprintProvider = new FingerprintProvider(mContext,
-                mBiometricStateCallback, mSensorProps, TAG, mLockoutResetDispatcher,
-                mGestureAvailabilityDispatcher, mBiometricContext, mDaemon);
+                mBiometricStateCallback, mAuthenticationStateListeners, mSensorProps, TAG,
+                mLockoutResetDispatcher, mGestureAvailabilityDispatcher, mBiometricContext,
+                mDaemon);
     }
 
     @Test
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21Test.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21Test.java
index b32b89a..0d3f192 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21Test.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/hidl/Fingerprint21Test.java
@@ -40,6 +40,7 @@
 
 import com.android.internal.R;
 import com.android.server.biometrics.log.BiometricContext;
+import com.android.server.biometrics.sensors.AuthenticationStateListeners;
 import com.android.server.biometrics.sensors.BiometricScheduler;
 import com.android.server.biometrics.sensors.BiometricStateCallback;
 import com.android.server.biometrics.sensors.LockoutResetDispatcher;
@@ -70,6 +71,8 @@
     @Mock
     private BiometricScheduler mScheduler;
     @Mock
+    private AuthenticationStateListeners mAuthenticationStateListeners;
+    @Mock
     private BiometricStateCallback mBiometricStateCallback;
     @Mock
     private BiometricContext mBiometricContext;
@@ -102,9 +105,10 @@
                         componentInfo, FingerprintSensorProperties.TYPE_UNKNOWN,
                         resetLockoutRequiresHardwareAuthToken);
 
-        mFingerprint21 = new TestableFingerprint21(mContext, mBiometricStateCallback, sensorProps,
-                mScheduler, new Handler(Looper.getMainLooper()), mLockoutResetDispatcher,
-                mHalResultController, mBiometricContext);
+        mFingerprint21 = new TestableFingerprint21(mContext, mBiometricStateCallback,
+                mAuthenticationStateListeners, sensorProps, mScheduler,
+                new Handler(Looper.getMainLooper()), mLockoutResetDispatcher, mHalResultController,
+                mBiometricContext);
     }
 
     @Test
@@ -126,13 +130,14 @@
 
         TestableFingerprint21(@NonNull Context context,
                 @NonNull BiometricStateCallback biometricStateCallback,
+                @NonNull AuthenticationStateListeners authenticationStateListeners,
                 @NonNull FingerprintSensorPropertiesInternal sensorProps,
                 @NonNull BiometricScheduler scheduler, @NonNull Handler handler,
                 @NonNull LockoutResetDispatcher lockoutResetDispatcher,
                 @NonNull HalResultController controller,
                 @NonNull BiometricContext biometricContext) {
-            super(context, biometricStateCallback, sensorProps, scheduler, handler,
-                    lockoutResetDispatcher, controller, biometricContext);
+            super(context, biometricStateCallback, authenticationStateListeners, sensorProps,
+                    scheduler, handler, lockoutResetDispatcher, controller, biometricContext);
         }
 
         @Override
diff --git a/services/tests/servicestests/src/com/android/server/devicestate/OverrideRequestControllerTest.java b/services/tests/servicestests/src/com/android/server/devicestate/OverrideRequestControllerTest.java
index fe37f42..b3d25f2 100644
--- a/services/tests/servicestests/src/com/android/server/devicestate/OverrideRequestControllerTest.java
+++ b/services/tests/servicestests/src/com/android/server/devicestate/OverrideRequestControllerTest.java
@@ -47,6 +47,10 @@
 @Presubmit
 @RunWith(AndroidJUnit4.class)
 public final class OverrideRequestControllerTest {
+
+    private static final DeviceState TEST_DEVICE_STATE_ZERO = new DeviceState(0, "TEST_STATE", 0);
+    private static final DeviceState TEST_DEVICE_STATE_ONE = new DeviceState(1, "TEST_STATE", 0);
+
     private TestStatusChangeListener mStatusListener;
     private OverrideRequestController mController;
 
@@ -59,7 +63,7 @@
     @Test
     public void addRequest() {
         OverrideRequest request = new OverrideRequest(new Binder(), 0 /* pid */, 0 /* uid */,
-                0 /* requestedState */, 0 /* flags */, OVERRIDE_REQUEST_TYPE_EMULATED_STATE);
+                TEST_DEVICE_STATE_ZERO, 0 /* flags */, OVERRIDE_REQUEST_TYPE_EMULATED_STATE);
         assertNull(mStatusListener.getLastStatus(request));
 
         mController.addRequest(request);
@@ -69,14 +73,14 @@
     @Test
     public void addRequest_cancelExistingRequestThroughNewRequest() {
         OverrideRequest firstRequest = new OverrideRequest(new Binder(), 0 /* pid */, 0 /* uid */,
-                0 /* requestedState */, 0 /* flags */, OVERRIDE_REQUEST_TYPE_EMULATED_STATE);
+                TEST_DEVICE_STATE_ZERO, 0 /* flags */, OVERRIDE_REQUEST_TYPE_EMULATED_STATE);
         assertNull(mStatusListener.getLastStatus(firstRequest));
 
         mController.addRequest(firstRequest);
         assertEquals(mStatusListener.getLastStatus(firstRequest).intValue(), STATUS_ACTIVE);
 
         OverrideRequest secondRequest = new OverrideRequest(new Binder(), 0 /* pid */, 0 /* uid */,
-                1 /* requestedState */, 0 /* flags */, OVERRIDE_REQUEST_TYPE_EMULATED_STATE);
+                TEST_DEVICE_STATE_ONE, 0 /* flags */, OVERRIDE_REQUEST_TYPE_EMULATED_STATE);
         assertNull(mStatusListener.getLastStatus(secondRequest));
 
         mController.addRequest(secondRequest);
@@ -87,7 +91,7 @@
     @Test
     public void addRequest_cancelActiveRequest() {
         OverrideRequest firstRequest = new OverrideRequest(new Binder(), 0 /* pid */, 0 /* uid */,
-                0 /* requestedState */, 0 /* flags */, OVERRIDE_REQUEST_TYPE_EMULATED_STATE);
+                TEST_DEVICE_STATE_ZERO, 0 /* flags */, OVERRIDE_REQUEST_TYPE_EMULATED_STATE);
 
         mController.addRequest(firstRequest);
 
@@ -101,7 +105,7 @@
     @Test
     public void addBaseStateRequest() {
         OverrideRequest request = new OverrideRequest(new Binder(), 0 /* pid */, 0 /* uid */,
-                0 /* requestedState */, 0 /* flags */, OVERRIDE_REQUEST_TYPE_BASE_STATE);
+                TEST_DEVICE_STATE_ZERO, 0 /* flags */, OVERRIDE_REQUEST_TYPE_BASE_STATE);
         assertNull(mStatusListener.getLastStatus(request));
 
         mController.addBaseStateRequest(request);
@@ -111,14 +115,14 @@
     @Test
     public void addBaseStateRequest_cancelExistingBaseStateRequestThroughNewRequest() {
         OverrideRequest firstRequest = new OverrideRequest(new Binder(), 0 /* pid */, 0 /* uid */,
-                0 /* requestedState */, 0 /* flags */, OVERRIDE_REQUEST_TYPE_BASE_STATE);
+                TEST_DEVICE_STATE_ZERO, 0 /* flags */, OVERRIDE_REQUEST_TYPE_BASE_STATE);
         assertNull(mStatusListener.getLastStatus(firstRequest));
 
         mController.addBaseStateRequest(firstRequest);
         assertEquals(mStatusListener.getLastStatus(firstRequest).intValue(), STATUS_ACTIVE);
 
         OverrideRequest secondRequest = new OverrideRequest(new Binder(), 0 /* pid */, 0 /* uid */,
-                1 /* requestedState */, 0 /* flags */, OVERRIDE_REQUEST_TYPE_BASE_STATE);
+                TEST_DEVICE_STATE_ONE, 0 /* flags */, OVERRIDE_REQUEST_TYPE_BASE_STATE);
         assertNull(mStatusListener.getLastStatus(secondRequest));
 
         mController.addBaseStateRequest(secondRequest);
@@ -129,7 +133,7 @@
     @Test
     public void addBaseStateRequest_cancelActiveBaseStateRequest() {
         OverrideRequest firstRequest = new OverrideRequest(new Binder(), 0 /* pid */, 0 /* uid */,
-                0 /* requestedState */, 0 /* flags */, OVERRIDE_REQUEST_TYPE_BASE_STATE);
+                TEST_DEVICE_STATE_ZERO, 0 /* flags */, OVERRIDE_REQUEST_TYPE_BASE_STATE);
 
         mController.addBaseStateRequest(firstRequest);
 
@@ -143,13 +147,13 @@
     @Test
     public void handleBaseStateChanged() {
         OverrideRequest firstRequest = new OverrideRequest(new Binder(), 0 /* pid */, 0 /* uid */,
-                0 /* requestedState */,
+                TEST_DEVICE_STATE_ZERO,
                 DeviceStateRequest.FLAG_CANCEL_WHEN_BASE_CHANGES /* flags */,
                 OVERRIDE_REQUEST_TYPE_EMULATED_STATE);
 
         OverrideRequest baseStateRequest = new OverrideRequest(new Binder(), 0 /* pid */,
                 0 /* uid */,
-                0 /* requestedState */,
+                TEST_DEVICE_STATE_ZERO,
                 0 /* flags */, OVERRIDE_REQUEST_TYPE_BASE_STATE);
 
         mController.addRequest(firstRequest);
@@ -169,11 +173,11 @@
     @Test
     public void handleProcessDied() {
         OverrideRequest firstRequest = new OverrideRequest(new Binder(), 0 /* pid */, 0 /* uid */,
-                0 /* requestedState */, 0 /* flags */, OVERRIDE_REQUEST_TYPE_EMULATED_STATE);
+                TEST_DEVICE_STATE_ZERO, 0 /* flags */, OVERRIDE_REQUEST_TYPE_EMULATED_STATE);
 
         OverrideRequest baseStateRequest = new OverrideRequest(new Binder(), 0 /* pid */,
                 0 /* uid */,
-                1 /* requestedState */,
+                TEST_DEVICE_STATE_ONE,
                 0 /* flags */, OVERRIDE_REQUEST_TYPE_BASE_STATE);
 
         mController.addRequest(firstRequest);
@@ -192,11 +196,11 @@
         mController.setStickyRequestsAllowed(true);
 
         OverrideRequest firstRequest = new OverrideRequest(new Binder(), 0 /* pid */, 0 /* uid */,
-                0 /* requestedState */, 0 /* flags */, OVERRIDE_REQUEST_TYPE_EMULATED_STATE);
+                TEST_DEVICE_STATE_ZERO, 0 /* flags */, OVERRIDE_REQUEST_TYPE_EMULATED_STATE);
 
         OverrideRequest baseStateRequest = new OverrideRequest(new Binder(), 0 /* pid */,
                 0 /* uid */,
-                1 /* requestedState */, 0 /* flags */, OVERRIDE_REQUEST_TYPE_BASE_STATE);
+                TEST_DEVICE_STATE_ONE, 0 /* flags */, OVERRIDE_REQUEST_TYPE_BASE_STATE);
 
         mController.addRequest(firstRequest);
         assertEquals(mStatusListener.getLastStatus(firstRequest).intValue(), STATUS_ACTIVE);
@@ -215,11 +219,11 @@
     @Test
     public void handleNewSupportedStates() {
         OverrideRequest firstRequest = new OverrideRequest(new Binder(), 0 /* pid */, 0 /* uid */,
-                1 /* requestedState */, 0 /* flags */, OVERRIDE_REQUEST_TYPE_EMULATED_STATE);
+                TEST_DEVICE_STATE_ONE, 0 /* flags */, OVERRIDE_REQUEST_TYPE_EMULATED_STATE);
 
         OverrideRequest baseStateRequest = new OverrideRequest(new Binder(), 0 /* pid */,
                 0 /* uid */,
-                1 /* requestedState */,
+                TEST_DEVICE_STATE_ONE,
                 0 /* flags */, OVERRIDE_REQUEST_TYPE_BASE_STATE);
 
         mController.addRequest(firstRequest);
@@ -242,7 +246,7 @@
     @Test
     public void cancelOverrideRequestsTest() {
         OverrideRequest firstRequest = new OverrideRequest(new Binder(), 0 /* pid */, 0 /* uid */,
-                1 /* requestedState */, 0 /* flags */, OVERRIDE_REQUEST_TYPE_EMULATED_STATE);
+                TEST_DEVICE_STATE_ONE, 0 /* flags */, OVERRIDE_REQUEST_TYPE_EMULATED_STATE);
 
         mController.addRequest(firstRequest);
         assertEquals(mStatusListener.getLastStatus(firstRequest).intValue(), STATUS_ACTIVE);
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTvTest.java b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTvTest.java
index a2a8424..0973d46 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTvTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/HdmiCecLocalDeviceTvTest.java
@@ -23,6 +23,7 @@
 import static com.android.server.hdmi.Constants.ADDR_PLAYBACK_2;
 import static com.android.server.hdmi.Constants.ADDR_RECORDER_1;
 import static com.android.server.hdmi.Constants.ADDR_TV;
+import static com.android.server.hdmi.HdmiCecLocalDevice.ActiveSource;
 import static com.android.server.hdmi.HdmiControlService.INITIATED_BY_ENABLE_CEC;
 
 import static com.google.common.truth.Truth.assertThat;
@@ -1712,13 +1713,14 @@
                 HdmiCecMessageBuilder.buildRequestActiveSource(ADDR_TV);
         HdmiCecMessage activeSourceFromTv =
                 HdmiCecMessageBuilder.buildActiveSource(ADDR_TV, 0x0000);
-
         mHdmiControlService.getHdmiCecNetwork().clearLocalDevices();
         mNativeWrapper.setPollAddressResponse(ADDR_PLAYBACK_1, SendMessageResult.SUCCESS);
         mHdmiControlService.allocateLogicalAddress(mLocalDevices, INITIATED_BY_ENABLE_CEC);
         mTestLooper.dispatchAll();
 
         assertThat(mNativeWrapper.getResultMessages()).contains(requestActiveSource);
+        assertThat(mHdmiControlService.getLocalActiveSource()).isEqualTo(
+                new ActiveSource(Constants.ADDR_INVALID, Constants.INVALID_PHYSICAL_ADDRESS));
         mNativeWrapper.clearResultMessages();
         mTestLooper.moveTimeForward(HdmiConfig.TIMEOUT_MS);
         mTestLooper.dispatchAll();
@@ -1728,16 +1730,72 @@
         mTestLooper.moveTimeForward(HdmiConfig.TIMEOUT_MS);
         mTestLooper.dispatchAll();
         assertThat(mNativeWrapper.getResultMessages()).contains(activeSourceFromTv);
+        assertThat(mHdmiControlService.getLocalActiveSource()).isEqualTo(
+                new ActiveSource(mTvLogicalAddress, mTvPhysicalAddress));
     }
 
     @Test
+    public void requestActiveSourceActionComplete_validLocalActiveSource_doNotSendActiveSource() {
+        HdmiCecMessage requestActiveSource =
+                HdmiCecMessageBuilder.buildRequestActiveSource(ADDR_TV);
+        HdmiCecMessage activeSourceFromTv =
+                HdmiCecMessageBuilder.buildActiveSource(ADDR_TV, 0x0000);
+        mHdmiControlService.getHdmiCecNetwork().clearLocalDevices();
+        mHdmiControlService.allocateLogicalAddress(mLocalDevices, INITIATED_BY_ENABLE_CEC);
+        mTestLooper.dispatchAll();
+
+        assertThat(mNativeWrapper.getResultMessages()).contains(requestActiveSource);
+        mHdmiControlService.setActiveSource(mTvLogicalAddress, mTvPhysicalAddress,
+                "HdmiCecLocalDeviceTvTest");
+        mNativeWrapper.clearResultMessages();
+        mTestLooper.moveTimeForward(HdmiConfig.TIMEOUT_MS * 2);
+        mTestLooper.dispatchAll();
+
+        assertThat(mNativeWrapper.getResultMessages()).doesNotContain(activeSourceFromTv);
+    }
+
+    @Test
+    public void onAddressAllocated_startRequestActiveSourceAction_cancelOnDeviceSelect() {
+        HdmiCecMessage requestActiveSource =
+                HdmiCecMessageBuilder.buildRequestActiveSource(ADDR_TV);
+        HdmiCecMessage activeSourceFromTv =
+                HdmiCecMessageBuilder.buildActiveSource(ADDR_TV, 0x0000);
+        mHdmiControlService.getHdmiCecNetwork().clearLocalDevices();
+        mHdmiControlService.allocateLogicalAddress(mLocalDevices, INITIATED_BY_ENABLE_CEC);
+        HdmiDeviceInfo playbackDevice = HdmiDeviceInfo.cecDeviceBuilder()
+                .setLogicalAddress(ADDR_PLAYBACK_1)
+                .setPhysicalAddress(0x1000)
+                .setPortId(PORT_1)
+                .setDeviceType(HdmiDeviceInfo.DEVICE_PLAYBACK)
+                .setVendorId(0x1234)
+                .setDisplayName("Playback 1")
+                .setDevicePowerStatus(HdmiControlManager.POWER_STATUS_ON)
+                .setCecVersion(HdmiControlManager.HDMI_CEC_VERSION_1_4_B)
+                .build();
+        mHdmiControlService.getHdmiCecNetwork().addCecDevice(playbackDevice);
+        mTestLooper.dispatchAll();
+
+        assertThat(mNativeWrapper.getResultMessages()).contains(requestActiveSource);
+        mNativeWrapper.clearResultMessages();
+        mHdmiCecLocalDeviceTv.deviceSelect(playbackDevice.getId(), null);
+        mTestLooper.dispatchAll();
+
+        mTestLooper.moveTimeForward(HdmiConfig.TIMEOUT_MS * 2);
+        mTestLooper.dispatchAll();
+
+        // RequestActiveSourceAction should be cancelled and TV will not broadcast <Active Source>.
+        assertThat(mNativeWrapper.getResultMessages()).doesNotContain(activeSourceFromTv);
+    }
+
+
+    @Test
     public void newDeviceConnectedIfOnlyOneGiveOsdNameSent() {
         mHdmiControlService.getHdmiCecNetwork().clearDeviceList();
         assertThat(mHdmiControlService.getHdmiCecNetwork().getDeviceInfoList(false))
                 .isEmpty();
         HdmiCecMessage reportPhysicalAddress =
                 HdmiCecMessageBuilder.buildReportPhysicalAddressCommand(
-                ADDR_PLAYBACK_2, 0x1000, HdmiDeviceInfo.DEVICE_PLAYBACK);
+                        ADDR_PLAYBACK_2, 0x1000, HdmiDeviceInfo.DEVICE_PLAYBACK);
         HdmiCecMessage giveOsdName = HdmiCecMessageBuilder.buildGiveOsdNameCommand(
                 ADDR_TV, ADDR_PLAYBACK_2);
         mNativeWrapper.onCecMessage(reportPhysicalAddress);
diff --git a/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsServiceTestable.java b/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsServiceTestable.java
index 18961c0..ee076c6 100644
--- a/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsServiceTestable.java
+++ b/services/tests/servicestests/src/com/android/server/locksettings/LockSettingsServiceTestable.java
@@ -140,9 +140,9 @@
         }
 
         @Override
-        public ManagedProfilePasswordCache getManagedProfilePasswordCache(
+        public UnifiedProfilePasswordCache getUnifiedProfilePasswordCache(
                 java.security.KeyStore ks) {
-            return mock(ManagedProfilePasswordCache.class);
+            return mock(UnifiedProfilePasswordCache.class);
         }
 
         @Override
diff --git a/services/tests/servicestests/src/com/android/server/pm/SELinuxMMACTest.java b/services/tests/servicestests/src/com/android/server/pm/SELinuxMMACTest.java
index dd687fd..86a1358 100644
--- a/services/tests/servicestests/src/com/android/server/pm/SELinuxMMACTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/SELinuxMMACTest.java
@@ -27,9 +27,9 @@
 import android.os.Build;
 import android.platform.test.annotations.Presubmit;
 
+import com.android.internal.pm.parsing.pkg.PackageImpl;
 import com.android.internal.pm.parsing.pkg.ParsedPackage;
 import com.android.server.compat.PlatformCompat;
-import com.android.server.pm.parsing.pkg.PackageImpl;
 import com.android.server.pm.pkg.PackageState;
 
 import org.junit.Test;
diff --git a/services/tests/servicestests/src/com/android/server/pm/UserSystemPackageInstallerTest.java b/services/tests/servicestests/src/com/android/server/pm/UserSystemPackageInstallerTest.java
index 8464969..ee93bc1 100644
--- a/services/tests/servicestests/src/com/android/server/pm/UserSystemPackageInstallerTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/UserSystemPackageInstallerTest.java
@@ -53,10 +53,10 @@
 import androidx.test.runner.AndroidJUnit4;
 import androidx.test.uiautomator.UiDevice;
 
+import com.android.internal.pm.parsing.pkg.PackageImpl;
 import com.android.internal.pm.parsing.pkg.ParsedPackage;
 import com.android.server.LocalServices;
 import com.android.server.SystemConfig;
-import com.android.server.pm.parsing.pkg.PackageImpl;
 import com.android.server.pm.pkg.AndroidPackage;
 
 import org.junit.After;
diff --git a/services/tests/servicestests/src/com/android/server/pm/dex/DexoptUtilsTest.java b/services/tests/servicestests/src/com/android/server/pm/dex/DexoptUtilsTest.java
index 0f87202..587f5fa 100644
--- a/services/tests/servicestests/src/com/android/server/pm/dex/DexoptUtilsTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/dex/DexoptUtilsTest.java
@@ -29,9 +29,9 @@
 import androidx.test.filters.SmallTest;
 import androidx.test.runner.AndroidJUnit4;
 
+import com.android.internal.pm.parsing.pkg.PackageImpl;
 import com.android.internal.pm.parsing.pkg.ParsedPackage;
 import com.android.internal.pm.pkg.parsing.ParsingPackage;
-import com.android.server.pm.parsing.pkg.PackageImpl;
 import com.android.server.pm.pkg.AndroidPackage;
 
 import dalvik.system.DelegateLastClassLoader;
diff --git a/services/tests/servicestests/src/com/android/server/pm/parsing/AndroidPackageParsingValidationTest.kt b/services/tests/servicestests/src/com/android/server/pm/parsing/AndroidPackageParsingValidationTest.kt
index ee23a00..9b4ca2a 100644
--- a/services/tests/servicestests/src/com/android/server/pm/parsing/AndroidPackageParsingValidationTest.kt
+++ b/services/tests/servicestests/src/com/android/server/pm/parsing/AndroidPackageParsingValidationTest.kt
@@ -21,17 +21,17 @@
 import android.os.SystemProperties.PROP_VALUE_MAX
 import android.platform.test.annotations.Postsubmit
 import com.android.internal.R
+import com.android.internal.pm.pkg.parsing.ParsingPackageUtils
 import com.android.server.pm.PackageManagerService
-import com.android.server.pm.pkg.parsing.ParsingPackageUtils
 import com.google.common.truth.Truth.assertThat
+import java.io.ByteArrayInputStream
+import java.io.File
 import org.junit.Assert.assertEquals
 import org.junit.Assert.assertThrows
 import org.junit.Assert.fail
 import org.junit.Test
 import org.xmlpull.v1.XmlPullParser
 import org.xmlpull.v1.XmlPullParserFactory
-import java.io.ByteArrayInputStream
-import java.io.File
 
 @Postsubmit
 class AndroidPackageParsingValidationTest {
diff --git a/services/tests/servicestests/src/com/android/server/pm/parsing/TestPackageParser2.kt b/services/tests/servicestests/src/com/android/server/pm/parsing/TestPackageParser2.kt
index 2332817..c44f583 100644
--- a/services/tests/servicestests/src/com/android/server/pm/parsing/TestPackageParser2.kt
+++ b/services/tests/servicestests/src/com/android/server/pm/parsing/TestPackageParser2.kt
@@ -17,6 +17,7 @@
 package com.android.server.pm.parsing
 
 import android.content.pm.ApplicationInfo
+import android.util.ArraySet
 import java.io.File
 
 class TestPackageParser2(var cacheDir: File? = null) : PackageParser2(
@@ -33,4 +34,7 @@
         // behavior.
         return false
     }
+
+    override fun getHiddenApiWhitelistedApps() = ArraySet<String>()
+    override fun getInstallConstraintsAllowlist() = ArraySet<String>()
 })
diff --git a/services/tests/servicestests/src/com/android/server/rollback/RollbackUnitTest.java b/services/tests/servicestests/src/com/android/server/rollback/RollbackUnitTest.java
index 5ba4851..759b204 100644
--- a/services/tests/servicestests/src/com/android/server/rollback/RollbackUnitTest.java
+++ b/services/tests/servicestests/src/com/android/server/rollback/RollbackUnitTest.java
@@ -32,8 +32,9 @@
 import android.util.SparseArray;
 import android.util.SparseIntArray;
 
+import com.android.internal.pm.parsing.pkg.PackageImpl;
 import com.android.server.pm.PackageList;
-import com.android.server.pm.parsing.pkg.PackageImpl;
+
 import com.google.common.collect.Range;
 
 import org.junit.Before;
@@ -415,7 +416,7 @@
 
     private void addPkgWithMinExtVersions(String pkg, int[][] minExtVersions) {
         mPackages.add(pkg);
-        PackageImpl pkgImpl = new PackageImpl(pkg, "baseCodePath", "codePath", null, false);
+        PackageImpl pkgImpl = new PackageImpl(pkg, "baseCodePath", "codePath", null, false, null);
         pkgImpl.setMinExtensionVersions(sparseArrayFrom(minExtVersions));
 
         when(mMockPmi.getPackage(pkg)).thenReturn(pkgImpl);
diff --git a/services/tests/uiservicestests/Android.bp b/services/tests/uiservicestests/Android.bp
index 6335b3b..4e1c72a 100644
--- a/services/tests/uiservicestests/Android.bp
+++ b/services/tests/uiservicestests/Android.bp
@@ -46,6 +46,7 @@
         "testng",
         "flag-junit",
         "notification_flags_lib",
+        "platform-test-rules",
     ],
 
     libs: [
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
index 36d55a4..14b551a 100755
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
@@ -211,6 +211,9 @@
 import android.permission.PermissionManager;
 import android.platform.test.annotations.EnableFlags;
 import android.platform.test.flag.junit.SetFlagsRule;
+import android.platform.test.rule.DeniedDevices;
+import android.platform.test.rule.DeviceProduct;
+import android.platform.test.rule.LimitDevicesRule;
 import android.provider.DeviceConfig;
 import android.provider.MediaStore;
 import android.provider.Settings;
@@ -281,6 +284,7 @@
 import org.junit.After;
 import org.junit.Assert;
 import org.junit.Before;
+import org.junit.ClassRule;
 import org.junit.Rule;
 import org.junit.Test;
 import org.junit.rules.TestRule;
@@ -313,6 +317,7 @@
 @RunWith(AndroidTestingRunner.class)
 @SuppressLint("GuardedBy") // It's ok for this test to access guarded methods from the service.
 @RunWithLooper
+@DeniedDevices(denied = {DeviceProduct.CF_AUTO})
 public class NotificationManagerServiceTest extends UiServiceTestCase {
     private static final String TEST_CHANNEL_ID = "NotificationManagerServiceTestChannelId";
     private static final String TEST_PACKAGE = "The.name.is.Package.Test.Package";
@@ -331,6 +336,9 @@
     private final int mUid = Binder.getCallingUid();
     private final @UserIdInt int mUserId = UserHandle.getUserId(mUid);
 
+    @ClassRule
+    public static final LimitDevicesRule sLimitDevicesRule = new LimitDevicesRule();
+
     @Rule
     public TestRule compatChangeRule = new PlatformCompatChangeRule();
 
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java
index 646ee3f..1aea56c 100644
--- a/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/ZenModeHelperTest.java
@@ -80,6 +80,7 @@
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.notNull;
 import static org.mockito.Mockito.reset;
+import static org.mockito.Mockito.spy;
 import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.verifyNoMoreInteractions;
@@ -265,10 +266,12 @@
                 .thenReturn(CUSTOM_PKG_UID);
         when(mPackageManager.getPackagesForUid(anyInt())).thenReturn(
                 new String[] {pkg});
-        ApplicationInfo mockAppInfo = mock(ApplicationInfo.class);
-        when(mockAppInfo.loadLabel(any())).thenReturn(CUSTOM_APP_LABEL);
+
+        ApplicationInfo appInfoSpy = spy(new ApplicationInfo());
+        appInfoSpy.icon = ICON_RES_ID;
+        when(appInfoSpy.loadLabel(any())).thenReturn(CUSTOM_APP_LABEL);
         when(mPackageManager.getApplicationInfo(eq(CUSTOM_PKG_NAME), anyInt()))
-                .thenReturn(mockAppInfo);
+                .thenReturn(appInfoSpy);
         mZenModeHelper.mPm = mPackageManager;
 
         mZenModeEventLogger.reset();
@@ -3753,6 +3756,10 @@
         rule.zenPolicy = policy;
         rule.pkg = ownerPkg;
         rule.name = CUSTOM_APP_LABEL;
+        rule.iconResName = ICON_RES_NAME;
+        rule.triggerDescription = mContext.getString(R.string.zen_mode_implicit_trigger_description,
+                CUSTOM_APP_LABEL);
+        rule.type = AutomaticZenRule.TYPE_OTHER;
         rule.enabled = true;
         return rule;
     }
diff --git a/services/tests/vibrator/src/com/android/server/vibrator/VibratorControlServiceTest.java b/services/tests/vibrator/src/com/android/server/vibrator/VibratorControlServiceTest.java
new file mode 100644
index 0000000..49efd1b
--- /dev/null
+++ b/services/tests/vibrator/src/com/android/server/vibrator/VibratorControlServiceTest.java
@@ -0,0 +1,63 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.vibrator;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.os.RemoteException;
+
+import org.junit.Before;
+import org.junit.Test;
+
+public class VibratorControlServiceTest {
+
+    private VibratorControlService mVibratorControlService;
+    private final Object mLock = new Object();
+
+    @Before
+    public void setUp() throws Exception {
+        mVibratorControlService = new VibratorControlService(new VibratorControllerHolder(), mLock);
+    }
+
+    @Test
+    public void testRegisterVibratorController() throws RemoteException {
+        FakeVibratorController fakeController = new FakeVibratorController();
+        mVibratorControlService.registerVibratorController(fakeController);
+
+        assertThat(fakeController.isLinkedToDeath).isTrue();
+    }
+
+    @Test
+    public void testUnregisterVibratorController_providingTheRegisteredController_performsRequest()
+            throws RemoteException {
+        FakeVibratorController fakeController = new FakeVibratorController();
+        mVibratorControlService.registerVibratorController(fakeController);
+        mVibratorControlService.unregisterVibratorController(fakeController);
+        assertThat(fakeController.isLinkedToDeath).isFalse();
+    }
+
+    @Test
+    public void testUnregisterVibratorController_providingAnInvalidController_ignoresRequest()
+            throws RemoteException {
+        FakeVibratorController fakeController1 = new FakeVibratorController();
+        FakeVibratorController fakeController2 = new FakeVibratorController();
+        mVibratorControlService.registerVibratorController(fakeController1);
+
+        mVibratorControlService.unregisterVibratorController(fakeController2);
+        assertThat(fakeController1.isLinkedToDeath).isTrue();
+    }
+}
diff --git a/services/tests/vibrator/src/com/android/server/vibrator/VibratorControllerHolderTest.java b/services/tests/vibrator/src/com/android/server/vibrator/VibratorControllerHolderTest.java
new file mode 100644
index 0000000..79abe21
--- /dev/null
+++ b/services/tests/vibrator/src/com/android/server/vibrator/VibratorControllerHolderTest.java
@@ -0,0 +1,72 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.vibrator;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.os.RemoteException;
+
+import org.junit.Before;
+import org.junit.Test;
+
+public class VibratorControllerHolderTest {
+
+    private final FakeVibratorController mFakeVibratorController = new FakeVibratorController();
+    private VibratorControllerHolder mVibratorControllerHolder;
+
+    @Before
+    public void setUp() throws Exception {
+        mVibratorControllerHolder = new VibratorControllerHolder();
+    }
+
+    @Test
+    public void testSetVibratorController_linksVibratorControllerToDeath() throws RemoteException {
+        mVibratorControllerHolder.setVibratorController(mFakeVibratorController);
+        assertThat(mVibratorControllerHolder.getVibratorController())
+                .isEqualTo(mFakeVibratorController);
+        assertThat(mFakeVibratorController.isLinkedToDeath).isTrue();
+    }
+
+    @Test
+    public void testSetVibratorController_setControllerToNull_unlinksVibratorControllerToDeath()
+            throws RemoteException {
+        mVibratorControllerHolder.setVibratorController(mFakeVibratorController);
+        mVibratorControllerHolder.setVibratorController(null);
+        assertThat(mFakeVibratorController.isLinkedToDeath).isFalse();
+        assertThat(mVibratorControllerHolder.getVibratorController()).isNull();
+    }
+
+    @Test
+    public void testBinderDied_withValidController_unlinksVibratorControllerToDeath()
+            throws RemoteException {
+        mVibratorControllerHolder.setVibratorController(mFakeVibratorController);
+        mVibratorControllerHolder.binderDied(mFakeVibratorController);
+        assertThat(mFakeVibratorController.isLinkedToDeath).isFalse();
+        assertThat(mVibratorControllerHolder.getVibratorController()).isNull();
+    }
+
+    @Test
+    public void testBinderDied_withInvalidController_ignoresRequest()
+            throws RemoteException {
+        mVibratorControllerHolder.setVibratorController(mFakeVibratorController);
+        FakeVibratorController imposterVibratorController = new FakeVibratorController();
+        mVibratorControllerHolder.binderDied(imposterVibratorController);
+        assertThat(mFakeVibratorController.isLinkedToDeath).isTrue();
+        assertThat(mVibratorControllerHolder.getVibratorController())
+                .isEqualTo(mFakeVibratorController);
+    }
+}
diff --git a/services/tests/vibrator/src/com/android/server/vibrator/VibratorManagerServiceTest.java b/services/tests/vibrator/src/com/android/server/vibrator/VibratorManagerServiceTest.java
index 4e9bbe0..d6b2116 100644
--- a/services/tests/vibrator/src/com/android/server/vibrator/VibratorManagerServiceTest.java
+++ b/services/tests/vibrator/src/com/android/server/vibrator/VibratorManagerServiceTest.java
@@ -307,9 +307,10 @@
 
                     @Override
                     void addService(String name, IBinder service) {
-                        Object serviceInstance = service;
-                        mExternalVibratorService =
-                                (VibratorManagerService.ExternalVibratorService) serviceInstance;
+                        if (service instanceof VibratorManagerService.ExternalVibratorService) {
+                            mExternalVibratorService =
+                                    (VibratorManagerService.ExternalVibratorService) service;
+                        }
                     }
 
                     HapticFeedbackVibrationProvider createHapticFeedbackVibrationProvider(
diff --git a/services/tests/vibrator/utils/com/android/server/vibrator/FakeVibratorController.java b/services/tests/vibrator/utils/com/android/server/vibrator/FakeVibratorController.java
new file mode 100644
index 0000000..7e23587
--- /dev/null
+++ b/services/tests/vibrator/utils/com/android/server/vibrator/FakeVibratorController.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.server.vibrator;
+
+import android.annotation.NonNull;
+import android.frameworks.vibrator.IVibratorController;
+import android.os.IBinder;
+import android.os.RemoteException;
+
+/**
+ * Provides a fake implementation of {@link android.frameworks.vibrator.IVibratorController} for
+ * testing.
+ */
+public final class FakeVibratorController extends IVibratorController.Stub {
+
+    public boolean isLinkedToDeath = false;
+
+    @Override
+    public void requestVibrationParams(int i, long l, IBinder iBinder) throws RemoteException {
+
+    }
+
+    @Override
+    public int getInterfaceVersion() throws RemoteException {
+        return 0;
+    }
+
+    @Override
+    public String getInterfaceHash() throws RemoteException {
+        return null;
+    }
+
+    @Override
+    public void linkToDeath(@NonNull DeathRecipient recipient, int flags) {
+        super.linkToDeath(recipient, flags);
+        isLinkedToDeath = true;
+    }
+
+    @Override
+    public boolean unlinkToDeath(@NonNull DeathRecipient recipient, int flags) {
+        isLinkedToDeath = false;
+        return super.unlinkToDeath(recipient, flags);
+    }
+}
diff --git a/services/tests/voiceinteractiontests/Android.bp b/services/tests/voiceinteractiontests/Android.bp
index 744cb63..8a79fe4 100644
--- a/services/tests/voiceinteractiontests/Android.bp
+++ b/services/tests/voiceinteractiontests/Android.bp
@@ -44,6 +44,7 @@
         "servicestests-core-utils",
         "servicestests-utils-mockito-extended",
         "truth",
+        "frameworks-base-testutils",
     ],
 
     libs: [
diff --git a/services/tests/voiceinteractiontests/src/com/android/server/voiceinteraction/SetSandboxedTrainingDataAllowedTest.java b/services/tests/voiceinteractiontests/src/com/android/server/voiceinteraction/SetSandboxedTrainingDataAllowedTest.java
new file mode 100644
index 0000000..159c760
--- /dev/null
+++ b/services/tests/voiceinteractiontests/src/com/android/server/voiceinteraction/SetSandboxedTrainingDataAllowedTest.java
@@ -0,0 +1,180 @@
+/*
+ * 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.voiceinteraction;
+
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.junit.Assert.assertThrows;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.verify;
+
+import android.Manifest;
+import android.app.ActivityManagerInternal;
+import android.app.AppOpsManager;
+import android.app.role.RoleManager;
+import android.content.Context;
+import android.content.pm.ApplicationInfo;
+import android.os.PermissionEnforcer;
+import android.os.Process;
+import android.os.test.FakePermissionEnforcer;
+import android.platform.test.annotations.Presubmit;
+
+import androidx.test.core.app.ApplicationProvider;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SmallTest;
+
+import com.android.modules.utils.testing.ExtendedMockitoRule;
+import com.android.server.LocalServices;
+import com.android.server.pm.UserManagerInternal;
+import com.android.server.pm.permission.LegacyPermissionManagerInternal;
+import com.android.server.pm.permission.PermissionManagerServiceInternal;
+import com.android.server.wm.ActivityTaskManagerInternal;
+
+import org.junit.Before;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.mockito.quality.Strictness;
+
+@SmallTest
+@Presubmit
+@RunWith(AndroidJUnit4.class)
+public class SetSandboxedTrainingDataAllowedTest {
+
+    @Captor private ArgumentCaptor<Integer> mOpIdCaptor, mUidCaptor, mOpModeCaptor;
+
+    @Mock
+    private AppOpsManager mAppOpsManager;
+
+    @Mock
+    private VoiceInteractionManagerServiceImpl mVoiceInteractionManagerServiceImpl;
+
+    private FakePermissionEnforcer mPermissionEnforcer = new FakePermissionEnforcer();
+
+    private Context mContext;
+
+    private VoiceInteractionManagerService mVoiceInteractionManagerService;
+    private VoiceInteractionManagerService.VoiceInteractionManagerServiceStub
+            mVoiceInteractionManagerServiceStub;
+
+    private ApplicationInfo mApplicationInfo = new ApplicationInfo();
+
+    @Rule
+    public final ExtendedMockitoRule mExtendedMockitoRule =
+            new ExtendedMockitoRule.Builder(this)
+                    .setStrictness(Strictness.WARN)
+                    .mockStatic(LocalServices.class)
+                    .mockStatic(PermissionEnforcer.class)
+                    .build();
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+
+        mContext = spy(ApplicationProvider.getApplicationContext());
+
+        doReturn(mPermissionEnforcer).when(() -> PermissionEnforcer.fromContext(any()));
+        doReturn(mock(PermissionManagerServiceInternal.class)).when(
+                () -> LocalServices.getService(PermissionManagerServiceInternal.class));
+        doReturn(mock(ActivityManagerInternal.class)).when(
+                () -> LocalServices.getService(ActivityManagerInternal.class));
+        doReturn(mock(UserManagerInternal.class)).when(
+                () -> LocalServices.getService(UserManagerInternal.class));
+        doReturn(mock(ActivityTaskManagerInternal.class)).when(
+                () -> LocalServices.getService(ActivityTaskManagerInternal.class));
+        doReturn(mock(LegacyPermissionManagerInternal.class)).when(
+                () -> LocalServices.getService(LegacyPermissionManagerInternal.class));
+        doReturn(mock(RoleManager.class)).when(mContext).getSystemService(RoleManager.class);
+        doReturn(mAppOpsManager).when(mContext).getSystemService(Context.APP_OPS_SERVICE);
+        doReturn(mApplicationInfo).when(mVoiceInteractionManagerServiceImpl).getApplicationInfo();
+
+        mVoiceInteractionManagerService = new VoiceInteractionManagerService(mContext);
+        mVoiceInteractionManagerServiceStub =
+                mVoiceInteractionManagerService.new VoiceInteractionManagerServiceStub();
+        mVoiceInteractionManagerServiceStub.mImpl = mVoiceInteractionManagerServiceImpl;
+        mPermissionEnforcer.grant(Manifest.permission.MANAGE_HOTWORD_DETECTION);
+    }
+
+    @Test
+    public void setShouldReceiveSandboxedTrainingData_currentAndPreinstalledAssistant_setsOp() {
+        // Set application info so current app is the current and preinstalled assistant.
+        mApplicationInfo.uid = Process.myUid();
+        mApplicationInfo.flags = ApplicationInfo.FLAG_SYSTEM;
+
+        mVoiceInteractionManagerServiceStub.setShouldReceiveSandboxedTrainingData(
+                /* allowed= */ true);
+
+        verify(mAppOpsManager).setUidMode(mOpIdCaptor.capture(), mUidCaptor.capture(),
+                mOpModeCaptor.capture());
+        assertThat(mOpIdCaptor.getValue()).isEqualTo(
+                AppOpsManager.OP_RECEIVE_SANDBOXED_DETECTION_TRAINING_DATA);
+        assertThat(mOpModeCaptor.getValue()).isEqualTo(AppOpsManager.MODE_ALLOWED);
+        assertThat(mUidCaptor.getValue()).isEqualTo(Process.myUid());
+    }
+
+    @Test
+    public void setShouldReceiveSandboxedTrainingData_missingPermission_doesNotSetOp() {
+        // Set application info so current app is the current and preinstalled assistant.
+        mApplicationInfo.uid = Process.myUid();
+        mApplicationInfo.flags = ApplicationInfo.FLAG_SYSTEM;
+
+        // Simulate missing MANAGE_HOTWORD_DETECTION permission.
+        mPermissionEnforcer.revoke(Manifest.permission.MANAGE_HOTWORD_DETECTION);
+
+        assertThrows(SecurityException.class,
+                () -> mVoiceInteractionManagerServiceStub.setShouldReceiveSandboxedTrainingData(
+                        /* allowed= */ true));
+
+        verify(mAppOpsManager, never()).setUidMode(anyInt(), anyInt(), anyInt());
+    }
+
+    @Test
+    public void setShouldReceiveSandboxedTrainingData_notPreinstalledAssistant_doesNotSetOp() {
+        // Set application info so current app is not preinstalled assistant.
+        mApplicationInfo.uid = Process.myUid();
+        mApplicationInfo.flags = ApplicationInfo.FLAG_INSTALLED; // Does not contain FLAG_SYSTEM.
+
+        assertThrows(SecurityException.class,
+                () -> mVoiceInteractionManagerServiceStub.setShouldReceiveSandboxedTrainingData(
+                                /* allowed= */ true));
+
+        verify(mAppOpsManager, never()).setUidMode(anyInt(), anyInt(), anyInt());
+    }
+
+    @Test
+    public void setShouldReceiveSandboxedTrainingData_notCurrentAssistant_doesNotSetOp() {
+        // Set application info so current app is not current assistant.
+        mApplicationInfo.uid = Process.SHELL_UID; // Set current assistant uid to shell UID.
+        mApplicationInfo.flags = ApplicationInfo.FLAG_SYSTEM;
+
+        assertThrows(SecurityException.class,
+                () -> mVoiceInteractionManagerServiceStub.setShouldReceiveSandboxedTrainingData(
+                                /* allowed= */ true));
+
+        verify(mAppOpsManager, never()).setUidMode(anyInt(), anyInt(), anyInt());
+    }
+}
diff --git a/services/tests/wmtests/src/com/android/server/policy/ModifierShortcutTests.java b/services/tests/wmtests/src/com/android/server/policy/ModifierShortcutTests.java
index 8d236ed..0382ca0 100644
--- a/services/tests/wmtests/src/com/android/server/policy/ModifierShortcutTests.java
+++ b/services/tests/wmtests/src/com/android/server/policy/ModifierShortcutTests.java
@@ -61,6 +61,7 @@
         META_SHORTCUTS.append(KEYCODE_P, Intent.CATEGORY_APP_MUSIC);
         META_SHORTCUTS.append(KEYCODE_S, Intent.CATEGORY_APP_MESSAGING);
     }
+    private static final int ANY_DISPLAY_ID = 123;
 
     @Before
     public void setUp() {
@@ -96,8 +97,9 @@
      */
     @Test
     public void testCtrlSpace() {
-        sendKeyCombination(new int[]{KEYCODE_CTRL_LEFT, KEYCODE_SPACE}, 0);
-        mPhoneWindowManager.assertSwitchKeyboardLayout(1);
+        sendKeyCombination(new int[]{KEYCODE_CTRL_LEFT, KEYCODE_SPACE}, /* duration= */ 0,
+                ANY_DISPLAY_ID);
+        mPhoneWindowManager.assertSwitchKeyboardLayout(/* direction= */ 1, ANY_DISPLAY_ID);
     }
 
     /**
@@ -105,8 +107,9 @@
      */
     @Test
     public void testCtrlShiftSpace() {
-        sendKeyCombination(new int[]{KEYCODE_CTRL_LEFT, KEYCODE_SHIFT_LEFT, KEYCODE_SPACE}, 0);
-        mPhoneWindowManager.assertSwitchKeyboardLayout(-1);
+        sendKeyCombination(new int[]{KEYCODE_CTRL_LEFT, KEYCODE_SHIFT_LEFT, KEYCODE_SPACE},
+                /* duration= */ 0, ANY_DISPLAY_ID);
+        mPhoneWindowManager.assertSwitchKeyboardLayout(/* direction= */ -1, ANY_DISPLAY_ID);
     }
 
     /**
diff --git a/services/tests/wmtests/src/com/android/server/policy/ShortcutKeyTestBase.java b/services/tests/wmtests/src/com/android/server/policy/ShortcutKeyTestBase.java
index 9cdec25..157d162 100644
--- a/services/tests/wmtests/src/com/android/server/policy/ShortcutKeyTestBase.java
+++ b/services/tests/wmtests/src/com/android/server/policy/ShortcutKeyTestBase.java
@@ -114,7 +114,7 @@
         }
     }
 
-    void sendKeyCombination(int[] keyCodes, long durationMillis, boolean longPress) {
+    void sendKeyCombination(int[] keyCodes, long durationMillis, boolean longPress, int displayId) {
         final long downTime = mPhoneWindowManager.getCurrentTime();
         final int count = keyCodes.length;
         int metaState = 0;
@@ -124,7 +124,7 @@
             final KeyEvent event = new KeyEvent(downTime, downTime, KeyEvent.ACTION_DOWN, keyCode,
                     0 /*repeat*/, metaState, KeyCharacterMap.VIRTUAL_KEYBOARD, 0 /*scancode*/,
                     0 /*flags*/, InputDevice.SOURCE_KEYBOARD);
-            event.setDisplayId(DEFAULT_DISPLAY);
+            event.setDisplayId(displayId);
             interceptKey(event);
             // The order is important here, metaState could be updated and applied to the next key.
             metaState |= MODIFIER.getOrDefault(keyCode, 0);
@@ -142,7 +142,7 @@
                         KeyEvent.ACTION_DOWN, keyCode, 1 /*repeat*/, metaState,
                         KeyCharacterMap.VIRTUAL_KEYBOARD, 0 /*scancode*/,
                         KeyEvent.FLAG_LONG_PRESS /*flags*/, InputDevice.SOURCE_KEYBOARD);
-                nextDownEvent.setDisplayId(DEFAULT_DISPLAY);
+                nextDownEvent.setDisplayId(displayId);
                 interceptKey(nextDownEvent);
             }
         }
@@ -153,18 +153,23 @@
             final KeyEvent upEvent = new KeyEvent(downTime, eventTime, KeyEvent.ACTION_UP, keyCode,
                     0, metaState, KeyCharacterMap.VIRTUAL_KEYBOARD, 0 /*scancode*/, 0 /*flags*/,
                     InputDevice.SOURCE_KEYBOARD);
-            upEvent.setDisplayId(DEFAULT_DISPLAY);
+            upEvent.setDisplayId(displayId);
             interceptKey(upEvent);
             metaState &= ~MODIFIER.getOrDefault(keyCode, 0);
         }
     }
 
     void sendKeyCombination(int[] keyCodes, long durationMillis) {
-        sendKeyCombination(keyCodes, durationMillis, false /* longPress */);
+        sendKeyCombination(keyCodes, durationMillis, false /* longPress */, DEFAULT_DISPLAY);
+    }
+
+    void sendKeyCombination(int[] keyCodes, long durationMillis, int displayId) {
+        sendKeyCombination(keyCodes, durationMillis, false /* longPress */, displayId);
     }
 
     void sendLongPressKeyCombination(int[] keyCodes) {
-        sendKeyCombination(keyCodes, ViewConfiguration.getLongPressTimeout(), true /* longPress */);
+        sendKeyCombination(keyCodes, ViewConfiguration.getLongPressTimeout(), true /* longPress */,
+                DEFAULT_DISPLAY);
     }
 
     void sendKey(int keyCode) {
@@ -172,7 +177,7 @@
     }
 
     void sendKey(int keyCode, boolean longPress) {
-        sendKeyCombination(new int[]{keyCode}, 0 /*durationMillis*/, longPress);
+        sendKeyCombination(new int[]{keyCode}, 0 /*durationMillis*/, longPress, DEFAULT_DISPLAY);
     }
 
     private void interceptKey(KeyEvent keyEvent) {
diff --git a/services/tests/wmtests/src/com/android/server/policy/TestPhoneWindowManager.java b/services/tests/wmtests/src/com/android/server/policy/TestPhoneWindowManager.java
index d057226..0678210 100644
--- a/services/tests/wmtests/src/com/android/server/policy/TestPhoneWindowManager.java
+++ b/services/tests/wmtests/src/com/android/server/policy/TestPhoneWindowManager.java
@@ -166,12 +166,34 @@
     @Mock
     private PhoneWindowManager.ButtonOverridePermissionChecker mButtonOverridePermissionChecker;
 
+    @Mock private IBinder mInputToken;
+    @Mock private IBinder mImeTargetWindowToken;
+
     private StaticMockitoSession mMockitoSession;
     private OffsettableClock mClock = new OffsettableClock();
     private TestLooper mTestLooper = new TestLooper(() -> mClock.now());
     private HandlerThread mHandlerThread;
     private Handler mHandler;
 
+    private boolean mIsTalkBackEnabled;
+
+    class TestTalkbackShortcutController extends TalkbackShortcutController {
+        TestTalkbackShortcutController(Context context) {
+            super(context);
+        }
+
+        @Override
+        boolean toggleTalkback(int currentUserId) {
+            mIsTalkBackEnabled = !mIsTalkBackEnabled;
+            return mIsTalkBackEnabled;
+        }
+
+        @Override
+        boolean isTalkBackShortcutGestureEnabled() {
+            return true;
+        }
+    }
+
     private class TestInjector extends PhoneWindowManager.Injector {
         TestInjector(Context context, WindowManagerPolicy.WindowManagerFuncs funcs) {
             super(context, funcs, mTestLooper.getLooper());
@@ -197,6 +219,10 @@
         PhoneWindowManager.ButtonOverridePermissionChecker getButtonOverridePermissionChecker() {
             return mButtonOverridePermissionChecker;
         }
+
+        TalkbackShortcutController getTalkbackShortcutController() {
+            return new TestTalkbackShortcutController(mContext);
+        }
     }
 
     TestPhoneWindowManager(Context context, boolean supportSettingsUpdate) {
@@ -304,6 +330,9 @@
         doNothing().when(mPhoneWindowManager).finishedWakingUp(anyInt(), anyInt());
         doNothing().when(mPhoneWindowManager).lockNow(any());
 
+        doReturn(mImeTargetWindowToken)
+                .when(mWindowManagerInternal).getTargetWindowTokenFromInputToken(mInputToken);
+
         mPhoneWindowManager.init(new TestInjector(mContext, mWindowManagerFuncsImpl));
         mPhoneWindowManager.systemReady();
         mPhoneWindowManager.systemBooted();
@@ -342,12 +371,12 @@
     }
 
     long interceptKeyBeforeDispatching(KeyEvent event) {
-        return mPhoneWindowManager.interceptKeyBeforeDispatching(null /*focusedToken*/,
-                event, FLAG_INTERACTIVE);
+        return mPhoneWindowManager.interceptKeyBeforeDispatching(mInputToken, event,
+                FLAG_INTERACTIVE);
     }
 
     void dispatchUnhandledKey(KeyEvent event) {
-        mPhoneWindowManager.dispatchUnhandledKey(null /*focusedToken*/, event, FLAG_INTERACTIVE);
+        mPhoneWindowManager.dispatchUnhandledKey(mInputToken, event, FLAG_INTERACTIVE);
     }
 
     long getCurrentTime() {
@@ -623,14 +652,16 @@
         verify(mStatusBarManagerInternal).startAssist(any());
     }
 
-    void assertSwitchKeyboardLayout(int direction) {
+    void assertSwitchKeyboardLayout(int direction, int displayId) {
         mTestLooper.dispatchAll();
         if (FeatureFlagUtils.isEnabled(mContext, FeatureFlagUtils.SETTINGS_NEW_KEYBOARD_UI)) {
-            verify(mInputMethodManagerInternal).switchKeyboardLayout(eq(direction));
+            verify(mInputMethodManagerInternal).onSwitchKeyboardLayoutShortcut(eq(direction),
+                    eq(displayId), eq(mImeTargetWindowToken));
             verify(mWindowManagerFuncsImpl, never()).switchKeyboardLayout(anyInt(), anyInt());
         } else {
             verify(mWindowManagerFuncsImpl).switchKeyboardLayout(anyInt(), eq(direction));
-            verify(mInputMethodManagerInternal, never()).switchKeyboardLayout(anyInt());
+            verify(mInputMethodManagerInternal, never())
+                    .onSwitchKeyboardLayoutShortcut(anyInt(), anyInt(), any());
         }
     }
 
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityMetricsLaunchObserverTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityMetricsLaunchObserverTests.java
index 65e77dc..d4ba3b2 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityMetricsLaunchObserverTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityMetricsLaunchObserverTests.java
@@ -124,7 +124,7 @@
     private void verifyOnActivityLaunched(ActivityRecord activity) {
         final ArgumentCaptor<Long> idCaptor = ArgumentCaptor.forClass(Long.class);
         verifyAsync(mLaunchObserver).onActivityLaunched(idCaptor.capture(),
-                eq(activity.mActivityComponent), anyInt());
+                eq(activity.mActivityComponent), anyInt(), anyInt());
         final long id = idCaptor.getValue();
         setExpectedStartedId(id, activity);
         mLastLaunchedIds.put(activity.mActivityComponent, id);
@@ -132,7 +132,7 @@
 
     private void verifyOnActivityLaunchFinished(ActivityRecord activity) {
         verifyAsync(mLaunchObserver).onActivityLaunchFinished(eq(mExpectedStartedId),
-                eq(activity.mActivityComponent), anyLong());
+                eq(activity.mActivityComponent), anyLong(), anyInt());
     }
 
     private void setExpectedStartedId(long id, Object reason) {
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
index 7aa46a6..85c6f9e 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
@@ -123,6 +123,7 @@
 import android.app.PictureInPictureParams;
 import android.app.servertransaction.ActivityConfigurationChangeItem;
 import android.app.servertransaction.ClientTransaction;
+import android.app.servertransaction.ClientTransactionItem;
 import android.app.servertransaction.DestroyActivityItem;
 import android.app.servertransaction.PauseActivityItem;
 import android.app.servertransaction.WindowStateResizeItem;
@@ -169,6 +170,7 @@
 import org.mockito.invocation.InvocationOnMock;
 
 import java.util.ArrayList;
+import java.util.List;
 import java.util.function.BiConsumer;
 import java.util.function.Consumer;
 
@@ -260,8 +262,18 @@
         final MutableBoolean pauseFound = new MutableBoolean(false);
         doAnswer((InvocationOnMock invocationOnMock) -> {
             final ClientTransaction transaction = invocationOnMock.getArgument(0);
-            if (transaction.getLifecycleStateRequest() instanceof PauseActivityItem) {
-                pauseFound.value = true;
+            final List<ClientTransactionItem> items = transaction.getTransactionItems();
+            if (items != null) {
+                for (ClientTransactionItem item : items) {
+                    if (item instanceof PauseActivityItem) {
+                        pauseFound.value = true;
+                        break;
+                    }
+                }
+            } else {
+                if (transaction.getLifecycleStateRequest() instanceof PauseActivityItem) {
+                    pauseFound.value = true;
+                }
             }
             return null;
         }).when(mClientLifecycleManager).scheduleTransaction(any());
@@ -279,6 +291,8 @@
 
         // If the activity is not focusable, it should move to paused.
         activity.makeVisibleIfNeeded(null /* starting */, true /* reportToClient */);
+        mClientLifecycleManager.dispatchPendingTransactions();
+
         assertTrue(activity.isState(PAUSING));
         assertTrue(pauseFound.value);
 
@@ -1239,7 +1253,7 @@
     }
 
     @Test
-    public void testFinishActivityIfPossible_sendResultImmediately() {
+    public void testFinishActivityIfPossible_sendResultImmediately() throws RemoteException {
         // Create activity representing the source of the activity result.
         final ComponentName sourceComponent = ComponentName.createRelative(
                 DEFAULT_COMPONENT_PACKAGE_NAME, ".SourceActivity");
@@ -1270,12 +1284,8 @@
 
         targetActivity.finishIfPossible(0, new Intent(), null, "test", false /* oomAdj */);
 
-        try {
-            verify(mClientLifecycleManager, atLeastOnce()).scheduleTransaction(
-                    any(ClientTransaction.class));
-        } catch (RemoteException ignored) {
-        }
-
+        verify(mClientLifecycleManager, atLeastOnce()).scheduleTransactionItem(
+                any(), any(ClientTransactionItem.class));
         assertNull(targetActivity.results);
     }
 
diff --git a/services/tests/wmtests/src/com/android/server/wm/ClientLifecycleManagerTests.java b/services/tests/wmtests/src/com/android/server/wm/ClientLifecycleManagerTests.java
index 04aa981..7fdc5fc 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ClientLifecycleManagerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ClientLifecycleManagerTests.java
@@ -17,13 +17,15 @@
 package com.android.server.wm;
 
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.spy;
+import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
+import static com.android.window.flags.Flags.FLAG_BUNDLE_CLIENT_TRANSACTION_FLAG;
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.Mockito.clearInvocations;
-import static org.mockito.Mockito.doNothing;
 import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.never;
 
@@ -31,12 +33,15 @@
 import android.app.servertransaction.ActivityLifecycleItem;
 import android.app.servertransaction.ClientTransaction;
 import android.app.servertransaction.ClientTransactionItem;
+import android.os.IBinder;
 import android.os.RemoteException;
 import android.platform.test.annotations.Presubmit;
+import android.platform.test.flag.junit.SetFlagsRule;
 
 import androidx.test.filters.SmallTest;
 
 import org.junit.Before;
+import org.junit.Rule;
 import org.junit.Test;
 import org.mockito.ArgumentCaptor;
 import org.mockito.Captor;
@@ -47,30 +52,47 @@
  * Build/Install/Run:
  *  atest WmTests:ClientLifecycleManagerTests
  */
+// Suppress GuardedBy warning on unit tests
+@SuppressWarnings("GuardedBy")
 @SmallTest
 @Presubmit
 public class ClientLifecycleManagerTests {
 
+    @Rule(order = 0)
+    public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
+
+    @Rule(order = 1)
+    public final SystemServicesTestRule mSystemServices = new SystemServicesTestRule();
+
+    @Mock
+    private IBinder mClientBinder;
     @Mock
     private IApplicationThread mClient;
     @Mock
     private IApplicationThread.Stub mNonBinderClient;
     @Mock
+    private ClientTransaction mTransaction;
+    @Mock
     private ClientTransactionItem mTransactionItem;
     @Mock
     private ActivityLifecycleItem mLifecycleItem;
     @Captor
     private ArgumentCaptor<ClientTransaction> mTransactionCaptor;
 
+    private WindowManagerService mWms;
     private ClientLifecycleManager mLifecycleManager;
 
     @Before
     public void setup() {
         MockitoAnnotations.initMocks(this);
 
+        mWms = mSystemServices.getWindowManagerService();
         mLifecycleManager = spy(new ClientLifecycleManager());
+        mLifecycleManager.setWindowManager(mWms);
 
         doReturn(true).when(mLifecycleItem).isActivityLifecycleItem();
+        doReturn(mClientBinder).when(mClient).asBinder();
+        doReturn(mNonBinderClient).when(mNonBinderClient).asBinder();
     }
 
     @Test
@@ -92,9 +114,11 @@
     }
 
     @Test
-    public void testScheduleTransactionItem() throws RemoteException {
-        doNothing().when(mLifecycleManager).scheduleTransaction(any());
-        mLifecycleManager.scheduleTransactionItem(mClient, mTransactionItem);
+    public void testScheduleTransactionItem_notBundle() throws RemoteException {
+        mSetFlagsRule.disableFlags(FLAG_BUNDLE_CLIENT_TRANSACTION_FLAG);
+
+        // Use non binder client to get non-recycled ClientTransaction.
+        mLifecycleManager.scheduleTransactionItem(mNonBinderClient, mTransactionItem);
 
         verify(mLifecycleManager).scheduleTransaction(mTransactionCaptor.capture());
         ClientTransaction transaction = mTransactionCaptor.getValue();
@@ -104,7 +128,7 @@
         assertNull(transaction.getTransactionItems());
 
         clearInvocations(mLifecycleManager);
-        mLifecycleManager.scheduleTransactionItem(mClient, mLifecycleItem);
+        mLifecycleManager.scheduleTransactionItem(mNonBinderClient, mLifecycleItem);
 
         verify(mLifecycleManager).scheduleTransaction(mTransactionCaptor.capture());
         transaction = mTransactionCaptor.getValue();
@@ -113,9 +137,54 @@
     }
 
     @Test
-    public void testScheduleTransactionAndLifecycleItems() throws RemoteException {
-        doNothing().when(mLifecycleManager).scheduleTransaction(any());
-        mLifecycleManager.scheduleTransactionAndLifecycleItems(mClient, mTransactionItem,
+    public void testScheduleTransactionItem() throws RemoteException {
+        mSetFlagsRule.enableFlags(FLAG_BUNDLE_CLIENT_TRANSACTION_FLAG);
+        spyOn(mWms.mWindowPlacerLocked);
+        doReturn(true).when(mWms.mWindowPlacerLocked).isTraversalScheduled();
+
+        // Use non binder client to get non-recycled ClientTransaction.
+        mLifecycleManager.scheduleTransactionItem(mNonBinderClient, mTransactionItem);
+
+        // When there is traversal scheduled, add transaction items to pending.
+        assertEquals(1, mLifecycleManager.mPendingTransactions.size());
+        ClientTransaction transaction =
+                mLifecycleManager.mPendingTransactions.get(mNonBinderClient);
+        assertEquals(1, transaction.getTransactionItems().size());
+        assertEquals(mTransactionItem, transaction.getTransactionItems().get(0));
+        assertNull(transaction.getCallbacks());
+        assertNull(transaction.getLifecycleStateRequest());
+        verify(mLifecycleManager, never()).scheduleTransaction(any());
+
+        // Add new transaction item to the existing pending.
+        clearInvocations(mLifecycleManager);
+        mLifecycleManager.scheduleTransactionItem(mNonBinderClient, mLifecycleItem);
+
+        assertEquals(1, mLifecycleManager.mPendingTransactions.size());
+        transaction = mLifecycleManager.mPendingTransactions.get(mNonBinderClient);
+        assertEquals(2, transaction.getTransactionItems().size());
+        assertEquals(mTransactionItem, transaction.getTransactionItems().get(0));
+        assertEquals(mLifecycleItem, transaction.getTransactionItems().get(1));
+        assertNull(transaction.getCallbacks());
+        assertNull(transaction.getLifecycleStateRequest());
+        verify(mLifecycleManager, never()).scheduleTransaction(any());
+    }
+
+    @Test
+    public void testScheduleTransactionItemUnlocked() throws RemoteException {
+        // Use non binder client to get non-recycled ClientTransaction.
+        mLifecycleManager.scheduleTransactionItemUnlocked(mNonBinderClient, mTransactionItem);
+
+        // Dispatch immediately.
+        assertTrue(mLifecycleManager.mPendingTransactions.isEmpty());
+        verify(mLifecycleManager).scheduleTransaction(any());
+    }
+
+    @Test
+    public void testScheduleTransactionAndLifecycleItems_notBundle() throws RemoteException {
+        mSetFlagsRule.disableFlags(FLAG_BUNDLE_CLIENT_TRANSACTION_FLAG);
+
+        // Use non binder client to get non-recycled ClientTransaction.
+        mLifecycleManager.scheduleTransactionAndLifecycleItems(mNonBinderClient, mTransactionItem,
                 mLifecycleItem);
 
         verify(mLifecycleManager).scheduleTransaction(mTransactionCaptor.capture());
@@ -124,4 +193,36 @@
         assertEquals(mTransactionItem, transaction.getCallbacks().get(0));
         assertEquals(mLifecycleItem, transaction.getLifecycleStateRequest());
     }
+
+    @Test
+    public void testScheduleTransactionAndLifecycleItems() throws RemoteException {
+        mSetFlagsRule.enableFlags(FLAG_BUNDLE_CLIENT_TRANSACTION_FLAG);
+        spyOn(mWms.mWindowPlacerLocked);
+        doReturn(true).when(mWms.mWindowPlacerLocked).isTraversalScheduled();
+
+        // Use non binder client to get non-recycled ClientTransaction.
+        mLifecycleManager.scheduleTransactionAndLifecycleItems(mNonBinderClient, mTransactionItem,
+                mLifecycleItem);
+
+        assertEquals(1, mLifecycleManager.mPendingTransactions.size());
+        final ClientTransaction transaction =
+                mLifecycleManager.mPendingTransactions.get(mNonBinderClient);
+        assertEquals(2, transaction.getTransactionItems().size());
+        assertEquals(mTransactionItem, transaction.getTransactionItems().get(0));
+        assertEquals(mLifecycleItem, transaction.getTransactionItems().get(1));
+        assertNull(transaction.getCallbacks());
+        assertNull(transaction.getLifecycleStateRequest());
+        verify(mLifecycleManager, never()).scheduleTransaction(any());
+    }
+
+    @Test
+    public void testDispatchPendingTransactions() throws RemoteException {
+        mLifecycleManager.mPendingTransactions.put(mClientBinder, mTransaction);
+
+        mLifecycleManager.dispatchPendingTransactions();
+
+        assertTrue(mLifecycleManager.mPendingTransactions.isEmpty());
+        verify(mTransaction).schedule();
+        verify(mTransaction).recycle();
+    }
 }
diff --git a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
index 71d2504..dfe79bf 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DisplayContentTests.java
@@ -993,7 +993,9 @@
         dc.getDisplayPolicy().getDecorInsetsInfo(ROTATION_0, dc.mBaseDisplayHeight,
                 dc.mBaseDisplayWidth).mConfigFrame.set(0, 0, 1000, 990);
         dc.computeScreenConfiguration(config, ROTATION_0);
+        dc.onRequestedOverrideConfigurationChanged(config);
         assertEquals(Configuration.ORIENTATION_LANDSCAPE, config.orientation);
+        assertEquals(Configuration.ORIENTATION_LANDSCAPE, dc.getNaturalOrientation());
     }
 
     @Test
diff --git a/services/tests/wmtests/src/com/android/server/wm/DragDropControllerTests.java b/services/tests/wmtests/src/com/android/server/wm/DragDropControllerTests.java
index 50fe042..1fb7cd8 100644
--- a/services/tests/wmtests/src/com/android/server/wm/DragDropControllerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/DragDropControllerTests.java
@@ -553,7 +553,7 @@
             assertTrue(mWm.mInputManager.transferTouchFocus(new InputChannel(),
                     new InputChannel(), true /* isDragDrop */));
             mToken = mTarget.performDrag(TEST_PID, 0, mWindow.mClient,
-                    flag, surface, 0, 0, 0, 0, 0, data);
+                    flag, surface, 0, 0, 0, 0, 0, 0, 0, data);
             assertNotNull(mToken);
 
             r.run();
@@ -575,7 +575,7 @@
 
     private void startA11yDrag(int flags, ClipData data, Runnable r) {
         mToken = mTarget.performDrag(0, 0, mWindow.mClient,
-                flags | View.DRAG_FLAG_ACCESSIBILITY_ACTION, null, 0, 0, 0, 0, 0, data);
+                flags | View.DRAG_FLAG_ACCESSIBILITY_ACTION, null, 0, 0, 0, 0, 0, 0, 0, data);
         assertNotNull(mToken);
         r.run();
     }
diff --git a/services/usage/java/com/android/server/usage/UsageStatsService.java b/services/usage/java/com/android/server/usage/UsageStatsService.java
index 3d2340c..72db7fe 100644
--- a/services/usage/java/com/android/server/usage/UsageStatsService.java
+++ b/services/usage/java/com/android/server/usage/UsageStatsService.java
@@ -2278,6 +2278,12 @@
                         "Only the system or holders of the REPORT_USAGE_STATS"
                             + " permission are allowed to call reportUserInteraction");
                 }
+                if (userId != UserHandle.getCallingUserId()) {
+                    // Cross-user event reporting.
+                    getContext().enforceCallingPermission(
+                            Manifest.permission.INTERACT_ACROSS_USERS_FULL,
+                            "Caller doesn't have INTERACT_ACROSS_USERS_FULL permission");
+                }
             } else {
                 if (!isCallingUidSystem()) {
                     throw new SecurityException("Only system is allowed to call"
@@ -2287,7 +2293,8 @@
 
             // Verify if this package exists before reporting an event for it.
             if (mPackageManagerInternal.getPackageUid(packageName, 0, userId) < 0) {
-                throw new IllegalArgumentException("Package " + packageName + "not exist!");
+                throw new IllegalArgumentException("Package " + packageName
+                        + " does not exist!");
             }
 
             final Event event = new Event(USER_INTERACTION, SystemClock.elapsedRealtime());
diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java
index b214591..c902d459 100644
--- a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java
+++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java
@@ -1569,13 +1569,13 @@
 
         @Override
         @EnforcePermission(android.Manifest.permission.MANAGE_HOTWORD_DETECTION)
-        public void setIsReceiveSandboxedTrainingDataAllowed(boolean allowed) {
-            super.setIsReceiveSandboxedTrainingDataAllowed_enforcePermission();
+        public void setShouldReceiveSandboxedTrainingData(boolean allowed) {
+            super.setShouldReceiveSandboxedTrainingData_enforcePermission();
 
             synchronized (this) {
                 if (mImpl == null) {
                     throw new IllegalStateException(
-                            "setIsReceiveSandboxedTrainingDataAllowed without running voice "
+                            "setShouldReceiveSandboxedTrainingData without running voice "
                                     + "interaction service");
                 }
 
@@ -2273,9 +2273,9 @@
 
         private boolean isCallerPreinstalledAssistant() {
             return mImpl != null
-                    && mImpl.mInfo.getServiceInfo().applicationInfo.uid == Binder.getCallingUid()
-                    && (mImpl.mInfo.getServiceInfo().applicationInfo.isSystemApp()
-                    || mImpl.mInfo.getServiceInfo().applicationInfo.isUpdatedSystemApp());
+                    && mImpl.getApplicationInfo().uid == Binder.getCallingUid()
+                    && (mImpl.getApplicationInfo().isSystemApp()
+                    || mImpl.getApplicationInfo().isUpdatedSystemApp());
         }
 
         private void setImplLocked(VoiceInteractionManagerServiceImpl impl) {
diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerServiceImpl.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerServiceImpl.java
index 3c4b58f..7e0cbad 100644
--- a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerServiceImpl.java
+++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerServiceImpl.java
@@ -40,6 +40,7 @@
 import android.content.Intent;
 import android.content.IntentFilter;
 import android.content.ServiceConnection;
+import android.content.pm.ApplicationInfo;
 import android.content.pm.PackageManager;
 import android.content.pm.PackageManagerInternal;
 import android.content.pm.ParceledListSlice;
@@ -540,6 +541,10 @@
         return mInfo.getSupportsLocalInteraction();
     }
 
+    public ApplicationInfo getApplicationInfo() {
+        return mInfo.getServiceInfo().applicationInfo;
+    }
+
     public void startListeningVisibleActivityChangedLocked(@NonNull IBinder token) {
         if (DEBUG) {
             Slog.d(TAG, "startListeningVisibleActivityChangedLocked: token=" + token);
diff --git a/telephony/java/android/telephony/CarrierRestrictionRules.java b/telephony/java/android/telephony/CarrierRestrictionRules.java
index eac4d16..cc768bc 100644
--- a/telephony/java/android/telephony/CarrierRestrictionRules.java
+++ b/telephony/java/android/telephony/CarrierRestrictionRules.java
@@ -16,12 +16,16 @@
 
 package android.telephony;
 
+import android.annotation.FlaggedApi;
 import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.SystemApi;
 import android.os.Parcel;
 import android.os.Parcelable;
 import android.service.carrier.CarrierIdentifier;
+import android.telephony.TelephonyManager.CarrierRestrictionStatus;
+
+import com.android.internal.telephony.flags.Flags;
 
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
@@ -104,7 +108,7 @@
     private int mCarrierRestrictionDefault;
     @MultiSimPolicy
     private int mMultiSimPolicy;
-    @TelephonyManager.CarrierRestrictionStatus
+    @CarrierRestrictionStatus
     private int mCarrierRestrictionStatus;
 
     private CarrierRestrictionRules() {
@@ -293,8 +297,22 @@
         return true;
     }
 
-    /** @hide */
-    public int getCarrierRestrictionStatus() {
+    /**
+     * Get the carrier restriction status of the device.
+     * The return value of the API is as follows.
+     * <ul>
+     *      <li>return {@link TelephonyManager#CARRIER_RESTRICTION_STATUS_RESTRICTED_TO_CALLER}
+     *      if the caller and the device locked by the network are same</li>
+     *      <li>return {@link TelephonyManager#CARRIER_RESTRICTION_STATUS_RESTRICTED} if the
+     *      caller and the device locked by the network are different</li>
+     *      <li>return {@link TelephonyManager#CARRIER_RESTRICTION_STATUS_NOT_RESTRICTED} if the
+     *      device is not locked</li>
+     *      <li>return {@link TelephonyManager#CARRIER_RESTRICTION_STATUS_UNKNOWN} if the device
+     *      locking state is unavailable or radio does not supports the feature</li>
+     * </ul>
+     */
+    @FlaggedApi(Flags.FLAG_CARRIER_RESTRICTION_STATUS)
+    public @CarrierRestrictionStatus int getCarrierRestrictionStatus() {
         return mCarrierRestrictionStatus;
     }
 
diff --git a/telephony/java/android/telephony/data/ApnSetting.java b/telephony/java/android/telephony/data/ApnSetting.java
index 3e87872..8679bd4 100644
--- a/telephony/java/android/telephony/data/ApnSetting.java
+++ b/telephony/java/android/telephony/data/ApnSetting.java
@@ -1866,7 +1866,7 @@
         private int mCarrierId = TelephonyManager.UNKNOWN_CARRIER_ID;
         private int mSkip464Xlat = Carriers.SKIP_464XLAT_DEFAULT;
         private boolean mAlwaysOn;
-        private int mInfrastructureBitmask = INFRASTRUCTURE_CELLULAR;
+        private int mInfrastructureBitmask = INFRASTRUCTURE_CELLULAR | INFRASTRUCTURE_SATELLITE;
         private boolean mEsimBootstrapProvisioning;
 
         /**
diff --git a/telephony/java/android/telephony/satellite/NtnSignalStrength.java b/telephony/java/android/telephony/satellite/NtnSignalStrength.java
index 16d7654..2fec423 100644
--- a/telephony/java/android/telephony/satellite/NtnSignalStrength.java
+++ b/telephony/java/android/telephony/satellite/NtnSignalStrength.java
@@ -86,6 +86,9 @@
         readFromParcel(in);
     }
 
+    /**
+     * Returns notified non-terrestrial network signal strength level.
+     */
     @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
     @NtnSignalStrengthLevel public int getLevel() {
         return mLevel;
diff --git a/telephony/java/android/telephony/satellite/SatelliteManager.java b/telephony/java/android/telephony/satellite/SatelliteManager.java
index 71786b3..e09bd20 100644
--- a/telephony/java/android/telephony/satellite/SatelliteManager.java
+++ b/telephony/java/android/telephony/satellite/SatelliteManager.java
@@ -34,6 +34,7 @@
 import android.os.OutcomeReceiver;
 import android.os.RemoteException;
 import android.os.ResultReceiver;
+import android.os.ServiceSpecificException;
 import android.telephony.SubscriptionManager;
 import android.telephony.TelephonyCallback;
 import android.telephony.TelephonyFrameworkInitializer;
@@ -1919,7 +1920,6 @@
      */
     @RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION)
     @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
-    @NonNull
     public void requestNtnSignalStrength(@NonNull @CallbackExecutor Executor executor,
             @NonNull OutcomeReceiver<NtnSignalStrength, SatelliteException> callback) {
         Objects.requireNonNull(executor);
@@ -1962,6 +1962,8 @@
 
     /**
      * Registers for NTN signal strength changed from satellite modem.
+     * If the registration operation is not successful, a {@link SatelliteException} that contains
+     * {@link SatelliteResult} will be thrown.
      *
      * <p>
      * Note: This API is specifically designed for OEM enabled satellite connectivity only.
@@ -1973,16 +1975,14 @@
      * @param executor The executor on which the callback will be called.
      * @param callback The callback to handle the NTN signal strength changed event.
      *
-     * @return The {@link SatelliteResult} result of the operation.
-     *
      * @throws SecurityException if the caller doesn't have required permission.
      * @throws IllegalStateException if the Telephony process is not currently available.
+     * @throws SatelliteException if the callback registration operation fails.
      */
     @RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION)
     @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
-    @SatelliteResult public int registerForNtnSignalStrengthChanged(
-            @NonNull @CallbackExecutor Executor executor,
-            @NonNull NtnSignalStrengthCallback callback) {
+    public void registerForNtnSignalStrengthChanged(@NonNull @CallbackExecutor Executor executor,
+            @NonNull NtnSignalStrengthCallback callback) throws SatelliteException {
         Objects.requireNonNull(executor);
         Objects.requireNonNull(callback);
 
@@ -1999,16 +1999,18 @@
                                                 ntnSignalStrength)));
                             }
                         };
+                telephony.registerForNtnSignalStrengthChanged(mSubId, internalCallback);
                 sNtnSignalStrengthCallbackMap.put(callback, internalCallback);
-                return telephony.registerForNtnSignalStrengthChanged(mSubId, internalCallback);
             } else {
                 throw new IllegalStateException("Telephony service is null.");
             }
+        } catch (ServiceSpecificException ex) {
+            logd("registerForNtnSignalStrengthChanged() registration fails: " + ex.errorCode);
+            throw new SatelliteException(ex.errorCode);
         } catch (RemoteException ex) {
             loge("registerForNtnSignalStrengthChanged() RemoteException: " + ex);
             ex.rethrowFromSystemServer();
         }
-        return SATELLITE_RESULT_REQUEST_FAILED;
     }
 
     /**
@@ -2025,6 +2027,8 @@
      * {@link #registerForNtnSignalStrengthChanged(Executor, NtnSignalStrengthCallback)}.
      *
      * @throws SecurityException if the caller doesn't have required permission.
+     * @throws IllegalArgumentException if the callback is not valid or has already been
+     * unregistered.
      * @throws IllegalStateException if the Telephony process is not currently available.
      */
     @RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION)
@@ -2041,6 +2045,7 @@
                     telephony.unregisterForNtnSignalStrengthChanged(mSubId, internalCallback);
                 } else {
                     loge("unregisterForNtnSignalStrengthChanged: No internal callback.");
+                    throw new IllegalArgumentException("callback is not valid");
                 }
             } else {
                 throw new IllegalStateException("Telephony service is null.");
diff --git a/telephony/java/com/android/internal/telephony/ITelephony.aidl b/telephony/java/com/android/internal/telephony/ITelephony.aidl
index d7d28a1..397fb2d 100644
--- a/telephony/java/com/android/internal/telephony/ITelephony.aidl
+++ b/telephony/java/com/android/internal/telephony/ITelephony.aidl
@@ -3101,16 +3101,21 @@
     void requestNtnSignalStrength(int subId, in ResultReceiver receiver);
 
     /**
-     * Registers for NTN signal strength changed from satellite modem.
+     * Registers for NTN signal strength changed from satellite modem. If the registration operation
+     * is not successful, a {@link SatelliteException} that contains {@link SatelliteResult} will be
+     * thrown.
      *
      * @param subId The subId of the subscription to request for.
-     * @param callback The callback to handle the NTN signal strength changed event.
-     *
-     * @return The {@link SatelliteResult} result of the operation.
+     * @param callback The callback to handle the NTN signal strength changed event. If the
+     * operation is successful, {@link NtnSignalStrengthCallback#onNtnSignalStrengthChanged(
+     * NtnSignalStrength)} will return an instance of {@link NtnSignalStrength} with a value of
+     * {@link NtnSignalStrength.NtnSignalStrengthLevel} when the signal strength of non-terrestrial
+     * network has changed.
      */
     @JavaPassthrough(annotation="@android.annotation.RequiresPermission("
             + "android.Manifest.permission.SATELLITE_COMMUNICATION)")
-    int registerForNtnSignalStrengthChanged(int subId, in INtnSignalStrengthCallback callback);
+    void registerForNtnSignalStrengthChanged(int subId,
+            in INtnSignalStrengthCallback callback);
 
     /**
      * Unregisters for NTN signal strength changed from satellite modem.
diff --git a/tests/FlickerTests/ActivityEmbedding/src/com/android/server/wm/flicker/activityembedding/pip/SecondaryActivityEnterPipTest.kt b/tests/FlickerTests/ActivityEmbedding/src/com/android/server/wm/flicker/activityembedding/pip/SecondaryActivityEnterPipTest.kt
index 12a57d5..c8cac8f 100644
--- a/tests/FlickerTests/ActivityEmbedding/src/com/android/server/wm/flicker/activityembedding/pip/SecondaryActivityEnterPipTest.kt
+++ b/tests/FlickerTests/ActivityEmbedding/src/com/android/server/wm/flicker/activityembedding/pip/SecondaryActivityEnterPipTest.kt
@@ -25,6 +25,7 @@
 import android.tools.device.flicker.legacy.FlickerBuilder
 import android.tools.device.flicker.legacy.LegacyFlickerTest
 import android.tools.device.flicker.legacy.LegacyFlickerTestFactory
+import androidx.test.filters.FlakyTest
 import androidx.test.filters.RequiresDevice
 import com.android.server.wm.flicker.activityembedding.ActivityEmbeddingTestBase
 import com.android.server.wm.flicker.helpers.ActivityEmbeddingAppHelper
@@ -142,7 +143,7 @@
     }
 
     /** During the transition Secondary Activity shrinks to the bottom right corner. */
-    @Presubmit
+    @FlakyTest(bugId = 315605409)
     @Test
     fun secondaryLayerShrinks() {
         flicker.assertLayers {
diff --git a/tools/aapt2/integration-tests/SymlinkTest/Android.bp b/tools/aapt2/integration-tests/SymlinkTest/Android.bp
index 15a6a20..6fcdf1c 100644
--- a/tools/aapt2/integration-tests/SymlinkTest/Android.bp
+++ b/tools/aapt2/integration-tests/SymlinkTest/Android.bp
@@ -27,4 +27,7 @@
     name: "AaptSymlinkTest",
     sdk_version: "current",
     use_resource_processor: false,
+    compile_data: [
+        "targets/*",
+    ],
 }
diff --git a/tools/hoststubgen/hoststubgen/helper-framework-runtime-src/framework/com/android/hoststubgen/nativesubstitution/LongArrayMultiStateCounter_host.java b/tools/hoststubgen/hoststubgen/helper-framework-runtime-src/framework/com/android/hoststubgen/nativesubstitution/LongArrayMultiStateCounter_host.java
index 068dfe8..a135623 100644
--- a/tools/hoststubgen/hoststubgen/helper-framework-runtime-src/framework/com/android/hoststubgen/nativesubstitution/LongArrayMultiStateCounter_host.java
+++ b/tools/hoststubgen/hoststubgen/helper-framework-runtime-src/framework/com/android/hoststubgen/nativesubstitution/LongArrayMultiStateCounter_host.java
@@ -100,6 +100,10 @@
             mLastStateChangeTimestampMs = timestampMs;
         }
 
+        public void setValue(int state, long[] values) {
+            System.arraycopy(values, 0, mStates[state].mCounter, 0, mArrayLength);
+        }
+
         public void updateValue(long[] values, long timestampMs) {
             if (mEnabled || mLastUpdateTimestampMs < mLastStateChangeTimestampMs) {
                 if (timestampMs < mLastStateChangeTimestampMs) {
@@ -306,6 +310,11 @@
         return getInstance(instanceId).mArrayLength;
     }
 
+    public static void native_setValues(long instanceId, int state, long containerInstanceId) {
+        getInstance(instanceId).setValue(state,
+                LongArrayContainer_host.getInstance(containerInstanceId));
+    }
+
     public static void native_updateValues(long instanceId, long containerInstanceId,
             long timestampMs) {
         getInstance(instanceId).updateValue(
diff --git a/tools/hoststubgen/hoststubgen/helper-framework-runtime-src/framework/com/android/hoststubgen/nativesubstitution/Parcel_host.java b/tools/hoststubgen/hoststubgen/helper-framework-runtime-src/framework/com/android/hoststubgen/nativesubstitution/Parcel_host.java
index 3bcabcb..d63bff6 100644
--- a/tools/hoststubgen/hoststubgen/helper-framework-runtime-src/framework/com/android/hoststubgen/nativesubstitution/Parcel_host.java
+++ b/tools/hoststubgen/hoststubgen/helper-framework-runtime-src/framework/com/android/hoststubgen/nativesubstitution/Parcel_host.java
@@ -343,6 +343,28 @@
         p.mPos += length;
         p.updateSize();
     }
+    public static int nativeCompareData(long thisNativePtr, long otherNativePtr) {
+        var a = getInstance(thisNativePtr);
+        var b = getInstance(otherNativePtr);
+        if ((a.mSize == b.mSize) && Arrays.equals(a.mBuffer, b.mBuffer)) {
+            return 0;
+        } else {
+            return -1;
+        }
+    }
+    public static boolean nativeCompareDataInRange(
+            long ptrA, int offsetA, long ptrB, int offsetB, int length) {
+        var a = getInstance(ptrA);
+        var b = getInstance(ptrB);
+        if (offsetA < 0 || offsetA + length > a.mSize) {
+            throw new IllegalArgumentException();
+        }
+        if (offsetB < 0 || offsetB + length > b.mSize) {
+            throw new IllegalArgumentException();
+        }
+        return Arrays.equals(Arrays.copyOfRange(a.mBuffer, offsetA, offsetA + length),
+                Arrays.copyOfRange(b.mBuffer, offsetB, offsetB + length));
+    }
     public static void nativeAppendFrom(
             long thisNativePtr, long otherNativePtr, int srcOffset, int length) {
         var dst = getInstance(thisNativePtr);