Merge "Revert "Remove SSG timeout when SSG is complete"" into main
diff --git a/AconfigFlags.bp b/AconfigFlags.bp
index 605ce22..6d74a84 100644
--- a/AconfigFlags.bp
+++ b/AconfigFlags.bp
@@ -16,6 +16,7 @@
     ":android.app.usage.flags-aconfig-java{.generated_srcjars}",
     ":android.companion.flags-aconfig-java{.generated_srcjars}",
     ":android.content.pm.flags-aconfig-java{.generated_srcjars}",
+    ":android.content.res.flags-aconfig-java{.generated_srcjars}",
     ":android.hardware.radio.flags-aconfig-java{.generated_srcjars}",
     ":android.nfc.flags-aconfig-java{.generated_srcjars}",
     ":android.os.flags-aconfig-java{.generated_srcjars}",
@@ -306,6 +307,19 @@
     defaults: ["framework-minus-apex-aconfig-java-defaults"],
 }
 
+// Resources
+aconfig_declarations {
+    name: "android.content.res.flags-aconfig",
+    package: "android.content.res",
+    srcs: ["core/java/android/content/res/*.aconfig"],
+}
+
+java_aconfig_library {
+    name: "android.content.res.flags-aconfig-java",
+    aconfig_declarations: "android.content.res.flags-aconfig",
+    defaults: ["framework-minus-apex-aconfig-java-defaults"],
+}
+
 // Media BetterTogether
 aconfig_declarations {
     name: "com.android.media.flags.bettertogether-aconfig",
diff --git a/Android.bp b/Android.bp
index a507465a..0c199a6 100644
--- a/Android.bp
+++ b/Android.bp
@@ -227,7 +227,6 @@
         "android.hardware.radio.messaging-V3-java",
         "android.hardware.radio.modem-V3-java",
         "android.hardware.radio.network-V3-java",
-        "android.hardware.radio.satellite-V1-java",
         "android.hardware.radio.sim-V3-java",
         "android.hardware.radio.voice-V3-java",
         "android.hardware.thermal-V1.0-java-constants",
diff --git a/api/ApiDocs.bp b/api/ApiDocs.bp
index 7142eb5..e162100 100644
--- a/api/ApiDocs.bp
+++ b/api/ApiDocs.bp
@@ -131,13 +131,7 @@
     defaults: ["framework-doc-stubs-sources-default"],
     args: metalava_framework_docs_args +
         " --show-annotation android.annotation.SystemApi\\(client=android.annotation.SystemApi.Client.PRIVILEGED_APPS\\) ",
-    api_levels_annotations_enabled: true,
-    api_levels_annotations_dirs: [
-        "sdk-dir",
-        "api-versions-jars-dir",
-    ],
-    api_levels_sdk_type: "system",
-    extensions_info_file: ":sdk-extensions-info",
+    api_levels_module: "api_versions_system",
 }
 
 /////////////////////////////////////////////////////////////////////
diff --git a/core/api/current.txt b/core/api/current.txt
index f6564ec..66aeb0f 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -10571,7 +10571,7 @@
   public final class ContextParams {
     method @Nullable public String getAttributionTag();
     method @Nullable public android.content.AttributionSource getNextAttributionSource();
-    method @NonNull public boolean shouldRegisterAttributionSource();
+    method @FlaggedApi("android.permission.flags.should_register_attribution_source") @NonNull public boolean shouldRegisterAttributionSource();
   }
 
   public static final class ContextParams.Builder {
@@ -10580,7 +10580,7 @@
     method @NonNull public android.content.ContextParams build();
     method @NonNull public android.content.ContextParams.Builder setAttributionTag(@Nullable String);
     method @NonNull public android.content.ContextParams.Builder setNextAttributionSource(@Nullable android.content.AttributionSource);
-    method @NonNull public android.content.ContextParams.Builder setShouldRegisterAttributionSource(boolean);
+    method @FlaggedApi("android.permission.flags.should_register_attribution_source") @NonNull public android.content.ContextParams.Builder setShouldRegisterAttributionSource(boolean);
   }
 
   public class ContextWrapper extends android.content.Context {
diff --git a/core/api/system-current.txt b/core/api/system-current.txt
index c72d09d..5d1f6dc 100644
--- a/core/api/system-current.txt
+++ b/core/api/system-current.txt
@@ -312,7 +312,7 @@
     field public static final String REMOVE_DRM_CERTIFICATES = "android.permission.REMOVE_DRM_CERTIFICATES";
     field public static final String REMOVE_TASKS = "android.permission.REMOVE_TASKS";
     field public static final String RENOUNCE_PERMISSIONS = "android.permission.RENOUNCE_PERMISSIONS";
-    field @FlaggedApi("backstage_power.report_usage_stats_permission") public static final String REPORT_USAGE_STATS = "android.permission.REPORT_USAGE_STATS";
+    field @FlaggedApi("android.app.usage.report_usage_stats_permission") public static final String REPORT_USAGE_STATS = "android.permission.REPORT_USAGE_STATS";
     field @Deprecated public static final String REQUEST_NETWORK_SCORES = "android.permission.REQUEST_NETWORK_SCORES";
     field public static final String REQUEST_NOTIFICATION_ASSISTANT_SERVICE = "android.permission.REQUEST_NOTIFICATION_ASSISTANT_SERVICE";
     field public static final String RESET_PASSWORD = "android.permission.RESET_PASSWORD";
@@ -16741,6 +16741,23 @@
     field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") @NonNull public static final android.os.Parcelable.Creator<android.telephony.satellite.AntennaPosition> CREATOR;
   }
 
+  @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public final class NtnSignalStrength implements android.os.Parcelable {
+    ctor @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public NtnSignalStrength(@Nullable android.telephony.satellite.NtnSignalStrength);
+    method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public int describeContents();
+    method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public int getLevel();
+    method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public void writeToParcel(@NonNull android.os.Parcel, int);
+    field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") @NonNull public static final android.os.Parcelable.Creator<android.telephony.satellite.NtnSignalStrength> CREATOR;
+    field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int NTN_SIGNAL_STRENGTH_GOOD = 3; // 0x3
+    field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int NTN_SIGNAL_STRENGTH_GREAT = 4; // 0x4
+    field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int NTN_SIGNAL_STRENGTH_MODERATE = 2; // 0x2
+    field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int NTN_SIGNAL_STRENGTH_NONE = 0; // 0x0
+    field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int NTN_SIGNAL_STRENGTH_POOR = 1; // 0x1
+  }
+
+  @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public interface NtnSignalStrengthCallback {
+    method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public void onNtnSignalStrengthChanged(@NonNull android.telephony.satellite.NtnSignalStrength);
+  }
+
   @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public final class PointingInfo implements android.os.Parcelable {
     method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public int describeContents();
     method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public float getSatelliteAzimuthDegrees();
@@ -16776,6 +16793,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 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);
     method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public int registerForSatelliteProvisionStateChanged(@NonNull java.util.concurrent.Executor, @NonNull android.telephony.satellite.SatelliteProvisionStateCallback);
@@ -16786,6 +16804,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.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>);
@@ -16794,6 +16813,7 @@
     method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void setDeviceAlignedWithSatellite(boolean);
     method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void startSatelliteTransmissionUpdates(@NonNull java.util.concurrent.Executor, @NonNull java.util.function.Consumer<java.lang.Integer>, @NonNull android.telephony.satellite.SatelliteTransmissionUpdateCallback);
     method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void stopSatelliteTransmissionUpdates(@NonNull android.telephony.satellite.SatelliteTransmissionUpdateCallback, @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 unregisterForNtnSignalStrengthChanged(@NonNull android.telephony.satellite.NtnSignalStrengthCallback);
     method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void unregisterForSatelliteDatagram(@NonNull android.telephony.satellite.SatelliteDatagramCallback);
     method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void unregisterForSatelliteModemStateChanged(@NonNull android.telephony.satellite.SatelliteStateCallback);
     method @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") @RequiresPermission(android.Manifest.permission.SATELLITE_COMMUNICATION) public void unregisterForSatelliteProvisionStateChanged(@NonNull android.telephony.satellite.SatelliteProvisionStateCallback);
diff --git a/core/api/test-current.txt b/core/api/test-current.txt
index acac4c20..b905287 100644
--- a/core/api/test-current.txt
+++ b/core/api/test-current.txt
@@ -3185,7 +3185,6 @@
     field public static final int HAL_SERVICE_MESSAGING = 2; // 0x2
     field public static final int HAL_SERVICE_MODEM = 3; // 0x3
     field public static final int HAL_SERVICE_NETWORK = 4; // 0x4
-    field @FlaggedApi("com.android.internal.telephony.flags.oem_enabled_satellite_flag") public static final int HAL_SERVICE_SATELLITE = 8; // 0x8
     field public static final int HAL_SERVICE_SIM = 5; // 0x5
     field public static final int HAL_SERVICE_VOICE = 6; // 0x6
     field public static final android.util.Pair HAL_VERSION_UNKNOWN;
diff --git a/core/java/android/app/usage/flags.aconfig b/core/java/android/app/usage/flags.aconfig
index d1f9067..f401a76 100644
--- a/core/java/android/app/usage/flags.aconfig
+++ b/core/java/android/app/usage/flags.aconfig
@@ -2,7 +2,7 @@
 
 flag {
     name: "user_interaction_type_api"
-    namespace: "power_optimization"
+    namespace: "backstage_power"
     description: "Feature flag for user interaction event report/query API"
     bug: "296061232"
 }
diff --git a/core/java/android/content/ContextParams.java b/core/java/android/content/ContextParams.java
index 988a9c0..b844d35 100644
--- a/core/java/android/content/ContextParams.java
+++ b/core/java/android/content/ContextParams.java
@@ -16,7 +16,10 @@
 
 package android.content;
 
+import static android.permission.flags.Flags.FLAG_SHOULD_REGISTER_ATTRIBUTION_SOURCE;
+
 import android.Manifest;
+import android.annotation.FlaggedApi;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.RequiresPermission;
@@ -102,6 +105,7 @@
      * registered.
      */
     @NonNull
+    @FlaggedApi(FLAG_SHOULD_REGISTER_ATTRIBUTION_SOURCE)
     public boolean shouldRegisterAttributionSource() {
         return mShouldRegisterAttributionSource;
     }
@@ -179,6 +183,7 @@
          *                       created should be registered.
          */
         @NonNull
+        @FlaggedApi(FLAG_SHOULD_REGISTER_ATTRIBUTION_SOURCE)
         public Builder setShouldRegisterAttributionSource(boolean shouldRegister) {
             mShouldRegisterAttributionSource = shouldRegister;
             return this;
diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java
index d2b2308..b765562 100644
--- a/core/java/android/content/Intent.java
+++ b/core/java/android/content/Intent.java
@@ -3929,6 +3929,8 @@
      * {@link #ACTION_BOOT_COMPLETED} is sent.  This is sent as a foreground
      * broadcast, since it is part of a visible user interaction; be as quick
      * as possible when handling it.
+     *
+     * <p><b>Note:</b> This broadcast is not sent to the system user.
      */
     public static final String ACTION_USER_INITIALIZE =
             "android.intent.action.USER_INITIALIZE";
diff --git a/core/java/android/content/pm/ArchivedActivityParcel.aidl b/core/java/android/content/pm/ArchivedActivityParcel.aidl
index 7ab7ed1..74953ff 100644
--- a/core/java/android/content/pm/ArchivedActivityParcel.aidl
+++ b/core/java/android/content/pm/ArchivedActivityParcel.aidl
@@ -16,9 +16,12 @@
 
 package android.content.pm;
 
+import android.content.ComponentName;
+
 /** @hide */
 parcelable ArchivedActivityParcel {
     String title;
+    ComponentName originalComponentName;
     // PNG compressed bitmaps.
     byte[] iconBitmap;
     byte[] monochromeIconBitmap;
diff --git a/core/java/android/content/pm/flags.aconfig b/core/java/android/content/pm/flags.aconfig
index b2cc070..db12728 100644
--- a/core/java/android/content/pm/flags.aconfig
+++ b/core/java/android/content/pm/flags.aconfig
@@ -50,3 +50,11 @@
     description: "Feature flag to enable the features that rely on new ART Service APIs that are in the VIC version of the ART module."
     bug: "304741685"
 }
+
+flag {
+    name: "sdk_lib_independence"
+    namespace: "package_manager_service"
+    description: "Feature flag to keep app working even if its declared sdk-library dependency is unavailable."
+    bug: "295827951"
+    is_fixed_read_only: true
+}
diff --git a/core/java/android/content/res/flags.aconfig b/core/java/android/content/res/flags.aconfig
new file mode 100644
index 0000000..0c2c0f4
--- /dev/null
+++ b/core/java/android/content/res/flags.aconfig
@@ -0,0 +1,10 @@
+package: "android.content.res"
+
+flag {
+    name: "default_locale"
+    namespace: "resource_manager"
+    description: "Feature flag for default locale in LocaleConfig"
+    bug: "117306409"
+    # fixed_read_only or device wont boot because of permission issues accessing flags during boot
+    is_fixed_read_only: true
+}
diff --git a/core/java/android/hardware/SensorManager.java b/core/java/android/hardware/SensorManager.java
index f033f97..bcf447b 100644
--- a/core/java/android/hardware/SensorManager.java
+++ b/core/java/android/hardware/SensorManager.java
@@ -16,6 +16,7 @@
 
 package android.hardware;
 
+import android.annotation.IntDef;
 import android.annotation.Nullable;
 import android.annotation.SystemApi;
 import android.annotation.SystemService;
@@ -27,6 +28,8 @@
 import android.util.Log;
 import android.util.SparseArray;
 
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.List;
@@ -1809,6 +1812,41 @@
     protected abstract boolean cancelTriggerSensorImpl(TriggerEventListener listener,
             Sensor sensor, boolean disable);
 
+    /**
+     * @hide
+     */
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef({DATA_INJECTION, REPLAY_DATA_INJECTION, HAL_BYPASS_REPLAY_DATA_INJECTION})
+    public @interface DataInjectionMode {}
+    /**
+     * This mode is only used for testing purposes. Not all HALs support this mode. In this mode,
+     * the HAL ignores the sensor data provided by physical sensors and accepts the data that is
+     * injected from the SensorService as if it were the real sensor data. This mode is primarily
+     * used for testing various algorithms like vendor provided SensorFusion, Step Counter and
+     * Step Detector etc. Typically, in this mode, there is a client app which injects
+     * sensor data into the HAL. Normal apps can register and unregister for any sensor
+     * that supports injection. Registering to sensors that do not support injection will
+     * give an error.
+     * This is the default data injection mode.
+     * @hide
+     */
+    public static final int DATA_INJECTION = 1;
+    /**
+     * Mostly equivalent to DATA_INJECTION with the difference being that the injected data is
+     * delivered to all requesting apps rather than just the package allowed to inject data.
+     * This mode is only allowed to be used on development builds.
+     * @hide
+     */
+    public static final int REPLAY_DATA_INJECTION = 3;
+    /**
+     * Like REPLAY_DATA_INJECTION but injected data is not sent into the HAL. It is stored in a
+     * buffer in the platform and played back to all requesting apps.
+     * This is useful for playing back sensor data to test platform components without
+     * relying on the HAL to support data injection.
+     * @hide
+     */
+    public static final int HAL_BYPASS_REPLAY_DATA_INJECTION = 4;
+
 
     /**
      * For testing purposes only. Not for third party applications.
@@ -1833,13 +1871,47 @@
      */
     @SystemApi
     public boolean initDataInjection(boolean enable) {
-        return initDataInjectionImpl(enable);
+        return initDataInjectionImpl(enable, DATA_INJECTION);
+    }
+
+    /**
+     * For testing purposes only. Not for third party applications.
+     *
+     * Initialize data injection mode and create a client for data injection. SensorService should
+     * already be operating in one of DATA_INJECTION, REPLAY_DATA_INJECTION or
+     * HAL_BYPASS_REPLAY_DATA_INJECTION modes for this call succeed. To set SensorService in
+     * a Data Injection mode, use one of:
+     *
+     * <ul>
+     *      <li>adb shell dumpsys sensorservice data_injection</li>
+     *      <li>adb shell dumpsys sensorservice replay_data_injection package_name</li>
+     *      <li>adb shell dumpsys sensorservice hal_bypass_replay_data_injection package_name</li>
+     * </ul>
+     *
+     * Typically this is done using a host side test.  This mode is expected to be used
+     * only for testing purposes. See {@link DataInjectionMode} for details of each data injection
+     * mode. Once this method succeeds, the test can call
+     * {@link #injectSensorData(Sensor, float[], int, long)} to inject sensor data into the HAL.
+     * To put SensorService back into normal mode, use "adb shell dumpsys sensorservice enable"
+     *
+     * @param enable True to initialize a client in a data injection mode.
+     *               False to clean up the native resources.
+     *
+     * @param mode One of DATA_INJECTION, REPLAY_DATA_INJECTION or HAL_BYPASS_DATA_INJECTION.
+     *             See {@link DataInjectionMode} for details.
+     *
+     * @return true if the HAL supports data injection and false
+     *         otherwise.
+     * @hide
+     */
+    public boolean initDataInjection(boolean enable, @DataInjectionMode int mode) {
+        return initDataInjectionImpl(enable, mode);
     }
 
     /**
      * @hide
      */
-    protected abstract boolean initDataInjectionImpl(boolean enable);
+    protected abstract boolean initDataInjectionImpl(boolean enable, @DataInjectionMode int mode);
 
     /**
      * For testing purposes only. Not for third party applications.
@@ -1871,9 +1943,6 @@
         if (sensor == null) {
             throw new IllegalArgumentException("sensor cannot be null");
         }
-        if (!sensor.isDataInjectionSupported()) {
-            throw new IllegalArgumentException("sensor does not support data injection");
-        }
         if (values == null) {
             throw new IllegalArgumentException("sensor data cannot be null");
         }
diff --git a/core/java/android/hardware/SystemSensorManager.java b/core/java/android/hardware/SystemSensorManager.java
index dfd3802..40e03db 100644
--- a/core/java/android/hardware/SystemSensorManager.java
+++ b/core/java/android/hardware/SystemSensorManager.java
@@ -90,6 +90,8 @@
     private static native void nativeGetRuntimeSensors(
             long nativeInstance, int deviceId, List<Sensor> list);
     private static native boolean nativeIsDataInjectionEnabled(long nativeInstance);
+    private static native boolean nativeIsReplayDataInjectionEnabled(long nativeInstance);
+    private static native boolean nativeIsHalBypassReplayDataInjectionEnabled(long nativeInstance);
 
     private static native int nativeCreateDirectChannel(
             long nativeInstance, int deviceId, long size, int channelType, int fd,
@@ -384,20 +386,41 @@
         }
     }
 
-    protected boolean initDataInjectionImpl(boolean enable) {
+    protected boolean initDataInjectionImpl(boolean enable, @DataInjectionMode int mode) {
         synchronized (sLock) {
+            boolean isDataInjectionModeEnabled = false;
             if (enable) {
-                boolean isDataInjectionModeEnabled = nativeIsDataInjectionEnabled(mNativeInstance);
+                switch (mode) {
+                    case DATA_INJECTION:
+                        isDataInjectionModeEnabled = nativeIsDataInjectionEnabled(mNativeInstance);
+                        break;
+                    case REPLAY_DATA_INJECTION:
+                        isDataInjectionModeEnabled = nativeIsReplayDataInjectionEnabled(
+                                mNativeInstance);
+                        break;
+                    case HAL_BYPASS_REPLAY_DATA_INJECTION:
+                        isDataInjectionModeEnabled = nativeIsHalBypassReplayDataInjectionEnabled(
+                                mNativeInstance);
+                        break;
+                    default:
+                        break;
+                }
                 // The HAL does not support injection OR SensorService hasn't been set in DI mode.
                 if (!isDataInjectionModeEnabled) {
-                    Log.e(TAG, "Data Injection mode not enabled");
+                    Log.e(TAG, "The correct Data Injection mode has not been enabled");
                     return false;
                 }
+                if (sInjectEventQueue != null && sInjectEventQueue.getDataInjectionMode() != mode) {
+                    // The inject event queue has been initialized for a different type of DI
+                    // close it and create a new one
+                    sInjectEventQueue.dispose();
+                    sInjectEventQueue = null;
+                }
                 // Initialize a client for data_injection.
                 if (sInjectEventQueue == null) {
                     try {
                         sInjectEventQueue = new InjectEventQueue(
-                                mMainLooper, this, mContext.getPackageName());
+                                mMainLooper, this, mode, mContext.getPackageName());
                     } catch (RuntimeException e) {
                         Log.e(TAG, "Cannot create InjectEventQueue: " + e);
                     }
@@ -421,6 +444,12 @@
                 Log.e(TAG, "Data injection mode not activated before calling injectSensorData");
                 return false;
             }
+            if (sInjectEventQueue.getDataInjectionMode() != HAL_BYPASS_REPLAY_DATA_INJECTION
+                    && !sensor.isDataInjectionSupported()) {
+                // DI mode and Replay DI mode require support from the sensor HAL
+                // HAL Bypass mode doesn't require this.
+                throw new IllegalArgumentException("sensor does not support data injection");
+            }
             int ret = sInjectEventQueue.injectSensorData(sensor.getHandle(), values, accuracy,
                                                          timestamp);
             // If there are any errors in data injection clean up the native resources.
@@ -825,6 +854,8 @@
 
         protected static final int OPERATING_MODE_NORMAL = 0;
         protected static final int OPERATING_MODE_DATA_INJECTION = 1;
+        protected static final int OPERATING_MODE_REPLAY_DATA_INJECTION = 3;
+        protected static final int OPERATING_MODE_HAL_BYPASS_REPLAY_DATA_INJECTION = 4;
 
         BaseEventQueue(Looper looper, SystemSensorManager manager, int mode, String packageName) {
             if (packageName == null) packageName = "";
@@ -1134,8 +1165,12 @@
     }
 
     final class InjectEventQueue extends BaseEventQueue {
-        public InjectEventQueue(Looper looper, SystemSensorManager manager, String packageName) {
-            super(looper, manager, OPERATING_MODE_DATA_INJECTION, packageName);
+
+        private int mMode;
+        public InjectEventQueue(Looper looper, SystemSensorManager manager,
+                @DataInjectionMode int mode, String packageName) {
+            super(looper, manager, mode, packageName);
+            mMode = mode;
         }
 
         int injectSensorData(int handle, float[] values, int accuracy, long timestamp) {
@@ -1161,6 +1196,10 @@
         protected void removeSensorEvent(Sensor sensor) {
 
         }
+
+        int getDataInjectionMode() {
+            return mMode;
+        }
     }
 
     protected boolean setOperationParameterImpl(SensorAdditionalInfo parameter) {
diff --git a/core/java/android/hardware/face/FaceManager.java b/core/java/android/hardware/face/FaceManager.java
index 0221296..02304b5 100644
--- a/core/java/android/hardware/face/FaceManager.java
+++ b/core/java/android/hardware/face/FaceManager.java
@@ -1074,6 +1074,14 @@
      */
     public interface FaceDetectionCallback {
         void onFaceDetected(int sensorId, int userId, boolean isStrongBiometric);
+
+        /**
+         * An error has occurred with face detection.
+         *
+         * This callback signifies that this operation has been completed, and
+         * no more callbacks should be expected.
+         */
+        default void onDetectionError(int errorMsgId) {}
     }
 
     /**
@@ -1373,6 +1381,9 @@
         } else if (mRemovalCallback != null) {
             mRemovalCallback.onRemovalError(mRemovalFace, clientErrMsgId,
                     getErrorString(mContext, errMsgId, vendorCode));
+        } else if (mFaceDetectionCallback != null) {
+            mFaceDetectionCallback.onDetectionError(errMsgId);
+            mFaceDetectionCallback = null;
         }
     }
 
diff --git a/core/java/android/hardware/fingerprint/FingerprintManager.java b/core/java/android/hardware/fingerprint/FingerprintManager.java
index 5bfda70..935157a 100644
--- a/core/java/android/hardware/fingerprint/FingerprintManager.java
+++ b/core/java/android/hardware/fingerprint/FingerprintManager.java
@@ -462,6 +462,14 @@
          * Invoked when a fingerprint has been detected.
          */
         void onFingerprintDetected(int sensorId, int userId, boolean isStrongBiometric);
+
+        /**
+         * An error has occurred with fingerprint detection.
+         *
+         * This callback signifies that this operation has been completed, and
+         * no more callbacks should be expected.
+         */
+        default void onDetectionError(int errorMsgId) {}
     }
 
     /**
@@ -1484,6 +1492,9 @@
                     ? mRemoveTracker.mSingleFingerprint : null;
             mRemovalCallback.onRemovalError(fp, clientErrMsgId,
                     getErrorString(mContext, errMsgId, vendorCode));
+        } else if (mFingerprintDetectionCallback != null) {
+            mFingerprintDetectionCallback.onDetectionError(errMsgId);
+            mFingerprintDetectionCallback = null;
         }
     }
 
diff --git a/core/java/android/hardware/input/InputDeviceSensorManager.java b/core/java/android/hardware/input/InputDeviceSensorManager.java
index aa55e54..05024ea 100644
--- a/core/java/android/hardware/input/InputDeviceSensorManager.java
+++ b/core/java/android/hardware/input/InputDeviceSensorManager.java
@@ -644,7 +644,7 @@
         }
 
         @Override
-        protected boolean initDataInjectionImpl(boolean enable) {
+        protected boolean initDataInjectionImpl(boolean enable, int mode) {
             return false;
         }
 
diff --git a/core/java/android/os/flags.aconfig b/core/java/android/os/flags.aconfig
index 7fceda4..b7f2e06 100644
--- a/core/java/android/os/flags.aconfig
+++ b/core/java/android/os/flags.aconfig
@@ -16,7 +16,7 @@
 
 flag {
     name: "remove_app_profiler_pss_collection"
-    namespace: "power_optimization"
+    namespace: "backstage_power"
     description: "Replaces background PSS collection in AppProfiler with RSS"
     bug: "297542292"
 }
diff --git a/core/java/android/permission/flags.aconfig b/core/java/android/permission/flags.aconfig
index d60d4c6..3f06a91 100644
--- a/core/java/android/permission/flags.aconfig
+++ b/core/java/android/permission/flags.aconfig
@@ -28,3 +28,10 @@
   description: "enable AttributionSource.setNextAttributionSource"
   bug: "304478648"
 }
+
+flag {
+    name: "should_register_attribution_source"
+    namespace: "permissions"
+    description: "enable the shouldRegisterAttributionSource API"
+    bug: "305057691"
+}
diff --git a/core/java/android/service/voice/VoiceInteractionService.java b/core/java/android/service/voice/VoiceInteractionService.java
index 3f41c56..d280621 100644
--- a/core/java/android/service/voice/VoiceInteractionService.java
+++ b/core/java/android/service/voice/VoiceInteractionService.java
@@ -520,7 +520,7 @@
             @NonNull String keyphrase, @SuppressLint("UseIcu") @NonNull Locale locale,
             @NonNull @CallbackExecutor Executor executor,
             @NonNull AlwaysOnHotwordDetector.Callback callback) {
-        // TODO (b/269080850): Resolve AndroidFrameworkRequiresPermission lint warning
+        // TODO(b/269080850): Resolve AndroidFrameworkRequiresPermission lint warning
 
         Objects.requireNonNull(keyphrase);
         Objects.requireNonNull(locale);
@@ -546,6 +546,10 @@
             @NonNull SoundTrigger.ModuleProperties moduleProperties,
             @NonNull @CallbackExecutor Executor executor,
             @NonNull AlwaysOnHotwordDetector.Callback callback) {
+        // TODO(b/305787465): Remove the MANAGE_HOTWORD_DETECTION permission enforcement on the
+        // {@link #createAlwaysOnHotwordDetectorForTest(String, Locale,
+        // SoundTrigger.ModuleProperties, AlwaysOnHotwordDetector.Callback)} and replace with the
+        // permission RECEIVE_SANDBOX_TRIGGER_AUDIO when it is fully launched.
 
         Objects.requireNonNull(keyphrase);
         Objects.requireNonNull(locale);
@@ -612,6 +616,11 @@
             @Nullable PersistableBundle options,
             @Nullable SharedMemory sharedMemory,
             @SuppressLint("MissingNullability") AlwaysOnHotwordDetector.Callback callback) {
+        // TODO(b/305787465): Remove the MANAGE_HOTWORD_DETECTION permission enforcement on the
+        // {@link #createAlwaysOnHotwordDetector(String, Locale, PersistableBundle, SharedMemory,
+        // AlwaysOnHotwordDetector.Callback)} and replace with the permission
+        // RECEIVE_SANDBOX_TRIGGER_AUDIO when it is fully launched.
+
         return createAlwaysOnHotwordDetectorInternal(keyphrase, locale,
                 /* supportHotwordDetectionService= */ true, options, sharedMemory,
                 /* modulProperties */ null, /* executor= */ null, callback);
@@ -663,7 +672,11 @@
             @Nullable PersistableBundle options, @Nullable SharedMemory sharedMemory,
             @NonNull @CallbackExecutor Executor executor,
             @NonNull AlwaysOnHotwordDetector.Callback callback) {
-        // TODO (b/269080850): Resolve AndroidFrameworkRequiresPermission lint warning
+        // TODO(b/269080850): Resolve AndroidFrameworkRequiresPermission lint warning
+        // TODO(b/305787465): Remove the MANAGE_HOTWORD_DETECTION permission enforcement on the
+        // {@link #createAlwaysOnHotwordDetector(String, Locale, PersistableBundle, SharedMemory,
+        // Executor, AlwaysOnHotwordDetector.Callback)} and replace with the permission
+        // RECEIVE_SANDBOX_TRIGGER_AUDIO when it is fully launched.
 
         Objects.requireNonNull(keyphrase);
         Objects.requireNonNull(locale);
@@ -690,6 +703,10 @@
             @NonNull SoundTrigger.ModuleProperties moduleProperties,
             @NonNull @CallbackExecutor Executor executor,
             @NonNull AlwaysOnHotwordDetector.Callback callback) {
+        // TODO(b/305787465): Remove the MANAGE_HOTWORD_DETECTION permission enforcement on the
+        // {@link #createAlwaysOnHotwordDetectorForTest(String, Locale, PersistableBundle,
+        // SharedMemory, SoundTrigger.ModuleProperties, Executor, AlwaysOnHotwordDetector.Callback)}
+        // and replace with the permission RECEIVE_SANDBOX_TRIGGER_AUDIO when it is fully launched.
 
         Objects.requireNonNull(keyphrase);
         Objects.requireNonNull(locale);
diff --git a/core/java/android/util/FeatureFlagUtils.java b/core/java/android/util/FeatureFlagUtils.java
index 2906d86..766e924 100644
--- a/core/java/android/util/FeatureFlagUtils.java
+++ b/core/java/android/util/FeatureFlagUtils.java
@@ -200,6 +200,12 @@
     public static final String SETTINGS_REMOTE_DEVICE_CREDENTIAL_VALIDATION =
             "settings_remote_device_credential_validation";
 
+    /**
+     * Flag to enable/disable to start treating any calls to "suspend" an app as "quarantine".
+     * @hide
+     */
+    public static final String SETTINGS_TREAT_PAUSE_AS_QUARANTINE =
+            "settings_treat_pause_as_quarantine";
 
     private static final Map<String, String> DEFAULT_FLAGS;
 
@@ -247,6 +253,7 @@
         DEFAULT_FLAGS.put(SETTINGS_BIOMETRICS2_FINGERPRINT_SETTINGS, "false");
         // TODO: b/298454866 Replace with Trunk Stable Feature Flag
         DEFAULT_FLAGS.put(SETTINGS_REMOTEAUTH_ENROLLMENT_SETTINGS, "false");
+        DEFAULT_FLAGS.put(SETTINGS_TREAT_PAUSE_AS_QUARANTINE, "false");
     }
 
     private static final Set<String> PERSISTENT_FLAGS;
@@ -264,6 +271,7 @@
         PERSISTENT_FLAGS.add(SETTINGS_ENABLE_SPA);
         PERSISTENT_FLAGS.add(SETTINGS_ENABLE_SPA_PHASE2);
         PERSISTENT_FLAGS.add(SETTINGS_PREFER_ACCESSIBILITY_MENU_IN_SYSTEM);
+        PERSISTENT_FLAGS.add(SETTINGS_TREAT_PAUSE_AS_QUARANTINE);
     }
 
     /**
diff --git a/core/java/android/view/ContentRecordingSession.java b/core/java/android/view/ContentRecordingSession.java
index a89f795..dc41b70 100644
--- a/core/java/android/view/ContentRecordingSession.java
+++ b/core/java/android/view/ContentRecordingSession.java
@@ -52,6 +52,12 @@
      */
     public static final int RECORD_CONTENT_TASK = 1;
 
+    /** Full screen sharing (app is not selected). */
+    public static final int TARGET_UID_FULL_SCREEN = -1;
+
+    /** Can't report (e.g. side loaded app). */
+    public static final int TARGET_UID_UNKNOWN = -2;
+
     /**
      * Unique logical identifier of the {@link android.hardware.display.VirtualDisplay} that has
      * recorded content rendered to its surface.
@@ -89,27 +95,36 @@
      */
     private boolean mWaitingForConsent = false;
 
+    /** UID of the package that is captured if selected. */
+    private int mTargetUid = TARGET_UID_UNKNOWN;
+
     /**
      * Default instance, with recording the display.
      */
     private ContentRecordingSession() {
     }
 
-    /**
-     * Returns an instance initialized for recording the indicated display.
-     */
+    /** Returns an instance initialized for recording the indicated display. */
     public static ContentRecordingSession createDisplaySession(int displayToMirror) {
-        return new ContentRecordingSession().setDisplayToRecord(displayToMirror)
-                .setContentToRecord(RECORD_CONTENT_DISPLAY);
+        return new ContentRecordingSession()
+                .setDisplayToRecord(displayToMirror)
+                .setContentToRecord(RECORD_CONTENT_DISPLAY)
+                .setTargetUid(TARGET_UID_FULL_SCREEN);
     }
 
-    /**
-     * Returns an instance initialized for task recording.
-     */
+    /** Returns an instance initialized for task recording. */
     public static ContentRecordingSession createTaskSession(
             @NonNull IBinder taskWindowContainerToken) {
-        return new ContentRecordingSession().setContentToRecord(RECORD_CONTENT_TASK)
-                .setTokenToRecord(taskWindowContainerToken);
+        return createTaskSession(taskWindowContainerToken, TARGET_UID_UNKNOWN);
+    }
+
+    /** Returns an instance initialized for task recording. */
+    public static ContentRecordingSession createTaskSession(
+            @NonNull IBinder taskWindowContainerToken, int targetUid) {
+        return new ContentRecordingSession()
+                .setContentToRecord(RECORD_CONTENT_TASK)
+                .setTokenToRecord(taskWindowContainerToken)
+                .setTargetUid(targetUid);
     }
 
     /**
@@ -175,13 +190,33 @@
         }
     }
 
+    @IntDef(prefix = "TARGET_UID_", value = {
+        TARGET_UID_FULL_SCREEN,
+        TARGET_UID_UNKNOWN
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    @DataClass.Generated.Member
+    public @interface TargetUid {}
+
+    @DataClass.Generated.Member
+    public static String targetUidToString(@TargetUid int value) {
+        switch (value) {
+            case TARGET_UID_FULL_SCREEN:
+                    return "TARGET_UID_FULL_SCREEN";
+            case TARGET_UID_UNKNOWN:
+                    return "TARGET_UID_UNKNOWN";
+            default: return Integer.toHexString(value);
+        }
+    }
+
     @DataClass.Generated.Member
     /* package-private */ ContentRecordingSession(
             int virtualDisplayId,
             @RecordContent int contentToRecord,
             int displayToRecord,
             @Nullable IBinder tokenToRecord,
-            boolean waitingForConsent) {
+            boolean waitingForConsent,
+            int targetUid) {
         this.mVirtualDisplayId = virtualDisplayId;
         this.mContentToRecord = contentToRecord;
 
@@ -196,6 +231,7 @@
         this.mDisplayToRecord = displayToRecord;
         this.mTokenToRecord = tokenToRecord;
         this.mWaitingForConsent = waitingForConsent;
+        this.mTargetUid = targetUid;
 
         // onConstructed(); // You can define this method to get a callback
     }
@@ -251,6 +287,14 @@
     }
 
     /**
+     * UID of the package that is captured if selected.
+     */
+    @DataClass.Generated.Member
+    public int getTargetUid() {
+        return mTargetUid;
+    }
+
+    /**
      * Unique logical identifier of the {@link android.hardware.display.VirtualDisplay} that has
      * recorded content rendered to its surface.
      */
@@ -314,6 +358,15 @@
         return this;
     }
 
+    /**
+     * UID of the package that is captured if selected.
+     */
+    @DataClass.Generated.Member
+    public @NonNull ContentRecordingSession setTargetUid( int value) {
+        mTargetUid = value;
+        return this;
+    }
+
     @Override
     @DataClass.Generated.Member
     public String toString() {
@@ -325,7 +378,8 @@
                 "contentToRecord = " + recordContentToString(mContentToRecord) + ", " +
                 "displayToRecord = " + mDisplayToRecord + ", " +
                 "tokenToRecord = " + mTokenToRecord + ", " +
-                "waitingForConsent = " + mWaitingForConsent +
+                "waitingForConsent = " + mWaitingForConsent + ", " +
+                "targetUid = " + mTargetUid +
         " }";
     }
 
@@ -346,7 +400,8 @@
                 && mContentToRecord == that.mContentToRecord
                 && mDisplayToRecord == that.mDisplayToRecord
                 && java.util.Objects.equals(mTokenToRecord, that.mTokenToRecord)
-                && mWaitingForConsent == that.mWaitingForConsent;
+                && mWaitingForConsent == that.mWaitingForConsent
+                && mTargetUid == that.mTargetUid;
     }
 
     @Override
@@ -361,6 +416,7 @@
         _hash = 31 * _hash + mDisplayToRecord;
         _hash = 31 * _hash + java.util.Objects.hashCode(mTokenToRecord);
         _hash = 31 * _hash + Boolean.hashCode(mWaitingForConsent);
+        _hash = 31 * _hash + mTargetUid;
         return _hash;
     }
 
@@ -378,6 +434,7 @@
         dest.writeInt(mContentToRecord);
         dest.writeInt(mDisplayToRecord);
         if (mTokenToRecord != null) dest.writeStrongBinder(mTokenToRecord);
+        dest.writeInt(mTargetUid);
     }
 
     @Override
@@ -397,6 +454,7 @@
         int contentToRecord = in.readInt();
         int displayToRecord = in.readInt();
         IBinder tokenToRecord = (flg & 0x8) == 0 ? null : (IBinder) in.readStrongBinder();
+        int targetUid = in.readInt();
 
         this.mVirtualDisplayId = virtualDisplayId;
         this.mContentToRecord = contentToRecord;
@@ -412,6 +470,7 @@
         this.mDisplayToRecord = displayToRecord;
         this.mTokenToRecord = tokenToRecord;
         this.mWaitingForConsent = waitingForConsent;
+        this.mTargetUid = targetUid;
 
         // onConstructed(); // You can define this method to get a callback
     }
@@ -442,6 +501,7 @@
         private int mDisplayToRecord;
         private @Nullable IBinder mTokenToRecord;
         private boolean mWaitingForConsent;
+        private int mTargetUid;
 
         private long mBuilderFieldsSet = 0L;
 
@@ -513,10 +573,21 @@
             return this;
         }
 
+        /**
+         * UID of the package that is captured if selected.
+         */
+        @DataClass.Generated.Member
+        public @NonNull Builder setTargetUid(int value) {
+            checkNotUsed();
+            mBuilderFieldsSet |= 0x20;
+            mTargetUid = value;
+            return this;
+        }
+
         /** Builds the instance. This builder should not be touched after calling this! */
         public @NonNull ContentRecordingSession build() {
             checkNotUsed();
-            mBuilderFieldsSet |= 0x20; // Mark builder used
+            mBuilderFieldsSet |= 0x40; // Mark builder used
 
             if ((mBuilderFieldsSet & 0x1) == 0) {
                 mVirtualDisplayId = INVALID_DISPLAY;
@@ -533,17 +604,21 @@
             if ((mBuilderFieldsSet & 0x10) == 0) {
                 mWaitingForConsent = false;
             }
+            if ((mBuilderFieldsSet & 0x20) == 0) {
+                mTargetUid = TARGET_UID_UNKNOWN;
+            }
             ContentRecordingSession o = new ContentRecordingSession(
                     mVirtualDisplayId,
                     mContentToRecord,
                     mDisplayToRecord,
                     mTokenToRecord,
-                    mWaitingForConsent);
+                    mWaitingForConsent,
+                    mTargetUid);
             return o;
         }
 
         private void checkNotUsed() {
-            if ((mBuilderFieldsSet & 0x20) != 0) {
+            if ((mBuilderFieldsSet & 0x40) != 0) {
                 throw new IllegalStateException(
                         "This Builder should not be reused. Use a new Builder instance instead");
             }
@@ -551,10 +626,10 @@
     }
 
     @DataClass.Generated(
-            time = 1683628463074L,
+            time = 1697456140720L,
             codegenVersion = "1.0.23",
             sourceFile = "frameworks/base/core/java/android/view/ContentRecordingSession.java",
-            inputSignatures = "public static final  int RECORD_CONTENT_DISPLAY\npublic static final  int RECORD_CONTENT_TASK\nprivate  int mVirtualDisplayId\nprivate @android.view.ContentRecordingSession.RecordContent int mContentToRecord\nprivate  int mDisplayToRecord\nprivate @android.annotation.Nullable android.os.IBinder mTokenToRecord\nprivate  boolean mWaitingForConsent\npublic static  android.view.ContentRecordingSession createDisplaySession(int)\npublic static  android.view.ContentRecordingSession createTaskSession(android.os.IBinder)\npublic static  boolean isValid(android.view.ContentRecordingSession)\npublic static  boolean isProjectionOnSameDisplay(android.view.ContentRecordingSession,android.view.ContentRecordingSession)\nclass ContentRecordingSession extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genConstructor=false, genToString=true, genSetters=true, genEqualsHashCode=true)")
+            inputSignatures = "public static final  int RECORD_CONTENT_DISPLAY\npublic static final  int RECORD_CONTENT_TASK\npublic static final  int TARGET_UID_FULL_SCREEN\npublic static final  int TARGET_UID_UNKNOWN\nprivate  int mVirtualDisplayId\nprivate @android.view.ContentRecordingSession.RecordContent int mContentToRecord\nprivate  int mDisplayToRecord\nprivate @android.annotation.Nullable android.os.IBinder mTokenToRecord\nprivate  boolean mWaitingForConsent\nprivate  int mTargetUid\npublic static  android.view.ContentRecordingSession createDisplaySession(int)\npublic static  android.view.ContentRecordingSession createTaskSession(android.os.IBinder)\npublic static  android.view.ContentRecordingSession createTaskSession(android.os.IBinder,int)\npublic static  boolean isValid(android.view.ContentRecordingSession)\npublic static  boolean isProjectionOnSameDisplay(android.view.ContentRecordingSession,android.view.ContentRecordingSession)\nclass ContentRecordingSession extends java.lang.Object implements [android.os.Parcelable]\n@com.android.internal.util.DataClass(genConstructor=false, genToString=true, genSetters=true, genEqualsHashCode=true)")
     @Deprecated
     private void __metadata() {}
 
diff --git a/core/java/android/view/SurfaceControlRegistry.java b/core/java/android/view/SurfaceControlRegistry.java
index 67ac811..0f35048 100644
--- a/core/java/android/view/SurfaceControlRegistry.java
+++ b/core/java/android/view/SurfaceControlRegistry.java
@@ -78,6 +78,11 @@
             for (int i = 0; i < size; i++) {
                 final Map.Entry<SurfaceControl, Long> entry = entries.get(i);
                 final SurfaceControl sc = entry.getKey();
+                if (sc == null) {
+                    // Just skip if the key has since been removed from the weak hash map
+                    continue;
+                }
+
                 final long timeRegistered = entry.getValue();
                 pw.print("  ");
                 pw.print(sc.getName());
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index e9d0e4c..49b16c7 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -19,6 +19,8 @@
 import static android.content.res.Resources.ID_NULL;
 import static android.os.Trace.TRACE_TAG_APP;
 import static android.view.ContentInfo.SOURCE_DRAG_AND_DROP;
+import static android.view.Surface.FRAME_RATE_CATEGORY_LOW;
+import static android.view.Surface.FRAME_RATE_CATEGORY_NORMAL;
 import static android.view.accessibility.AccessibilityEvent.CONTENT_CHANGE_TYPE_UNDEFINED;
 import static android.view.displayhash.DisplayHashResultCallback.DISPLAY_HASH_ERROR_INVALID_BOUNDS;
 import static android.view.displayhash.DisplayHashResultCallback.DISPLAY_HASH_ERROR_MISSING_WINDOW;
@@ -27,6 +29,7 @@
 import static android.view.displayhash.DisplayHashResultCallback.EXTRA_DISPLAY_HASH;
 import static android.view.displayhash.DisplayHashResultCallback.EXTRA_DISPLAY_HASH_ERROR_CODE;
 import static android.view.flags.Flags.FLAG_VIEW_VELOCITY_API;
+import static android.view.flags.Flags.toolkitSetFrameRate;
 import static android.view.flags.Flags.viewVelocityApi;
 
 import static com.android.internal.util.FrameworkStatsLog.TOUCH_GESTURE_CLASSIFIED__CLASSIFICATION__DEEP_PRESS;
@@ -114,6 +117,7 @@
 import android.text.InputType;
 import android.text.TextUtils;
 import android.util.AttributeSet;
+import android.util.DisplayMetrics;
 import android.util.FloatProperty;
 import android.util.LayoutDirection;
 import android.util.Log;
@@ -5509,6 +5513,11 @@
     private ViewTranslationResponse mViewTranslationResponse;
 
     /**
+     * A threshold value to determine the frame rate category of the View based on the size.
+     */
+    private static final float FRAME_RATE_SIZE_PERCENTAGE_THRESHOLD = 0.07f;
+
+    /**
      * Simple constructor to use when creating a view from code.
      *
      * @param context The Context the view is running in, through which it can
@@ -20183,6 +20192,9 @@
             return;
         }
 
+        // For VRR to vote the preferred frame rate
+        votePreferredFrameRate();
+
         // Reset content capture caches
         mPrivateFlags4 &= ~PFLAG4_CONTENT_CAPTURE_IMPORTANCE_MASK;
         mContentCaptureSessionCached = false;
@@ -20285,6 +20297,8 @@
      */
     protected void damageInParent() {
         if (mParent != null && mAttachInfo != null) {
+            // For VRR to vote the preferred frame rate
+            votePreferredFrameRate();
             mParent.onDescendantInvalidated(this, this);
         }
     }
@@ -32981,6 +32995,40 @@
         return null;
     }
 
+    private float getSizePercentage() {
+        if (mResources == null || getAlpha() == 0 || getVisibility() != VISIBLE) {
+            return 0;
+        }
+
+        DisplayMetrics displayMetrics = mResources.getDisplayMetrics();
+        int screenSize = displayMetrics.widthPixels
+                * displayMetrics.heightPixels;
+        int viewSize = getWidth() * getHeight();
+
+        if (screenSize == 0 || viewSize == 0) {
+            return 0f;
+        }
+        return (float) viewSize / screenSize;
+    }
+
+    private int calculateFrameRateCategory() {
+        float sizePercentage = getSizePercentage();
+
+        if (sizePercentage <= FRAME_RATE_SIZE_PERCENTAGE_THRESHOLD) {
+            return FRAME_RATE_CATEGORY_LOW;
+        } else {
+            return FRAME_RATE_CATEGORY_NORMAL;
+        }
+    }
+
+    private void votePreferredFrameRate() {
+        // use toolkitSetFrameRate flag to gate the change
+        ViewRootImpl viewRootImpl = getViewRootImpl();
+        if (toolkitSetFrameRate() && viewRootImpl != null && getSizePercentage() > 0) {
+            viewRootImpl.votePreferredFrameRateCategory(calculateFrameRateCategory());
+        }
+    }
+
     /**
      * Set the current velocity of the View, we only track positive value.
      * We will use the velocity information to adjust the frame rate when applicable.
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index 9392783..9d15c78 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -24,6 +24,8 @@
 import static android.view.Display.INVALID_DISPLAY;
 import static android.view.InputDevice.SOURCE_CLASS_NONE;
 import static android.view.InsetsSource.ID_IME;
+import static android.view.Surface.FRAME_RATE_CATEGORY_HIGH;
+import static android.view.Surface.FRAME_RATE_CATEGORY_NO_PREFERENCE;
 import static android.view.View.PFLAG_DRAW_ANIMATION;
 import static android.view.View.SYSTEM_UI_FLAG_FULLSCREEN;
 import static android.view.View.SYSTEM_UI_FLAG_HIDE_NAVIGATION;
@@ -74,7 +76,10 @@
 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_STATUS_BAR_ADDITIONAL;
 import static android.view.WindowManager.LayoutParams.TYPE_SYSTEM_ALERT;
@@ -87,6 +92,7 @@
 import static android.view.accessibility.Flags.forceInvertColor;
 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.toolkitSetFrameRate;
 
 import android.Manifest;
 import android.accessibilityservice.AccessibilityService;
@@ -732,6 +738,7 @@
     private SurfaceControl mBoundsLayer;
     private final SurfaceSession mSurfaceSession = new SurfaceSession();
     private final Transaction mTransaction = new Transaction();
+    private final Transaction mFrameRateTransaction = new Transaction();
 
     @UnsupportedAppUsage
     boolean mAdded;
@@ -955,6 +962,34 @@
 
     private AccessibilityWindowAttributes mAccessibilityWindowAttributes;
 
+    /*
+     * for Variable Refresh Rate project
+     */
+
+    // The preferred frame rate category of the view that
+    // could be updated on a frame-by-frame basis.
+    private int mPreferredFrameRateCategory = FRAME_RATE_CATEGORY_NO_PREFERENCE;
+    // The preferred frame rate category of the last frame that
+    // could be used to lower frame rate after touch boost
+    private int mLastPreferredFrameRateCategory = FRAME_RATE_CATEGORY_NO_PREFERENCE;
+    // The preferred frame rate of the view that is mainly used for
+    // touch boosting, view velocity handling, and TextureView.
+    private float mPreferredFrameRate = 0;
+    // Used to check if there were any view invalidations in
+    // the previous time frame (FRAME_RATE_IDLENESS_REEVALUATE_TIME).
+    private boolean mHasInvalidation = false;
+    // Used to check if it is in the touch boosting period.
+    private boolean mIsFrameRateBoosting = false;
+    // Used to check if there is a message in the message queue
+    // for idleness handling.
+    private boolean mHasIdledMessage = false;
+    // time for touch boost period.
+    private static final int FRAME_RATE_TOUCH_BOOST_TIME = 1500;
+    // time for checking idle status periodically.
+    private static final int FRAME_RATE_IDLENESS_CHECK_TIME_MILLIS = 500;
+    // time for revaluating the idle status before lowering the frame rate.
+    private static final int FRAME_RATE_IDLENESS_REEVALUATE_TIME = 500;
+
     /**
      * A temporary object used so relayoutWindow can return the latest SyncSeqId
      * system. The SyncSeqId system was designed to work without synchronous relayout
@@ -3918,6 +3953,12 @@
                 mWmsRequestSyncGroupState = WMS_SYNC_NONE;
             }
         }
+
+        // For the variable refresh rate project.
+        setPreferredFrameRate(mPreferredFrameRate);
+        setPreferredFrameRateCategory(mPreferredFrameRateCategory);
+        mLastPreferredFrameRateCategory = mPreferredFrameRateCategory;
+        mPreferredFrameRateCategory = FRAME_RATE_CATEGORY_NO_PREFERENCE;
     }
 
     private void createSyncIfNeeded() {
@@ -5970,6 +6011,8 @@
     private static final int MSG_REPORT_KEEP_CLEAR_RECTS = 36;
     private static final int MSG_PAUSED_FOR_SYNC_TIMEOUT = 37;
     private static final int MSG_DECOR_VIEW_GESTURE_INTERCEPTION = 38;
+    private static final int MSG_TOUCH_BOOST_TIMEOUT = 39;
+    private static final int MSG_CHECK_INVALIDATION_IDLE = 40;
 
     final class ViewRootHandler extends Handler {
         @Override
@@ -6265,6 +6308,32 @@
                     mNumPausedForSync = 0;
                     scheduleTraversals();
                     break;
+                case MSG_TOUCH_BOOST_TIMEOUT:
+                    /**
+                     * Lower the frame rate after the boosting period (FRAME_RATE_TOUCH_BOOST_TIME).
+                     */
+                    mIsFrameRateBoosting = false;
+                    setPreferredFrameRateCategory(Math.max(mPreferredFrameRateCategory,
+                            mLastPreferredFrameRateCategory));
+                    break;
+                case MSG_CHECK_INVALIDATION_IDLE:
+                    if (!mHasInvalidation && !mIsFrameRateBoosting) {
+                        mPreferredFrameRateCategory = FRAME_RATE_CATEGORY_NO_PREFERENCE;
+                        setPreferredFrameRateCategory(mPreferredFrameRateCategory);
+                        mHasIdledMessage = false;
+                    } else {
+                        /**
+                         * If there is no invalidation within a certain period,
+                         * we consider the display is idled.
+                         * We then set the frame rate catetogry to NO_PREFERENCE.
+                         * Note that SurfaceFlinger also has a mechanism to lower the refresh rate
+                         * if there is no updates of the buffer.
+                         */
+                        mHasInvalidation = false;
+                        mHandler.sendEmptyMessageDelayed(MSG_CHECK_INVALIDATION_IDLE,
+                                FRAME_RATE_IDLENESS_REEVALUATE_TIME);
+                    }
+                    break;
             }
         }
     }
@@ -7207,6 +7276,7 @@
 
         private int processPointerEvent(QueuedInputEvent q) {
             final MotionEvent event = (MotionEvent)q.mEvent;
+            final int action = event.getAction();
             boolean handled = mHandwritingInitiator.onTouchEvent(event);
             if (handled) {
                 // If handwriting is started, toolkit doesn't receive ACTION_UP.
@@ -7227,6 +7297,22 @@
                     scheduleConsumeBatchedInputImmediately();
                 }
             }
+
+            // For the variable refresh rate project
+            if (handled && shouldTouchBoost(action, mWindowAttributes.type)) {
+                // set the frame rate to the maximum value.
+                mIsFrameRateBoosting = true;
+                setPreferredFrameRateCategory(mPreferredFrameRateCategory);
+            }
+            /**
+             * We want to lower the refresh rate when MotionEvent.ACTION_UP,
+             * MotionEvent.ACTION_CANCEL is detected.
+             * Not using ACTION_MOVE to avoid checking and sending messages too frequently.
+             */
+            if (mIsFrameRateBoosting && (action == MotionEvent.ACTION_UP
+                    || action == MotionEvent.ACTION_CANCEL)) {
+                sendDelayedEmptyMessage(MSG_TOUCH_BOOST_TIMEOUT, FRAME_RATE_TOUCH_BOOST_TIME);
+            }
             return handled ? FINISH_HANDLED : FORWARD;
         }
 
@@ -11810,4 +11896,94 @@
             @NonNull Consumer<Boolean> listener) {
         t.clearTrustedPresentationCallback(getSurfaceControl());
     }
+
+    private void setPreferredFrameRateCategory(int preferredFrameRateCategory) {
+        if (!shouldSetFrameRateCategory()) {
+            return;
+        }
+
+        int frameRateCategory = mIsFrameRateBoosting
+                ? FRAME_RATE_CATEGORY_HIGH : preferredFrameRateCategory;
+
+        try {
+            mFrameRateTransaction.setFrameRateCategory(mSurfaceControl,
+                    frameRateCategory, false).apply();
+        } catch (Exception e) {
+            Log.e(mTag, "Unable to set frame rate category", e);
+        }
+
+        if (mPreferredFrameRateCategory != FRAME_RATE_CATEGORY_NO_PREFERENCE && !mHasIdledMessage) {
+            // Check where the display is idled periodically.
+            // If so, set the frame rate category to NO_PREFERENCE
+            mHandler.sendEmptyMessageDelayed(MSG_CHECK_INVALIDATION_IDLE,
+                    FRAME_RATE_IDLENESS_CHECK_TIME_MILLIS);
+            mHasIdledMessage = true;
+        }
+    }
+
+    private void setPreferredFrameRate(float preferredFrameRate) {
+        if (!shouldSetFrameRate()) {
+            return;
+        }
+
+        try {
+            mFrameRateTransaction.setFrameRate(mSurfaceControl,
+                    preferredFrameRate, Surface.FRAME_RATE_COMPATIBILITY_DEFAULT).apply();
+        } catch (Exception e) {
+            Log.e(mTag, "Unable to set frame rate", e);
+        }
+    }
+
+    private void sendDelayedEmptyMessage(int message, int delayedTime) {
+        mHandler.removeMessages(message);
+
+        mHandler.sendEmptyMessageDelayed(message, delayedTime);
+    }
+
+    private boolean shouldSetFrameRateCategory() {
+        // use toolkitSetFrameRate flag to gate the change
+        return  mSurface.isValid() && toolkitSetFrameRate();
+    }
+
+    private boolean shouldSetFrameRate() {
+        // use toolkitSetFrameRate flag to gate the change
+        return mPreferredFrameRate > 0 && toolkitSetFrameRate();
+    }
+
+    private boolean shouldTouchBoost(int motionEventAction, int windowType) {
+        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;
+        // use toolkitSetFrameRate flag to gate the change
+        return desiredAction && desiredType && toolkitSetFrameRate();
+    }
+
+    /**
+     * Allow Views to vote for the preferred frame rate category
+     *
+     * @param frameRateCategory the preferred frame rate category of a View
+     */
+    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PROTECTED)
+    public void votePreferredFrameRateCategory(int frameRateCategory) {
+        mPreferredFrameRateCategory = Math.max(mPreferredFrameRateCategory, frameRateCategory);
+        mHasInvalidation = true;
+    }
+
+    /**
+     * Get the value of mPreferredFrameRateCategory
+     */
+    @VisibleForTesting
+    public int getPreferredFrameRateCategory() {
+        return mPreferredFrameRateCategory;
+    }
+
+    /**
+     * Get the value of mPreferredFrameRate
+     */
+    @VisibleForTesting
+    public float getPreferredFrameRate() {
+        return mPreferredFrameRate;
+    }
 }
diff --git a/core/java/android/view/contentcapture/MainContentCaptureSession.java b/core/java/android/view/contentcapture/MainContentCaptureSession.java
index 2241fd5..b44d6a4 100644
--- a/core/java/android/view/contentcapture/MainContentCaptureSession.java
+++ b/core/java/android/view/contentcapture/MainContentCaptureSession.java
@@ -317,16 +317,14 @@
             }
         }
 
-        // Should not be possible for mComponentName to be null here but check anyway
-        if (mManager.mOptions.contentProtectionOptions.enableReceiver
-                && mManager.getContentProtectionEventBuffer() != null
-                && mComponentName != null) {
+        if (isContentProtectionEnabled()) {
             mContentProtectionEventProcessor =
                     new ContentProtectionEventProcessor(
                             mManager.getContentProtectionEventBuffer(),
                             mHandler,
                             mSystemServerInterface,
-                            mComponentName.getPackageName());
+                            mComponentName.getPackageName(),
+                            mManager.mOptions.contentProtectionOptions);
         } else {
             mContentProtectionEventProcessor = null;
         }
@@ -956,4 +954,15 @@
     private boolean isContentCaptureReceiverEnabled() {
         return mManager.mOptions.enableReceiver;
     }
+
+    @UiThread
+    private boolean isContentProtectionEnabled() {
+        // Should not be possible for mComponentName to be null here but check anyway
+        // Should not be possible for groups to be empty if receiver is enabled but check anyway
+        return mManager.mOptions.contentProtectionOptions.enableReceiver
+                && mManager.getContentProtectionEventBuffer() != null
+                && mComponentName != null
+                && (!mManager.mOptions.contentProtectionOptions.requiredGroups.isEmpty()
+                        || !mManager.mOptions.contentProtectionOptions.optionalGroups.isEmpty());
+    }
 }
diff --git a/core/java/android/view/contentprotection/ContentProtectionEventProcessor.java b/core/java/android/view/contentprotection/ContentProtectionEventProcessor.java
index b44abf3..aaf90bd 100644
--- a/core/java/android/view/contentprotection/ContentProtectionEventProcessor.java
+++ b/core/java/android/view/contentprotection/ContentProtectionEventProcessor.java
@@ -19,9 +19,9 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.UiThread;
+import android.content.ContentCaptureOptions;
 import android.content.pm.ParceledListSlice;
 import android.os.Handler;
-import android.text.InputType;
 import android.util.Log;
 import android.view.contentcapture.ContentCaptureEvent;
 import android.view.contentcapture.IContentCaptureManager;
@@ -33,10 +33,10 @@
 import java.time.Duration;
 import java.time.Instant;
 import java.util.Arrays;
-import java.util.Collections;
-import java.util.HashSet;
+import java.util.Collection;
 import java.util.List;
 import java.util.Set;
+import java.util.stream.Stream;
 
 /**
  * Main entry point for processing {@link ContentCaptureEvent} for the content protection flow.
@@ -47,33 +47,13 @@
 
     private static final String TAG = "ContentProtectionEventProcessor";
 
-    private static final List<Integer> PASSWORD_FIELD_INPUT_TYPES =
-            Collections.unmodifiableList(
-                    Arrays.asList(
-                            InputType.TYPE_NUMBER_VARIATION_PASSWORD,
-                            InputType.TYPE_TEXT_VARIATION_PASSWORD,
-                            InputType.TYPE_TEXT_VARIATION_VISIBLE_PASSWORD,
-                            InputType.TYPE_TEXT_VARIATION_WEB_PASSWORD));
-
-    private static final List<String> PASSWORD_TEXTS =
-            Collections.unmodifiableList(
-                    Arrays.asList("password", "pass word", "code", "pin", "credential"));
-
-    private static final List<String> ADDITIONAL_SUSPICIOUS_TEXTS =
-            Collections.unmodifiableList(
-                    Arrays.asList("user", "mail", "phone", "number", "login", "log in", "sign in"));
-
     private static final Duration MIN_DURATION_BETWEEN_FLUSHING = Duration.ofSeconds(3);
 
-    private static final String ANDROID_CLASS_NAME_PREFIX = "android.";
-
     private static final Set<Integer> EVENT_TYPES_TO_STORE =
-            Collections.unmodifiableSet(
-                    new HashSet<>(
-                            Arrays.asList(
-                                    ContentCaptureEvent.TYPE_VIEW_APPEARED,
-                                    ContentCaptureEvent.TYPE_VIEW_DISAPPEARED,
-                                    ContentCaptureEvent.TYPE_VIEW_TEXT_CHANGED)));
+            Set.of(
+                    ContentCaptureEvent.TYPE_VIEW_APPEARED,
+                    ContentCaptureEvent.TYPE_VIEW_DISAPPEARED,
+                    ContentCaptureEvent.TYPE_VIEW_TEXT_CHANGED);
 
     private static final int RESET_LOGIN_TOTAL_EVENTS_TO_PROCESS = 150;
 
@@ -85,11 +65,7 @@
 
     @NonNull private final String mPackageName;
 
-    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
-    public boolean mPasswordFieldDetected = false;
-
-    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
-    public boolean mSuspiciousTextDetected = false;
+    @NonNull private final ContentCaptureOptions.ContentProtectionOptions mOptions;
 
     @VisibleForTesting(visibility = VisibleForTesting.Visibility.PRIVATE)
     @Nullable
@@ -97,15 +73,32 @@
 
     private int mResetLoginRemainingEventsToProcess;
 
+    private boolean mAnyGroupFound = false;
+
+    // Ordered by priority
+    private final List<SearchGroup> mGroupsRequired;
+
+    // Ordered by priority
+    private final List<SearchGroup> mGroupsOptional;
+
+    // Ordered by priority
+    private final List<SearchGroup> mGroupsAll;
+
     public ContentProtectionEventProcessor(
             @NonNull RingBuffer<ContentCaptureEvent> eventBuffer,
             @NonNull Handler handler,
             @NonNull IContentCaptureManager contentCaptureManager,
-            @NonNull String packageName) {
+            @NonNull String packageName,
+            @NonNull ContentCaptureOptions.ContentProtectionOptions options) {
         mEventBuffer = eventBuffer;
         mHandler = handler;
         mContentCaptureManager = contentCaptureManager;
         mPackageName = packageName;
+        mOptions = options;
+        mGroupsRequired = options.requiredGroups.stream().map(SearchGroup::new).toList();
+        mGroupsOptional = options.optionalGroups.stream().map(SearchGroup::new).toList();
+        mGroupsAll =
+                Stream.of(mGroupsRequired, mGroupsOptional).flatMap(Collection::stream).toList();
     }
 
     /** Main entry point for {@link ContentCaptureEvent} processing. */
@@ -130,9 +123,31 @@
 
     @UiThread
     private void processViewAppearedEvent(@NonNull ContentCaptureEvent event) {
-        mPasswordFieldDetected |= isPasswordField(event);
-        mSuspiciousTextDetected |= isSuspiciousText(event);
-        if (mPasswordFieldDetected && mSuspiciousTextDetected) {
+        ViewNode viewNode = event.getViewNode();
+        String eventText = ContentProtectionUtils.getEventTextLower(event);
+        String viewNodeText = ContentProtectionUtils.getViewNodeTextLower(viewNode);
+        String hintText = ContentProtectionUtils.getHintTextLower(viewNode);
+
+        mGroupsAll.stream()
+                .filter(group -> !group.mFound)
+                .filter(
+                        group ->
+                                group.matches(eventText)
+                                        || group.matches(viewNodeText)
+                                        || group.matches(hintText))
+                .findFirst()
+                .ifPresent(
+                        group -> {
+                            group.mFound = true;
+                            mAnyGroupFound = true;
+                        });
+
+        boolean loginDetected =
+                mGroupsRequired.stream().allMatch(group -> group.mFound)
+                        && mGroupsOptional.stream().filter(group -> group.mFound).count()
+                                >= mOptions.optionalGroupsThreshold;
+
+        if (loginDetected) {
             loginDetected();
         } else {
             maybeResetLoginFlags();
@@ -150,14 +165,13 @@
 
     @UiThread
     private void resetLoginFlags() {
-        mPasswordFieldDetected = false;
-        mSuspiciousTextDetected = false;
-        mResetLoginRemainingEventsToProcess = 0;
+        mGroupsAll.forEach(group -> group.mFound = false);
+        mAnyGroupFound = false;
     }
 
     @UiThread
     private void maybeResetLoginFlags() {
-        if (mPasswordFieldDetected || mSuspiciousTextDetected) {
+        if (mAnyGroupFound) {
             if (mResetLoginRemainingEventsToProcess <= 0) {
                 mResetLoginRemainingEventsToProcess = RESET_LOGIN_TOTAL_EVENTS_TO_PROCESS;
             } else {
@@ -194,61 +208,21 @@
         }
     }
 
-    private boolean isPasswordField(@NonNull ContentCaptureEvent event) {
-        return isPasswordField(event.getViewNode());
-    }
+    private static final class SearchGroup {
 
-    private boolean isPasswordField(@Nullable ViewNode viewNode) {
-        if (viewNode == null) {
-            return false;
+        @NonNull private final List<String> mSearchStrings;
+
+        public boolean mFound = false;
+
+        SearchGroup(@NonNull List<String> searchStrings) {
+            mSearchStrings = searchStrings;
         }
-        return isAndroidPasswordField(viewNode) || isWebViewPasswordField(viewNode);
-    }
 
-    private boolean isAndroidPasswordField(@NonNull ViewNode viewNode) {
-        if (!isAndroidViewNode(viewNode)) {
-            return false;
+        public boolean matches(@Nullable String text) {
+            if (text == null) {
+                return false;
+            }
+            return mSearchStrings.stream().anyMatch(text::contains);
         }
-        int inputType = viewNode.getInputType();
-        return PASSWORD_FIELD_INPUT_TYPES.stream()
-                .anyMatch(passwordInputType -> (inputType & passwordInputType) != 0);
-    }
-
-    private boolean isWebViewPasswordField(@NonNull ViewNode viewNode) {
-        if (viewNode.getClassName() != null) {
-            return false;
-        }
-        return isPasswordText(ContentProtectionUtils.getViewNodeText(viewNode));
-    }
-
-    private boolean isAndroidViewNode(@NonNull ViewNode viewNode) {
-        String className = viewNode.getClassName();
-        return className != null && className.startsWith(ANDROID_CLASS_NAME_PREFIX);
-    }
-
-    private boolean isSuspiciousText(@NonNull ContentCaptureEvent event) {
-        return isSuspiciousText(ContentProtectionUtils.getEventText(event))
-                || isSuspiciousText(ContentProtectionUtils.getViewNodeText(event));
-    }
-
-    private boolean isSuspiciousText(@Nullable String text) {
-        if (text == null) {
-            return false;
-        }
-        if (isPasswordText(text)) {
-            return true;
-        }
-        String lowerCaseText = text.toLowerCase();
-        return ADDITIONAL_SUSPICIOUS_TEXTS.stream()
-                .anyMatch(suspiciousText -> lowerCaseText.contains(suspiciousText));
-    }
-
-    private boolean isPasswordText(@Nullable String text) {
-        if (text == null) {
-            return false;
-        }
-        String lowerCaseText = text.toLowerCase();
-        return PASSWORD_TEXTS.stream()
-                .anyMatch(passwordText -> lowerCaseText.contains(passwordText));
     }
 }
diff --git a/core/java/android/view/contentprotection/ContentProtectionUtils.java b/core/java/android/view/contentprotection/ContentProtectionUtils.java
index 9abf6f1..1ecac7f 100644
--- a/core/java/android/view/contentprotection/ContentProtectionUtils.java
+++ b/core/java/android/view/contentprotection/ContentProtectionUtils.java
@@ -28,33 +28,39 @@
  */
 public final class ContentProtectionUtils {
 
-    /** Returns the text extracted directly from the {@link ContentCaptureEvent}, if set. */
+    /** Returns the lowercase text extracted from the {@link ContentCaptureEvent}, if set. */
     @Nullable
-    public static String getEventText(@NonNull ContentCaptureEvent event) {
+    public static String getEventTextLower(@NonNull ContentCaptureEvent event) {
         CharSequence text = event.getText();
         if (text == null) {
             return null;
         }
-        return text.toString();
+        return text.toString().toLowerCase();
     }
 
-    /** Returns the text extracted from the event's {@link ViewNode}, if set. */
+    /** Returns the lowercase text extracted from the {@link ViewNode}, if set. */
     @Nullable
-    public static String getViewNodeText(@NonNull ContentCaptureEvent event) {
-        ViewNode viewNode = event.getViewNode();
+    public static String getViewNodeTextLower(@Nullable ViewNode viewNode) {
         if (viewNode == null) {
             return null;
         }
-        return getViewNodeText(viewNode);
-    }
-
-    /** Returns the text extracted directly from the {@link ViewNode}, if set. */
-    @Nullable
-    public static String getViewNodeText(@NonNull ViewNode viewNode) {
         CharSequence text = viewNode.getText();
         if (text == null) {
             return null;
         }
-        return text.toString();
+        return text.toString().toLowerCase();
+    }
+
+    /** Returns the lowercase hint text extracted from the {@link ViewNode}, if set. */
+    @Nullable
+    public static String getHintTextLower(@Nullable ViewNode viewNode) {
+        if (viewNode == null) {
+            return null;
+        }
+        String text = viewNode.getHint();
+        if (text == null) {
+            return null;
+        }
+        return text.toLowerCase();
     }
 }
diff --git a/core/java/android/window/TaskFragmentOperation.java b/core/java/android/window/TaskFragmentOperation.java
index 43fa0be..4e0f9a5 100644
--- a/core/java/android/window/TaskFragmentOperation.java
+++ b/core/java/android/window/TaskFragmentOperation.java
@@ -88,6 +88,26 @@
      */
     public static final int OP_TYPE_SET_ISOLATED_NAVIGATION = 11;
 
+    /**
+     * Reorders the TaskFragment to be the bottom-most in the Task. Note that this op will bring the
+     * TaskFragment to the bottom of the Task below all the other Activities and TaskFragments.
+     *
+     * This is only allowed for system organizers. See
+     * {@link com.android.server.wm.TaskFragmentOrganizerController#registerOrganizer(
+     * ITaskFragmentOrganizer, boolean)}
+     */
+    public static final int OP_TYPE_REORDER_TO_BOTTOM_OF_TASK = 12;
+
+    /**
+     * Reorders the TaskFragment to be the top-most in the Task. Note that this op will bring the
+     * TaskFragment to the top of the Task above all the other Activities and TaskFragments.
+     *
+     * This is only allowed for system organizers. See
+     * {@link com.android.server.wm.TaskFragmentOrganizerController#registerOrganizer(
+     * ITaskFragmentOrganizer, boolean)}
+     */
+    public static final int OP_TYPE_REORDER_TO_TOP_OF_TASK = 13;
+
     @IntDef(prefix = { "OP_TYPE_" }, value = {
             OP_TYPE_UNKNOWN,
             OP_TYPE_CREATE_TASK_FRAGMENT,
@@ -101,7 +121,9 @@
             OP_TYPE_SET_ANIMATION_PARAMS,
             OP_TYPE_SET_RELATIVE_BOUNDS,
             OP_TYPE_REORDER_TO_FRONT,
-            OP_TYPE_SET_ISOLATED_NAVIGATION
+            OP_TYPE_SET_ISOLATED_NAVIGATION,
+            OP_TYPE_REORDER_TO_BOTTOM_OF_TASK,
+            OP_TYPE_REORDER_TO_TOP_OF_TASK,
     })
     @Retention(RetentionPolicy.SOURCE)
     public @interface OperationType {}
diff --git a/core/java/android/window/flags/windowing_frontend.aconfig b/core/java/android/window/flags/windowing_frontend.aconfig
index 392aa1b..6c025a4 100644
--- a/core/java/android/window/flags/windowing_frontend.aconfig
+++ b/core/java/android/window/flags/windowing_frontend.aconfig
@@ -20,4 +20,11 @@
   description: "Refactor dim to fix flickers"
   bug: "281632483,295291019"
   is_fixed_read_only: true
-}
\ No newline at end of file
+}
+
+flag {
+  name: "transit_ready_tracking"
+  namespace: "windowing_frontend"
+  description: "Enable accurate transition readiness tracking"
+  bug: "294925498"
+}
diff --git a/core/java/com/android/internal/policy/WearGestureInterceptionDetector.java b/core/java/com/android/internal/policy/WearGestureInterceptionDetector.java
index 6fd5018..504928c 100644
--- a/core/java/com/android/internal/policy/WearGestureInterceptionDetector.java
+++ b/core/java/com/android/internal/policy/WearGestureInterceptionDetector.java
@@ -74,16 +74,15 @@
         return windowSwipeToDismiss;
     }
 
-    private boolean isPointerIndexValid(MotionEvent ev) {
+    private int getIndexForValidPointer(MotionEvent ev) {
         int pointerIndex = ev.findPointerIndex(mActivePointerId);
         if (pointerIndex == -1) {
             if (DEBUG) {
                 Log.e(TAG, "Invalid pointer index: ignoring.");
             }
             mDiscardIntercept = true;
-            return false;
         }
-        return true;
+        return pointerIndex;
     }
 
     private void updateSwiping(MotionEvent ev) {
@@ -98,7 +97,7 @@
         }
     }
 
-    private void updateDiscardIntercept(MotionEvent ev) {
+    private void updateDiscardIntercept(MotionEvent ev, int pointerIndex) {
         if (!mSwiping) {
             // Don't look at canScroll until we have passed the touch slop
             return;
@@ -107,8 +106,8 @@
             return;
         }
         final boolean checkLeft = mDownX < ev.getRawX();
-        final float x = ev.getX(mActivePointerId);
-        final float y = ev.getY(mActivePointerId);
+        final float x = ev.getX(pointerIndex);
+        final float y = ev.getY(pointerIndex);
         if (canScroll(mInstalledDecorView, false, checkLeft, x, y)) {
             mDiscardIntercept = true;
         }
@@ -152,11 +151,12 @@
                 if (mDiscardIntercept) {
                     break;
                 }
-                if (!isPointerIndexValid(ev)) {
+                final int pointerIndex = getIndexForValidPointer(ev);
+                if (pointerIndex == -1) {
                     break;
                 }
                 updateSwiping(ev);
-                updateDiscardIntercept(ev);
+                updateDiscardIntercept(ev, pointerIndex);
                 break;
             case MotionEvent.ACTION_CANCEL:
             case MotionEvent.ACTION_UP:
diff --git a/core/jni/android_hardware_SensorManager.cpp b/core/jni/android_hardware_SensorManager.cpp
index deb138f..9c883d1 100644
--- a/core/jni/android_hardware_SensorManager.cpp
+++ b/core/jni/android_hardware_SensorManager.cpp
@@ -265,6 +265,18 @@
     return mgr->isDataInjectionEnabled();
 }
 
+static jboolean nativeIsReplayDataInjectionEnabled(JNIEnv *_env, jclass _this,
+                                                   jlong sensorManager) {
+    SensorManager *mgr = reinterpret_cast<SensorManager *>(sensorManager);
+    return mgr->isReplayDataInjectionEnabled();
+}
+
+static jboolean nativeIsHalBypassReplayDataInjectionEnabled(JNIEnv *_env, jclass _this,
+                                                            jlong sensorManager) {
+    SensorManager *mgr = reinterpret_cast<SensorManager *>(sensorManager);
+    return mgr->isHalBypassReplayDataInjectionEnabled();
+}
+
 static jint nativeCreateDirectChannel(JNIEnv *_env, jclass _this, jlong sensorManager,
                                       jint deviceId, jlong size, jint channelType, jint fd,
                                       jobject hardwareBufferObj) {
@@ -533,6 +545,11 @@
 
         {"nativeIsDataInjectionEnabled", "(J)Z", (void *)nativeIsDataInjectionEnabled},
 
+        {"nativeIsReplayDataInjectionEnabled", "(J)Z", (void *)nativeIsReplayDataInjectionEnabled},
+
+        {"nativeIsHalBypassReplayDataInjectionEnabled", "(J)Z",
+         (void *)nativeIsHalBypassReplayDataInjectionEnabled},
+
         {"nativeCreateDirectChannel", "(JIJIILandroid/hardware/HardwareBuffer;)I",
          (void *)nativeCreateDirectChannel},
 
diff --git a/core/proto/android/service/package.proto b/core/proto/android/service/package.proto
index eb14db0..068f4dd 100644
--- a/core/proto/android/service/package.proto
+++ b/core/proto/android/service/package.proto
@@ -115,6 +115,9 @@
 
                 // Only set if the app defined a monochrome icon.
                 optional string monochrome_icon_bitmap_path = 3;
+
+                // The component name of the original activity (pre-archival).
+                optional string original_component_name = 4;
             }
 
             /** Information about main activities. */
diff --git a/core/res/Android.bp b/core/res/Android.bp
index b71995f..4e686b7 100644
--- a/core/res/Android.bp
+++ b/core/res/Android.bp
@@ -104,6 +104,7 @@
 
 android_app {
     name: "framework-res",
+    use_resource_processor: false,
     sdk_version: "core_platform",
     certificate: "platform",
 
diff --git a/core/res/AndroidManifest.xml b/core/res/AndroidManifest.xml
index 88b578b..cffbaa7 100644
--- a/core/res/AndroidManifest.xml
+++ b/core/res/AndroidManifest.xml
@@ -6123,7 +6123,7 @@
     <uses-permission android:name="android.permission.PACKAGE_USAGE_STATS" />
 
     <!-- @SystemApi @hide
-         @FlaggedApi("backstage_power.report_usage_stats_permission")
+         @FlaggedApi("android.app.usage.report_usage_stats_permission")
          Allows trusted system components to report events to UsageStatsManager -->
     <permission android:name="android.permission.REPORT_USAGE_STATS"
                 android:protectionLevel="signature|module" />
diff --git a/core/tests/coretests/Android.bp b/core/tests/coretests/Android.bp
index 2993a0e..445ddf5 100644
--- a/core/tests/coretests/Android.bp
+++ b/core/tests/coretests/Android.bp
@@ -65,6 +65,7 @@
         "device-time-shell-utils",
         "testables",
         "com.android.text.flags-aconfig-java",
+        "flag-junit",
     ],
 
     libs: [
@@ -75,6 +76,7 @@
         "framework",
         "ext",
         "framework-res",
+        "android.view.flags-aconfig-java",
     ],
     jni_libs: [
         "libpowermanagertest_jni",
diff --git a/core/tests/coretests/src/android/hardware/face/FaceManagerTest.java b/core/tests/coretests/src/android/hardware/face/FaceManagerTest.java
index 07dec5d..b843ad7 100644
--- a/core/tests/coretests/src/android/hardware/face/FaceManagerTest.java
+++ b/core/tests/coretests/src/android/hardware/face/FaceManagerTest.java
@@ -79,6 +79,8 @@
     private FaceManager.AuthenticationCallback mAuthCallback;
     @Mock
     private FaceManager.EnrollmentCallback mEnrollmentCallback;
+    @Mock
+    private FaceManager.FaceDetectionCallback mFaceDetectionCallback;
 
     @Captor
     private ArgumentCaptor<IFaceAuthenticatorsRegisteredCallback> mCaptor;
@@ -191,6 +193,23 @@
                 any(), anyString(), any(), any(), anyBoolean());
     }
 
+    @Test
+    public void detectClient_onError() throws RemoteException {
+        ArgumentCaptor<IFaceServiceReceiver> argumentCaptor =
+                ArgumentCaptor.forClass(IFaceServiceReceiver.class);
+
+        CancellationSignal cancellationSignal = new CancellationSignal();
+        mFaceManager.detectFace(cancellationSignal, mFaceDetectionCallback,
+                new FaceAuthenticateOptions.Builder().build());
+
+        verify(mService).detectFace(any(), argumentCaptor.capture(), any());
+
+        argumentCaptor.getValue().onError(5 /* error */, 0 /* vendorCode */);
+        mLooper.dispatchAll();
+
+        verify(mFaceDetectionCallback).onDetectionError(anyInt());
+    }
+
     private void initializeProperties() throws RemoteException {
         verify(mService).addAuthenticatorsRegisteredCallback(mCaptor.capture());
 
diff --git a/core/tests/coretests/src/android/hardware/fingerprint/FingerprintManagerTest.java b/core/tests/coretests/src/android/hardware/fingerprint/FingerprintManagerTest.java
index 625e2e3..70313b8 100644
--- a/core/tests/coretests/src/android/hardware/fingerprint/FingerprintManagerTest.java
+++ b/core/tests/coretests/src/android/hardware/fingerprint/FingerprintManagerTest.java
@@ -74,6 +74,8 @@
     private FingerprintManager.AuthenticationCallback mAuthCallback;
     @Mock
     private FingerprintManager.EnrollmentCallback mEnrollCallback;
+    @Mock
+    private FingerprintManager.FingerprintDetectionCallback mFingerprintDetectionCallback;
 
     @Captor
     private ArgumentCaptor<IFingerprintAuthenticatorsRegisteredCallback> mCaptor;
@@ -166,4 +168,21 @@
                 anyString());
         verify(mService, never()).enroll(any(), any(), anyInt(), any(), anyString(), anyInt());
     }
+
+    @Test
+    public void detectClient_onError() throws RemoteException {
+        ArgumentCaptor<IFingerprintServiceReceiver> argumentCaptor =
+                ArgumentCaptor.forClass(IFingerprintServiceReceiver.class);
+
+        mFingerprintManager.detectFingerprint(new CancellationSignal(),
+                mFingerprintDetectionCallback,
+                new FingerprintAuthenticateOptions.Builder().build());
+
+        verify(mService).detectFingerprint(any(), argumentCaptor.capture(), any());
+
+        argumentCaptor.getValue().onError(5 /* error */, 0 /* vendorCode */);
+        mLooper.dispatchAll();
+
+        verify(mFingerprintDetectionCallback).onDetectionError(anyInt());
+    }
 }
diff --git a/core/tests/coretests/src/android/view/ViewRootImplTest.java b/core/tests/coretests/src/android/view/ViewRootImplTest.java
index 6a9fc04..1a38dec 100644
--- a/core/tests/coretests/src/android/view/ViewRootImplTest.java
+++ b/core/tests/coretests/src/android/view/ViewRootImplTest.java
@@ -17,6 +17,11 @@
 package android.view;
 
 import static android.view.accessibility.Flags.FLAG_FORCE_INVERT_COLOR;
+import static android.view.flags.Flags.FLAG_TOOLKIT_SET_FRAME_RATE;
+import static android.view.Surface.FRAME_RATE_CATEGORY_HIGH;
+import static android.view.Surface.FRAME_RATE_CATEGORY_LOW;
+import static android.view.Surface.FRAME_RATE_CATEGORY_NORMAL;
+import static android.view.Surface.FRAME_RATE_CATEGORY_NO_PREFERENCE;
 import static android.view.View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY;
 import static android.view.View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN;
 import static android.view.View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION;
@@ -48,8 +53,12 @@
 import android.os.Binder;
 import android.os.SystemProperties;
 import android.platform.test.annotations.Presubmit;
+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.provider.Settings;
+import android.util.DisplayMetrics;
 import android.util.Log;
 import android.view.WindowInsets.Side;
 import android.view.WindowInsets.Type;
@@ -97,6 +106,10 @@
     // state after the test completes.
     private static boolean sOriginalTouchMode;
 
+    @Rule
+    public final CheckFlagsRule mCheckFlagsRule =
+            DeviceFlagsValueProvider.createCheckFlagsRule();
+
     @BeforeClass
     public static void setUpClass() {
         sContext = sInstrumentation.getTargetContext();
@@ -427,6 +440,129 @@
         assertThat(result).isFalse();
     }
 
+    /**
+     * Test the default values are properly set
+     */
+    @UiThreadTest
+    @Test
+    @RequiresFlagsEnabled(FLAG_TOOLKIT_SET_FRAME_RATE)
+    public void votePreferredFrameRate_getDefaultValues() {
+        ViewRootImpl viewRootImpl = new ViewRootImpl(sContext,
+                sContext.getDisplayNoVerify());
+        assertEquals(viewRootImpl.getPreferredFrameRateCategory(),
+                FRAME_RATE_CATEGORY_NO_PREFERENCE);
+        assertEquals(viewRootImpl.getPreferredFrameRate(), 0, 0.1);
+    }
+
+    /**
+     * Test the value of the frame rate cateogry based on the visibility of a view
+     * Invsible: FRAME_RATE_CATEGORY_NO_PREFERENCE
+     * Visible: FRAME_RATE_CATEGORY_NORMAL
+     */
+    @Test
+    @RequiresFlagsEnabled(FLAG_TOOLKIT_SET_FRAME_RATE)
+    public void votePreferredFrameRate_voteFrameRateCategory_visibility() {
+        View view = new View(sContext);
+        attachViewToWindow(view);
+        ViewRootImpl viewRootImpl = view.getViewRootImpl();
+        sInstrumentation.runOnMainSync(() -> {
+            view.setVisibility(View.INVISIBLE);
+            view.invalidate();
+            assertEquals(viewRootImpl.getPreferredFrameRateCategory(),
+                    FRAME_RATE_CATEGORY_NO_PREFERENCE);
+        });
+
+        sInstrumentation.runOnMainSync(() -> {
+            view.setVisibility(View.VISIBLE);
+            view.invalidate();
+            assertEquals(viewRootImpl.getPreferredFrameRateCategory(),
+                    FRAME_RATE_CATEGORY_NORMAL);
+        });
+    }
+
+    /**
+     * Test the value of the frame rate cateogry based on the size of a view.
+     * The current threshold value is 7% of the screen size
+     * <7%: FRAME_RATE_CATEGORY_LOW
+     */
+    @Test
+    @RequiresFlagsEnabled(FLAG_TOOLKIT_SET_FRAME_RATE)
+    public void votePreferredFrameRate_voteFrameRateCategory_smallSize() {
+        View view = new View(sContext);
+        WindowManager.LayoutParams wmlp = new WindowManager.LayoutParams(TYPE_APPLICATION_OVERLAY);
+        wmlp.token = new Binder(); // Set a fake token to bypass 'is your activity running' check
+        wmlp.width = 1;
+        wmlp.height = 1;
+
+        sInstrumentation.runOnMainSync(() -> {
+            WindowManager wm = sContext.getSystemService(WindowManager.class);
+            wm.addView(view, wmlp);
+        });
+        sInstrumentation.waitForIdleSync();
+
+        ViewRootImpl viewRootImpl = view.getViewRootImpl();
+        sInstrumentation.runOnMainSync(() -> {
+            view.invalidate();
+            assertEquals(viewRootImpl.getPreferredFrameRateCategory(), FRAME_RATE_CATEGORY_LOW);
+        });
+    }
+
+    /**
+     * Test the value of the frame rate cateogry based on the size of a view.
+     * The current threshold value is 7% of the screen size
+     * >=7% : FRAME_RATE_CATEGORY_NORMAL
+     */
+    @Test
+    @RequiresFlagsEnabled(FLAG_TOOLKIT_SET_FRAME_RATE)
+    public void votePreferredFrameRate_voteFrameRateCategory_normalSize() {
+        View view = new View(sContext);
+        WindowManager.LayoutParams wmlp = new WindowManager.LayoutParams(TYPE_APPLICATION_OVERLAY);
+        wmlp.token = new Binder(); // Set a fake token to bypass 'is your activity running' check
+
+        sInstrumentation.runOnMainSync(() -> {
+            WindowManager wm = sContext.getSystemService(WindowManager.class);
+            Display display = wm.getDefaultDisplay();
+            DisplayMetrics metrics = new DisplayMetrics();
+            display.getMetrics(metrics);
+            wmlp.width = (int) (metrics.widthPixels * 0.9);
+            wmlp.height = (int) (metrics.heightPixels * 0.9);
+            wm.addView(view, wmlp);
+        });
+        sInstrumentation.waitForIdleSync();
+
+        ViewRootImpl viewRootImpl = view.getViewRootImpl();
+        sInstrumentation.runOnMainSync(() -> {
+            view.invalidate();
+            assertEquals(viewRootImpl.getPreferredFrameRateCategory(), FRAME_RATE_CATEGORY_NORMAL);
+        });
+    }
+
+    /**
+     * Test how values of the frame rate cateogry are aggregated.
+     * It should take the max value among all of the voted categories per frame.
+     */
+    @Test
+    @RequiresFlagsEnabled(FLAG_TOOLKIT_SET_FRAME_RATE)
+    public void votePreferredFrameRate_voteFrameRateCategory_aggregate() {
+        View view = new View(sContext);
+        attachViewToWindow(view);
+        sInstrumentation.runOnMainSync(() -> {
+            ViewRootImpl viewRootImpl = view.getViewRootImpl();
+            assertEquals(viewRootImpl.getPreferredFrameRateCategory(),
+                    FRAME_RATE_CATEGORY_NO_PREFERENCE);
+            viewRootImpl.votePreferredFrameRateCategory(FRAME_RATE_CATEGORY_LOW);
+            assertEquals(viewRootImpl.getPreferredFrameRateCategory(), FRAME_RATE_CATEGORY_LOW);
+            viewRootImpl.votePreferredFrameRateCategory(FRAME_RATE_CATEGORY_NORMAL);
+            assertEquals(viewRootImpl.getPreferredFrameRateCategory(), FRAME_RATE_CATEGORY_NORMAL);
+            viewRootImpl.votePreferredFrameRateCategory(FRAME_RATE_CATEGORY_HIGH);
+            assertEquals(viewRootImpl.getPreferredFrameRateCategory(), FRAME_RATE_CATEGORY_HIGH);
+            viewRootImpl.votePreferredFrameRateCategory(FRAME_RATE_CATEGORY_NORMAL);
+            assertEquals(viewRootImpl.getPreferredFrameRateCategory(), FRAME_RATE_CATEGORY_HIGH);
+            viewRootImpl.votePreferredFrameRateCategory(FRAME_RATE_CATEGORY_LOW);
+            assertEquals(viewRootImpl.getPreferredFrameRateCategory(), FRAME_RATE_CATEGORY_HIGH);
+        });
+    }
+
     @Test
     public void forceInvertOffDarkThemeOff_forceDarkModeDisabled() {
         mSetFlagsRule.enableFlags(FLAG_FORCE_INVERT_COLOR);
diff --git a/core/tests/coretests/src/android/view/contentcapture/MainContentCaptureSessionTest.java b/core/tests/coretests/src/android/view/contentcapture/MainContentCaptureSessionTest.java
index e76d266..d47d789 100644
--- a/core/tests/coretests/src/android/view/contentcapture/MainContentCaptureSessionTest.java
+++ b/core/tests/coretests/src/android/view/contentcapture/MainContentCaptureSessionTest.java
@@ -115,6 +115,26 @@
                         new ContentCaptureOptions.ContentProtectionOptions(
                                 /* enableReceiver= */ true,
                                 -BUFFER_SIZE,
+                                /* requiredGroups= */ List.of(List.of("a")),
+                                /* optionalGroups= */ Collections.emptyList(),
+                                /* optionalGroupsThreshold= */ 0));
+        MainContentCaptureSession session = createSession(options);
+        session.mContentProtectionEventProcessor = mMockContentProtectionEventProcessor;
+
+        session.onSessionStarted(/* resultCode= */ 0, /* binder= */ null);
+
+        assertThat(session.mContentProtectionEventProcessor).isNull();
+        verifyZeroInteractions(mMockContentProtectionEventProcessor);
+    }
+
+    @Test
+    public void onSessionStarted_contentProtectionNoGroups_processorNotCreated() {
+        ContentCaptureOptions options =
+                createOptions(
+                        /* enableContentCaptureReceiver= */ true,
+                        new ContentCaptureOptions.ContentProtectionOptions(
+                                /* enableReceiver= */ true,
+                                BUFFER_SIZE,
                                 /* requiredGroups= */ Collections.emptyList(),
                                 /* optionalGroups= */ Collections.emptyList(),
                                 /* optionalGroupsThreshold= */ 0));
@@ -320,7 +340,7 @@
                 new ContentCaptureOptions.ContentProtectionOptions(
                         enableContentProtectionReceiver,
                         BUFFER_SIZE,
-                        /* requiredGroups= */ Collections.emptyList(),
+                        /* requiredGroups= */ List.of(List.of("a")),
                         /* optionalGroups= */ Collections.emptyList(),
                         /* optionalGroupsThreshold= */ 0));
     }
diff --git a/core/tests/coretests/src/android/view/contentprotection/ContentProtectionEventProcessorTest.java b/core/tests/coretests/src/android/view/contentprotection/ContentProtectionEventProcessorTest.java
index 39a2e0e..ba0dbf4 100644
--- a/core/tests/coretests/src/android/view/contentprotection/ContentProtectionEventProcessorTest.java
+++ b/core/tests/coretests/src/android/view/contentprotection/ContentProtectionEventProcessorTest.java
@@ -29,12 +29,13 @@
 import static org.mockito.Mockito.verifyZeroInteractions;
 import static org.mockito.Mockito.when;
 
+import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.content.ContentCaptureOptions;
 import android.content.Context;
 import android.content.pm.ParceledListSlice;
 import android.os.Handler;
-import android.os.Looper;
-import android.text.InputType;
+import android.os.test.TestLooper;
 import android.view.View;
 import android.view.contentcapture.ContentCaptureEvent;
 import android.view.contentcapture.IContentCaptureManager;
@@ -57,6 +58,7 @@
 import org.mockito.Mock;
 import org.mockito.junit.MockitoJUnit;
 import org.mockito.junit.MockitoRule;
+import org.mockito.verification.VerificationMode;
 
 import java.time.Instant;
 import java.util.ArrayList;
@@ -75,13 +77,25 @@
 
     private static final String PACKAGE_NAME = "com.test.package.name";
 
-    private static final String ANDROID_CLASS_NAME = "android.test.some.class.name";
+    private static final String TEXT_REQUIRED1 = "TEXT REQUIRED1 TEXT";
 
-    private static final String PASSWORD_TEXT = "ENTER PASSWORD HERE";
+    private static final String TEXT_REQUIRED2 = "TEXT REQUIRED2 TEXT";
 
-    private static final String SUSPICIOUS_TEXT = "PLEASE SIGN IN";
+    private static final String TEXT_OPTIONAL1 = "TEXT OPTIONAL1 TEXT";
 
-    private static final String SAFE_TEXT = "SAFE TEXT";
+    private static final String TEXT_OPTIONAL2 = "TEXT OPTIONAL2 TEXT";
+
+    private static final String TEXT_CONTAINS_OPTIONAL3 = "TEXTOPTIONAL3TEXT";
+
+    private static final String TEXT_SHARED = "TEXT SHARED TEXT";
+
+    private static final String TEXT_SAFE = "TEXT SAFE TEXT";
+
+    private static final List<List<String>> REQUIRED_GROUPS =
+            List.of(List.of("required1", "missing"), List.of("required2", "shared"));
+
+    private static final List<List<String>> OPTIONAL_GROUPS =
+            List.of(List.of("optional1"), List.of("optional2", "optional3"), List.of("shared"));
 
     private static final ContentCaptureEvent PROCESS_EVENT = createProcessEvent();
 
@@ -91,7 +105,17 @@
     private static final Set<Integer> EVENT_TYPES_TO_STORE =
             ImmutableSet.of(TYPE_VIEW_APPEARED, TYPE_VIEW_DISAPPEARED, TYPE_VIEW_TEXT_CHANGED);
 
-    private static final int RESET_LOGIN_TOTAL_EVENTS_TO_PROCESS = 150;
+    private static final int BUFFER_SIZE = 150;
+
+    private static final int OPTIONAL_GROUPS_THRESHOLD = 1;
+
+    private static final ContentCaptureOptions.ContentProtectionOptions OPTIONS =
+            new ContentCaptureOptions.ContentProtectionOptions(
+                    /* enableReceiver= */ true,
+                    BUFFER_SIZE,
+                    REQUIRED_GROUPS,
+                    OPTIONAL_GROUPS,
+                    OPTIONAL_GROUPS_THRESHOLD);
 
     @Rule public final MockitoRule mMockitoRule = MockitoJUnit.rule();
 
@@ -101,16 +125,19 @@
 
     private final Context mContext = ApplicationProvider.getApplicationContext();
 
-    private ContentProtectionEventProcessor mContentProtectionEventProcessor;
+    private final TestLooper mTestLooper = new TestLooper();
+
+    @NonNull private ContentProtectionEventProcessor mContentProtectionEventProcessor;
 
     @Before
     public void setup() {
         mContentProtectionEventProcessor =
                 new ContentProtectionEventProcessor(
                         mMockEventBuffer,
-                        new Handler(Looper.getMainLooper()),
+                        new Handler(mTestLooper.getLooper()),
                         mMockContentCaptureManager,
-                        PACKAGE_NAME);
+                        PACKAGE_NAME,
+                        OPTIONS);
     }
 
     @Test
@@ -156,347 +183,224 @@
     }
 
     @Test
-    public void processEvent_loginDetected_inspectsOnlyTypeViewAppeared() {
-        mContentProtectionEventProcessor.mPasswordFieldDetected = true;
-        mContentProtectionEventProcessor.mSuspiciousTextDetected = true;
+    public void processEvent_loginDetected_true_eventText() throws Exception {
+        when(mMockEventBuffer.toArray()).thenReturn(BUFFERED_EVENTS);
+
+        mContentProtectionEventProcessor.processEvent(
+                createProcessEvent(
+                        /* eventText= */ TEXT_REQUIRED1,
+                        /* viewNodeText= */ null,
+                        /* hintText= */ null));
+        mContentProtectionEventProcessor.processEvent(
+                createProcessEvent(
+                        /* eventText= */ TEXT_REQUIRED2,
+                        /* viewNodeText= */ null,
+                        /* hintText= */ null));
+        mContentProtectionEventProcessor.processEvent(
+                createProcessEvent(
+                        /* eventText= */ TEXT_OPTIONAL1,
+                        /* viewNodeText= */ null,
+                        /* hintText= */ null));
+
+        assertLoginDetected();
+    }
+
+    @Test
+    public void processEvent_loginDetected_true_viewNodeText() throws Exception {
+        when(mMockEventBuffer.toArray()).thenReturn(BUFFERED_EVENTS);
+
+        mContentProtectionEventProcessor.processEvent(
+                createProcessEvent(
+                        /* eventText= */ null,
+                        /* viewNodeText= */ TEXT_REQUIRED1,
+                        /* hintText= */ null));
+        mContentProtectionEventProcessor.processEvent(
+                createProcessEvent(
+                        /* eventText= */ null,
+                        /* viewNodeText= */ TEXT_REQUIRED2,
+                        /* hintText= */ null));
+        mContentProtectionEventProcessor.processEvent(
+                createProcessEvent(
+                        /* eventText= */ null,
+                        /* viewNodeText= */ TEXT_OPTIONAL1,
+                        /* hintText= */ null));
+
+        assertLoginDetected();
+    }
+
+    @Test
+    public void processEvent_loginDetected_true_hintText() throws Exception {
+        when(mMockEventBuffer.toArray()).thenReturn(BUFFERED_EVENTS);
+
+        mContentProtectionEventProcessor.processEvent(
+                createProcessEvent(
+                        /* eventText= */ null,
+                        /* viewNodeText= */ null,
+                        /* hintText= */ TEXT_REQUIRED1));
+        mContentProtectionEventProcessor.processEvent(
+                createProcessEvent(
+                        /* eventText= */ null,
+                        /* viewNodeText= */ null,
+                        /* hintText= */ TEXT_REQUIRED2));
+        mContentProtectionEventProcessor.processEvent(
+                createProcessEvent(
+                        /* eventText= */ null,
+                        /* viewNodeText= */ null,
+                        /* hintText= */ TEXT_OPTIONAL1));
+
+        assertLoginDetected();
+    }
+
+    @Test
+    public void processEvent_loginDetected_true_differentOptionalGroup() throws Exception {
+        when(mMockEventBuffer.toArray()).thenReturn(BUFFERED_EVENTS);
+
+        mContentProtectionEventProcessor.processEvent(createProcessEvent(TEXT_REQUIRED1));
+        mContentProtectionEventProcessor.processEvent(createProcessEvent(TEXT_REQUIRED2));
+        mContentProtectionEventProcessor.processEvent(createProcessEvent(TEXT_OPTIONAL2));
+
+        assertLoginDetected();
+    }
+
+    @Test
+    public void processEvent_loginDetected_true_usesContains() throws Exception {
+        when(mMockEventBuffer.toArray()).thenReturn(BUFFERED_EVENTS);
+
+        mContentProtectionEventProcessor.processEvent(createProcessEvent(TEXT_REQUIRED1));
+        mContentProtectionEventProcessor.processEvent(createProcessEvent(TEXT_REQUIRED2));
+        mContentProtectionEventProcessor.processEvent(createProcessEvent(TEXT_CONTAINS_OPTIONAL3));
+
+        assertLoginDetected();
+    }
+
+    @Test
+    public void processEvent_loginDetected_false_missingRequiredGroups() {
+        mContentProtectionEventProcessor.processEvent(createProcessEvent(TEXT_REQUIRED1));
+        mContentProtectionEventProcessor.processEvent(createProcessEvent(TEXT_OPTIONAL1));
+
+        assertLoginNotDetected();
+    }
+
+    @Test
+    public void processEvent_loginDetected_false_missingOptionalGroups() {
+        mContentProtectionEventProcessor.processEvent(createProcessEvent(TEXT_REQUIRED1));
+        mContentProtectionEventProcessor.processEvent(createProcessEvent(TEXT_REQUIRED2));
+
+        assertLoginNotDetected();
+    }
+
+    @Test
+    public void processEvent_loginDetected_false_safeText() {
+        mContentProtectionEventProcessor.processEvent(createProcessEvent(TEXT_REQUIRED1));
+        mContentProtectionEventProcessor.processEvent(createProcessEvent(TEXT_REQUIRED2));
+        mContentProtectionEventProcessor.processEvent(createProcessEvent(TEXT_SAFE));
+        mContentProtectionEventProcessor.processEvent(createProcessEvent(TEXT_SAFE));
+        mContentProtectionEventProcessor.processEvent(createProcessEvent(TEXT_SAFE));
+
+        assertLoginNotDetected();
+    }
+
+    @Test
+    public void processEvent_loginDetected_false_sharedTextOnce() {
+        mContentProtectionEventProcessor.processEvent(createProcessEvent(TEXT_REQUIRED1));
+        mContentProtectionEventProcessor.processEvent(createProcessEvent(TEXT_SHARED));
+
+        assertLoginNotDetected();
+    }
+
+    @Test
+    public void processEvent_loginDetected_true_sharedTextMultiple() throws Exception {
+        when(mMockEventBuffer.toArray()).thenReturn(BUFFERED_EVENTS);
+
+        mContentProtectionEventProcessor.processEvent(createProcessEvent(TEXT_REQUIRED1));
+        mContentProtectionEventProcessor.processEvent(createProcessEvent(TEXT_SHARED));
+        mContentProtectionEventProcessor.processEvent(createProcessEvent(TEXT_SHARED));
+
+        assertLoginDetected();
+    }
+
+    @Test
+    public void processEvent_loginDetected_false_inspectsOnlyTypeViewAppeared() {
+        mContentProtectionEventProcessor.processEvent(createProcessEvent(TEXT_REQUIRED1));
+        mContentProtectionEventProcessor.processEvent(createProcessEvent(TEXT_REQUIRED2));
 
         for (int type = -100; type <= 100; type++) {
             if (type == TYPE_VIEW_APPEARED) {
                 continue;
             }
-
-            mContentProtectionEventProcessor.processEvent(createEvent(type));
-
-            assertThat(mContentProtectionEventProcessor.mPasswordFieldDetected).isTrue();
-            assertThat(mContentProtectionEventProcessor.mSuspiciousTextDetected).isTrue();
+            ContentCaptureEvent event = createEvent(type);
+            event.setText(TEXT_OPTIONAL1);
+            mContentProtectionEventProcessor.processEvent(event);
         }
 
-        verify(mMockEventBuffer, never()).clear();
-        verify(mMockEventBuffer, never()).toArray();
-        verifyZeroInteractions(mMockContentCaptureManager);
+        assertLoginNotDetected();
     }
 
     @Test
-    public void processEvent_loginDetected() throws Exception {
+    public void processEvent_loginDetected_true_belowResetLimit() throws Exception {
         when(mMockEventBuffer.toArray()).thenReturn(BUFFERED_EVENTS);
-        mContentProtectionEventProcessor.mPasswordFieldDetected = true;
-        mContentProtectionEventProcessor.mSuspiciousTextDetected = true;
 
-        mContentProtectionEventProcessor.processEvent(PROCESS_EVENT);
+        mContentProtectionEventProcessor.processEvent(createProcessEvent(TEXT_REQUIRED1));
+        mContentProtectionEventProcessor.processEvent(createProcessEvent(TEXT_REQUIRED2));
 
-        assertThat(mContentProtectionEventProcessor.mPasswordFieldDetected).isFalse();
-        assertThat(mContentProtectionEventProcessor.mSuspiciousTextDetected).isFalse();
-        verify(mMockEventBuffer).clear();
-        verify(mMockEventBuffer).toArray();
-        assertOnLoginDetected();
-    }
-
-    @Test
-    public void processEvent_loginDetected_passwordFieldNotDetected() {
-        mContentProtectionEventProcessor.mSuspiciousTextDetected = true;
-        assertThat(mContentProtectionEventProcessor.mPasswordFieldDetected).isFalse();
-
-        mContentProtectionEventProcessor.processEvent(PROCESS_EVENT);
-
-        assertThat(mContentProtectionEventProcessor.mPasswordFieldDetected).isFalse();
-        assertThat(mContentProtectionEventProcessor.mSuspiciousTextDetected).isTrue();
-        verify(mMockEventBuffer, never()).clear();
-        verify(mMockEventBuffer, never()).toArray();
-        verifyZeroInteractions(mMockContentCaptureManager);
-    }
-
-    @Test
-    public void processEvent_loginDetected_suspiciousTextNotDetected() {
-        mContentProtectionEventProcessor.mPasswordFieldDetected = true;
-        assertThat(mContentProtectionEventProcessor.mSuspiciousTextDetected).isFalse();
-
-        mContentProtectionEventProcessor.processEvent(PROCESS_EVENT);
-
-        assertThat(mContentProtectionEventProcessor.mPasswordFieldDetected).isTrue();
-        assertThat(mContentProtectionEventProcessor.mSuspiciousTextDetected).isFalse();
-        verify(mMockEventBuffer, never()).clear();
-        verify(mMockEventBuffer, never()).toArray();
-        verifyZeroInteractions(mMockContentCaptureManager);
-    }
-
-    @Test
-    public void processEvent_loginDetected_withoutViewNode() {
-        assertThat(mContentProtectionEventProcessor.mPasswordFieldDetected).isFalse();
-        assertThat(mContentProtectionEventProcessor.mSuspiciousTextDetected).isFalse();
-
-        mContentProtectionEventProcessor.processEvent(PROCESS_EVENT);
-
-        assertThat(mContentProtectionEventProcessor.mPasswordFieldDetected).isFalse();
-        assertThat(mContentProtectionEventProcessor.mSuspiciousTextDetected).isFalse();
-        verify(mMockEventBuffer, never()).clear();
-        verify(mMockEventBuffer, never()).toArray();
-        verifyZeroInteractions(mMockContentCaptureManager);
-    }
-
-    @Test
-    public void processEvent_loginDetected_belowResetLimit() throws Exception {
-        when(mMockEventBuffer.toArray()).thenReturn(BUFFERED_EVENTS);
-        mContentProtectionEventProcessor.mSuspiciousTextDetected = true;
-        ContentCaptureEvent event =
-                createAndroidPasswordFieldEvent(
-                        ANDROID_CLASS_NAME, InputType.TYPE_TEXT_VARIATION_PASSWORD);
-
-        for (int i = 0; i < RESET_LOGIN_TOTAL_EVENTS_TO_PROCESS; i++) {
+        for (int i = 0; i < BUFFER_SIZE - 2; i++) {
             mContentProtectionEventProcessor.processEvent(PROCESS_EVENT);
         }
 
-        assertThat(mContentProtectionEventProcessor.mPasswordFieldDetected).isFalse();
-        assertThat(mContentProtectionEventProcessor.mSuspiciousTextDetected).isTrue();
-        verify(mMockEventBuffer, never()).clear();
-        verify(mMockEventBuffer, never()).toArray();
+        assertLoginNotDetected();
 
-        mContentProtectionEventProcessor.processEvent(event);
+        mContentProtectionEventProcessor.processEvent(createProcessEvent(TEXT_OPTIONAL1));
 
-        assertThat(mContentProtectionEventProcessor.mPasswordFieldDetected).isFalse();
-        assertThat(mContentProtectionEventProcessor.mSuspiciousTextDetected).isFalse();
-        verify(mMockEventBuffer).clear();
-        verify(mMockEventBuffer).toArray();
-        assertOnLoginDetected();
+        assertLoginDetected();
     }
 
     @Test
-    public void processEvent_loginDetected_aboveResetLimit() throws Exception {
-        mContentProtectionEventProcessor.mSuspiciousTextDetected = true;
-        ContentCaptureEvent event =
-                createAndroidPasswordFieldEvent(
-                        ANDROID_CLASS_NAME, InputType.TYPE_TEXT_VARIATION_PASSWORD);
+    public void processEvent_loginDetected_false_aboveResetLimit() {
+        mContentProtectionEventProcessor.processEvent(createProcessEvent(TEXT_REQUIRED1));
+        mContentProtectionEventProcessor.processEvent(createProcessEvent(TEXT_REQUIRED2));
 
-        for (int i = 0; i < RESET_LOGIN_TOTAL_EVENTS_TO_PROCESS + 1; i++) {
+        for (int i = 0; i < BUFFER_SIZE - 1; i++) {
             mContentProtectionEventProcessor.processEvent(PROCESS_EVENT);
         }
 
-        assertThat(mContentProtectionEventProcessor.mPasswordFieldDetected).isFalse();
-        assertThat(mContentProtectionEventProcessor.mSuspiciousTextDetected).isFalse();
-        verify(mMockEventBuffer, never()).clear();
-        verify(mMockEventBuffer, never()).toArray();
+        assertLoginNotDetected();
 
-        mContentProtectionEventProcessor.processEvent(event);
+        mContentProtectionEventProcessor.processEvent(createProcessEvent(TEXT_OPTIONAL1));
 
-        assertThat(mContentProtectionEventProcessor.mPasswordFieldDetected).isTrue();
-        assertThat(mContentProtectionEventProcessor.mSuspiciousTextDetected).isFalse();
-        verify(mMockEventBuffer, never()).clear();
-        verify(mMockEventBuffer, never()).toArray();
+        assertLoginNotDetected();
     }
 
     @Test
     public void processEvent_multipleLoginsDetected_belowFlushThreshold() throws Exception {
         when(mMockEventBuffer.toArray()).thenReturn(BUFFERED_EVENTS);
 
-        mContentProtectionEventProcessor.mPasswordFieldDetected = true;
-        mContentProtectionEventProcessor.mSuspiciousTextDetected = true;
-        mContentProtectionEventProcessor.processEvent(PROCESS_EVENT);
+        for (int i = 0; i < 2; i++) {
+            mContentProtectionEventProcessor.processEvent(createProcessEvent(TEXT_REQUIRED1));
+            mContentProtectionEventProcessor.processEvent(createProcessEvent(TEXT_REQUIRED2));
+            mContentProtectionEventProcessor.processEvent(createProcessEvent(TEXT_OPTIONAL1));
+            mContentProtectionEventProcessor.processEvent(PROCESS_EVENT);
+        }
 
-        mContentProtectionEventProcessor.mPasswordFieldDetected = true;
-        mContentProtectionEventProcessor.mSuspiciousTextDetected = true;
-        mContentProtectionEventProcessor.processEvent(PROCESS_EVENT);
-
-        assertThat(mContentProtectionEventProcessor.mPasswordFieldDetected).isFalse();
-        assertThat(mContentProtectionEventProcessor.mSuspiciousTextDetected).isFalse();
-        verify(mMockEventBuffer).clear();
-        verify(mMockEventBuffer).toArray();
-        assertOnLoginDetected();
+        assertLoginDetected();
     }
 
     @Test
     public void processEvent_multipleLoginsDetected_aboveFlushThreshold() throws Exception {
         when(mMockEventBuffer.toArray()).thenReturn(BUFFERED_EVENTS);
 
-        mContentProtectionEventProcessor.mPasswordFieldDetected = true;
-        mContentProtectionEventProcessor.mSuspiciousTextDetected = true;
+        mContentProtectionEventProcessor.processEvent(createProcessEvent(TEXT_REQUIRED1));
+        mContentProtectionEventProcessor.processEvent(createProcessEvent(TEXT_REQUIRED2));
+        mContentProtectionEventProcessor.processEvent(createProcessEvent(TEXT_OPTIONAL1));
         mContentProtectionEventProcessor.processEvent(PROCESS_EVENT);
 
         mContentProtectionEventProcessor.mLastFlushTime = Instant.now().minusSeconds(5);
 
-        mContentProtectionEventProcessor.mPasswordFieldDetected = true;
-        mContentProtectionEventProcessor.mSuspiciousTextDetected = true;
+        mContentProtectionEventProcessor.processEvent(createProcessEvent(TEXT_REQUIRED1));
+        mContentProtectionEventProcessor.processEvent(createProcessEvent(TEXT_REQUIRED2));
+        mContentProtectionEventProcessor.processEvent(createProcessEvent(TEXT_OPTIONAL1));
         mContentProtectionEventProcessor.processEvent(PROCESS_EVENT);
 
-        assertThat(mContentProtectionEventProcessor.mPasswordFieldDetected).isFalse();
-        assertThat(mContentProtectionEventProcessor.mSuspiciousTextDetected).isFalse();
-        verify(mMockEventBuffer, times(2)).clear();
-        verify(mMockEventBuffer, times(2)).toArray();
-        assertOnLoginDetected(PROCESS_EVENT, /* times= */ 2);
-    }
-
-    @Test
-    public void isPasswordField_android() {
-        ContentCaptureEvent event =
-                createAndroidPasswordFieldEvent(
-                        ANDROID_CLASS_NAME, InputType.TYPE_TEXT_VARIATION_PASSWORD);
-
-        mContentProtectionEventProcessor.processEvent(event);
-
-        assertThat(mContentProtectionEventProcessor.mPasswordFieldDetected).isTrue();
-        verify(mMockEventBuffer, never()).clear();
-        verify(mMockEventBuffer, never()).toArray();
-        verifyZeroInteractions(mMockContentCaptureManager);
-    }
-
-    @Test
-    public void isPasswordField_android_withoutClassName() {
-        ContentCaptureEvent event =
-                createAndroidPasswordFieldEvent(
-                        /* className= */ null, InputType.TYPE_TEXT_VARIATION_PASSWORD);
-
-        mContentProtectionEventProcessor.processEvent(event);
-
-        assertThat(mContentProtectionEventProcessor.mPasswordFieldDetected).isFalse();
-        verify(mMockEventBuffer, never()).clear();
-        verify(mMockEventBuffer, never()).toArray();
-        verifyZeroInteractions(mMockContentCaptureManager);
-    }
-
-    @Test
-    public void isPasswordField_android_wrongClassName() {
-        ContentCaptureEvent event =
-                createAndroidPasswordFieldEvent(
-                        "wrong.prefix" + ANDROID_CLASS_NAME,
-                        InputType.TYPE_TEXT_VARIATION_PASSWORD);
-
-        mContentProtectionEventProcessor.processEvent(event);
-
-        assertThat(mContentProtectionEventProcessor.mPasswordFieldDetected).isFalse();
-        verify(mMockEventBuffer, never()).clear();
-        verify(mMockEventBuffer, never()).toArray();
-        verifyZeroInteractions(mMockContentCaptureManager);
-    }
-
-    @Test
-    public void isPasswordField_android_wrongInputType() {
-        ContentCaptureEvent event =
-                createAndroidPasswordFieldEvent(
-                        ANDROID_CLASS_NAME, InputType.TYPE_TEXT_VARIATION_NORMAL);
-
-        mContentProtectionEventProcessor.processEvent(event);
-
-        assertThat(mContentProtectionEventProcessor.mPasswordFieldDetected).isFalse();
-        verify(mMockEventBuffer, never()).clear();
-        verify(mMockEventBuffer, never()).toArray();
-        verifyZeroInteractions(mMockContentCaptureManager);
-    }
-
-    @Test
-    public void isPasswordField_webView() throws Exception {
-        ContentCaptureEvent event =
-                createWebViewPasswordFieldEvent(
-                        /* className= */ null, /* eventText= */ null, PASSWORD_TEXT);
-        when(mMockEventBuffer.toArray()).thenReturn(new ContentCaptureEvent[] {event});
-
-        mContentProtectionEventProcessor.processEvent(event);
-
-        assertThat(mContentProtectionEventProcessor.mPasswordFieldDetected).isFalse();
-        verify(mMockEventBuffer).clear();
-        verify(mMockEventBuffer).toArray();
-        assertOnLoginDetected(event, /* times= */ 1);
-    }
-
-    @Test
-    public void isPasswordField_webView_withClassName() {
-        ContentCaptureEvent event =
-                createWebViewPasswordFieldEvent(
-                        /* className= */ "any.class.name", /* eventText= */ null, PASSWORD_TEXT);
-
-        mContentProtectionEventProcessor.processEvent(event);
-
-        assertThat(mContentProtectionEventProcessor.mPasswordFieldDetected).isFalse();
-        verify(mMockEventBuffer, never()).clear();
-        verify(mMockEventBuffer, never()).toArray();
-        verifyZeroInteractions(mMockContentCaptureManager);
-    }
-
-    @Test
-    public void isPasswordField_webView_withSafeViewNodeText() {
-        ContentCaptureEvent event =
-                createWebViewPasswordFieldEvent(
-                        /* className= */ null, /* eventText= */ null, SAFE_TEXT);
-
-        mContentProtectionEventProcessor.processEvent(event);
-
-        assertThat(mContentProtectionEventProcessor.mPasswordFieldDetected).isFalse();
-        verify(mMockEventBuffer, never()).clear();
-        verify(mMockEventBuffer, never()).toArray();
-        verifyZeroInteractions(mMockContentCaptureManager);
-    }
-
-    @Test
-    public void isPasswordField_webView_withEventText() {
-        ContentCaptureEvent event =
-                createWebViewPasswordFieldEvent(/* className= */ null, PASSWORD_TEXT, SAFE_TEXT);
-
-        mContentProtectionEventProcessor.processEvent(event);
-
-        assertThat(mContentProtectionEventProcessor.mPasswordFieldDetected).isFalse();
-        verify(mMockEventBuffer, never()).clear();
-        verify(mMockEventBuffer, never()).toArray();
-        verifyZeroInteractions(mMockContentCaptureManager);
-    }
-
-    @Test
-    public void isSuspiciousText_withSafeText() {
-        ContentCaptureEvent event = createSuspiciousTextEvent(SAFE_TEXT, SAFE_TEXT);
-
-        mContentProtectionEventProcessor.processEvent(event);
-
-        assertThat(mContentProtectionEventProcessor.mSuspiciousTextDetected).isFalse();
-        verify(mMockEventBuffer, never()).clear();
-        verify(mMockEventBuffer, never()).toArray();
-        verifyZeroInteractions(mMockContentCaptureManager);
-    }
-
-    @Test
-    public void isSuspiciousText_eventText_suspiciousText() {
-        ContentCaptureEvent event = createSuspiciousTextEvent(SUSPICIOUS_TEXT, SAFE_TEXT);
-
-        mContentProtectionEventProcessor.processEvent(event);
-
-        assertThat(mContentProtectionEventProcessor.mSuspiciousTextDetected).isTrue();
-        verify(mMockEventBuffer, never()).clear();
-        verify(mMockEventBuffer, never()).toArray();
-        verifyZeroInteractions(mMockContentCaptureManager);
-    }
-
-    @Test
-    public void isSuspiciousText_viewNodeText_suspiciousText() {
-        ContentCaptureEvent event = createSuspiciousTextEvent(SAFE_TEXT, SUSPICIOUS_TEXT);
-
-        mContentProtectionEventProcessor.processEvent(event);
-
-        assertThat(mContentProtectionEventProcessor.mSuspiciousTextDetected).isTrue();
-        verify(mMockEventBuffer, never()).clear();
-        verify(mMockEventBuffer, never()).toArray();
-        verifyZeroInteractions(mMockContentCaptureManager);
-    }
-
-    @Test
-    public void isSuspiciousText_eventText_passwordText() {
-        ContentCaptureEvent event = createSuspiciousTextEvent(PASSWORD_TEXT, SAFE_TEXT);
-
-        mContentProtectionEventProcessor.processEvent(event);
-
-        assertThat(mContentProtectionEventProcessor.mSuspiciousTextDetected).isTrue();
-        verify(mMockEventBuffer, never()).clear();
-        verify(mMockEventBuffer, never()).toArray();
-        verifyZeroInteractions(mMockContentCaptureManager);
-    }
-
-    @Test
-    public void isSuspiciousText_viewNodeText_passwordText() {
-        // Specify the class to differ from {@link isPasswordField_webView} test in this version
-        ContentCaptureEvent event =
-                createProcessEvent(
-                        "test.class.not.a.web.view", /* inputType= */ 0, SAFE_TEXT, PASSWORD_TEXT);
-
-        mContentProtectionEventProcessor.processEvent(event);
-
-        assertThat(mContentProtectionEventProcessor.mSuspiciousTextDetected).isTrue();
-        verify(mMockEventBuffer, never()).clear();
-        verify(mMockEventBuffer, never()).toArray();
-        verifyZeroInteractions(mMockContentCaptureManager);
+        assertLoginDetected(times(2));
     }
 
     private static ContentCaptureEvent createEvent(int type) {
@@ -511,20 +415,20 @@
         return createEvent(TYPE_VIEW_APPEARED);
     }
 
+    private ContentCaptureEvent createProcessEvent(@Nullable String eventText) {
+        return createProcessEvent(eventText, /* viewNodeText= */ null, /* hintText= */ null);
+    }
+
     private ContentCaptureEvent createProcessEvent(
-            @Nullable String className,
-            int inputType,
-            @Nullable String eventText,
-            @Nullable String viewNodeText) {
+            @Nullable String eventText, @Nullable String viewNodeText, @Nullable String hintText) {
         View view = new View(mContext);
         ViewStructureImpl viewStructure = new ViewStructureImpl(view);
-        if (className != null) {
-            viewStructure.setClassName(className);
-        }
         if (viewNodeText != null) {
             viewStructure.setText(viewNodeText);
         }
-        viewStructure.setInputType(inputType);
+        if (hintText != null) {
+            viewStructure.setHint(hintText);
+        }
 
         ContentCaptureEvent event = createProcessEvent();
         event.setViewNode(viewStructure.getNode());
@@ -535,34 +439,28 @@
         return event;
     }
 
-    private ContentCaptureEvent createAndroidPasswordFieldEvent(
-            @Nullable String className, int inputType) {
-        return createProcessEvent(
-                className, inputType, /* eventText= */ null, /* viewNodeText= */ null);
+    private void assertLoginNotDetected() {
+        mTestLooper.dispatchAll();
+        verify(mMockEventBuffer, never()).clear();
+        verify(mMockEventBuffer, never()).toArray();
+        verifyZeroInteractions(mMockContentCaptureManager);
     }
 
-    private ContentCaptureEvent createWebViewPasswordFieldEvent(
-            @Nullable String className, @Nullable String eventText, @Nullable String viewNodeText) {
-        return createProcessEvent(className, /* inputType= */ 0, eventText, viewNodeText);
+    private void assertLoginDetected() throws Exception {
+        assertLoginDetected(times(1));
     }
 
-    private ContentCaptureEvent createSuspiciousTextEvent(
-            @Nullable String eventText, @Nullable String viewNodeText) {
-        return createProcessEvent(
-                /* className= */ null, /* inputType= */ 0, eventText, viewNodeText);
-    }
+    private void assertLoginDetected(@NonNull VerificationMode verificationMode) throws Exception {
+        mTestLooper.dispatchAll();
+        verify(mMockEventBuffer, verificationMode).clear();
+        verify(mMockEventBuffer, verificationMode).toArray();
 
-    private void assertOnLoginDetected() throws Exception {
-        assertOnLoginDetected(PROCESS_EVENT, /* times= */ 1);
-    }
-
-    private void assertOnLoginDetected(ContentCaptureEvent event, int times) throws Exception {
         ArgumentCaptor<ParceledListSlice> captor = ArgumentCaptor.forClass(ParceledListSlice.class);
-        verify(mMockContentCaptureManager, times(times)).onLoginDetected(captor.capture());
+        verify(mMockContentCaptureManager, verificationMode).onLoginDetected(captor.capture());
 
         assertThat(captor.getValue()).isNotNull();
         List<ContentCaptureEvent> actual = captor.getValue().getList();
         assertThat(actual).isNotNull();
-        assertThat(actual).containsExactly(event);
+        assertThat(actual).containsExactly(PROCESS_EVENT);
     }
 }
diff --git a/core/tests/coretests/src/android/view/contentprotection/ContentProtectionUtilsTest.java b/core/tests/coretests/src/android/view/contentprotection/ContentProtectionUtilsTest.java
index 1459799..fbe478e 100644
--- a/core/tests/coretests/src/android/view/contentprotection/ContentProtectionUtilsTest.java
+++ b/core/tests/coretests/src/android/view/contentprotection/ContentProtectionUtilsTest.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (C) 2013 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.
@@ -44,68 +44,74 @@
 
     private static final String TEXT = "TEST_TEXT";
 
-    private static final ContentCaptureEvent EVENT = createEvent();
-
-    private static final ViewNode VIEW_NODE = new ViewNode();
-
-    private static final ViewNode VIEW_NODE_WITH_TEXT = createViewNodeWithText();
+    private static final String TEXT_LOWER = TEXT.toLowerCase();
 
     @Test
-    public void event_getEventText_null() {
-        String actual = ContentProtectionUtils.getEventText(EVENT);
+    public void getEventTextLower_null() {
+        String actual = ContentProtectionUtils.getEventTextLower(createEvent());
 
         assertThat(actual).isNull();
     }
 
     @Test
-    public void event_getEventText_notNull() {
-        ContentCaptureEvent event = createEvent();
-        event.setText(TEXT);
+    public void getEventTextLower_notNull() {
+        String actual = ContentProtectionUtils.getEventTextLower(createEventWithText());
 
-        String actual = ContentProtectionUtils.getEventText(event);
-
-        assertThat(actual).isEqualTo(TEXT);
+        assertThat(actual).isEqualTo(TEXT_LOWER);
     }
 
     @Test
-    public void event_getViewNodeText_null() {
-        String actual = ContentProtectionUtils.getViewNodeText(EVENT);
+    public void getViewNodeTextLower_null() {
+        String actual = ContentProtectionUtils.getViewNodeTextLower(new ViewNode());
 
         assertThat(actual).isNull();
     }
 
     @Test
-    public void event_getViewNodeText_notNull() {
-        ContentCaptureEvent event = createEvent();
-        event.setViewNode(VIEW_NODE_WITH_TEXT);
+    public void getViewNodeTextLower_notNull() {
+        String actual = ContentProtectionUtils.getViewNodeTextLower(createViewNodeWithText());
 
-        String actual = ContentProtectionUtils.getViewNodeText(event);
-
-        assertThat(actual).isEqualTo(TEXT);
+        assertThat(actual).isEqualTo(TEXT_LOWER);
     }
 
     @Test
-    public void viewNode_getViewNodeText_null() {
-        String actual = ContentProtectionUtils.getViewNodeText(VIEW_NODE);
+    public void getHintTextLower_null() {
+        String actual = ContentProtectionUtils.getHintTextLower(new ViewNode());
 
         assertThat(actual).isNull();
     }
 
     @Test
-    public void viewNode_getViewNodeText_notNull() {
-        String actual = ContentProtectionUtils.getViewNodeText(VIEW_NODE_WITH_TEXT);
+    public void getHintTextLower_notNull() {
+        String actual = ContentProtectionUtils.getHintTextLower(createViewNodeWithHint());
 
-        assertThat(actual).isEqualTo(TEXT);
+        assertThat(actual).isEqualTo(TEXT_LOWER);
     }
 
     private static ContentCaptureEvent createEvent() {
         return new ContentCaptureEvent(/* sessionId= */ 123, TYPE_SESSION_STARTED);
     }
 
-    private static ViewNode createViewNodeWithText() {
+    private static ContentCaptureEvent createEventWithText() {
+        ContentCaptureEvent event = createEvent();
+        event.setText(TEXT);
+        return event;
+    }
+
+    private static ViewStructureImpl createViewStructureImpl() {
         View view = new View(ApplicationProvider.getApplicationContext());
-        ViewStructureImpl viewStructure = new ViewStructureImpl(view);
-        viewStructure.setText(TEXT);
-        return viewStructure.getNode();
+        return new ViewStructureImpl(view);
+    }
+
+    private static ViewNode createViewNodeWithText() {
+        ViewStructureImpl viewStructureImpl = createViewStructureImpl();
+        viewStructureImpl.setText(TEXT);
+        return viewStructureImpl.getNode();
+    }
+
+    private static ViewNode createViewNodeWithHint() {
+        ViewStructureImpl viewStructureImpl = createViewStructureImpl();
+        viewStructureImpl.setHint(TEXT);
+        return viewStructureImpl.getNode();
     }
 }
diff --git a/core/tests/coretests/src/android/widget/HorizontalScrollViewFunctionalTest.java b/core/tests/coretests/src/android/widget/HorizontalScrollViewFunctionalTest.java
index 86f26e5..df212eb 100644
--- a/core/tests/coretests/src/android/widget/HorizontalScrollViewFunctionalTest.java
+++ b/core/tests/coretests/src/android/widget/HorizontalScrollViewFunctionalTest.java
@@ -17,7 +17,9 @@
 package android.widget;
 
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
 
+import android.content.Context;
 import android.platform.test.annotations.Presubmit;
 import android.util.PollingCheck;
 
@@ -32,6 +34,9 @@
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
 @RunWith(AndroidJUnit4.class)
 @MediumTest
 @Presubmit
@@ -49,23 +54,43 @@
     }
 
     @Test
-    public void testScrollAfterFlingTop() {
-        mHorizontalScrollView.scrollTo(100, 0);
-        mHorizontalScrollView.fling(-10000);
-        PollingCheck.waitFor(() -> mHorizontalScrollView.mEdgeGlowLeft.getDistance() > 0);
-        PollingCheck.waitFor(() -> mHorizontalScrollView.mEdgeGlowLeft.getDistance() == 0f);
+    public void testScrollAfterFlingLeft() throws Throwable {
+        WatchedEdgeEffect edgeEffect = new WatchedEdgeEffect(mActivity);
+        mHorizontalScrollView.mEdgeGlowLeft = edgeEffect;
+        mActivityRule.runOnUiThread(() -> mHorizontalScrollView.scrollTo(100, 0));
+        mActivityRule.runOnUiThread(() -> mHorizontalScrollView.fling(-10000));
+        assertTrue(edgeEffect.onAbsorbLatch.await(1, TimeUnit.SECONDS));
+        mActivityRule.runOnUiThread(() -> {}); // let the absorb takes effect -- least one frame
+        PollingCheck.waitFor(() -> edgeEffect.getDistance() == 0f);
         assertEquals(0, mHorizontalScrollView.getScrollX());
     }
 
     @Test
-    public void testScrollAfterFlingBottom() {
+    public void testScrollAfterFlingRight() throws Throwable {
+        WatchedEdgeEffect edgeEffect = new WatchedEdgeEffect(mActivity);
+        mHorizontalScrollView.mEdgeGlowRight = edgeEffect;
         int childWidth = mHorizontalScrollView.getChildAt(0).getWidth();
         int maxScroll = childWidth - mHorizontalScrollView.getWidth();
-        mHorizontalScrollView.scrollTo(maxScroll - 100, 0);
-        mHorizontalScrollView.fling(10000);
-        PollingCheck.waitFor(() -> mHorizontalScrollView.mEdgeGlowRight.getDistance() > 0);
+        mActivityRule.runOnUiThread(() -> mHorizontalScrollView.scrollTo(maxScroll - 100, 0));
+        mActivityRule.runOnUiThread(() -> mHorizontalScrollView.fling(10000));
+        assertTrue(edgeEffect.onAbsorbLatch.await(1, TimeUnit.SECONDS));
+        mActivityRule.runOnUiThread(() -> {}); // let the absorb takes effect -- at least one frame
         PollingCheck.waitFor(() -> mHorizontalScrollView.mEdgeGlowRight.getDistance() == 0f);
         assertEquals(maxScroll, mHorizontalScrollView.getScrollX());
     }
+
+    static class WatchedEdgeEffect extends EdgeEffect {
+        public CountDownLatch onAbsorbLatch = new CountDownLatch(1);
+
+        WatchedEdgeEffect(Context context) {
+            super(context);
+        }
+
+        @Override
+        public void onAbsorb(int velocity) {
+            super.onAbsorb(velocity);
+            onAbsorbLatch.countDown();
+        }
+    }
 }
 
diff --git a/keystore/java/android/security/keystore/KeyGenParameterSpec.java b/keystore/java/android/security/keystore/KeyGenParameterSpec.java
index 96c257b..1ba41b1 100644
--- a/keystore/java/android/security/keystore/KeyGenParameterSpec.java
+++ b/keystore/java/android/security/keystore/KeyGenParameterSpec.java
@@ -75,16 +75,18 @@
  * {@link java.security.interfaces.ECPublicKey} or {@link java.security.interfaces.RSAPublicKey}
  * interfaces.
  *
- * <p>For asymmetric key pairs, a self-signed X.509 certificate will be also generated and stored in
- * the Android Keystore. This is because the {@link java.security.KeyStore} abstraction does not
- * support storing key pairs without a certificate. The subject, serial number, and validity dates
- * of the certificate can be customized in this spec. The self-signed certificate may be replaced at
- * a later time by a certificate signed by a Certificate Authority (CA).
+ * <p>For asymmetric key pairs, a X.509 certificate will be also generated and stored in the Android
+ * Keystore. This is because the {@link java.security.KeyStore} abstraction does not support storing
+ * key pairs without a certificate. The subject, serial number, and validity dates of the
+ * certificate can be customized in this spec. The certificate may be replaced at a later time by a
+ * certificate signed by a Certificate Authority (CA).
  *
- * <p>NOTE: If a private key is not authorized to sign the self-signed certificate, then the
- * certificate will be created with an invalid signature which will not verify. Such a certificate
- * is still useful because it provides access to the public key. To generate a valid signature for
- * the certificate the key needs to be authorized for all of the following:
+ * <p>NOTE: If attestation is not requested using {@link Builder#setAttestationChallenge(byte[])},
+ * generated certificate may be self-signed. If a private key is not authorized to sign the
+ * certificate, then the certificate will be created with an invalid signature which will not
+ * verify. Such a certificate is still useful because it provides access to the public key. To
+ * generate a valid signature for the certificate the key needs to be authorized for all of the
+ * following:
  * <ul>
  * <li>{@link KeyProperties#PURPOSE_SIGN},</li>
  * <li>operation without requiring the user to be authenticated (see
@@ -989,12 +991,6 @@
          * @param purposes set of purposes (e.g., encrypt, decrypt, sign) for which the key can be
          *        used. Attempts to use the key for any other purpose will be rejected.
          *
-         *        <p>If the set of purposes for which the key can be used does not contain
-         *        {@link KeyProperties#PURPOSE_SIGN}, the self-signed certificate generated by
-         *        {@link KeyPairGenerator} of {@code AndroidKeyStore} provider will contain an
-         *        invalid signature. This is OK if the certificate is only used for obtaining the
-         *        public key from Android KeyStore.
-         *
          *        <p>See {@link KeyProperties}.{@code PURPOSE} flags.
          */
         public Builder(@NonNull String keystoreAlias, @KeyProperties.PurposeEnum int purposes) {
@@ -1140,7 +1136,7 @@
         }
 
         /**
-         * Sets the subject used for the self-signed certificate of the generated key pair.
+         * Sets the subject used for the certificate of the generated key pair.
          *
          * <p>By default, the subject is {@code CN=fake}.
          */
@@ -1154,7 +1150,7 @@
         }
 
         /**
-         * Sets the serial number used for the self-signed certificate of the generated key pair.
+         * Sets the serial number used for the certificate of the generated key pair.
          *
          * <p>By default, the serial number is {@code 1}.
          */
@@ -1168,8 +1164,7 @@
         }
 
         /**
-         * Sets the start of the validity period for the self-signed certificate of the generated
-         * key pair.
+         * Sets the start of the validity period for the certificate of the generated key pair.
          *
          * <p>By default, this date is {@code Jan 1 1970}.
          */
@@ -1183,8 +1178,7 @@
         }
 
         /**
-         * Sets the end of the validity period for the self-signed certificate of the generated key
-         * pair.
+         * Sets the end of the validity period for the certificate of the generated key pair.
          *
          * <p>By default, this date is {@code Jan 1 2048}.
          */
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/OWNERS b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/OWNERS
new file mode 100644
index 0000000..74a29dd
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/OWNERS
@@ -0,0 +1 @@
+hwwang@google.com
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip2Module.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip2Module.java
index 8a64037..1898ea7 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip2Module.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/pip/Pip2Module.java
@@ -17,19 +17,28 @@
 package com.android.wm.shell.dagger.pip;
 
 import android.annotation.NonNull;
+import android.content.Context;
 
 import com.android.wm.shell.ShellTaskOrganizer;
+import com.android.wm.shell.common.DisplayController;
+import com.android.wm.shell.common.DisplayInsetsController;
 import com.android.wm.shell.common.pip.PipBoundsAlgorithm;
 import com.android.wm.shell.common.pip.PipBoundsState;
+import com.android.wm.shell.common.pip.PipDisplayLayoutState;
+import com.android.wm.shell.common.pip.PipUtils;
 import com.android.wm.shell.dagger.WMShellBaseModule;
 import com.android.wm.shell.dagger.WMSingleton;
+import com.android.wm.shell.pip2.phone.PipController;
 import com.android.wm.shell.pip2.phone.PipTransition;
+import com.android.wm.shell.sysui.ShellController;
 import com.android.wm.shell.sysui.ShellInit;
 import com.android.wm.shell.transition.Transitions;
 
 import dagger.Module;
 import dagger.Provides;
 
+import java.util.Optional;
+
 /**
  * Provides dependencies from {@link com.android.wm.shell.pip2}, this implementation is meant to be
  * the successor of its sibling {@link Pip1Module}.
@@ -42,8 +51,26 @@
             @NonNull ShellTaskOrganizer shellTaskOrganizer,
             @NonNull Transitions transitions,
             PipBoundsState pipBoundsState,
-            PipBoundsAlgorithm pipBoundsAlgorithm) {
+            PipBoundsAlgorithm pipBoundsAlgorithm,
+            Optional<PipController> pipController) {
         return new PipTransition(shellInit, shellTaskOrganizer, transitions, pipBoundsState, null,
                 pipBoundsAlgorithm);
     }
+
+    @WMSingleton
+    @Provides
+    static Optional<PipController> providePipController(Context context,
+            ShellInit shellInit,
+            ShellController shellController,
+            DisplayController displayController,
+            DisplayInsetsController displayInsetsController,
+            PipDisplayLayoutState pipDisplayLayoutState) {
+        if (!PipUtils.isPip2ExperimentEnabled()) {
+            return Optional.empty();
+        } else {
+            return Optional.ofNullable(PipController.create(
+                    context, shellInit, shellController, displayController, displayInsetsController,
+                    pipDisplayLayoutState));
+        }
+    }
 }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipController.java
new file mode 100644
index 0000000..186cb61
--- /dev/null
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip2/phone/PipController.java
@@ -0,0 +1,133 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.wm.shell.pip2.phone;
+
+import static android.content.pm.PackageManager.FEATURE_PICTURE_IN_PICTURE;
+
+import android.content.Context;
+import android.content.res.Configuration;
+import android.view.InsetsState;
+
+import com.android.internal.protolog.common.ProtoLog;
+import com.android.wm.shell.common.DisplayController;
+import com.android.wm.shell.common.DisplayInsetsController;
+import com.android.wm.shell.common.DisplayLayout;
+import com.android.wm.shell.common.pip.PipDisplayLayoutState;
+import com.android.wm.shell.common.pip.PipUtils;
+import com.android.wm.shell.protolog.ShellProtoLogGroup;
+import com.android.wm.shell.sysui.ConfigurationChangeListener;
+import com.android.wm.shell.sysui.ShellController;
+import com.android.wm.shell.sysui.ShellInit;
+
+/**
+ * Manages the picture-in-picture (PIP) UI and states for Phones.
+ */
+public class PipController implements ConfigurationChangeListener,
+        DisplayController.OnDisplaysChangedListener {
+    private static final String TAG = PipController.class.getSimpleName();
+
+    private Context mContext;
+    private ShellController mShellController;
+    private DisplayController mDisplayController;
+    private DisplayInsetsController mDisplayInsetsController;
+    private PipDisplayLayoutState mPipDisplayLayoutState;
+
+    private PipController(Context context,
+            ShellInit shellInit,
+            ShellController shellController,
+            DisplayController displayController,
+            DisplayInsetsController displayInsetsController,
+            PipDisplayLayoutState pipDisplayLayoutState) {
+        mContext = context;
+        mShellController = shellController;
+        mDisplayController = displayController;
+        mDisplayInsetsController = displayInsetsController;
+        mPipDisplayLayoutState = pipDisplayLayoutState;
+
+        if (PipUtils.isPip2ExperimentEnabled()) {
+            shellInit.addInitCallback(this::onInit, this);
+        }
+    }
+
+    private void onInit() {
+        // Ensure that we have the display info in case we get calls to update the bounds before the
+        // listener calls back
+        mPipDisplayLayoutState.setDisplayId(mContext.getDisplayId());
+        DisplayLayout layout = new DisplayLayout(mContext, mContext.getDisplay());
+        mPipDisplayLayoutState.setDisplayLayout(layout);
+
+        mShellController.addConfigurationChangeListener(this);
+        mDisplayController.addDisplayWindowListener(this);
+        mDisplayInsetsController.addInsetsChangedListener(mPipDisplayLayoutState.getDisplayId(),
+                new DisplayInsetsController.OnInsetsChangedListener() {
+                    @Override
+                    public void insetsChanged(InsetsState insetsState) {
+                        onDisplayChanged(mDisplayController
+                                        .getDisplayLayout(mPipDisplayLayoutState.getDisplayId()));
+                    }
+                });
+    }
+
+    /**
+     * Instantiates {@link PipController}, returns {@code null} if the feature not supported.
+     */
+    public static PipController create(Context context,
+            ShellInit shellInit,
+            ShellController shellController,
+            DisplayController displayController,
+            DisplayInsetsController displayInsetsController,
+            PipDisplayLayoutState pipDisplayLayoutState) {
+        if (!context.getPackageManager().hasSystemFeature(FEATURE_PICTURE_IN_PICTURE)) {
+            ProtoLog.w(ShellProtoLogGroup.WM_SHELL_PICTURE_IN_PICTURE,
+                    "%s: Device doesn't support Pip feature", TAG);
+            return null;
+        }
+        return new PipController(context, shellInit, shellController, displayController,
+                displayInsetsController, pipDisplayLayoutState);
+    }
+
+
+    @Override
+    public void onConfigurationChanged(Configuration newConfiguration) {
+        mPipDisplayLayoutState.onConfigurationChanged();
+    }
+
+    @Override
+    public void onThemeChanged() {
+        onDisplayChanged(new DisplayLayout(mContext, mContext.getDisplay()));
+    }
+
+    @Override
+    public void onDisplayAdded(int displayId) {
+        if (displayId != mPipDisplayLayoutState.getDisplayId()) {
+            return;
+        }
+        onDisplayChanged(mDisplayController.getDisplayLayout(displayId));
+    }
+
+    @Override
+    public void onDisplayConfigurationChanged(int displayId, Configuration newConfig) {
+        if (displayId != mPipDisplayLayoutState.getDisplayId()) {
+            return;
+        }
+        onDisplayChanged(mDisplayController.getDisplayLayout(displayId));
+    }
+
+    private void onDisplayChanged(DisplayLayout layout) {
+        mPipDisplayLayoutState.setDisplayLayout(layout);
+    }
+}
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 d31476c..d277eef 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
@@ -925,19 +925,8 @@
                 if (toHome) wct.reorder(mRecentsTask, true /* toTop */);
                 else wct.restoreTransientOrder(mRecentsTask);
             }
-            if (!toHome
-                    // If a recents gesture starts on the 3p launcher, then the 3p launcher is the
-                    // live tile (pausing app). If the gesture is "cancelled" we need to return to
-                    // 3p launcher instead of "task-switching" away from it.
-                    && (!mWillFinishToHome || mPausingSeparateHome)
-                    && mPausingTasks != null && mState == STATE_NORMAL) {
-                if (mPausingSeparateHome) {
-                    ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION,
-                            "  returning to 3p home");
-                } else {
-                    ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION,
-                            "  returning to app");
-                }
+            if (!toHome && !mWillFinishToHome && mPausingTasks != null && mState == STATE_NORMAL) {
+                ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION, "  returning to app");
                 // The gesture is returning to the pausing-task(s) rather than continuing with
                 // recents, so end the transition by moving the app back to the top (and also
                 // re-showing it's task).
@@ -969,6 +958,15 @@
                     wct.restoreTransientOrder(mRecentsTask);
                 }
             } else {
+                if (mPausingSeparateHome) {
+                    if (mOpeningTasks.isEmpty()) {
+                        ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION,
+                                "  recents occluded 3p home");
+                    } else {
+                        ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION,
+                                "  switch task by recents on 3p home");
+                    }
+                }
                 ProtoLog.v(ShellProtoLogGroup.WM_SHELL_RECENTS_TRANSITION, "  normal finish");
                 // The general case: committing to recents, going home, or switching tasks.
                 for (int i = 0; i < mOpeningTasks.size(); ++i) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java
index 0548a8e..d0e647b 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/windowdecor/WindowDecoration.java
@@ -297,7 +297,7 @@
         }
 
         // Task surface itself
-        float shadowRadius = loadDimension(resources, params.mShadowRadiusId);
+        float shadowRadius;
         final Point taskPosition = mTaskInfo.positionInParent;
         if (isFullscreen) {
             // Setting the task crop to the width/height stops input events from being sent to
@@ -308,9 +308,12 @@
             // drag-resized by the window decoration.
             startT.setWindowCrop(mTaskSurface, null);
             finishT.setWindowCrop(mTaskSurface, null);
+            // Shadow is not needed for fullscreen tasks
+            shadowRadius = 0;
         } else {
             startT.setWindowCrop(mTaskSurface, outResult.mWidth, outResult.mHeight);
             finishT.setWindowCrop(mTaskSurface, outResult.mWidth, outResult.mHeight);
+            shadowRadius = loadDimension(resources, params.mShadowRadiusId);
         }
         startT.setShadowRadius(mTaskSurface, shadowRadius)
                 .show(mTaskSurface);
diff --git a/media/java/android/media/RingtoneV1.java b/media/java/android/media/RingtoneV1.java
index b761afa..3c54d4a 100644
--- a/media/java/android/media/RingtoneV1.java
+++ b/media/java/android/media/RingtoneV1.java
@@ -16,14 +16,15 @@
 
 package android.media;
 
-import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.compat.annotation.UnsupportedAppUsage;
 import android.content.Context;
 import android.content.res.AssetFileDescriptor;
 import android.content.res.Resources.NotFoundException;
 import android.media.audiofx.HapticGenerator;
 import android.net.Uri;
 import android.os.Binder;
+import android.os.Build;
 import android.os.RemoteException;
 import android.os.Trace;
 import android.os.VibrationEffect;
@@ -61,7 +62,6 @@
 
     private final Context mContext;
     private final AudioManager mAudioManager;
-    private final Ringtone.Injectables mInjectables;
     private VolumeShaper.Configuration mVolumeShaperConfig;
     private VolumeShaper mVolumeShaper;
 
@@ -74,10 +74,12 @@
     private final IRingtonePlayer mRemotePlayer;
     private final Binder mRemoteToken;
 
+    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
     private MediaPlayer mLocalPlayer;
     private final MyOnCompletionListener mCompletionListener = new MyOnCompletionListener();
     private HapticGenerator mHapticGenerator;
 
+    @UnsupportedAppUsage
     private Uri mUri;
     private String mTitle;
 
@@ -92,15 +94,10 @@
     private boolean mHapticGeneratorEnabled = false;
     private final Object mPlaybackSettingsLock = new Object();
 
-    /** @hide */
+    /** {@hide} */
+    @UnsupportedAppUsage
     public RingtoneV1(Context context, boolean allowRemote) {
-        this(context, new Ringtone.Injectables(), allowRemote);
-    }
-
-    /** @hide */
-    RingtoneV1(Context context, @NonNull Ringtone.Injectables injectables, boolean allowRemote) {
         mContext = context;
-        mInjectables = injectables;
         mAudioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);
         mAllowRemote = allowRemote;
         mRemotePlayer = allowRemote ? mAudioManager.getRingtonePlayer() : null;
@@ -203,7 +200,7 @@
         }
         destroyLocalPlayer();
         // try opening uri locally before delegating to remote player
-        mLocalPlayer = mInjectables.newMediaPlayer();
+        mLocalPlayer = new MediaPlayer();
         try {
             mLocalPlayer.setDataSource(mContext, mUri);
             mLocalPlayer.setAudioAttributes(mAudioAttributes);
@@ -243,7 +240,19 @@
      */
     public boolean hasHapticChannels() {
         // FIXME: support remote player, or internalize haptic channels support and remove entirely.
-        return mInjectables.hasHapticChannels(mLocalPlayer);
+        try {
+            android.os.Trace.beginSection("Ringtone.hasHapticChannels");
+            if (mLocalPlayer != null) {
+                for(MediaPlayer.TrackInfo trackInfo : mLocalPlayer.getTrackInfo()) {
+                    if (trackInfo.hasHapticChannels()) {
+                        return true;
+                    }
+                }
+            }
+        } finally {
+            android.os.Trace.endSection();
+        }
+        return false;
     }
 
     /**
@@ -325,7 +334,7 @@
      * @see android.media.audiofx.HapticGenerator#isAvailable()
      */
     public boolean setHapticGeneratorEnabled(boolean enabled) {
-        if (!mInjectables.isHapticGeneratorAvailable()) {
+        if (!HapticGenerator.isAvailable()) {
             return false;
         }
         synchronized (mPlaybackSettingsLock) {
@@ -353,7 +362,7 @@
             mLocalPlayer.setVolume(mVolume);
             mLocalPlayer.setLooping(mIsLooping);
             if (mHapticGenerator == null && mHapticGeneratorEnabled) {
-                mHapticGenerator = mInjectables.createHapticGenerator(mLocalPlayer);
+                mHapticGenerator = HapticGenerator.create(mLocalPlayer.getAudioSessionId());
             }
             if (mHapticGenerator != null) {
                 mHapticGenerator.setEnabled(mHapticGeneratorEnabled);
@@ -388,6 +397,7 @@
      *
      * @hide
      */
+    @UnsupportedAppUsage
     public void setUri(Uri uri) {
         setUri(uri, null);
     }
@@ -415,6 +425,7 @@
     }
 
     /** {@hide} */
+    @UnsupportedAppUsage
     public Uri getUri() {
         return mUri;
     }
@@ -545,7 +556,7 @@
                 Log.e(TAG, "Could not load fallback ringtone");
                 return false;
             }
-            mLocalPlayer = mInjectables.newMediaPlayer();
+            mLocalPlayer = new MediaPlayer();
             if (afd.getDeclaredLength() < 0) {
                 mLocalPlayer.setDataSource(afd.getFileDescriptor());
             } else {
@@ -583,12 +594,12 @@
     }
 
     public boolean isLocalOnly() {
-        return !mAllowRemote;
+        return mAllowRemote;
     }
 
     public boolean isUsingRemotePlayer() {
         // V2 testing api, but this is the v1 approximation.
-        return (mLocalPlayer == null) && mAllowRemote && (mRemotePlayer != null) && (mUri != null);
+        return (mLocalPlayer == null) && mAllowRemote && (mRemotePlayer != null);
     }
 
     class MyOnCompletionListener implements MediaPlayer.OnCompletionListener {
diff --git a/media/java/android/media/projection/IMediaProjectionManager.aidl b/media/java/android/media/projection/IMediaProjectionManager.aidl
index 80e2247..31e65eb 100644
--- a/media/java/android/media/projection/IMediaProjectionManager.aidl
+++ b/media/java/android/media/projection/IMediaProjectionManager.aidl
@@ -175,5 +175,5 @@
     @EnforcePermission("android.Manifest.permission.MANAGE_MEDIA_PROJECTION")
     @JavaPassthrough(annotation = "@android.annotation.RequiresPermission(android.Manifest"
             + ".permission.MANAGE_MEDIA_PROJECTION)")
-    void notifyPermissionRequestStateChange(int hostUid, int state, int sessionCreationSource);
+    oneway void notifyPermissionRequestStateChange(int hostUid, int state, int sessionCreationSource);
 }
diff --git a/media/tests/ringtone/src/com/android/media/RingtoneBuilderTest.java b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/RingtoneTest.java
similarity index 69%
rename from media/tests/ringtone/src/com/android/media/RingtoneBuilderTest.java
rename to media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/RingtoneTest.java
index 2c8daba..3c0c684 100644
--- a/media/tests/ringtone/src/com/android/media/RingtoneBuilderTest.java
+++ b/media/tests/MediaFrameworkTest/src/com/android/mediaframeworktest/unit/RingtoneTest.java
@@ -14,22 +14,20 @@
  * limitations under the License.
  */
 
-package com.android.media;
+package com.android.mediaframeworktest.unit;
 
 import static android.media.Ringtone.MEDIA_SOUND;
 import static android.media.Ringtone.MEDIA_SOUND_AND_VIBRATION;
 import static android.media.Ringtone.MEDIA_VIBRATION;
 
-import static com.android.media.testing.MediaPlayerTestHelper.verifyPlayerFallbackSetup;
-import static com.android.media.testing.MediaPlayerTestHelper.verifyPlayerSetup;
-import static com.android.media.testing.MediaPlayerTestHelper.verifyPlayerStarted;
-import static com.android.media.testing.MediaPlayerTestHelper.verifyPlayerStopped;
-
 import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth.assertWithMessage;
 
 import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyBoolean;
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.ArgumentMatchers.isNull;
+import static org.mockito.Mockito.doCallRealMethod;
 import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.doThrow;
 import static org.mockito.Mockito.never;
@@ -55,29 +53,34 @@
 import android.os.VibrationEffect;
 import android.os.Vibrator;
 import android.testing.TestableContext;
+import android.util.ArrayMap;
+import android.util.ArraySet;
 
 import androidx.test.InstrumentationRegistry;
 import androidx.test.runner.AndroidJUnit4;
 
-import com.android.framework.base.media.ringtone.tests.R;
-import com.android.media.testing.RingtoneInjectablesTrackingTestRule;
+import com.android.mediaframeworktest.R;
 
 import org.junit.Before;
 import org.junit.Rule;
 import org.junit.Test;
+import org.junit.rules.TestRule;
+import org.junit.runner.Description;
 import org.junit.runner.RunWith;
+import org.junit.runners.model.Statement;
 import org.mockito.ArgumentCaptor;
 import org.mockito.Captor;
 import org.mockito.Mock;
+import org.mockito.Mockito;
 import org.mockito.MockitoAnnotations;
 
 import java.io.FileNotFoundException;
+import java.util.ArrayDeque;
+import java.util.Map;
+import java.util.Queue;
 
-/**
- * Test behavior of {@link Ringtone} when it's created via {@link Ringtone.Builder}.
- */
 @RunWith(AndroidJUnit4.class)
-public class RingtoneBuilderTest {
+public class RingtoneTest {
 
     private static final Uri SOUND_URI = Uri.parse("content://fake-sound-uri");
 
@@ -90,8 +93,11 @@
 
     private static final VibrationEffect VIBRATION_EFFECT =
             VibrationEffect.createWaveform(new long[] { 0, 100, 50, 100}, -1);
+    private static final VibrationEffect VIBRATION_EFFECT_REPEATING =
+            VibrationEffect.createWaveform(new long[] { 0, 100, 50, 100, 50}, 1);
 
-    @Rule public final RingtoneInjectablesTrackingTestRule
+    @Rule
+    public final RingtoneInjectablesTrackingTestRule
             mMediaPlayerRule = new RingtoneInjectablesTrackingTestRule();
 
     @Captor private ArgumentCaptor<IBinder> mIBinderCaptor;
@@ -116,7 +122,6 @@
         mContext = spy(testContext);
     }
 
-
     @Test
     public void testRingtone_fullLifecycleUsingLocalMediaPlayer() throws Exception {
         MediaPlayer mockMediaPlayer = mMediaPlayerRule.expectLocalMediaPlayer();
@@ -137,14 +142,14 @@
         assertThat(ringtone.isLocalOnly()).isFalse();
 
         // Prepare
-        verifyPlayerSetup(mContext, mockMediaPlayer, SOUND_URI, RINGTONE_ATTRIBUTES);
+        verifyLocalPlayerSetup(mockMediaPlayer, SOUND_URI, RINGTONE_ATTRIBUTES);
         verify(mockMediaPlayer).setVolume(1.0f);
         verify(mockMediaPlayer).setLooping(false);
         verify(mockMediaPlayer).prepare();
 
         // Play
         ringtone.play();
-        verifyPlayerStarted(mockMediaPlayer);
+        verifyLocalPlay(mockMediaPlayer);
 
         // Verify dynamic controls.
         ringtone.setVolume(0.8f);
@@ -160,7 +165,7 @@
 
         // Release
         ringtone.stop();
-        verifyPlayerStopped(mockMediaPlayer);
+        verifyLocalStop(mockMediaPlayer);
 
         // This test is intended to strictly verify all interactions with MediaPlayer in a local
         // playback case. This shouldn't be necessary in other tests that have the same basic
@@ -194,16 +199,16 @@
         assertThat(ringtone.getAudioAttributes()).isEqualTo(audioAttributes);
 
         // Prepare
-        verifyPlayerSetup(mContext, mockMediaPlayer, SOUND_URI, audioAttributes);
+        verifyLocalPlayerSetup(mockMediaPlayer, SOUND_URI, audioAttributes);
         verify(mockMediaPlayer).prepare();
 
         // Play
         ringtone.play();
-        verifyPlayerStarted(mockMediaPlayer);
+        verifyLocalPlay(mockMediaPlayer);
 
         // Release
         ringtone.stop();
-        verifyPlayerStopped(mockMediaPlayer);
+        verifyLocalStop(mockMediaPlayer);
 
         verifyZeroInteractions(mMockRemotePlayer);
         verifyZeroInteractions(mMockVibrator);
@@ -215,8 +220,8 @@
         setupFileNotFound(mockMediaPlayer, SOUND_URI);
         Ringtone ringtone =
                 newBuilder(MEDIA_SOUND, RINGTONE_ATTRIBUTES)
-                        .setUri(SOUND_URI)
-                        .build();
+                .setUri(SOUND_URI)
+                .build();
         assertThat(ringtone).isNotNull();
         assertThat(ringtone.isUsingRemotePlayer()).isTrue();
 
@@ -279,7 +284,7 @@
         // Prepare
         // Uses attributes with haptic channels enabled, but will use the effect when there aren't
         // any present.
-        verifyPlayerSetup(mContext, mockMediaPlayer, SOUND_URI, RINGTONE_ATTRIBUTES_WITH_HC);
+        verifyLocalPlayerSetup(mockMediaPlayer, SOUND_URI, RINGTONE_ATTRIBUTES_WITH_HC);
         verify(mockMediaPlayer).setVolume(1.0f);
         verify(mockMediaPlayer).setLooping(false);
         verify(mockMediaPlayer).prepare();
@@ -287,7 +292,7 @@
         // Play
         ringtone.play();
 
-        verifyPlayerStarted(mockMediaPlayer);
+        verifyLocalPlay(mockMediaPlayer);
         verify(mMockVibrator).vibrate(VIBRATION_EFFECT, RINGTONE_VIB_ATTRIBUTES);
 
         // Verify dynamic controls.
@@ -305,7 +310,7 @@
 
         // Release
         ringtone.stop();
-        verifyPlayerStopped(mockMediaPlayer);
+        verifyLocalStop(mockMediaPlayer);
         verify(mMockVibrator).cancel(VibrationAttributes.USAGE_RINGTONE);
 
         // This test is intended to strictly verify all interactions with MediaPlayer in a local
@@ -383,7 +388,7 @@
         // Prepare
         // Uses attributes with haptic channels enabled, but will abandon the MediaPlayer when it
         // knows there aren't any.
-        verifyPlayerSetup(mContext, mockMediaPlayer, SOUND_URI, RINGTONE_ATTRIBUTES_WITH_HC);
+        verifyLocalPlayerSetup(mockMediaPlayer, SOUND_URI, RINGTONE_ATTRIBUTES_WITH_HC);
         verify(mockMediaPlayer).setVolume(0.0f);  // Vibration-only: sound muted.
         verify(mockMediaPlayer).setLooping(false);
         verify(mockMediaPlayer).prepare();
@@ -438,7 +443,7 @@
         // Prepare
         // Uses attributes with haptic channels enabled, but will use the effect when there aren't
         // any present.
-        verifyPlayerSetup(mContext, mockMediaPlayer, SOUND_URI, RINGTONE_ATTRIBUTES_WITH_HC);
+        verifyLocalPlayerSetup(mockMediaPlayer, SOUND_URI, RINGTONE_ATTRIBUTES_WITH_HC);
         verify(mockMediaPlayer).setVolume(0.0f);  // Vibration-only: sound muted.
         verify(mockMediaPlayer).setLooping(false);
         verify(mockMediaPlayer).prepare();
@@ -446,7 +451,7 @@
         // Play
         ringtone.play();
         // Vibrator.vibrate isn't called because the vibration comes from the sound.
-        verifyPlayerStarted(mockMediaPlayer);
+        verifyLocalPlay(mockMediaPlayer);
 
         // Verify dynamic controls (no-op without sound)
         ringtone.setVolume(0.8f);
@@ -461,7 +466,7 @@
 
         // Release
         ringtone.stop();
-        verifyPlayerStopped(mockMediaPlayer);
+        verifyLocalStop(mockMediaPlayer);
 
         // This test is intended to strictly verify all interactions with MediaPlayer in a local
         // playback case. This shouldn't be necessary in other tests that have the same basic
@@ -491,17 +496,17 @@
 
         // Prepare
         // The attributes here have haptic channels enabled (unlike above)
-        verifyPlayerSetup(mContext, mockMediaPlayer, SOUND_URI, RINGTONE_ATTRIBUTES_WITH_HC);
+        verifyLocalPlayerSetup(mockMediaPlayer, SOUND_URI, RINGTONE_ATTRIBUTES_WITH_HC);
         verify(mockMediaPlayer).prepare();
 
         // Play
         ringtone.play();
         when(mockMediaPlayer.isPlaying()).thenReturn(true);
-        verifyPlayerStarted(mockMediaPlayer);
+        verifyLocalPlay(mockMediaPlayer);
 
         // Release
         ringtone.stop();
-        verifyPlayerStopped(mockMediaPlayer);
+        verifyLocalStop(mockMediaPlayer);
 
         verifyZeroInteractions(mMockRemotePlayer);
         // Nothing after the initial hasVibrator - it uses audio-coupled.
@@ -531,7 +536,7 @@
 
         // Prepare
         // The attributes here have haptic channels enabled (unlike above)
-        verifyPlayerSetup(mContext, mockMediaPlayer, SOUND_URI, RINGTONE_ATTRIBUTES_WITH_HC);
+        verifyLocalPlayerSetup(mockMediaPlayer, SOUND_URI, RINGTONE_ATTRIBUTES_WITH_HC);
         verify(mockMediaPlayer).prepare();
 
         // Play
@@ -554,7 +559,7 @@
     @Test
     public void testRingtone_nullMediaOnBuilderUsesFallback() throws Exception {
         AssetFileDescriptor testResourceFd =
-                mContext.getResources().openRawResourceFd(R.raw.test_sound_file);
+                mContext.getResources().openRawResourceFd(R.raw.shortmp3);
         // Ensure it will flow as expected.
         assertThat(testResourceFd).isNotNull();
         assertThat(testResourceFd.getDeclaredLength()).isAtLeast(0);
@@ -570,18 +575,18 @@
 
         // Delegates straight to fallback in local player.
         // Prepare
-        verifyPlayerFallbackSetup(mockMediaPlayer, testResourceFd, RINGTONE_ATTRIBUTES);
+        verifyLocalPlayerFallbackSetup(mockMediaPlayer, testResourceFd, RINGTONE_ATTRIBUTES);
         verify(mockMediaPlayer).setVolume(1.0f);
         verify(mockMediaPlayer).setLooping(false);
         verify(mockMediaPlayer).prepare();
 
         // Play
         ringtone.play();
-        verifyPlayerStarted(mockMediaPlayer);
+        verifyLocalPlay(mockMediaPlayer);
 
         // Release
         ringtone.stop();
-        verifyPlayerStopped(mockMediaPlayer);
+        verifyLocalStop(mockMediaPlayer);
 
         verifyNoMoreInteractions(mockMediaPlayer);
         verifyNoMoreInteractions(mMockRemotePlayer);
@@ -610,10 +615,24 @@
         verifyNoMoreInteractions(mMockRemotePlayer);
     }
 
+    @Test
+    public void testRingtone_noMediaSetOnBuilderFallbackFailsAndNoRemote() throws Exception {
+        mContext.getOrCreateTestableResources()
+                .addOverride(com.android.internal.R.raw.fallbackring, null);
+        Ringtone ringtone = newBuilder(MEDIA_SOUND, RINGTONE_ATTRIBUTES)
+                .setUri(null)
+                .setLocalOnly()
+                .build();
+        // Local player fallback fails as the resource isn't found (no media player creation is
+        // attempted), and since there is no local player, the ringtone ends up having nothing to
+        // do.
+        assertThat(ringtone).isNull();
+    }
+
     private Ringtone.Builder newBuilder(@Ringtone.RingtoneMedia int ringtoneMedia,
             AudioAttributes audioAttributes) {
         return new Ringtone.Builder(mContext, ringtoneMedia, audioAttributes)
-                .setInjectables(mMediaPlayerRule.getRingtoneTestInjectables());
+                .setInjectables(mMediaPlayerRule.injectables);
     }
 
     private static AudioAttributes audioAttributes(int audioUsage) {
@@ -628,4 +647,194 @@
         doThrow(new FileNotFoundException("Fake file not found"))
                 .when(mockMediaPlayer).setDataSource(any(Context.class), eq(uri));
     }
+
+    private void verifyLocalPlayerSetup(MediaPlayer mockPlayer, Uri expectedUri,
+            AudioAttributes expectedAudioAttributes) throws Exception {
+        verify(mockPlayer).setDataSource(mContext, expectedUri);
+        verify(mockPlayer).setAudioAttributes(expectedAudioAttributes);
+        verify(mockPlayer).setPreferredDevice(null);
+        verify(mockPlayer).prepare();
+    }
+
+    private void verifyLocalPlayerFallbackSetup(MediaPlayer mockPlayer, AssetFileDescriptor afd,
+            AudioAttributes expectedAudioAttributes) throws Exception {
+        // This is very specific but it's a simple way to test that the test resource matches.
+        if (afd.getDeclaredLength() < 0) {
+            verify(mockPlayer).setDataSource(afd.getFileDescriptor());
+        } else {
+            verify(mockPlayer).setDataSource(afd.getFileDescriptor(),
+                    afd.getStartOffset(),
+                    afd.getDeclaredLength());
+        }
+        verify(mockPlayer).setAudioAttributes(expectedAudioAttributes);
+        verify(mockPlayer).setPreferredDevice(null);
+        verify(mockPlayer).prepare();
+    }
+
+    private void verifyLocalPlay(MediaPlayer mockMediaPlayer) {
+        verify(mockMediaPlayer).setOnCompletionListener(any());
+        verify(mockMediaPlayer).start();
+    }
+
+    private void verifyLocalStop(MediaPlayer mockMediaPlayer) {
+        verify(mockMediaPlayer).stop();
+        verify(mockMediaPlayer).setOnCompletionListener(isNull());
+        verify(mockMediaPlayer).reset();
+        verify(mockMediaPlayer).release();
+    }
+
+    /**
+     * This rule ensures that all expected media player creations from the factory do actually
+     * occur. The reason for this level of control is that creating a media player is fairly
+     * expensive and blocking, so we do want unit tests of this class to "declare" interactions
+     * of all created media players.
+     *
+     * This needs to be a TestRule so that the teardown assertions can be skipped if the test has
+     * failed (and media player assertions may just be a distracting side effect). Otherwise, the
+     * teardown failures hide the real test ones.
+     */
+    public static class RingtoneInjectablesTrackingTestRule implements TestRule {
+        public Ringtone.Injectables injectables = new TestInjectables();
+        public boolean hapticGeneratorAvailable = true;
+
+        // Queue of (local) media players, in order of expected creation. Enqueue using
+        // expectNewMediaPlayer(), dequeued by the media player factory passed to Ringtone.
+        // This queue is asserted to be empty at the end of the test.
+        private Queue<MediaPlayer> mMockMediaPlayerQueue = new ArrayDeque<>();
+
+        // Similar to media players, but for haptic generator, which also needs releasing.
+        private Map<MediaPlayer, HapticGenerator> mMockHapticGeneratorMap = new ArrayMap<>();
+
+        // Media players with haptic channels.
+        private ArraySet<MediaPlayer> mHapticChannels = new ArraySet<>();
+
+        @Override
+        public Statement apply(Statement base, Description description) {
+            return new Statement() {
+                @Override
+                public void evaluate() throws Throwable {
+                    base.evaluate();
+                    // Only assert if the test didn't fail (base.evaluate() would throw).
+                    assertWithMessage("Test setup an expectLocalMediaPlayer but it wasn't consumed")
+                            .that(mMockMediaPlayerQueue).isEmpty();
+                    // Only assert if the test didn't fail (base.evaluate() would throw).
+                    assertWithMessage(
+                            "Test setup an expectLocalHapticGenerator but it wasn't consumed")
+                            .that(mMockHapticGeneratorMap).isEmpty();
+                }
+            };
+        }
+
+        private TestMediaPlayer expectLocalMediaPlayer() {
+            TestMediaPlayer mockMediaPlayer = Mockito.mock(TestMediaPlayer.class);
+            // Delegate to simulated methods. This means they can be verified but also reflect
+            // realistic transitions from the TestMediaPlayer.
+            doCallRealMethod().when(mockMediaPlayer).start();
+            doCallRealMethod().when(mockMediaPlayer).stop();
+            doCallRealMethod().when(mockMediaPlayer).setLooping(anyBoolean());
+            when(mockMediaPlayer.isLooping()).thenCallRealMethod();
+            when(mockMediaPlayer.isLooping()).thenCallRealMethod();
+            mMockMediaPlayerQueue.add(mockMediaPlayer);
+            return mockMediaPlayer;
+        }
+
+        private HapticGenerator expectHapticGenerator(MediaPlayer mockMediaPlayer) {
+            HapticGenerator mockHapticGenerator = Mockito.mock(HapticGenerator.class);
+            // A test should never want this.
+            assertWithMessage("Can't expect a second haptic generator created "
+                    + "for one media player")
+                    .that(mMockHapticGeneratorMap.put(mockMediaPlayer, mockHapticGenerator))
+                    .isNull();
+            return mockHapticGenerator;
+        }
+
+        private void setHasHapticChannels(MediaPlayer mp, boolean hasHapticChannels) {
+            if (hasHapticChannels) {
+                mHapticChannels.add(mp);
+            } else {
+                mHapticChannels.remove(mp);
+            }
+        }
+
+        private class TestInjectables extends Ringtone.Injectables {
+            @Override
+            public MediaPlayer newMediaPlayer() {
+                assertWithMessage(
+                        "Unexpected MediaPlayer creation. Bug or need expectNewMediaPlayer")
+                        .that(mMockMediaPlayerQueue)
+                        .isNotEmpty();
+                return mMockMediaPlayerQueue.remove();
+            }
+
+            @Override
+            public boolean isHapticGeneratorAvailable() {
+                return hapticGeneratorAvailable;
+            }
+
+            @Override
+            public HapticGenerator createHapticGenerator(MediaPlayer mediaPlayer) {
+                HapticGenerator mockHapticGenerator = mMockHapticGeneratorMap.remove(mediaPlayer);
+                assertWithMessage("Unexpected HapticGenerator creation. "
+                        + "Bug or need expectHapticGenerator")
+                        .that(mockHapticGenerator)
+                        .isNotNull();
+                return mockHapticGenerator;
+            }
+
+            @Override
+            public boolean isHapticPlaybackSupported() {
+                return true;
+            }
+
+            @Override
+            public boolean hasHapticChannels(MediaPlayer mp) {
+                return mHapticChannels.contains(mp);
+            }
+        }
+    }
+
+    /**
+     * MediaPlayer relies on a native backend and so its necessary to intercept calls from
+     * fake usage hitting them.
+     *
+     * Mocks don't work directly on native calls, but if they're overridden then it does work.
+     * Some basic state faking is also done to make the mocks more realistic.
+     */
+    private static class TestMediaPlayer extends MediaPlayer {
+        private boolean mIsPlaying = false;
+        private boolean mIsLooping = false;
+
+        @Override
+        public void start() {
+            mIsPlaying = true;
+        }
+
+        @Override
+        public void stop() {
+            mIsPlaying = false;
+        }
+
+        @Override
+        public void setLooping(boolean value) {
+            mIsLooping = value;
+        }
+
+        @Override
+        public boolean isLooping() {
+            return mIsLooping;
+        }
+
+        @Override
+        public boolean isPlaying() {
+            return mIsPlaying;
+        }
+
+        void simulatePlayingFinished() {
+            if (!mIsPlaying) {
+                throw new IllegalStateException(
+                        "Attempted to pretend playing finished when not playing");
+            }
+            mIsPlaying = false;
+        }
+    }
 }
diff --git a/media/tests/ringtone/Android.bp b/media/tests/ringtone/Android.bp
index 8d1e5e3..55b98c4 100644
--- a/media/tests/ringtone/Android.bp
+++ b/media/tests/ringtone/Android.bp
@@ -9,24 +9,15 @@
     srcs: ["src/**/*.java"],
 
     libs: [
-        "android.test.base",
-        "android.test.mock",
         "android.test.runner",
+        "android.test.base",
     ],
 
     static_libs: [
-        "androidx.test.ext.junit",
-        "androidx.test.ext.truth",
         "androidx.test.rules",
-        "frameworks-base-testutils",
-        "mockito-target-inline-minus-junit4",
-        "testables",
         "testng",
-    ],
-
-    jni_libs: [
-        "libdexmakerjvmtiagent",
-        "libstaticjvmtiagent",
+        "androidx.test.ext.truth",
+        "frameworks-base-testutils",
     ],
 
     test_suites: [
diff --git a/media/tests/ringtone/OWNERS b/media/tests/ringtone/OWNERS
deleted file mode 100644
index 93b44f4..0000000
--- a/media/tests/ringtone/OWNERS
+++ /dev/null
@@ -1,3 +0,0 @@
-# Bug component: 345036
-
-include /services/core/java/com/android/server/vibrator/OWNERS
diff --git a/media/tests/ringtone/src/com/android/media/testing/MediaPlayerTestHelper.java b/media/tests/ringtone/src/com/android/media/testing/MediaPlayerTestHelper.java
deleted file mode 100644
index e97e117..0000000
--- a/media/tests/ringtone/src/com/android/media/testing/MediaPlayerTestHelper.java
+++ /dev/null
@@ -1,75 +0,0 @@
-/*
- * Copyright 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.media.testing;
-
-import static org.mockito.ArgumentMatchers.any;
-import static org.mockito.ArgumentMatchers.isNull;
-import static org.mockito.Mockito.verify;
-
-import android.content.Context;
-import android.content.res.AssetFileDescriptor;
-import android.media.AudioAttributes;
-import android.media.MediaPlayer;
-import android.net.Uri;
-
-/**
- * Helper class with assertion methods on mock {@link MediaPlayer} instances.
- */
-public final class MediaPlayerTestHelper {
-
-    /** Verify this local media player mock instance was started. */
-    public static void verifyPlayerStarted(MediaPlayer mockMediaPlayer) {
-        verify(mockMediaPlayer).setOnCompletionListener(any());
-        verify(mockMediaPlayer).start();
-    }
-
-    /** Verify this local media player mock instance was stopped and released. */
-    public static void verifyPlayerStopped(MediaPlayer mockMediaPlayer) {
-        verify(mockMediaPlayer).stop();
-        verify(mockMediaPlayer).setOnCompletionListener(isNull());
-        verify(mockMediaPlayer).reset();
-        verify(mockMediaPlayer).release();
-    }
-
-    /** Verify this local media player mock instance was setup with given attributes. */
-    public static void verifyPlayerSetup(Context context, MediaPlayer mockPlayer,
-            Uri expectedUri, AudioAttributes expectedAudioAttributes) throws Exception {
-        verify(mockPlayer).setDataSource(context, expectedUri);
-        verify(mockPlayer).setAudioAttributes(expectedAudioAttributes);
-        verify(mockPlayer).setPreferredDevice(null);
-        verify(mockPlayer).prepare();
-    }
-
-    /** Verify this local media player mock instance was setup with given fallback attributes. */
-    public static void verifyPlayerFallbackSetup(MediaPlayer mockPlayer,
-            AssetFileDescriptor afd, AudioAttributes expectedAudioAttributes) throws Exception {
-        // This is very specific but it's a simple way to test that the test resource matches.
-        if (afd.getDeclaredLength() < 0) {
-            verify(mockPlayer).setDataSource(afd.getFileDescriptor());
-        } else {
-            verify(mockPlayer).setDataSource(afd.getFileDescriptor(),
-                    afd.getStartOffset(),
-                    afd.getDeclaredLength());
-        }
-        verify(mockPlayer).setAudioAttributes(expectedAudioAttributes);
-        verify(mockPlayer).setPreferredDevice(null);
-        verify(mockPlayer).prepare();
-    }
-
-    private MediaPlayerTestHelper() {
-    }
-}
diff --git a/media/tests/ringtone/src/com/android/media/testing/RingtoneInjectablesTrackingTestRule.java b/media/tests/ringtone/src/com/android/media/testing/RingtoneInjectablesTrackingTestRule.java
deleted file mode 100644
index 25752ce..0000000
--- a/media/tests/ringtone/src/com/android/media/testing/RingtoneInjectablesTrackingTestRule.java
+++ /dev/null
@@ -1,225 +0,0 @@
-/*
- * Copyright 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.media.testing;
-
-import static com.google.common.truth.Truth.assertWithMessage;
-
-import static org.mockito.ArgumentMatchers.anyBoolean;
-import static org.mockito.Mockito.doCallRealMethod;
-import static org.mockito.Mockito.when;
-
-import android.media.MediaPlayer;
-import android.media.Ringtone;
-import android.media.audiofx.HapticGenerator;
-import android.util.ArrayMap;
-import android.util.ArraySet;
-
-import org.junit.rules.TestRule;
-import org.junit.runner.Description;
-import org.junit.runners.model.Statement;
-import org.mockito.Mockito;
-
-import java.util.ArrayDeque;
-import java.util.Map;
-import java.util.Queue;
-
-/**
- * This rule ensures that all expected media player creations from the factory do actually
- * occur. The reason for this level of control is that creating a media player is fairly
- * expensive and blocking, so we do want unit tests of this class to "declare" interactions
- * of all created media players.
- * <p>
- * This needs to be a TestRule so that the teardown assertions can be skipped if the test has
- * failed (and media player assertions may just be a distracting side effect). Otherwise, the
- * teardown failures hide the real test ones.
- */
-public class RingtoneInjectablesTrackingTestRule implements TestRule {
-
-    private final Ringtone.Injectables mRingtoneTestInjectables = new TestInjectables();
-
-    // Queue of (local) media players, in order of expected creation. Enqueue using
-    // expectNewMediaPlayer(), dequeued by the media player factory passed to Ringtone.
-    // This queue is asserted to be empty at the end of the test.
-    private final Queue<MediaPlayer> mMockMediaPlayerQueue = new ArrayDeque<>();
-
-    // Similar to media players, but for haptic generator, which also needs releasing.
-    private final Map<MediaPlayer, HapticGenerator> mMockHapticGeneratorMap = new ArrayMap<>();
-
-    // Media players with haptic channels.
-    private final ArraySet<MediaPlayer> mHapticChannels = new ArraySet<>();
-
-    private boolean mHapticGeneratorAvailable = true;
-
-    @Override
-    public Statement apply(Statement base, Description description) {
-        return new Statement() {
-            @Override
-            public void evaluate() throws Throwable {
-                base.evaluate();
-                // Only assert if the test didn't fail (base.evaluate() would throw).
-                assertWithMessage("Test setup an expectLocalMediaPlayer but it wasn't consumed")
-                        .that(mMockMediaPlayerQueue).isEmpty();
-                // Only assert if the test didn't fail (base.evaluate() would throw).
-                assertWithMessage(
-                        "Test setup an expectLocalHapticGenerator but it wasn't consumed")
-                        .that(mMockHapticGeneratorMap).isEmpty();
-            }
-        };
-    }
-
-    /** The {@link Ringtone.Injectables} to be used for creating a testable {@link Ringtone}. */
-    public Ringtone.Injectables getRingtoneTestInjectables() {
-        return mRingtoneTestInjectables;
-    }
-
-    /**
-     * Create a test {@link MediaPlayer} that will be provided to the {@link Ringtone} instance
-     * created with {@link #getRingtoneTestInjectables()}.
-     *
-     * <p>If a media player is not created during the test execution after this method is called
-     * then the test will fail. It will also fail if the ringtone attempts to create one without
-     * this method being called first.
-     */
-    public TestMediaPlayer expectLocalMediaPlayer() {
-        TestMediaPlayer mockMediaPlayer = Mockito.mock(TestMediaPlayer.class);
-        // Delegate to simulated methods. This means they can be verified but also reflect
-        // realistic transitions from the TestMediaPlayer.
-        doCallRealMethod().when(mockMediaPlayer).start();
-        doCallRealMethod().when(mockMediaPlayer).stop();
-        doCallRealMethod().when(mockMediaPlayer).setLooping(anyBoolean());
-        when(mockMediaPlayer.isLooping()).thenCallRealMethod();
-        mMockMediaPlayerQueue.add(mockMediaPlayer);
-        return mockMediaPlayer;
-    }
-
-    /**
-     * Create a test {@link HapticGenerator} that will be provided to the {@link Ringtone} instance
-     * created with {@link #getRingtoneTestInjectables()}.
-     *
-     * <p>If a haptic generator is not created during the test execution after this method is called
-     * then the test will fail. It will also fail if the ringtone attempts to create one without
-     * this method being called first.
-     */
-    public HapticGenerator expectHapticGenerator(MediaPlayer mediaPlayer) {
-        HapticGenerator mockHapticGenerator = Mockito.mock(HapticGenerator.class);
-        // A test should never want this.
-        assertWithMessage("Can't expect a second haptic generator created "
-                + "for one media player")
-                .that(mMockHapticGeneratorMap.put(mediaPlayer, mockHapticGenerator))
-                .isNull();
-        return mockHapticGenerator;
-    }
-
-    /**
-     * Configures the {@link MediaPlayer} to always return given flag when
-     * {@link Ringtone.Injectables#hasHapticChannels(MediaPlayer)} is called.
-     */
-    public void setHasHapticChannels(MediaPlayer mp, boolean hasHapticChannels) {
-        if (hasHapticChannels) {
-            mHapticChannels.add(mp);
-        } else {
-            mHapticChannels.remove(mp);
-        }
-    }
-
-    /** Test implementation of {@link Ringtone.Injectables} that uses the test rule setup. */
-    private class TestInjectables extends Ringtone.Injectables {
-        @Override
-        public MediaPlayer newMediaPlayer() {
-            assertWithMessage(
-                    "Unexpected MediaPlayer creation. Bug or need expectNewMediaPlayer")
-                    .that(mMockMediaPlayerQueue)
-                    .isNotEmpty();
-            return mMockMediaPlayerQueue.remove();
-        }
-
-        @Override
-        public boolean isHapticGeneratorAvailable() {
-            return mHapticGeneratorAvailable;
-        }
-
-        @Override
-        public HapticGenerator createHapticGenerator(MediaPlayer mediaPlayer) {
-            HapticGenerator mockHapticGenerator = mMockHapticGeneratorMap.remove(mediaPlayer);
-            assertWithMessage("Unexpected HapticGenerator creation. "
-                    + "Bug or need expectHapticGenerator")
-                    .that(mockHapticGenerator)
-                    .isNotNull();
-            return mockHapticGenerator;
-        }
-
-        @Override
-        public boolean isHapticPlaybackSupported() {
-            return true;
-        }
-
-        @Override
-        public boolean hasHapticChannels(MediaPlayer mp) {
-            return mHapticChannels.contains(mp);
-        }
-    }
-
-    /**
-     * MediaPlayer relies on a native backend and so its necessary to intercept calls from
-     * fake usage hitting them.
-     * <p>
-     * Mocks don't work directly on native calls, but if they're overridden then it does work.
-     * Some basic state faking is also done to make the mocks more realistic.
-     */
-    public static class TestMediaPlayer extends MediaPlayer {
-        private boolean mIsPlaying = false;
-        private boolean mIsLooping = false;
-
-        @Override
-        public void start() {
-            mIsPlaying = true;
-        }
-
-        @Override
-        public void stop() {
-            mIsPlaying = false;
-        }
-
-        @Override
-        public void setLooping(boolean value) {
-            mIsLooping = value;
-        }
-
-        @Override
-        public boolean isLooping() {
-            return mIsLooping;
-        }
-
-        @Override
-        public boolean isPlaying() {
-            return mIsPlaying;
-        }
-
-        /**
-         * Updates {@link #isPlaying()} result to false, if it's set to true.
-         *
-         * @throws IllegalStateException is {@link #isPlaying()} is already false
-         */
-        public void simulatePlayingFinished() {
-            if (!mIsPlaying) {
-                throw new IllegalStateException(
-                        "Attempted to pretend playing finished when not playing");
-            }
-            mIsPlaying = false;
-        }
-    }
-}
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 ce96bbf..abc62c4 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
@@ -43,6 +43,7 @@
 import androidx.compose.ui.unit.IntSize
 import androidx.compose.ui.unit.round
 import com.android.compose.animation.scene.transformation.PropertyTransformation
+import com.android.compose.animation.scene.transformation.SharedElementTransformation
 import com.android.compose.modifiers.thenIf
 import com.android.compose.ui.util.lerp
 
@@ -196,29 +197,44 @@
             state.fromScene == state.toScene ||
             !layoutImpl.isTransitionReady(state) ||
             state.fromScene !in element.sceneValues ||
-            state.toScene !in element.sceneValues ||
-            !isSharedElementEnabled(layoutImpl, state, element.key)
+            state.toScene !in element.sceneValues
     ) {
         return true
     }
 
-    val otherScene =
-        layoutImpl.scenes.getValue(
-            if (scene.key == state.fromScene) {
-                state.toScene
-            } else {
-                state.fromScene
-            }
-        )
-
-    // When the element is shared, draw the one in the highest scene unless it is a background, i.e.
-    // it is usually drawn below everything else.
-    val isHighestScene = scene.zIndex > otherScene.zIndex
-    return if (element.key.isBackground) {
-        !isHighestScene
-    } else {
-        isHighestScene
+    val sharedTransformation = sharedElementTransformation(layoutImpl, state, element.key)
+    if (sharedTransformation?.enabled == false) {
+        return true
     }
+
+    return shouldDrawOrComposeSharedElement(
+        layoutImpl,
+        state,
+        scene.key,
+        element.key,
+        sharedTransformation,
+    )
+}
+
+internal fun shouldDrawOrComposeSharedElement(
+    layoutImpl: SceneTransitionLayoutImpl,
+    transition: TransitionState.Transition,
+    scene: SceneKey,
+    element: ElementKey,
+    sharedTransformation: SharedElementTransformation?
+): Boolean {
+    val scenePicker = sharedTransformation?.scenePicker ?: DefaultSharedElementScenePicker
+    val fromScene = transition.fromScene
+    val toScene = transition.toScene
+
+    return scenePicker.sceneDuringTransition(
+        element = element,
+        fromScene = fromScene,
+        toScene = toScene,
+        progress = transition::progress,
+        fromSceneZIndex = layoutImpl.scenes.getValue(fromScene).zIndex,
+        toSceneZIndex = layoutImpl.scenes.getValue(toScene).zIndex,
+    ) == scene
 }
 
 private fun isSharedElementEnabled(
@@ -226,6 +242,14 @@
     transition: TransitionState.Transition,
     element: ElementKey,
 ): Boolean {
+    return sharedElementTransformation(layoutImpl, transition, element)?.enabled ?: true
+}
+
+internal fun sharedElementTransformation(
+    layoutImpl: SceneTransitionLayoutImpl,
+    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
@@ -238,7 +262,7 @@
         )
     }
 
-    return sharedInFromScene?.enabled ?: true
+    return sharedInFromScene
 }
 
 /**
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 6dbeb69..fa385d0 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
@@ -22,6 +22,8 @@
 import androidx.compose.foundation.layout.Spacer
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.State
+import androidx.compose.runtime.derivedStateOf
+import androidx.compose.runtime.getValue
 import androidx.compose.runtime.remember
 import androidx.compose.runtime.snapshots.Snapshot
 import androidx.compose.ui.Modifier
@@ -60,7 +62,16 @@
         // which case we still need to draw it.
         val picture = remember { Picture() }
 
-        if (shouldComposeMovableElement(layoutImpl, scene.key, element)) {
+        // Whether we should compose the movable element here. The scene picker logic to know in
+        // which scene we should compose/draw a movable element might depend on the current
+        // transition progress, so we put this in a derivedStateOf to prevent many recompositions
+        // during the transition.
+        val shouldComposeMovableElement by
+            remember(layoutImpl, scene.key, element) {
+                derivedStateOf { shouldComposeMovableElement(layoutImpl, scene.key, element) }
+            }
+
+        if (shouldComposeMovableElement) {
             Box(
                 Modifier.drawWithCache {
                     val width = size.width.toInt()
@@ -172,14 +183,13 @@
         return scene == fromScene
     }
 
-    // If we are ready in both scenes, then compose in the scene that has the highest zIndex (unless
-    // it is a background) given that this is the one that is going to be drawn.
-    val isHighestScene = layoutImpl.scene(scene).zIndex > layoutImpl.scene(otherScene).zIndex
-    return if (element.key.isBackground) {
-        !isHighestScene
-    } else {
-        isHighestScene
-    }
+    return shouldDrawOrComposeSharedElement(
+        layoutImpl,
+        transitionState,
+        scene,
+        element.key,
+        sharedElementTransformation(layoutImpl, transitionState, element.key),
+    )
 }
 
 private class MovableElementScopeImpl(
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/TransitionDsl.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/TransitionDsl.kt
index 4966977..7b7ddfa 100644
--- a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/TransitionDsl.kt
+++ b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/TransitionDsl.kt
@@ -120,8 +120,14 @@
      *
      * @param enabled whether the matched element(s) should actually be shared in this transition.
      *   Defaults to true.
+     * @param scenePicker the [SharedElementScenePicker] to use when deciding in which scene we
+     *   should draw or compose this shared element.
      */
-    fun sharedElement(matcher: ElementMatcher, enabled: Boolean = true)
+    fun sharedElement(
+        matcher: ElementMatcher,
+        enabled: Boolean = true,
+        scenePicker: SharedElementScenePicker = DefaultSharedElementScenePicker,
+    )
 
     /**
      * Punch a hole in the element(s) matching [matcher] that has the same bounds as [bounds] and
@@ -144,6 +150,44 @@
     fun reversed(builder: TransitionBuilder.() -> Unit)
 }
 
+interface SharedElementScenePicker {
+    /**
+     * Return the scene in which [element] should be drawn (when using `Modifier.element(key)`) or
+     * composed (when using `MovableElement(key)`) during the transition from [fromScene] to
+     * [toScene].
+     */
+    fun sceneDuringTransition(
+        element: ElementKey,
+        fromScene: SceneKey,
+        toScene: SceneKey,
+        progress: () -> Float,
+        fromSceneZIndex: Float,
+        toSceneZIndex: Float,
+    ): SceneKey
+}
+
+object DefaultSharedElementScenePicker : SharedElementScenePicker {
+    override fun sceneDuringTransition(
+        element: ElementKey,
+        fromScene: SceneKey,
+        toScene: SceneKey,
+        progress: () -> Float,
+        fromSceneZIndex: Float,
+        toSceneZIndex: Float
+    ): SceneKey {
+        // By default shared elements are drawn in the highest scene possible, unless it is a
+        // background.
+        return if (
+            (fromSceneZIndex > toSceneZIndex && !element.isBackground) ||
+                (fromSceneZIndex < toSceneZIndex && element.isBackground)
+        ) {
+            fromScene
+        } else {
+            toScene
+        }
+    }
+}
+
 @TransitionDsl
 interface PropertyTransformationBuilder {
     /**
diff --git a/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/TransitionDslImpl.kt b/packages/SystemUI/compose/scene/src/com/android/compose/animation/scene/TransitionDslImpl.kt
index f1c2717..d2bfd91 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
@@ -111,8 +111,12 @@
         range = null
     }
 
-    override fun sharedElement(matcher: ElementMatcher, enabled: Boolean) {
-        transformations.add(SharedElementTransformation(matcher, enabled))
+    override fun sharedElement(
+        matcher: ElementMatcher,
+        enabled: Boolean,
+        scenePicker: SharedElementScenePicker,
+    ) {
+        transformations.add(SharedElementTransformation(matcher, enabled, scenePicker))
     }
 
     override fun timestampRange(
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 2ef8d56..0db8469 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
@@ -21,6 +21,7 @@
 import com.android.compose.animation.scene.ElementMatcher
 import com.android.compose.animation.scene.Scene
 import com.android.compose.animation.scene.SceneTransitionLayoutImpl
+import com.android.compose.animation.scene.SharedElementScenePicker
 import com.android.compose.animation.scene.TransitionState
 
 /** A transformation applied to one or more elements during a transition. */
@@ -48,6 +49,7 @@
 internal class SharedElementTransformation(
     override val matcher: ElementMatcher,
     internal val enabled: Boolean,
+    internal val scenePicker: SharedElementScenePicker,
 ) : Transformation
 
 /** A transformation that is applied on the element during the whole transition. */
diff --git a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/MovableElementTest.kt b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/MovableElementTest.kt
index 4204cd5..83af630 100644
--- a/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/MovableElementTest.kt
+++ b/packages/SystemUI/compose/scene/tests/src/com/android/compose/animation/scene/MovableElementTest.kt
@@ -144,7 +144,36 @@
         rule.testTransition(
             fromSceneContent = { MovableCounter(TestElements.Foo, Modifier.size(50.dp)) },
             toSceneContent = { MovableCounter(TestElements.Foo, Modifier.size(100.dp)) },
-            transition = { spec = tween(durationMillis = 16 * 4, easing = LinearEasing) },
+            transition = {
+                spec = tween(durationMillis = 16 * 4, easing = LinearEasing)
+                sharedElement(
+                    TestElements.Foo,
+                    scenePicker =
+                        object : SharedElementScenePicker {
+                            override fun sceneDuringTransition(
+                                element: ElementKey,
+                                fromScene: SceneKey,
+                                toScene: SceneKey,
+                                progress: () -> Float,
+                                fromSceneZIndex: Float,
+                                toSceneZIndex: Float
+                            ): SceneKey {
+                                assertThat(fromScene).isEqualTo(TestScenes.SceneA)
+                                assertThat(toScene).isEqualTo(TestScenes.SceneB)
+                                assertThat(fromSceneZIndex).isEqualTo(0)
+                                assertThat(toSceneZIndex).isEqualTo(1)
+
+                                // Compose Foo in Scene A if progress < 0.65f, otherwise compose it
+                                // in Scene B.
+                                return if (progress() < 0.65f) {
+                                    TestScenes.SceneA
+                                } else {
+                                    TestScenes.SceneB
+                                }
+                            }
+                        }
+                )
+            },
             fromScene = TestScenes.SceneA,
             toScene = TestScenes.SceneB,
         ) {
@@ -170,9 +199,12 @@
 
             at(32) {
                 // During the transition, there is a single counter that is moved, with the current
-                // value.
+                // value. Given that progress = 0.5f, it is currently composed in SceneA.
                 rule
-                    .onNode(hasText("count: 3"))
+                    .onNode(
+                        hasText("count: 3") and
+                            hasParent(isElement(TestElements.Foo, scene = TestScenes.SceneA))
+                    )
                     .assertIsDisplayed()
                     .assertSizeIsEqualTo(75.dp, 75.dp)
 
@@ -186,6 +218,26 @@
                     .isEqualTo(1)
             }
 
+            at(48) {
+                // During the transition, there is a single counter that is moved, with the current
+                // value. Given that progress = 0.75f, it is currently composed in SceneB.
+                rule
+                    .onNode(
+                        hasText("count: 3") and
+                            hasParent(isElement(TestElements.Foo, scene = TestScenes.SceneB))
+                    )
+                    .assertIsDisplayed()
+
+                // There are no other counters.
+                assertThat(
+                        rule
+                            .onAllNodesWithText("count: ", substring = true)
+                            .fetchSemanticsNodes()
+                            .size
+                    )
+                    .isEqualTo(1)
+            }
+
             after {
                 // At the end of the transition, the counter still has the current value.
                 rule
diff --git a/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordViewController.java b/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordViewController.java
index 959cf6f..01fc035 100644
--- a/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordViewController.java
+++ b/packages/SystemUI/src/com/android/keyguard/KeyguardPasswordViewController.java
@@ -51,7 +51,6 @@
 public class KeyguardPasswordViewController
         extends KeyguardAbsKeyInputViewController<KeyguardPasswordView> {
 
-    private static final int DELAY_MILLIS_TO_REEVALUATE_IME_SWITCH_ICON = 500;  // 500ms
     private final KeyguardSecurityCallback mKeyguardSecurityCallback;
     private final DevicePostureController mPostureController;
     private final DevicePostureController.Callback mPostureCallback = posture ->
@@ -164,13 +163,6 @@
 
         // If there's more than one IME, enable the IME switcher button
         updateSwitchImeButton();
-
-        // When we the current user is switching, InputMethodManagerService sometimes has not
-        // switched internal state yet here. As a quick workaround, we check the keyboard state
-        // again.
-        // TODO: Remove this workaround by ensuring such a race condition never happens.
-        mMainExecutor.executeDelayed(
-                this::updateSwitchImeButton, DELAY_MILLIS_TO_REEVALUATE_IME_SWITCH_ICON);
     }
 
     @Override
diff --git a/packages/SystemUI/src/com/android/systemui/deviceentry/DeviceEntryModule.kt b/packages/SystemUI/src/com/android/systemui/deviceentry/DeviceEntryModule.kt
index e7f835f..c3aaef7 100644
--- a/packages/SystemUI/src/com/android/systemui/deviceentry/DeviceEntryModule.kt
+++ b/packages/SystemUI/src/com/android/systemui/deviceentry/DeviceEntryModule.kt
@@ -1,5 +1,6 @@
 package com.android.systemui.deviceentry
 
+import com.android.systemui.deviceentry.data.repository.DeviceEntryHapticsRepositoryModule
 import com.android.systemui.deviceentry.data.repository.DeviceEntryRepositoryModule
 import dagger.Module
 
@@ -7,6 +8,7 @@
     includes =
         [
             DeviceEntryRepositoryModule::class,
+            DeviceEntryHapticsRepositoryModule::class,
         ],
 )
 object DeviceEntryModule
diff --git a/packages/SystemUI/src/com/android/systemui/deviceentry/data/repository/DeviceEntryHapticsRepository.kt b/packages/SystemUI/src/com/android/systemui/deviceentry/data/repository/DeviceEntryHapticsRepository.kt
new file mode 100644
index 0000000..1458404
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/deviceentry/data/repository/DeviceEntryHapticsRepository.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.deviceentry.data.repository
+
+import com.android.systemui.dagger.SysUISingleton
+import dagger.Binds
+import dagger.Module
+import javax.inject.Inject
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.asStateFlow
+
+/** Interface for classes that can access device-entry haptics application state. */
+interface DeviceEntryHapticsRepository {
+    /**
+     * Whether a successful biometric haptic has been requested. Has not yet been handled if true.
+     */
+    val successHapticRequest: Flow<Boolean>
+
+    /** Whether an error biometric haptic has been requested. Has not yet been handled if true. */
+    val errorHapticRequest: Flow<Boolean>
+
+    fun requestSuccessHaptic()
+    fun handleSuccessHaptic()
+    fun requestErrorHaptic()
+    fun handleErrorHaptic()
+}
+
+/** Encapsulates application state for device entry haptics. */
+@SysUISingleton
+class DeviceEntryHapticsRepositoryImpl @Inject constructor() : DeviceEntryHapticsRepository {
+    private val _successHapticRequest = MutableStateFlow(false)
+    override val successHapticRequest: Flow<Boolean> = _successHapticRequest.asStateFlow()
+
+    private val _errorHapticRequest = MutableStateFlow(false)
+    override val errorHapticRequest: Flow<Boolean> = _errorHapticRequest.asStateFlow()
+
+    override fun requestSuccessHaptic() {
+        _successHapticRequest.value = true
+    }
+
+    override fun handleSuccessHaptic() {
+        _successHapticRequest.value = false
+    }
+
+    override fun requestErrorHaptic() {
+        _errorHapticRequest.value = true
+    }
+
+    override fun handleErrorHaptic() {
+        _errorHapticRequest.value = false
+    }
+}
+
+@Module
+interface DeviceEntryHapticsRepositoryModule {
+    @Binds fun repository(impl: DeviceEntryHapticsRepositoryImpl): DeviceEntryHapticsRepository
+}
diff --git a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryHapticsInteractor.kt b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryHapticsInteractor.kt
new file mode 100644
index 0000000..53d6f73
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryHapticsInteractor.kt
@@ -0,0 +1,133 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.systemui.deviceentry.domain.interactor
+
+import com.android.keyguard.logging.BiometricUnlockLogger
+import com.android.systemui.biometrics.data.repository.FingerprintPropertyRepository
+import com.android.systemui.biometrics.shared.model.FingerprintSensorType
+import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.deviceentry.data.repository.DeviceEntryHapticsRepository
+import com.android.systemui.keyevent.domain.interactor.KeyEventInteractor
+import com.android.systemui.keyguard.data.repository.BiometricSettingsRepository
+import com.android.systemui.power.domain.interactor.PowerInteractor
+import com.android.systemui.power.shared.model.WakeSleepReason
+import com.android.systemui.util.kotlin.sample
+import com.android.systemui.util.time.SystemClock
+import javax.inject.Inject
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.combine
+import kotlinx.coroutines.flow.combineTransform
+import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.filter
+import kotlinx.coroutines.flow.map
+import kotlinx.coroutines.flow.onStart
+
+/**
+ * Business logic for device entry haptic events. Determines whether the haptic should play. In
+ * particular, there are extra guards for whether device entry error and successes hatpics should
+ * play when the physical fingerprint sensor is located on the power button.
+ */
+@ExperimentalCoroutinesApi
+@SysUISingleton
+class DeviceEntryHapticsInteractor
+@Inject
+constructor(
+    private val repository: DeviceEntryHapticsRepository,
+    fingerprintPropertyRepository: FingerprintPropertyRepository,
+    biometricSettingsRepository: BiometricSettingsRepository,
+    keyEventInteractor: KeyEventInteractor,
+    powerInteractor: PowerInteractor,
+    private val systemClock: SystemClock,
+    private val logger: BiometricUnlockLogger,
+) {
+    private val powerButtonSideFpsEnrolled =
+        combineTransform(
+                fingerprintPropertyRepository.sensorType,
+                biometricSettingsRepository.isFingerprintEnrolledAndEnabled,
+            ) { sensorType, enrolledAndEnabled ->
+                if (sensorType == FingerprintSensorType.POWER_BUTTON) {
+                    emit(enrolledAndEnabled)
+                } else {
+                    emit(false)
+                }
+            }
+            .distinctUntilChanged()
+    private val powerButtonDown: Flow<Boolean> = keyEventInteractor.isPowerButtonDown
+    private val lastPowerButtonWakeup: Flow<Long> =
+        powerInteractor.detailedWakefulness
+            .filter { it.isAwakeFrom(WakeSleepReason.POWER_BUTTON) }
+            .map { systemClock.uptimeMillis() }
+            .onStart {
+                // If the power button hasn't been pressed, we still want this to evaluate to true:
+                // `uptimeMillis() - lastPowerButtonWakeup > recentPowerButtonPressThresholdMs`
+                emit(recentPowerButtonPressThresholdMs * -1L - 1L)
+            }
+
+    val playSuccessHaptic: Flow<Boolean> =
+        repository.successHapticRequest
+            .filter { it }
+            .sample(
+                combine(
+                    powerButtonSideFpsEnrolled,
+                    powerButtonDown,
+                    lastPowerButtonWakeup,
+                    ::Triple
+                )
+            )
+            .map { (sideFpsEnrolled, powerButtonDown, lastPowerButtonWakeup) ->
+                val sideFpsAllowsHaptic =
+                    !powerButtonDown &&
+                        systemClock.uptimeMillis() - lastPowerButtonWakeup >
+                            recentPowerButtonPressThresholdMs
+                val allowHaptic = !sideFpsEnrolled || sideFpsAllowsHaptic
+                if (!allowHaptic) {
+                    logger.d("Skip success haptic. Recent power button press or button is down.")
+                    handleSuccessHaptic() // immediately handle, don't vibrate
+                }
+                allowHaptic
+            }
+    val playErrorHaptic: Flow<Boolean> =
+        repository.errorHapticRequest
+            .filter { it }
+            .sample(combine(powerButtonSideFpsEnrolled, powerButtonDown, ::Pair))
+            .map { (sideFpsEnrolled, powerButtonDown) ->
+                val allowHaptic = !sideFpsEnrolled || !powerButtonDown
+                if (!allowHaptic) {
+                    logger.d("Skip error haptic. Power button is down.")
+                    handleErrorHaptic() // immediately handle, don't vibrate
+                }
+                allowHaptic
+            }
+
+    fun vibrateSuccess() {
+        repository.requestSuccessHaptic()
+    }
+
+    fun vibrateError() {
+        repository.requestErrorHaptic()
+    }
+
+    fun handleSuccessHaptic() {
+        repository.handleSuccessHaptic()
+    }
+
+    fun handleErrorHaptic() {
+        repository.handleErrorHaptic()
+    }
+
+    private val recentPowerButtonPressThresholdMs = 400L
+}
diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
index 081618e..472cc24 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
+++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.kt
@@ -259,7 +259,7 @@
     // TODO(b/290652751): Tracking bug.
     @JvmField
     val MIGRATE_SPLIT_KEYGUARD_BOTTOM_AREA =
-        unreleasedFlag("migrate_split_keyguard_bottom_area", teamfood = true)
+        releasedFlag("migrate_split_keyguard_bottom_area")
 
     // TODO(b/297037052): Tracking bug.
     @JvmField
@@ -274,7 +274,7 @@
 
     /** Migrate the lock icon view to the new keyguard root view. */
     // TODO(b/286552209): Tracking bug.
-    @JvmField val MIGRATE_LOCK_ICON = unreleasedFlag("migrate_lock_icon", teamfood = true)
+    @JvmField val MIGRATE_LOCK_ICON = releasedFlag("migrate_lock_icon")
 
     // TODO(b/288276738): Tracking bug.
     @JvmField val WIDGET_ON_KEYGUARD = unreleasedFlag("widget_on_keyguard")
@@ -419,7 +419,7 @@
         releasedFlag("incompatible_charging_battery_icon")
 
     // TODO(b/293585143): Tracking Bug
-    val INSTANT_TETHER = unreleasedFlag("instant_tether")
+    val INSTANT_TETHER = unreleasedFlag("instant_tether", teamfood = true)
 
     // TODO(b/294588085): Tracking Bug
     val WIFI_SECONDARY_NETWORKS = releasedFlag("wifi_secondary_networks")
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewConfigurator.kt b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewConfigurator.kt
index a511713..119ade4 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewConfigurator.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/KeyguardViewConfigurator.kt
@@ -28,6 +28,7 @@
 import com.android.keyguard.dagger.KeyguardStatusViewComponent
 import com.android.systemui.CoreStartable
 import com.android.systemui.dagger.SysUISingleton
+import com.android.systemui.deviceentry.domain.interactor.DeviceEntryHapticsInteractor
 import com.android.systemui.flags.FeatureFlags
 import com.android.systemui.flags.Flags
 import com.android.systemui.keyguard.ui.binder.KeyguardBlueprintViewBinder
@@ -44,6 +45,7 @@
 import com.android.systemui.shade.NotificationShadeWindowView
 import com.android.systemui.shade.domain.interactor.ShadeInteractor
 import com.android.systemui.statusbar.KeyguardIndicationController
+import com.android.systemui.statusbar.VibratorHelper
 import com.android.systemui.statusbar.policy.KeyguardStateController
 import com.android.systemui.temporarydisplay.chipbar.ChipbarCoordinator
 import javax.inject.Inject
@@ -72,7 +74,9 @@
     private val keyguardIndicationController: KeyguardIndicationController,
     private val lockIconViewController: LockIconViewController,
     private val shadeInteractor: ShadeInteractor,
-    private val interactionJankMonitor: InteractionJankMonitor
+    private val interactionJankMonitor: InteractionJankMonitor,
+    private val deviceEntryHapticsInteractor: DeviceEntryHapticsInteractor,
+    private val vibratorHelper: VibratorHelper,
 ) : CoreStartable {
 
     private var rootViewHandle: DisposableHandle? = null
@@ -143,6 +147,8 @@
                 shadeInteractor,
                 { keyguardStatusViewController!!.getClockController() },
                 interactionJankMonitor,
+                deviceEntryHapticsInteractor,
+                vibratorHelper,
             )
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardRootViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardRootViewBinder.kt
index c72e6ce..4d5c503 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardRootViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardRootViewBinder.kt
@@ -17,6 +17,7 @@
 package com.android.systemui.keyguard.ui.binder
 
 import android.annotation.DrawableRes
+import android.view.HapticFeedbackConstants
 import android.view.View
 import android.view.View.OnLayoutChangeListener
 import android.view.ViewGroup
@@ -29,6 +30,7 @@
 import com.android.systemui.common.shared.model.Icon
 import com.android.systemui.common.shared.model.Text
 import com.android.systemui.common.shared.model.TintedIcon
+import com.android.systemui.deviceentry.domain.interactor.DeviceEntryHapticsInteractor
 import com.android.systemui.flags.FeatureFlags
 import com.android.systemui.flags.Flags
 import com.android.systemui.keyguard.shared.model.TransitionState
@@ -38,6 +40,7 @@
 import com.android.systemui.plugins.ClockController
 import com.android.systemui.res.R
 import com.android.systemui.shade.domain.interactor.ShadeInteractor
+import com.android.systemui.statusbar.VibratorHelper
 import com.android.systemui.statusbar.policy.KeyguardStateController
 import com.android.systemui.temporarydisplay.ViewPriority
 import com.android.systemui.temporarydisplay.chipbar.ChipbarCoordinator
@@ -45,6 +48,7 @@
 import javax.inject.Provider
 import kotlinx.coroutines.DisposableHandle
 import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.filter
 import kotlinx.coroutines.launch
 
 /** Bind occludingAppDeviceEntryMessageViewModel to run whenever the keyguard view is attached. */
@@ -62,6 +66,8 @@
         shadeInteractor: ShadeInteractor,
         clockControllerProvider: Provider<ClockController>?,
         interactionJankMonitor: InteractionJankMonitor?,
+        deviceEntryHapticsInteractor: DeviceEntryHapticsInteractor?,
+        vibratorHelper: VibratorHelper?,
     ): DisposableHandle {
         var onLayoutChangeListener: OnLayoutChange? = null
         val childViews = mutableMapOf<Int, View?>()
@@ -177,6 +183,44 @@
                                 }
                         }
                     }
+
+                    if (deviceEntryHapticsInteractor != null && vibratorHelper != null) {
+                        launch {
+                            deviceEntryHapticsInteractor.playSuccessHaptic
+                                .filter { it }
+                                .collect {
+                                    if (
+                                        featureFlags.isEnabled(Flags.ONE_WAY_HAPTICS_API_MIGRATION)
+                                    ) {
+                                        vibratorHelper.performHapticFeedback(
+                                            view,
+                                            HapticFeedbackConstants.CONFIRM,
+                                        )
+                                    } else {
+                                        vibratorHelper.vibrateAuthSuccess("device-entry::success")
+                                    }
+                                    deviceEntryHapticsInteractor.handleSuccessHaptic()
+                                }
+                        }
+
+                        launch {
+                            deviceEntryHapticsInteractor.playErrorHaptic
+                                .filter { it }
+                                .collect {
+                                    if (
+                                        featureFlags.isEnabled(Flags.ONE_WAY_HAPTICS_API_MIGRATION)
+                                    ) {
+                                        vibratorHelper.performHapticFeedback(
+                                            view,
+                                            HapticFeedbackConstants.REJECT,
+                                        )
+                                    } else {
+                                        vibratorHelper.vibrateAuthSuccess("device-entry::error")
+                                    }
+                                    deviceEntryHapticsInteractor.handleErrorHaptic()
+                                }
+                        }
+                    }
                 }
             }
         viewModel.clockControllerProvider = clockControllerProvider
@@ -189,7 +233,7 @@
         view.setOnHierarchyChangeListener(
             object : OnHierarchyChangeListener {
                 override fun onChildViewAdded(parent: View, child: View) {
-                    childViews.put(child.id, view)
+                    childViews.put(child.id, child)
                 }
 
                 override fun onChildViewRemoved(parent: View, child: View) {
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardPreviewRenderer.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardPreviewRenderer.kt
index 5a4bbef..692984a 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardPreviewRenderer.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/preview/KeyguardPreviewRenderer.kt
@@ -46,6 +46,7 @@
 import com.android.systemui.broadcast.BroadcastDispatcher
 import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.deviceentry.domain.interactor.DeviceEntryHapticsInteractor
 import com.android.systemui.flags.FeatureFlags
 import com.android.systemui.flags.Flags
 import com.android.systemui.keyguard.ui.binder.KeyguardPreviewClockViewBinder
@@ -114,6 +115,7 @@
     private val chipbarCoordinator: ChipbarCoordinator,
     private val keyguardStateController: KeyguardStateController,
     private val shadeInteractor: ShadeInteractor,
+    private val deviceEntryHapticsInteractor: DeviceEntryHapticsInteractor,
 ) {
 
     val hostToken: IBinder? = bundle.getBinder(KEY_HOST_TOKEN)
@@ -339,6 +341,8 @@
                 shadeInteractor,
                 null, // clock provider only needed for burn in
                 null, // jank monitor not required for preview mode
+                null, // device entry haptics not required for preview mode
+                null, // device entry haptics not required for preview mode
             )
         )
         rootView.addView(
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultAmbientIndicationAreaSection.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultAmbientIndicationAreaSection.kt
index 9371d4e..342a440 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultAmbientIndicationAreaSection.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/DefaultAmbientIndicationAreaSection.kt
@@ -50,11 +50,8 @@
 
     override fun addViews(constraintLayout: ConstraintLayout) {
         if (featureFlags.isEnabled(Flags.MIGRATE_SPLIT_KEYGUARD_BOTTOM_AREA)) {
-            val view =
-                LayoutInflater.from(constraintLayout.context)
-                    .inflate(R.layout.ambient_indication, constraintLayout, false)
-
-            constraintLayout.addView(view)
+            LayoutInflater.from(constraintLayout.context)
+                .inflate(R.layout.ambient_indication, constraintLayout, true)
         }
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/MediaProjectionMetricsLogger.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/MediaProjectionMetricsLogger.kt
index 8634b09..a53f0f1 100644
--- a/packages/SystemUI/src/com/android/systemui/mediaprojection/MediaProjectionMetricsLogger.kt
+++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/MediaProjectionMetricsLogger.kt
@@ -19,7 +19,11 @@
 import android.os.Process
 import android.os.RemoteException
 import android.util.Log
-import com.android.internal.util.FrameworkStatsLog
+import com.android.internal.util.FrameworkStatsLog.MEDIA_PROJECTION_STATE_CHANGED__CREATION_SOURCE__CREATION_SOURCE_APP as METRICS_CREATION_SOURCE_APP
+import com.android.internal.util.FrameworkStatsLog.MEDIA_PROJECTION_STATE_CHANGED__CREATION_SOURCE__CREATION_SOURCE_CAST as METRICS_CREATION_SOURCE_CAST
+import com.android.internal.util.FrameworkStatsLog.MEDIA_PROJECTION_STATE_CHANGED__CREATION_SOURCE__CREATION_SOURCE_SYSTEM_UI_SCREEN_RECORDER as METRICS_CREATION_SOURCE_SYSTEM_UI_SCREEN_RECORDER
+import com.android.internal.util.FrameworkStatsLog.MEDIA_PROJECTION_STATE_CHANGED__CREATION_SOURCE__CREATION_SOURCE_UNKNOWN as METRICS_CREATION_SOURCE_UNKNOWN
+import com.android.internal.util.FrameworkStatsLog.MEDIA_PROJECTION_STATE_CHANGED__STATE__MEDIA_PROJECTION_STATE_INITIATED
 import com.android.systemui.dagger.SysUISingleton
 import javax.inject.Inject
 
@@ -36,21 +40,23 @@
      *
      * @param sessionCreationSource The entry point requesting permission to capture.
      */
-    fun notifyPermissionProgress(state: Int, sessionCreationSource: Int) {
-        // TODO check that state & SessionCreationSource matches expected values
-        notifyToServer(state, sessionCreationSource)
+    fun notifyProjectionInitiated(sessionCreationSource: SessionCreationSource) {
+        notifyToServer(
+            MEDIA_PROJECTION_STATE_CHANGED__STATE__MEDIA_PROJECTION_STATE_INITIATED,
+            sessionCreationSource
+        )
     }
 
     /**
      * Request to log that the permission request moved to the given state.
      *
-     * Should not be used for the initialization state, since that
+     * Should not be used for the initialization state, since that should use {@link
+     * MediaProjectionMetricsLogger#notifyProjectionInitiated(Int)} and pass the
+     * sessionCreationSource.
      */
     fun notifyPermissionProgress(state: Int) {
         // TODO validate state is valid
-        notifyToServer(
-            state,
-            FrameworkStatsLog.MEDIA_PROJECTION_STATE_CHANGED__STATE__MEDIA_PROJECTION_STATE_UNKNOWN)
+        notifyToServer(state, SessionCreationSource.UNKNOWN)
     }
 
     /**
@@ -64,16 +70,21 @@
      *   Indicates the entry point for requesting the permission. Must be a valid state defined in
      *   the SessionCreationSource enum.
      */
-    private fun notifyToServer(state: Int, sessionCreationSource: Int) {
+    private fun notifyToServer(state: Int, sessionCreationSource: SessionCreationSource) {
         Log.v(TAG, "FOO notifyToServer of state $state and source $sessionCreationSource")
         try {
             service.notifyPermissionRequestStateChange(
-                Process.myUid(), state, sessionCreationSource)
+                Process.myUid(),
+                state,
+                sessionCreationSource.toMetricsConstant()
+            )
         } catch (e: RemoteException) {
             Log.e(
                 TAG,
-                "Error notifying server of permission flow state $state from source $sessionCreationSource",
-                e)
+                "Error notifying server of permission flow state $state from source " +
+                    "$sessionCreationSource",
+                e
+            )
         }
     }
 
@@ -81,3 +92,18 @@
         const val TAG = "MediaProjectionMetricsLogger"
     }
 }
+
+enum class SessionCreationSource {
+    APP,
+    CAST,
+    SYSTEM_UI_SCREEN_RECORDER,
+    UNKNOWN;
+
+    fun toMetricsConstant(): Int =
+        when (this) {
+            APP -> METRICS_CREATION_SOURCE_APP
+            CAST -> METRICS_CREATION_SOURCE_CAST
+            SYSTEM_UI_SCREEN_RECORDER -> METRICS_CREATION_SOURCE_SYSTEM_UI_SCREEN_RECORDER
+            UNKNOWN -> METRICS_CREATION_SOURCE_UNKNOWN
+        }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorActivity.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorActivity.kt
index b5d3e91..0bbcfd9 100644
--- a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorActivity.kt
+++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorActivity.kt
@@ -87,14 +87,15 @@
 
     override fun getLayoutResource() = R.layout.media_projection_app_selector
 
-    public override fun onCreate(bundle: Bundle?) {
+    public override fun onCreate(savedInstanceState: Bundle?) {
         lifecycleRegistry.handleLifecycleEvent(Lifecycle.Event.ON_CREATE)
         component =
             componentFactory.create(
                 hostUserHandle = hostUserHandle,
                 callingPackage = callingPackage,
                 view = this,
-                resultHandler = this
+                resultHandler = this,
+                isFirstStart = savedInstanceState == null
             )
         component.lifecycleObservers.forEach { lifecycle.addObserver(it) }
 
@@ -113,7 +114,7 @@
         reviewGrantedConsentRequired =
             intent.getBooleanExtra(EXTRA_USER_REVIEW_GRANTED_CONSENT, false)
 
-        super.onCreate(bundle)
+        super.onCreate(savedInstanceState)
         controller.init()
         // we override AppList's AccessibilityDelegate set in ResolverActivity.onCreate because in
         // our case this delegate must extend RecyclerViewAccessibilityDelegate, otherwise
diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorComponent.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorComponent.kt
index 2217509..8c6f307 100644
--- a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorComponent.kt
+++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorComponent.kt
@@ -146,6 +146,9 @@
             @BindsInstance @MediaProjectionAppSelector callingPackage: String?,
             @BindsInstance view: MediaProjectionAppSelectorView,
             @BindsInstance resultHandler: MediaProjectionAppSelectorResultHandler,
+            // Whether the app selector is starting for the first time. False when it is re-starting
+            // due to a config change.
+            @BindsInstance @MediaProjectionAppSelector isFirstStart: Boolean,
         ): MediaProjectionAppSelectorComponent
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorController.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorController.kt
index fced117..69132d3 100644
--- a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorController.kt
+++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorController.kt
@@ -18,6 +18,8 @@
 
 import android.content.ComponentName
 import android.os.UserHandle
+import com.android.internal.util.FrameworkStatsLog.MEDIA_PROJECTION_STATE_CHANGED__STATE__MEDIA_PROJECTION_STATE_APP_SELECTOR_DISPLAYED as STATE_APP_SELECTOR_DISPLAYED
+import com.android.systemui.mediaprojection.MediaProjectionMetricsLogger
 import com.android.systemui.mediaprojection.appselector.data.RecentTask
 import com.android.systemui.mediaprojection.appselector.data.RecentTaskListProvider
 import com.android.systemui.mediaprojection.appselector.data.RecentTaskThumbnailLoader
@@ -43,9 +45,17 @@
     @MediaProjectionAppSelector private val appSelectorComponentName: ComponentName,
     @MediaProjectionAppSelector private val callerPackageName: String?,
     private val thumbnailLoader: RecentTaskThumbnailLoader,
+    @MediaProjectionAppSelector private val isFirstStart: Boolean,
+    private val logger: MediaProjectionMetricsLogger,
 ) {
 
     fun init() {
+        // Only log during the first start of the app selector.
+        // Don't log when the app selector restarts due to a config change.
+        if (isFirstStart) {
+            logger.notifyPermissionProgress(STATE_APP_SELECTOR_DISPLAYED)
+        }
+
         scope.launch {
             val recentTasks = recentTaskListProvider.loadRecentTasks()
 
diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/data/RecentTask.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/data/RecentTask.kt
index a9e6c53..e9b4582 100644
--- a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/data/RecentTask.kt
+++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/data/RecentTask.kt
@@ -22,6 +22,7 @@
 
 data class RecentTask(
     val taskId: Int,
+    val displayId: Int,
     @UserIdInt val userId: Int,
     val topActivityComponent: ComponentName?,
     val baseIntentComponent: ComponentName?,
diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/data/RecentTaskListProvider.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/data/RecentTaskListProvider.kt
index aa4c4e5..730aa62 100644
--- a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/data/RecentTaskListProvider.kt
+++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/data/RecentTaskListProvider.kt
@@ -60,6 +60,7 @@
                 .map {
                     RecentTask(
                         it.taskId,
+                        it.displayId,
                         it.userId,
                         it.topActivity,
                         it.baseIntent?.component,
diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/view/MediaProjectionRecentsViewController.kt b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/view/MediaProjectionRecentsViewController.kt
index fd1a683..ba837db 100644
--- a/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/view/MediaProjectionRecentsViewController.kt
+++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/appselector/view/MediaProjectionRecentsViewController.kt
@@ -130,10 +130,10 @@
                 view.width,
                 view.height
             )
-        activityOptions.setPendingIntentBackgroundActivityStartMode(
+        activityOptions.pendingIntentBackgroundActivityStartMode =
             MODE_BACKGROUND_ACTIVITY_START_ALLOWED
-        )
         activityOptions.launchCookie = launchCookie
+        activityOptions.launchDisplayId = task.displayId
 
         activityTaskManager.startActivityFromRecents(task.taskId, activityOptions.toBundle())
         resultHandler.returnSelectedApp(launchCookie)
diff --git a/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/MediaProjectionPermissionActivity.java b/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/MediaProjectionPermissionActivity.java
index d08d040..fa418fc 100644
--- a/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/MediaProjectionPermissionActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/mediaprojection/permission/MediaProjectionPermissionActivity.java
@@ -53,7 +53,9 @@
 
 import com.android.systemui.flags.FeatureFlags;
 import com.android.systemui.flags.Flags;
+import com.android.systemui.mediaprojection.MediaProjectionMetricsLogger;
 import com.android.systemui.mediaprojection.MediaProjectionServiceHelper;
+import com.android.systemui.mediaprojection.SessionCreationSource;
 import com.android.systemui.mediaprojection.appselector.MediaProjectionAppSelectorActivity;
 import com.android.systemui.mediaprojection.devicepolicy.ScreenCaptureDevicePolicyResolver;
 import com.android.systemui.mediaprojection.devicepolicy.ScreenCaptureDisabledDialog;
@@ -74,6 +76,7 @@
     private final FeatureFlags mFeatureFlags;
     private final Lazy<ScreenCaptureDevicePolicyResolver> mScreenCaptureDevicePolicyResolver;
     private final StatusBarManager mStatusBarManager;
+    private final MediaProjectionMetricsLogger mMediaProjectionMetricsLogger;
 
     private String mPackageName;
     private int mUid;
@@ -90,15 +93,17 @@
     @Inject
     public MediaProjectionPermissionActivity(FeatureFlags featureFlags,
             Lazy<ScreenCaptureDevicePolicyResolver> screenCaptureDevicePolicyResolver,
-            StatusBarManager statusBarManager) {
+            StatusBarManager statusBarManager,
+            MediaProjectionMetricsLogger mediaProjectionMetricsLogger) {
         mFeatureFlags = featureFlags;
         mScreenCaptureDevicePolicyResolver = screenCaptureDevicePolicyResolver;
         mStatusBarManager = statusBarManager;
+        mMediaProjectionMetricsLogger = mediaProjectionMetricsLogger;
     }
 
     @Override
-    public void onCreate(Bundle icicle) {
-        super.onCreate(icicle);
+    public void onCreate(Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
 
         final Intent launchingIntent = getIntent();
         mReviewGrantedConsentRequired = launchingIntent.getBooleanExtra(
@@ -133,6 +138,10 @@
 
         try {
             if (MediaProjectionServiceHelper.hasProjectionPermission(mUid, mPackageName)) {
+                if (savedInstanceState == null) {
+                    mMediaProjectionMetricsLogger.notifyProjectionInitiated(
+                            SessionCreationSource.APP);
+                }
                 final IMediaProjection projection =
                         MediaProjectionServiceHelper.createOrReuseProjection(mUid, mPackageName,
                                 mReviewGrantedConsentRequired);
@@ -231,6 +240,13 @@
             mDialog = dialogBuilder.create();
         }
 
+        if (savedInstanceState == null) {
+            mMediaProjectionMetricsLogger.notifyProjectionInitiated(
+                    appName == null
+                            ? SessionCreationSource.CAST
+                            : SessionCreationSource.APP);
+        }
+
         setUpDialog(mDialog);
         mDialog.show();
     }
diff --git a/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingController.java b/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingController.java
index 7a0c087..f469c6b 100644
--- a/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingController.java
+++ b/packages/SystemUI/src/com/android/systemui/screenrecord/RecordingController.java
@@ -38,6 +38,8 @@
 import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.flags.FeatureFlags;
 import com.android.systemui.flags.Flags;
+import com.android.systemui.mediaprojection.MediaProjectionMetricsLogger;
+import com.android.systemui.mediaprojection.SessionCreationSource;
 import com.android.systemui.mediaprojection.devicepolicy.ScreenCaptureDevicePolicyResolver;
 import com.android.systemui.mediaprojection.devicepolicy.ScreenCaptureDisabledDialog;
 import com.android.systemui.plugins.ActivityStarter;
@@ -45,13 +47,13 @@
 import com.android.systemui.settings.UserTracker;
 import com.android.systemui.statusbar.policy.CallbackController;
 
+import dagger.Lazy;
+
 import java.util.concurrent.CopyOnWriteArrayList;
 import java.util.concurrent.Executor;
 
 import javax.inject.Inject;
 
-import dagger.Lazy;
-
 /**
  * Helper class to initiate a screen recording
  */
@@ -71,6 +73,7 @@
     private final FeatureFlags mFlags;
     private final UserContextProvider mUserContextProvider;
     private final UserTracker mUserTracker;
+    private final MediaProjectionMetricsLogger mMediaProjectionMetricsLogger;
 
     protected static final String INTENT_UPDATE_STATE =
             "com.android.systemui.screenrecord.UPDATE_STATE";
@@ -115,7 +118,8 @@
             FeatureFlags flags,
             UserContextProvider userContextProvider,
             Lazy<ScreenCaptureDevicePolicyResolver> devicePolicyResolver,
-            UserTracker userTracker) {
+            UserTracker userTracker,
+            MediaProjectionMetricsLogger mediaProjectionMetricsLogger) {
         mMainExecutor = mainExecutor;
         mContext = context;
         mFlags = flags;
@@ -123,6 +127,7 @@
         mBroadcastDispatcher = broadcastDispatcher;
         mUserContextProvider = userContextProvider;
         mUserTracker = userTracker;
+        mMediaProjectionMetricsLogger = mediaProjectionMetricsLogger;
 
         BroadcastOptions options = BroadcastOptions.makeBasic();
         options.setInteractive(true);
@@ -149,6 +154,9 @@
             return new ScreenCaptureDisabledDialog(mContext);
         }
 
+        mMediaProjectionMetricsLogger.notifyProjectionInitiated(
+                SessionCreationSource.SYSTEM_UI_SCREEN_RECORDER);
+
         return flags.isEnabled(Flags.WM_ENABLE_PARTIAL_SCREEN_SHARING)
                 ? new ScreenRecordPermissionDialog(context,  getHostUserHandle(), this,
                     activityStarter, mUserContextProvider, onStartRecordingClicked)
diff --git a/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessDialog.java b/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessDialog.java
index 6783afa..1ecb127 100644
--- a/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessDialog.java
+++ b/packages/SystemUI/src/com/android/systemui/settings/brightness/BrightnessDialog.java
@@ -21,6 +21,7 @@
 import static android.view.WindowManagerPolicyConstants.EXTRA_FROM_BRIGHTNESS_KEY;
 
 import android.app.Activity;
+import android.content.res.Configuration;
 import android.graphics.Rect;
 import android.os.Bundle;
 import android.view.Gravity;
@@ -35,8 +36,8 @@
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.logging.MetricsLogger;
 import com.android.internal.logging.nano.MetricsProto.MetricsEvent;
-import com.android.systemui.res.R;
 import com.android.systemui.dagger.qualifiers.Main;
+import com.android.systemui.res.R;
 import com.android.systemui.statusbar.policy.AccessibilityManagerWrapper;
 import com.android.systemui.util.concurrency.DelayableExecutor;
 
@@ -74,21 +75,26 @@
     @Override
     protected void onCreate(Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
+        setWindowAttributes();
+        setContentView(R.layout.brightness_mirror_container);
+        setBrightnessDialogViewAttributes();
+    }
 
+    private void setWindowAttributes() {
         final Window window = getWindow();
 
-        window.setGravity(Gravity.TOP | Gravity.CENTER_HORIZONTAL);
+        window.setGravity(Gravity.TOP | Gravity.LEFT);
         window.clearFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND);
         window.requestFeature(Window.FEATURE_NO_TITLE);
 
         // Calling this creates the decor View, so setLayout takes proper effect
         // (see Dialog#onWindowAttributesChanged)
         window.getDecorView();
-        window.setLayout(
-                WindowManager.LayoutParams.MATCH_PARENT, WindowManager.LayoutParams.WRAP_CONTENT);
+        window.setLayout(WRAP_CONTENT, WRAP_CONTENT);
         getTheme().applyStyle(R.style.Theme_SystemUI_QuickSettings, false);
+    }
 
-        setContentView(R.layout.brightness_mirror_container);
+    void setBrightnessDialogViewAttributes() {
         FrameLayout frame = findViewById(R.id.brightness_mirror_container);
         // The brightness mirror container is INVISIBLE by default.
         frame.setVisibility(View.VISIBLE);
@@ -97,6 +103,14 @@
                 getResources().getDimensionPixelSize(R.dimen.notification_side_paddings);
         lp.leftMargin = horizontalMargin;
         lp.rightMargin = horizontalMargin;
+
+        int verticalMargin =
+                getResources().getDimensionPixelSize(
+                        R.dimen.notification_guts_option_vertical_padding);
+
+        lp.topMargin = verticalMargin;
+        lp.bottomMargin = verticalMargin;
+
         frame.setLayoutParams(lp);
         Rect bounds = new Rect();
         frame.addOnLayoutChangeListener(
@@ -113,6 +127,20 @@
         frame.addView(controller.getRootView(), MATCH_PARENT, WRAP_CONTENT);
 
         mBrightnessController = mBrightnessControllerFactory.create(controller);
+
+        Configuration configuration = getResources().getConfiguration();
+        int orientation = configuration.orientation;
+
+        if (orientation == Configuration.ORIENTATION_LANDSCAPE) {
+            lp.width = getWindowManager().getDefaultDisplay().getWidth() / 2
+                    - lp.leftMargin * 2;
+        } else if (orientation == Configuration.ORIENTATION_PORTRAIT) {
+            lp.width = getWindowManager().getDefaultDisplay().getWidth()
+                    - lp.leftMargin * 2;
+        }
+
+        frame.setLayoutParams(lp);
+
     }
 
     @Override
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/MediaArtworkProcessor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/MediaArtworkProcessor.kt
deleted file mode 100644
index 17b4e3b..0000000
--- a/packages/SystemUI/src/com/android/systemui/statusbar/MediaArtworkProcessor.kt
+++ /dev/null
@@ -1,101 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package com.android.systemui.statusbar
-
-import android.content.Context
-import android.graphics.Bitmap
-import android.graphics.Canvas
-import android.graphics.Point
-import android.graphics.Rect
-import android.renderscript.Allocation
-import android.renderscript.Element
-import android.renderscript.RenderScript
-import android.renderscript.ScriptIntrinsicBlur
-import android.util.Log
-import android.util.MathUtils
-import com.android.internal.graphics.ColorUtils
-import com.android.systemui.dagger.SysUISingleton
-import com.android.systemui.statusbar.notification.MediaNotificationProcessor
-import javax.inject.Inject
-
-private const val TAG = "MediaArtworkProcessor"
-private const val COLOR_ALPHA = (255 * 0.7f).toInt()
-private const val BLUR_RADIUS = 25f
-private const val DOWNSAMPLE = 6
-
-@SysUISingleton
-class MediaArtworkProcessor @Inject constructor() {
-
-    private val mTmpSize = Point()
-    private var mArtworkCache: Bitmap? = null
-
-    fun processArtwork(context: Context, artwork: Bitmap): Bitmap? {
-        if (mArtworkCache != null) {
-            return mArtworkCache
-        }
-        val renderScript = RenderScript.create(context)
-        val blur = ScriptIntrinsicBlur.create(renderScript, Element.U8_4(renderScript))
-        var input: Allocation? = null
-        var output: Allocation? = null
-        var inBitmap: Bitmap? = null
-        try {
-            @Suppress("DEPRECATION")
-            context.display?.getSize(mTmpSize)
-            val rect = Rect(0, 0, artwork.width, artwork.height)
-            MathUtils.fitRect(rect, Math.max(mTmpSize.x / DOWNSAMPLE, mTmpSize.y / DOWNSAMPLE))
-            inBitmap = Bitmap.createScaledBitmap(artwork, rect.width(), rect.height(),
-                    true /* filter */)
-            // Render script blurs only support ARGB_8888, we need a conversion if we got a
-            // different bitmap config.
-            if (inBitmap.config != Bitmap.Config.ARGB_8888) {
-                val oldIn = inBitmap
-                inBitmap = oldIn.copy(Bitmap.Config.ARGB_8888, false /* isMutable */)
-                oldIn.recycle()
-            }
-            val outBitmap = Bitmap.createBitmap(inBitmap?.width ?: 0, inBitmap?.height ?: 0,
-                    Bitmap.Config.ARGB_8888)
-
-            input = Allocation.createFromBitmap(renderScript, inBitmap,
-                    Allocation.MipmapControl.MIPMAP_NONE, Allocation.USAGE_GRAPHICS_TEXTURE)
-            output = Allocation.createFromBitmap(renderScript, outBitmap)
-
-            blur.setRadius(BLUR_RADIUS)
-            blur.setInput(input)
-            blur.forEach(output)
-            output.copyTo(outBitmap)
-
-            val swatch = MediaNotificationProcessor.findBackgroundSwatch(artwork)
-
-            val canvas = Canvas(outBitmap)
-            canvas.drawColor(ColorUtils.setAlphaComponent(swatch.rgb, COLOR_ALPHA))
-            return outBitmap
-        } catch (ex: IllegalArgumentException) {
-            Log.e(TAG, "error while processing artwork", ex)
-            return null
-        } finally {
-            input?.destroy()
-            output?.destroy()
-            blur.destroy()
-            inBitmap?.recycle()
-        }
-    }
-
-    fun clearCache() {
-        mArtworkCache?.recycle()
-        mArtworkCache = null
-    }
-}
\ No newline at end of file
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaManager.java
index 5bd40b8..389486f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/NotificationMediaManager.java
@@ -15,43 +15,29 @@
  */
 package com.android.systemui.statusbar;
 
-import static com.android.systemui.statusbar.StatusBarState.KEYGUARD;
-import static com.android.systemui.statusbar.phone.CentralSurfaces.DEBUG_MEDIA_FAKE_ARTWORK;
-import static com.android.systemui.statusbar.phone.CentralSurfaces.ENABLE_LOCKSCREEN_WALLPAPER;
-import static com.android.systemui.statusbar.phone.CentralSurfaces.SHOW_LOCKSCREEN_MEDIA_ARTWORK;
-
-import android.annotation.MainThread;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.app.Notification;
 import android.app.WallpaperManager;
 import android.content.Context;
-import android.graphics.Bitmap;
 import android.graphics.Point;
-import android.graphics.drawable.BitmapDrawable;
-import android.graphics.drawable.ColorDrawable;
-import android.graphics.drawable.Drawable;
 import android.graphics.drawable.Icon;
 import android.hardware.display.DisplayManager;
 import android.media.MediaMetadata;
 import android.media.session.MediaController;
 import android.media.session.MediaSession;
 import android.media.session.PlaybackState;
-import android.os.AsyncTask;
 import android.os.Trace;
 import android.service.notification.NotificationStats;
 import android.service.notification.StatusBarNotification;
-import android.util.ArraySet;
 import android.util.Log;
 import android.view.Display;
 import android.view.View;
 import android.widget.ImageView;
 
-import com.android.app.animation.Interpolators;
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.systemui.Dumpable;
 import com.android.systemui.colorextraction.SysuiColorExtractor;
-import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.dump.DumpManager;
 import com.android.systemui.media.controls.models.player.MediaData;
 import com.android.systemui.media.controls.models.recommendation.SmartspaceMediaData;
@@ -65,18 +51,11 @@
 import com.android.systemui.statusbar.notification.collection.notifcollection.NotifCollectionListener;
 import com.android.systemui.statusbar.notification.collection.render.NotificationVisibilityProvider;
 import com.android.systemui.statusbar.phone.BiometricUnlockController;
-import com.android.systemui.statusbar.phone.KeyguardBypassController;
 import com.android.systemui.statusbar.phone.LockscreenWallpaper;
 import com.android.systemui.statusbar.phone.ScrimController;
-import com.android.systemui.statusbar.phone.ScrimState;
 import com.android.systemui.statusbar.policy.KeyguardStateController;
-import com.android.systemui.util.Utils;
-import com.android.systemui.util.concurrency.DelayableExecutor;
-
-import dagger.Lazy;
 
 import java.io.PrintWriter;
-import java.lang.ref.WeakReference;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collection;
@@ -85,7 +64,6 @@
 import java.util.List;
 import java.util.Objects;
 import java.util.Optional;
-import java.util.Set;
 import java.util.stream.Collectors;
 
 /**
@@ -99,7 +77,6 @@
     private final StatusBarStateController mStatusBarStateController;
     private final SysuiColorExtractor mColorExtractor;
     private final KeyguardStateController mKeyguardStateController;
-    private final KeyguardBypassController mKeyguardBypassController;
     private static final HashSet<Integer> PAUSED_MEDIA_STATES = new HashSet<>();
     private static final HashSet<Integer> CONNECTING_MEDIA_STATES = new HashSet<>();
     static {
@@ -117,9 +94,6 @@
     private final NotifCollection mNotifCollection;
 
     @Nullable
-    private Lazy<NotificationShadeWindowController> mNotificationShadeWindowController;
-
-    @Nullable
     private BiometricUnlockController mBiometricUnlockController;
     @Nullable
     private ScrimController mScrimController;
@@ -128,12 +102,8 @@
     @VisibleForTesting
     boolean mIsLockscreenLiveWallpaperEnabled;
 
-    private final DelayableExecutor mMainExecutor;
-
     private final Context mContext;
     private final ArrayList<MediaListener> mMediaListeners;
-    private final MediaArtworkProcessor mMediaArtworkProcessor;
-    private final Set<AsyncTask<?, ?, ?>> mProcessArtworkTasks = new ArraySet<>();
 
     protected NotificationPresenter mPresenter;
     private MediaController mMediaController;
@@ -150,8 +120,6 @@
     private List<String> mSmallerInternalDisplayUids;
     private Display mCurrentDisplay;
 
-    private LockscreenWallpaper.WallpaperDrawable mWallapperDrawable;
-
     private final MediaController.Callback mMediaListener = new MediaController.Callback() {
         @Override
         public void onPlaybackStateChanged(PlaybackState state) {
@@ -173,7 +141,6 @@
             if (DEBUG_MEDIA) {
                 Log.v(TAG, "DEBUG_MEDIA: onMetadataChanged: " + metadata);
             }
-            mMediaArtworkProcessor.clearCache();
             mMediaMetadata = metadata;
             dispatchUpdateMediaMetaData(true /* changed */, true /* allowAnimation */);
         }
@@ -184,13 +151,9 @@
      */
     public NotificationMediaManager(
             Context context,
-            Lazy<NotificationShadeWindowController> notificationShadeWindowController,
             NotificationVisibilityProvider visibilityProvider,
-            MediaArtworkProcessor mediaArtworkProcessor,
-            KeyguardBypassController keyguardBypassController,
             NotifPipeline notifPipeline,
             NotifCollection notifCollection,
-            @Main DelayableExecutor mainExecutor,
             MediaDataManager mediaDataManager,
             StatusBarStateController statusBarStateController,
             SysuiColorExtractor colorExtractor,
@@ -199,12 +162,8 @@
             WallpaperManager wallpaperManager,
             DisplayManager displayManager) {
         mContext = context;
-        mMediaArtworkProcessor = mediaArtworkProcessor;
-        mKeyguardBypassController = keyguardBypassController;
         mMediaListeners = new ArrayList<>();
-        mNotificationShadeWindowController = notificationShadeWindowController;
         mVisibilityProvider = visibilityProvider;
-        mMainExecutor = mainExecutor;
         mMediaDataManager = mediaDataManager;
         mNotifPipeline = notifPipeline;
         mNotifCollection = notifCollection;
@@ -476,7 +435,6 @@
     }
 
     private void clearCurrentMediaNotificationSession() {
-        mMediaArtworkProcessor.clearCache();
         mMediaMetadata = null;
         if (mMediaController != null) {
             if (DEBUG_MEDIA) {
@@ -494,9 +452,6 @@
     public void onDisplayUpdated(Display display) {
         Trace.beginSection("NotificationMediaManager#onDisplayUpdated");
         mCurrentDisplay = display;
-        if (mWallapperDrawable != null) {
-            mWallapperDrawable.onDisplayUpdated(isOnSmallerInternalDisplays());
-        }
         Trace.endSection();
     }
 
@@ -531,18 +486,13 @@
     }
 
     /**
-     * Refresh or remove lockscreen artwork from media metadata or the lockscreen wallpaper.
+     * Update media state of lockscreen media views and controllers .
      */
-    public void updateMediaMetaData(boolean metaDataChanged, boolean allowEnterAnimation) {
+    public void updateMediaMetaData(boolean metaDataChanged) {
 
         if (mIsLockscreenLiveWallpaperEnabled) return;
 
         Trace.beginSection("CentralSurfaces#updateMediaMetaData");
-        if (!SHOW_LOCKSCREEN_MEDIA_ARTWORK) {
-            Trace.endSection();
-            return;
-        }
-
         if (getBackDropView() == null) {
             Trace.endSection();
             return; // called too early
@@ -566,170 +516,14 @@
                     + " state=" + mStatusBarStateController.getState());
         }
 
-        Bitmap artworkBitmap = null;
-        if (mediaMetadata != null && !mKeyguardBypassController.getBypassEnabled()) {
-            artworkBitmap = mediaMetadata.getBitmap(MediaMetadata.METADATA_KEY_ART);
-            if (artworkBitmap == null) {
-                artworkBitmap = mediaMetadata.getBitmap(MediaMetadata.METADATA_KEY_ALBUM_ART);
-            }
-        }
-
-        // Process artwork on a background thread and send the resulting bitmap to
-        // finishUpdateMediaMetaData.
-        if (metaDataChanged) {
-            for (AsyncTask<?, ?, ?> task : mProcessArtworkTasks) {
-                task.cancel(true);
-            }
-            mProcessArtworkTasks.clear();
-        }
-        if (artworkBitmap != null && !Utils.useQsMediaPlayer(mContext)) {
-            mProcessArtworkTasks.add(new ProcessArtworkTask(this, metaDataChanged,
-                    allowEnterAnimation).execute(artworkBitmap));
-        } else {
-            finishUpdateMediaMetaData(metaDataChanged, allowEnterAnimation, null);
+        mColorExtractor.setHasMediaArtwork(false);
+        if (mScrimController != null) {
+            mScrimController.setHasBackdrop(false);
         }
 
         Trace.endSection();
     }
 
-    private void finishUpdateMediaMetaData(boolean metaDataChanged, boolean allowEnterAnimation,
-            @Nullable Bitmap bmp) {
-        Drawable artworkDrawable = null;
-        if (bmp != null) {
-            artworkDrawable = new BitmapDrawable(mBackdropBack.getResources(), bmp);
-        }
-        boolean hasMediaArtwork = artworkDrawable != null;
-        boolean allowWhenShade = false;
-        if (ENABLE_LOCKSCREEN_WALLPAPER && artworkDrawable == null) {
-            Bitmap lockWallpaper =
-                    mLockscreenWallpaper != null ? mLockscreenWallpaper.getBitmap() : null;
-            if (lockWallpaper != null) {
-                artworkDrawable = new LockscreenWallpaper.WallpaperDrawable(
-                        mBackdropBack.getResources(), lockWallpaper, isOnSmallerInternalDisplays());
-                // We're in the SHADE mode on the SIM screen - yet we still need to show
-                // the lockscreen wallpaper in that mode.
-                allowWhenShade = mStatusBarStateController.getState() == KEYGUARD;
-            }
-        }
-
-        NotificationShadeWindowController windowController =
-                mNotificationShadeWindowController.get();
-        boolean hideBecauseOccluded = mKeyguardStateController.isOccluded();
-
-        final boolean hasArtwork = artworkDrawable != null;
-        mColorExtractor.setHasMediaArtwork(hasMediaArtwork);
-        if (mScrimController != null) {
-            mScrimController.setHasBackdrop(hasArtwork);
-        }
-
-        if ((hasArtwork || DEBUG_MEDIA_FAKE_ARTWORK)
-                && (mStatusBarStateController.getState() != StatusBarState.SHADE || allowWhenShade)
-                &&  mBiometricUnlockController != null && mBiometricUnlockController.getMode()
-                        != BiometricUnlockController.MODE_WAKE_AND_UNLOCK_PULSING
-                && !hideBecauseOccluded) {
-            // time to show some art!
-            if (mBackdrop.getVisibility() != View.VISIBLE) {
-                mBackdrop.setVisibility(View.VISIBLE);
-                if (allowEnterAnimation) {
-                    mBackdrop.setAlpha(0);
-                    mBackdrop.animate().alpha(1f);
-                } else {
-                    mBackdrop.animate().cancel();
-                    mBackdrop.setAlpha(1f);
-                }
-                if (windowController != null) {
-                    windowController.setBackdropShowing(true);
-                }
-                metaDataChanged = true;
-                if (DEBUG_MEDIA) {
-                    Log.v(TAG, "DEBUG_MEDIA: Fading in album artwork");
-                }
-            }
-            if (metaDataChanged) {
-                if (mBackdropBack.getDrawable() != null) {
-                    Drawable drawable =
-                            mBackdropBack.getDrawable().getConstantState()
-                                    .newDrawable(mBackdropFront.getResources()).mutate();
-                    mBackdropFront.setImageDrawable(drawable);
-                    mBackdropFront.setAlpha(1f);
-                    mBackdropFront.setVisibility(View.VISIBLE);
-                } else {
-                    mBackdropFront.setVisibility(View.INVISIBLE);
-                }
-
-                if (DEBUG_MEDIA_FAKE_ARTWORK) {
-                    final int c = 0xFF000000 | (int)(Math.random() * 0xFFFFFF);
-                    Log.v(TAG, String.format("DEBUG_MEDIA: setting new color: 0x%08x", c));
-                    mBackdropBack.setBackgroundColor(0xFFFFFFFF);
-                    mBackdropBack.setImageDrawable(new ColorDrawable(c));
-                } else {
-                    if (artworkDrawable instanceof LockscreenWallpaper.WallpaperDrawable) {
-                        mWallapperDrawable =
-                                (LockscreenWallpaper.WallpaperDrawable) artworkDrawable;
-                    }
-                    mBackdropBack.setImageDrawable(artworkDrawable);
-                }
-
-                if (mBackdropFront.getVisibility() == View.VISIBLE) {
-                    if (DEBUG_MEDIA) {
-                        Log.v(TAG, "DEBUG_MEDIA: Crossfading album artwork from "
-                                + mBackdropFront.getDrawable()
-                                + " to "
-                                + mBackdropBack.getDrawable());
-                    }
-                    mBackdropFront.animate()
-                            .setDuration(250)
-                            .alpha(0f).withEndAction(mHideBackdropFront);
-                }
-            }
-        } else {
-            // need to hide the album art, either because we are unlocked, on AOD
-            // or because the metadata isn't there to support it
-            if (mBackdrop.getVisibility() != View.GONE) {
-                if (DEBUG_MEDIA) {
-                    Log.v(TAG, "DEBUG_MEDIA: Fading out album artwork");
-                }
-                boolean cannotAnimateDoze = mStatusBarStateController.isDozing()
-                        && !ScrimState.AOD.getAnimateChange();
-                if (((mBiometricUnlockController != null && mBiometricUnlockController.getMode()
-                        == BiometricUnlockController.MODE_WAKE_AND_UNLOCK_PULSING
-                                || cannotAnimateDoze))
-                        || hideBecauseOccluded) {
-                    // We are unlocking directly - no animation!
-                    mBackdrop.setVisibility(View.GONE);
-                    mBackdropBack.setImageDrawable(null);
-                    if (windowController != null) {
-                        windowController.setBackdropShowing(false);
-                    }
-                } else {
-                    if (windowController != null) {
-                        windowController.setBackdropShowing(false);
-                    }
-                    mBackdrop.animate()
-                            .alpha(0)
-                            .setInterpolator(Interpolators.ACCELERATE_DECELERATE)
-                            .setDuration(300)
-                            .setStartDelay(0)
-                            .withEndAction(() -> {
-                                mBackdrop.setVisibility(View.GONE);
-                                mBackdropFront.animate().cancel();
-                                mBackdropBack.setImageDrawable(null);
-                                mMainExecutor.execute(mHideBackdropFront);
-                            });
-                    if (mKeyguardStateController.isKeyguardFadingAway()) {
-                        mBackdrop.animate()
-                                .setDuration(
-                                        mKeyguardStateController.getShortenedFadingAwayDuration())
-                                .setStartDelay(
-                                        mKeyguardStateController.getKeyguardFadingAwayDelay())
-                                .setInterpolator(Interpolators.LINEAR)
-                                .start();
-                    }
-                }
-            }
-        }
-    }
-
     public void setup(BackDropView backdrop, ImageView backdropFront, ImageView backdropBack,
             ScrimController scrimController, LockscreenWallpaper lockscreenWallpaper) {
         mBackdrop = backdrop;
@@ -758,15 +552,6 @@
         }
     };
 
-    private Bitmap processArtwork(Bitmap artwork) {
-        return mMediaArtworkProcessor.processArtwork(mContext, artwork);
-    }
-
-    @MainThread
-    private void removeTask(AsyncTask<?, ?, ?> task) {
-        mProcessArtworkTasks.remove(task);
-    }
-
     // TODO(b/273443374): remove
     public boolean isLockscreenWallpaperOnNotificationShade() {
         return mBackdrop != null && mLockscreenWallpaper != null
@@ -780,52 +565,6 @@
         return mBackdrop;
     }
 
-    /**
-     * {@link AsyncTask} to prepare album art for use as backdrop on lock screen.
-     */
-    private static final class ProcessArtworkTask extends AsyncTask<Bitmap, Void, Bitmap> {
-
-        private final WeakReference<NotificationMediaManager> mManagerRef;
-        private final boolean mMetaDataChanged;
-        private final boolean mAllowEnterAnimation;
-
-        ProcessArtworkTask(NotificationMediaManager manager, boolean changed,
-                boolean allowAnimation) {
-            mManagerRef = new WeakReference<>(manager);
-            mMetaDataChanged = changed;
-            mAllowEnterAnimation = allowAnimation;
-        }
-
-        @Override
-        protected Bitmap doInBackground(Bitmap... bitmaps) {
-            NotificationMediaManager manager = mManagerRef.get();
-            if (manager == null || bitmaps.length == 0 || isCancelled()) {
-                return null;
-            }
-            return manager.processArtwork(bitmaps[0]);
-        }
-
-        @Override
-        protected void onPostExecute(@Nullable Bitmap result) {
-            NotificationMediaManager manager = mManagerRef.get();
-            if (manager != null && !isCancelled()) {
-                manager.removeTask(this);
-                manager.finishUpdateMediaMetaData(mMetaDataChanged, mAllowEnterAnimation, result);
-            }
-        }
-
-        @Override
-        protected void onCancelled(Bitmap result) {
-            if (result != null) {
-                result.recycle();
-            }
-            NotificationMediaManager manager = mManagerRef.get();
-            if (manager != null) {
-                manager.removeTask(this);
-            }
-        }
-    }
-
     public interface MediaListener {
         /**
          * Called whenever there's new metadata or playback state.
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/dagger/CentralSurfacesDependenciesModule.java b/packages/SystemUI/src/com/android/systemui/statusbar/dagger/CentralSurfacesDependenciesModule.java
index 7f5829d..125c8efe 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/dagger/CentralSurfacesDependenciesModule.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/dagger/CentralSurfacesDependenciesModule.java
@@ -31,7 +31,6 @@
 import com.android.systemui.bouncer.domain.interactor.AlternateBouncerInteractor;
 import com.android.systemui.colorextraction.SysuiColorExtractor;
 import com.android.systemui.dagger.SysUISingleton;
-import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.dump.DumpHandler;
 import com.android.systemui.dump.DumpManager;
 import com.android.systemui.flags.FeatureFlags;
@@ -45,12 +44,10 @@
 import com.android.systemui.shade.carrier.ShadeCarrierGroupController;
 import com.android.systemui.statusbar.ActionClickLogger;
 import com.android.systemui.statusbar.CommandQueue;
-import com.android.systemui.statusbar.MediaArtworkProcessor;
 import com.android.systemui.statusbar.NotificationClickNotifier;
 import com.android.systemui.statusbar.NotificationLockscreenUserManager;
 import com.android.systemui.statusbar.NotificationMediaManager;
 import com.android.systemui.statusbar.NotificationRemoteInputManager;
-import com.android.systemui.statusbar.NotificationShadeWindowController;
 import com.android.systemui.statusbar.SmartReplyController;
 import com.android.systemui.statusbar.StatusBarStateControllerImpl;
 import com.android.systemui.statusbar.SysuiStatusBarStateController;
@@ -61,7 +58,6 @@
 import com.android.systemui.statusbar.notification.collection.NotifPipeline;
 import com.android.systemui.statusbar.notification.collection.render.NotificationVisibilityProvider;
 import com.android.systemui.statusbar.phone.CentralSurfacesImpl;
-import com.android.systemui.statusbar.phone.KeyguardBypassController;
 import com.android.systemui.statusbar.phone.ManagedProfileController;
 import com.android.systemui.statusbar.phone.ManagedProfileControllerImpl;
 import com.android.systemui.statusbar.phone.StatusBarIconController;
@@ -71,7 +67,6 @@
 import com.android.systemui.statusbar.phone.StatusBarRemoteInputCallback;
 import com.android.systemui.statusbar.policy.KeyguardStateController;
 import com.android.systemui.statusbar.policy.RemoteInputUriController;
-import com.android.systemui.util.concurrency.DelayableExecutor;
 
 import dagger.Binds;
 import dagger.Lazy;
@@ -122,13 +117,9 @@
     @Provides
     static NotificationMediaManager provideNotificationMediaManager(
             Context context,
-            Lazy<NotificationShadeWindowController> notificationShadeWindowController,
             NotificationVisibilityProvider visibilityProvider,
-            MediaArtworkProcessor mediaArtworkProcessor,
-            KeyguardBypassController keyguardBypassController,
             NotifPipeline notifPipeline,
             NotifCollection notifCollection,
-            @Main DelayableExecutor mainExecutor,
             MediaDataManager mediaDataManager,
             StatusBarStateController statusBarStateController,
             SysuiColorExtractor colorExtractor,
@@ -138,13 +129,9 @@
             DisplayManager displayManager) {
         return new NotificationMediaManager(
                 context,
-                notificationShadeWindowController,
                 visibilityProvider,
-                mediaArtworkProcessor,
-                keyguardBypassController,
                 notifPipeline,
                 notifCollection,
-                mainExecutor,
                 mediaDataManager,
                 statusBarStateController,
                 colorExtractor,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/MediaNotificationProcessor.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/MediaNotificationProcessor.java
deleted file mode 100644
index 732c115..0000000
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/MediaNotificationProcessor.java
+++ /dev/null
@@ -1,131 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package com.android.systemui.statusbar.notification;
-
-import android.graphics.Bitmap;
-import android.graphics.Color;
-
-import androidx.palette.graphics.Palette;
-
-import java.util.List;
-
-/**
- * A gutted class that now contains only a color extraction utility used by the
- * MediaArtworkProcessor, which has otherwise supplanted this.
- *
- * TODO(b/182926117): move this into MediaArtworkProcessor.kt
- */
-public class MediaNotificationProcessor {
-
-    /**
-     * The population fraction to select a white or black color as the background over a color.
-     */
-    private static final float POPULATION_FRACTION_FOR_WHITE_OR_BLACK = 2.5f;
-    private static final float BLACK_MAX_LIGHTNESS = 0.08f;
-    private static final float WHITE_MIN_LIGHTNESS = 0.90f;
-    private static final int RESIZE_BITMAP_AREA = 150 * 150;
-
-    private MediaNotificationProcessor() {
-    }
-
-    /**
-     * Finds an appropriate background swatch from media artwork.
-     *
-     * @param artwork Media artwork
-     * @return Swatch that should be used as the background of the media notification.
-     */
-    public static Palette.Swatch findBackgroundSwatch(Bitmap artwork) {
-        return findBackgroundSwatch(generateArtworkPaletteBuilder(artwork).generate());
-    }
-
-    /**
-     * Finds an appropriate background swatch from the palette of media artwork.
-     *
-     * @param palette Artwork palette, should be obtained from {@link generateArtworkPaletteBuilder}
-     * @return Swatch that should be used as the background of the media notification.
-     */
-    public static Palette.Swatch findBackgroundSwatch(Palette palette) {
-        // by default we use the dominant palette
-        Palette.Swatch dominantSwatch = palette.getDominantSwatch();
-        if (dominantSwatch == null) {
-            return new Palette.Swatch(Color.WHITE, 100);
-        }
-
-        if (!isWhiteOrBlack(dominantSwatch.getHsl())) {
-            return dominantSwatch;
-        }
-        // Oh well, we selected black or white. Lets look at the second color!
-        List<Palette.Swatch> swatches = palette.getSwatches();
-        float highestNonWhitePopulation = -1;
-        Palette.Swatch second = null;
-        for (Palette.Swatch swatch : swatches) {
-            if (swatch != dominantSwatch
-                    && swatch.getPopulation() > highestNonWhitePopulation
-                    && !isWhiteOrBlack(swatch.getHsl())) {
-                second = swatch;
-                highestNonWhitePopulation = swatch.getPopulation();
-            }
-        }
-        if (second == null) {
-            return dominantSwatch;
-        }
-        if (dominantSwatch.getPopulation() / highestNonWhitePopulation
-                > POPULATION_FRACTION_FOR_WHITE_OR_BLACK) {
-            // The dominant swatch is very dominant, lets take it!
-            // We're not filtering on white or black
-            return dominantSwatch;
-        } else {
-            return second;
-        }
-    }
-
-    /**
-     * Generate a palette builder for media artwork.
-     *
-     * For producing a smooth background transition, the palette is extracted from only the left
-     * side of the artwork.
-     *
-     * @param artwork Media artwork
-     * @return Builder that generates the {@link Palette} for the media artwork.
-     */
-    public static Palette.Builder generateArtworkPaletteBuilder(Bitmap artwork) {
-        // for the background we only take the left side of the image to ensure
-        // a smooth transition
-        return Palette.from(artwork)
-                .setRegion(0, 0, artwork.getWidth() / 2, artwork.getHeight())
-                .clearFilters() // we want all colors, red / white / black ones too!
-                .resizeBitmapArea(RESIZE_BITMAP_AREA);
-    }
-
-    private static boolean isWhiteOrBlack(float[] hsl) {
-        return isBlack(hsl) || isWhite(hsl);
-    }
-
-    /**
-     * @return true if the color represents a color which is close to black.
-     */
-    private static boolean isBlack(float[] hslColor) {
-        return hslColor[2] <= BLACK_MAX_LIGHTNESS;
-    }
-
-    /**
-     * @return true if the color represents a color which is close to white.
-     */
-    private static boolean isWhite(float[] hslColor) {
-        return hslColor[2] >= WHITE_MIN_LIGHTNESS;
-    }
-}
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java
index 2809cad..8129b83 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/BiometricUnlockController.java
@@ -17,8 +17,6 @@
 package com.android.systemui.statusbar.phone;
 
 import static android.app.StatusBarManager.SESSION_KEYGUARD;
-import static com.android.systemui.flags.Flags.ONE_WAY_HAPTICS_API_MIGRATION;
-import static com.android.systemui.keyguard.WakefulnessLifecycle.UNKNOWN_LAST_WAKE_TIME;
 
 import android.annotation.IntDef;
 import android.content.res.Resources;
@@ -30,7 +28,6 @@
 import android.os.Handler;
 import android.os.PowerManager;
 import android.os.Trace;
-import android.view.HapticFeedbackConstants;
 
 import androidx.annotation.Nullable;
 
@@ -47,16 +44,17 @@
 import com.android.keyguard.KeyguardViewController;
 import com.android.keyguard.logging.BiometricUnlockLogger;
 import com.android.systemui.Dumpable;
-import com.android.systemui.res.R;
 import com.android.systemui.biometrics.AuthController;
 import com.android.systemui.dagger.SysUISingleton;
 import com.android.systemui.dagger.qualifiers.Main;
+import com.android.systemui.deviceentry.domain.interactor.DeviceEntryHapticsInteractor;
 import com.android.systemui.dump.DumpManager;
 import com.android.systemui.flags.FeatureFlags;
 import com.android.systemui.keyguard.KeyguardViewMediator;
 import com.android.systemui.keyguard.WakefulnessLifecycle;
 import com.android.systemui.log.SessionTracker;
 import com.android.systemui.plugins.statusbar.StatusBarStateController;
+import com.android.systemui.res.R;
 import com.android.systemui.statusbar.NotificationMediaManager;
 import com.android.systemui.statusbar.NotificationShadeWindowController;
 import com.android.systemui.statusbar.VibratorHelper;
@@ -73,12 +71,14 @@
 
 import javax.inject.Inject;
 
+import kotlinx.coroutines.ExperimentalCoroutinesApi;
+
 /**
  * Controller which coordinates all the biometric unlocking actions with the UI.
  */
+@ExperimentalCoroutinesApi
 @SysUISingleton
 public class BiometricUnlockController extends KeyguardUpdateMonitorCallback implements Dumpable {
-    private static final long RECENT_POWER_BUTTON_PRESS_THRESHOLD_MS = 400L;
     private static final long BIOMETRIC_WAKELOCK_TIMEOUT_MS = 15 * 1000;
     private static final String BIOMETRIC_WAKE_LOCK_NAME = "wake-and-unlock:wakelock";
     private static final UiEventLogger UI_EVENT_LOGGER = new UiEventLoggerImpl();
@@ -175,6 +175,7 @@
     private final BiometricUnlockLogger mLogger;
     private final SystemClock mSystemClock;
     private final boolean mOrderUnlockAndWake;
+    private final DeviceEntryHapticsInteractor mHapticsInteractor;
 
     private long mLastFpFailureUptimeMillis;
     private int mNumConsecutiveFpFailures;
@@ -284,7 +285,8 @@
             ScreenOffAnimationController screenOffAnimationController,
             VibratorHelper vibrator,
             SystemClock systemClock,
-            FeatureFlags featureFlags
+            FeatureFlags featureFlags,
+            DeviceEntryHapticsInteractor hapticsInteractor
     ) {
         mPowerManager = powerManager;
         mUpdateMonitor = keyguardUpdateMonitor;
@@ -314,6 +316,7 @@
         mFeatureFlags = featureFlags;
         mOrderUnlockAndWake = resources.getBoolean(
                 com.android.internal.R.bool.config_orderUnlockAndWake);
+        mHapticsInteractor = hapticsInteractor;
 
         dumpManager.registerDumpable(this);
     }
@@ -434,7 +437,7 @@
         if (mode == MODE_WAKE_AND_UNLOCK
                 || mode == MODE_WAKE_AND_UNLOCK_PULSING || mode == MODE_UNLOCK_COLLAPSING
                 || mode == MODE_WAKE_AND_UNLOCK_FROM_DREAM || mode == MODE_DISMISS_BOUNCER) {
-            vibrateSuccess(biometricSourceType);
+            mHapticsInteractor.vibrateSuccess();
             onBiometricUnlockedWithKeyguardDismissal(biometricSourceType);
         }
         startWakeAndUnlock(mode);
@@ -498,8 +501,7 @@
             case MODE_WAKE_AND_UNLOCK:
                 if (mMode == MODE_WAKE_AND_UNLOCK_PULSING) {
                     Trace.beginSection("MODE_WAKE_AND_UNLOCK_PULSING");
-                    mMediaManager.updateMediaMetaData(false /* metaDataChanged */,
-                            true /* allowEnterAnimation */);
+                    mMediaManager.updateMediaMetaData(false /* metaDataChanged */);
                 } else if (mMode == MODE_WAKE_AND_UNLOCK){
                     Trace.beginSection("MODE_WAKE_AND_UNLOCK");
                 } else {
@@ -723,7 +725,7 @@
                 && !mUpdateMonitor.getCachedIsUnlockWithFingerprintPossible(
                 KeyguardUpdateMonitor.getCurrentUser()))
                 || (biometricSourceType == BiometricSourceType.FINGERPRINT)) {
-            vibrateError(biometricSourceType);
+            mHapticsInteractor.vibrateError();
         }
 
         cleanup();
@@ -750,45 +752,6 @@
         cleanup();
     }
 
-    // these haptics are for device-entry only
-    private void vibrateSuccess(BiometricSourceType type) {
-        if (mAuthController.isSfpsEnrolled(KeyguardUpdateMonitor.getCurrentUser())
-                && lastWakeupFromPowerButtonWithinHapticThreshold()) {
-            mLogger.d("Skip auth success haptic. Power button was recently pressed.");
-            return;
-        }
-        if (mFeatureFlags.isEnabled(ONE_WAY_HAPTICS_API_MIGRATION)) {
-            mVibratorHelper.performHapticFeedback(
-                    mKeyguardViewController.getViewRootImpl().getView(),
-                    HapticFeedbackConstants.CONFIRM
-            );
-        } else {
-            mVibratorHelper.vibrateAuthSuccess(
-                    getClass().getSimpleName() + ", type =" + type + "device-entry::success");
-        }
-    }
-
-    private boolean lastWakeupFromPowerButtonWithinHapticThreshold() {
-        final boolean lastWakeupFromPowerButton = mWakefulnessLifecycle.getLastWakeReason()
-                == PowerManager.WAKE_REASON_POWER_BUTTON;
-        return lastWakeupFromPowerButton
-                && mWakefulnessLifecycle.getLastWakeTime() != UNKNOWN_LAST_WAKE_TIME
-                && mSystemClock.uptimeMillis() - mWakefulnessLifecycle.getLastWakeTime()
-                < RECENT_POWER_BUTTON_PRESS_THRESHOLD_MS;
-    }
-
-    private void vibrateError(BiometricSourceType type) {
-        if (mFeatureFlags.isEnabled(ONE_WAY_HAPTICS_API_MIGRATION)) {
-            mVibratorHelper.performHapticFeedback(
-                    mKeyguardViewController.getViewRootImpl().getView(),
-                    HapticFeedbackConstants.REJECT
-            );
-        } else {
-            mVibratorHelper.vibrateAuthError(
-                    getClass().getSimpleName() + ", type =" + type + "device-entry::error");
-        }
-    }
-
     private void cleanup() {
         releaseBiometricWakeLock();
     }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockscreenWallpaper.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockscreenWallpaper.java
index 92c786f..00fd9fb 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockscreenWallpaper.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/LockscreenWallpaper.java
@@ -263,8 +263,7 @@
                 if (result.success) {
                     mCached = true;
                     mCache = result.bitmap;
-                    mMediaManager.updateMediaMetaData(
-                            true /* metaDataChanged */, true /* allowEnterAnimation */);
+                    mMediaManager.updateMediaMetaData(true /* metaDataChanged */);
                 }
                 mLoader = null;
             }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
index 3adf338..400ac7b 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarKeyguardViewManager.java
@@ -961,7 +961,7 @@
                     SysUiStatsLog.KEYGUARD_STATE_CHANGED__STATE__SHOWN);
         }
         if (isShowing) {
-            mMediaManager.updateMediaMetaData(false, animate && !isOccluded);
+            mMediaManager.updateMediaMetaData(false);
         }
         mNotificationShadeWindowController.setKeyguardOccluded(isOccluded);
 
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java
index 2d14f6b..57a8e6f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/StatusBarNotificationPresenter.java
@@ -221,7 +221,7 @@
 
     @Override
     public void updateMediaMetaData(boolean metaDataChanged, boolean allowEnterAnimation) {
-        mMediaManager.updateMediaMetaData(metaDataChanged, allowEnterAnimation);
+        mMediaManager.updateMediaMetaData(metaDataChanged);
     }
 
     @Override
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryViaTrackerLib.kt b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryViaTrackerLib.kt
index e9e52a2..1670dd3 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryViaTrackerLib.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryViaTrackerLib.kt
@@ -104,7 +104,7 @@
                 val callback =
                     object : WifiPickerTracker.WifiPickerTrackerCallback {
                         override fun onWifiEntriesChanged() {
-                            val connectedEntry = wifiPickerTracker?.connectedWifiEntry
+                            val connectedEntry = wifiPickerTracker.mergedOrPrimaryConnection
                             logOnWifiEntriesChanged(connectedEntry)
 
                             val secondaryNetworks =
@@ -217,6 +217,21 @@
             .stateIn(scope, SharingStarted.Eagerly, emptyList())
 
     /**
+     * [WifiPickerTracker.getConnectedWifiEntry] stores a [MergedCarrierEntry] separately from the
+     * [WifiEntry] for the primary connection. Therefore, we have to prefer the carrier-merged entry
+     * if it exists, falling back on the connected entry if null
+     */
+    private val WifiPickerTracker?.mergedOrPrimaryConnection: WifiEntry?
+        get() {
+            val mergedEntry: MergedCarrierEntry? = this?.mergedCarrierEntry
+            return if (mergedEntry != null && mergedEntry.isDefaultNetwork) {
+                mergedEntry
+            } else {
+                this?.connectedWifiEntry
+            }
+        }
+
+    /**
      * Converts WifiTrackerLib's [WifiEntry] into our internal model only if the entry is the
      * primary network. Returns an inactive network if it's not primary.
      */
diff --git a/packages/SystemUI/src/com/android/systemui/util/sensors/AsyncSensorManager.java b/packages/SystemUI/src/com/android/systemui/util/sensors/AsyncSensorManager.java
index 9b06a37..d566725 100644
--- a/packages/SystemUI/src/com/android/systemui/util/sensors/AsyncSensorManager.java
+++ b/packages/SystemUI/src/com/android/systemui/util/sensors/AsyncSensorManager.java
@@ -191,7 +191,7 @@
     }
 
     @Override
-    protected boolean initDataInjectionImpl(boolean enable) {
+    protected boolean initDataInjectionImpl(boolean enable, @DataInjectionMode int mode) {
         throw new UnsupportedOperationException("not implemented");
     }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/deviceentry/data/repository/DeviceEntryHapticsInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/deviceentry/data/repository/DeviceEntryHapticsInteractorTest.kt
new file mode 100644
index 0000000..9b8e581
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/deviceentry/data/repository/DeviceEntryHapticsInteractorTest.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.deviceentry.data.repository
+
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.android.keyguard.logging.BiometricUnlockLogger
+import com.android.systemui.SysuiTestCase
+import com.android.systemui.biometrics.data.repository.FakeFingerprintPropertyRepository
+import com.android.systemui.biometrics.shared.model.FingerprintSensorType
+import com.android.systemui.biometrics.shared.model.SensorStrength
+import com.android.systemui.classifier.FalsingCollector
+import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.deviceentry.domain.interactor.DeviceEntryHapticsInteractor
+import com.android.systemui.keyevent.data.repository.FakeKeyEventRepository
+import com.android.systemui.keyevent.domain.interactor.KeyEventInteractor
+import com.android.systemui.keyguard.data.repository.FakeBiometricSettingsRepository
+import com.android.systemui.plugins.statusbar.StatusBarStateController
+import com.android.systemui.power.data.repository.FakePowerRepository
+import com.android.systemui.power.domain.interactor.PowerInteractor
+import com.android.systemui.power.shared.model.WakeSleepReason
+import com.android.systemui.power.shared.model.WakefulnessState
+import com.android.systemui.statusbar.phone.ScreenOffAnimationController
+import com.android.systemui.util.time.FakeSystemClock
+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.Mockito.mock
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class DeviceEntryHapticsInteractorTest : SysuiTestCase() {
+
+    private lateinit var repository: DeviceEntryHapticsRepository
+    private lateinit var fingerprintPropertyRepository: FakeFingerprintPropertyRepository
+    private lateinit var biometricSettingsRepository: FakeBiometricSettingsRepository
+    private lateinit var keyEventRepository: FakeKeyEventRepository
+    private lateinit var powerRepository: FakePowerRepository
+    private lateinit var systemClock: FakeSystemClock
+    private lateinit var underTest: DeviceEntryHapticsInteractor
+
+    @Before
+    fun setUp() {
+        repository = DeviceEntryHapticsRepositoryImpl()
+        fingerprintPropertyRepository = FakeFingerprintPropertyRepository()
+        biometricSettingsRepository = FakeBiometricSettingsRepository()
+        keyEventRepository = FakeKeyEventRepository()
+        powerRepository = FakePowerRepository()
+        systemClock = FakeSystemClock()
+        underTest =
+            DeviceEntryHapticsInteractor(
+                repository = repository,
+                fingerprintPropertyRepository = fingerprintPropertyRepository,
+                biometricSettingsRepository = biometricSettingsRepository,
+                keyEventInteractor = KeyEventInteractor(keyEventRepository),
+                powerInteractor =
+                    PowerInteractor(
+                        powerRepository,
+                        mock(FalsingCollector::class.java),
+                        mock(ScreenOffAnimationController::class.java),
+                        mock(StatusBarStateController::class.java),
+                    ),
+                systemClock = systemClock,
+                logger = mock(BiometricUnlockLogger::class.java),
+            )
+    }
+
+    @Test
+    fun nonPowerButtonFPS_vibrateSuccess() = runTest {
+        val playSuccessHaptic by collectLastValue(underTest.playSuccessHaptic)
+        setFingerprintSensorType(FingerprintSensorType.UDFPS_ULTRASONIC)
+        underTest.vibrateSuccess()
+        assertThat(playSuccessHaptic).isTrue()
+    }
+
+    @Test
+    fun powerButtonFPS_vibrateSuccess() = runTest {
+        val playSuccessHaptic by collectLastValue(underTest.playSuccessHaptic)
+        setPowerButtonFingerprintProperty()
+        setFingerprintEnrolled()
+        keyEventRepository.setPowerButtonDown(false)
+
+        // It's been 10 seconds since the last power button wakeup
+        setAwakeFromPowerButton()
+        runCurrent()
+        systemClock.setUptimeMillis(systemClock.uptimeMillis() + 10000)
+
+        underTest.vibrateSuccess()
+        assertThat(playSuccessHaptic).isTrue()
+    }
+
+    @Test
+    fun powerButtonFPS_powerDown_doNotVibrateSuccess() = runTest {
+        val playSuccessHaptic by collectLastValue(underTest.playSuccessHaptic)
+        setPowerButtonFingerprintProperty()
+        setFingerprintEnrolled()
+        keyEventRepository.setPowerButtonDown(true) // power button is currently DOWN
+
+        // It's been 10 seconds since the last power button wakeup
+        setAwakeFromPowerButton()
+        runCurrent()
+        systemClock.setUptimeMillis(systemClock.uptimeMillis() + 10000)
+
+        underTest.vibrateSuccess()
+        assertThat(playSuccessHaptic).isFalse()
+    }
+
+    @Test
+    fun powerButtonFPS_powerButtonRecentlyPressed_doNotVibrateSuccess() = runTest {
+        val playSuccessHaptic by collectLastValue(underTest.playSuccessHaptic)
+        setPowerButtonFingerprintProperty()
+        setFingerprintEnrolled()
+        keyEventRepository.setPowerButtonDown(false)
+
+        // It's only been 50ms since the last power button wakeup
+        setAwakeFromPowerButton()
+        runCurrent()
+        systemClock.setUptimeMillis(systemClock.uptimeMillis() + 50)
+
+        underTest.vibrateSuccess()
+        assertThat(playSuccessHaptic).isFalse()
+    }
+
+    @Test
+    fun nonPowerButtonFPS_vibrateError() = runTest {
+        val playErrorHaptic by collectLastValue(underTest.playErrorHaptic)
+        setFingerprintSensorType(FingerprintSensorType.UDFPS_ULTRASONIC)
+        underTest.vibrateError()
+        assertThat(playErrorHaptic).isTrue()
+    }
+
+    @Test
+    fun powerButtonFPS_vibrateError() = runTest {
+        val playErrorHaptic by collectLastValue(underTest.playErrorHaptic)
+        setPowerButtonFingerprintProperty()
+        setFingerprintEnrolled()
+        underTest.vibrateError()
+        assertThat(playErrorHaptic).isTrue()
+    }
+
+    @Test
+    fun powerButtonFPS_powerDown_doNotVibrateError() = runTest {
+        val playErrorHaptic by collectLastValue(underTest.playErrorHaptic)
+        setPowerButtonFingerprintProperty()
+        setFingerprintEnrolled()
+        keyEventRepository.setPowerButtonDown(true)
+        underTest.vibrateError()
+        assertThat(playErrorHaptic).isFalse()
+    }
+
+    private fun setFingerprintSensorType(fingerprintSensorType: FingerprintSensorType) {
+        fingerprintPropertyRepository.setProperties(
+            sensorId = 0,
+            strength = SensorStrength.STRONG,
+            sensorType = fingerprintSensorType,
+            sensorLocations = mapOf(),
+        )
+    }
+
+    private fun setPowerButtonFingerprintProperty() {
+        setFingerprintSensorType(FingerprintSensorType.POWER_BUTTON)
+    }
+
+    private fun setFingerprintEnrolled() {
+        biometricSettingsRepository.setIsFingerprintAuthEnrolledAndEnabled(true)
+    }
+
+    private fun setAwakeFromPowerButton() {
+        powerRepository.updateWakefulness(
+            WakefulnessState.AWAKE,
+            WakeSleepReason.POWER_BUTTON,
+            WakeSleepReason.POWER_BUTTON,
+            powerButtonLaunchGestureTriggered = false,
+        )
+    }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorControllerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorControllerTest.kt
index 34360d2..6cdf4ef 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/MediaProjectionAppSelectorControllerTest.kt
@@ -4,7 +4,9 @@
 import android.os.UserHandle
 import android.testing.AndroidTestingRunner
 import androidx.test.filters.SmallTest
+import com.android.internal.util.FrameworkStatsLog.MEDIA_PROJECTION_STATE_CHANGED__STATE__MEDIA_PROJECTION_STATE_APP_SELECTOR_DISPLAYED as STATE_APP_SELECTOR_DISPLAYED
 import com.android.systemui.SysuiTestCase
+import com.android.systemui.mediaprojection.MediaProjectionMetricsLogger
 import com.android.systemui.mediaprojection.appselector.data.RecentTask
 import com.android.systemui.mediaprojection.appselector.data.RecentTaskListProvider
 import com.android.systemui.mediaprojection.appselector.data.RecentTaskThumbnailLoader
@@ -20,6 +22,7 @@
 import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
+import org.mockito.Mockito.never
 import org.mockito.Mockito.verify
 
 @RunWith(AndroidTestingRunner::class)
@@ -37,10 +40,11 @@
 
     private val view: MediaProjectionAppSelectorView = mock()
     private val policyResolver: ScreenCaptureDevicePolicyResolver = mock()
+    private val logger = mock<MediaProjectionMetricsLogger>()
 
     private val thumbnailLoader = FakeThumbnailLoader()
 
-    private val controller =
+    private fun createController(isFirstStart: Boolean = true) =
         MediaProjectionAppSelectorController(
             taskListProvider,
             view,
@@ -50,6 +54,8 @@
             appSelectorComponentName,
             callerPackageName,
             thumbnailLoader,
+            isFirstStart,
+            logger
         )
 
     @Before
@@ -61,7 +67,7 @@
     fun initNoRecentTasks_bindsEmptyList() {
         taskListProvider.tasks = emptyList()
 
-        controller.init()
+        createController().init()
 
         verify(view).bind(emptyList())
     }
@@ -70,7 +76,7 @@
     fun initOneRecentTask_bindsList() {
         taskListProvider.tasks = listOf(createRecentTask(taskId = 1))
 
-        controller.init()
+        createController().init()
 
         verify(view).bind(listOf(createRecentTask(taskId = 1)))
     }
@@ -86,7 +92,7 @@
             )
         taskListProvider.tasks = tasks
 
-        controller.init()
+        createController().init()
 
         assertThat(thumbnailLoader.capturedTaskIds).containsExactly(2, 3)
     }
@@ -101,7 +107,7 @@
             )
         taskListProvider.tasks = tasks
 
-        controller.init()
+        createController().init()
 
         verify(view)
             .bind(
@@ -124,7 +130,7 @@
             )
         taskListProvider.tasks = tasks
 
-        controller.init()
+        createController().init()
 
         verify(view)
             .bind(
@@ -147,7 +153,7 @@
             )
         taskListProvider.tasks = tasks
 
-        controller.init()
+        createController().init()
 
         verify(view)
             .bind(
@@ -172,7 +178,7 @@
             )
         taskListProvider.tasks = tasks
 
-        controller.init()
+        createController().init()
 
         verify(view)
             .bind(
@@ -199,11 +205,29 @@
         taskListProvider.tasks = tasks
 
         givenCaptureAllowed(isAllow = false)
-        controller.init()
+        createController().init()
 
         verify(view).bind(emptyList())
     }
 
+    @Test
+    fun init_firstStart_logsAppSelectorDisplayed() {
+        val controller = createController(isFirstStart = true)
+
+        controller.init()
+
+        verify(logger).notifyPermissionProgress(STATE_APP_SELECTOR_DISPLAYED)
+    }
+
+    @Test
+    fun init_notFirstStart_doesNotLogAppSelectorDisplayed() {
+        val controller = createController(isFirstStart = false)
+
+        controller.init()
+
+        verify(logger, never()).notifyPermissionProgress(STATE_APP_SELECTOR_DISPLAYED)
+    }
+
     private fun givenCaptureAllowed(isAllow: Boolean) {
         whenever(policyResolver.isScreenCaptureAllowed(any(), any())).thenReturn(isAllow)
     }
@@ -216,6 +240,7 @@
     ): RecentTask {
         return RecentTask(
             taskId = taskId,
+            displayId = 0,
             topActivityComponent = topActivityComponent,
             baseIntentComponent = ComponentName("com", "Test"),
             userId = userId,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/data/ShellRecentTaskListProviderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/data/ShellRecentTaskListProviderTest.kt
index 2c7ee56..d75553f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/data/ShellRecentTaskListProviderTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/mediaprojection/appselector/data/ShellRecentTaskListProviderTest.kt
@@ -128,6 +128,7 @@
     private fun createRecentTask(taskId: Int): RecentTask =
         RecentTask(
             taskId = taskId,
+            displayId = 0,
             userId = 0,
             topActivityComponent = null,
             baseIntentComponent = null,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/screenrecord/RecordingControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/screenrecord/RecordingControllerTest.java
index 6e6833d..90d2e78 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/screenrecord/RecordingControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/screenrecord/RecordingControllerTest.java
@@ -17,8 +17,10 @@
 package com.android.systemui.screenrecord;
 
 import static com.google.common.truth.Truth.assertThat;
+
 import static junit.framework.Assert.assertFalse;
 import static junit.framework.Assert.assertTrue;
+
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.verify;
@@ -37,6 +39,8 @@
 import com.android.systemui.broadcast.BroadcastDispatcher;
 import com.android.systemui.flags.FakeFeatureFlags;
 import com.android.systemui.flags.Flags;
+import com.android.systemui.mediaprojection.MediaProjectionMetricsLogger;
+import com.android.systemui.mediaprojection.SessionCreationSource;
 import com.android.systemui.mediaprojection.devicepolicy.ScreenCaptureDevicePolicyResolver;
 import com.android.systemui.mediaprojection.devicepolicy.ScreenCaptureDisabledDialog;
 import com.android.systemui.plugins.ActivityStarter;
@@ -76,6 +80,8 @@
     private ActivityStarter mActivityStarter;
     @Mock
     private UserTracker mUserTracker;
+    @Mock
+    private MediaProjectionMetricsLogger mMediaProjectionMetricsLogger;
 
     private FakeFeatureFlags mFeatureFlags;
     private RecordingController mController;
@@ -86,8 +92,15 @@
     public void setUp() {
         MockitoAnnotations.initMocks(this);
         mFeatureFlags = new FakeFeatureFlags();
-        mController = new RecordingController(mMainExecutor, mBroadcastDispatcher, mContext,
-                mFeatureFlags, mUserContextProvider, () -> mDevicePolicyResolver, mUserTracker);
+        mController = new RecordingController(
+                mMainExecutor,
+                mBroadcastDispatcher,
+                mContext,
+                mFeatureFlags,
+                mUserContextProvider,
+                () -> mDevicePolicyResolver,
+                mUserTracker,
+                mMediaProjectionMetricsLogger);
         mController.addCallback(mCallback);
     }
 
@@ -269,4 +282,21 @@
 
         assertThat(dialog).isInstanceOf(ScreenRecordPermissionDialog.class);
     }
+
+    @Test
+    public void testPoliciesFlagEnabled_screenCapturingAllowed_logsProjectionInitiated() {
+        if (Looper.myLooper() == null) {
+            Looper.prepare();
+        }
+
+        mFeatureFlags.set(Flags.WM_ENABLE_PARTIAL_SCREEN_SHARING, true);
+        mFeatureFlags.set(Flags.WM_ENABLE_PARTIAL_SCREEN_SHARING_ENTERPRISE_POLICIES, true);
+        when(mDevicePolicyResolver.isScreenCaptureCompletelyDisabled((any()))).thenReturn(false);
+
+        mController.createScreenRecordDialog(mContext, mFeatureFlags,
+                mDialogLaunchAnimator, mActivityStarter, /* onStartRecordingClicked= */ null);
+
+        verify(mMediaProjectionMetricsLogger)
+                .notifyProjectionInitiated(SessionCreationSource.SYSTEM_UI_SCREEN_RECORDER);
+    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/MediaArtworkProcessorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/MediaArtworkProcessorTest.kt
deleted file mode 100644
index e4da53a..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/MediaArtworkProcessorTest.kt
+++ /dev/null
@@ -1,109 +0,0 @@
-/*
- * Copyright (C) 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package com.android.systemui.statusbar
-
-import com.google.common.truth.Truth.assertThat
-
-import android.graphics.Bitmap
-import android.graphics.Canvas
-import android.graphics.Color
-import android.graphics.Point
-import android.testing.AndroidTestingRunner
-import androidx.test.filters.SmallTest
-import com.android.systemui.SysuiTestCase
-import org.junit.After
-import org.junit.Before
-import org.junit.Test
-import org.junit.runner.RunWith
-
-private const val WIDTH = 200
-private const val HEIGHT = 200
-
-@RunWith(AndroidTestingRunner::class)
-@SmallTest
-class MediaArtworkProcessorTest : SysuiTestCase() {
-
-    private var screenWidth = 0
-    private var screenHeight = 0
-
-    private lateinit var processor: MediaArtworkProcessor
-
-    @Before
-    fun setUp() {
-        processor = MediaArtworkProcessor()
-
-        val point = Point()
-        checkNotNull(context.display).getSize(point)
-        screenWidth = point.x
-        screenHeight = point.y
-    }
-
-    @After
-    fun tearDown() {
-        processor.clearCache()
-    }
-
-    @Test
-    fun testProcessArtwork() {
-        // GIVEN some "artwork", which is just a solid blue image
-        val artwork = Bitmap.createBitmap(WIDTH, HEIGHT, Bitmap.Config.ARGB_8888)
-        Canvas(artwork).drawColor(Color.BLUE)
-        // WHEN the background is created from the artwork
-        val background = processor.processArtwork(context, artwork)!!
-        // THEN the background has the size of the screen that has been downsamples
-        assertThat(background.height).isLessThan(screenHeight)
-        assertThat(background.width).isLessThan(screenWidth)
-        assertThat(background.config).isEqualTo(Bitmap.Config.ARGB_8888)
-    }
-
-    @Test
-    fun testCache() {
-        // GIVEN a solid blue image
-        val artwork = Bitmap.createBitmap(WIDTH, HEIGHT, Bitmap.Config.ARGB_8888)
-        Canvas(artwork).drawColor(Color.BLUE)
-        // WHEN the background is processed twice
-        val background1 = processor.processArtwork(context, artwork)!!
-        val background2 = processor.processArtwork(context, artwork)!!
-        // THEN the two bitmaps are the same
-        // Note: This is currently broken and trying to use caching causes issues
-        assertThat(background1).isNotSameInstanceAs(background2)
-    }
-
-    @Test
-    fun testConfig() {
-        // GIVEN some which is not ARGB_8888
-        val artwork = Bitmap.createBitmap(WIDTH, HEIGHT, Bitmap.Config.ALPHA_8)
-        Canvas(artwork).drawColor(Color.BLUE)
-        // WHEN the background is created from the artwork
-        val background = processor.processArtwork(context, artwork)!!
-        // THEN the background has Config ARGB_8888
-        assertThat(background.config).isEqualTo(Bitmap.Config.ARGB_8888)
-    }
-
-    @Test
-    fun testRecycledArtwork() {
-        // GIVEN some "artwork", which is just a solid blue image
-        val artwork = Bitmap.createBitmap(WIDTH, HEIGHT, Bitmap.Config.ARGB_8888)
-        Canvas(artwork).drawColor(Color.BLUE)
-        // AND the artwork is recycled
-        artwork.recycle()
-        // WHEN the background is created from the artwork
-        val background = processor.processArtwork(context, artwork)
-        // THEN the processed bitmap is null
-        assertThat(background).isNull()
-    }
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationMediaManagerTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationMediaManagerTest.kt
index 9d6ea85..cfcf425 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationMediaManagerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/NotificationMediaManagerTest.kt
@@ -48,9 +48,7 @@
     @Before
     fun setUp() {
         MockitoAnnotations.initMocks(this)
-        doCallRealMethod()
-            .whenever(notificationMediaManager)
-            .updateMediaMetaData(anyBoolean(), anyBoolean())
+        doCallRealMethod().whenever(notificationMediaManager).updateMediaMetaData(anyBoolean())
         doReturn(mockBackDropView).whenever(notificationMediaManager).backDropView
     }
 
@@ -62,7 +60,7 @@
         notificationMediaManager.mIsLockscreenLiveWallpaperEnabled = true
         for (metaDataChanged in listOf(true, false)) {
             for (allowEnterAnimation in listOf(true, false)) {
-                notificationMediaManager.updateMediaMetaData(metaDataChanged, allowEnterAnimation)
+                notificationMediaManager.updateMediaMetaData(metaDataChanged)
                 verify(notificationMediaManager, never()).mediaMetadata
             }
         }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/MediaNotificationProcessorTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/MediaNotificationProcessorTest.java
deleted file mode 100644
index aeb5b03..0000000
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/MediaNotificationProcessorTest.java
+++ /dev/null
@@ -1,87 +0,0 @@
-/*
- * Copyright (C) 2017 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License
- */
-
-package com.android.systemui.statusbar.notification;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import android.annotation.Nullable;
-import android.graphics.Bitmap;
-import android.graphics.Canvas;
-import android.graphics.Color;
-import android.test.suitebuilder.annotation.SmallTest;
-
-import androidx.palette.graphics.Palette;
-import androidx.test.runner.AndroidJUnit4;
-
-import com.android.systemui.SysuiTestCase;
-
-import org.junit.After;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-@SmallTest
-@RunWith(AndroidJUnit4.class)
-public class MediaNotificationProcessorTest extends SysuiTestCase {
-
-    private static final int BITMAP_WIDTH = 10;
-    private static final int BITMAP_HEIGHT = 10;
-
-    /**
-     * Color tolerance is borrowed from the AndroidX test utilities for Palette.
-     */
-    private static final int COLOR_TOLERANCE = 8;
-
-    @Nullable private Bitmap mArtwork;
-
-    @After
-    public void tearDown() {
-        if (mArtwork != null) {
-            mArtwork.recycle();
-            mArtwork = null;
-        }
-    }
-
-    @Test
-    public void findBackgroundSwatch_white() {
-        // Given artwork that is completely white.
-        mArtwork = Bitmap.createBitmap(BITMAP_WIDTH, BITMAP_HEIGHT, Bitmap.Config.ARGB_8888);
-        Canvas canvas = new Canvas(mArtwork);
-        canvas.drawColor(Color.WHITE);
-        // WHEN the background swatch is computed
-        Palette.Swatch swatch = MediaNotificationProcessor.findBackgroundSwatch(mArtwork);
-        // THEN the swatch color is white
-        assertCloseColors(swatch.getRgb(), Color.WHITE);
-    }
-
-    @Test
-    public void findBackgroundSwatch_red() {
-        // Given artwork that is completely red.
-        mArtwork = Bitmap.createBitmap(BITMAP_WIDTH, BITMAP_HEIGHT, Bitmap.Config.ARGB_8888);
-        Canvas canvas = new Canvas(mArtwork);
-        canvas.drawColor(Color.RED);
-        // WHEN the background swatch is computed
-        Palette.Swatch swatch = MediaNotificationProcessor.findBackgroundSwatch(mArtwork);
-        // THEN the swatch color is red
-        assertCloseColors(swatch.getRgb(), Color.RED);
-    }
-
-    static void assertCloseColors(int expected, int actual) {
-        assertThat((float) Color.red(expected)).isWithin(COLOR_TOLERANCE).of(Color.red(actual));
-        assertThat((float) Color.green(expected)).isWithin(COLOR_TOLERANCE).of(Color.green(actual));
-        assertThat((float) Color.blue(expected)).isWithin(COLOR_TOLERANCE).of(Color.blue(actual));
-    }
-}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/BiometricsUnlockControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/BiometricsUnlockControllerTest.java
index 700de53..8344cd1 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/BiometricsUnlockControllerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/BiometricsUnlockControllerTest.java
@@ -18,7 +18,9 @@
 
 import static com.android.systemui.flags.Flags.ONE_WAY_HAPTICS_API_MIGRATION;
 import static com.android.systemui.statusbar.phone.BiometricUnlockController.MODE_WAKE_AND_UNLOCK;
+
 import static com.google.common.truth.Truth.assertThat;
+
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyBoolean;
 import static org.mockito.ArgumentMatchers.anyInt;
@@ -38,7 +40,6 @@
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper.RunWithLooper;
 import android.testing.TestableResources;
-import android.view.HapticFeedbackConstants;
 import android.view.ViewRootImpl;
 
 import com.android.internal.logging.MetricsLogger;
@@ -47,6 +48,7 @@
 import com.android.keyguard.logging.BiometricUnlockLogger;
 import com.android.systemui.SysuiTestCase;
 import com.android.systemui.biometrics.AuthController;
+import com.android.systemui.deviceentry.domain.interactor.DeviceEntryHapticsInteractor;
 import com.android.systemui.dump.DumpManager;
 import com.android.systemui.flags.FakeFeatureFlags;
 import com.android.systemui.keyguard.KeyguardViewMediator;
@@ -122,6 +124,8 @@
     private BiometricUnlockLogger mLogger;
     @Mock
     private ViewRootImpl mViewRootImpl;
+    @Mock
+    private DeviceEntryHapticsInteractor mDeviceEntryHapticsInteractor;
     private final FakeSystemClock mSystemClock = new FakeSystemClock();
     private FakeFeatureFlags mFeatureFlags;
     private BiometricUnlockController mBiometricUnlockController;
@@ -158,7 +162,8 @@
                 mAuthController, mStatusBarStateController,
                 mSessionTracker, mLatencyTracker, mScreenOffAnimationController, mVibratorHelper,
                 mSystemClock,
-                mFeatureFlags
+                mFeatureFlags,
+                mDeviceEntryHapticsInteractor
         );
         biometricUnlockController.setKeyguardViewController(mStatusBarKeyguardViewManager);
         biometricUnlockController.addListener(mBiometricUnlockEventsListener);
@@ -462,145 +467,23 @@
     }
 
     @Test
-    public void onSideFingerprintSuccess_recentPowerButtonPress_noHaptic() {
-        // GIVEN side fingerprint enrolled, last wake reason was power button
-        when(mAuthController.isSfpsEnrolled(anyInt())).thenReturn(true);
-        when(mWakefulnessLifecycle.getLastWakeReason())
-                .thenReturn(PowerManager.WAKE_REASON_POWER_BUTTON);
-
-        // GIVEN last wake time just occurred
-        when(mWakefulnessLifecycle.getLastWakeTime()).thenReturn(mSystemClock.uptimeMillis());
-
+    public void onFingerprintSuccess_requestSuccessHaptic() {
         // WHEN biometric fingerprint succeeds
         givenFingerprintModeUnlockCollapsing();
         mBiometricUnlockController.startWakeAndUnlock(BiometricSourceType.FINGERPRINT,
                 true);
 
-        // THEN DO NOT vibrate the device
-        verify(mVibratorHelper, never()).vibrateAuthSuccess(anyString());
+        // THEN always vibrate the device
+        verify(mDeviceEntryHapticsInteractor).vibrateSuccess();
     }
 
     @Test
-    public void onSideFingerprintSuccess_oldPowerButtonPress_playHaptic() {
-        // GIVEN side fingerprint enrolled, last wake reason was power button
-        when(mAuthController.isSfpsEnrolled(anyInt())).thenReturn(true);
-        when(mWakefulnessLifecycle.getLastWakeReason())
-                .thenReturn(PowerManager.WAKE_REASON_POWER_BUTTON);
-
-        // GIVEN last wake time was 500ms ago
-        when(mWakefulnessLifecycle.getLastWakeTime()).thenReturn(mSystemClock.uptimeMillis());
-        mSystemClock.advanceTime(500);
-
-        // WHEN biometric fingerprint succeeds
-        givenFingerprintModeUnlockCollapsing();
-        mBiometricUnlockController.startWakeAndUnlock(BiometricSourceType.FINGERPRINT,
-                true);
-
-        // THEN vibrate the device
-        verify(mVibratorHelper).vibrateAuthSuccess(anyString());
-    }
-
-    @Test
-    public void onSideFingerprintSuccess_oldPowerButtonPress_playOneWayHaptic() {
-        // GIVEN oneway haptics is enabled
-        mFeatureFlags.set(ONE_WAY_HAPTICS_API_MIGRATION, true);
-        // GIVEN side fingerprint enrolled, last wake reason was power button
-        when(mAuthController.isSfpsEnrolled(anyInt())).thenReturn(true);
-        when(mWakefulnessLifecycle.getLastWakeReason())
-                .thenReturn(PowerManager.WAKE_REASON_POWER_BUTTON);
-
-        // GIVEN last wake time was 500ms ago
-        when(mWakefulnessLifecycle.getLastWakeTime()).thenReturn(mSystemClock.uptimeMillis());
-        mSystemClock.advanceTime(500);
-
-        // WHEN biometric fingerprint succeeds
-        givenFingerprintModeUnlockCollapsing();
-        mBiometricUnlockController.startWakeAndUnlock(BiometricSourceType.FINGERPRINT,
-                true);
-
-        // THEN vibrate the device
-        verify(mVibratorHelper).performHapticFeedback(
-                any(),
-                eq(HapticFeedbackConstants.CONFIRM)
-        );
-    }
-
-    @Test
-    public void onSideFingerprintSuccess_recentGestureWakeUp_playHaptic() {
-        // GIVEN side fingerprint enrolled, wakeup just happened
-        when(mAuthController.isSfpsEnrolled(anyInt())).thenReturn(true);
-        when(mWakefulnessLifecycle.getLastWakeTime()).thenReturn(mSystemClock.uptimeMillis());
-
-        // GIVEN last wake reason was from a gesture
-        when(mWakefulnessLifecycle.getLastWakeReason())
-                .thenReturn(PowerManager.WAKE_REASON_GESTURE);
-
-        // WHEN biometric fingerprint succeeds
-        givenFingerprintModeUnlockCollapsing();
-        mBiometricUnlockController.startWakeAndUnlock(BiometricSourceType.FINGERPRINT,
-                true);
-
-        // THEN vibrate the device
-        verify(mVibratorHelper).vibrateAuthSuccess(anyString());
-    }
-
-    @Test
-    public void onSideFingerprintSuccess_recentGestureWakeUp_playOnewayHaptic() {
-        //GIVEN oneway haptics is enabled
-        mFeatureFlags.set(ONE_WAY_HAPTICS_API_MIGRATION, true);
-        // GIVEN side fingerprint enrolled, wakeup just happened
-        when(mAuthController.isSfpsEnrolled(anyInt())).thenReturn(true);
-        when(mWakefulnessLifecycle.getLastWakeTime()).thenReturn(mSystemClock.uptimeMillis());
-
-        // GIVEN last wake reason was from a gesture
-        when(mWakefulnessLifecycle.getLastWakeReason())
-                .thenReturn(PowerManager.WAKE_REASON_GESTURE);
-
-        // WHEN biometric fingerprint succeeds
-        givenFingerprintModeUnlockCollapsing();
-        mBiometricUnlockController.startWakeAndUnlock(BiometricSourceType.FINGERPRINT,
-                true);
-
-        // THEN vibrate the device
-        verify(mVibratorHelper).performHapticFeedback(
-                any(),
-                eq(HapticFeedbackConstants.CONFIRM)
-        );
-    }
-
-    @Test
-    public void onSideFingerprintFail_alwaysPlaysHaptic() {
-        // GIVEN side fingerprint enrolled, last wake reason was recent power button
-        when(mAuthController.isSfpsEnrolled(anyInt())).thenReturn(true);
-        when(mWakefulnessLifecycle.getLastWakeReason())
-                .thenReturn(PowerManager.WAKE_REASON_POWER_BUTTON);
-        when(mWakefulnessLifecycle.getLastWakeTime()).thenReturn(mSystemClock.uptimeMillis());
-
+    public void onFingerprintFail_requestErrorHaptic() {
         // WHEN biometric fingerprint fails
         mBiometricUnlockController.onBiometricAuthFailed(BiometricSourceType.FINGERPRINT);
 
         // THEN always vibrate the device
-        verify(mVibratorHelper).vibrateAuthError(anyString());
-    }
-
-    @Test
-    public void onSideFingerprintFail_alwaysPlaysOneWayHaptic() {
-        // GIVEN oneway haptics is enabled
-        mFeatureFlags.set(ONE_WAY_HAPTICS_API_MIGRATION, true);
-        // GIVEN side fingerprint enrolled, last wake reason was recent power button
-        when(mAuthController.isSfpsEnrolled(anyInt())).thenReturn(true);
-        when(mWakefulnessLifecycle.getLastWakeReason())
-                .thenReturn(PowerManager.WAKE_REASON_POWER_BUTTON);
-        when(mWakefulnessLifecycle.getLastWakeTime()).thenReturn(mSystemClock.uptimeMillis());
-
-        // WHEN biometric fingerprint fails
-        mBiometricUnlockController.onBiometricAuthFailed(BiometricSourceType.FINGERPRINT);
-
-        // THEN always vibrate the device
-        verify(mVibratorHelper).performHapticFeedback(
-                any(),
-                eq(HapticFeedbackConstants.REJECT)
-        );
+        verify(mDeviceEntryHapticsInteractor).vibrateError();
     }
 
     @Test
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryViaTrackerLibTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryViaTrackerLibTest.kt
index c2f5665..3126362 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryViaTrackerLibTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/pipeline/wifi/data/repository/prod/WifiRepositoryViaTrackerLibTest.kt
@@ -201,11 +201,11 @@
         testScope.runTest {
             val latest by collectLastValue(underTest.isWifiDefault)
 
-            val wifiEntry =
+            val mergedEntry =
                 mock<MergedCarrierEntry>().apply {
                     whenever(this.isDefaultNetwork).thenReturn(true)
                 }
-            whenever(wifiPickerTracker.connectedWifiEntry).thenReturn(wifiEntry)
+            whenever(wifiPickerTracker.mergedCarrierEntry).thenReturn(mergedEntry)
             getCallback().onWifiEntriesChanged()
 
             assertThat(latest).isTrue()
@@ -229,11 +229,11 @@
         testScope.runTest {
             val latest by collectLastValue(underTest.isWifiDefault)
 
-            val wifiEntry =
+            val mergedEntry =
                 mock<MergedCarrierEntry>().apply {
                     whenever(this.isDefaultNetwork).thenReturn(false)
                 }
-            whenever(wifiPickerTracker.connectedWifiEntry).thenReturn(wifiEntry)
+            whenever(wifiPickerTracker.mergedCarrierEntry).thenReturn(mergedEntry)
             getCallback().onWifiEntriesChanged()
 
             assertThat(latest).isFalse()
@@ -526,13 +526,14 @@
         testScope.runTest {
             val latest by collectLastValue(underTest.wifiNetwork)
 
-            val wifiEntry =
+            val mergedEntry =
                 mock<MergedCarrierEntry>().apply {
                     whenever(this.isPrimaryNetwork).thenReturn(true)
                     whenever(this.level).thenReturn(3)
                     whenever(this.subscriptionId).thenReturn(567)
+                    whenever(this.isDefaultNetwork).thenReturn(true)
                 }
-            whenever(wifiPickerTracker.connectedWifiEntry).thenReturn(wifiEntry)
+            whenever(wifiPickerTracker.mergedCarrierEntry).thenReturn(mergedEntry)
             getCallback().onWifiEntriesChanged()
 
             assertThat(latest is WifiNetworkModel.CarrierMerged).isTrue()
@@ -546,11 +547,12 @@
         testScope.runTest {
             val latest by collectLastValue(underTest.wifiNetwork)
 
-            val wifiEntry =
+            val mergedEntry =
                 mock<MergedCarrierEntry>().apply {
                     whenever(this.isPrimaryNetwork).thenReturn(true)
+                    whenever(this.isDefaultNetwork).thenReturn(true)
                 }
-            whenever(wifiPickerTracker.connectedWifiEntry).thenReturn(wifiEntry)
+            whenever(wifiPickerTracker.mergedCarrierEntry).thenReturn(mergedEntry)
             whenever(wifiManager.maxSignalLevel).thenReturn(5)
 
             getCallback().onWifiEntriesChanged()
@@ -566,12 +568,13 @@
         testScope.runTest {
             val latest by collectLastValue(underTest.wifiNetwork)
 
-            val wifiEntry =
+            val mergedEntry =
                 mock<MergedCarrierEntry>().apply {
                     whenever(this.isPrimaryNetwork).thenReturn(true)
                     whenever(this.subscriptionId).thenReturn(INVALID_SUBSCRIPTION_ID)
+                    whenever(this.isDefaultNetwork).thenReturn(true)
                 }
-            whenever(wifiPickerTracker.connectedWifiEntry).thenReturn(wifiEntry)
+            whenever(wifiPickerTracker.mergedCarrierEntry).thenReturn(mergedEntry)
 
             getCallback().onWifiEntriesChanged()
 
@@ -628,11 +631,12 @@
         testScope.runTest {
             val latest by collectLastValue(underTest.wifiNetwork)
 
-            val wifiEntry =
+            val mergedEntry =
                 mock<MergedCarrierEntry>().apply {
                     whenever(this.isPrimaryNetwork).thenReturn(false)
                 }
-            whenever(wifiPickerTracker.connectedWifiEntry).thenReturn(wifiEntry)
+            whenever(wifiPickerTracker.mergedCarrierEntry).thenReturn(mergedEntry)
+            whenever(wifiPickerTracker.connectedWifiEntry).thenReturn(null)
             getCallback().onWifiEntriesChanged()
 
             assertThat(latest).isEqualTo(WifiNetworkModel.Inactive)
@@ -717,12 +721,14 @@
         testScope.runTest {
             val latest by collectLastValue(underTest.wifiNetwork)
 
-            val wifiEntry =
+            val mergedEntry =
                 mock<MergedCarrierEntry>().apply {
                     whenever(this.isPrimaryNetwork).thenReturn(true)
                     whenever(this.level).thenReturn(3)
+                    whenever(this.isDefaultNetwork).thenReturn(true)
                 }
-            whenever(wifiPickerTracker.connectedWifiEntry).thenReturn(wifiEntry)
+            whenever(wifiPickerTracker.connectedWifiEntry).thenReturn(null)
+            whenever(wifiPickerTracker.mergedCarrierEntry).thenReturn(mergedEntry)
             getCallback().onWifiEntriesChanged()
 
             assertThat(latest is WifiNetworkModel.CarrierMerged).isTrue()
@@ -730,6 +736,7 @@
 
             // WHEN we lose our current network
             whenever(wifiPickerTracker.connectedWifiEntry).thenReturn(null)
+            whenever(wifiPickerTracker.mergedCarrierEntry).thenReturn(null)
             getCallback().onWifiEntriesChanged()
 
             // THEN we update to no network
@@ -767,6 +774,56 @@
         }
 
     @Test
+    fun wifiNetwork_carrierMerged_default_usesCarrierMergedInfo() =
+        testScope.runTest {
+            val latest by collectLastValue(underTest.wifiNetwork)
+
+            val mergedEntry =
+                mock<MergedCarrierEntry>().apply {
+                    whenever(this.isPrimaryNetwork).thenReturn(true)
+                    whenever(this.level).thenReturn(3)
+                    whenever(this.isDefaultNetwork).thenReturn(true)
+                }
+            val wifiEntry =
+                mock<WifiEntry>().apply {
+                    whenever(this.isPrimaryNetwork).thenReturn(true)
+                    whenever(this.level).thenReturn(1)
+                    whenever(this.title).thenReturn(TITLE)
+                }
+            whenever(wifiPickerTracker.mergedCarrierEntry).thenReturn(mergedEntry)
+            whenever(wifiPickerTracker.connectedWifiEntry).thenReturn(wifiEntry)
+
+            getCallback().onWifiEntriesChanged()
+
+            assertThat(latest is WifiNetworkModel.CarrierMerged).isTrue()
+        }
+
+    @Test
+    fun wifiNetwork_carrierMerged_notDefault_usesConnectedInfo() =
+        testScope.runTest {
+            val latest by collectLastValue(underTest.wifiNetwork)
+
+            val mergedEntry =
+                mock<MergedCarrierEntry>().apply {
+                    whenever(this.isPrimaryNetwork).thenReturn(true)
+                    whenever(this.level).thenReturn(3)
+                    whenever(this.isDefaultNetwork).thenReturn(false)
+                }
+            val wifiEntry =
+                mock<WifiEntry>().apply {
+                    whenever(this.isPrimaryNetwork).thenReturn(true)
+                    whenever(this.level).thenReturn(1)
+                    whenever(this.title).thenReturn(TITLE)
+                }
+            whenever(wifiPickerTracker.mergedCarrierEntry).thenReturn(mergedEntry)
+            whenever(wifiPickerTracker.connectedWifiEntry).thenReturn(wifiEntry)
+
+            getCallback().onWifiEntriesChanged()
+
+            assertThat(latest is WifiNetworkModel.Active).isTrue()
+        }
+
+    @Test
     fun secondaryNetworks_activeEntriesEmpty_isEmpty() =
         testScope.runTest {
             featureFlags.set(Flags.WIFI_SECONDARY_NETWORKS, true)
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/util/sensors/FakeSensorManager.java b/packages/SystemUI/tests/utils/src/com/android/systemui/util/sensors/FakeSensorManager.java
index 197873f..288dcfe 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/util/sensors/FakeSensorManager.java
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/util/sensors/FakeSensorManager.java
@@ -186,7 +186,7 @@
     }
 
     @Override
-    protected boolean initDataInjectionImpl(boolean enable) {
+    protected boolean initDataInjectionImpl(boolean enable, @DataInjectionMode int mode) {
         return false;
     }
 
diff --git a/services/contentcapture/java/com/android/server/contentcapture/ContentCaptureManagerService.java b/services/contentcapture/java/com/android/server/contentcapture/ContentCaptureManagerService.java
index ee41a69..65975e4 100644
--- a/services/contentcapture/java/com/android/server/contentcapture/ContentCaptureManagerService.java
+++ b/services/contentcapture/java/com/android/server/contentcapture/ContentCaptureManagerService.java
@@ -1437,6 +1437,10 @@
                 if (!mDevCfgEnableContentProtectionReceiver) {
                     return false;
                 }
+                if (mDevCfgContentProtectionRequiredGroups.isEmpty()
+                        && mDevCfgContentProtectionOptionalGroups.isEmpty()) {
+                    return false;
+                }
             }
             return mContentProtectionConsentManager.isConsentGranted(userId)
                     && mContentProtectionBlocklistManager.isAllowed(packageName);
diff --git a/services/core/Android.bp b/services/core/Android.bp
index e5225f6..1d02e4c 100644
--- a/services/core/Android.bp
+++ b/services/core/Android.bp
@@ -191,7 +191,7 @@
         "ImmutabilityAnnotation",
         "securebox",
         "apache-commons-math",
-        "power_optimization_flags_lib",
+        "backstage_power_flags_lib",
         "notification_flags_lib",
         "camera_platform_flags_core_java_lib",
         "biometrics_flags_lib",
diff --git a/services/core/java/android/content/pm/PackageManagerInternal.java b/services/core/java/android/content/pm/PackageManagerInternal.java
index 8df5456..638abdb 100644
--- a/services/core/java/android/content/pm/PackageManagerInternal.java
+++ b/services/core/java/android/content/pm/PackageManagerInternal.java
@@ -1387,11 +1387,12 @@
             @UserIdInt int userId);
 
     /**
-     * Tells PackageManager when a component (except BroadcastReceivers) of the package is used
+     * Tells PackageManager when a component of the package is used
      * and the package should get out of stopped state and be enabled.
      */
     public abstract void notifyComponentUsed(@NonNull String packageName,
-            @UserIdInt int userId, @NonNull String recentCallingPackage, @NonNull String debugInfo);
+            @UserIdInt int userId, @Nullable String recentCallingPackage,
+            @NonNull String debugInfo);
 
     /** @deprecated For legacy shell command only. */
     @Deprecated
diff --git a/services/core/java/com/android/server/am/BroadcastQueueImpl.java b/services/core/java/com/android/server/am/BroadcastQueueImpl.java
index 127c5b3..3c56752 100644
--- a/services/core/java/com/android/server/am/BroadcastQueueImpl.java
+++ b/services/core/java/com/android/server/am/BroadcastQueueImpl.java
@@ -1440,10 +1440,9 @@
                     r.curComponent.getPackageName(), r.userId, Event.APP_COMPONENT_USED);
         }
 
-        // Broadcast is being executed, its package can't be stopped.
         try {
-            mService.mPackageManagerInt.setPackageStoppedState(
-                    r.curComponent.getPackageName(), false, r.userId);
+            mService.mPackageManagerInt.notifyComponentUsed(
+                    r.curComponent.getPackageName(), r.userId, r.callerPackage, r.toString());
         } catch (IllegalArgumentException e) {
             Slog.w(TAG, "Failed trying to unstop package "
                     + r.curComponent.getPackageName() + ": " + e);
diff --git a/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java b/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java
index d19eae5..b481697 100644
--- a/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java
+++ b/services/core/java/com/android/server/am/BroadcastQueueModernImpl.java
@@ -1982,8 +1982,8 @@
         mService.notifyPackageUse(receiverPackageName,
                 PackageManager.NOTIFY_PACKAGE_USE_BROADCAST_RECEIVER);
 
-        mService.mPackageManagerInt.setPackageStoppedState(
-                receiverPackageName, false, r.userId);
+        mService.mPackageManagerInt.notifyComponentUsed(
+                receiverPackageName, r.userId, r.callerPackage, r.toString());
     }
 
     private void reportUsageStatsBroadcastDispatched(@NonNull ProcessRecord app,
diff --git a/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java b/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java
index 16e3fdf2..2d231b3 100644
--- a/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java
+++ b/services/core/java/com/android/server/am/SettingsToPropertiesMapper.java
@@ -152,6 +152,7 @@
         "preload_safety",
         "responsible_apis",
         "rust",
+        "safety_center",
         "system_performance",
         "test_suites",
         "text",
@@ -159,7 +160,14 @@
         "tv_system_ui",
         "vibrator",
         "virtual_devices",
+        "wear_calling_messaging",
+        "wear_connectivity",
+        "wear_esim_carriers",
         "wear_frameworks",
+        "wear_health_services",
+        "wear_media",
+        "wear_offload",
+        "wear_security",
         "wear_system_health",
         "wear_systems",
         "window_surfaces",
diff --git a/services/core/java/com/android/server/am/flags.aconfig b/services/core/java/com/android/server/am/flags.aconfig
index 26d99d8..bb9ea28 100644
--- a/services/core/java/com/android/server/am/flags.aconfig
+++ b/services/core/java/com/android/server/am/flags.aconfig
@@ -2,7 +2,7 @@
 
 flag {
     name: "oomadjuster_correctness_rewrite"
-    namespace: "android_platform_power_optimization"
+    namespace: "backstage_power"
     description: "Utilize new OomAdjuster implementation"
     bug: "298055811"
     is_fixed_read_only: true
diff --git a/services/core/java/com/android/server/audio/MusicFxHelper.java b/services/core/java/com/android/server/audio/MusicFxHelper.java
index 6c0fef5..5f4e4c3 100644
--- a/services/core/java/com/android/server/audio/MusicFxHelper.java
+++ b/services/core/java/com/android/server/audio/MusicFxHelper.java
@@ -157,7 +157,8 @@
             Log.w(TAG, " inside handle MSG_EFFECT_CLIENT_GONE");
             // Once the uid is no longer running, close all remain audio session(s) for this UID
             if (mClientUidSessionMap.get(Integer.valueOf(uid)) != null) {
-                final List<Integer> sessions = mClientUidSessionMap.get(Integer.valueOf(uid));
+                final List<Integer> sessions =
+                        new ArrayList(mClientUidSessionMap.get(Integer.valueOf(uid)));
                 Log.i(TAG, "UID " + uid + " gone, closing " + sessions.size() + " sessions");
                 for (Integer session : sessions) {
                     Intent intent = new Intent(
diff --git a/services/core/java/com/android/server/display/DisplayManagerService.java b/services/core/java/com/android/server/display/DisplayManagerService.java
index 35a4f47..5b87eea 100644
--- a/services/core/java/com/android/server/display/DisplayManagerService.java
+++ b/services/core/java/com/android/server/display/DisplayManagerService.java
@@ -758,7 +758,8 @@
 
         mContext.registerReceiver(mIdleModeReceiver, filter);
 
-        mSmallAreaDetectionController = SmallAreaDetectionController.create(mContext);
+        mSmallAreaDetectionController = (mFlags.isSmallAreaDetectionEnabled())
+                ? SmallAreaDetectionController.create(mContext) : null;
     }
 
     @VisibleForTesting
diff --git a/services/core/java/com/android/server/display/SmallAreaDetectionController.java b/services/core/java/com/android/server/display/SmallAreaDetectionController.java
index adaa539..bf384b0 100644
--- a/services/core/java/com/android/server/display/SmallAreaDetectionController.java
+++ b/services/core/java/com/android/server/display/SmallAreaDetectionController.java
@@ -20,6 +20,7 @@
 import android.annotation.Nullable;
 import android.content.Context;
 import android.content.pm.PackageManagerInternal;
+import android.os.UserHandle;
 import android.provider.DeviceConfig;
 import android.provider.DeviceConfigInterface;
 import android.util.ArrayMap;
@@ -30,15 +31,14 @@
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.os.BackgroundThread;
 import com.android.server.LocalServices;
-import com.android.server.pm.UserManagerInternal;
+import com.android.server.pm.pkg.PackageStateInternal;
 
 import java.io.PrintWriter;
-import java.util.Arrays;
 import java.util.Map;
 
 final class SmallAreaDetectionController {
-    private static native void nativeUpdateSmallAreaDetection(int[] uids, float[] thresholds);
-    private static native void nativeSetSmallAreaDetectionThreshold(int uid, float threshold);
+    private static native void nativeUpdateSmallAreaDetection(int[] appIds, float[] thresholds);
+    private static native void nativeSetSmallAreaDetectionThreshold(int appId, float threshold);
 
     // TODO(b/281720315): Move this to DeviceConfig once server side ready.
     private static final String KEY_SMALL_AREA_DETECTION_ALLOWLIST =
@@ -47,12 +47,8 @@
     private final Object mLock = new Object();
     private final Context mContext;
     private final PackageManagerInternal mPackageManager;
-    private final UserManagerInternal mUserManager;
     @GuardedBy("mLock")
     private final Map<String, Float> mAllowPkgMap = new ArrayMap<>();
-    // TODO(b/298722189): Update allowlist when user changes
-    @GuardedBy("mLock")
-    private int[] mUserIds;
 
     static SmallAreaDetectionController create(@NonNull Context context) {
         final SmallAreaDetectionController controller =
@@ -67,7 +63,6 @@
     SmallAreaDetectionController(Context context, DeviceConfigInterface deviceConfig) {
         mContext = context;
         mPackageManager = LocalServices.getService(PackageManagerInternal.class);
-        mUserManager = LocalServices.getService(UserManagerInternal.class);
         deviceConfig.addOnPropertiesChangedListener(DeviceConfig.NAMESPACE_DISPLAY_MANAGER,
                 BackgroundThread.getExecutor(),
                 new SmallAreaDetectionController.OnPropertiesChangedListener());
@@ -76,6 +71,7 @@
 
     @VisibleForTesting
     void updateAllowlist(@Nullable String property) {
+        final Map<String, Float> allowPkgMap = new ArrayMap<>();
         synchronized (mLock) {
             mAllowPkgMap.clear();
             if (property != null) {
@@ -86,8 +82,11 @@
                         .getStringArray(R.array.config_smallAreaDetectionAllowlist);
                 for (String defaultMapString : defaultMapStrings) putToAllowlist(defaultMapString);
             }
-            updateSmallAreaDetection();
+
+            if (mAllowPkgMap.isEmpty()) return;
+            allowPkgMap.putAll(mAllowPkgMap);
         }
+        updateSmallAreaDetection(allowPkgMap);
     }
 
     @GuardedBy("mLock")
@@ -105,43 +104,32 @@
         }
     }
 
-    @GuardedBy("mLock")
-    private void updateUidListForAllUsers(SparseArray<Float> list, String pkg, float threshold) {
-        for (int i = 0; i < mUserIds.length; i++) {
-            final int userId = mUserIds[i];
-            final int uid = mPackageManager.getPackageUid(pkg, 0, userId);
-            if (uid > 0) list.put(uid, threshold);
-        }
-    }
-
-    @GuardedBy("mLock")
-    private void updateSmallAreaDetection() {
-        if (mAllowPkgMap.isEmpty()) return;
-
-        mUserIds = mUserManager.getUserIds();
-
-        final SparseArray<Float> uidThresholdList = new SparseArray<>();
-        for (String pkg : mAllowPkgMap.keySet()) {
-            final float threshold = mAllowPkgMap.get(pkg);
-            updateUidListForAllUsers(uidThresholdList, pkg, threshold);
+    private void updateSmallAreaDetection(Map<String, Float> allowPkgMap) {
+        final SparseArray<Float> appIdThresholdList = new SparseArray(allowPkgMap.size());
+        for (String pkg : allowPkgMap.keySet()) {
+            final float threshold = allowPkgMap.get(pkg);
+            final PackageStateInternal stage = mPackageManager.getPackageStateInternal(pkg);
+            if (stage != null) {
+                appIdThresholdList.put(stage.getAppId(), threshold);
+            }
         }
 
-        final int[] uids = new int[uidThresholdList.size()];
-        final float[] thresholds = new float[uidThresholdList.size()];
-        for (int i = 0; i < uidThresholdList.size();  i++) {
-            uids[i] = uidThresholdList.keyAt(i);
-            thresholds[i] = uidThresholdList.valueAt(i);
+        final int[] appIds = new int[appIdThresholdList.size()];
+        final float[] thresholds = new float[appIdThresholdList.size()];
+        for (int i = 0; i < appIdThresholdList.size();  i++) {
+            appIds[i] = appIdThresholdList.keyAt(i);
+            thresholds[i] = appIdThresholdList.valueAt(i);
         }
-        updateSmallAreaDetection(uids, thresholds);
+        updateSmallAreaDetection(appIds, thresholds);
     }
 
     @VisibleForTesting
-    void updateSmallAreaDetection(int[] uids, float[] thresholds) {
-        nativeUpdateSmallAreaDetection(uids, thresholds);
+    void updateSmallAreaDetection(int[] appIds, float[] thresholds) {
+        nativeUpdateSmallAreaDetection(appIds, thresholds);
     }
 
-    void setSmallAreaDetectionThreshold(int uid, float threshold) {
-        nativeSetSmallAreaDetectionThreshold(uid, threshold);
+    void setSmallAreaDetectionThreshold(int appId, float threshold) {
+        nativeSetSmallAreaDetectionThreshold(appId, threshold);
     }
 
     void dump(PrintWriter pw) {
@@ -151,7 +139,6 @@
             for (String pkg : mAllowPkgMap.keySet()) {
                 pw.println("    " + pkg + " threshold = " + mAllowPkgMap.get(pkg));
             }
-            pw.println("  mUserIds=" + Arrays.toString(mUserIds));
         }
     }
 
@@ -167,11 +154,15 @@
     private final class PackageReceiver implements PackageManagerInternal.PackageListObserver {
         @Override
         public void onPackageAdded(@NonNull String packageName, int uid) {
+            float threshold = 0.0f;
             synchronized (mLock) {
                 if (mAllowPkgMap.containsKey(packageName)) {
-                    setSmallAreaDetectionThreshold(uid, mAllowPkgMap.get(packageName));
+                    threshold = mAllowPkgMap.get(packageName);
                 }
             }
+            if (threshold > 0.0f) {
+                setSmallAreaDetectionThreshold(UserHandle.getAppId(uid), threshold);
+            }
         }
     }
 }
diff --git a/services/core/java/com/android/server/display/feature/DisplayManagerFlags.java b/services/core/java/com/android/server/display/feature/DisplayManagerFlags.java
index fae8383..d953e8e 100644
--- a/services/core/java/com/android/server/display/feature/DisplayManagerFlags.java
+++ b/services/core/java/com/android/server/display/feature/DisplayManagerFlags.java
@@ -71,6 +71,10 @@
             Flags.FLAG_ENABLE_POWER_THROTTLING_CLAMPER,
             Flags::enablePowerThrottlingClamper);
 
+    private final FlagState mSmallAreaDetectionFlagState = new FlagState(
+            Flags.FLAG_ENABLE_SMALL_AREA_DETECTION,
+            Flags::enableSmallAreaDetection);
+
     /** Returns whether connected display management is enabled or not. */
     public boolean isConnectedDisplayManagementEnabled() {
         return mConnectedDisplayManagementFlagState.isEnabled();
@@ -147,6 +151,10 @@
         return mBackUpSmoothDisplayAndForcePeakRefreshRateFlagState.isEnabled();
     }
 
+    public boolean isSmallAreaDetectionEnabled() {
+        return mSmallAreaDetectionFlagState.isEnabled();
+    }
+
     private static class FlagState {
 
         private final String mName;
diff --git a/services/core/java/com/android/server/display/feature/display_flags.aconfig b/services/core/java/com/android/server/display/feature/display_flags.aconfig
index 9ab9c9d..9141814 100644
--- a/services/core/java/com/android/server/display/feature/display_flags.aconfig
+++ b/services/core/java/com/android/server/display/feature/display_flags.aconfig
@@ -104,3 +104,12 @@
     bug: "211737588"
     is_fixed_read_only: true
 }
+
+flag {
+    name: "enable_small_area_detection"
+    namespace: "display_manager"
+    description: "Feature flag for SmallAreaDetection"
+    bug: "298722189"
+    is_fixed_read_only: true
+}
+
diff --git a/services/core/java/com/android/server/display/mode/DisplayModeDirector.java b/services/core/java/com/android/server/display/mode/DisplayModeDirector.java
index ca23844..d023913 100644
--- a/services/core/java/com/android/server/display/mode/DisplayModeDirector.java
+++ b/services/core/java/com/android/server/display/mode/DisplayModeDirector.java
@@ -723,7 +723,9 @@
             if (mode.getPhysicalWidth() > maxAllowedWidth
                     || mode.getPhysicalHeight() > maxAllowedHeight
                     || mode.getPhysicalWidth() < outSummary.minWidth
-                    || mode.getPhysicalHeight() < outSummary.minHeight) {
+                    || mode.getPhysicalHeight() < outSummary.minHeight
+                    || mode.getRefreshRate() < outSummary.minPhysicalRefreshRate
+                    || mode.getRefreshRate() > outSummary.maxPhysicalRefreshRate) {
                 continue;
             }
 
diff --git a/services/core/java/com/android/server/media/projection/MediaProjectionSessionIdGenerator.java b/services/core/java/com/android/server/media/projection/MediaProjectionSessionIdGenerator.java
new file mode 100644
index 0000000..ff70cb3
--- /dev/null
+++ b/services/core/java/com/android/server/media/projection/MediaProjectionSessionIdGenerator.java
@@ -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.server.media.projection;
+
+import android.content.Context;
+import android.content.SharedPreferences;
+import android.os.Environment;
+
+import com.android.internal.annotations.GuardedBy;
+import com.android.internal.annotations.VisibleForTesting;
+
+import java.io.File;
+
+public class MediaProjectionSessionIdGenerator {
+
+    private static final String PREFERENCES_FILE_NAME = "media_projection_session_id";
+    private static final String SESSION_ID_PREF_KEY = "media_projection_session_id_key";
+    private static final int SESSION_ID_DEFAULT_VALUE = 0;
+
+    private static final Object sInstanceLock = new Object();
+
+    @GuardedBy("sInstanceLock")
+    private static MediaProjectionSessionIdGenerator sInstance;
+
+    private final Object mSessionIdLock = new Object();
+
+    @GuardedBy("mSessionIdLock")
+    private final SharedPreferences mSharedPreferences;
+
+    /** Creates or returns an existing instance of {@link MediaProjectionSessionIdGenerator}. */
+    public static MediaProjectionSessionIdGenerator getInstance(Context context) {
+        synchronized (sInstanceLock) {
+            if (sInstance == null) {
+                File preferencesFile =
+                        new File(Environment.getDataSystemDirectory(), PREFERENCES_FILE_NAME);
+                SharedPreferences preferences =
+                        context.getSharedPreferences(preferencesFile, Context.MODE_PRIVATE);
+                sInstance = new MediaProjectionSessionIdGenerator(preferences);
+            }
+            return sInstance;
+        }
+    }
+
+    @VisibleForTesting
+    public MediaProjectionSessionIdGenerator(SharedPreferences sharedPreferences) {
+        this.mSharedPreferences = sharedPreferences;
+    }
+
+    /** Returns the current session ID. This value is persisted across reboots. */
+    public int getCurrentSessionId() {
+        synchronized (mSessionIdLock) {
+            return getCurrentSessionIdInternal();
+        }
+    }
+
+    /**
+     * Creates and returns a new session ID. This value will be persisted as the new current session
+     * ID, and will be persisted across reboots.
+     */
+    public int createAndGetNewSessionId() {
+        synchronized (mSessionIdLock) {
+            int newSessionId = getCurrentSessionId() + 1;
+            setSessionIdInternal(newSessionId);
+            return newSessionId;
+        }
+    }
+
+    @GuardedBy("mSessionIdLock")
+    private void setSessionIdInternal(int value) {
+        mSharedPreferences.edit().putInt(SESSION_ID_PREF_KEY, value).apply();
+    }
+
+    @GuardedBy("mSessionIdLock")
+    private int getCurrentSessionIdInternal() {
+        return mSharedPreferences.getInt(SESSION_ID_PREF_KEY, SESSION_ID_DEFAULT_VALUE);
+    }
+}
diff --git a/services/core/java/com/android/server/pm/PackageArchiver.java b/services/core/java/com/android/server/pm/PackageArchiver.java
index f59188e..0fb1f7a 100644
--- a/services/core/java/com/android/server/pm/PackageArchiver.java
+++ b/services/core/java/com/android/server/pm/PackageArchiver.java
@@ -196,8 +196,12 @@
             for (int i = 0, size = mainActivities.length; i < size; ++i) {
                 var mainActivity = mainActivities[i];
                 Path iconPath = storeIconForParcel(packageName, mainActivity, userId, i);
-                ArchiveActivityInfo activityInfo = new ArchiveActivityInfo(
-                        mainActivity.title, iconPath, null);
+                ArchiveActivityInfo activityInfo =
+                        new ArchiveActivityInfo(
+                                mainActivity.title,
+                                mainActivity.originalComponentName,
+                                iconPath,
+                                null);
                 archiveActivityInfos.add(activityInfo);
             }
 
@@ -215,8 +219,12 @@
         for (int i = 0, size = mainActivities.size(); i < size; i++) {
             LauncherActivityInfo mainActivity = mainActivities.get(i);
             Path iconPath = storeIcon(packageName, mainActivity, userId, i);
-            ArchiveActivityInfo activityInfo = new ArchiveActivityInfo(
-                    mainActivity.getLabel().toString(), iconPath, null);
+            ArchiveActivityInfo activityInfo =
+                    new ArchiveActivityInfo(
+                            mainActivity.getLabel().toString(),
+                            mainActivity.getComponentName(),
+                            iconPath,
+                            null);
             archiveActivityInfos.add(activityInfo);
         }
 
@@ -593,6 +601,7 @@
             }
             var archivedActivity = new ArchivedActivityParcel();
             archivedActivity.title = info.getTitle();
+            archivedActivity.originalComponentName = info.getOriginalComponentName();
             archivedActivity.iconBitmap = bytesFromBitmapFile(info.getIconBitmap());
             archivedActivity.monochromeIconBitmap = bytesFromBitmapFile(
                     info.getMonochromeIconBitmap());
@@ -624,6 +633,7 @@
             }
             var archivedActivity = new ArchivedActivityParcel();
             archivedActivity.title = info.getLabel().toString();
+            archivedActivity.originalComponentName = info.getComponentName();
             archivedActivity.iconBitmap =
                     info.getActivityInfo().getIconResource() == 0 ? null : bytesFromBitmap(
                             drawableToBitmap(info.getIcon(/* density= */ 0)));
diff --git a/services/core/java/com/android/server/pm/PackageManagerInternalBase.java b/services/core/java/com/android/server/pm/PackageManagerInternalBase.java
index 651845e..e749968 100644
--- a/services/core/java/com/android/server/pm/PackageManagerInternalBase.java
+++ b/services/core/java/com/android/server/pm/PackageManagerInternalBase.java
@@ -747,7 +747,7 @@
 
     @Override
     public void notifyComponentUsed(@NonNull String packageName, @UserIdInt int userId,
-            @NonNull String recentCallingPackage, @NonNull String debugInfo) {
+            @Nullable String recentCallingPackage, @NonNull String debugInfo) {
         mService.notifyComponentUsed(snapshot(), packageName, userId,
                 recentCallingPackage, debugInfo);
     }
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index abeabc9..839b699 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -36,6 +36,7 @@
 import static android.os.storage.StorageManager.FLAG_STORAGE_DE;
 import static android.os.storage.StorageManager.FLAG_STORAGE_EXTERNAL;
 import static android.provider.DeviceConfig.NAMESPACE_PACKAGE_MANAGER_SERVICE;
+import static android.util.FeatureFlagUtils.SETTINGS_TREAT_PAUSE_AS_QUARANTINE;
 
 import static com.android.internal.annotations.VisibleForTesting.Visibility;
 import static com.android.internal.util.FrameworkStatsLog.BOOT_TIME_EVENT_DURATION__EVENT__OTA_PACKAGE_MANAGER_INIT_TIME;
@@ -164,6 +165,7 @@
 import android.util.DisplayMetrics;
 import android.util.EventLog;
 import android.util.ExceptionUtils;
+import android.util.FeatureFlagUtils;
 import android.util.Log;
 import android.util.Pair;
 import android.util.Slog;
@@ -4576,7 +4578,7 @@
     }
 
     void notifyComponentUsed(@NonNull Computer snapshot, @NonNull String packageName,
-            @UserIdInt int userId, @NonNull String recentCallingPackage,
+            @UserIdInt int userId, @Nullable String recentCallingPackage,
             @NonNull String debugInfo) {
         synchronized (mLock) {
             final PackageSetting pkgSetting = mSettings.getPackageLPr(packageName);
@@ -6133,8 +6135,16 @@
             final Computer snapshot = snapshotComputer();
             enforceCanSetPackagesSuspendedAsUser(snapshot, callingPackage, callingUid, userId,
                     "setPackagesSuspendedAsUser");
-            boolean quarantined = ((flags & PackageManager.FLAG_SUSPEND_QUARANTINED) != 0)
-                    && Flags.quarantinedEnabled();
+            boolean quarantined = false;
+            if (Flags.quarantinedEnabled()) {
+                if ((flags & PackageManager.FLAG_SUSPEND_QUARANTINED) != 0) {
+                    quarantined = true;
+                } else if (FeatureFlagUtils.isEnabled(mContext,
+                        SETTINGS_TREAT_PAUSE_AS_QUARANTINE)) {
+                    final String wellbeingPkg = mContext.getString(R.string.config_systemWellbeing);
+                    quarantined = callingPackage.equals(wellbeingPkg);
+                }
+            }
             return mSuspendPackageHelper.setPackagesSuspended(snapshot, packageNames, suspended,
                     appExtras, launcherExtras, dialogInfo, callingPackage, userId, callingUid,
                     false /* forQuietMode */, quarantined);
diff --git a/services/core/java/com/android/server/pm/PackageSetting.java b/services/core/java/com/android/server/pm/PackageSetting.java
index 9f4e86d..3ca933a 100644
--- a/services/core/java/com/android/server/pm/PackageSetting.java
+++ b/services/core/java/com/android/server/pm/PackageSetting.java
@@ -1228,6 +1228,9 @@
             long activityInfoToken = proto.start(
                     PackageProto.UserInfoProto.ArchiveState.ACTIVITY_INFOS);
             proto.write(ArchiveActivityInfo.TITLE, activityInfo.getTitle());
+            proto.write(
+                    ArchiveActivityInfo.ORIGINAL_COMPONENT_NAME,
+                    activityInfo.getOriginalComponentName().flattenToString());
             if (activityInfo.getIconBitmap() != null) {
                 proto.write(ArchiveActivityInfo.ICON_BITMAP_PATH,
                         activityInfo.getIconBitmap().toAbsolutePath().toString());
diff --git a/services/core/java/com/android/server/pm/Settings.java b/services/core/java/com/android/server/pm/Settings.java
index 397a841..e726d91 100644
--- a/services/core/java/com/android/server/pm/Settings.java
+++ b/services/core/java/com/android/server/pm/Settings.java
@@ -368,6 +368,7 @@
     private static final String ATTR_VALUE = "value";
     private static final String ATTR_FIRST_INSTALL_TIME = "first-install-time";
     private static final String ATTR_ARCHIVE_ACTIVITY_TITLE = "activity-title";
+    private static final String ATTR_ARCHIVE_ORIGINAL_COMPONENT_NAME = "original-component-name";
     private static final String ATTR_ARCHIVE_INSTALLER_TITLE = "installer-title";
     private static final String ATTR_ARCHIVE_ICON_PATH = "icon-path";
     private static final String ATTR_ARCHIVE_MONOCHROME_ICON_PATH = "monochrome-icon-path";
@@ -2079,6 +2080,8 @@
             if (tagName.equals(TAG_ARCHIVE_ACTIVITY_INFO)) {
                 String title = parser.getAttributeValue(null,
                         ATTR_ARCHIVE_ACTIVITY_TITLE);
+                String originalComponentName =
+                        parser.getAttributeValue(null, ATTR_ARCHIVE_ORIGINAL_COMPONENT_NAME);
                 String iconAttribute = parser.getAttributeValue(null,
                         ATTR_ARCHIVE_ICON_PATH);
                 Path iconPath = iconAttribute == null ? null : Path.of(iconAttribute);
@@ -2087,17 +2090,27 @@
                 Path monochromeIconPath = monochromeAttribute == null ? null : Path.of(
                         monochromeAttribute);
 
-                if (title == null || iconPath == null) {
-                    Slog.wtf(TAG,
-                            TextUtils.formatSimple("Missing attributes in tag %s. %s: %s, %s: %s",
-                                    TAG_ARCHIVE_ACTIVITY_INFO, ATTR_ARCHIVE_ACTIVITY_TITLE, title,
+                if (title == null || originalComponentName == null || iconPath == null) {
+                    Slog.wtf(
+                            TAG,
+                            TextUtils.formatSimple(
+                                    "Missing attributes in tag %s. %s: %s, %s: %s, %s: %s",
+                                    TAG_ARCHIVE_ACTIVITY_INFO,
+                                    ATTR_ARCHIVE_ACTIVITY_TITLE,
+                                    title,
+                                    ATTR_ARCHIVE_ORIGINAL_COMPONENT_NAME,
+                                    originalComponentName,
                                     ATTR_ARCHIVE_ICON_PATH,
                                     iconPath));
                     continue;
                 }
 
                 activityInfos.add(
-                        new ArchiveState.ArchiveActivityInfo(title, iconPath, monochromeIconPath));
+                        new ArchiveState.ArchiveActivityInfo(
+                                title,
+                                ComponentName.unflattenFromString(originalComponentName),
+                                iconPath,
+                                monochromeIconPath));
             }
         }
         return activityInfos;
@@ -2469,6 +2482,10 @@
         for (ArchiveState.ArchiveActivityInfo activityInfo : archiveState.getActivityInfos()) {
             serializer.startTag(null, TAG_ARCHIVE_ACTIVITY_INFO);
             serializer.attribute(null, ATTR_ARCHIVE_ACTIVITY_TITLE, activityInfo.getTitle());
+            serializer.attribute(
+                    null,
+                    ATTR_ARCHIVE_ORIGINAL_COMPONENT_NAME,
+                    activityInfo.getOriginalComponentName().flattenToString());
             if (activityInfo.getIconBitmap() != null) {
                 serializer.attribute(null, ATTR_ARCHIVE_ICON_PATH,
                         activityInfo.getIconBitmap().toAbsolutePath().toString());
diff --git a/services/core/java/com/android/server/pm/pkg/ArchiveState.java b/services/core/java/com/android/server/pm/pkg/ArchiveState.java
index 4916a4a..1e40d44 100644
--- a/services/core/java/com/android/server/pm/pkg/ArchiveState.java
+++ b/services/core/java/com/android/server/pm/pkg/ArchiveState.java
@@ -16,9 +16,11 @@
 
 package com.android.server.pm.pkg;
 
+import android.content.ComponentName;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 
+import com.android.internal.util.AnnotationValidations;
 import com.android.internal.util.DataClass;
 
 import java.nio.file.Path;
@@ -56,6 +58,10 @@
         @NonNull
         private final String mTitle;
 
+        /** The component name of the original activity (pre-archival). */
+        @NonNull
+        private final ComponentName mOriginalComponentName;
+
         /**
          * The path to the stored icon of the activity in the app's locale. Null if the app does
          * not define any icon (default icon would be shown on the launcher).
@@ -96,11 +102,13 @@
         @DataClass.Generated.Member
         public ArchiveActivityInfo(
                 @NonNull String title,
+                @NonNull ComponentName originalComponentName,
                 @Nullable Path iconBitmap,
                 @Nullable Path monochromeIconBitmap) {
             this.mTitle = title;
-            com.android.internal.util.AnnotationValidations.validate(
-                    NonNull.class, null, mTitle);
+            this.mOriginalComponentName = originalComponentName;
+            AnnotationValidations.validate(NonNull.class, null, mTitle);
+            AnnotationValidations.validate(NonNull.class, null, mOriginalComponentName);
             this.mIconBitmap = iconBitmap;
             this.mMonochromeIconBitmap = monochromeIconBitmap;
 
@@ -116,6 +124,14 @@
         }
 
         /**
+         * The component name of the original activity (pre-archival).
+            */
+        @DataClass.Generated.Member
+        public @NonNull ComponentName getOriginalComponentName() {
+            return mOriginalComponentName;
+        }
+
+        /**
          * The path to the stored icon of the activity in the app's locale. Null if the app does
          * not define any icon (default icon would be shown on the launcher).
          */
@@ -140,6 +156,7 @@
 
             return "ArchiveActivityInfo { " +
                     "title = " + mTitle + ", " +
+                    "originalComponentName = " + mOriginalComponentName + ", " +
                     "iconBitmap = " + mIconBitmap + ", " +
                     "monochromeIconBitmap = " + mMonochromeIconBitmap +
             " }";
@@ -159,6 +176,7 @@
             //noinspection PointlessBooleanExpression
             return true
                     && java.util.Objects.equals(mTitle, that.mTitle)
+                    && java.util.Objects.equals(mOriginalComponentName, that.mOriginalComponentName)
                     && java.util.Objects.equals(mIconBitmap, that.mIconBitmap)
                     && java.util.Objects.equals(mMonochromeIconBitmap, that.mMonochromeIconBitmap);
         }
@@ -171,6 +189,7 @@
 
             int _hash = 1;
             _hash = 31 * _hash + java.util.Objects.hashCode(mTitle);
+            _hash = 31* _hash + java.util.Objects.hashCode(mOriginalComponentName);
             _hash = 31 * _hash + java.util.Objects.hashCode(mIconBitmap);
             _hash = 31 * _hash + java.util.Objects.hashCode(mMonochromeIconBitmap);
             return _hash;
@@ -180,7 +199,8 @@
                 time = 1693590309015L,
                 codegenVersion = "1.0.23",
                 sourceFile = "frameworks/base/services/core/java/com/android/server/pm/pkg/ArchiveState.java",
-                inputSignatures = "private final @android.annotation.NonNull java.lang.String mTitle\nprivate final @android.annotation.Nullable java.nio.file.Path mIconBitmap\nprivate final @android.annotation.Nullable java.nio.file.Path mMonochromeIconBitmap\nclass ArchiveActivityInfo extends java.lang.Object implements []\n@com.android.internal.util.DataClass(genEqualsHashCode=true, genToString=true)")
+                inputSignatures =
+                        "private final @android.annotation.NonNull java.lang.String mTitle\nprivate final @android.annotation.NonNull android.content.ComponentName mOriginalComponentName\nprivate final @android.annotation.Nullable java.nio.file.Path mIconBitmap\nprivate final @android.annotation.Nullable java.nio.file.Path mMonochromeIconBitmap\nclass ArchiveActivityInfo extends java.lang.Object implements []\n@com.android.internal.util.DataClass(genEqualsHashCode=true, genToString=true)")
         @Deprecated
         private void __metadata() {}
 
@@ -224,11 +244,9 @@
             @NonNull List<ArchiveActivityInfo> activityInfos,
             @NonNull String installerTitle) {
         this.mActivityInfos = activityInfos;
-        com.android.internal.util.AnnotationValidations.validate(
-                NonNull.class, null, mActivityInfos);
+        AnnotationValidations.validate(NonNull.class, null, mActivityInfos);
         this.mInstallerTitle = installerTitle;
-        com.android.internal.util.AnnotationValidations.validate(
-                NonNull.class, null, mInstallerTitle);
+        AnnotationValidations.validate(NonNull.class, null, mInstallerTitle);
 
         // onConstructed(); // You can define this method to get a callback
     }
diff --git a/services/core/java/com/android/server/power/Android.bp b/services/core/java/com/android/server/power/Android.bp
index 1da9dd7..607d435 100644
--- a/services/core/java/com/android/server/power/Android.bp
+++ b/services/core/java/com/android/server/power/Android.bp
@@ -1,5 +1,5 @@
 aconfig_declarations {
-    name: "power_optimization_flags",
+    name: "backstage_power_flags",
     package: "com.android.server.power.optimization",
     srcs: [
         "stats/*.aconfig",
@@ -7,6 +7,6 @@
 }
 
 java_aconfig_library {
-    name: "power_optimization_flags_lib",
-    aconfig_declarations: "power_optimization_flags",
+    name: "backstage_power_flags_lib",
+    aconfig_declarations: "backstage_power_flags",
 }
diff --git a/services/core/java/com/android/server/power/stats/flags.aconfig b/services/core/java/com/android/server/power/stats/flags.aconfig
index add806f..0f13571 100644
--- a/services/core/java/com/android/server/power/stats/flags.aconfig
+++ b/services/core/java/com/android/server/power/stats/flags.aconfig
@@ -2,14 +2,14 @@
 
 flag {
     name: "power_monitor_api"
-    namespace: "power_optimization"
+    namespace: "backstage_power"
     description: "Feature flag for ODPM API"
     bug: "295027807"
 }
 
 flag {
     name: "streamlined_battery_stats"
-    namespace: "power_optimization"
+    namespace: "backstage_power"
     description: "Feature flag for streamlined battery stats"
     bug: "285646152"
 }
diff --git a/services/core/java/com/android/server/stats/OWNERS b/services/core/java/com/android/server/stats/OWNERS
index 174ad3a..c33f3d9 100644
--- a/services/core/java/com/android/server/stats/OWNERS
+++ b/services/core/java/com/android/server/stats/OWNERS
@@ -1,11 +1,10 @@
 jeffreyhuang@google.com
 joeo@google.com
-jtnguyen@google.com
+monicamwang@google.com
 muhammadq@google.com
+rayhdez@google.com
 rslawik@google.com
-ruchirr@google.com
 sharaienko@google.com
 singhtejinder@google.com
 tsaichristine@google.com
 yaochen@google.com
-yro@google.com
diff --git a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
index 0718f2f..4200fbf 100644
--- a/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
+++ b/services/core/java/com/android/server/wallpaper/WallpaperManagerService.java
@@ -3982,7 +3982,9 @@
         if (wallpaper == null) {
             // common case, this is the first lookup post-boot of the system or
             // unified lock, so we bring up the saved state lazily now and recheck.
-            int whichLoad = (which == FLAG_LOCK) ? FLAG_LOCK : FLAG_SYSTEM;
+            // if we're loading the system wallpaper for the first time, also load the lock
+            // wallpaper to determine if the system wallpaper is system+lock or system only.
+            int whichLoad = (which == FLAG_LOCK) ? FLAG_LOCK : FLAG_SYSTEM | FLAG_LOCK;
             loadSettingsLocked(userId, false, whichLoad);
             wallpaper = whichSet.get(userId);
             if (wallpaper == null) {
diff --git a/services/core/java/com/android/server/wm/BackNavigationController.java b/services/core/java/com/android/server/wm/BackNavigationController.java
index 4237668..6d59b29 100644
--- a/services/core/java/com/android/server/wm/BackNavigationController.java
+++ b/services/core/java/com/android/server/wm/BackNavigationController.java
@@ -276,6 +276,10 @@
                 // activity, we won't close the activity.
                 backType = BackNavigationInfo.TYPE_DIALOG_CLOSE;
                 removedWindowContainer = window;
+            } else if (!currentActivity.occludesParent() || currentActivity.showWallpaper()) {
+                // skip if current activity is translucent
+                backType = BackNavigationInfo.TYPE_CALLBACK;
+                removedWindowContainer = window;
             } else if (prevActivity != null) {
                 if (!isOccluded || prevActivity.canShowWhenLocked()) {
                     // We have another Activity in the same currentTask to go to
diff --git a/services/core/java/com/android/server/wm/Task.java b/services/core/java/com/android/server/wm/Task.java
index 016b0ff..4922e90 100644
--- a/services/core/java/com/android/server/wm/Task.java
+++ b/services/core/java/com/android/server/wm/Task.java
@@ -489,10 +489,6 @@
 
     private boolean mForceShowForAllUsers;
 
-    /** When set, will force the task to report as invisible. */
-    static final int FLAG_FORCE_HIDDEN_FOR_PINNED_TASK = 1;
-    static final int FLAG_FORCE_HIDDEN_FOR_TASK_ORG = 1 << 1;
-    private int mForceHiddenFlags = 0;
     private boolean mForceTranslucent = false;
 
     // The display category name for this task.
@@ -4495,20 +4491,13 @@
      * Sets/unsets the forced-hidden state flag for this task depending on {@param set}.
      * @return Whether the force hidden state changed
      */
-    boolean setForceHidden(int flags, boolean set) {
-        int newFlags = mForceHiddenFlags;
-        if (set) {
-            newFlags |= flags;
-        } else {
-            newFlags &= ~flags;
-        }
-        if (mForceHiddenFlags == newFlags) {
-            return false;
-        }
-
+    @Override
+    boolean setForceHidden(@FlagForceHidden int flags, boolean set) {
         final boolean wasHidden = isForceHidden();
         final boolean wasVisible = isVisible();
-        mForceHiddenFlags = newFlags;
+        if (!super.setForceHidden(flags, set)) {
+            return false;
+        }
         final boolean nowHidden = isForceHidden();
         if (wasHidden != nowHidden) {
             final String reason = "setForceHidden";
@@ -4539,11 +4528,6 @@
         return super.isAlwaysOnTop();
     }
 
-    @Override
-    protected boolean isForceHidden() {
-        return mForceHiddenFlags != 0;
-    }
-
     boolean isForceHiddenForPinnedTask() {
         return (mForceHiddenFlags & FLAG_FORCE_HIDDEN_FOR_PINNED_TASK) != 0;
     }
@@ -5651,6 +5635,8 @@
             if (noAnimation) {
                 mDisplayContent.prepareAppTransition(TRANSIT_NONE);
                 mTaskSupervisor.mNoAnimActivities.add(top);
+                mTransitionController.collect(top);
+                mTransitionController.setNoAnimation(top);
                 ActivityOptions.abort(options);
             } else {
                 updateTransitLocked(TRANSIT_TO_FRONT, options);
diff --git a/services/core/java/com/android/server/wm/TaskFragment.java b/services/core/java/com/android/server/wm/TaskFragment.java
index 7c5bc6e..906b3b5 100644
--- a/services/core/java/com/android/server/wm/TaskFragment.java
+++ b/services/core/java/com/android/server/wm/TaskFragment.java
@@ -362,6 +362,19 @@
      */
     private boolean mIsolatedNav;
 
+    /** When set, will force the task to report as invisible. */
+    static final int FLAG_FORCE_HIDDEN_FOR_PINNED_TASK = 1;
+    static final int FLAG_FORCE_HIDDEN_FOR_TASK_ORG = 1 << 1;
+    static final int FLAG_FORCE_HIDDEN_FOR_TASK_FRAGMENT_ORG = 1 << 2;
+
+    @IntDef(prefix = {"FLAG_FORCE_HIDDEN_"}, value = {
+            FLAG_FORCE_HIDDEN_FOR_PINNED_TASK,
+            FLAG_FORCE_HIDDEN_FOR_TASK_ORG,
+            FLAG_FORCE_HIDDEN_FOR_TASK_FRAGMENT_ORG,
+    }, flag = true)
+    @interface FlagForceHidden {}
+    protected int mForceHiddenFlags = 0;
+
     final Point mLastSurfaceSize = new Point();
 
     private final Rect mTmpBounds = new Rect();
@@ -845,7 +858,25 @@
      * Returns whether this TaskFragment is currently forced to be hidden for any reason.
      */
     protected boolean isForceHidden() {
-        return false;
+        return mForceHiddenFlags != 0;
+    }
+
+    /**
+     * Sets/unsets the forced-hidden state flag for this task depending on {@param set}.
+     * @return Whether the force hidden state changed
+     */
+    boolean setForceHidden(@FlagForceHidden int flags, boolean set) {
+        int newFlags = mForceHiddenFlags;
+        if (set) {
+            newFlags |= flags;
+        } else {
+            newFlags &= ~flags;
+        }
+        if (mForceHiddenFlags == newFlags) {
+            return false;
+        }
+        mForceHiddenFlags = newFlags;
+        return true;
     }
 
     protected boolean isForceTranslucent() {
diff --git a/services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java b/services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java
index 04164c2..ff766be 100644
--- a/services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java
+++ b/services/core/java/com/android/server/wm/TaskFragmentOrganizerController.java
@@ -51,7 +51,6 @@
 import android.window.ITaskFragmentOrganizerController;
 import android.window.TaskFragmentInfo;
 import android.window.TaskFragmentOperation;
-import android.window.TaskFragmentOrganizerToken;
 import android.window.TaskFragmentParentInfo;
 import android.window.TaskFragmentTransaction;
 import android.window.WindowContainerTransaction;
@@ -745,9 +744,9 @@
         }
     }
 
-    boolean isSystemOrganizer(@NonNull TaskFragmentOrganizerToken token) {
+    boolean isSystemOrganizer(@NonNull IBinder organizerToken) {
         final TaskFragmentOrganizerState state =
-                mTaskFragmentOrganizerState.get(token.asBinder());
+                mTaskFragmentOrganizerState.get(organizerToken);
         return state != null && state.mIsSystemOrganizer;
     }
 
diff --git a/services/core/java/com/android/server/wm/Transition.java b/services/core/java/com/android/server/wm/Transition.java
index 93db1ca..7d65c61 100644
--- a/services/core/java/com/android/server/wm/Transition.java
+++ b/services/core/java/com/android/server/wm/Transition.java
@@ -3321,8 +3321,8 @@
             mFrozen.add(wc);
             final ChangeInfo changeInfo = Objects.requireNonNull(mChanges.get(wc));
             changeInfo.mSnapshot = snapshotSurface;
-            if (isDisplayRotation) {
-                // This isn't cheap, so only do it for display rotations.
+            if (changeInfo.mRotation != wc.mDisplayContent.getRotation()) {
+                // This isn't cheap, so only do it for rotation change.
                 changeInfo.mSnapshotLuma = TransitionAnimation.getBorderLuma(
                         buffer, screenshotBuffer.getColorSpace());
             }
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index 9663f3a..88f72f9 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -8461,16 +8461,18 @@
                     return true;
                 }
                 // For a task session, find the activity identified by the launch cookie.
-                final WindowContainerToken wct = getTaskWindowContainerTokenForLaunchCookie(
+                final WindowContainerInfo wci = getTaskWindowContainerInfoForLaunchCookie(
                         incomingSession.getTokenToRecord());
-                if (wct == null) {
+                if (wci == null) {
                     Slog.w(TAG, "Handling a new recording session; unable to find the "
                             + "WindowContainerToken");
                     return false;
                 }
                 // Replace the launch cookie in the session details with the task's
                 // WindowContainerToken.
-                incomingSession.setTokenToRecord(wct.asBinder());
+                incomingSession.setTokenToRecord(wci.getToken().asBinder());
+                // Also replace the UNKNOWN target UID with the actual UID.
+                incomingSession.setTargetUid(wci.getUid());
                 mContentRecordingController.setContentRecordingSessionLocked(incomingSession,
                         WindowManagerService.this);
                 return true;
@@ -8798,21 +8800,41 @@
         mAtmService.setFocusedTask(task.mTaskId, touchedActivity);
     }
 
+    @VisibleForTesting
+    static class WindowContainerInfo {
+        private final int mUid;
+        @NonNull private final WindowContainerToken mToken;
+
+        private WindowContainerInfo(int uid, @NonNull WindowContainerToken token) {
+            this.mUid = uid;
+            this.mToken = token;
+        }
+
+        public int getUid() {
+            return mUid;
+        }
+
+        @NonNull
+        public WindowContainerToken getToken() {
+            return mToken;
+        }
+    }
+
     /**
-     * Retrieve the {@link WindowContainerToken} of the task that contains the activity started
-     * with the given launch cookie.
+     * Retrieve the {@link WindowContainerInfo} of the task that contains the activity started with
+     * the given launch cookie.
      *
      * @param launchCookie the launch cookie set on the {@link ActivityOptions} when starting an
-     *                     activity
+     *     activity
      * @return a token representing the task containing the activity started with the given launch
-     * cookie, or {@code null} if the token couldn't be found.
+     *     cookie, or {@code null} if the token couldn't be found.
      */
     @VisibleForTesting
     @Nullable
-    WindowContainerToken getTaskWindowContainerTokenForLaunchCookie(@NonNull IBinder launchCookie) {
+    WindowContainerInfo getTaskWindowContainerInfoForLaunchCookie(@NonNull IBinder launchCookie) {
         // Find the activity identified by the launch cookie.
-        final ActivityRecord targetActivity = mRoot.getActivity(
-                activity -> activity.mLaunchCookie == launchCookie);
+        final ActivityRecord targetActivity =
+                mRoot.getActivity(activity -> activity.mLaunchCookie == launchCookie);
         if (targetActivity == null) {
             Slog.w(TAG, "Unable to find the activity for this launch cookie");
             return null;
@@ -8827,7 +8849,7 @@
             Slog.w(TAG, "Unable to find the WindowContainerToken for " + targetActivity.getName());
             return null;
         }
-        return taskWindowContainerToken;
+        return new WindowContainerInfo(targetActivity.getUid(), taskWindowContainerToken);
     }
 
     /**
diff --git a/services/core/java/com/android/server/wm/WindowOrganizerController.java b/services/core/java/com/android/server/wm/WindowOrganizerController.java
index dd9a88f..5ed6caf 100644
--- a/services/core/java/com/android/server/wm/WindowOrganizerController.java
+++ b/services/core/java/com/android/server/wm/WindowOrganizerController.java
@@ -24,7 +24,9 @@
 import static android.window.TaskFragmentOperation.OP_TYPE_CLEAR_ADJACENT_TASK_FRAGMENTS;
 import static android.window.TaskFragmentOperation.OP_TYPE_CREATE_TASK_FRAGMENT;
 import static android.window.TaskFragmentOperation.OP_TYPE_DELETE_TASK_FRAGMENT;
+import static android.window.TaskFragmentOperation.OP_TYPE_REORDER_TO_BOTTOM_OF_TASK;
 import static android.window.TaskFragmentOperation.OP_TYPE_REORDER_TO_FRONT;
+import static android.window.TaskFragmentOperation.OP_TYPE_REORDER_TO_TOP_OF_TASK;
 import static android.window.TaskFragmentOperation.OP_TYPE_REPARENT_ACTIVITY_TO_TASK_FRAGMENT;
 import static android.window.TaskFragmentOperation.OP_TYPE_REQUEST_FOCUS_ON_TASK_FRAGMENT;
 import static android.window.TaskFragmentOperation.OP_TYPE_SET_ADJACENT_TASK_FRAGMENTS;
@@ -34,6 +36,8 @@
 import static android.window.TaskFragmentOperation.OP_TYPE_SET_RELATIVE_BOUNDS;
 import static android.window.TaskFragmentOperation.OP_TYPE_START_ACTIVITY_IN_TASK_FRAGMENT;
 import static android.window.TaskFragmentOperation.OP_TYPE_UNKNOWN;
+import static android.window.WindowContainerTransaction.Change.CHANGE_FOCUSABLE;
+import static android.window.WindowContainerTransaction.Change.CHANGE_HIDDEN;
 import static android.window.WindowContainerTransaction.Change.CHANGE_RELATIVE_BOUNDS;
 import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_ADD_INSETS_FRAME_PROVIDER;
 import static android.window.WindowContainerTransaction.HierarchyOp.HIERARCHY_OP_TYPE_ADD_TASK_FRAGMENT_OPERATION;
@@ -61,6 +65,7 @@
 import static com.android.server.wm.Task.FLAG_FORCE_HIDDEN_FOR_PINNED_TASK;
 import static com.android.server.wm.Task.FLAG_FORCE_HIDDEN_FOR_TASK_ORG;
 import static com.android.server.wm.TaskFragment.EMBEDDING_ALLOWED;
+import static com.android.server.wm.TaskFragment.FLAG_FORCE_HIDDEN_FOR_TASK_FRAGMENT_ORG;
 import static com.android.server.wm.WindowContainer.POSITION_BOTTOM;
 import static com.android.server.wm.WindowContainer.POSITION_TOP;
 
@@ -821,6 +826,7 @@
             return TRANSACT_EFFECTS_NONE;
         }
 
+        int effects = TRANSACT_EFFECTS_NONE;
         // When the TaskFragment is resized, we may want to create a change transition for it, for
         // which we want to defer the surface update until we determine whether or not to start
         // change transition.
@@ -843,7 +849,14 @@
             c.getConfiguration().windowConfiguration.setBounds(absBounds);
             taskFragment.setRelativeEmbeddedBounds(relBounds);
         }
-        final int effects = applyChanges(taskFragment, c);
+        if ((c.getChangeMask() & WindowContainerTransaction.Change.CHANGE_HIDDEN) != 0) {
+            if (taskFragment.setForceHidden(
+                    FLAG_FORCE_HIDDEN_FOR_TASK_FRAGMENT_ORG, c.getHidden())) {
+                effects |= TRANSACT_EFFECTS_LIFECYCLE;
+            }
+        }
+        effects |= applyChanges(taskFragment, c);
+
         if (taskFragment.shouldStartChangeTransition(mTmpBounds0, mTmpBounds1)) {
             taskFragment.initializeChangeTransition(mTmpBounds0);
         }
@@ -1393,6 +1406,24 @@
                 taskFragment.setIsolatedNav(isolatedNav);
                 break;
             }
+            case OP_TYPE_REORDER_TO_BOTTOM_OF_TASK: {
+                final Task task = taskFragment.getTask();
+                if (task != null) {
+                    task.mChildren.remove(taskFragment);
+                    task.mChildren.add(0, taskFragment);
+                    effects |= TRANSACT_EFFECTS_LIFECYCLE;
+                }
+                break;
+            }
+            case OP_TYPE_REORDER_TO_TOP_OF_TASK: {
+                final Task task = taskFragment.getTask();
+                if (task != null) {
+                    task.mChildren.remove(taskFragment);
+                    task.mChildren.add(taskFragment);
+                    effects |= TRANSACT_EFFECTS_LIFECYCLE;
+                }
+                break;
+            }
         }
         return effects;
     }
@@ -1420,6 +1451,18 @@
             return false;
         }
 
+        if ((opType == OP_TYPE_REORDER_TO_BOTTOM_OF_TASK
+                || opType == OP_TYPE_REORDER_TO_TOP_OF_TASK)
+                && !mTaskFragmentOrganizerController.isSystemOrganizer(organizer.asBinder())) {
+            final Throwable exception = new SecurityException(
+                    "Only a system organizer can perform OP_TYPE_REORDER_TO_BOTTOM_OF_TASK or "
+                            + "OP_TYPE_REORDER_TO_TOP_OF_TASK."
+            );
+            sendTaskFragmentOperationFailure(organizer, errorCallbackToken, taskFragment,
+                    opType, exception);
+            return false;
+        }
+
         final IBinder secondaryFragmentToken = operation.getSecondaryFragmentToken();
         return secondaryFragmentToken == null
                 || validateTaskFragment(mLaunchTaskFragments.get(secondaryFragmentToken), opType,
@@ -1920,6 +1963,11 @@
      * For config change on {@link TaskFragment}, we only support the following operations:
      * {@link WindowContainerTransaction#setRelativeBounds(WindowContainerToken, Rect)},
      * {@link WindowContainerTransaction#setWindowingMode(WindowContainerToken, int)}.
+     *
+     * For a system organizer, we additionally support
+     * {@link WindowContainerTransaction#setHidden(WindowContainerToken, boolean)}, and
+     * {@link WindowContainerTransaction#setFocusable(WindowContainerToken, boolean)}. See
+     * {@link TaskFragmentOrganizerController#registerOrganizer(ITaskFragmentOrganizer, boolean)}
      */
     private void enforceTaskFragmentConfigChangeAllowed(@NonNull String func,
             @Nullable WindowContainer wc, @NonNull WindowContainerTransaction.Change change,
@@ -1938,31 +1986,49 @@
             throw new SecurityException(msg);
         }
 
-        final int changeMask = change.getChangeMask();
-        final int configSetMask = change.getConfigSetMask();
-        final int windowSetMask = change.getWindowSetMask();
-        if (changeMask == 0 && configSetMask == 0 && windowSetMask == 0
-                && change.getWindowingMode() >= 0) {
-            // The change contains only setWindowingMode, which is allowed.
-            return;
+        final int originalChangeMask = change.getChangeMask();
+        final int originalConfigSetMask = change.getConfigSetMask();
+        final int originalWindowSetMask = change.getWindowSetMask();
+
+        int changeMaskToBeChecked = originalChangeMask;
+        int configSetMaskToBeChecked = originalConfigSetMask;
+        int windowSetMaskToBeChecked = originalWindowSetMask;
+
+        if (mTaskFragmentOrganizerController.isSystemOrganizer(organizer.asBinder())) {
+            // System organizer is allowed to update the hidden and focusable state.
+            // We unset the CHANGE_HIDDEN and CHANGE_FOCUSABLE bits because they are checked here.
+            changeMaskToBeChecked &= ~CHANGE_HIDDEN;
+            changeMaskToBeChecked &= ~CHANGE_FOCUSABLE;
         }
-        if (changeMask != CHANGE_RELATIVE_BOUNDS
-                || configSetMask != ActivityInfo.CONFIG_WINDOW_CONFIGURATION
-                || windowSetMask != WindowConfiguration.WINDOW_CONFIG_BOUNDS) {
-            // None of the change should be requested from a TaskFragment organizer except
-            // setRelativeBounds and setWindowingMode.
-            // For setRelativeBounds, we don't need to check whether it is outside of the Task
+
+        // setRelativeBounds is allowed.
+        if ((changeMaskToBeChecked & CHANGE_RELATIVE_BOUNDS) != 0
+                && (configSetMaskToBeChecked & ActivityInfo.CONFIG_WINDOW_CONFIGURATION) != 0
+                && (windowSetMaskToBeChecked & WindowConfiguration.WINDOW_CONFIG_BOUNDS) != 0) {
+            // For setRelativeBounds, we don't need to check whether it is outside the Task
             // bounds, because it is possible that the Task is also resizing, for which we don't
             // want to throw an exception. The bounds will be adjusted in
             // TaskFragment#translateRelativeBoundsToAbsoluteBounds.
-            String msg = "Permission Denial: " + func + " from pid="
-                    + Binder.getCallingPid() + ", uid=" + Binder.getCallingUid()
-                    + " trying to apply changes of changeMask=" + changeMask
-                    + " configSetMask=" + configSetMask + " windowSetMask=" + windowSetMask
-                    + " to TaskFragment=" + tf + " TaskFragmentOrganizer=" + organizer;
-            Slog.w(TAG, msg);
-            throw new SecurityException(msg);
+            changeMaskToBeChecked &= ~CHANGE_RELATIVE_BOUNDS;
+            configSetMaskToBeChecked &= ~ActivityInfo.CONFIG_WINDOW_CONFIGURATION;
+            windowSetMaskToBeChecked &= ~WindowConfiguration.WINDOW_CONFIG_BOUNDS;
         }
+
+        if (changeMaskToBeChecked == 0 && configSetMaskToBeChecked == 0
+                && windowSetMaskToBeChecked == 0) {
+            // All the changes have been checked.
+            // Note that setWindowingMode is always allowed, so we don't need to check the mask.
+            return;
+        }
+
+        final String msg = "Permission Denial: " + func + " from pid="
+                + Binder.getCallingPid() + ", uid=" + Binder.getCallingUid()
+                + " trying to apply changes of changeMask=" + originalChangeMask
+                + " configSetMask=" + originalConfigSetMask
+                + " windowSetMask=" + originalWindowSetMask
+                + " to TaskFragment=" + tf + " TaskFragmentOrganizer=" + organizer;
+        Slog.w(TAG, msg);
+        throw new SecurityException(msg);
     }
 
     private void createTaskFragment(@NonNull TaskFragmentCreationParams creationParams,
@@ -2019,7 +2085,7 @@
         TaskFragmentOrganizerToken organizerToken = creationParams.getOrganizer();
         taskFragment.setTaskFragmentOrganizer(organizerToken,
                 ownerActivity.getUid(), ownerActivity.info.processName,
-                mTaskFragmentOrganizerController.isSystemOrganizer(organizerToken));
+                mTaskFragmentOrganizerController.isSystemOrganizer(organizerToken.asBinder()));
         final int position;
         if (creationParams.getPairedPrimaryFragmentToken() != null) {
             // When there is a paired primary TaskFragment, we want to place the new TaskFragment
diff --git a/services/core/java/com/android/server/wm/WindowToken.java b/services/core/java/com/android/server/wm/WindowToken.java
index e434f29..7d21dbf 100644
--- a/services/core/java/com/android/server/wm/WindowToken.java
+++ b/services/core/java/com/android/server/wm/WindowToken.java
@@ -569,6 +569,7 @@
                 && asActivityRecord() != null && isVisible()) {
             // Trigger an activity level rotation transition.
             mTransitionController.requestTransitionIfNeeded(WindowManager.TRANSIT_CHANGE, this);
+            mTransitionController.collectVisibleChange(this);
             mTransitionController.setReady(this);
         }
         final int originalRotation = getWindowConfiguration().getRotation();
diff --git a/services/core/jni/Android.bp b/services/core/jni/Android.bp
index 709d5e3..24ee163 100644
--- a/services/core/jni/Android.bp
+++ b/services/core/jni/Android.bp
@@ -189,7 +189,7 @@
         "android.hardware.thermal@1.0",
         "android.hardware.thermal-V1-ndk",
         "android.hardware.tv.input@1.0",
-        "android.hardware.tv.input-V1-ndk",
+        "android.hardware.tv.input-V2-ndk",
         "android.hardware.vibrator-V2-cpp",
         "android.hardware.vibrator@1.0",
         "android.hardware.vibrator@1.1",
@@ -244,5 +244,5 @@
 
 filegroup {
     name: "lib_oomConnection_native",
-    srcs: ["com_android_server_am_OomConnection.cpp",],
+    srcs: ["com_android_server_am_OomConnection.cpp"],
 }
diff --git a/services/core/jni/com_android_server_display_SmallAreaDetectionController.cpp b/services/core/jni/com_android_server_display_SmallAreaDetectionController.cpp
index b256f16..1844d30 100644
--- a/services/core/jni/com_android_server_display_SmallAreaDetectionController.cpp
+++ b/services/core/jni/com_android_server_display_SmallAreaDetectionController.cpp
@@ -24,33 +24,33 @@
 #include "utils/Log.h"
 
 namespace android {
-static void nativeUpdateSmallAreaDetection(JNIEnv* env, jclass clazz, jintArray juids,
+static void nativeUpdateSmallAreaDetection(JNIEnv* env, jclass clazz, jintArray jappIds,
                                            jfloatArray jthresholds) {
-    if (juids == nullptr || jthresholds == nullptr) return;
+    if (jappIds == nullptr || jthresholds == nullptr) return;
 
-    ScopedIntArrayRO uids(env, juids);
+    ScopedIntArrayRO appIds(env, jappIds);
     ScopedFloatArrayRO thresholds(env, jthresholds);
 
-    if (uids.size() != thresholds.size()) {
-        ALOGE("uids size exceeds thresholds size!");
+    if (appIds.size() != thresholds.size()) {
+        ALOGE("appIds size exceeds thresholds size!");
         return;
     }
 
-    std::vector<int32_t> uidVector;
+    std::vector<int32_t> appIdVector;
     std::vector<float> thresholdVector;
-    size_t size = uids.size();
-    uidVector.reserve(size);
+    size_t size = appIds.size();
+    appIdVector.reserve(size);
     thresholdVector.reserve(size);
     for (int i = 0; i < size; i++) {
-        uidVector.push_back(static_cast<int32_t>(uids[i]));
+        appIdVector.push_back(static_cast<int32_t>(appIds[i]));
         thresholdVector.push_back(static_cast<float>(thresholds[i]));
     }
-    SurfaceComposerClient::updateSmallAreaDetection(uidVector, thresholdVector);
+    SurfaceComposerClient::updateSmallAreaDetection(appIdVector, thresholdVector);
 }
 
-static void nativeSetSmallAreaDetectionThreshold(JNIEnv* env, jclass clazz, jint uid,
+static void nativeSetSmallAreaDetectionThreshold(JNIEnv* env, jclass clazz, jint appId,
                                                  jfloat threshold) {
-    SurfaceComposerClient::setSmallAreaDetectionThreshold(uid, threshold);
+    SurfaceComposerClient::setSmallAreaDetectionThreshold(appId, threshold);
 }
 
 static const JNINativeMethod gMethods[] = {
diff --git a/services/core/jni/tvinput/JTvInputHal.cpp b/services/core/jni/tvinput/JTvInputHal.cpp
index c736617..dc05462 100644
--- a/services/core/jni/tvinput/JTvInputHal.cpp
+++ b/services/core/jni/tvinput/JTvInputHal.cpp
@@ -368,12 +368,20 @@
 }
 
 JTvInputHal::TvMessageEventWrapper JTvInputHal::TvMessageEventWrapper::createEventWrapper(
-        const AidlTvMessageEvent& aidlTvMessageEvent) {
+        const AidlTvMessageEvent& aidlTvMessageEvent, bool isLegacyMessage) {
+    auto messageList = aidlTvMessageEvent.messages;
     TvMessageEventWrapper event;
-    event.messages.insert(event.messages.begin(), std::begin(aidlTvMessageEvent.messages) + 1,
-                          std::end(aidlTvMessageEvent.messages));
+    // Handle backwards compatibility for V1
+    if (isLegacyMessage) {
+        event.deviceId = messageList[0].groupId;
+        event.messages.insert(event.messages.begin(), std::begin(messageList) + 1,
+                              std::end(messageList));
+    } else {
+        event.deviceId = aidlTvMessageEvent.deviceId;
+        event.messages.insert(event.messages.begin(), std::begin(messageList),
+                              std::end(messageList));
+    }
     event.streamId = aidlTvMessageEvent.streamId;
-    event.deviceId = aidlTvMessageEvent.messages[0].groupId;
     event.type = aidlTvMessageEvent.type;
     return event;
 }
@@ -449,15 +457,30 @@
 ::ndk::ScopedAStatus JTvInputHal::TvInputCallback::notifyTvMessageEvent(
         const AidlTvMessageEvent& event) {
     const std::string DEVICE_ID_SUBTYPE = "device_id";
-    if (event.messages.size() > 1 && event.messages[0].subType == DEVICE_ID_SUBTYPE) {
-        mHal->mLooper
-                ->sendMessage(new NotifyTvMessageHandler(mHal,
-                                                         TvMessageEventWrapper::createEventWrapper(
-                                                                 event)),
-                              static_cast<int>(event.type));
+    ::ndk::ScopedAStatus status = ::ndk::ScopedAStatus::ok();
+    int32_t aidlVersion = 0;
+    if (mHal->mTvInput->getAidlInterfaceVersion(&aidlVersion).isOk() && event.messages.size() > 0) {
+        bool validLegacyMessage = aidlVersion == 1 &&
+                event.messages[0].subType == DEVICE_ID_SUBTYPE && event.messages.size() > 1;
+        bool validTvMessage = aidlVersion > 1 && event.messages.size() > 0;
+        if (validLegacyMessage || validTvMessage) {
+            mHal->mLooper->sendMessage(
+                    new NotifyTvMessageHandler(mHal,
+                                               TvMessageEventWrapper::
+                                                       createEventWrapper(event,
+                                                                          validLegacyMessage)),
+                    static_cast<int>(event.type));
+        } else {
+            status = ::ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_ARGUMENT);
+            ALOGE("The TVMessage event was malformed for HAL version: %d", aidlVersion);
+        }
+    } else {
+        status = ::ndk::ScopedAStatus::fromExceptionCode(EX_ILLEGAL_ARGUMENT);
+        ALOGE("The TVMessage event was empty or the HAL version (version: %d) could not "
+              "be inferred.",
+              aidlVersion);
     }
-
-    return ::ndk::ScopedAStatus::ok();
+    return status;
 }
 
 JTvInputHal::ITvInputWrapper::ITvInputWrapper(std::shared_ptr<AidlITvInput>& aidlTvInput)
@@ -521,4 +544,12 @@
     }
 }
 
+::ndk::ScopedAStatus JTvInputHal::ITvInputWrapper::getAidlInterfaceVersion(int32_t* _aidl_return) {
+    if (mIsHidl) {
+        return ::ndk::ScopedAStatus::fromExceptionCode(EX_UNSUPPORTED_OPERATION);
+    } else {
+        return mAidlTvInput->getInterfaceVersion(_aidl_return);
+    }
+}
+
 } // namespace android
diff --git a/services/core/jni/tvinput/JTvInputHal.h b/services/core/jni/tvinput/JTvInputHal.h
index 1d8d162..6026a10 100644
--- a/services/core/jni/tvinput/JTvInputHal.h
+++ b/services/core/jni/tvinput/JTvInputHal.h
@@ -138,7 +138,7 @@
         TvMessageEventWrapper() {}
 
         static TvMessageEventWrapper createEventWrapper(
-                const AidlTvMessageEvent& aidlTvMessageEvent);
+                const AidlTvMessageEvent& aidlTvMessageEvent, bool isLegacyMessage);
 
         int streamId;
         int deviceId;
@@ -195,6 +195,7 @@
         ::ndk::ScopedAStatus getTvMessageQueueDesc(
                 MQDescriptor<int8_t, SynchronizedReadWrite>* out_queue, int32_t in_deviceId,
                 int32_t in_streamId);
+        ::ndk::ScopedAStatus getAidlInterfaceVersion(int32_t* _aidl_return);
 
     private:
         ::ndk::ScopedAStatus hidlSetCallback(const std::shared_ptr<TvInputCallback>& in_callback);
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index c26aee8..924e2f8 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -338,6 +338,8 @@
             "com.android.clockwork.modes.ModeManagerService";
     private static final String WEAR_DISPLAY_SERVICE_CLASS =
             "com.android.clockwork.display.WearDisplayService";
+    private static final String WEAR_DEBUG_SERVICE_CLASS =
+            "com.android.clockwork.debug.WearDebugService";
     private static final String WEAR_TIME_SERVICE_CLASS =
             "com.android.clockwork.time.WearTimeService";
     private static final String WEAR_SETTINGS_SERVICE_CLASS =
@@ -2636,6 +2638,12 @@
             mSystemServiceManager.startService(WEAR_DISPLAY_SERVICE_CLASS);
             t.traceEnd();
 
+            if (Build.IS_DEBUGGABLE) {
+                t.traceBegin("StartWearDebugService");
+                mSystemServiceManager.startService(WEAR_DEBUG_SERVICE_CLASS);
+                t.traceEnd();
+            }
+
             t.traceBegin("StartWearTimeService");
             mSystemServiceManager.startService(WEAR_TIME_SERVICE_CLASS);
             t.traceEnd();
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 952cfc4..cbedcaf 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
@@ -43,6 +43,7 @@
 
 import android.annotation.NonNull;
 import android.app.PropertyInvalidatedCache;
+import android.content.ComponentName;
 import android.content.pm.ApplicationInfo;
 import android.content.pm.PackageManager;
 import android.content.pm.SuspendDialogInfo;
@@ -873,12 +874,20 @@
                 .setUid(packageSetting.getAppId())
                 .hideAsFinal());
 
-        ArchiveState archiveState = new ArchiveState(
-                List.of(new ArchiveState.ArchiveActivityInfo("title1", Path.of("/path1"),
-                                Path.of("/monochromePath1")),
-                        new ArchiveState.ArchiveActivityInfo("title2", Path.of("/path2"),
-                                Path.of("/monochromePath2"))),
-                "installerTitle");
+        ArchiveState archiveState =
+                new ArchiveState(
+                        List.of(
+                                new ArchiveState.ArchiveActivityInfo(
+                                        "title1",
+                                        new ComponentName("pkg1", "class1"),
+                                        Path.of("/path1"),
+                                        Path.of("/monochromePath1")),
+                                new ArchiveState.ArchiveActivityInfo(
+                                        "title2",
+                                        new ComponentName("pkg2", "class2"),
+                                        Path.of("/path2"),
+                                        Path.of("/monochromePath2"))),
+                        "installerTitle");
         packageSetting.modifyUserState(UserHandle.SYSTEM.getIdentifier()).setArchiveState(
                 archiveState);
         settings.mPackages.put(PACKAGE_NAME_1, packageSetting);
diff --git a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageUserStateTest.java b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageUserStateTest.java
index 58ae740..87a297b 100644
--- a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageUserStateTest.java
+++ b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageUserStateTest.java
@@ -24,6 +24,7 @@
 import static org.junit.Assert.assertThat;
 import static org.junit.Assert.assertTrue;
 
+import android.content.ComponentName;
 import android.content.pm.PackageManager;
 import android.content.pm.SuspendDialogInfo;
 import android.content.pm.overlay.OverlayPaths;
@@ -192,8 +193,8 @@
         return new SuspendParams(dialogInfo, appExtras, launcherExtras);
     }
 
-    private static PersistableBundle createPersistableBundle(String lKey, long lValue, String sKey,
-            String sValue, String dKey, double dValue) {
+    private static PersistableBundle createPersistableBundle(
+            String lKey, long lValue, String sKey, String sValue, String dKey, double dValue) {
         final PersistableBundle result = new PersistableBundle(3);
         if (lKey != null) {
             result.putLong("com.unit_test." + lKey, lValue);
@@ -320,6 +321,7 @@
             assertEquals(0L, state.getLastPackageUsageTimeInMills()[i]);
         }
     }
+
     private static void assertLastPackageUsageSet(
             PackageStateUnserialized state, int reason, long value) throws Exception {
         for (int i = state.getLastPackageUsageTimeInMills().length - 1; i >= 0; --i) {
@@ -330,6 +332,7 @@
             }
         }
     }
+
     @Test
     public void testPackageUseReasons() throws Exception {
         PackageSetting packageSetting = Mockito.mock(PackageSetting.class);
@@ -377,6 +380,7 @@
         assertTrue(testState.setOverlayPaths(new OverlayPaths.Builder().build()));
         assertFalse(testState.setOverlayPaths(null));
     }
+
     @Test
     public void testSharedLibOverlayPaths() {
         final PackageUserStateImpl testState = new PackageUserStateImpl();
@@ -401,8 +405,12 @@
     @Test
     public void archiveState() {
         PackageUserStateImpl packageUserState = new PackageUserStateImpl();
-        ArchiveState.ArchiveActivityInfo archiveActivityInfo = new ArchiveState.ArchiveActivityInfo(
-                "appTitle", Path.of("/path1"), Path.of("/path2"));
+        ArchiveState.ArchiveActivityInfo archiveActivityInfo =
+                new ArchiveState.ArchiveActivityInfo(
+                        "appTitle",
+                        new ComponentName("pkg", "class"),
+                        Path.of("/path1"),
+                        Path.of("/path2"));
         ArchiveState archiveState = new ArchiveState(List.of(archiveActivityInfo),
                 "installerTitle");
         packageUserState.setArchiveState(archiveState);
diff --git a/services/tests/displayservicetests/src/com/android/server/display/DisplayManagerServiceTest.java b/services/tests/displayservicetests/src/com/android/server/display/DisplayManagerServiceTest.java
index d021f1d..16d72e4 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/DisplayManagerServiceTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/DisplayManagerServiceTest.java
@@ -117,7 +117,6 @@
 import com.android.server.display.notifications.DisplayNotificationManager;
 import com.android.server.input.InputManagerInternal;
 import com.android.server.lights.LightsManager;
-import com.android.server.pm.UserManagerInternal;
 import com.android.server.sensors.SensorManagerInternal;
 import com.android.server.wm.WindowManagerInternal;
 
@@ -312,7 +311,6 @@
     @Mock SensorManager mSensorManager;
     @Mock DisplayDeviceConfig mMockDisplayDeviceConfig;
     @Mock PackageManagerInternal mMockPackageManagerInternal;
-    @Mock UserManagerInternal mMockUserManagerInternal;
 
 
     @Captor ArgumentCaptor<ContentRecordingSession> mContentRecordingSessionCaptor;
@@ -336,8 +334,6 @@
                 VirtualDeviceManagerInternal.class, mMockVirtualDeviceManagerInternal);
         LocalServices.removeServiceForTest(PackageManagerInternal.class);
         LocalServices.addService(PackageManagerInternal.class, mMockPackageManagerInternal);
-        LocalServices.removeServiceForTest(UserManagerInternal.class);
-        LocalServices.addService(UserManagerInternal.class, mMockUserManagerInternal);
         // TODO: b/287945043
         mContext = spy(new ContextWrapper(ApplicationProvider.getApplicationContext()));
         mResources = Mockito.spy(mContext.getResources());
diff --git a/services/tests/displayservicetests/src/com/android/server/display/mode/DisplayModeDirectorTest.java b/services/tests/displayservicetests/src/com/android/server/display/mode/DisplayModeDirectorTest.java
index c4f72b3..6a95d5c 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/mode/DisplayModeDirectorTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/mode/DisplayModeDirectorTest.java
@@ -102,6 +102,9 @@
 import com.android.server.statusbar.StatusBarManagerInternal;
 import com.android.server.testutils.FakeDeviceConfigInterface;
 
+import junitparams.JUnitParamsRunner;
+import junitparams.Parameters;
+
 import org.junit.Before;
 import org.junit.Rule;
 import org.junit.Test;
@@ -121,26 +124,28 @@
 import java.util.concurrent.TimeUnit;
 import java.util.stream.Collectors;
 
-import junitparams.JUnitParamsRunner;
-import junitparams.Parameters;
-
 @SmallTest
 @RunWith(JUnitParamsRunner.class)
 public class DisplayModeDirectorTest {
     public static Collection<Object[]> getAppRequestedSizeTestCases() {
         var appRequestedSizeTestCases = Arrays.asList(new Object[][] {
-                {DEFAULT_MODE_75.getModeId(), Float.POSITIVE_INFINITY,
-                        DEFAULT_MODE_75.getRefreshRate(), Map.of()},
-                {APP_MODE_HIGH_90.getModeId(), Float.POSITIVE_INFINITY,
-                        APP_MODE_HIGH_90.getRefreshRate(),
-                        Map.of(
+                {/*expectedBaseModeId*/ DEFAULT_MODE_75.getModeId(),
+                        /*expectedPhysicalRefreshRate*/ Float.POSITIVE_INFINITY,
+                        /*expectedAppRequestedRefreshRate*/ DEFAULT_MODE_75.getRefreshRate(),
+                        /*votesWithPriorities*/ Map.of()},
+                {/*expectedBaseModeId*/ APP_MODE_HIGH_90.getModeId(),
+                        /*expectedPhysicalRefreshRate*/ Float.POSITIVE_INFINITY,
+                        /*expectedAppRequestedRefreshRate*/ APP_MODE_HIGH_90.getRefreshRate(),
+                        /*votesWithPriorities*/ Map.of(
                                 Vote.PRIORITY_APP_REQUEST_SIZE,
                                 Vote.forSize(APP_MODE_HIGH_90.getPhysicalWidth(),
                                         APP_MODE_HIGH_90.getPhysicalHeight()),
                                 Vote.PRIORITY_APP_REQUEST_BASE_MODE_REFRESH_RATE,
                                 Vote.forBaseModeRefreshRate(APP_MODE_HIGH_90.getRefreshRate()))},
-                {LIMIT_MODE_70.getModeId(), Float.POSITIVE_INFINITY, Float.POSITIVE_INFINITY,
-                        Map.of(
+                {/*expectedBaseModeId*/ LIMIT_MODE_70.getModeId(),
+                        /*expectedPhysicalRefreshRate*/ Float.POSITIVE_INFINITY,
+                        /*expectedAppRequestedRefreshRate*/ Float.POSITIVE_INFINITY,
+                        /*votesWithPriorities*/ Map.of(
                                 Vote.PRIORITY_APP_REQUEST_SIZE,
                                 Vote.forSize(APP_MODE_HIGH_90.getPhysicalWidth(),
                                         APP_MODE_HIGH_90.getPhysicalHeight()),
@@ -149,9 +154,10 @@
                                 Vote.PRIORITY_LOW_POWER_MODE,
                                 Vote.forSize(LIMIT_MODE_70.getPhysicalWidth(),
                                         LIMIT_MODE_70.getPhysicalHeight()))},
-                {LIMIT_MODE_70.getModeId(), LIMIT_MODE_70.getRefreshRate(),
-                        LIMIT_MODE_70.getRefreshRate(),
-                        Map.of(
+                {/*expectedBaseModeId*/ LIMIT_MODE_70.getModeId(),
+                        /*expectedPhysicalRefreshRate*/ LIMIT_MODE_70.getRefreshRate(),
+                        /*expectedAppRequestedRefreshRate*/ LIMIT_MODE_70.getRefreshRate(),
+                        /*votesWithPriorities*/ Map.of(
                                 Vote.PRIORITY_APP_REQUEST_SIZE,
                                 Vote.forSize(APP_MODE_65.getPhysicalWidth(),
                                         APP_MODE_65.getPhysicalHeight()),
@@ -160,9 +166,10 @@
                                 Vote.PRIORITY_LOW_POWER_MODE,
                                 Vote.forSize(LIMIT_MODE_70.getPhysicalWidth(),
                                         LIMIT_MODE_70.getPhysicalHeight()))},
-                {LIMIT_MODE_70.getModeId(), LIMIT_MODE_70.getRefreshRate(),
-                        LIMIT_MODE_70.getRefreshRate(),
-                        Map.of(
+                {/*expectedBaseModeId*/ LIMIT_MODE_70.getModeId(),
+                        /*expectedPhysicalRefreshRate*/ LIMIT_MODE_70.getRefreshRate(),
+                        /*expectedAppRequestedRefreshRate*/ LIMIT_MODE_70.getRefreshRate(),
+                        /*votesWithPriorities*/ Map.of(
                                 Vote.PRIORITY_APP_REQUEST_SIZE,
                                 Vote.forSize(APP_MODE_65.getPhysicalWidth(),
                                         APP_MODE_65.getPhysicalHeight()),
@@ -173,10 +180,12 @@
                                     0, 0,
                                     LIMIT_MODE_70.getPhysicalWidth(),
                                     LIMIT_MODE_70.getPhysicalHeight(),
-                                    0, Float.POSITIVE_INFINITY)), false},
-                {APP_MODE_65.getModeId(), APP_MODE_65.getRefreshRate(),
-                        APP_MODE_65.getRefreshRate(),
-                        Map.of(
+                                    0, Float.POSITIVE_INFINITY)),
+                        /*displayResolutionRangeVotingEnabled*/ false},
+                {/*expectedBaseModeId*/ APP_MODE_65.getModeId(),
+                        /*expectedPhysicalRefreshRate*/ APP_MODE_65.getRefreshRate(),
+                        /*expectedAppRequestedRefreshRate*/ APP_MODE_65.getRefreshRate(),
+                        /*votesWithPriorities*/ Map.of(
                                 Vote.PRIORITY_APP_REQUEST_SIZE,
                                 Vote.forSize(APP_MODE_65.getPhysicalWidth(),
                                         APP_MODE_65.getPhysicalHeight()),
@@ -187,7 +196,40 @@
                                     0, 0,
                                     LIMIT_MODE_70.getPhysicalWidth(),
                                     LIMIT_MODE_70.getPhysicalHeight(),
-                                    0, Float.POSITIVE_INFINITY)), true}});
+                                    0, Float.POSITIVE_INFINITY)),
+                        /*displayResolutionRangeVotingEnabled*/ true},
+                {/*expectedBaseModeId*/ DEFAULT_MODE_75.getModeId(),
+                        /*expectedPhysicalRefreshRate*/ APP_MODE_65.getRefreshRate(),
+                        /*expectedAppRequestedRefreshRate*/ APP_MODE_HIGH_90.getRefreshRate(),
+                        /*votesWithPriorities*/ Map.of(
+                                Vote.PRIORITY_APP_REQUEST_SIZE,
+                                Vote.forSize(APP_MODE_HIGH_90.getPhysicalWidth(),
+                                        APP_MODE_HIGH_90.getPhysicalHeight()),
+                                Vote.PRIORITY_APP_REQUEST_BASE_MODE_REFRESH_RATE,
+                                Vote.forBaseModeRefreshRate(APP_MODE_HIGH_90.getRefreshRate()),
+                                Vote.PRIORITY_LOW_POWER_MODE,
+                                Vote.forSizeAndPhysicalRefreshRatesRange(
+                                    0, 0,
+                                    LIMIT_MODE_70.getPhysicalWidth(),
+                                    LIMIT_MODE_70.getPhysicalHeight(),
+                                    0, APP_MODE_65.getRefreshRate())),
+                        /*displayResolutionRangeVotingEnabled*/ false},
+                {/*expectedBaseModeId*/ DEFAULT_MODE_60.getModeId(), // Resolution == APP_MODE_65
+                        /*expectedPhysicalRefreshRate*/ APP_MODE_65.getRefreshRate(),
+                        /*expectedAppRequestedRefreshRate*/ APP_MODE_65.getRefreshRate(),
+                        /*votesWithPriorities*/ Map.of(
+                                Vote.PRIORITY_APP_REQUEST_SIZE,
+                                Vote.forSize(APP_MODE_HIGH_90.getPhysicalWidth(),
+                                        APP_MODE_HIGH_90.getPhysicalHeight()),
+                                Vote.PRIORITY_APP_REQUEST_BASE_MODE_REFRESH_RATE,
+                                Vote.forBaseModeRefreshRate(APP_MODE_HIGH_90.getRefreshRate()),
+                                Vote.PRIORITY_LOW_POWER_MODE,
+                                Vote.forSizeAndPhysicalRefreshRatesRange(
+                                    0, 0,
+                                    LIMIT_MODE_70.getPhysicalWidth(),
+                                    LIMIT_MODE_70.getPhysicalHeight(),
+                                    0, APP_MODE_65.getRefreshRate())),
+                        /*displayResolutionRangeVotingEnabled*/ true}});
 
         final var res = new ArrayList<Object[]>(appRequestedSizeTestCases.size() * 2);
 
@@ -218,6 +260,8 @@
     private static final boolean DEBUG = false;
     private static final float FLOAT_TOLERANCE = 0.01f;
 
+    private static final Display.Mode DEFAULT_MODE_60 = new Display.Mode(
+            /*modeId=*/60, /*width=*/1900, /*height=*/1900, 60);
     private static final Display.Mode APP_MODE_65 = new Display.Mode(
             /*modeId=*/65, /*width=*/1900, /*height=*/1900, 65);
     private static final Display.Mode LIMIT_MODE_70 = new Display.Mode(
@@ -227,8 +271,7 @@
     private static final Display.Mode APP_MODE_HIGH_90 = new Display.Mode(
             /*modeId=*/90, /*width=*/3000, /*height=*/3000, 90);
     private static final Display.Mode[] TEST_MODES = new Display.Mode[] {
-        new Display.Mode(
-            /*modeId=*/60, /*width=*/1900, /*height=*/1900, 60),
+        DEFAULT_MODE_60,
         APP_MODE_65,
         LIMIT_MODE_70,
         DEFAULT_MODE_75,
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/AsyncProcessStartTest.java b/services/tests/mockingservicestests/src/com/android/server/am/AsyncProcessStartTest.java
index 596a3f3..b3605cc 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/AsyncProcessStartTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/AsyncProcessStartTest.java
@@ -280,7 +280,9 @@
                 0, 0);
 
         // Sleep until timeout should have triggered
-        SystemClock.sleep(ActivityManagerService.PROC_START_TIMEOUT + 1000);
+        if (wedge) {
+            SystemClock.sleep(ActivityManagerService.PROC_START_TIMEOUT + 1000);
+        }
 
         return app;
     }
diff --git a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java
index 410ae35..367e14b 100644
--- a/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/am/BroadcastQueueTest.java
@@ -228,7 +228,7 @@
         LocalServices.removeServiceForTest(AlarmManagerInternal.class);
         LocalServices.addService(AlarmManagerInternal.class, mAlarmManagerInt);
         doReturn(new ComponentName("", "")).when(mPackageManagerInt).getSystemUiServiceComponent();
-        doNothing().when(mPackageManagerInt).setPackageStoppedState(any(), anyBoolean(), anyInt());
+        doNothing().when(mPackageManagerInt).notifyComponentUsed(any(), anyInt(), any(), any());
         doAnswer((invocation) -> {
             return getUidForPackage(invocation.getArgument(0));
         }).when(mPackageManagerInt).getPackageUid(any(), anyLong(), eq(UserHandle.USER_SYSTEM));
@@ -1014,8 +1014,9 @@
                     eq(PackageManager.NOTIFY_PACKAGE_USE_BROADCAST_RECEIVER));
 
             // Confirm that we unstopped manifest receivers
-            verify(mAms.mPackageManagerInt, atLeastOnce()).setPackageStoppedState(
-                    eq(receiverApp.info.packageName), eq(false), eq(UserHandle.USER_SYSTEM));
+            verify(mAms.mPackageManagerInt, atLeastOnce()).notifyComponentUsed(
+                    eq(receiverApp.info.packageName), eq(UserHandle.USER_SYSTEM),
+                    eq(callerApp.info.packageName), any());
         }
 
         // Confirm that we've reported expected usage events
diff --git a/services/tests/mockingservicestests/src/com/android/server/display/SmallAreaDetectionControllerTest.java b/services/tests/mockingservicestests/src/com/android/server/display/SmallAreaDetectionControllerTest.java
index 1ce79a5..05ac5b5 100644
--- a/services/tests/mockingservicestests/src/com/android/server/display/SmallAreaDetectionControllerTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/display/SmallAreaDetectionControllerTest.java
@@ -16,8 +16,6 @@
 
 package com.android.server.display;
 
-import static android.os.Process.INVALID_UID;
-
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.eq;
 import static org.mockito.Mockito.doNothing;
@@ -35,7 +33,7 @@
 import androidx.test.runner.AndroidJUnit4;
 
 import com.android.server.LocalServices;
-import com.android.server.pm.UserManagerInternal;
+import com.android.server.pm.pkg.PackageStateInternal;
 
 import org.junit.Before;
 import org.junit.Rule;
@@ -55,7 +53,10 @@
     @Mock
     private PackageManagerInternal mMockPackageManagerInternal;
     @Mock
-    private UserManagerInternal mMockUserManagerInternal;
+    private PackageStateInternal mMockPkgStateA;
+    @Mock
+    private PackageStateInternal mMockPkgStateB;
+
 
     private SmallAreaDetectionController mSmallAreaDetectionController;
 
@@ -64,29 +65,18 @@
     private static final String PKG_NOT_INSTALLED = "com.not.installed";
     private static final float THRESHOLD_A = 0.05f;
     private static final float THRESHOLD_B = 0.07f;
-    private static final int USER_1 = 110;
-    private static final int USER_2 = 111;
-    private static final int UID_A_1 = 11011111;
-    private static final int UID_A_2 = 11111111;
-    private static final int UID_B_1 = 11022222;
-    private static final int UID_B_2 = 11122222;
+    private static final int APP_ID_A = 11111;
+    private static final int APP_ID_B = 22222;
 
     @Before
     public void setup() {
         LocalServices.removeServiceForTest(PackageManagerInternal.class);
         LocalServices.addService(PackageManagerInternal.class, mMockPackageManagerInternal);
-        LocalServices.removeServiceForTest(UserManagerInternal.class);
-        LocalServices.addService(UserManagerInternal.class, mMockUserManagerInternal);
 
-        when(mMockUserManagerInternal.getUserIds()).thenReturn(new int[]{USER_1, USER_2});
-        when(mMockPackageManagerInternal.getPackageUid(PKG_A, 0, USER_1)).thenReturn(UID_A_1);
-        when(mMockPackageManagerInternal.getPackageUid(PKG_A, 0, USER_2)).thenReturn(UID_A_2);
-        when(mMockPackageManagerInternal.getPackageUid(PKG_B, 0, USER_1)).thenReturn(UID_B_1);
-        when(mMockPackageManagerInternal.getPackageUid(PKG_B, 0, USER_2)).thenReturn(UID_B_2);
-        when(mMockPackageManagerInternal.getPackageUid(PKG_NOT_INSTALLED, 0, USER_1)).thenReturn(
-                INVALID_UID);
-        when(mMockPackageManagerInternal.getPackageUid(PKG_NOT_INSTALLED, 0, USER_2)).thenReturn(
-                INVALID_UID);
+        when(mMockPackageManagerInternal.getPackageStateInternal(PKG_A)).thenReturn(mMockPkgStateA);
+        when(mMockPackageManagerInternal.getPackageStateInternal(PKG_B)).thenReturn(mMockPkgStateB);
+        when(mMockPkgStateA.getAppId()).thenReturn(APP_ID_A);
+        when(mMockPkgStateB.getAppId()).thenReturn(APP_ID_B);
 
         mSmallAreaDetectionController = spy(new SmallAreaDetectionController(
                 new ContextWrapper(ApplicationProvider.getApplicationContext()),
@@ -99,9 +89,9 @@
         final String property = PKG_A + ":" + THRESHOLD_A + "," + PKG_B + ":" + THRESHOLD_B;
         mSmallAreaDetectionController.updateAllowlist(property);
 
-        final int[] resultUidArray = {UID_A_1, UID_B_1, UID_A_2, UID_B_2};
-        final float[] resultThresholdArray = {THRESHOLD_A, THRESHOLD_B, THRESHOLD_A, THRESHOLD_B};
-        verify(mSmallAreaDetectionController).updateSmallAreaDetection(eq(resultUidArray),
+        final int[] resultAppIdArray = {APP_ID_A, APP_ID_B};
+        final float[] resultThresholdArray = {THRESHOLD_A, THRESHOLD_B};
+        verify(mSmallAreaDetectionController).updateSmallAreaDetection(eq(resultAppIdArray),
                 eq(resultThresholdArray));
     }
 
@@ -110,9 +100,9 @@
         final String property = PKG_A + "," + PKG_B + ":" + THRESHOLD_B;
         mSmallAreaDetectionController.updateAllowlist(property);
 
-        final int[] resultUidArray = {UID_B_1, UID_B_2};
-        final float[] resultThresholdArray = {THRESHOLD_B, THRESHOLD_B};
-        verify(mSmallAreaDetectionController).updateSmallAreaDetection(eq(resultUidArray),
+        final int[] resultAppIdArray = {APP_ID_B};
+        final float[] resultThresholdArray = {THRESHOLD_B};
+        verify(mSmallAreaDetectionController).updateSmallAreaDetection(eq(resultAppIdArray),
                 eq(resultThresholdArray));
     }
 
@@ -122,9 +112,9 @@
                 PKG_A + ":" + THRESHOLD_A + "," + PKG_NOT_INSTALLED + ":" + THRESHOLD_B;
         mSmallAreaDetectionController.updateAllowlist(property);
 
-        final int[] resultUidArray = {UID_A_1, UID_A_2};
-        final float[] resultThresholdArray = {THRESHOLD_A, THRESHOLD_A};
-        verify(mSmallAreaDetectionController).updateSmallAreaDetection(eq(resultUidArray),
+        final int[] resultAppIdArray = {APP_ID_A};
+        final float[] resultThresholdArray = {THRESHOLD_A};
+        verify(mSmallAreaDetectionController).updateSmallAreaDetection(eq(resultAppIdArray),
                 eq(resultThresholdArray));
     }
 
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 eb50556..610ea90 100644
--- a/services/tests/mockingservicestests/src/com/android/server/pm/PackageArchiverTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/pm/PackageArchiverTest.java
@@ -35,6 +35,7 @@
 import static org.mockito.Mockito.when;
 
 import android.app.AppOpsManager;
+import android.content.ComponentName;
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentSender;
@@ -427,6 +428,7 @@
         for (LauncherActivityInfo mainActivity : createLauncherActivities()) {
             ArchiveState.ArchiveActivityInfo activityInfo = new ArchiveState.ArchiveActivityInfo(
                     mainActivity.getLabel().toString(),
+                    mainActivity.getComponentName(),
                     ICON_PATH, null);
             activityInfos.add(activityInfo);
         }
@@ -437,9 +439,11 @@
         ActivityInfo activityInfo = mock(ActivityInfo.class);
         LauncherActivityInfo activity1 = mock(LauncherActivityInfo.class);
         when(activity1.getLabel()).thenReturn("activity1");
+        when(activity1.getComponentName()).thenReturn(new ComponentName("pkg1", "class1"));
         when(activity1.getActivityInfo()).thenReturn(activityInfo);
         LauncherActivityInfo activity2 = mock(LauncherActivityInfo.class);
         when(activity2.getLabel()).thenReturn("activity2");
+        when(activity2.getComponentName()).thenReturn(new ComponentName("pkg2", "class2"));
         when(activity2.getActivityInfo()).thenReturn(activityInfo);
         return List.of(activity1, activity2);
     }
diff --git a/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java
index e8cbcf9..a3d415e 100644
--- a/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/companion/virtual/VirtualDeviceManagerServiceTest.java
@@ -337,11 +337,7 @@
         LocalServices.removeServiceForTest(DisplayManagerInternal.class);
         LocalServices.addService(DisplayManagerInternal.class, mDisplayManagerInternalMock);
 
-        mSetFlagsRule.disableFlags(Flags.FLAG_VDM_PUBLIC_APIS);
-        mSetFlagsRule.disableFlags(Flags.FLAG_DYNAMIC_POLICY);
-        mSetFlagsRule.disableFlags(Flags.FLAG_STREAM_PERMISSIONS);
-        mSetFlagsRule.disableFlags(Flags.FLAG_VDM_CUSTOM_HOME);
-        mSetFlagsRule.disableFlags(Flags.FLAG_ENABLE_NATIVE_VDM);
+        mSetFlagsRule.initAllFlagsToReleaseConfigDefault();
 
         doReturn(true).when(mInputManagerInternalMock).setVirtualMousePointerDisplayId(anyInt());
         doNothing().when(mInputManagerInternalMock).setPointerAcceleration(anyFloat(), anyInt());
diff --git a/services/tests/servicestests/src/com/android/server/contentcapture/ContentCaptureManagerServiceTest.java b/services/tests/servicestests/src/com/android/server/contentcapture/ContentCaptureManagerServiceTest.java
index 12d6161..5cc84b1 100644
--- a/services/tests/servicestests/src/com/android/server/contentcapture/ContentCaptureManagerServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/contentcapture/ContentCaptureManagerServiceTest.java
@@ -59,6 +59,7 @@
 import org.mockito.junit.MockitoJUnit;
 import org.mockito.junit.MockitoRule;
 
+import java.util.Collections;
 import java.util.List;
 
 /**
@@ -103,6 +104,10 @@
 
     private boolean mDevCfgEnableContentProtectionReceiver;
 
+    private List<List<String>> mDevCfgContentProtectionRequiredGroups = List.of(List.of("a"));
+
+    private List<List<String>> mDevCfgContentProtectionOptionalGroups = Collections.emptyList();
+
     private int mContentProtectionBlocklistManagersCreated;
 
     private int mContentProtectionServiceInfosCreated;
@@ -374,7 +379,21 @@
     }
 
     @Test
-    public void isContentProtectionReceiverEnabled_withoutManagers() {
+    public void isContentProtectionReceiverEnabled_true() {
+        when(mMockContentProtectionConsentManager.isConsentGranted(USER_ID)).thenReturn(true);
+        when(mMockContentProtectionBlocklistManager.isAllowed(PACKAGE_NAME)).thenReturn(true);
+        mDevCfgEnableContentProtectionReceiver = true;
+        mContentCaptureManagerService = new TestContentCaptureManagerService();
+
+        boolean actual =
+                mContentCaptureManagerService.mGlobalContentCaptureOptions.isWhitelisted(
+                        USER_ID, PACKAGE_NAME);
+
+        assertThat(actual).isTrue();
+    }
+
+    @Test
+    public void isContentProtectionReceiverEnabled_false_withoutManagers() {
         boolean actual =
                 mContentCaptureManagerService.mGlobalContentCaptureOptions.isWhitelisted(
                         USER_ID, PACKAGE_NAME);
@@ -385,7 +404,7 @@
     }
 
     @Test
-    public void isContentProtectionReceiverEnabled_disabledWithFlag() {
+    public void isContentProtectionReceiverEnabled_false_disabledWithFlag() {
         mDevCfgEnableContentProtectionReceiver = true;
         mContentCaptureManagerService = new TestContentCaptureManagerService();
         mContentCaptureManagerService.mDevCfgEnableContentProtectionReceiver = false;
@@ -400,6 +419,22 @@
     }
 
     @Test
+    public void isContentProtectionReceiverEnabled_false_emptyGroups() {
+        mDevCfgEnableContentProtectionReceiver = true;
+        mDevCfgContentProtectionRequiredGroups = Collections.emptyList();
+        mDevCfgContentProtectionOptionalGroups = Collections.emptyList();
+        mContentCaptureManagerService = new TestContentCaptureManagerService();
+
+        boolean actual =
+                mContentCaptureManagerService.mGlobalContentCaptureOptions.isWhitelisted(
+                        USER_ID, PACKAGE_NAME);
+
+        assertThat(actual).isFalse();
+        verify(mMockContentProtectionConsentManager, never()).isConsentGranted(anyInt());
+        verify(mMockContentProtectionBlocklistManager, never()).isAllowed(anyString());
+    }
+
+    @Test
     public void onLoginDetected_disabledAfterConstructor() {
         mDevCfgEnableContentProtectionReceiver = true;
         mContentCaptureManagerService = new TestContentCaptureManagerService();
@@ -525,6 +560,10 @@
             super(sContext);
             this.mDevCfgEnableContentProtectionReceiver =
                     ContentCaptureManagerServiceTest.this.mDevCfgEnableContentProtectionReceiver;
+            this.mDevCfgContentProtectionRequiredGroups =
+                    ContentCaptureManagerServiceTest.this.mDevCfgContentProtectionRequiredGroups;
+            this.mDevCfgContentProtectionOptionalGroups =
+                    ContentCaptureManagerServiceTest.this.mDevCfgContentProtectionOptionalGroups;
         }
 
         @Override
diff --git a/services/tests/servicestests/src/com/android/server/media/projection/MediaProjectionSessionIdGeneratorTest.java b/services/tests/servicestests/src/com/android/server/media/projection/MediaProjectionSessionIdGeneratorTest.java
new file mode 100644
index 0000000..07cdf4d
--- /dev/null
+++ b/services/tests/servicestests/src/com/android/server/media/projection/MediaProjectionSessionIdGeneratorTest.java
@@ -0,0 +1,103 @@
+/*
+ * 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.media.projection;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.content.Context;
+import android.content.SharedPreferences;
+import android.platform.test.annotations.Presubmit;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SmallTest;
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.io.File;
+
+/**
+ * Tests for the {@link MediaProjectionSessionIdGenerator} class.
+ *
+ * <p>Build/Install/Run: atest FrameworksServicesTests:MediaProjectionSessionIdGeneratorTest
+ */
+@SmallTest
+@Presubmit
+@RunWith(AndroidJUnit4.class)
+public class MediaProjectionSessionIdGeneratorTest {
+
+    private static final String TEST_PREFS_FILE = "media-projection-session-id-test";
+
+    private final Context mContext =
+            InstrumentationRegistry.getInstrumentation().getTargetContext();
+    private final File mSharedPreferencesFile = new File(mContext.getCacheDir(), TEST_PREFS_FILE);
+    private final SharedPreferences mSharedPreferences = createSharePreferences();
+    private final MediaProjectionSessionIdGenerator mGenerator =
+            createGenerator(mSharedPreferences);
+
+    @Before
+    public void setUp() {
+        mSharedPreferences.edit().clear().commit();
+    }
+
+    @After
+    public void tearDown() {
+        mSharedPreferences.edit().clear().commit();
+        mSharedPreferencesFile.delete();
+    }
+
+    @Test
+    public void getCurrentSessionId_byDefault_returns0() {
+        assertThat(mGenerator.getCurrentSessionId()).isEqualTo(0);
+    }
+
+    @Test
+    public void getCurrentSessionId_multipleTimes_returnsSameValue() {
+        assertThat(mGenerator.getCurrentSessionId()).isEqualTo(0);
+        assertThat(mGenerator.getCurrentSessionId()).isEqualTo(0);
+        assertThat(mGenerator.getCurrentSessionId()).isEqualTo(0);
+    }
+
+    @Test
+    public void createAndGetNewSessionId_returnsIncrementedId() {
+        int previousValue = mGenerator.getCurrentSessionId();
+
+        int newValue = mGenerator.createAndGetNewSessionId();
+
+        assertThat(newValue).isEqualTo(previousValue + 1);
+    }
+
+    @Test
+    public void createAndGetNewSessionId_persistsNewValue() {
+        int newValue = mGenerator.createAndGetNewSessionId();
+
+        MediaProjectionSessionIdGenerator newInstance = createGenerator(createSharePreferences());
+
+        assertThat(newInstance.getCurrentSessionId()).isEqualTo(newValue);
+    }
+
+    private SharedPreferences createSharePreferences() {
+        return mContext.getSharedPreferences(mSharedPreferencesFile, Context.MODE_PRIVATE);
+    }
+
+    private MediaProjectionSessionIdGenerator createGenerator(SharedPreferences sharedPreferences) {
+        return new MediaProjectionSessionIdGenerator(sharedPreferences);
+    }
+}
diff --git a/services/tests/wmtests/src/com/android/server/wm/BackNavigationControllerTests.java b/services/tests/wmtests/src/com/android/server/wm/BackNavigationControllerTests.java
index 8e7ba70..dd7dec0 100644
--- a/services/tests/wmtests/src/com/android/server/wm/BackNavigationControllerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/BackNavigationControllerTests.java
@@ -130,12 +130,22 @@
         // verify if back animation would start.
         assertTrue("Animation scheduled", backNavigationInfo.isPrepareRemoteAnimation());
 
-        // reset drawing status
+        // reset drawing status to test translucent activity
         backNavigationInfo.onBackNavigationFinished(false);
         mBackNavigationController.clearBackAnimations();
-        topTask.forAllWindows(w -> {
-            makeWindowVisibleAndDrawn(w);
-        }, true);
+        final ActivityRecord topActivity = topTask.getTopMostActivity();
+        makeWindowVisibleAndDrawn(topActivity.findMainWindow());
+        // simulate translucent
+        topActivity.setOccludesParent(false);
+        backNavigationInfo = startBackNavigation();
+        assertThat(typeToString(backNavigationInfo.getType()))
+                .isEqualTo(typeToString(BackNavigationInfo.TYPE_CALLBACK));
+
+        // reset drawing status to test keyguard occludes
+        topActivity.setOccludesParent(true);
+        backNavigationInfo.onBackNavigationFinished(false);
+        mBackNavigationController.clearBackAnimations();
+        makeWindowVisibleAndDrawn(topActivity.findMainWindow());
         setupKeyguardOccluded();
         backNavigationInfo = startBackNavigation();
         assertThat(typeToString(backNavigationInfo.getType()))
@@ -201,9 +211,7 @@
         // reset drawing status
         backNavigationInfo.onBackNavigationFinished(false);
         mBackNavigationController.clearBackAnimations();
-        testCase.recordFront.forAllWindows(w -> {
-            makeWindowVisibleAndDrawn(w);
-        }, true);
+        makeWindowVisibleAndDrawn(testCase.recordFront.findMainWindow());
         setupKeyguardOccluded();
         backNavigationInfo = startBackNavigation();
         assertThat(typeToString(backNavigationInfo.getType()))
diff --git a/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java b/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java
index 2bf1385..6235b3b 100644
--- a/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java
+++ b/services/tests/wmtests/src/com/android/server/wm/TaskFragmentOrganizerControllerTest.java
@@ -26,7 +26,9 @@
 import static android.view.WindowManager.TRANSIT_OPEN;
 import static android.window.TaskFragmentOperation.OP_TYPE_CREATE_TASK_FRAGMENT;
 import static android.window.TaskFragmentOperation.OP_TYPE_DELETE_TASK_FRAGMENT;
+import static android.window.TaskFragmentOperation.OP_TYPE_REORDER_TO_BOTTOM_OF_TASK;
 import static android.window.TaskFragmentOperation.OP_TYPE_REORDER_TO_FRONT;
+import static android.window.TaskFragmentOperation.OP_TYPE_REORDER_TO_TOP_OF_TASK;
 import static android.window.TaskFragmentOperation.OP_TYPE_REPARENT_ACTIVITY_TO_TASK_FRAGMENT;
 import static android.window.TaskFragmentOperation.OP_TYPE_SET_ADJACENT_TASK_FRAGMENTS;
 import static android.window.TaskFragmentOperation.OP_TYPE_SET_ANIMATION_PARAMS;
@@ -1655,6 +1657,127 @@
         assertEquals(frontMostTaskFragment, tf0);
     }
 
+    @Test
+    public void testApplyTransaction_reorderToBottomOfTask() {
+        mController.unregisterOrganizer(mIOrganizer);
+        mController.registerOrganizerInternal(mIOrganizer, true /* isSystemOrganizer */);
+        final Task task = createTask(mDisplayContent);
+        // Create a non-embedded Activity at the bottom.
+        final ActivityRecord bottomActivity = new ActivityBuilder(mAtm)
+                .setTask(task)
+                .build();
+        final TaskFragment tf0 = createTaskFragment(task);
+        final TaskFragment tf1 = createTaskFragment(task);
+        // Create a non-embedded Activity at the top.
+        final ActivityRecord topActivity = new ActivityBuilder(mAtm)
+                .setTask(task)
+                .build();
+
+        // Ensure correct order of the children before the operation
+        assertEquals(topActivity, task.getChildAt(3).asActivityRecord());
+        assertEquals(tf1, task.getChildAt(2).asTaskFragment());
+        assertEquals(tf0, task.getChildAt(1).asTaskFragment());
+        assertEquals(bottomActivity, task.getChildAt(0).asActivityRecord());
+
+        // Reorder TaskFragment to bottom
+        final TaskFragmentOperation operation = new TaskFragmentOperation.Builder(
+                OP_TYPE_REORDER_TO_BOTTOM_OF_TASK).build();
+        mTransaction.addTaskFragmentOperation(tf1.getFragmentToken(), operation);
+        assertApplyTransactionAllowed(mTransaction);
+
+        // Ensure correct order of the children after the operation
+        assertEquals(topActivity, task.getChildAt(3).asActivityRecord());
+        assertEquals(tf0, task.getChildAt(2).asTaskFragment());
+        assertEquals(bottomActivity, task.getChildAt(1).asActivityRecord());
+        assertEquals(tf1, task.getChildAt(0).asTaskFragment());
+    }
+
+    @Test
+    public void testApplyTransaction_reorderToTopOfTask() {
+        mController.unregisterOrganizer(mIOrganizer);
+        mController.registerOrganizerInternal(mIOrganizer, true /* isSystemOrganizer */);
+        final Task task = createTask(mDisplayContent);
+        // Create a non-embedded Activity at the bottom.
+        final ActivityRecord bottomActivity = new ActivityBuilder(mAtm)
+                .setTask(task)
+                .build();
+        final TaskFragment tf0 = createTaskFragment(task);
+        final TaskFragment tf1 = createTaskFragment(task);
+        // Create a non-embedded Activity at the top.
+        final ActivityRecord topActivity = new ActivityBuilder(mAtm)
+                .setTask(task)
+                .build();
+
+        // Ensure correct order of the children before the operation
+        assertEquals(topActivity, task.getChildAt(3).asActivityRecord());
+        assertEquals(tf1, task.getChildAt(2).asTaskFragment());
+        assertEquals(tf0, task.getChildAt(1).asTaskFragment());
+        assertEquals(bottomActivity, task.getChildAt(0).asActivityRecord());
+
+        // Reorder TaskFragment to top
+        final TaskFragmentOperation operation = new TaskFragmentOperation.Builder(
+                OP_TYPE_REORDER_TO_TOP_OF_TASK).build();
+        mTransaction.addTaskFragmentOperation(tf0.getFragmentToken(), operation);
+        assertApplyTransactionAllowed(mTransaction);
+
+        // Ensure correct order of the children after the operation
+        assertEquals(tf0, task.getChildAt(3).asTaskFragment());
+        assertEquals(topActivity, task.getChildAt(2).asActivityRecord());
+        assertEquals(tf1, task.getChildAt(1).asTaskFragment());
+        assertEquals(bottomActivity, task.getChildAt(0).asActivityRecord());
+    }
+
+    @Test
+    public void testApplyTransaction_reorderToBottomOfTask_failsIfNotSystemOrganizer() {
+        testApplyTransaction_reorder_failsIfNotSystemOrganizer_common(
+                OP_TYPE_REORDER_TO_BOTTOM_OF_TASK);
+    }
+
+    @Test
+    public void testApplyTransaction_reorderToTopOfTask_failsIfNotSystemOrganizer() {
+        testApplyTransaction_reorder_failsIfNotSystemOrganizer_common(
+                OP_TYPE_REORDER_TO_TOP_OF_TASK);
+    }
+
+    private void testApplyTransaction_reorder_failsIfNotSystemOrganizer_common(
+            @TaskFragmentOperation.OperationType int opType) {
+        final Task task = createTask(mDisplayContent);
+        // Create a non-embedded Activity at the bottom.
+        final ActivityRecord bottomActivity = new ActivityBuilder(mAtm)
+                .setTask(task)
+                .build();
+        final TaskFragment tf0 = createTaskFragment(task);
+        final TaskFragment tf1 = createTaskFragment(task);
+        // Create a non-embedded Activity at the top.
+        final ActivityRecord topActivity = new ActivityBuilder(mAtm)
+                .setTask(task)
+                .build();
+
+        // Ensure correct order of the children before the operation
+        assertEquals(topActivity, task.getChildAt(3).asActivityRecord());
+        assertEquals(tf1, task.getChildAt(2).asTaskFragment());
+        assertEquals(tf0, task.getChildAt(1).asTaskFragment());
+        assertEquals(bottomActivity, task.getChildAt(0).asActivityRecord());
+
+        // Apply reorder transaction, which is expected to fail for non-system organizer.
+        final TaskFragmentOperation operation = new TaskFragmentOperation.Builder(
+                opType).build();
+        mTransaction
+                .addTaskFragmentOperation(tf0.getFragmentToken(), operation)
+                .setErrorCallbackToken(mErrorToken);
+        assertApplyTransactionAllowed(mTransaction);
+        // The pending event will be dispatched on the handler (from requestTraversal).
+        waitHandlerIdle(mWm.mAnimationHandler);
+
+        assertTaskFragmentErrorTransaction(opType, SecurityException.class);
+
+        // Ensure no change to the order of the children after the operation
+        assertEquals(topActivity, task.getChildAt(3).asActivityRecord());
+        assertEquals(tf1, task.getChildAt(2).asTaskFragment());
+        assertEquals(tf0, task.getChildAt(1).asTaskFragment());
+        assertEquals(bottomActivity, task.getChildAt(0).asActivityRecord());
+    }
+
     /**
      * Creates a {@link TaskFragment} with the {@link WindowContainerTransaction}. Calls
      * {@link WindowOrganizerController#applyTransaction(WindowContainerTransaction)} to apply the
@@ -1782,6 +1905,19 @@
         assertEquals(activityToken, change.getActivityToken());
     }
 
+    /** Setups an embedded TaskFragment. */
+    private TaskFragment createTaskFragment(Task task) {
+        final IBinder token = new Binder();
+        TaskFragment taskFragment = new TaskFragmentBuilder(mAtm)
+                .setParentTask(task)
+                .setFragmentToken(token)
+                .setOrganizer(mOrganizer)
+                .createActivityCount(1)
+                .build();
+        mWindowOrganizerController.mLaunchTaskFragments.put(token, taskFragment);
+        return taskFragment;
+    }
+
     /** Setups an embedded TaskFragment in a PIP Task. */
     private void setupTaskFragmentInPip() {
         mTaskFragment = new TaskFragmentBuilder(mAtm)
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java
index e86fc36..eaeb804 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowManagerServiceTests.java
@@ -100,6 +100,9 @@
 import com.android.compatibility.common.util.AdoptShellPermissionsRule;
 import com.android.internal.os.IResultReceiver;
 import com.android.server.LocalServices;
+import com.android.server.wm.WindowManagerService.WindowContainerInfo;
+
+import com.google.common.truth.Expect;
 
 import org.junit.Rule;
 import org.junit.Test;
@@ -125,6 +128,9 @@
             InstrumentationRegistry.getInstrumentation().getUiAutomation(),
             ADD_TRUSTED_DISPLAY);
 
+    @Rule
+    public Expect mExpect = Expect.create();
+
     @Test
     public void testIsRequestedOrientationMapped() {
         mWm.setOrientationRequestPolicy(/* isIgnoreOrientationRequestDisabled*/ true,
@@ -674,64 +680,68 @@
 
     @Test
     public void testGetTaskWindowContainerTokenForLaunchCookie_nullCookie() {
-        WindowContainerToken wct = mWm.getTaskWindowContainerTokenForLaunchCookie(null);
-        assertThat(wct).isNull();
+        WindowContainerInfo wci = mWm.getTaskWindowContainerInfoForLaunchCookie(null);
+        assertThat(wci).isNull();
     }
 
     @Test
     public void testGetTaskWindowContainerTokenForLaunchCookie_invalidCookie() {
         Binder cookie = new Binder("test cookie");
-        WindowContainerToken wct = mWm.getTaskWindowContainerTokenForLaunchCookie(cookie);
-        assertThat(wct).isNull();
+        WindowContainerInfo wci = mWm.getTaskWindowContainerInfoForLaunchCookie(cookie);
+        assertThat(wci).isNull();
 
         final ActivityRecord testActivity = new ActivityBuilder(mAtm)
                 .setCreateTask(true)
                 .build();
 
-        wct = mWm.getTaskWindowContainerTokenForLaunchCookie(cookie);
-        assertThat(wct).isNull();
+        wci = mWm.getTaskWindowContainerInfoForLaunchCookie(cookie);
+        assertThat(wci).isNull();
     }
 
     @Test
     public void testGetTaskWindowContainerTokenForLaunchCookie_validCookie() {
         final Binder cookie = new Binder("ginger cookie");
         final WindowContainerToken launchRootTask = mock(WindowContainerToken.class);
-        setupActivityWithLaunchCookie(cookie, launchRootTask);
+        final int uid = 123;
+        setupActivityWithLaunchCookie(cookie, launchRootTask, uid);
 
-        WindowContainerToken wct = mWm.getTaskWindowContainerTokenForLaunchCookie(cookie);
-        assertThat(wct).isEqualTo(launchRootTask);
+        WindowContainerInfo wci = mWm.getTaskWindowContainerInfoForLaunchCookie(cookie);
+        mExpect.that(wci.getToken()).isEqualTo(launchRootTask);
+        mExpect.that(wci.getUid()).isEqualTo(uid);
     }
 
     @Test
     public void testGetTaskWindowContainerTokenForLaunchCookie_multipleCookies() {
         final Binder cookie1 = new Binder("ginger cookie");
         final WindowContainerToken launchRootTask1 = mock(WindowContainerToken.class);
-        setupActivityWithLaunchCookie(cookie1, launchRootTask1);
+        final int uid1 = 123;
+        setupActivityWithLaunchCookie(cookie1, launchRootTask1, uid1);
 
         setupActivityWithLaunchCookie(new Binder("choc chip cookie"),
-                mock(WindowContainerToken.class));
+                mock(WindowContainerToken.class), /* uid= */ 456);
 
         setupActivityWithLaunchCookie(new Binder("peanut butter cookie"),
-                mock(WindowContainerToken.class));
+                mock(WindowContainerToken.class), /* uid= */ 789);
 
-        WindowContainerToken wct = mWm.getTaskWindowContainerTokenForLaunchCookie(cookie1);
-        assertThat(wct).isEqualTo(launchRootTask1);
+        WindowContainerInfo wci = mWm.getTaskWindowContainerInfoForLaunchCookie(cookie1);
+        mExpect.that(wci.getToken()).isEqualTo(launchRootTask1);
+        mExpect.that(wci.getUid()).isEqualTo(uid1);
     }
 
     @Test
     public void testGetTaskWindowContainerTokenForLaunchCookie_multipleCookies_noneValid() {
         setupActivityWithLaunchCookie(new Binder("ginger cookie"),
-                mock(WindowContainerToken.class));
+                mock(WindowContainerToken.class), /* uid= */ 123);
 
         setupActivityWithLaunchCookie(new Binder("choc chip cookie"),
-                mock(WindowContainerToken.class));
+                mock(WindowContainerToken.class), /* uid= */ 456);
 
         setupActivityWithLaunchCookie(new Binder("peanut butter cookie"),
-                mock(WindowContainerToken.class));
+                mock(WindowContainerToken.class), /* uid= */ 789);
 
-        WindowContainerToken wct = mWm.getTaskWindowContainerTokenForLaunchCookie(
+        WindowContainerInfo wci = mWm.getTaskWindowContainerInfoForLaunchCookie(
                 new Binder("some other cookie"));
-        assertThat(wct).isNull();
+        assertThat(wci).isNull();
     }
 
     @Test
@@ -778,17 +788,18 @@
     }
 
     @Test
-    public void setContentRecordingSession_matchingTask_mutatesSessionWithWindowContainerToken() {
+    public void setContentRecordingSession_matchingTask_mutatesSessionWithWindowContainerInfo() {
         WindowManagerInternal wmInternal = LocalServices.getService(WindowManagerInternal.class);
         Task task = createTask(mDefaultDisplay);
         ActivityRecord activityRecord = createActivityRecord(task);
-        ContentRecordingSession session = ContentRecordingSession.createTaskSession(
-                activityRecord.mLaunchCookie);
+        ContentRecordingSession session =
+                ContentRecordingSession.createTaskSession(activityRecord.mLaunchCookie);
 
         wmInternal.setContentRecordingSession(session);
 
-        assertThat(session.getTokenToRecord()).isEqualTo(
-                task.mRemoteToken.toWindowContainerToken().asBinder());
+        mExpect.that(session.getTokenToRecord())
+                .isEqualTo(task.mRemoteToken.toWindowContainerToken().asBinder());
+        mExpect.that(session.getTargetUid()).isEqualTo(activityRecord.getUid());
     }
 
     @Test
@@ -1010,12 +1021,12 @@
         }
     }
 
-    private void setupActivityWithLaunchCookie(IBinder launchCookie, WindowContainerToken wct) {
+    private void setupActivityWithLaunchCookie(
+            IBinder launchCookie, WindowContainerToken wct, int uid) {
         final WindowContainer.RemoteToken remoteToken = mock(WindowContainer.RemoteToken.class);
         when(remoteToken.toWindowContainerToken()).thenReturn(wct);
-        final ActivityRecord testActivity = new ActivityBuilder(mAtm)
-                .setCreateTask(true)
-                .build();
+        final ActivityRecord testActivity =
+                new ActivityBuilder(mAtm).setCreateTask(true).setUid(uid).build();
         testActivity.mLaunchCookie = launchCookie;
         testActivity.getTask().mRemoteToken = remoteToken;
     }
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java
index 7168670..0b77fd8 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowOrganizerTests.java
@@ -40,6 +40,7 @@
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.times;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.when;
+import static com.android.server.wm.testing.Assert.assertThrows;
 import static com.android.server.wm.ActivityRecord.State.RESUMED;
 import static com.android.server.wm.WindowContainer.POSITION_TOP;
 import static com.android.server.wm.WindowContainer.SYNC_STATE_READY;
@@ -58,6 +59,7 @@
 import static org.mockito.Mockito.atLeastOnce;
 import static org.mockito.Mockito.clearInvocations;
 
+import android.annotation.NonNull;
 import android.app.ActivityManager;
 import android.app.ActivityManager.RunningTaskInfo;
 import android.app.ActivityOptions;
@@ -77,11 +79,13 @@
 import android.view.Display;
 import android.view.SurfaceControl;
 import android.view.WindowInsets;
+import android.window.ITaskFragmentOrganizer;
 import android.window.ITaskOrganizer;
 import android.window.IWindowContainerTransactionCallback;
 import android.window.StartingWindowInfo;
 import android.window.StartingWindowRemovalInfo;
 import android.window.TaskAppearedInfo;
+import android.window.TaskFragmentOrganizer;
 import android.window.WindowContainerToken;
 import android.window.WindowContainerTransaction;
 
@@ -579,6 +583,87 @@
     }
 
     @Test
+    public void testTaskFragmentHiddenAndFocusableChanges() {
+        removeGlobalMinSizeRestriction();
+        final Task rootTask = new TaskBuilder(mSupervisor).setCreateActivity(true)
+                .setWindowingMode(WINDOWING_MODE_FULLSCREEN).build();
+
+        final WindowContainerTransaction t = new WindowContainerTransaction();
+        final TaskFragmentOrganizer organizer =
+                createTaskFragmentOrganizer(t, true /* isSystemOrganizer */);
+
+        final IBinder token = new Binder();
+        final TaskFragment taskFragment = new TaskFragmentBuilder(mAtm)
+                .setParentTask(rootTask)
+                .setFragmentToken(token)
+                .setOrganizer(organizer)
+                .createActivityCount(1)
+                .build();
+
+        // Should be visible and focusable initially.
+        assertTrue(rootTask.shouldBeVisible(null));
+        assertTrue(taskFragment.shouldBeVisible(null));
+        assertTrue(taskFragment.isFocusable());
+        assertTrue(taskFragment.isTopActivityFocusable());
+
+        // Apply transaction to the TaskFragment hidden and not focusable.
+        t.setHidden(taskFragment.mRemoteToken.toWindowContainerToken(), true);
+        t.setFocusable(taskFragment.mRemoteToken.toWindowContainerToken(), false);
+        mWm.mAtmService.mWindowOrganizerController.applyTaskFragmentTransactionLocked(
+                t, TaskFragmentOrganizer.TASK_FRAGMENT_TRANSIT_CHANGE,
+                false /* shouldApplyIndependently */);
+
+        // Should be not visible and not focusable after the transaction.
+        assertFalse(taskFragment.shouldBeVisible(null));
+        assertFalse(taskFragment.isFocusable());
+        assertFalse(taskFragment.isTopActivityFocusable());
+
+        // Apply transaction to the TaskFragment not hidden and focusable.
+        t.setHidden(taskFragment.mRemoteToken.toWindowContainerToken(), false);
+        t.setFocusable(taskFragment.mRemoteToken.toWindowContainerToken(), true);
+        mWm.mAtmService.mWindowOrganizerController.applyTaskFragmentTransactionLocked(
+                t, TaskFragmentOrganizer.TASK_FRAGMENT_TRANSIT_CHANGE,
+                false /* shouldApplyIndependently */);
+
+        // Should be visible and focusable after the transaction.
+        assertTrue(taskFragment.shouldBeVisible(null));
+        assertTrue(taskFragment.isFocusable());
+        assertTrue(taskFragment.isTopActivityFocusable());
+    }
+
+    @Test
+    public void testTaskFragmentHiddenAndFocusableChanges_throwsWhenNotSystemOrganizer() {
+        removeGlobalMinSizeRestriction();
+        final Task rootTask = new TaskBuilder(mSupervisor).setCreateActivity(true)
+                .setWindowingMode(WINDOWING_MODE_FULLSCREEN).build();
+
+        final WindowContainerTransaction t = new WindowContainerTransaction();
+        final TaskFragmentOrganizer organizer =
+                createTaskFragmentOrganizer(t, false /* isSystemOrganizer */);
+
+        final IBinder token = new Binder();
+        final TaskFragment taskFragment = new TaskFragmentBuilder(mAtm)
+                .setParentTask(rootTask)
+                .setFragmentToken(token)
+                .setOrganizer(organizer)
+                .createActivityCount(1)
+                .build();
+
+        assertTrue(rootTask.shouldBeVisible(null));
+        assertTrue(taskFragment.shouldBeVisible(null));
+
+        t.setHidden(taskFragment.mRemoteToken.toWindowContainerToken(), true);
+        t.setFocusable(taskFragment.mRemoteToken.toWindowContainerToken(), false);
+
+        // Non-system organizers are not allow to update the hidden and focusable states.
+        assertThrows(SecurityException.class, () ->
+                mWm.mAtmService.mWindowOrganizerController.applyTaskFragmentTransactionLocked(
+                        t, TaskFragmentOrganizer.TASK_FRAGMENT_TRANSIT_CHANGE,
+                        false /* shouldApplyIndependently */)
+        );
+    }
+
+    @Test
     public void testContainerTranslucentChanges() {
         removeGlobalMinSizeRestriction();
         final Task rootTask = new TaskBuilder(mSupervisor).setCreateActivity(true)
@@ -1600,4 +1685,20 @@
             assertTrue(taskIds.contains(expectedTasks[i].mTaskId));
         }
     }
+
+    @NonNull
+    private TaskFragmentOrganizer createTaskFragmentOrganizer(
+            @NonNull WindowContainerTransaction t, boolean isSystemOrganizer) {
+        final TaskFragmentOrganizer organizer = new TaskFragmentOrganizer(Runnable::run);
+        final ITaskFragmentOrganizer organizerInterface =
+                ITaskFragmentOrganizer.Stub.asInterface(organizer.getOrganizerToken().asBinder());
+        mWm.mAtmService.mWindowOrganizerController.mTaskFragmentOrganizerController
+                .registerOrganizerInternal(
+                        ITaskFragmentOrganizer.Stub.asInterface(
+                                organizer.getOrganizerToken().asBinder()),
+                        isSystemOrganizer);
+        t.setTaskFragmentOrganizer(organizerInterface);
+
+        return organizer;
+    }
 }
diff --git a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java
index 138e575..1c689d0 100644
--- a/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java
+++ b/services/voiceinteraction/java/com/android/server/voiceinteraction/VoiceInteractionManagerService.java
@@ -58,6 +58,7 @@
 import android.os.IBinder;
 import android.os.Parcel;
 import android.os.ParcelFileDescriptor;
+import android.os.PermissionEnforcer;
 import android.os.PersistableBundle;
 import android.os.RemoteCallback;
 import android.os.RemoteCallbackList;
@@ -67,6 +68,7 @@
 import android.os.ShellCallback;
 import android.os.Trace;
 import android.os.UserHandle;
+import android.permission.flags.Flags;
 import android.provider.Settings;
 import android.service.voice.IMicrophoneHotwordDetectionVoiceInteractionCallback;
 import android.service.voice.IVisualQueryDetectionVoiceInteractionCallback;
@@ -1286,6 +1288,17 @@
             }
         }
 
+        // Enforce permissions that are flag controlled. The flag value decides if the permission
+        // should be enforced.
+        private void initAndVerifyDetector_enforcePermissionWithFlags() {
+            PermissionEnforcer enforcer = mContext.getSystemService(PermissionEnforcer.class);
+            if (Flags.voiceActivationPermissionApis()) {
+                enforcer.enforcePermission(
+                        android.Manifest.permission.RECEIVE_SANDBOX_TRIGGER_AUDIO,
+                        getCallingPid(), getCallingUid());
+            }
+        }
+
         @android.annotation.EnforcePermission(android.Manifest.permission.MANAGE_HOTWORD_DETECTION)
         @Override
         public void initAndVerifyDetector(
@@ -1295,7 +1308,13 @@
                 @NonNull IBinder token,
                 IHotwordRecognitionStatusCallback callback,
                 int detectorType) {
+            // TODO(b/305787465): Remove the MANAGE_HOTWORD_DETECTION permission enforcement on the
+            // {@link #initAndVerifyDetector(Identity,  PersistableBundle, ShareMemory, IBinder,
+            // IHotwordRecognitionStatusCallback, int)}
+            // and replace with the permission RECEIVE_SANDBOX_TRIGGER_AUDIO when it is fully
+            // launched.
             super.initAndVerifyDetector_enforcePermission();
+            initAndVerifyDetector_enforcePermissionWithFlags();
 
             synchronized (this) {
                 enforceIsCurrentVoiceInteractionService();
diff --git a/telephony/java/android/telephony/BarringInfo.java b/telephony/java/android/telephony/BarringInfo.java
index 971fc78..e20e4d2 100644
--- a/telephony/java/android/telephony/BarringInfo.java
+++ b/telephony/java/android/telephony/BarringInfo.java
@@ -202,6 +202,24 @@
                     && mConditionalBarringTimeSeconds == other.mConditionalBarringTimeSeconds;
         }
 
+        private static String barringTypeToString(@BarringType int barringType) {
+            return switch (barringType) {
+                case BARRING_TYPE_NONE -> "NONE";
+                case BARRING_TYPE_CONDITIONAL -> "CONDITIONAL";
+                case BARRING_TYPE_UNCONDITIONAL -> "UNCONDITIONAL";
+                case BARRING_TYPE_UNKNOWN -> "UNKNOWN";
+                default -> "UNKNOWN(" + barringType + ")";
+            };
+        }
+
+        @Override
+        public String toString() {
+            return "BarringServiceInfo {mBarringType=" + barringTypeToString(mBarringType)
+                    + ", mIsConditionallyBarred=" + mIsConditionallyBarred
+                    + ", mConditionalBarringFactor=" + mConditionalBarringFactor
+                    + ", mConditionalBarringTimeSeconds=" + mConditionalBarringTimeSeconds + "}";
+        }
+
         /** @hide */
         public BarringServiceInfo(Parcel p) {
             mBarringType = p.readInt();
diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java
index 90fa69f..73220c3 100644
--- a/telephony/java/android/telephony/TelephonyManager.java
+++ b/telephony/java/android/telephony/TelephonyManager.java
@@ -15033,15 +15033,6 @@
     @TestApi
     public static final int HAL_SERVICE_IMS = 7;
 
-    /**
-     * HAL service type that supports the HAL APIs implementation of IRadioSatellite
-     * {@link RadioSatelliteProxy}
-     * @hide
-     */
-    @TestApi
-    @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
-    public static final int HAL_SERVICE_SATELLITE = 8;
-
     /** @hide */
     @Retention(RetentionPolicy.SOURCE)
     @IntDef(prefix = {"HAL_SERVICE_"},
@@ -15054,7 +15045,6 @@
                     HAL_SERVICE_SIM,
                     HAL_SERVICE_VOICE,
                     HAL_SERVICE_IMS,
-                    HAL_SERVICE_SATELLITE
             })
     public @interface HalService {}
 
diff --git a/telephony/java/android/telephony/satellite/INtnSignalStrengthCallback.aidl b/telephony/java/android/telephony/satellite/INtnSignalStrengthCallback.aidl
new file mode 100644
index 0000000..54cab48
--- /dev/null
+++ b/telephony/java/android/telephony/satellite/INtnSignalStrengthCallback.aidl
@@ -0,0 +1,31 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.telephony.satellite;
+
+import android.telephony.satellite.NtnSignalStrength;
+
+/**
+ * Interface for non-terrestrial signal strength notify callback.
+ * @hide
+ */
+oneway interface INtnSignalStrengthCallback {
+    /**
+     * Called when NTN signal strength changes.
+     * @param ntnSignalStrength The new NTN signal strength.
+     */
+    void onNtnSignalStrengthChanged(in NtnSignalStrength ntnSignalStrength);
+}
diff --git a/telephony/java/android/telephony/satellite/NtnSignalStrength.aidl b/telephony/java/android/telephony/satellite/NtnSignalStrength.aidl
new file mode 100644
index 0000000..a79cb69
--- /dev/null
+++ b/telephony/java/android/telephony/satellite/NtnSignalStrength.aidl
@@ -0,0 +1,19 @@
+/*
+ * Copyright 2023, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.telephony.satellite;
+
+parcelable NtnSignalStrength;
diff --git a/telephony/java/android/telephony/satellite/NtnSignalStrength.java b/telephony/java/android/telephony/satellite/NtnSignalStrength.java
new file mode 100644
index 0000000..16d7654
--- /dev/null
+++ b/telephony/java/android/telephony/satellite/NtnSignalStrength.java
@@ -0,0 +1,149 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.telephony.satellite;
+
+import android.annotation.FlaggedApi;
+import android.annotation.IntDef;
+import android.annotation.Nullable;
+import android.annotation.SystemApi;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import androidx.annotation.NonNull;
+
+import com.android.internal.telephony.flags.Flags;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/**
+ * NTN signal strength related information.
+ * @hide
+ */
+@SystemApi
+@FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
+public final class NtnSignalStrength implements Parcelable {
+    /** Non-terrestrial network signal strength is not available. */
+    @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
+    public static final int NTN_SIGNAL_STRENGTH_NONE = 0;
+    /** Non-terrestrial network signal strength is poor. */
+    @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
+    public static final int NTN_SIGNAL_STRENGTH_POOR = 1;
+    /** Non-terrestrial network signal strength is moderate. */
+    @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
+    public static final int NTN_SIGNAL_STRENGTH_MODERATE = 2;
+    /** Non-terrestrial network signal strength is good. */
+    @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
+    public static final int NTN_SIGNAL_STRENGTH_GOOD = 3;
+    /** Non-terrestrial network signal strength is great. */
+    @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
+    public static final int NTN_SIGNAL_STRENGTH_GREAT = 4;
+    @NtnSignalStrengthLevel private int mLevel;
+
+    /** @hide */
+    @IntDef(prefix = "NTN_SIGNAL_STRENGTH_", value = {
+            NTN_SIGNAL_STRENGTH_NONE,
+            NTN_SIGNAL_STRENGTH_POOR,
+            NTN_SIGNAL_STRENGTH_MODERATE,
+            NTN_SIGNAL_STRENGTH_GOOD,
+            NTN_SIGNAL_STRENGTH_GREAT
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface NtnSignalStrengthLevel {}
+
+    /**
+     * Create a parcelable object to inform the current non-terrestrial signal strength
+     * @hide
+     */
+    @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
+    public NtnSignalStrength(@NtnSignalStrengthLevel int level) {
+        this.mLevel = level;
+    }
+
+    /**
+     * This constructor is used to create a copy of an existing NtnSignalStrength object.
+     */
+    @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
+    public NtnSignalStrength(@Nullable NtnSignalStrength source) {
+        this.mLevel = (source == null) ? NTN_SIGNAL_STRENGTH_NONE : source.getLevel();
+    }
+
+    private NtnSignalStrength(Parcel in) {
+        readFromParcel(in);
+    }
+
+    @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
+    @NtnSignalStrengthLevel public int getLevel() {
+        return mLevel;
+    }
+
+    /**
+     * @return 0
+     */
+    @Override
+    @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
+    public int describeContents() {
+        return 0;
+    }
+
+    /**
+     * @param out  The Parcel in which the object should be written.
+     * @param flags Additional flags about how the object should be written.
+     *              May be 0 or {@link #PARCELABLE_WRITE_RETURN_VALUE}.
+     */
+    @Override
+    @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
+    public void writeToParcel(@NonNull Parcel out, int flags) {
+        out.writeInt(mLevel);
+    }
+
+    private void readFromParcel(Parcel in) {
+        mLevel = in.readInt();
+    }
+
+    @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
+    @NonNull public static final Creator<NtnSignalStrength> CREATOR =
+            new Creator<NtnSignalStrength>() {
+                @Override public NtnSignalStrength createFromParcel(Parcel in) {
+                    return new NtnSignalStrength(in);
+                }
+
+                @Override public NtnSignalStrength[] newArray(int size) {
+                    return new NtnSignalStrength[size];
+                }
+            };
+
+    @Override
+    public int hashCode() {
+        return mLevel;
+    }
+
+    @Override
+    public boolean equals(Object obj) {
+        if (this == obj) return true;
+        if (obj == null || getClass() != obj.getClass()) return false;
+
+        NtnSignalStrength that = (NtnSignalStrength) obj;
+        return mLevel == that.mLevel;
+    }
+
+    @Override public String toString() {
+        return "NtnSignalStrength{"
+                + "mLevel=" + mLevel
+                + '}';
+    }
+}
diff --git a/telephony/java/android/telephony/satellite/NtnSignalStrengthCallback.java b/telephony/java/android/telephony/satellite/NtnSignalStrengthCallback.java
new file mode 100644
index 0000000..4b79590
--- /dev/null
+++ b/telephony/java/android/telephony/satellite/NtnSignalStrengthCallback.java
@@ -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 android.telephony.satellite;
+
+import android.annotation.FlaggedApi;
+import android.annotation.NonNull;
+import android.annotation.SystemApi;
+
+import com.android.internal.telephony.flags.Flags;
+
+/**
+ * A callback class for notifying satellite signal strength change.
+ *
+ * @hide
+ */
+@SystemApi
+@FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
+public interface NtnSignalStrengthCallback {
+    /**
+     * Called when non-terrestrial network signal strength changes.
+     * @param ntnSignalStrength The new non-terrestrial network signal strength.
+     */
+    @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
+    void onNtnSignalStrengthChanged(@NonNull NtnSignalStrength ntnSignalStrength);
+}
diff --git a/telephony/java/android/telephony/satellite/SatelliteManager.java b/telephony/java/android/telephony/satellite/SatelliteManager.java
index 7322aeb..21d93bd 100644
--- a/telephony/java/android/telephony/satellite/SatelliteManager.java
+++ b/telephony/java/android/telephony/satellite/SatelliteManager.java
@@ -35,7 +35,9 @@
 import android.os.RemoteException;
 import android.os.ResultReceiver;
 import android.telephony.SubscriptionManager;
+import android.telephony.TelephonyCallback;
 import android.telephony.TelephonyFrameworkInitializer;
+import android.telephony.TelephonyManager;
 
 import com.android.internal.telephony.IIntegerConsumer;
 import com.android.internal.telephony.ITelephony;
@@ -77,6 +79,8 @@
     private static final ConcurrentHashMap<SatelliteTransmissionUpdateCallback,
             ISatelliteTransmissionUpdateCallback> sSatelliteTransmissionUpdateCallbackMap =
             new ConcurrentHashMap<>();
+    private static final ConcurrentHashMap<NtnSignalStrengthCallback, INtnSignalStrengthCallback>
+            sNtnSignalStrengthCallbackMap = new ConcurrentHashMap<>();
 
     private final int mSubId;
 
@@ -192,6 +196,14 @@
     public static final String KEY_SATELLITE_NEXT_VISIBILITY = "satellite_next_visibility";
 
     /**
+     * Bundle key to get the response from
+     * {@link #requestNtnSignalStrength(Executor, OutcomeReceiver)}.
+     * @hide
+     */
+
+    public static final String KEY_NTN_SIGNAL_STRENGTH = "ntn_signal_strength";
+
+    /**
      * The request was successfully processed.
      */
     @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
@@ -1866,6 +1878,165 @@
         return new HashSet<>();
     }
 
+    /**
+     * Request to get the signal strength of the satellite connection.
+     *
+     * <p>
+     * Note: This API is specifically designed for OEM enabled satellite connectivity only.
+     * For satellite connectivity enabled using carrier roaming, please refer to
+     * {@link android.telephony.TelephonyCallback.SignalStrengthsListener}, and
+     * {@link TelephonyManager#registerTelephonyCallback(Executor, TelephonyCallback)}.
+     * </p>
+     *
+     * @param executor The executor on which the callback will be called.
+     * @param callback The callback object to which the result will be delivered. If the request is
+     * successful, {@link OutcomeReceiver#onResult(Object)} will return an instance of
+     * {@link NtnSignalStrength} with a value of {@link NtnSignalStrength.NtnSignalStrengthLevel}
+     * The {@link NtnSignalStrength#NTN_SIGNAL_STRENGTH_NONE} will be returned if there is no
+     * signal strength data available.
+     * If the request is not successful, {@link OutcomeReceiver#onError(Throwable)} will return a
+     * {@link SatelliteException} with the {@link SatelliteResult}.
+     *
+     * @throws SecurityException if the caller doesn't have required permission.
+     * @throws IllegalStateException if the Telephony process is not currently available or
+     * satellite is not supported.
+     */
+    @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);
+        Objects.requireNonNull(callback);
+
+        try {
+            ITelephony telephony = getITelephony();
+            if (telephony != null) {
+                ResultReceiver receiver = new ResultReceiver(null) {
+                    @Override
+                    protected void onReceiveResult(int resultCode, Bundle resultData) {
+                        if (resultCode == SATELLITE_RESULT_SUCCESS) {
+                            if (resultData.containsKey(KEY_NTN_SIGNAL_STRENGTH)) {
+                                NtnSignalStrength ntnSignalStrength =
+                                        resultData.getParcelable(KEY_NTN_SIGNAL_STRENGTH,
+                                                NtnSignalStrength.class);
+                                executor.execute(() -> Binder.withCleanCallingIdentity(() ->
+                                        callback.onResult(ntnSignalStrength)));
+                            } else {
+                                loge("KEY_NTN_SIGNAL_STRENGTH does not exist.");
+                                executor.execute(() -> Binder.withCleanCallingIdentity(() ->
+                                        callback.onError(new SatelliteException(
+                                                SATELLITE_RESULT_REQUEST_FAILED))));
+                            }
+                        } else {
+                            executor.execute(() -> Binder.withCleanCallingIdentity(() ->
+                                    callback.onError(new SatelliteException(resultCode))));
+                        }
+                    }
+                };
+                telephony.requestNtnSignalStrength(mSubId, receiver);
+            } else {
+                throw new IllegalStateException("Telephony service is null.");
+            }
+        } catch (RemoteException ex) {
+            loge("requestNtnSignalStrength() RemoteException: " + ex);
+            ex.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Registers for NTN signal strength changed from satellite modem.
+     *
+     * <p>
+     * Note: This API is specifically designed for OEM enabled satellite connectivity only.
+     * For satellite connectivity enabled using carrier roaming, please refer to
+     * {@link android.telephony.TelephonyCallback.SignalStrengthsListener}, and
+     * {@link TelephonyManager#registerTelephonyCallback(Executor, TelephonyCallback)}.
+     * </p>
+     *
+     * @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.
+     */
+    @RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION)
+    @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
+    @SatelliteResult public int registerForNtnSignalStrengthChanged(
+            @NonNull @CallbackExecutor Executor executor,
+            @NonNull NtnSignalStrengthCallback callback) {
+        Objects.requireNonNull(executor);
+        Objects.requireNonNull(callback);
+
+        try {
+            ITelephony telephony = getITelephony();
+            if (telephony != null) {
+                INtnSignalStrengthCallback internalCallback =
+                        new INtnSignalStrengthCallback.Stub() {
+                            @Override
+                            public void onNtnSignalStrengthChanged(
+                                    NtnSignalStrength ntnSignalStrength) {
+                                executor.execute(() -> Binder.withCleanCallingIdentity(
+                                        () -> callback.onNtnSignalStrengthChanged(
+                                                ntnSignalStrength)));
+                            }
+                        };
+                sNtnSignalStrengthCallbackMap.put(callback, internalCallback);
+                return telephony.registerForNtnSignalStrengthChanged(mSubId, internalCallback);
+            } else {
+                throw new IllegalStateException("Telephony service is null.");
+            }
+        } catch (RemoteException ex) {
+            loge("registerForNtnSignalStrengthChanged() RemoteException: " + ex);
+            ex.rethrowFromSystemServer();
+        }
+        return SATELLITE_RESULT_REQUEST_FAILED;
+    }
+
+    /**
+     * Unregisters for NTN signal strength changed from satellite modem.
+     * If callback was not registered before, the request will be ignored.
+     *
+     * <p>
+     * Note: This API is specifically designed for OEM enabled satellite connectivity only.
+     * For satellite connectivity enabled using carrier roaming, please refer to
+     * {@link TelephonyManager#unregisterTelephonyCallback(TelephonyCallback)}..
+     * </p>
+     *
+     * @param callback The callback that was passed to
+     * {@link #registerForNtnSignalStrengthChanged(Executor, NtnSignalStrengthCallback)}.
+     *
+     * @throws SecurityException if the caller doesn't have required permission.
+     * @throws IllegalStateException if the Telephony process is not currently available.
+     */
+    @RequiresPermission(Manifest.permission.SATELLITE_COMMUNICATION)
+    @FlaggedApi(Flags.FLAG_OEM_ENABLED_SATELLITE_FLAG)
+    public void unregisterForNtnSignalStrengthChanged(@NonNull NtnSignalStrengthCallback callback) {
+        Objects.requireNonNull(callback);
+        INtnSignalStrengthCallback internalCallback =
+                sNtnSignalStrengthCallbackMap.remove(callback);
+
+        try {
+            ITelephony telephony = getITelephony();
+            if (telephony != null) {
+                if (internalCallback != null) {
+                    telephony.unregisterForNtnSignalStrengthChanged(mSubId, internalCallback);
+                } else {
+                    loge("unregisterForNtnSignalStrengthChanged: No internal callback.");
+                }
+            } else {
+                throw new IllegalStateException("Telephony service is null.");
+            }
+        } catch (RemoteException ex) {
+            loge("unregisterForNtnSignalStrengthChanged() RemoteException: " + ex);
+            ex.rethrowFromSystemServer();
+        }
+
+    }
+
+
     private static ITelephony getITelephony() {
         ITelephony binder = ITelephony.Stub.asInterface(TelephonyFrameworkInitializer
                 .getTelephonyServiceManager()
diff --git a/telephony/java/android/telephony/satellite/stub/INtnSignalStrengthConsumer.aidl b/telephony/java/android/telephony/satellite/stub/INtnSignalStrengthConsumer.aidl
new file mode 100644
index 0000000..b7712bd
--- /dev/null
+++ b/telephony/java/android/telephony/satellite/stub/INtnSignalStrengthConsumer.aidl
@@ -0,0 +1,28 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.telephony.satellite.stub;
+
+import android.telephony.satellite.NtnSignalStrength;
+
+/**
+ * Consumer pattern for a request that receives the signal strength of non-terrestrial network from
+ * the SatelliteService.
+ * @hide
+ */
+oneway interface INtnSignalStrengthConsumer {
+    void accept(in NtnSignalStrength result);
+}
diff --git a/telephony/java/android/telephony/satellite/stub/ISatellite.aidl b/telephony/java/android/telephony/satellite/stub/ISatellite.aidl
index 7fda550..6b47db1 100644
--- a/telephony/java/android/telephony/satellite/stub/ISatellite.aidl
+++ b/telephony/java/android/telephony/satellite/stub/ISatellite.aidl
@@ -16,6 +16,7 @@
 
 package android.telephony.satellite.stub;
 
+import android.telephony.satellite.stub.INtnSignalStrengthConsumer;
 import android.telephony.satellite.stub.ISatelliteCapabilitiesConsumer;
 import android.telephony.satellite.stub.ISatelliteListener;
 import android.telephony.satellite.stub.SatelliteDatagram;
@@ -454,4 +455,44 @@
      */
     void requestIsSatelliteEnabledForCarrier(int simSlot, in IIntegerConsumer resultCallback,
             in IBooleanConsumer callback);
+
+    /**
+     * Request to get the signal strength of the satellite connection.
+     *
+     * @param resultCallback The {@link SatelliteError} result of the operation.
+     * @param callback The callback to handle the NTN signal strength changed event.
+     */
+    void requestSignalStrength(in IIntegerConsumer resultCallback,
+            in INtnSignalStrengthConsumer callback);
+
+    /**
+     * The satellite service should report the NTN signal strength via
+     * ISatelliteListener#onNtnSignalStrengthChanged when the NTN signal strength changes.
+     *
+     * @param resultCallback The callback to receive the error code result of the operation.
+     *
+     * Valid result codes returned:
+     *   SatelliteResult:SATELLITE_RESULT_SUCCESS
+     *   SatelliteResult:SATELLITE_RESULT_SERVICE_ERROR
+     *   SatelliteResult:SATELLITE_RESULT_INVALID_MODEM_STATE
+     *   SatelliteResult:SATELLITE_RESULT_RADIO_NOT_AVAILABLE
+     *   SatelliteResult:SATELLITE_RESULT_REQUEST_NOT_SUPPORTED
+     */
+     void startSendingNtnSignalStrength(in IIntegerConsumer resultCallback);
+
+    /**
+     * The satellite service should stop reporting NTN signal strength to the framework. This will
+     * be called when device is screen off to save power by not letting signal strength updates to
+     * wake up application processor.
+     *
+     * @param resultCallback The callback to receive the error code result of the operation.
+     *
+     * Valid result codes returned:
+     *   SatelliteResult:SATELLITE_RESULT_SUCCESS
+     *   SatelliteResult:SATELLITE_RESULT_SERVICE_ERROR
+     *   SatelliteResult:SATELLITE_RESULT_INVALID_MODEM_STATE
+     *   SatelliteResult:SATELLITE_RESULT_RADIO_NOT_AVAILABLE
+     *   SatelliteResult:SATELLITE_RESULT_REQUEST_NOT_SUPPORTED
+     */
+     void stopSendingNtnSignalStrength(in IIntegerConsumer resultCallback);
 }
diff --git a/telephony/java/android/telephony/satellite/stub/ISatelliteListener.aidl b/telephony/java/android/telephony/satellite/stub/ISatelliteListener.aidl
index d687162..d44ddfa 100644
--- a/telephony/java/android/telephony/satellite/stub/ISatelliteListener.aidl
+++ b/telephony/java/android/telephony/satellite/stub/ISatelliteListener.aidl
@@ -16,6 +16,7 @@
 
 package android.telephony.satellite.stub;
 
+import android.telephony.satellite.stub.NtnSignalStrength;
 import android.telephony.satellite.stub.NTRadioTechnology;
 import android.telephony.satellite.stub.PointingInfo;
 import android.telephony.satellite.stub.SatelliteDatagram;
@@ -58,4 +59,10 @@
      * @param state The current satellite modem state.
      */
     void onSatelliteModemStateChanged(in SatelliteModemState state);
+
+    /**
+     * Called when NTN signal strength changes.
+     * @param ntnSignalStrength The new NTN signal strength.
+     */
+    void onNtnSignalStrengthChanged(in NtnSignalStrength ntnSignalStrength);
 }
diff --git a/telephony/java/android/telephony/satellite/stub/NtnSignalStrength.aidl b/telephony/java/android/telephony/satellite/stub/NtnSignalStrength.aidl
new file mode 100644
index 0000000..f489005
--- /dev/null
+++ b/telephony/java/android/telephony/satellite/stub/NtnSignalStrength.aidl
@@ -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 android.telephony.satellite.stub;
+
+import android.telephony.satellite.stub.NtnSignalStrengthLevel;
+
+/**
+ * @hide
+ */
+parcelable NtnSignalStrength {
+    /**
+     * Non-terrestrial signal strength. The value represents the level of signal strength which can
+     * be translated to the number of signal bars.
+     */
+    NtnSignalStrengthLevel signalStrengthLevel;
+}
diff --git a/telephony/java/android/telephony/satellite/stub/NtnSignalStrengthLevel.aidl b/telephony/java/android/telephony/satellite/stub/NtnSignalStrengthLevel.aidl
new file mode 100644
index 0000000..53b1373
--- /dev/null
+++ b/telephony/java/android/telephony/satellite/stub/NtnSignalStrengthLevel.aidl
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.telephony.satellite.stub;
+
+/**
+ * {@hide}
+ */
+@Backing(type="int")
+enum NtnSignalStrengthLevel {
+    NTN_SIGNAL_STRENGTH_NONE = 0,
+    NTN_SIGNAL_STRENGTH_POOR = 1,
+    NTN_SIGNAL_STRENGTH_MODERATE = 2,
+    NTN_SIGNAL_STRENGTH_GOOD = 3,
+    NTN_SIGNAL_STRENGTH_GREAT = 4
+}
diff --git a/telephony/java/android/telephony/satellite/stub/SatelliteImplBase.java b/telephony/java/android/telephony/satellite/stub/SatelliteImplBase.java
index 4cee01e..a636a61 100644
--- a/telephony/java/android/telephony/satellite/stub/SatelliteImplBase.java
+++ b/telephony/java/android/telephony/satellite/stub/SatelliteImplBase.java
@@ -241,6 +241,30 @@
                     "requestIsSatelliteEnabledForCarrier");
         }
 
+        @Override
+        public void requestSignalStrength(IIntegerConsumer resultCallback,
+                INtnSignalStrengthConsumer callback) throws RemoteException {
+            executeMethodAsync(
+                    () -> SatelliteImplBase.this.requestSignalStrength(resultCallback, callback),
+                    "requestSignalStrength");
+        }
+
+        @Override
+        public void startSendingNtnSignalStrength(IIntegerConsumer resultCallback)
+                throws RemoteException {
+            executeMethodAsync(
+                    () -> SatelliteImplBase.this.startSendingNtnSignalStrength(resultCallback),
+                    "startSendingNtnSignalStrength");
+        }
+
+        @Override
+        public void stopSendingNtnSignalStrength(IIntegerConsumer resultCallback)
+                throws RemoteException {
+            executeMethodAsync(
+                    () -> SatelliteImplBase.this.stopSendingNtnSignalStrength(resultCallback),
+                    "stopSendingNtnSignalStrength");
+        }
+
         // Call the methods with a clean calling identity on the executor and wait indefinitely for
         // the future to return.
         private void executeMethodAsync(Runnable r, String errorLogName) throws RemoteException {
@@ -728,4 +752,35 @@
             @NonNull IIntegerConsumer resultCallback, @NonNull IBooleanConsumer callback) {
         // stub implementation
     }
+
+    /**
+     * Request to get the signal strength of the satellite connection.
+     *
+     * @param resultCallback The {@link SatelliteError} result of the operation.
+     * @param callback The callback to handle the NTN signal strength changed event.
+     */
+    public void requestSignalStrength(@NonNull IIntegerConsumer resultCallback,
+            INtnSignalStrengthConsumer callback) {
+        // stub implementation
+    }
+
+    /**
+     * Requests to deliver signal strength changed events through the
+     * {@link ISatelliteListener#onNtnSignalStrengthChanged(NtnSignalStrength ntnSignalStrength)}
+     * callback.
+     *
+     * @param resultCallback The {@link SatelliteError} result of the operation.
+     */
+    public void startSendingNtnSignalStrength(@NonNull IIntegerConsumer resultCallback) {
+        // stub implementation
+    }
+
+    /**
+     * Requests to stop signal strength changed events
+     *
+     * @param resultCallback The {@link SatelliteError} result of the operation.
+     */
+    public void stopSendingNtnSignalStrength(@NonNull IIntegerConsumer resultCallback){
+        // stub implementation
+    }
 }
diff --git a/telephony/java/com/android/internal/telephony/ITelephony.aidl b/telephony/java/com/android/internal/telephony/ITelephony.aidl
index 3aa5a5a..58e7026 100644
--- a/telephony/java/com/android/internal/telephony/ITelephony.aidl
+++ b/telephony/java/com/android/internal/telephony/ITelephony.aidl
@@ -67,10 +67,12 @@
 import android.telephony.ims.aidl.IImsRegistration;
 import android.telephony.ims.aidl.IImsRegistrationCallback;
 import android.telephony.ims.aidl.IRcsConfigCallback;
+import android.telephony.satellite.INtnSignalStrengthCallback;
 import android.telephony.satellite.ISatelliteDatagramCallback;
 import android.telephony.satellite.ISatelliteTransmissionUpdateCallback;
 import android.telephony.satellite.ISatelliteProvisionStateCallback;
 import android.telephony.satellite.ISatelliteStateCallback;
+import android.telephony.satellite.NtnSignalStrength;
 import android.telephony.satellite.SatelliteCapabilities;
 import android.telephony.satellite.SatelliteDatagram;
 import com.android.ims.internal.IImsServiceFeatureCallback;
@@ -2837,7 +2839,6 @@
             + "android.Manifest.permission.SATELLITE_COMMUNICATION)")
     void deprovisionSatelliteService(int subId, in String token, in IIntegerConsumer callback);
 
-
     /**
      * Registers for provision state changed from satellite modem.
      *
@@ -3071,4 +3072,40 @@
     @JavaPassthrough(annotation="@android.annotation.RequiresPermission("
             + "android.Manifest.permission.SATELLITE_COMMUNICATION)")
     int[] getSatelliteAttachRestrictionReasonsForCarrier(int subId);
+
+    /**
+     * Request to get the signal strength of the satellite connection.
+     *
+     * @param subId The subId of the subscription to request for.
+     * @param receiver Result receiver to get the error code of the request and the current signal
+     * strength of the satellite connection.
+     */
+    @JavaPassthrough(annotation="@android.annotation.RequiresPermission("
+            + "android.Manifest.permission.SATELLITE_COMMUNICATION)")
+    void requestNtnSignalStrength(int subId, in ResultReceiver receiver);
+
+    /**
+     * Registers for NTN signal strength changed from satellite modem.
+     *
+     * @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.
+     */
+    @JavaPassthrough(annotation="@android.annotation.RequiresPermission("
+            + "android.Manifest.permission.SATELLITE_COMMUNICATION)")
+    int registerForNtnSignalStrengthChanged(int subId, in INtnSignalStrengthCallback callback);
+
+    /**
+     * Unregisters for NTN signal strength changed from satellite modem.
+     * If callback was not registered before, the request will be ignored.
+     *
+     * @param subId The subId of the subscription to unregister for provision state changed.
+     * @param callback The callback that was passed to
+     * {@link #registerForNtnSignalStrengthChanged(Executor, NtnSignalStrengthCallback)}.
+     */
+    @JavaPassthrough(annotation="@android.annotation.RequiresPermission("
+            + "android.Manifest.permission.SATELLITE_COMMUNICATION)")
+    void unregisterForNtnSignalStrengthChanged(int subId,
+            in INtnSignalStrengthCallback callback);
 }
diff --git a/telephony/java/com/android/internal/telephony/RILConstants.java b/telephony/java/com/android/internal/telephony/RILConstants.java
index 72e4389..a20f26c 100644
--- a/telephony/java/com/android/internal/telephony/RILConstants.java
+++ b/telephony/java/com/android/internal/telephony/RILConstants.java
@@ -120,31 +120,6 @@
     int BLOCKED_DUE_TO_CALL = 69;                   /* SMS is blocked due to call control */
     int RF_HARDWARE_ISSUE = 70;                     /* RF HW issue is detected */
     int NO_RF_CALIBRATION_INFO = 71;                /* No RF calibration in device */
-    int ENCODING_NOT_SUPPORTED = 72;                /* The encoding scheme is not supported by
-                                                       either the network or the MS. */
-    int FEATURE_NOT_SUPPORTED = 73;                 /* The requesting feature is not supported by
-                                                       the service provider. */
-    int INVALID_CONTACT = 74;                       /* The contact to be added is either not
-                                                       existing or not valid. */
-    int MODEM_INCOMPATIBLE = 75;                    /* The modem of the MS is not compatible with
-                                                       the service provider. */
-    int NETWORK_TIMEOUT = 76;                       /* Modem timeout to receive ACK or response from
-                                                       network after sending a request to it. */
-    int NO_SATELLITE_SIGNAL = 77;                   /* Modem fails to communicate with the satellite
-                                                       network since there is no satellite signal.*/
-    int NOT_SUFFICIENT_ACCOUNT_BALANCE = 78;        /* The request cannot be performed since the
-                                                       subscriber's account balance is not
-                                                       sufficient. */
-    int RADIO_TECHNOLOGY_NOT_SUPPORTED = 79;        /* The radio technology is not supported by the
-                                                       service provider. */
-    int SUBSCRIBER_NOT_AUTHORIZED = 80;             /* The subscription is not authorized to
-                                                       register with the service provider. */
-    int SWITCHED_FROM_SATELLITE_TO_TERRESTRIAL = 81; /* While processing a request from the
-                                                       Framework the satellite modem detects
-                                                       terrestrial signal, aborts the request, and
-                                                       switches to the terrestrial network. */
-    int UNIDENTIFIED_SUBSCRIBER = 82;               /* The subscriber is not registered with the
-                                                       service provider */
 
     // Below is list of OEM specific error codes which can by used by OEMs in case they don't want to
     // reveal particular replacement for Generic failure
@@ -568,22 +543,7 @@
     int RIL_REQUEST_IS_N1_MODE_ENABLED = 242;
     int RIL_REQUEST_SET_LOCATION_PRIVACY_SETTING = 243;
     int RIL_REQUEST_GET_LOCATION_PRIVACY_SETTING = 244;
-    int RIL_REQUEST_GET_SATELLITE_CAPABILITIES = 245;
-    int RIL_REQUEST_SET_SATELLITE_POWER = 246;
-    int RIL_REQUEST_GET_SATELLITE_POWER = 247;
-    int RIL_REQUEST_PROVISION_SATELLITE_SERVICE = 248;
-    int RIL_REQUEST_ADD_ALLOWED_SATELLITE_CONTACTS = 249;
-    int RIL_REQUEST_REMOVE_ALLOWED_SATELLITE_CONTACTS = 250;
-    int RIL_REQUEST_SEND_SATELLITE_MESSAGES = 251;
-    int RIL_REQUEST_GET_PENDING_SATELLITE_MESSAGES = 252;
-    int RIL_REQUEST_GET_SATELLITE_MODE = 253;
-    int RIL_REQUEST_SET_SATELLITE_INDICATION_FILTER = 254;
-    int RIL_REQUEST_START_SENDING_SATELLITE_POINTING_INFO = 255;
-    int RIL_REQUEST_STOP_SENDING_SATELLITE_POINTING_INFO = 256;
-    int RIL_REQUEST_GET_MAX_CHARACTERS_PER_SATELLITE_TEXT_MESSAGE = 257;
-    int RIL_REQUEST_GET_TIME_FOR_NEXT_SATELLITE_VISIBILITY = 258;
-    int RIL_REQUEST_IS_NULL_CIPHER_AND_INTEGRITY_ENABLED = 259;
-    int RIL_REQUEST_SET_SATELLITE_PLMN = 260;
+    int RIL_REQUEST_IS_NULL_CIPHER_AND_INTEGRITY_ENABLED = 245;
 
     /* Responses begin */
     int RIL_RESPONSE_ACKNOWLEDGEMENT = 800;
@@ -645,13 +605,6 @@
     int RIL_UNSOL_RESPONSE_SIM_PHONEBOOK_CHANGED = 1053;
     int RIL_UNSOL_RESPONSE_SIM_PHONEBOOK_RECORDS_RECEIVED = 1054;
     int RIL_UNSOL_SLICING_CONFIG_CHANGED = 1055;
-    int RIL_UNSOL_PENDING_SATELLITE_MESSAGE_COUNT = 1056;
-    int RIL_UNSOL_NEW_SATELLITE_MESSAGES = 1057;
-    int RIL_UNSOL_SATELLITE_MESSAGES_TRANSFER_COMPLETE = 1058;
-    int RIL_UNSOL_SATELLITE_POINTING_INFO_CHANGED = 1059;
-    int RIL_UNSOL_SATELLITE_MODE_CHANGED = 1060;
-    int RIL_UNSOL_SATELLITE_RADIO_TECHNOLOGY_CHANGED = 1061;
-    int RIL_UNSOL_SATELLITE_PROVISION_STATE_CHANGED = 1062;
 
     /* The following unsols are not defined in RIL.h */
     int RIL_UNSOL_HAL_NON_RIL_BASE = 1100;
diff --git a/tools/aapt2/integration-tests/AutoVersionTest/Android.bp b/tools/aapt2/integration-tests/AutoVersionTest/Android.bp
index bfd3508..c901efa 100644
--- a/tools/aapt2/integration-tests/AutoVersionTest/Android.bp
+++ b/tools/aapt2/integration-tests/AutoVersionTest/Android.bp
@@ -26,4 +26,5 @@
 android_test {
     name: "AaptAutoVersionTest",
     sdk_version: "current",
+    use_resource_processor: false,
 }
diff --git a/tools/aapt2/integration-tests/BasicTest/Android.bp b/tools/aapt2/integration-tests/BasicTest/Android.bp
index 7db9d26..d0649ea 100644
--- a/tools/aapt2/integration-tests/BasicTest/Android.bp
+++ b/tools/aapt2/integration-tests/BasicTest/Android.bp
@@ -26,4 +26,5 @@
 android_test {
     name: "AaptBasicTest",
     sdk_version: "current",
+    use_resource_processor: false,
 }
diff --git a/tools/aapt2/integration-tests/StaticLibTest/App/Android.bp b/tools/aapt2/integration-tests/StaticLibTest/App/Android.bp
index 80404ee..ebb4e9f 100644
--- a/tools/aapt2/integration-tests/StaticLibTest/App/Android.bp
+++ b/tools/aapt2/integration-tests/StaticLibTest/App/Android.bp
@@ -24,9 +24,9 @@
 }
 
 android_test {
-
     name: "AaptTestStaticLib_App",
     sdk_version: "current",
+    use_resource_processor: false,
     srcs: ["src/**/*.java"],
     asset_dirs: [
         "assets",
diff --git a/tools/aapt2/integration-tests/StaticLibTest/LibOne/Android.bp b/tools/aapt2/integration-tests/StaticLibTest/LibOne/Android.bp
index a84da43..ee12a929 100644
--- a/tools/aapt2/integration-tests/StaticLibTest/LibOne/Android.bp
+++ b/tools/aapt2/integration-tests/StaticLibTest/LibOne/Android.bp
@@ -26,6 +26,7 @@
 android_library {
     name: "AaptTestStaticLib_LibOne",
     sdk_version: "current",
+    use_resource_processor: false,
     srcs: ["src/**/*.java"],
     resource_dirs: ["res"],
 }
diff --git a/tools/aapt2/integration-tests/StaticLibTest/LibTwo/Android.bp b/tools/aapt2/integration-tests/StaticLibTest/LibTwo/Android.bp
index d386c3a..83b23624 100644
--- a/tools/aapt2/integration-tests/StaticLibTest/LibTwo/Android.bp
+++ b/tools/aapt2/integration-tests/StaticLibTest/LibTwo/Android.bp
@@ -26,6 +26,7 @@
 android_library {
     name: "AaptTestStaticLib_LibTwo",
     sdk_version: "current",
+    use_resource_processor: false,
     srcs: ["src/**/*.java"],
     resource_dirs: ["res"],
     libs: ["AaptTestStaticLib_LibOne"],
diff --git a/tools/aapt2/integration-tests/SymlinkTest/Android.bp b/tools/aapt2/integration-tests/SymlinkTest/Android.bp
index 1e8cf86..15a6a20 100644
--- a/tools/aapt2/integration-tests/SymlinkTest/Android.bp
+++ b/tools/aapt2/integration-tests/SymlinkTest/Android.bp
@@ -26,4 +26,5 @@
 android_test {
     name: "AaptSymlinkTest",
     sdk_version: "current",
+    use_resource_processor: false,
 }