Merge "Detach OomAdjuster rewrite flag" into main
diff --git a/apct-tests/perftests/healthconnect/Android.bp b/apct-tests/perftests/healthconnect/Android.bp
new file mode 100644
index 0000000..c2d0a6f
--- /dev/null
+++ b/apct-tests/perftests/healthconnect/Android.bp
@@ -0,0 +1,44 @@
+// 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 {
+    // See: http://go/android-license-faq
+    // A large-scale-change added 'default_applicable_licenses' to import
+    // all of the 'license_kinds' from "frameworks_base_license"
+    // to get the below license kinds:
+    //   SPDX-license-identifier-Apache-2.0
+    default_applicable_licenses: ["frameworks_base_license"],
+}
+
+android_test {
+    name: "HealthConnectPerfTests",
+
+    srcs: [
+        "src/**/*.java",
+        "src/**/*.kt",
+    ],
+
+    static_libs: [
+        "androidx.test.rules",
+        "androidx.test.ext.junit",
+        "apct-perftests-utils",
+        "collector-device-lib-platform",
+    ],
+
+    libs: ["android.test.base"],
+    platform_apis: true,
+    test_suites: ["device-tests"],
+    data: [":perfetto_artifacts"],
+    certificate: "platform",
+}
diff --git a/apct-tests/perftests/healthconnect/AndroidManifest.xml b/apct-tests/perftests/healthconnect/AndroidManifest.xml
new file mode 100644
index 0000000..6a6370b
--- /dev/null
+++ b/apct-tests/perftests/healthconnect/AndroidManifest.xml
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2023 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+        package="com.android.perftests.healthconnect">
+
+    <application>
+        <uses-library android:name="android.test.runner" />
+    </application>
+
+    <instrumentation android:name="androidx.test.runner.AndroidJUnitRunner"
+            android:targetPackage="com.android.perftests.healthconnect"/>
+</manifest>
diff --git a/apct-tests/perftests/healthconnect/AndroidTest.xml b/apct-tests/perftests/healthconnect/AndroidTest.xml
new file mode 100644
index 0000000..5036202
--- /dev/null
+++ b/apct-tests/perftests/healthconnect/AndroidTest.xml
@@ -0,0 +1,61 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2023 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+<configuration description="Runs HealthConnectPerfTests metric instrumentation.">
+    <option name="test-suite-tag" value="apct" />
+    <option name="test-suite-tag" value="apct-metric-instrumentation" />
+
+    <target_preparer class="com.android.tradefed.targetprep.suite.SuiteApkInstaller">
+        <option name="cleanup-apks" value="true" />
+        <option name="test-file-name" value="HealthConnectPerfTests.apk" />
+    </target_preparer>
+
+    <!-- Needed for pushing the trace config file -->
+    <target_preparer class="com.android.tradefed.targetprep.RootTargetPreparer"/>
+    <target_preparer class="com.android.tradefed.targetprep.PushFilePreparer">
+        <option name="push-file" key="trace_config_detailed.textproto" value="/data/misc/perfetto-traces/trace_config.textproto" />
+    </target_preparer>
+
+    <!-- Needed for pulling the collected trace config on to the host -->
+    <metrics_collector class="com.android.tradefed.device.metric.FilePullerLogCollector">
+        <option name="pull-pattern-keys" value="perfetto_file_path" />
+    </metrics_collector>
+
+    <!-- Needed for storing the perfetto trace files in the sdcard/test_results-->
+    <option name="isolated-storage" value="false" />
+
+    <test class="com.android.tradefed.testtype.AndroidJUnitTest" >
+        <option name="package" value="com.android.perftests.healthconnect" />
+        <option name="hidden-api-checks" value="false"/>
+
+         <!-- Listener related args for collecting the traces and waiting for the device to stabilize. -->
+        <option name="device-listeners" value="android.device.collectors.ProcLoadListener,android.device.collectors.PerfettoListener" />
+        <!-- Guarantee that user defined RunListeners will be running before any of the default listeners defined in this runner. -->
+        <option name="instrumentation-arg" key="newRunListenerMode" value="true" />
+
+        <!-- ProcLoadListener related arguments -->
+        <!-- Wait for device last minute threshold to reach 3 with 2 minute timeout before starting the test run -->
+        <option name="instrumentation-arg" key="procload-collector:per_run" value="true" />
+        <option name="instrumentation-arg" key="proc-loadavg-threshold" value="3" />
+        <option name="instrumentation-arg" key="proc-loadavg-timeout" value="120000" />
+        <option name="instrumentation-arg" key="proc-loadavg-interval" value="10000" />
+
+        <!-- PerfettoListener related arguments -->
+        <option name="instrumentation-arg" key="perfetto_config_text_proto" value="true" />
+        <option name="instrumentation-arg" key="perfetto_config_file" value="trace_config.textproto" />
+
+        <option name="instrumentation-arg" key="newRunListenerMode" value="true" />
+    </test>
+</configuration>
diff --git a/apct-tests/perftests/healthconnect/OWNERS b/apct-tests/perftests/healthconnect/OWNERS
new file mode 100644
index 0000000..da0b46a
--- /dev/null
+++ b/apct-tests/perftests/healthconnect/OWNERS
@@ -0,0 +1,6 @@
+# Bug component: 1219472
+
+arkivanov@google.com
+jstembridge@google.com
+pratyushmore@google.com
+itsleo@google.com
diff --git a/apct-tests/perftests/healthconnect/src/com/android/perftests/healthconnect/HealthConnectReadWritePerfTest.kt b/apct-tests/perftests/healthconnect/src/com/android/perftests/healthconnect/HealthConnectReadWritePerfTest.kt
new file mode 100644
index 0000000..21a4ca0
--- /dev/null
+++ b/apct-tests/perftests/healthconnect/src/com/android/perftests/healthconnect/HealthConnectReadWritePerfTest.kt
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.perftests.healthconnect
+
+import android.health.connect.HealthConnectManager
+import android.os.SystemClock
+import android.perftests.utils.PerfStatusReporter
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.platform.app.InstrumentationRegistry
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+/**
+ * Read/write benchmark tests for [HealthConnectManager]
+ *
+ * Build/Install/Run: atest HealthConnectReadWritePerfTest
+ */
+@RunWith(AndroidJUnit4::class)
+class HealthConnectReadWritePerfTest {
+
+    @get:Rule
+    val perfStatusReporter = PerfStatusReporter()
+
+    private val context by lazy { InstrumentationRegistry.getInstrumentation().context }
+
+    private val manager by lazy {
+        requireNotNull(context.getSystemService(HealthConnectManager::class.java))
+    }
+
+    /**
+     * A first empty test just to setup the test package and make sure it runs properly.
+     */
+    @Test
+    fun placeholder() {
+        val state = perfStatusReporter.benchmarkState
+        while (state.keepRunning()) {
+            SystemClock.sleep(100)
+        }
+    }
+}
\ No newline at end of file
diff --git a/api/Android.bp b/api/Android.bp
index 9029d25..363197a 100644
--- a/api/Android.bp
+++ b/api/Android.bp
@@ -307,10 +307,6 @@
         "framework-protos",
     ],
     flags: [
-        "--api-lint-ignore-prefix android.icu.",
-        "--api-lint-ignore-prefix java.",
-        "--api-lint-ignore-prefix junit.",
-        "--api-lint-ignore-prefix org.",
         "--error NoSettingsProvider",
         "--error UnhiddenSystemApi",
         "--error UnflaggedApi",
diff --git a/core/api/current.txt b/core/api/current.txt
index 008521a..b043503 100644
--- a/core/api/current.txt
+++ b/core/api/current.txt
@@ -9315,10 +9315,14 @@
   public final class StorageStats implements android.os.Parcelable {
     method public int describeContents();
     method public long getAppBytes();
+    method @FlaggedApi("android.app.usage.get_app_bytes_by_data_type_api") public long getAppBytesByDataType(int);
     method public long getCacheBytes();
     method public long getDataBytes();
     method public long getExternalCacheBytes();
     method public void writeToParcel(android.os.Parcel, int);
+    field @FlaggedApi("android.app.usage.get_app_bytes_by_data_type_api") public static final int APP_DATA_TYPE_FILE_TYPE_APK = 0; // 0x0
+    field @FlaggedApi("android.app.usage.get_app_bytes_by_data_type_api") public static final int APP_DATA_TYPE_FILE_TYPE_DM = 1; // 0x1
+    field @FlaggedApi("android.app.usage.get_app_bytes_by_data_type_api") public static final int APP_DATA_TYPE_LIB = 2; // 0x2
     field @NonNull public static final android.os.Parcelable.Creator<android.app.usage.StorageStats> CREATOR;
   }
 
@@ -23972,6 +23976,7 @@
     method @Nullable public android.net.Uri getIconUri();
     method @NonNull public String getId();
     method @NonNull public CharSequence getName();
+    method @FlaggedApi("com.android.media.flags.enable_built_in_speaker_route_suitability_statuses") public int getSuitabilityStatus();
     method public int getType();
     method public int getVolume();
     method public int getVolumeHandling();
@@ -23989,6 +23994,9 @@
     field public static final String FEATURE_REMOTE_VIDEO_PLAYBACK = "android.media.route.feature.REMOTE_VIDEO_PLAYBACK";
     field public static final int PLAYBACK_VOLUME_FIXED = 0; // 0x0
     field public static final int PLAYBACK_VOLUME_VARIABLE = 1; // 0x1
+    field @FlaggedApi("com.android.media.flags.enable_built_in_speaker_route_suitability_statuses") public static final int SUITABILITY_STATUS_NOT_SUITABLE_FOR_TRANSFER = 2; // 0x2
+    field @FlaggedApi("com.android.media.flags.enable_built_in_speaker_route_suitability_statuses") public static final int SUITABILITY_STATUS_SUITABLE_FOR_DEFAULT_TRANSFER = 0; // 0x0
+    field @FlaggedApi("com.android.media.flags.enable_built_in_speaker_route_suitability_statuses") public static final int SUITABILITY_STATUS_SUITABLE_FOR_MANUAL_TRANSFER = 1; // 0x1
     field public static final int TYPE_BLE_HEADSET = 26; // 0x1a
     field public static final int TYPE_BLUETOOTH_A2DP = 8; // 0x8
     field public static final int TYPE_BUILTIN_SPEAKER = 2; // 0x2
@@ -24029,6 +24037,7 @@
     method @NonNull public android.media.MediaRoute2Info.Builder setDescription(@Nullable CharSequence);
     method @NonNull public android.media.MediaRoute2Info.Builder setExtras(@Nullable android.os.Bundle);
     method @NonNull public android.media.MediaRoute2Info.Builder setIconUri(@Nullable android.net.Uri);
+    method @FlaggedApi("com.android.media.flags.enable_built_in_speaker_route_suitability_statuses") @NonNull public android.media.MediaRoute2Info.Builder setSuitabilityStatus(int);
     method @NonNull public android.media.MediaRoute2Info.Builder setType(int);
     method @NonNull public android.media.MediaRoute2Info.Builder setVisibilityPublic();
     method @NonNull public android.media.MediaRoute2Info.Builder setVisibilityRestricted(@NonNull java.util.Set<java.lang.String>);
@@ -24242,6 +24251,7 @@
     method public void release();
     method public void selectRoute(@NonNull android.media.MediaRoute2Info);
     method public void setVolume(int);
+    method @FlaggedApi("com.android.media.flags.enable_built_in_speaker_route_suitability_statuses") public boolean wasTransferRequestedBySelf();
   }
 
   public abstract static class MediaRouter2.TransferCallback {
@@ -24639,12 +24649,16 @@
     method @Nullable public CharSequence getName();
     method @NonNull public java.util.List<java.lang.String> getSelectableRoutes();
     method @NonNull public java.util.List<java.lang.String> getSelectedRoutes();
+    method @FlaggedApi("com.android.media.flags.enable_built_in_speaker_route_suitability_statuses") public int getTransferReason();
     method @NonNull public java.util.List<java.lang.String> getTransferableRoutes();
     method public int getVolume();
     method public int getVolumeHandling();
     method public int getVolumeMax();
     method public void writeToParcel(@NonNull android.os.Parcel, int);
     field @NonNull public static final android.os.Parcelable.Creator<android.media.RoutingSessionInfo> CREATOR;
+    field @FlaggedApi("com.android.media.flags.enable_built_in_speaker_route_suitability_statuses") public static final int TRANSFER_REASON_APP = 2; // 0x2
+    field @FlaggedApi("com.android.media.flags.enable_built_in_speaker_route_suitability_statuses") public static final int TRANSFER_REASON_FALLBACK = 0; // 0x0
+    field @FlaggedApi("com.android.media.flags.enable_built_in_speaker_route_suitability_statuses") public static final int TRANSFER_REASON_SYSTEM_REQUEST = 1; // 0x1
   }
 
   public static final class RoutingSessionInfo.Builder {
@@ -24665,6 +24679,8 @@
     method @NonNull public android.media.RoutingSessionInfo.Builder removeTransferableRoute(@NonNull String);
     method @NonNull public android.media.RoutingSessionInfo.Builder setControlHints(@Nullable android.os.Bundle);
     method @NonNull public android.media.RoutingSessionInfo.Builder setName(@Nullable CharSequence);
+    method @FlaggedApi("com.android.media.flags.enable_built_in_speaker_route_suitability_statuses") @NonNull public android.media.RoutingSessionInfo.Builder setTransferInitiator(@Nullable android.os.UserHandle, @Nullable String);
+    method @FlaggedApi("com.android.media.flags.enable_built_in_speaker_route_suitability_statuses") @NonNull public android.media.RoutingSessionInfo.Builder setTransferReason(int);
     method @NonNull public android.media.RoutingSessionInfo.Builder setVolume(int);
     method @NonNull public android.media.RoutingSessionInfo.Builder setVolumeHandling(int);
     method @NonNull public android.media.RoutingSessionInfo.Builder setVolumeMax(int);
@@ -43277,6 +43293,7 @@
     field public static final String KEY_CDMA_NONROAMING_NETWORKS_STRING_ARRAY = "cdma_nonroaming_networks_string_array";
     field public static final String KEY_CDMA_ROAMING_MODE_INT = "cdma_roaming_mode_int";
     field public static final String KEY_CDMA_ROAMING_NETWORKS_STRING_ARRAY = "cdma_roaming_networks_string_array";
+    field @FlaggedApi("com.android.internal.telephony.flags.data_only_cellular_service") public static final String KEY_CELLULAR_SERVICE_CAPABILITIES_INT_ARRAY = "cellular_service_capabilities_int_array";
     field public static final String KEY_CELLULAR_USAGE_SETTING_INT = "cellular_usage_setting_int";
     field public static final String KEY_CHECK_PRICING_WITH_CARRIER_FOR_DATA_ROAMING_BOOL = "check_pricing_with_carrier_data_roaming_bool";
     field public static final String KEY_CI_ACTION_ON_SYS_UPDATE_BOOL = "ci_action_on_sys_update_bool";
@@ -45272,6 +45289,7 @@
     method @Nullable public String getMncString();
     method @Deprecated public String getNumber();
     method public int getPortIndex();
+    method @FlaggedApi("com.android.internal.telephony.flags.data_only_cellular_service") @NonNull public java.util.Set<java.lang.Integer> getServiceCapabilities();
     method public int getSimSlotIndex();
     method public int getSubscriptionId();
     method public int getSubscriptionType();
@@ -45353,6 +45371,9 @@
     field public static final int PHONE_NUMBER_SOURCE_CARRIER = 2; // 0x2
     field public static final int PHONE_NUMBER_SOURCE_IMS = 3; // 0x3
     field public static final int PHONE_NUMBER_SOURCE_UICC = 1; // 0x1
+    field @FlaggedApi("com.android.internal.telephony.flags.data_only_cellular_service") public static final int SERVICE_CAPABILITY_DATA = 3; // 0x3
+    field @FlaggedApi("com.android.internal.telephony.flags.data_only_cellular_service") public static final int SERVICE_CAPABILITY_SMS = 2; // 0x2
+    field @FlaggedApi("com.android.internal.telephony.flags.data_only_cellular_service") public static final int SERVICE_CAPABILITY_VOICE = 1; // 0x1
     field public static final int SUBSCRIPTION_TYPE_LOCAL_SIM = 0; // 0x0
     field public static final int SUBSCRIPTION_TYPE_REMOTE_SIM = 1; // 0x1
     field public static final int USAGE_SETTING_DATA_CENTRIC = 2; // 0x2
@@ -45601,6 +45622,8 @@
     method @RequiresPermission(anyOf={android.Manifest.permission.ACCESS_NETWORK_STATE, android.Manifest.permission.MODIFY_PHONE_STATE, android.Manifest.permission.READ_PHONE_STATE, android.Manifest.permission.READ_BASIC_PHONE_STATE}) public boolean isDataEnabled();
     method @RequiresPermission(anyOf={android.Manifest.permission.ACCESS_NETWORK_STATE, android.Manifest.permission.READ_PHONE_STATE, android.Manifest.permission.MODIFY_PHONE_STATE, android.Manifest.permission.READ_BASIC_PHONE_STATE}) public boolean isDataEnabledForReason(int);
     method @RequiresPermission(anyOf={android.Manifest.permission.ACCESS_NETWORK_STATE, android.Manifest.permission.READ_PHONE_STATE, android.Manifest.permission.READ_BASIC_PHONE_STATE}) public boolean isDataRoamingEnabled();
+    method @FlaggedApi("com.android.internal.telephony.flags.data_only_cellular_service") public boolean isDeviceSmsCapable();
+    method @FlaggedApi("com.android.internal.telephony.flags.data_only_cellular_service") public boolean isDeviceVoiceCapable();
     method public boolean isEmergencyNumber(@NonNull String);
     method public boolean isHearingAidCompatibilitySupported();
     method @RequiresPermission(anyOf={android.Manifest.permission.READ_PRECISE_PHONE_STATE, "android.permission.READ_PRIVILEGED_PHONE_STATE"}) public boolean isManualNetworkSelectionAllowed();
@@ -45610,9 +45633,9 @@
     method @RequiresPermission(android.Manifest.permission.READ_BASIC_PHONE_STATE) public boolean isPremiumCapabilityAvailableForPurchase(int);
     method public boolean isRadioInterfaceCapabilitySupported(@NonNull String);
     method public boolean isRttSupported();
-    method public boolean isSmsCapable();
+    method @Deprecated public boolean isSmsCapable();
     method @Deprecated public boolean isTtyModeSupported();
-    method public boolean isVoiceCapable();
+    method @Deprecated public boolean isVoiceCapable();
     method public boolean isVoicemailVibrationEnabled(android.telecom.PhoneAccountHandle);
     method public boolean isWorldPhone();
     method @Deprecated public void listen(android.telephony.PhoneStateListener, int);
@@ -53525,6 +53548,7 @@
     method public android.transition.Transition getExitTransition();
     method protected final int getFeatures();
     method protected final int getForcedWindowFlags();
+    method @FlaggedApi("android.view.flags.toolkit_set_frame_rate_read_only") public boolean getFrameRateBoostOnTouchEnabled();
     method @Nullable public android.view.WindowInsetsController getInsetsController();
     method @NonNull public abstract android.view.LayoutInflater getLayoutInflater();
     method protected final int getLocalFeatures();
@@ -53602,6 +53626,7 @@
     method public abstract void setFeatureInt(int, int);
     method public void setFlags(int, int);
     method public void setFormat(int);
+    method @FlaggedApi("android.view.flags.toolkit_set_frame_rate_read_only") public void setFrameRateBoostOnTouchEnabled(boolean);
     method public void setGravity(int);
     method @RequiresPermission(android.Manifest.permission.HIDE_OVERLAY_WINDOWS) public final void setHideOverlayWindows(boolean);
     method public void setIcon(@DrawableRes int);
@@ -53946,6 +53971,7 @@
     method @FlaggedApi("com.android.graphics.hwui.flags.limited_hdr") public float getDesiredHdrHeadroom();
     method public int getFitInsetsSides();
     method public int getFitInsetsTypes();
+    method @FlaggedApi("android.view.flags.toolkit_set_frame_rate_read_only") public boolean getFrameRateBoostOnTouchEnabled();
     method public final CharSequence getTitle();
     method public boolean isFitInsetsIgnoringVisibility();
     method public boolean isHdrConversionEnabled();
@@ -53957,6 +53983,7 @@
     method public void setFitInsetsIgnoringVisibility(boolean);
     method public void setFitInsetsSides(int);
     method public void setFitInsetsTypes(int);
+    method @FlaggedApi("android.view.flags.toolkit_set_frame_rate_read_only") public void setFrameRateBoostOnTouchEnabled(boolean);
     method public void setHdrConversionEnabled(boolean);
     method public final void setTitle(CharSequence);
     method public void setWallpaperTouchEventsEnabled(boolean);
diff --git a/core/java/android/app/OWNERS b/core/java/android/app/OWNERS
index 772b0b4..47d19ed 100644
--- a/core/java/android/app/OWNERS
+++ b/core/java/android/app/OWNERS
@@ -88,6 +88,10 @@
 # Pinner
 per-file pinner-client.aconfig = file:/core/java/android/app/pinner/OWNERS
 
+# BackgroundInstallControlManager
+per-file BackgroundInstallControlManager.java = file:/services/core/java/com/android/server/pm/OWNERS
+per-file background_install_control_manager.aconfig = file:/services/core/java/com/android/server/pm/OWNERS
+
 # ResourcesManager
 per-file ResourcesManager.java = file:RESOURCES_OWNERS
 
diff --git a/core/java/android/app/usage/StorageStats.java b/core/java/android/app/usage/StorageStats.java
index 8d25d7b..87d97d5 100644
--- a/core/java/android/app/usage/StorageStats.java
+++ b/core/java/android/app/usage/StorageStats.java
@@ -17,11 +17,16 @@
 package android.app.usage;
 
 import android.annotation.BytesLong;
+import android.annotation.FlaggedApi;
+import android.annotation.IntDef;
 import android.content.Context;
 import android.os.Parcel;
 import android.os.Parcelable;
 import android.os.UserHandle;
 
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
 /**
  * Storage statistics for a UID, package, or {@link UserHandle} on a single
  * storage volume.
@@ -29,10 +34,47 @@
  * @see StorageStatsManager
  */
 public final class StorageStats implements Parcelable {
-    /** {@hide} */ public long codeBytes;
-    /** {@hide} */ public long dataBytes;
-    /** {@hide} */ public long cacheBytes;
-    /** {@hide} */ public long externalCacheBytes;
+    /** @hide */ public long codeBytes;
+    /** @hide */ public long dataBytes;
+    /** @hide */ public long cacheBytes;
+    /** @hide */ public long apkBytes;
+    /** @hide */ public long libBytes;
+    /** @hide */ public long dmBytes;
+    /** @hide */ public long externalCacheBytes;
+
+    /** Represents all .apk files in application code path.
+     * Can be used as an input to {@link #getAppBytesByDataType(int)}
+     * to get the sum of sizes for files of this type.
+     */
+    @FlaggedApi(Flags.FLAG_GET_APP_BYTES_BY_DATA_TYPE_API)
+    public static final int APP_DATA_TYPE_FILE_TYPE_APK = 0;
+
+    /** Represents all .dm files in application code path.
+     * Can be used as an input to {@link #getAppBytesByDataType(int)}
+     * to get the sum of sizes for files of this type.
+     */
+    @FlaggedApi(Flags.FLAG_GET_APP_BYTES_BY_DATA_TYPE_API)
+    public static final int APP_DATA_TYPE_FILE_TYPE_DM = 1;
+
+    /** Represents lib/ in application code path.
+     * Can be used as an input to {@link #getAppBytesByDataType(int)}
+     * to get the size of lib/ directory.
+     */
+    @FlaggedApi(Flags.FLAG_GET_APP_BYTES_BY_DATA_TYPE_API)
+    public static final int APP_DATA_TYPE_LIB = 2;
+
+    /**
+     * Keep in sync with the file types defined above.
+     * @hide
+     */
+    @FlaggedApi(Flags.FLAG_GET_APP_BYTES_BY_DATA_TYPE_API)
+    @IntDef(flag = false, value = {
+        APP_DATA_TYPE_FILE_TYPE_APK,
+        APP_DATA_TYPE_FILE_TYPE_DM,
+        APP_DATA_TYPE_LIB,
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface AppDataType {}
 
     /**
      * Return the size of app. This includes {@code APK} files, optimized
@@ -48,6 +90,27 @@
     }
 
     /**
+     * Return the size of the specified data type. This includes files stored under
+     * application code path.
+     * <p>
+     * If there is more than one package inside a uid, the return represents the aggregated
+     * stats when query StorageStat for package or uid.
+     * The data  is not collected and the return defaults to 0 when query StorageStats for user.
+     *
+     * <p>
+     * Data is isolated for each user on a multiuser device.
+     */
+    @FlaggedApi(Flags.FLAG_GET_APP_BYTES_BY_DATA_TYPE_API)
+    public long getAppBytesByDataType(@AppDataType int dataType) {
+        switch (dataType) {
+          case APP_DATA_TYPE_FILE_TYPE_APK: return apkBytes;
+          case APP_DATA_TYPE_LIB: return libBytes;
+          case APP_DATA_TYPE_FILE_TYPE_DM: return dmBytes;
+          default: return 0;
+        }
+    }
+
+    /**
      * Return the size of all data. This includes files stored under
      * {@link Context#getDataDir()}, {@link Context#getCacheDir()},
      * {@link Context#getCodeCacheDir()}.
@@ -98,6 +161,9 @@
         this.codeBytes = in.readLong();
         this.dataBytes = in.readLong();
         this.cacheBytes = in.readLong();
+        this.apkBytes = in.readLong();
+        this.libBytes = in.readLong();
+        this.dmBytes = in.readLong();
         this.externalCacheBytes = in.readLong();
     }
 
@@ -111,6 +177,9 @@
         dest.writeLong(codeBytes);
         dest.writeLong(dataBytes);
         dest.writeLong(cacheBytes);
+        dest.writeLong(apkBytes);
+        dest.writeLong(libBytes);
+        dest.writeLong(dmBytes);
         dest.writeLong(externalCacheBytes);
     }
 
diff --git a/core/java/android/app/usage/flags.aconfig b/core/java/android/app/usage/flags.aconfig
index a611255..4d9d911 100644
--- a/core/java/android/app/usage/flags.aconfig
+++ b/core/java/android/app/usage/flags.aconfig
@@ -35,3 +35,10 @@
     description: " Feature flag to support filter based event query API"
     bug: "194321117"
 }
+
+flag {
+    name: "get_app_bytes_by_data_type_api"
+    namespace: "system_performance"
+    description: "Feature flag for collecting app data size by file type API"
+    bug: "294088945"
+}
diff --git a/core/java/android/companion/CompanionDeviceManager.java b/core/java/android/companion/CompanionDeviceManager.java
index b11840e..879656a 100644
--- a/core/java/android/companion/CompanionDeviceManager.java
+++ b/core/java/android/companion/CompanionDeviceManager.java
@@ -220,6 +220,12 @@
      */
     public static final int MESSAGE_REQUEST_PING = 0x63807378; // ?PIN
     /**
+     * Test message type without a response.
+     *
+     * @hide
+     */
+    public static final int MESSAGE_ONEWAY_PING = 0x43807378; // +PIN
+    /**
      * Message header assigned to the remote authentication handshakes.
      *
      * @hide
@@ -237,6 +243,18 @@
      * @hide
      */
     public static final int MESSAGE_REQUEST_PERMISSION_RESTORE = 0x63826983; // ?RES
+    /**
+     * Message header assigned to the one-way message sent from the wearable device.
+     *
+     * @hide
+     */
+    public static final int MESSAGE_ONEWAY_FROM_WEARABLE = 0x43708287; // +FRW
+    /**
+     * Message header assigned to the one-way message sent to the wearable device.
+     *
+     * @hide
+     */
+    public static final int MESSAGE_ONEWAY_TO_WEARABLE = 0x43847987; // +TOW
 
     /**
      * The length limit of Association tag.
diff --git a/core/java/android/content/pm/PackageStats.java b/core/java/android/content/pm/PackageStats.java
index 81cfc07..b919c4b 100644
--- a/core/java/android/content/pm/PackageStats.java
+++ b/core/java/android/content/pm/PackageStats.java
@@ -55,6 +55,18 @@
     /** Size of cache used by the application. (e.g., /data/data/<app>/cache) */
     public long cacheSize;
 
+    /** Size of .apk files of the application. */
+    /** @hide */
+    public long apkSize;
+
+    /** Size of the libraries of the application. */
+    /** @hide */
+    public long libSize;
+
+    /** Size of the .dm files of the application. */
+    /** @hide */
+    public long dmSize;
+
     /**
      * Size of the secure container on external storage holding the
      * application's code.
@@ -108,6 +120,18 @@
             sb.append(" cache=");
             sb.append(cacheSize);
         }
+        if (apkSize != 0) {
+            sb.append(" apk=");
+            sb.append(apkSize);
+        }
+        if (libSize != 0) {
+            sb.append(" lib=");
+            sb.append(libSize);
+        }
+        if (dmSize != 0) {
+            sb.append(" dm=");
+            sb.append(dmSize);
+        }
         if (externalCodeSize != 0) {
             sb.append(" extCode=");
             sb.append(externalCodeSize);
@@ -149,6 +173,9 @@
         codeSize = source.readLong();
         dataSize = source.readLong();
         cacheSize = source.readLong();
+        apkSize = source.readLong();
+        libSize = source.readLong();
+        dmSize = source.readLong();
         externalCodeSize = source.readLong();
         externalDataSize = source.readLong();
         externalCacheSize = source.readLong();
@@ -162,6 +189,9 @@
         codeSize = pStats.codeSize;
         dataSize = pStats.dataSize;
         cacheSize = pStats.cacheSize;
+        apkSize = pStats.apkSize;
+        libSize = pStats.libSize;
+        dmSize = pStats.dmSize;
         externalCodeSize = pStats.externalCodeSize;
         externalDataSize = pStats.externalDataSize;
         externalCacheSize = pStats.externalCacheSize;
@@ -179,6 +209,9 @@
         dest.writeLong(codeSize);
         dest.writeLong(dataSize);
         dest.writeLong(cacheSize);
+        dest.writeLong(apkSize);
+        dest.writeLong(libSize);
+        dest.writeLong(dmSize);
         dest.writeLong(externalCodeSize);
         dest.writeLong(externalDataSize);
         dest.writeLong(externalCacheSize);
@@ -198,6 +231,9 @@
                 && codeSize == otherStats.codeSize
                 && dataSize == otherStats.dataSize
                 && cacheSize == otherStats.cacheSize
+                && apkSize == otherStats.apkSize
+                && libSize == otherStats.libSize
+                && dmSize == otherStats.dmSize
                 && externalCodeSize == otherStats.externalCodeSize
                 && externalDataSize == otherStats.externalDataSize
                 && externalCacheSize == otherStats.externalCacheSize
@@ -208,7 +244,8 @@
     @Override
     public int hashCode() {
         return Objects.hash(packageName, userHandle, codeSize, dataSize,
-                cacheSize, externalCodeSize, externalDataSize, externalCacheSize, externalMediaSize,
+                apkSize, libSize, dmSize, cacheSize, externalCodeSize,
+                externalDataSize, externalCacheSize, externalMediaSize,
                 externalObbSize);
     }
 
diff --git a/core/java/android/hardware/display/DisplayManagerInternal.java b/core/java/android/hardware/display/DisplayManagerInternal.java
index ffd7212..86024ea 100644
--- a/core/java/android/hardware/display/DisplayManagerInternal.java
+++ b/core/java/android/hardware/display/DisplayManagerInternal.java
@@ -751,6 +751,13 @@
          */
         boolean blockScreenOn(Runnable unblocker);
 
+        /** Get the current brightness levels used to determine automatic brightness based on lux
+         * levels. */
+        float[] getCurrentAutoBrightnessLevels();
+
+        /** Get the current lux levels used to determine automatic brightness. */
+        float[] getCurrentAutoBrightnessLuxLevels();
+
         /** Returns whether displayoffload supports the given display state. */
         static boolean isSupportedOffloadState(int displayState) {
             return Display.isSuspendedState(displayState);
diff --git a/core/java/android/provider/Telephony.java b/core/java/android/provider/Telephony.java
index 72c436e..e4af2da 100644
--- a/core/java/android/provider/Telephony.java
+++ b/core/java/android/provider/Telephony.java
@@ -4940,6 +4940,13 @@
          */
         public static final String COLUMN_IS_NTN = "is_ntn";
 
+        /**
+         * TelephonyProvider column name to indicate the service capability bitmasks.
+         *
+         * @hide
+         */
+        public static final String COLUMN_SERVICE_CAPABILITIES = "service_capabilities";
+
         /** All columns in {@link SimInfo} table. */
         private static final List<String> ALL_COLUMNS = List.of(
                 COLUMN_UNIQUE_KEY_SUBSCRIPTION_ID,
@@ -5011,7 +5018,8 @@
                 COLUMN_USER_HANDLE,
                 COLUMN_SATELLITE_ENABLED,
                 COLUMN_SATELLITE_ATTACH_ENABLED_FOR_CARRIER,
-                COLUMN_IS_NTN
+                COLUMN_IS_NTN,
+                COLUMN_SERVICE_CAPABILITIES
         );
 
         /**
diff --git a/core/java/android/view/SurfaceControl.java b/core/java/android/view/SurfaceControl.java
index cbbe785..b957b31 100644
--- a/core/java/android/view/SurfaceControl.java
+++ b/core/java/android/view/SurfaceControl.java
@@ -22,7 +22,6 @@
 import static android.graphics.Matrix.MSKEW_Y;
 import static android.graphics.Matrix.MTRANS_X;
 import static android.graphics.Matrix.MTRANS_Y;
-import static android.view.Display.INVALID_DISPLAY;
 import static android.view.SurfaceControlProto.HASH_CODE;
 import static android.view.SurfaceControlProto.LAYER_ID;
 import static android.view.SurfaceControlProto.NAME;
@@ -38,7 +37,6 @@
 import android.annotation.Size;
 import android.annotation.TestApi;
 import android.compat.annotation.UnsupportedAppUsage;
-import android.content.Context;
 import android.graphics.ColorSpace;
 import android.graphics.GraphicBuffer;
 import android.graphics.Matrix;
@@ -53,13 +51,8 @@
 import android.hardware.OverlayProperties;
 import android.hardware.SyncFence;
 import android.hardware.display.DeviceProductInfo;
-import android.hardware.display.DisplayManager;
-import android.hardware.display.DisplayManagerGlobal;
 import android.hardware.display.DisplayedContentSample;
 import android.hardware.display.DisplayedContentSamplingAttributes;
-import android.hardware.display.IDisplayManager;
-import android.hardware.display.IVirtualDisplayCallback;
-import android.hardware.display.VirtualDisplay;
 import android.hardware.graphics.common.DisplayDecorationSupport;
 import android.opengl.EGLDisplay;
 import android.opengl.EGLSync;
@@ -68,8 +61,6 @@
 import android.os.Looper;
 import android.os.Parcel;
 import android.os.Parcelable;
-import android.os.RemoteException;
-import android.os.ServiceManager;
 import android.util.ArrayMap;
 import android.util.Log;
 import android.util.Slog;
@@ -2355,92 +2346,6 @@
     }
 
     /**
-     * Because this API is now going through {@link DisplayManager}, orientation and displayRect
-     * will automatically be computed based on configuration changes. Because of this, the params
-     * orientation and displayRect are ignored
-     *
-     * @hide
-     */
-    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.TIRAMISU,
-            publicAlternatives = "Use {@code VirtualDisplay#resize(int, int, int)} instead.",
-            trackingBug = 247078497)
-    public static void setDisplayProjection(IBinder displayToken, int orientation,
-            Rect layerStackRect, Rect displayRect) {
-        DisplayManagerGlobal.getInstance().resizeVirtualDisplay(
-                IVirtualDisplayCallback.Stub.asInterface(displayToken), layerStackRect.width(),
-                layerStackRect.height(), 1);
-    }
-
-    /**
-     * @hide
-     */
-    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.TIRAMISU,
-            publicAlternatives = "Use {@code MediaProjection#createVirtualDisplay()} with flag "
-                    + " {@code VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR} for mirroring instead.",
-            trackingBug = 247078497)
-    public static void setDisplayLayerStack(IBinder displayToken, int layerStack) {
-        IBinder b = ServiceManager.getService(Context.DISPLAY_SERVICE);
-        if (b == null) {
-            throw new UnsupportedOperationException();
-        }
-
-        IDisplayManager dm = IDisplayManager.Stub.asInterface(b);
-        try {
-            dm.setDisplayIdToMirror(displayToken, layerStack);
-        } catch (RemoteException e) {
-            throw new UnsupportedOperationException(e);
-        }
-    }
-
-    /**
-     * @hide
-     */
-    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.TIRAMISU,
-            publicAlternatives = "Use {@code VirtualDisplay#setSurface(Surface)} instead.",
-            trackingBug = 247078497)
-    public static void setDisplaySurface(IBinder displayToken, Surface surface) {
-        IVirtualDisplayCallback virtualDisplayCallback =
-                IVirtualDisplayCallback.Stub.asInterface(displayToken);
-        DisplayManagerGlobal dm = DisplayManagerGlobal.getInstance();
-        dm.setVirtualDisplaySurface(virtualDisplayCallback, surface);
-    }
-
-    /**
-     * Secure is no longer supported because this is only called from outside system which cannot
-     * create secure displays.
-     * @hide
-     */
-    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.TIRAMISU,
-            publicAlternatives = "Use {@code MediaProjection#createVirtualDisplay()} or "
-                    + "{@code DisplayManager#createVirtualDisplay()} instead.",
-            trackingBug = 247078497)
-    public static IBinder createDisplay(String name, boolean secure) {
-        if (name == null) {
-            throw new IllegalArgumentException("name must not be null");
-        }
-
-        // We don't have a size yet so pass in 1 for width and height since 0 is invalid
-        VirtualDisplay vd = DisplayManager.createVirtualDisplay(name, 1 /* width */, 1 /* height */,
-                INVALID_DISPLAY, null /* Surface */);
-        return vd == null ? null : vd.getToken().asBinder();
-    }
-
-    /**
-     * @hide
-     */
-    @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.TIRAMISU,
-            publicAlternatives = "Use {@code VirtualDisplay#release()} instead.",
-            trackingBug = 247078497)
-    public static void destroyDisplay(IBinder displayToken) {
-        if (displayToken == null) {
-            throw new IllegalArgumentException("displayToken must not be null");
-        }
-
-        DisplayManagerGlobal.getInstance().releaseVirtualDisplay(
-                IVirtualDisplayCallback.Stub.asInterface(displayToken));
-    }
-
-    /**
      * Returns whether protected content is supported in GPU composition.
      * @hide
      */
diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java
index cbafd1c..ec99459 100644
--- a/core/java/android/view/View.java
+++ b/core/java/android/view/View.java
@@ -32,6 +32,7 @@
 import static android.view.displayhash.DisplayHashResultCallback.EXTRA_DISPLAY_HASH_ERROR_CODE;
 import static android.view.flags.Flags.FLAG_TOOLKIT_SET_FRAME_RATE_READ_ONLY;
 import static android.view.flags.Flags.FLAG_VIEW_VELOCITY_API;
+import static android.view.flags.Flags.toolkitMetricsForFrameRateDecision;
 import static android.view.flags.Flags.toolkitSetFrameRateReadOnly;
 import static android.view.flags.Flags.viewVelocityApi;
 import static android.view.inputmethod.Flags.FLAG_HOME_SCREEN_HANDWRITING_DELEGATOR;
@@ -2309,6 +2310,7 @@
     protected static final int[] PRESSED_ENABLED_FOCUSED_SELECTED_WINDOW_FOCUSED_STATE_SET;
 
     private static boolean sToolkitSetFrameRateReadOnlyFlagValue;
+    private static boolean sToolkitMetricsForFrameRateDecisionFlagValue;
 
     static {
         EMPTY_STATE_SET = StateSet.get(0);
@@ -2393,6 +2395,7 @@
                         | StateSet.VIEW_STATE_PRESSED);
 
         sToolkitSetFrameRateReadOnlyFlagValue = toolkitSetFrameRateReadOnly();
+        sToolkitMetricsForFrameRateDecisionFlagValue = toolkitMetricsForFrameRateDecision();
     }
 
     /**
@@ -33056,7 +33059,10 @@
     }
 
     private float getSizePercentage() {
-        if (mResources == null || getVisibility() != VISIBLE) {
+        float alpha = mTransformationInfo != null ? mTransformationInfo.mAlpha : 1;
+        int visibility = mViewFlags & VISIBILITY_MASK;
+
+        if (mResources == null || alpha == 0 || visibility != VISIBLE) {
             return 0;
         }
 
@@ -33084,22 +33090,26 @@
         ViewRootImpl viewRootImpl = getViewRootImpl();
         float sizePercentage = getSizePercentage();
         int frameRateCateogry = calculateFrameRateCategory(sizePercentage);
-        if (sToolkitSetFrameRateReadOnlyFlagValue && viewRootImpl != null
-                && sizePercentage > 0) {
-            if (mPreferredFrameRate < 0) {
-                if (mPreferredFrameRate == REQUESTED_FRAME_RATE_CATEGORY_NO_PREFERENCE) {
-                    frameRateCateogry = FRAME_RATE_CATEGORY_NO_PREFERENCE;
-                } else if (mPreferredFrameRate == REQUESTED_FRAME_RATE_CATEGORY_LOW) {
-                    frameRateCateogry = FRAME_RATE_CATEGORY_LOW;
-                } else if (mPreferredFrameRate == REQUESTED_FRAME_RATE_CATEGORY_NORMAL) {
-                    frameRateCateogry = FRAME_RATE_CATEGORY_NORMAL;
-                } else if (mPreferredFrameRate == REQUESTED_FRAME_RATE_CATEGORY_HIGH) {
-                    frameRateCateogry = FRAME_RATE_CATEGORY_HIGH;
+        if (viewRootImpl != null && sizePercentage > 0) {
+            if (sToolkitSetFrameRateReadOnlyFlagValue) {
+                if (mPreferredFrameRate < 0) {
+                    if (mPreferredFrameRate == REQUESTED_FRAME_RATE_CATEGORY_NO_PREFERENCE) {
+                        frameRateCateogry = FRAME_RATE_CATEGORY_NO_PREFERENCE;
+                    } else if (mPreferredFrameRate == REQUESTED_FRAME_RATE_CATEGORY_LOW) {
+                        frameRateCateogry = FRAME_RATE_CATEGORY_LOW;
+                    } else if (mPreferredFrameRate == REQUESTED_FRAME_RATE_CATEGORY_NORMAL) {
+                        frameRateCateogry = FRAME_RATE_CATEGORY_NORMAL;
+                    } else if (mPreferredFrameRate == REQUESTED_FRAME_RATE_CATEGORY_HIGH) {
+                        frameRateCateogry = FRAME_RATE_CATEGORY_HIGH;
+                    }
+                } else {
+                    viewRootImpl.votePreferredFrameRate(mPreferredFrameRate);
                 }
-            } else {
-                viewRootImpl.votePreferredFrameRate(mPreferredFrameRate);
+                viewRootImpl.votePreferredFrameRateCategory(frameRateCateogry);
             }
-            viewRootImpl.votePreferredFrameRateCategory(frameRateCateogry);
+            if (sToolkitMetricsForFrameRateDecisionFlagValue) {
+                viewRootImpl.recordViewPercentage(sizePercentage);
+            }
         }
     }
 
diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java
index 9f6395e..487b15c 100644
--- a/core/java/android/view/ViewRootImpl.java
+++ b/core/java/android/view/ViewRootImpl.java
@@ -252,7 +252,6 @@
 import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.Executor;
 import java.util.function.Predicate;
-
 /**
  * The top of a view hierarchy, implementing the needed protocol between View
  * and the WindowManager.  This is for the most part an internal implementation
@@ -828,6 +827,8 @@
     private boolean mInsetsAnimationRunning;
 
     private long mPreviousFrameDrawnTime = -1;
+    // The largest view size percentage to the display size. Used on trace to collect metric.
+    private float mLargestChildPercentage = 0.0f;
 
     /**
      * The resolved pointer icon type requested by this window.
@@ -1067,6 +1068,7 @@
 
     private String mTag = TAG;
     private String mFpsTraceName;
+    private String mLargestViewTraceName;
 
     private static boolean sToolkitSetFrameRateReadOnlyFlagValue;
     private static boolean sToolkitMetricsForFrameRateDecisionFlagValue;
@@ -1318,6 +1320,7 @@
                 attrs = mWindowAttributes;
                 setTag();
                 mFpsTraceName = "FPS of " + getTitle();
+                mLargestViewTraceName = "Largest view percentage(per hundred) of " + getTitle();
 
                 if (DEBUG_KEEP_SCREEN_ON && (mClientWindowLayoutFlags
                         & WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON) != 0
@@ -4739,6 +4742,10 @@
         long fps = NANOS_PER_SEC / timeDiff;
         Trace.setCounter(mFpsTraceName, fps);
         mPreviousFrameDrawnTime = expectedDrawnTime;
+
+        long percentage = (long) (mLargestChildPercentage * 100);
+        Trace.setCounter(mLargestViewTraceName, percentage);
+        mLargestChildPercentage = 0.0f;
     }
 
     private void reportDrawFinished(@Nullable Transaction t, int seqId) {
@@ -5059,6 +5066,7 @@
         if (DEBUG_FPS) {
             trackFPS();
         }
+
         if (sToolkitMetricsForFrameRateDecisionFlagValue) {
             collectFrameRateDecisionMetrics();
         }
@@ -12148,7 +12156,8 @@
                 || motionEventAction == MotionEvent.ACTION_UP;
         boolean undesiredType = windowType == TYPE_INPUT_METHOD;
         // use toolkitSetFrameRate flag to gate the change
-        return desiredAction && !undesiredType && sToolkitSetFrameRateReadOnlyFlagValue;
+        return desiredAction && !undesiredType && sToolkitSetFrameRateReadOnlyFlagValue
+                && getFrameRateBoostOnTouchEnabled();
     }
 
     /**
@@ -12223,6 +12232,15 @@
         return mIsFrameRateBoosting;
     }
 
+    /**
+     * Get the value of mFrameRateBoostOnTouchEnabled
+     * Can be used to checked if touch boost is enabled. The default value is true.
+     */
+    @VisibleForTesting
+    public boolean getFrameRateBoostOnTouchEnabled() {
+        return mWindowAttributes.getFrameRateBoostOnTouchEnabled();
+    }
+
     private void boostFrameRate(int boostTimeOut) {
         mIsFrameRateBoosting = true;
         setPreferredFrameRateCategory(mPreferredFrameRateCategory);
@@ -12252,4 +12270,10 @@
     void setBackKeyCallbackForWindowlessWindow(@NonNull Predicate<KeyEvent> callback) {
         mWindowlessBackKeyCallback = callback;
     }
+
+    void recordViewPercentage(float percentage) {
+        if (!Trace.isEnabled()) return;
+        // Record the largest view of percentage to the display size.
+        mLargestChildPercentage = Math.max(percentage, mLargestChildPercentage);
+    }
 }
diff --git a/core/java/android/view/Window.java b/core/java/android/view/Window.java
index 87537fbc..7bae7ec 100644
--- a/core/java/android/view/Window.java
+++ b/core/java/android/view/Window.java
@@ -334,6 +334,9 @@
     private boolean mOverlayWithDecorCaptionEnabled = true;
     private boolean mCloseOnSwipeEnabled = false;
 
+    private static boolean sToolkitSetFrameRateReadOnlyFlagValue =
+                android.view.flags.Flags.toolkitSetFrameRateReadOnly();
+
     // The current window attributes.
     @UnsupportedAppUsage
     private final WindowManager.LayoutParams mWindowAttributes =
@@ -1373,6 +1376,39 @@
     }
 
     /**
+     * Sets whether the frame rate touch boost is enabled for this Window.
+     * When enabled, the frame rate will be boosted when a user touches the Window.
+     *
+     * @param enabled whether the frame rate touch boost is enabled.
+     * @see #getFrameRateBoostOnTouchEnabled()
+     * @see WindowManager.LayoutParams#setFrameRateBoostOnTouchEnabled(boolean)
+     */
+    @FlaggedApi(android.view.flags.Flags.FLAG_TOOLKIT_SET_FRAME_RATE_READ_ONLY)
+    public void setFrameRateBoostOnTouchEnabled(boolean enabled) {
+        if (sToolkitSetFrameRateReadOnlyFlagValue) {
+            final WindowManager.LayoutParams attrs = getAttributes();
+            attrs.setFrameRateBoostOnTouchEnabled(enabled);
+            dispatchWindowAttributesChanged(attrs);
+        }
+    }
+
+    /**
+     * Get whether frame rate touch boost is enabled
+     * {@link #setFrameRateBoostOnTouchEnabled(boolean)}
+     *
+     * @return whether the frame rate touch boost is enabled.
+     * @see #setFrameRateBoostOnTouchEnabled(boolean)
+     * @see WindowManager.LayoutParams#getFrameRateBoostOnTouchEnabled()
+     */
+    @FlaggedApi(android.view.flags.Flags.FLAG_TOOLKIT_SET_FRAME_RATE_READ_ONLY)
+    public boolean getFrameRateBoostOnTouchEnabled() {
+        if (sToolkitSetFrameRateReadOnlyFlagValue) {
+            return getAttributes().getFrameRateBoostOnTouchEnabled();
+        }
+        return true;
+    }
+
+    /**
      * If {@code isPreferred} is true, this method requests that the connected display does minimal
      * post processing when this window is visible on the screen. Otherwise, it requests that the
      * display switches back to standard image processing.
diff --git a/core/java/android/view/WindowManager.java b/core/java/android/view/WindowManager.java
index 07a347a..f76822f 100644
--- a/core/java/android/view/WindowManager.java
+++ b/core/java/android/view/WindowManager.java
@@ -4332,6 +4332,13 @@
         private float mDesiredHdrHeadroom = 0;
 
         /**
+         * For variable refresh rate project.
+         */
+        private boolean mFrameRateBoostOnTouch = true;
+        private static boolean sToolkitSetFrameRateReadOnlyFlagValue =
+                android.view.flags.Flags.toolkitSetFrameRateReadOnly();
+
+        /**
          * Carries the requests about {@link WindowInsetsController.Appearance} and
          * {@link WindowInsetsController.Behavior} to the system windows which can produce insets.
          *
@@ -4766,6 +4773,32 @@
         }
 
         /**
+         * Set the value whether we should enable Touch Boost
+         *
+         * @param enabled Whether we should enable Touch Boost
+         */
+        @FlaggedApi(android.view.flags.Flags.FLAG_TOOLKIT_SET_FRAME_RATE_READ_ONLY)
+        public void setFrameRateBoostOnTouchEnabled(boolean enabled) {
+            if (sToolkitSetFrameRateReadOnlyFlagValue) {
+                mFrameRateBoostOnTouch = enabled;
+            }
+        }
+
+        /**
+         * Get the value whether we should enable touch boost as set
+         * by {@link #setFrameRateBoostOnTouchEnabled(boolean)}
+         *
+         * @return A boolean value to indicate whether we should enable touch boost
+         */
+        @FlaggedApi(android.view.flags.Flags.FLAG_TOOLKIT_SET_FRAME_RATE_READ_ONLY)
+        public boolean getFrameRateBoostOnTouchEnabled() {
+            if (sToolkitSetFrameRateReadOnlyFlagValue) {
+                return mFrameRateBoostOnTouch;
+            }
+            return true;
+        }
+
+        /**
          * <p>
          * Blurs the screen behind the window. The effect is similar to that of {@link #dimAmount},
          * but instead of dimmed, the content behind the window will be blurred (or combined with
@@ -4916,6 +4949,9 @@
             out.writeTypedArray(paramsForRotation, 0 /* parcelableFlags */);
             out.writeInt(mDisplayFlags);
             out.writeFloat(mDesiredHdrHeadroom);
+            if (sToolkitSetFrameRateReadOnlyFlagValue) {
+                out.writeBoolean(mFrameRateBoostOnTouch);
+            }
         }
 
         public static final @android.annotation.NonNull Parcelable.Creator<LayoutParams> CREATOR
@@ -4988,6 +5024,9 @@
             paramsForRotation = in.createTypedArray(LayoutParams.CREATOR);
             mDisplayFlags = in.readInt();
             mDesiredHdrHeadroom = in.readFloat();
+            if (sToolkitSetFrameRateReadOnlyFlagValue) {
+                mFrameRateBoostOnTouch = in.readBoolean();
+            }
         }
 
         @SuppressWarnings({"PointlessBitwiseExpression"})
@@ -5324,6 +5363,12 @@
                 changes |= LAYOUT_CHANGED;
             }
 
+            if (sToolkitSetFrameRateReadOnlyFlagValue
+                    && mFrameRateBoostOnTouch != o.mFrameRateBoostOnTouch) {
+                mFrameRateBoostOnTouch = o.mFrameRateBoostOnTouch;
+                changes |= LAYOUT_CHANGED;
+            }
+
             return changes;
         }
 
@@ -5546,6 +5591,11 @@
                 sb.append(prefix).append("  forciblyShownTypes=").append(
                         WindowInsets.Type.toString(forciblyShownTypes));
             }
+            if (sToolkitSetFrameRateReadOnlyFlagValue && mFrameRateBoostOnTouch) {
+                sb.append(System.lineSeparator());
+                sb.append(prefix).append("  frameRateBoostOnTouch=");
+                sb.append(mFrameRateBoostOnTouch);
+            }
             if (paramsForRotation != null && paramsForRotation.length != 0) {
                 sb.append(System.lineSeparator());
                 sb.append(prefix).append("  paramsForRotation:");
diff --git a/core/java/android/window/TransitionInfo.java b/core/java/android/window/TransitionInfo.java
index 5bfa3d7..7c9340e 100644
--- a/core/java/android/window/TransitionInfo.java
+++ b/core/java/android/window/TransitionInfo.java
@@ -165,6 +165,9 @@
     public static final int FLAGS_IS_NON_APP_WINDOW =
             FLAG_IS_WALLPAPER | FLAG_IS_INPUT_METHOD | FLAG_IS_SYSTEM_WINDOW;
 
+    /** The change will not participate in the animation. */
+    public static final int FLAGS_IS_OCCLUDED_NO_ANIMATION = FLAG_IS_OCCLUDED | FLAG_NO_ANIMATION;
+
     /** @hide */
     @IntDef(prefix = { "FLAG_" }, value = {
             FLAG_NONE,
diff --git a/core/java/com/android/internal/pm/parsing/IPackageCacher.java b/core/java/com/android/internal/pm/parsing/IPackageCacher.java
new file mode 100644
index 0000000..3e01730
--- /dev/null
+++ b/core/java/com/android/internal/pm/parsing/IPackageCacher.java
@@ -0,0 +1,35 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.internal.pm.parsing;
+
+import com.android.internal.pm.parsing.pkg.ParsedPackage;
+
+import java.io.File;
+
+/** @hide */
+public interface IPackageCacher {
+
+    /**
+     * Returns the cached parse result for {@code packageFile} for parse flags {@code flags},
+     * or {@code null} if no cached result exists.
+     */
+    ParsedPackage getCachedResult(File packageFile, int flags);
+
+    /**
+     * Caches the parse result for {@code packageFile} with flags {@code flags}.
+     */
+    void cacheResult(File packageFile, int flags, ParsedPackage parsed);
+}
diff --git a/services/core/java/com/android/server/pm/parsing/PackageParser2.java b/core/java/com/android/internal/pm/parsing/PackageParser2.java
similarity index 69%
rename from services/core/java/com/android/server/pm/parsing/PackageParser2.java
rename to core/java/com/android/internal/pm/parsing/PackageParser2.java
index b6a08a5..e413293 100644
--- a/services/core/java/com/android/server/pm/parsing/PackageParser2.java
+++ b/core/java/com/android/internal/pm/parsing/PackageParser2.java
@@ -14,13 +14,12 @@
  * limitations under the License.
  */
 
-package com.android.server.pm.parsing;
+package com.android.internal.pm.parsing;
 
 import android.annotation.AnyThread;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.app.ActivityThread;
-import android.content.Context;
 import android.content.pm.ApplicationInfo;
 import android.content.pm.parsing.PackageLite;
 import android.content.pm.parsing.result.ParseInput;
@@ -28,26 +27,20 @@
 import android.content.pm.parsing.result.ParseTypeImpl;
 import android.content.res.TypedArray;
 import android.os.Build;
-import android.os.ServiceManager;
 import android.os.SystemClock;
 import android.permission.PermissionManager;
 import android.util.DisplayMetrics;
 import android.util.Slog;
 
-import com.android.internal.compat.IPlatformCompat;
 import com.android.internal.pm.parsing.pkg.PackageImpl;
 import com.android.internal.pm.parsing.pkg.ParsedPackage;
 import com.android.internal.pm.pkg.parsing.ParsingPackage;
 import com.android.internal.pm.pkg.parsing.ParsingPackageUtils;
 import com.android.internal.pm.pkg.parsing.ParsingUtils;
 import com.android.internal.util.ArrayUtils;
-import com.android.server.SystemConfig;
-import com.android.server.pm.PackageManagerException;
-import com.android.server.pm.PackageManagerService;
 
 import java.io.File;
 import java.util.List;
-import java.util.Set;
 
 /**
  * The v2 of package parsing for use when parsing is initiated in the server and must
@@ -59,50 +52,6 @@
  */
 public class PackageParser2 implements AutoCloseable {
 
-    /**
-     * For parsing inside the system server but outside of {@link PackageManagerService}.
-     * Generally used for parsing information in an APK that hasn't been installed yet.
-     *
-     * This must be called inside the system process as it relies on {@link ServiceManager}.
-     */
-    @NonNull
-    public static PackageParser2 forParsingFileWithDefaults() {
-        IPlatformCompat platformCompat = IPlatformCompat.Stub.asInterface(
-                ServiceManager.getService(Context.PLATFORM_COMPAT_SERVICE));
-        return new PackageParser2(null /* separateProcesses */, null /* displayMetrics */,
-                null /* cacheDir */, new Callback() {
-            @Override
-            public boolean isChangeEnabled(long changeId, @NonNull ApplicationInfo appInfo) {
-                try {
-                    return platformCompat.isChangeEnabled(changeId, appInfo);
-                } catch (Exception e) {
-                    // This shouldn't happen, but assume enforcement if it does
-                    Slog.wtf(TAG, "IPlatformCompat query failed", e);
-                    return true;
-                }
-            }
-
-            @Override
-            public boolean hasFeature(String feature) {
-                // Assume the device doesn't support anything. This will affect permission parsing
-                // and will force <uses-permission/> declarations to include all requiredNotFeature
-                // permissions and exclude all requiredFeature permissions. This mirrors the old
-                // behavior.
-                return false;
-            }
-
-            @Override
-            public Set<String> getHiddenApiWhitelistedApps() {
-                return SystemConfig.getInstance().getHiddenApiWhitelistedApps();
-            }
-
-            @Override
-            public Set<String> getInstallConstraintsAllowlist() {
-                return SystemConfig.getInstance().getInstallConstraintsAllowlist();
-            }
-        });
-    }
-
     private static final String TAG = ParsingUtils.TAG;
 
     private static final boolean LOG_PARSE_TIMINGS = Build.IS_DEBUGGABLE;
@@ -118,12 +67,12 @@
     private final ThreadLocal<ParseTypeImpl> mSharedResult;
 
     @Nullable
-    protected PackageCacher mCacher;
+    protected IPackageCacher mCacher;
 
-    private final ParsingPackageUtils parsingUtils;
+    private final ParsingPackageUtils mParsingUtils;
 
     public PackageParser2(String[] separateProcesses, DisplayMetrics displayMetrics,
-            @Nullable File cacheDir, @NonNull Callback callback) {
+            @Nullable IPackageCacher cacher, @NonNull Callback callback) {
         if (displayMetrics == null) {
             displayMetrics = new DisplayMetrics();
             displayMetrics.setToDefaults();
@@ -134,9 +83,9 @@
         List<PermissionManager.SplitPermissionInfo> splitPermissions = permissionManager
                 .getSplitPermissions();
 
-        mCacher = cacheDir == null ? null : new PackageCacher(cacheDir);
+        mCacher = cacher;
 
-        parsingUtils = new ParsingPackageUtils(separateProcesses, displayMetrics, splitPermissions,
+        mParsingUtils = new ParsingPackageUtils(separateProcesses, displayMetrics, splitPermissions,
                 callback);
 
         ParseInput.Callback enforcementCallback = (changeId, packageName, targetSdkVersion) -> {
@@ -155,7 +104,7 @@
      */
     @AnyThread
     public ParsedPackage parsePackage(File packageFile, int flags, boolean useCaches)
-            throws PackageManagerException {
+            throws PackageParserException {
         var files = packageFile.listFiles();
         // Apk directory is directly nested under the current directory
         if (ArrayUtils.size(files) == 1 && files[0].isDirectory()) {
@@ -171,9 +120,9 @@
 
         long parseTime = LOG_PARSE_TIMINGS ? SystemClock.uptimeMillis() : 0;
         ParseInput input = mSharedResult.get().reset();
-        ParseResult<ParsingPackage> result = parsingUtils.parsePackage(input, packageFile, flags);
+        ParseResult<ParsingPackage> result = mParsingUtils.parsePackage(input, packageFile, flags);
         if (result.isError()) {
-            throw new PackageManagerException(result.getErrorCode(), result.getErrorMessage(),
+            throw new PackageParserException(result.getErrorCode(), result.getErrorMessage(),
                     result.getException());
         }
 
@@ -201,12 +150,12 @@
      */
     @AnyThread
     public ParsedPackage parsePackageFromPackageLite(PackageLite packageLite, int flags)
-            throws PackageManagerException {
+            throws PackageParserException {
         ParseInput input = mSharedResult.get().reset();
-        ParseResult<ParsingPackage> result = parsingUtils.parsePackageFromPackageLite(input,
+        ParseResult<ParsingPackage> result = mParsingUtils.parsePackageFromPackageLite(input,
                 packageLite, flags);
         if (result.isError()) {
-            throw new PackageManagerException(result.getErrorCode(), result.getErrorMessage(),
+            throw new PackageParserException(result.getErrorCode(), result.getErrorMessage(),
                     result.getException());
         }
         return result.getResult().hideAsParsed();
@@ -226,7 +175,7 @@
         mSharedAppInfo.remove();
     }
 
-    public static abstract class Callback implements ParsingPackageUtils.Callback {
+    public abstract static class Callback implements ParsingPackageUtils.Callback {
 
         @Override
         public final ParsingPackage startParsingPackage(@NonNull String packageName,
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index d0de5f0..5400c58 100644
--- a/core/res/res/values/config.xml
+++ b/core/res/res/values/config.xml
@@ -6862,4 +6862,8 @@
 
     <!-- Whether the media player is shown on the quick settings -->
     <bool name="config_quickSettingsShowMediaPlayer">true</bool>
+
+    <!-- Defines suitability of the built-in speaker route.
+         Refer to {@link MediaRoute2Info} to see supported values.  -->
+    <integer name="config_mediaRouter_builtInSpeakerSuitability">0</integer>
 </resources>
diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml
index 3894330..eeef192 100644
--- a/core/res/res/values/symbols.xml
+++ b/core/res/res/values/symbols.xml
@@ -5306,4 +5306,7 @@
   <java-symbol type="bool" name="config_viewBasedRotaryEncoderHapticsEnabled" />
 
   <java-symbol type="bool" name="config_quickSettingsShowMediaPlayer" />
+
+  <!-- Android MediaRouter framework configs. -->
+  <java-symbol type="integer" name="config_mediaRouter_builtInSpeakerSuitability" />
 </resources>
diff --git a/core/tests/companiontests/src/android/companion/SystemDataTransportTest.java b/core/tests/companiontests/src/android/companion/SystemDataTransportTest.java
index 2b4123a..73d7fe9 100644
--- a/core/tests/companiontests/src/android/companion/SystemDataTransportTest.java
+++ b/core/tests/companiontests/src/android/companion/SystemDataTransportTest.java
@@ -16,6 +16,9 @@
 
 package android.companion;
 
+import static android.companion.CompanionDeviceManager.MESSAGE_ONEWAY_PING;
+import static android.companion.CompanionDeviceManager.MESSAGE_REQUEST_PING;
+
 import android.content.Context;
 import android.os.SystemClock;
 import android.test.InstrumentationTestCase;
@@ -36,16 +39,22 @@
 import java.nio.charset.StandardCharsets;
 import java.util.List;
 import java.util.Random;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
 
+/**
+ * Tests that CDM can intake incoming messages in the system data transport and output results.
+ *
+ * Build/Install/Run: atest CompanionTests:SystemDataTransportTest
+ */
 public class SystemDataTransportTest extends InstrumentationTestCase {
     private static final String TAG = "SystemDataTransportTest";
 
     private static final int MESSAGE_INVALID = 0xF00DCAFE;
-
-    private static final int MESSAGE_REQUEST_INVALID = 0x63636363; // ????
-    private static final int MESSAGE_REQUEST_PING = 0x63807378; // ?PIN
-
+    private static final int MESSAGE_ONEWAY_INVALID = 0x43434343; // ++++
     private static final int MESSAGE_RESPONSE_INVALID = 0x33333333; // !!!!
+    private static final int MESSAGE_REQUEST_INVALID = 0x63636363; // ????
+
     private static final int MESSAGE_RESPONSE_SUCCESS = 0x33838567; // !SUC
     private static final int MESSAGE_RESPONSE_FAILURE = 0x33706573; // !FAI
 
@@ -122,8 +131,6 @@
         new Random().nextBytes(blob);
 
         final byte[] input = generatePacket(MESSAGE_REQUEST_PING, /* sequence */ 1, blob);
-        final byte[] expected = generatePacket(MESSAGE_RESPONSE_SUCCESS, /* sequence */ 1, blob);
-        assertTransportBehavior(input, expected);
     }
 
     public void testMultiplePingPing() {
@@ -176,6 +183,43 @@
         testPingHandRolled();
     }
 
+    public void testInvalidOnewayMessages() throws InterruptedException {
+        // Add a callback
+        final CountDownLatch received = new CountDownLatch(1);
+        mCdm.addOnMessageReceivedListener(Runnable::run, MESSAGE_ONEWAY_INVALID,
+                (id, data) -> received.countDown());
+
+        final byte[] input = generatePacket(MESSAGE_ONEWAY_INVALID, /* sequence */ 1);
+        final ByteArrayInputStream in = new ByteArrayInputStream(input);
+        final ByteArrayOutputStream out = new ByteArrayOutputStream();
+        mCdm.attachSystemDataTransport(mAssociationId, in, out);
+
+        // Assert that a one-way message was ignored (does not trigger a callback)
+        assertFalse(received.await(5, TimeUnit.SECONDS));
+
+        // There should not be a response to one-way messages
+        assertEquals(0, out.toByteArray().length);
+    }
+
+
+    public void testOnewayMessages() throws InterruptedException {
+        // Add a callback
+        final CountDownLatch received = new CountDownLatch(1);
+        mCdm.addOnMessageReceivedListener(Runnable::run, MESSAGE_ONEWAY_PING,
+                (id, data) -> received.countDown());
+
+        final byte[] input = generatePacket(MESSAGE_ONEWAY_PING, /* sequence */ 1);
+        final ByteArrayInputStream in = new ByteArrayInputStream(input);
+        final ByteArrayOutputStream out = new ByteArrayOutputStream();
+        mCdm.attachSystemDataTransport(mAssociationId, in, out);
+
+        // Assert that a one-way message was received
+        assertTrue(received.await(1, TimeUnit.SECONDS));
+
+        // There should not be a response to one-way messages
+        assertEquals(0, out.toByteArray().length);
+    }
+
     public static byte[] concat(byte[]... blobs) {
         int length = 0;
         for (byte[] blob : blobs) {
diff --git a/core/tests/coretests/src/android/view/ViewRootImplTest.java b/core/tests/coretests/src/android/view/ViewRootImplTest.java
index b30a0c8..cf3eb12 100644
--- a/core/tests/coretests/src/android/view/ViewRootImplTest.java
+++ b/core/tests/coretests/src/android/view/ViewRootImplTest.java
@@ -659,8 +659,6 @@
         ViewRootImpl viewRootImpl = view.getViewRootImpl();
         sInstrumentation.runOnMainSync(() -> {
             view.invalidate();
-            assertEquals(viewRootImpl.getLastPreferredFrameRateCategory(),
-                    FRAME_RATE_CATEGORY_NORMAL);
             viewRootImpl.notifyInsetsAnimationRunningStateChanged(true);
             view.invalidate();
         });
@@ -672,6 +670,37 @@
         });
     }
 
+
+    /**
+     * Test FrameRateBoostOnTouchEnabled API
+     */
+    @Test
+    @RequiresFlagsEnabled(FLAG_TOOLKIT_SET_FRAME_RATE_READ_ONLY)
+    public void votePreferredFrameRate_frameRateBoostOnTouch() {
+        View view = new View(sContext);
+        attachViewToWindow(view);
+        sInstrumentation.waitForIdleSync();
+
+        ViewRootImpl viewRootImpl = view.getViewRootImpl();
+        final WindowManager.LayoutParams attrs = viewRootImpl.mWindowAttributes;
+        assertEquals(attrs.getFrameRateBoostOnTouchEnabled(), true);
+        assertEquals(viewRootImpl.getFrameRateBoostOnTouchEnabled(),
+                attrs.getFrameRateBoostOnTouchEnabled());
+
+        sInstrumentation.runOnMainSync(() -> {
+            attrs.setFrameRateBoostOnTouchEnabled(false);
+            viewRootImpl.setLayoutParams(attrs, false);
+        });
+        sInstrumentation.waitForIdleSync();
+
+        sInstrumentation.runOnMainSync(() -> {
+            final WindowManager.LayoutParams newAttrs = viewRootImpl.mWindowAttributes;
+            assertEquals(newAttrs.getFrameRateBoostOnTouchEnabled(), false);
+            assertEquals(viewRootImpl.getFrameRateBoostOnTouchEnabled(),
+                    newAttrs.getFrameRateBoostOnTouchEnabled());
+        });
+    }
+
     @Test
     public void forceInvertOffDarkThemeOff_forceDarkModeDisabled() {
         mSetFlagsRule.enableFlags(FLAG_FORCE_INVERT_COLOR);
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/OverlayCreateParams.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/OverlayCreateParams.java
deleted file mode 100644
index ff49cdc..0000000
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/OverlayCreateParams.java
+++ /dev/null
@@ -1,119 +0,0 @@
-/*
- * Copyright (C) 2023 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package androidx.window.extensions.embedding;
-
-import static java.util.Objects.requireNonNull;
-
-import android.graphics.Rect;
-import android.os.Bundle;
-
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-import androidx.annotation.VisibleForTesting;
-
-/**
- * The parameter to create an overlay container that retrieved from
- * {@link android.app.ActivityOptions} bundle.
- */
-class OverlayCreateParams {
-
-    // TODO(b/295803704): Move them to WM Extensions so that we can reuse in WM Jetpack.
-    @VisibleForTesting
-    static final String KEY_OVERLAY_CREATE_PARAMS =
-            "androidx.window.extensions.OverlayCreateParams";
-
-    @VisibleForTesting
-    static final String KEY_OVERLAY_CREATE_PARAMS_TASK_ID =
-            "androidx.window.extensions.OverlayCreateParams.taskId";
-
-    @VisibleForTesting
-    static final String KEY_OVERLAY_CREATE_PARAMS_TAG =
-            "androidx.window.extensions.OverlayCreateParams.tag";
-
-    @VisibleForTesting
-    static final String KEY_OVERLAY_CREATE_PARAMS_BOUNDS =
-            "androidx.window.extensions.OverlayCreateParams.bounds";
-
-    private final int mTaskId;
-
-    @NonNull
-    private final String mTag;
-
-    @NonNull
-    private final Rect mBounds;
-
-    OverlayCreateParams(int taskId, @NonNull String tag, @NonNull Rect bounds) {
-        mTaskId = taskId;
-        mTag = requireNonNull(tag);
-        mBounds = requireNonNull(bounds);
-    }
-
-    int getTaskId() {
-        return mTaskId;
-    }
-
-    @NonNull
-    String getTag() {
-        return mTag;
-    }
-
-    @NonNull
-    Rect getBounds() {
-        return mBounds;
-    }
-
-    @Override
-    public int hashCode() {
-        int result = mTaskId;
-        result = 31 * result + mTag.hashCode();
-        result = 31 * result + mBounds.hashCode();
-        return result;
-    }
-
-    @Override
-    public boolean equals(Object obj) {
-        if (obj == this) return true;
-        if (!(obj instanceof OverlayCreateParams thatParams)) return false;
-        return mTaskId == thatParams.mTaskId
-                && mTag.equals(thatParams.mTag)
-                && mBounds.equals(thatParams.mBounds);
-    }
-
-    @Override
-    public String toString() {
-        return OverlayCreateParams.class.getSimpleName() + ": {"
-                + "taskId=" + mTaskId
-                + ", tag=" + mTag
-                + ", bounds=" + mBounds
-                + "}";
-    }
-
-    /** Retrieves the {@link OverlayCreateParams} from {@link android.app.ActivityOptions} bundle */
-    @Nullable
-    static OverlayCreateParams fromBundle(@NonNull Bundle bundle) {
-        final Bundle paramsBundle = bundle.getBundle(KEY_OVERLAY_CREATE_PARAMS);
-        if (paramsBundle == null) {
-            return null;
-        }
-        final int taskId = paramsBundle.getInt(KEY_OVERLAY_CREATE_PARAMS_TASK_ID);
-        final String tag = requireNonNull(paramsBundle.getString(KEY_OVERLAY_CREATE_PARAMS_TAG));
-        final Rect bounds = requireNonNull(paramsBundle.getParcelable(
-                KEY_OVERLAY_CREATE_PARAMS_BOUNDS, Rect.class));
-
-        return new OverlayCreateParams(taskId, tag, bounds);
-    }
-}
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java
index 4973a4d..15ee4e1 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitController.java
@@ -34,6 +34,7 @@
 import static android.window.TaskFragmentTransaction.TYPE_TASK_FRAGMENT_PARENT_INFO_CHANGED;
 import static android.window.TaskFragmentTransaction.TYPE_TASK_FRAGMENT_VANISHED;
 
+import static androidx.window.extensions.embedding.ActivityEmbeddingOptionsProperties.KEY_OVERLAY_TAG;
 import static androidx.window.extensions.embedding.SplitContainer.getFinishPrimaryWithSecondaryBehavior;
 import static androidx.window.extensions.embedding.SplitContainer.getFinishSecondaryWithPrimaryBehavior;
 import static androidx.window.extensions.embedding.SplitContainer.isStickyPlaceholderRule;
@@ -136,6 +137,15 @@
     private Function<SplitAttributesCalculatorParams, SplitAttributes> mSplitAttributesCalculator;
 
     /**
+     * A calculator function to compute {@link ActivityStack} attributes in a task, which is called
+     * when there's {@link #onTaskFragmentParentInfoChanged} or folding state changed.
+     */
+    @GuardedBy("mLock")
+    @Nullable
+    private Function<ActivityStackAttributesCalculatorParams, ActivityStackAttributes>
+            mActivityStackAttributesCalculator;
+
+    /**
      * Map from Task id to {@link TaskContainer} which contains all TaskFragment and split pair info
      * below it.
      * When the app is host of multiple Tasks, there can be multiple splits controlled by the same
@@ -319,6 +329,22 @@
         }
     }
 
+    @Override
+    public void setActivityStackAttributesCalculator(
+            @NonNull Function<ActivityStackAttributesCalculatorParams, ActivityStackAttributes>
+                    calculator) {
+        synchronized (mLock) {
+            mActivityStackAttributesCalculator = calculator;
+        }
+    }
+
+    @Override
+    public void clearActivityStackAttributesCalculator() {
+        synchronized (mLock) {
+            mActivityStackAttributesCalculator = null;
+        }
+    }
+
     @GuardedBy("mLock")
     @Nullable
     Function<SplitAttributesCalculatorParams, SplitAttributes> getSplitAttributesCalculator() {
@@ -1412,7 +1438,7 @@
             @NonNull WindowContainerTransaction wct, @NonNull Intent intent, int taskId,
             @Nullable Activity launchingActivity) {
         return createEmptyContainer(wct, intent, taskId, new Rect(), launchingActivity,
-                null /* overlayTag */);
+                null /* overlayTag */, null /* launchOptions */);
     }
 
     /**
@@ -1426,7 +1452,7 @@
     TaskFragmentContainer createEmptyContainer(
             @NonNull WindowContainerTransaction wct, @NonNull Intent intent, int taskId,
             @NonNull Rect bounds, @Nullable Activity launchingActivity,
-            @Nullable String overlayTag) {
+            @Nullable String overlayTag, @Nullable Bundle launchOptions) {
         // We need an activity in the organizer process in the same Task to use as the owner
         // activity, as well as to get the Task window info.
         final Activity activityInTask;
@@ -1443,7 +1469,8 @@
             return null;
         }
         final TaskFragmentContainer container = newContainer(null /* pendingAppearedActivity */,
-                intent, activityInTask, taskId, null /* pairedPrimaryContainer*/, overlayTag);
+                intent, activityInTask, taskId, null /* pairedPrimaryContainer*/, overlayTag,
+                launchOptions);
         final IBinder taskFragmentToken = container.getTaskFragmentToken();
         // Note that taskContainer will not exist before calling #newContainer if the container
         // is the first embedded TF in the task.
@@ -1570,14 +1597,16 @@
     TaskFragmentContainer newContainer(@NonNull Activity pendingAppearedActivity,
             @NonNull Activity activityInTask, int taskId) {
         return newContainer(pendingAppearedActivity, null /* pendingAppearedIntent */,
-                activityInTask, taskId, null /* pairedPrimaryContainer */, null /* tag */);
+                activityInTask, taskId, null /* pairedPrimaryContainer */, null /* tag */,
+                null /* launchOptions */);
     }
 
     @GuardedBy("mLock")
     TaskFragmentContainer newContainer(@NonNull Intent pendingAppearedIntent,
             @NonNull Activity activityInTask, int taskId) {
         return newContainer(null /* pendingAppearedActivity */, pendingAppearedIntent,
-                activityInTask, taskId, null /* pairedPrimaryContainer */, null /* tag */);
+                activityInTask, taskId, null /* pairedPrimaryContainer */, null /* tag */,
+                null /* launchOptions */);
     }
 
     @GuardedBy("mLock")
@@ -1585,7 +1614,8 @@
                                        @NonNull Activity activityInTask, int taskId,
                                        @NonNull TaskFragmentContainer pairedPrimaryContainer) {
         return newContainer(null /* pendingAppearedActivity */, pendingAppearedIntent,
-                activityInTask, taskId, pairedPrimaryContainer, null /* tag */);
+                activityInTask, taskId, pairedPrimaryContainer, null /* tag */,
+                null /* launchOptions */);
     }
 
     /**
@@ -1602,11 +1632,14 @@
      * @param overlayTag                The tag for the new created overlay container. It must be
      *                                  needed if {@code isOverlay} is {@code true}. Otherwise,
      *                                  it should be {@code null}.
+     * @param launchOptions             The launch options bundle to create a container. Must be
+     *                                  specified for overlay container.
      */
     @GuardedBy("mLock")
     TaskFragmentContainer newContainer(@Nullable Activity pendingAppearedActivity,
             @Nullable Intent pendingAppearedIntent, @NonNull Activity activityInTask, int taskId,
-            @Nullable TaskFragmentContainer pairedPrimaryContainer, @Nullable String overlayTag) {
+            @Nullable TaskFragmentContainer pairedPrimaryContainer, @Nullable String overlayTag,
+            @Nullable Bundle launchOptions) {
         if (activityInTask == null) {
             throw new IllegalArgumentException("activityInTask must not be null,");
         }
@@ -1615,7 +1648,8 @@
         }
         final TaskContainer taskContainer = mTaskContainers.get(taskId);
         final TaskFragmentContainer container = new TaskFragmentContainer(pendingAppearedActivity,
-                pendingAppearedIntent, taskContainer, this, pairedPrimaryContainer, overlayTag);
+                pendingAppearedIntent, taskContainer, this, pairedPrimaryContainer, overlayTag,
+                launchOptions);
         return container;
     }
 
@@ -2345,28 +2379,28 @@
     @GuardedBy("mLock")
     @Nullable
     TaskFragmentContainer createOrUpdateOverlayTaskFragmentIfNeeded(
-            @NonNull WindowContainerTransaction wct,
-            @NonNull OverlayCreateParams overlayCreateParams, int launchTaskId,
+            @NonNull WindowContainerTransaction wct, @NonNull Bundle options,
             @NonNull Intent intent, @NonNull Activity launchActivity) {
-        final int taskId = overlayCreateParams.getTaskId();
-        if (taskId != launchTaskId) {
-            // The task ID doesn't match the launch activity's. Cannot determine the host task
-            // to launch the overlay.
-            throw new IllegalArgumentException("The task ID of "
-                    + "OverlayCreateParams#launchingActivity must match the task ID of "
-                    + "the activity to #startActivity with the activity options that takes "
-                    + "OverlayCreateParams.");
-        }
         final List<TaskFragmentContainer> overlayContainers =
                 getAllOverlayTaskFragmentContainers();
-        final String overlayTag = overlayCreateParams.getTag();
+        final String overlayTag = Objects.requireNonNull(options.getString(KEY_OVERLAY_TAG));
 
         // If the requested bounds of OverlayCreateParams are smaller than minimum dimensions
         // specified by Intent, expand the overlay container to fill the parent task instead.
-        final Rect bounds = overlayCreateParams.getBounds();
-        final Size minDimensions = getMinDimensions(intent);
-        final boolean shouldExpandContainer = boundsSmallerThanMinDimensions(bounds,
-                minDimensions);
+        final ActivityStackAttributesCalculatorParams params =
+                new ActivityStackAttributesCalculatorParams(mPresenter.toParentContainerInfo(
+                        mPresenter.getTaskProperties(launchActivity)), overlayTag, options);
+        // Fallback to expand the bounds if there's no activityStackAttributes calculator.
+        final Rect relativeBounds = mActivityStackAttributesCalculator != null
+                ? new Rect(mActivityStackAttributesCalculator.apply(params).getRelativeBounds())
+                : new Rect();
+        final boolean shouldExpandContainer = boundsSmallerThanMinDimensions(relativeBounds,
+                getMinDimensions(intent));
+        // Expand the bounds if the requested bounds are smaller than minimum dimensions.
+        if (shouldExpandContainer) {
+            relativeBounds.setEmpty();
+        }
+        final int taskId = getTaskId(launchActivity);
         if (!overlayContainers.isEmpty()) {
             for (final TaskFragmentContainer overlayContainer : overlayContainers) {
                 if (!overlayTag.equals(overlayContainer.getOverlayTag())
@@ -2390,7 +2424,7 @@
                     final Rect taskBounds = overlayContainer.getTaskContainer().getTaskProperties()
                             .getTaskMetrics().getBounds();
                     final IBinder overlayToken = overlayContainer.getTaskFragmentToken();
-                    final Rect sanitizedBounds = sanitizeBounds(bounds, intent, taskBounds);
+                    final Rect sanitizedBounds = sanitizeBounds(relativeBounds, intent, taskBounds);
                     mPresenter.resizeTaskFragment(wct, overlayToken, sanitizedBounds);
                     mPresenter.setTaskFragmentIsolatedNavigation(wct, overlayToken,
                             !sanitizedBounds.isEmpty());
@@ -2402,8 +2436,9 @@
                 }
             }
         }
-        return createEmptyContainer(wct, intent, taskId,
-                (shouldExpandContainer ? new Rect() : bounds), launchActivity, overlayTag);
+        // Launch the overlay container to the task with taskId.
+        return createEmptyContainer(wct, intent, taskId, relativeBounds, launchActivity, overlayTag,
+                options);
     }
 
     private final class LifecycleCallbacks extends EmptyLifecycleCallbacksAdapter {
@@ -2568,12 +2603,11 @@
                 final TaskFragmentContainer launchedInTaskFragment;
                 if (launchingActivity != null) {
                     final int taskId = getTaskId(launchingActivity);
-                    final OverlayCreateParams overlayCreateParams =
-                            OverlayCreateParams.fromBundle(options);
+                    final String overlayTag = options.getString(KEY_OVERLAY_TAG);
                     if (Flags.activityEmbeddingOverlayPresentationFlag()
-                            && overlayCreateParams != null) {
+                            && overlayTag != null) {
                         launchedInTaskFragment = createOrUpdateOverlayTaskFragmentIfNeeded(wct,
-                                overlayCreateParams, taskId, intent, launchingActivity);
+                                options, intent, launchingActivity);
                     } else {
                         launchedInTaskFragment = resolveStartActivityIntent(wct, taskId, intent,
                                 launchingActivity);
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java
index b5c32bb..acfd8e4 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/SplitPresenter.java
@@ -1084,4 +1084,14 @@
     WindowMetrics getTaskWindowMetrics(@NonNull Activity activity) {
         return getTaskProperties(activity).getTaskMetrics();
     }
+
+    @NonNull
+    ParentContainerInfo toParentContainerInfo(@NonNull TaskProperties taskProperties) {
+        final Configuration configuration = taskProperties.getConfiguration();
+        final WindowLayoutInfo windowLayoutInfo = mWindowLayoutComponent
+                .getCurrentWindowLayoutInfo(taskProperties.getDisplayId(),
+                        configuration.windowConfiguration);
+        return new ParentContainerInfo(taskProperties.getTaskMetrics(), configuration,
+                windowLayoutInfo);
+    }
 }
diff --git a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java
index ed8c4f3..da87339 100644
--- a/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java
+++ b/libs/WindowManager/Jetpack/src/androidx/window/extensions/embedding/TaskFragmentContainer.java
@@ -24,6 +24,7 @@
 import android.content.Intent;
 import android.graphics.Rect;
 import android.os.Binder;
+import android.os.Bundle;
 import android.os.IBinder;
 import android.util.Size;
 import android.window.TaskFragmentAnimationParams;
@@ -105,6 +106,13 @@
     @Nullable
     private final String mOverlayTag;
 
+    /**
+     * The launch options that was used to create this container. Must not be {@code null} for
+     * {@link #isOverlay()} container.
+     */
+    @Nullable
+    private final Bundle mLaunchOptions;
+
     /** Indicates whether the container was cleaned up after the last activity was removed. */
     private boolean mIsFinished;
 
@@ -165,7 +173,7 @@
 
     /**
      * @see #TaskFragmentContainer(Activity, Intent, TaskContainer, SplitController,
-     * TaskFragmentContainer, String)
+     * TaskFragmentContainer, String, Bundle)
      */
     TaskFragmentContainer(@Nullable Activity pendingAppearedActivity,
                           @Nullable Intent pendingAppearedIntent,
@@ -173,7 +181,8 @@
                           @NonNull SplitController controller,
                           @Nullable TaskFragmentContainer pairedPrimaryContainer) {
         this(pendingAppearedActivity, pendingAppearedIntent, taskContainer,
-                controller, pairedPrimaryContainer, null /* overlayTag */);
+                controller, pairedPrimaryContainer, null /* overlayTag */,
+                null /* launchOptions */);
     }
 
     /**
@@ -181,11 +190,14 @@
      * container transaction.
      * @param pairedPrimaryContainer    when it is set, the new container will be add right above it
      * @param overlayTag                Sets to indicate this taskFragment is an overlay container
+     * @param launchOptions             The launch options to create this container. Must not be
+     *                                  {@code null} for an overlay container
      */
     TaskFragmentContainer(@Nullable Activity pendingAppearedActivity,
             @Nullable Intent pendingAppearedIntent, @NonNull TaskContainer taskContainer,
             @NonNull SplitController controller,
-            @Nullable TaskFragmentContainer pairedPrimaryContainer, @Nullable String overlayTag) {
+            @Nullable TaskFragmentContainer pairedPrimaryContainer, @Nullable String overlayTag,
+            @Nullable Bundle launchOptions) {
         if ((pendingAppearedActivity == null && pendingAppearedIntent == null)
                 || (pendingAppearedActivity != null && pendingAppearedIntent != null)) {
             throw new IllegalArgumentException(
@@ -195,6 +207,10 @@
         mToken = new Binder("TaskFragmentContainer");
         mTaskContainer = taskContainer;
         mOverlayTag = overlayTag;
+        if (overlayTag != null) {
+            Objects.requireNonNull(launchOptions);
+        }
+        mLaunchOptions = launchOptions;
 
         if (pairedPrimaryContainer != null) {
             // The TaskFragment will be positioned right above the paired container.
@@ -903,8 +919,8 @@
     }
 
     /**
-     * Returns the tag specified in {@link OverlayCreateParams#getTag()}. {@code null} if this
-     * taskFragment container is not an overlay container.
+     * Returns the tag specified in launch options. {@code null} if this taskFragment container is
+     * not an overlay container.
      */
     @Nullable
     String getOverlayTag() {
diff --git a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/OverlayPresentationTest.java b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/OverlayPresentationTest.java
index 4c2433f..678bdef 100644
--- a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/OverlayPresentationTest.java
+++ b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/OverlayPresentationTest.java
@@ -18,14 +18,11 @@
 
 import static android.view.Display.DEFAULT_DISPLAY;
 
+import static androidx.window.extensions.embedding.ActivityEmbeddingOptionsProperties.KEY_OVERLAY_TAG;
 import static androidx.window.extensions.embedding.EmbeddingTestUtils.TASK_BOUNDS;
 import static androidx.window.extensions.embedding.EmbeddingTestUtils.TASK_ID;
 import static androidx.window.extensions.embedding.EmbeddingTestUtils.createMockTaskFragmentInfo;
 import static androidx.window.extensions.embedding.EmbeddingTestUtils.createSplitPairRuleBuilder;
-import static androidx.window.extensions.embedding.OverlayCreateParams.KEY_OVERLAY_CREATE_PARAMS;
-import static androidx.window.extensions.embedding.OverlayCreateParams.KEY_OVERLAY_CREATE_PARAMS_BOUNDS;
-import static androidx.window.extensions.embedding.OverlayCreateParams.KEY_OVERLAY_CREATE_PARAMS_TAG;
-import static androidx.window.extensions.embedding.OverlayCreateParams.KEY_OVERLAY_CREATE_PARAMS_TASK_ID;
 
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
@@ -45,6 +42,7 @@
 import static org.mockito.Mockito.never;
 
 import android.app.Activity;
+import android.app.ActivityOptions;
 import android.content.ComponentName;
 import android.content.Intent;
 import android.content.pm.ActivityInfo;
@@ -98,9 +96,6 @@
     @Rule
     public final SetFlagsRule mSetFlagRule = new SetFlagsRule();
 
-    private static final OverlayCreateParams TEST_OVERLAY_CREATE_PARAMS =
-            new OverlayCreateParams(TASK_ID, "test,", new Rect(0, 0, 200, 200));
-
     private SplitController.ActivityStartMonitor mMonitor;
 
     private Intent mIntent;
@@ -165,37 +160,15 @@
     }
 
     @Test
-    public void testOverlayCreateParamsFromBundle() {
-        assertThat(OverlayCreateParams.fromBundle(new Bundle())).isNull();
-
-        assertThat(OverlayCreateParams.fromBundle(createOverlayCreateParamsTestBundle()))
-                .isEqualTo(TEST_OVERLAY_CREATE_PARAMS);
-    }
-
-    @Test
     public void testStartActivity_overlayFeatureDisabled_notInvokeCreateOverlayContainer() {
         mSetFlagRule.disableFlags(Flags.FLAG_ACTIVITY_EMBEDDING_OVERLAY_PRESENTATION_FLAG);
 
-        mMonitor.onStartActivity(mActivity, mIntent, createOverlayCreateParamsTestBundle());
+        final Bundle optionsBundle = ActivityOptions.makeBasic().toBundle();
+        optionsBundle.putString(KEY_OVERLAY_TAG, "test");
+        mMonitor.onStartActivity(mActivity, mIntent, optionsBundle);
 
         verify(mSplitController, never()).createOrUpdateOverlayTaskFragmentIfNeeded(any(), any(),
-                anyInt(), any(), any());
-    }
-
-    @NonNull
-    private static Bundle createOverlayCreateParamsTestBundle() {
-        final Bundle bundle = new Bundle();
-
-        final Bundle paramsBundle = new Bundle();
-        paramsBundle.putInt(KEY_OVERLAY_CREATE_PARAMS_TASK_ID,
-                TEST_OVERLAY_CREATE_PARAMS.getTaskId());
-        paramsBundle.putString(KEY_OVERLAY_CREATE_PARAMS_TAG, TEST_OVERLAY_CREATE_PARAMS.getTag());
-        paramsBundle.putObject(KEY_OVERLAY_CREATE_PARAMS_BOUNDS,
-                TEST_OVERLAY_CREATE_PARAMS.getBounds());
-
-        bundle.putBundle(KEY_OVERLAY_CREATE_PARAMS, paramsBundle);
-
-        return bundle;
+                any(), any());
     }
 
     @Test
@@ -221,19 +194,11 @@
     }
 
     @Test
-    public void testCreateOrUpdateOverlayTaskFragmentIfNeeded_taskIdNotMatch_throwException() {
-        assertThrows("The method must return null due to task mismatch between"
-                + " launchingActivity and OverlayCreateParams", IllegalArgumentException.class,
-                () -> createOrUpdateOverlayTaskFragmentIfNeeded(
-                        TEST_OVERLAY_CREATE_PARAMS, TASK_ID + 1));
-    }
-
-    @Test
     public void testCreateOrUpdateOverlayTaskFragmentIfNeeded_anotherTagInTask_dismissOverlay() {
         createExistingOverlayContainers();
 
-        final TaskFragmentContainer overlayContainer = createOrUpdateOverlayTaskFragmentIfNeeded(
-                new OverlayCreateParams(TASK_ID, "test3", new Rect(0, 0, 100, 100)), TASK_ID);
+        final TaskFragmentContainer overlayContainer =
+                createOrUpdateOverlayTaskFragmentIfNeeded("test3");
 
         assertWithMessage("overlayContainer1 must be dismissed since the new overlay container"
                 + " is launched to the same task")
@@ -245,9 +210,9 @@
     public void testCreateOrUpdateOverlayTaskFragmentIfNeeded_sameTagAnotherTask_dismissOverlay() {
         createExistingOverlayContainers();
 
-        final TaskFragmentContainer overlayContainer = createOrUpdateOverlayTaskFragmentIfNeeded(
-                new OverlayCreateParams(TASK_ID + 2, "test1", new Rect(0, 0, 100, 100)),
-                TASK_ID + 2);
+        doReturn(TASK_ID + 2).when(mActivity).getTaskId();
+        final TaskFragmentContainer overlayContainer =
+                createOrUpdateOverlayTaskFragmentIfNeeded("test1");
 
         assertWithMessage("overlayContainer1 must be dismissed since the new overlay container"
                 + " is launched with the same tag as an existing overlay container in a different "
@@ -261,9 +226,10 @@
         createExistingOverlayContainers();
 
         final Rect bounds = new Rect(0, 0, 100, 100);
+        mSplitController.setActivityStackAttributesCalculator(params ->
+                new ActivityStackAttributes.Builder().setRelativeBounds(bounds).build());
         final TaskFragmentContainer overlayContainer = createOrUpdateOverlayTaskFragmentIfNeeded(
-                new OverlayCreateParams(TASK_ID, "test1", bounds),
-                TASK_ID);
+                "test1");
 
         assertWithMessage("overlayContainer1 must be updated since the new overlay container"
                 + " is launched with the same tag and task")
@@ -279,9 +245,8 @@
     public void testCreateOrUpdateOverlayTaskFragmentIfNeeded_dismissMultipleOverlays() {
         createExistingOverlayContainers();
 
-        final TaskFragmentContainer overlayContainer = createOrUpdateOverlayTaskFragmentIfNeeded(
-                new OverlayCreateParams(TASK_ID, "test2", new Rect(0, 0, 100, 100)),
-                TASK_ID);
+        final TaskFragmentContainer overlayContainer =
+                createOrUpdateOverlayTaskFragmentIfNeeded("test2");
 
         // OverlayContainer1 is dismissed since new container is launched in the same task with
         // different tag. OverlayContainer2 is dismissed since new container is launched with the
@@ -304,8 +269,11 @@
         mIntent.setComponent(new ComponentName(ApplicationProvider.getApplicationContext(),
                 MinimumDimensionActivity.class));
 
-        final TaskFragmentContainer overlayContainer = createOrUpdateOverlayTaskFragmentIfNeeded(
-                TEST_OVERLAY_CREATE_PARAMS, TASK_ID);
+        final Rect bounds = new Rect(0, 0, 100, 100);
+        mSplitController.setActivityStackAttributesCalculator(params ->
+                new ActivityStackAttributes.Builder().setRelativeBounds(bounds).build());
+        final TaskFragmentContainer overlayContainer =
+                createOrUpdateOverlayTaskFragmentIfNeeded("test");
         final IBinder overlayToken = overlayContainer.getTaskFragmentToken();
 
         assertThat(mSplitController.getAllOverlayTaskFragmentContainers())
@@ -316,7 +284,7 @@
 
         // Call createOrUpdateOverlayTaskFragmentIfNeeded again to check the update case.
         clearInvocations(mSplitPresenter);
-        createOrUpdateOverlayTaskFragmentIfNeeded(TEST_OVERLAY_CREATE_PARAMS, TASK_ID);
+        createOrUpdateOverlayTaskFragmentIfNeeded("test");
 
         verify(mSplitPresenter).resizeTaskFragment(mTransaction, overlayToken, new Rect());
         verify(mSplitPresenter).setTaskFragmentIsolatedNavigation(mTransaction, overlayToken,
@@ -329,11 +297,11 @@
     public void testCreateOrUpdateOverlayTaskFragmentIfNeeded_notInTaskBounds_expandOverlay() {
         final Rect bounds = new Rect(TASK_BOUNDS);
         bounds.offset(10, 10);
-        final OverlayCreateParams paramsOutsideTaskBounds = new OverlayCreateParams(TASK_ID,
-                "test", bounds);
+        mSplitController.setActivityStackAttributesCalculator(params ->
+                new ActivityStackAttributes.Builder().setRelativeBounds(bounds).build());
 
-        final TaskFragmentContainer overlayContainer = createOrUpdateOverlayTaskFragmentIfNeeded(
-                paramsOutsideTaskBounds, TASK_ID);
+        final TaskFragmentContainer overlayContainer =
+                createOrUpdateOverlayTaskFragmentIfNeeded("test");
         final IBinder overlayToken = overlayContainer.getTaskFragmentToken();
 
         assertThat(mSplitController.getAllOverlayTaskFragmentContainers())
@@ -344,7 +312,7 @@
 
         // Call createOrUpdateOverlayTaskFragmentIfNeeded again to check the update case.
         clearInvocations(mSplitPresenter);
-        createOrUpdateOverlayTaskFragmentIfNeeded(paramsOutsideTaskBounds, TASK_ID);
+        createOrUpdateOverlayTaskFragmentIfNeeded("test");
 
         verify(mSplitPresenter).resizeTaskFragment(mTransaction, overlayToken, new Rect());
         verify(mSplitPresenter).setTaskFragmentIsolatedNavigation(mTransaction, overlayToken,
@@ -355,15 +323,17 @@
 
     @Test
     public void testCreateOrUpdateOverlayTaskFragmentIfNeeded_createOverlay() {
-        final TaskFragmentContainer overlayContainer = createOrUpdateOverlayTaskFragmentIfNeeded(
-                TEST_OVERLAY_CREATE_PARAMS, TASK_ID);
+        final Rect bounds = new Rect(0, 0, 100, 100);
+        mSplitController.setActivityStackAttributesCalculator(params ->
+                new ActivityStackAttributes.Builder().setRelativeBounds(bounds).build());
+        final TaskFragmentContainer overlayContainer =
+                createOrUpdateOverlayTaskFragmentIfNeeded("test");
 
         assertThat(mSplitController.getAllOverlayTaskFragmentContainers())
                 .containsExactly(overlayContainer);
         assertThat(overlayContainer.getTaskId()).isEqualTo(TASK_ID);
-        assertThat(overlayContainer
-                .areLastRequestedBoundsEqual(TEST_OVERLAY_CREATE_PARAMS.getBounds())).isTrue();
-        assertThat(overlayContainer.getOverlayTag()).isEqualTo(TEST_OVERLAY_CREATE_PARAMS.getTag());
+        assertThat(overlayContainer.areLastRequestedBoundsEqual(bounds)).isTrue();
+        assertThat(overlayContainer.getOverlayTag()).isEqualTo("test");
     }
 
     @Test
@@ -416,12 +386,14 @@
 
     @Test
     public void testGetTopNonFinishingActivityWithOverlay() {
-        createTestOverlayContainer(TASK_ID, "test1");
+        TaskFragmentContainer overlayContainer = createTestOverlayContainer(TASK_ID, "test1");
+
         final Activity activity = createMockActivity();
         final TaskFragmentContainer container = createMockTaskFragmentContainer(activity);
         final TaskContainer task = container.getTaskContainer();
 
-        assertThat(task.getTopNonFinishingActivity(true /* includeOverlay */)).isEqualTo(mActivity);
+        assertThat(task.getTopNonFinishingActivity(true /* includeOverlay */))
+                .isEqualTo(overlayContainer.getTopNonFinishingActivity());
         assertThat(task.getTopNonFinishingActivity(false /* includeOverlay */)).isEqualTo(activity);
     }
 
@@ -458,10 +430,11 @@
      * #createOrUpdateOverlayTaskFragmentIfNeeded}
      */
     @Nullable
-    private TaskFragmentContainer createOrUpdateOverlayTaskFragmentIfNeeded(
-            @NonNull OverlayCreateParams params, int taskId) {
-        return mSplitController.createOrUpdateOverlayTaskFragmentIfNeeded(mTransaction, params,
-                taskId, mIntent, mActivity);
+    private TaskFragmentContainer createOrUpdateOverlayTaskFragmentIfNeeded(@NonNull String tag) {
+        final Bundle launchOptions = new Bundle();
+        launchOptions.putString(KEY_OVERLAY_TAG, tag);
+        return mSplitController.createOrUpdateOverlayTaskFragmentIfNeeded(mTransaction,
+                launchOptions, mIntent, mActivity);
     }
 
     /** Creates a mock TaskFragment that has been registered and appeared in the organizer. */
@@ -475,10 +448,11 @@
 
     @NonNull
     private TaskFragmentContainer createTestOverlayContainer(int taskId, @NonNull String tag) {
+        Activity activity = createMockActivity();
         TaskFragmentContainer overlayContainer = mSplitController.newContainer(
-                null /* pendingAppearedActivity */, mIntent, mActivity, taskId,
-                null /* pairedPrimaryContainer */, tag);
-        setupTaskFragmentInfo(overlayContainer, mActivity);
+                null /* pendingAppearedActivity */, mIntent, activity, taskId,
+                null /* pairedPrimaryContainer */, tag, Bundle.EMPTY);
+        setupTaskFragmentInfo(overlayContainer, activity);
         return overlayContainer;
     }
 
diff --git a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitControllerTest.java b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitControllerTest.java
index 8c274a2..bab4e91 100644
--- a/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitControllerTest.java
+++ b/libs/WindowManager/Jetpack/tests/unittest/src/androidx/window/extensions/embedding/SplitControllerTest.java
@@ -590,7 +590,7 @@
 
         assertFalse(result);
         verify(mSplitController, never()).newContainer(any(), any(), any(), anyInt(), any(),
-                anyString());
+                anyString(), any());
     }
 
     @Test
@@ -753,7 +753,7 @@
 
         assertTrue(result);
         verify(mSplitController, never()).newContainer(any(), any(), any(), anyInt(), any(),
-                anyString());
+                anyString(), any());
         verify(mSplitController, never()).registerSplit(any(), any(), any(), any(), any(), any());
     }
 
@@ -796,7 +796,7 @@
 
         assertTrue(result);
         verify(mSplitController, never()).newContainer(any(), any(), any(), anyInt(), any(),
-                anyString());
+                anyString(), any());
         verify(mSplitController, never()).registerSplit(any(), any(), any(), any(), any(), any());
     }
 
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarAnimationHelper.java b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarAnimationHelper.java
index f794fef..893a87f 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarAnimationHelper.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarAnimationHelper.java
@@ -48,12 +48,14 @@
     private static final float EXPANDED_VIEW_ANIMATE_OUT_SCALE_AMOUNT = .75f;
     private static final int EXPANDED_VIEW_ALPHA_ANIMATION_DURATION = 150;
     private static final int EXPANDED_VIEW_SNAP_TO_DISMISS_DURATION = 100;
+    private static final int EXPANDED_VIEW_ANIMATE_POSITION_DURATION = 300;
+    private static final int EXPANDED_VIEW_DISMISS_DURATION = 250;
+    private static final int EXPANDED_VIEW_DRAG_ANIMATION_DURATION = 150;
     /**
      * Additional scale applied to expanded view when it is positioned inside a magnetic target.
      */
-    private static final float EXPANDED_VIEW_IN_TARGET_SCALE = 0.75f;
-    private static final int EXPANDED_VIEW_ANIMATE_POSITION_DURATION = 300;
-    private static final int EXPANDED_VIEW_DISMISS_DURATION = 250;
+    private static final float EXPANDED_VIEW_IN_TARGET_SCALE = 0.6f;
+    private static final float EXPANDED_VIEW_DRAG_SCALE = 0.5f;
 
     /** Spring config for the expanded view scale-in animation. */
     private final PhysicsAnimator.SpringConfig mScaleInSpringConfig =
@@ -72,6 +74,7 @@
     private final Context mContext;
     private final BubbleBarLayerView mLayerView;
     private final BubblePositioner mPositioner;
+    private final int[] mTmpLocation = new int[2];
 
     private BubbleViewProvider mExpandedBubble;
     private boolean mIsExpanded = false;
@@ -220,6 +223,25 @@
     }
 
     /**
+     * Animate the expanded bubble when it is being dragged
+     */
+    public void animateStartDrag() {
+        final BubbleBarExpandedView bbev = getExpandedView();
+        if (bbev == null) {
+            Log.w(TAG, "Trying to animate start drag without a bubble");
+            return;
+        }
+        bbev.setPivotX(bbev.getWidth() / 2f);
+        bbev.setPivotY(0f);
+        bbev.animate()
+                .scaleX(EXPANDED_VIEW_DRAG_SCALE)
+                .scaleY(EXPANDED_VIEW_DRAG_SCALE)
+                .setInterpolator(Interpolators.EMPHASIZED)
+                .setDuration(EXPANDED_VIEW_DRAG_ANIMATION_DURATION)
+                .start();
+    }
+
+    /**
      * Animates dismissal of currently expanded bubble
      *
      * @param endRunnable a runnable to run at the end of the animation
@@ -261,7 +283,10 @@
                 .setDuration(EXPANDED_VIEW_ANIMATE_POSITION_DURATION)
                 .setInterpolator(Interpolators.EMPHASIZED_DECELERATE)
                 .withStartAction(() -> bbev.setAnimating(true))
-                .withEndAction(() -> bbev.setAnimating(false))
+                .withEndAction(() -> {
+                    bbev.setAnimating(false);
+                    bbev.resetPivot();
+                })
                 .start();
     }
 
@@ -277,25 +302,52 @@
             Log.w(TAG, "Trying to snap the expanded view to target without a bubble");
             return;
         }
-        Point expandedViewCenter = getViewCenterOnScreen(bbev);
-
-        // Calculate the difference between the target's center coordinates and the object's.
-        // Animating the object's x/y properties by these values will center the object on top
-        // of the magnetic target.
-        float xDiff = target.getCenterOnScreen().x - expandedViewCenter.x;
-        float yDiff = target.getCenterOnScreen().y - expandedViewCenter.y;
 
         // Calculate scale of expanded view so it fits inside the magnetic target
         float bbevMaxSide = Math.max(bbev.getWidth(), bbev.getHeight());
-        float targetMaxSide = Math.max(target.getTargetView().getWidth(),
-                target.getTargetView().getHeight());
-        float scale = (targetMaxSide * EXPANDED_VIEW_IN_TARGET_SCALE) / bbevMaxSide;
+        View targetView = target.getTargetView();
+        float targetMaxSide = Math.max(targetView.getWidth(), targetView.getHeight());
+        // Reduce target size to have some padding between the target and expanded view
+        targetMaxSide *= EXPANDED_VIEW_IN_TARGET_SCALE;
+        float scaleInTarget = targetMaxSide / bbevMaxSide;
+
+        // Scale around the top center of the expanded view. Same as when dragging.
+        bbev.setPivotX(bbev.getWidth() / 2f);
+        bbev.setPivotY(0);
+
+        // When the view animates into the target, it is scaled down with the pivot at center top.
+        // Find the point on the view that would be the center of the view at its final scale.
+        // Once we know that, we can calculate x and y distance from the center of the target view
+        // and use that for the translation animation to ensure that the view at final scale is
+        // placed at the center of the target.
+
+        // Set mTmpLocation to the current location of the view on the screen, taking into account
+        // any scale applied.
+        bbev.getLocationOnScreen(mTmpLocation);
+        // Since pivotX is at the center of the x-axis, even at final scale, center of the view on
+        // x-axis will be the same as the center of the view at current size.
+        // Get scaled width of the view and adjust mTmpLocation so that point on x-axis is at the
+        // center of the view at its current size.
+        float currentWidth = bbev.getWidth() * bbev.getScaleX();
+        mTmpLocation[0] += currentWidth / 2;
+        // Since pivotY is at the top of the view, at final scale, top coordinate of the view
+        // remains the same.
+        // Get height of the view at final scale and adjust mTmpLocation so that point on y-axis is
+        // moved down by half of the height at final scale.
+        float targetHeight = bbev.getHeight() * scaleInTarget;
+        mTmpLocation[1] += targetHeight / 2;
+        // mTmpLocation is now set to the point on the view that will be the center of the view once
+        // scale is applied.
+
+        // Calculate the difference between the target's center coordinates and mTmpLocation
+        float xDiff = target.getCenterOnScreen().x - mTmpLocation[0];
+        float yDiff = target.getCenterOnScreen().y - mTmpLocation[1];
 
         bbev.animate()
                 .translationX(bbev.getTranslationX() + xDiff)
                 .translationY(bbev.getTranslationY() + yDiff)
-                .scaleX(scale)
-                .scaleY(scale)
+                .scaleX(scaleInTarget)
+                .scaleY(scaleInTarget)
                 .setDuration(EXPANDED_VIEW_SNAP_TO_DISMISS_DURATION)
                 .setInterpolator(Interpolators.EMPHASIZED)
                 .withStartAction(() -> bbev.setAnimating(true))
@@ -319,8 +371,8 @@
         }
         expandedView
                 .animate()
-                .scaleX(1f)
-                .scaleY(1f)
+                .scaleX(EXPANDED_VIEW_DRAG_SCALE)
+                .scaleY(EXPANDED_VIEW_DRAG_SCALE)
                 .setDuration(EXPANDED_VIEW_SNAP_TO_DISMISS_DURATION)
                 .setInterpolator(Interpolators.EMPHASIZED)
                 .withStartAction(() -> expandedView.setAnimating(true))
@@ -385,12 +437,4 @@
         final int height = mPositioner.getExpandedViewHeightForBubbleBar(isOverflowExpanded);
         return new Size(width, height);
     }
-
-    private Point getViewCenterOnScreen(View view) {
-        Point center = new Point();
-        int[] onScreenLocation = view.getLocationOnScreen();
-        center.x = (int) (onScreenLocation[0] + (view.getWidth() / 2f));
-        center.y = (int) (onScreenLocation[1] + (view.getHeight() / 2f));
-        return center;
-    }
 }
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedViewDragController.kt b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedViewDragController.kt
index d215450..5e634a2 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedViewDragController.kt
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/bubbles/bar/BubbleBarExpandedViewDragController.kt
@@ -74,6 +74,9 @@
     }
 
     private inner class HandleDragListener : RelativeTouchListener() {
+
+        private var isMoving = false
+
         override fun onDown(v: View, ev: MotionEvent): Boolean {
             // While animating, don't allow new touch events
             return !expandedView.isAnimating
@@ -87,6 +90,10 @@
             dx: Float,
             dy: Float
         ) {
+            if (!isMoving) {
+                isMoving = true
+                animationHelper.animateStartDrag()
+            }
             expandedView.translationX = expandedViewInitialTranslationX + dx
             expandedView.translationY = expandedViewInitialTranslationY + dy
             dismissView.show()
@@ -114,6 +121,7 @@
                 animationHelper.animateToRestPosition()
                 dismissView.hide()
             }
+            isMoving = false
         }
     }
 
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java
index af69b52..b0d8b47 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/transition/Transitions.java
@@ -757,6 +757,10 @@
             }
             if (!change.hasFlags(FLAG_IS_OCCLUDED)) {
                 allOccluded = false;
+            } else if (change.hasAllFlags(TransitionInfo.FLAGS_IS_OCCLUDED_NO_ANIMATION)) {
+                // Remove the change because it should be invisible in the animation.
+                info.getChanges().remove(i);
+                continue;
             }
             // The change has already animated by back gesture, don't need to play transition
             // animation on it.
diff --git a/media/java/android/media/IMediaRouter2.aidl b/media/java/android/media/IMediaRouter2.aidl
index 29bfd1a..e2dddad 100644
--- a/media/java/android/media/IMediaRouter2.aidl
+++ b/media/java/android/media/IMediaRouter2.aidl
@@ -19,6 +19,7 @@
 import android.media.MediaRoute2Info;
 import android.media.RoutingSessionInfo;
 import android.os.Bundle;
+import android.os.UserHandle;
 
 /**
  * @hide
@@ -35,5 +36,6 @@
      * Call MediaRouterService#requestCreateSessionWithRouter2 to pass the result.
      */
     void requestCreateSessionByManager(long uniqueRequestId, in RoutingSessionInfo oldSession,
-        in MediaRoute2Info route);
+        in MediaRoute2Info route, in UserHandle transferInitiatorUserHandle,
+        in String transferInitiatorPackageName);
 }
diff --git a/media/java/android/media/IMediaRouterService.aidl b/media/java/android/media/IMediaRouterService.aidl
index fa4d1a1..04e99ea 100644
--- a/media/java/android/media/IMediaRouterService.aidl
+++ b/media/java/android/media/IMediaRouterService.aidl
@@ -64,7 +64,8 @@
 
     void requestCreateSessionWithRouter2(IMediaRouter2 router, int requestId, long managerRequestId,
             in RoutingSessionInfo oldSession, in MediaRoute2Info route,
-            in @nullable Bundle sessionHints);
+            in @nullable Bundle sessionHints, in UserHandle transferInitiatorUserHandle,
+            in String transferInitiatorPackageName);
     void selectRouteWithRouter2(IMediaRouter2 router, String sessionId, in MediaRoute2Info route);
     void deselectRouteWithRouter2(IMediaRouter2 router, String sessionId, in MediaRoute2Info route);
     void transferToRouteWithRouter2(IMediaRouter2 router, String sessionId,
@@ -84,13 +85,16 @@
     void stopScan(IMediaRouter2Manager manager);
 
     void requestCreateSessionWithManager(IMediaRouter2Manager manager, int requestId,
-            in RoutingSessionInfo oldSession, in @nullable MediaRoute2Info route);
+            in RoutingSessionInfo oldSession, in @nullable MediaRoute2Info route,
+            in UserHandle transferInitiatorUserHandle, in String transferInitiatorPackageName);
     void selectRouteWithManager(IMediaRouter2Manager manager, int requestId,
             String sessionId, in MediaRoute2Info route);
     void deselectRouteWithManager(IMediaRouter2Manager manager, int requestId,
             String sessionId, in MediaRoute2Info route);
+    @JavaPassthrough(annotation="@android.annotation.RequiresPermission(android.Manifest.permission.MEDIA_CONTENT_CONTROL)")
     void transferToRouteWithManager(IMediaRouter2Manager manager, int requestId,
-            String sessionId, in MediaRoute2Info route);
+            String sessionId, in MediaRoute2Info route,
+            in UserHandle transferInitiatorUserHandle, String transferInitiatorPackageName);
     void setSessionVolumeWithManager(IMediaRouter2Manager manager, int requestId,
             String sessionId, int volume);
     void releaseSessionWithManager(IMediaRouter2Manager manager, int requestId, String sessionId);
diff --git a/media/java/android/media/MediaRoute2Info.java b/media/java/android/media/MediaRoute2Info.java
index 8ad3587..0eabe66 100644
--- a/media/java/android/media/MediaRoute2Info.java
+++ b/media/java/android/media/MediaRoute2Info.java
@@ -19,6 +19,7 @@
 import static android.media.MediaRouter2Utils.toUniqueId;
 
 import static com.android.media.flags.Flags.FLAG_ENABLE_AUDIO_POLICIES_DEVICE_AND_BLUETOOTH_CONTROLLER;
+import static com.android.media.flags.Flags.FLAG_ENABLE_BUILT_IN_SPEAKER_ROUTE_SUITABILITY_STATUSES;
 import static com.android.media.flags.Flags.FLAG_ENABLE_NEW_MEDIA_ROUTE_2_INFO_TYPES;
 
 import android.annotation.FlaggedApi;
@@ -479,6 +480,37 @@
     public static final String FEATURE_REMOTE_GROUP_PLAYBACK =
             "android.media.route.feature.REMOTE_GROUP_PLAYBACK";
 
+    /** Indicates the route is always suitable for media playback. */
+    @FlaggedApi(FLAG_ENABLE_BUILT_IN_SPEAKER_ROUTE_SUITABILITY_STATUSES)
+    public static final int SUITABILITY_STATUS_SUITABLE_FOR_DEFAULT_TRANSFER = 0;
+
+    /**
+     * Indicates that the route is suitable for media playback only after explicit user selection.
+     */
+    @FlaggedApi(FLAG_ENABLE_BUILT_IN_SPEAKER_ROUTE_SUITABILITY_STATUSES)
+    public static final int SUITABILITY_STATUS_SUITABLE_FOR_MANUAL_TRANSFER = 1;
+
+    /** Indicates that the route is never suitable for media playback. */
+    @FlaggedApi(FLAG_ENABLE_BUILT_IN_SPEAKER_ROUTE_SUITABILITY_STATUSES)
+    public static final int SUITABILITY_STATUS_NOT_SUITABLE_FOR_TRANSFER = 2;
+
+    /**
+     * Route suitability status.
+     *
+     * <p>Signals whether the route is suitable to play media.
+     *
+     * @hide
+     */
+    @IntDef(
+            value = {
+                SUITABILITY_STATUS_SUITABLE_FOR_DEFAULT_TRANSFER,
+                SUITABILITY_STATUS_SUITABLE_FOR_MANUAL_TRANSFER,
+                SUITABILITY_STATUS_NOT_SUITABLE_FOR_TRANSFER
+            })
+    @Retention(RetentionPolicy.SOURCE)
+    @FlaggedApi(FLAG_ENABLE_BUILT_IN_SPEAKER_ROUTE_SUITABILITY_STATUSES)
+    public @interface SuitabilityStatus {}
+
     private final String mId;
     private final CharSequence mName;
     private final List<String> mFeatures;
@@ -500,6 +532,7 @@
     private final String mProviderId;
     private final boolean mIsVisibilityRestricted;
     private final Set<String> mAllowedPackages;
+    @SuitabilityStatus private final int mSuitabilityStatus;
 
     MediaRoute2Info(@NonNull Builder builder) {
         mId = builder.mId;
@@ -521,6 +554,7 @@
         mProviderId = builder.mProviderId;
         mIsVisibilityRestricted = builder.mIsVisibilityRestricted;
         mAllowedPackages = builder.mAllowedPackages;
+        mSuitabilityStatus = builder.mSuitabilityStatus;
     }
 
     MediaRoute2Info(@NonNull Parcel in) {
@@ -544,6 +578,7 @@
         mProviderId = in.readString();
         mIsVisibilityRestricted = in.readBoolean();
         mAllowedPackages = Set.of(in.createString8Array());
+        mSuitabilityStatus = in.readInt();
     }
 
     /**
@@ -778,6 +813,13 @@
                 || mAllowedPackages.contains(packageName);
     }
 
+    /** Returns the route suitability status. */
+    @SuitabilityStatus
+    @FlaggedApi(FLAG_ENABLE_BUILT_IN_SPEAKER_ROUTE_SUITABILITY_STATUSES)
+    public int getSuitabilityStatus() {
+        return mSuitabilityStatus;
+    }
+
     /**
      * Dumps the current state of the object to the given {@code pw} as a human-readable string.
      *
@@ -809,6 +851,7 @@
         pw.println(indent + "mProviderId=" + mProviderId);
         pw.println(indent + "mIsVisibilityRestricted=" + mIsVisibilityRestricted);
         pw.println(indent + "mAllowedPackages=" + mAllowedPackages);
+        pw.println(indent + "mSuitabilityStatus=" + mSuitabilityStatus);
     }
 
     private void dumpVolume(@NonNull PrintWriter pw, @NonNull String prefix) {
@@ -861,39 +904,74 @@
                 && Objects.equals(mDeduplicationIds, other.mDeduplicationIds)
                 && Objects.equals(mProviderId, other.mProviderId)
                 && (mIsVisibilityRestricted == other.mIsVisibilityRestricted)
-                && Objects.equals(mAllowedPackages, other.mAllowedPackages);
+                && Objects.equals(mAllowedPackages, other.mAllowedPackages)
+                && mSuitabilityStatus == other.mSuitabilityStatus;
     }
 
     @Override
     public int hashCode() {
         // Note: mExtras is not included.
-        return Objects.hash(mId, mName, mFeatures, mType, mIsSystem, mIconUri, mDescription,
-                mConnectionState, mClientPackageName, mPackageName, mVolumeHandling, mVolumeMax,
-                mVolume, mAddress, mDeduplicationIds, mProviderId, mIsVisibilityRestricted,
-                mAllowedPackages);
+        return Objects.hash(
+                mId,
+                mName,
+                mFeatures,
+                mType,
+                mIsSystem,
+                mIconUri,
+                mDescription,
+                mConnectionState,
+                mClientPackageName,
+                mPackageName,
+                mVolumeHandling,
+                mVolumeMax,
+                mVolume,
+                mAddress,
+                mDeduplicationIds,
+                mProviderId,
+                mIsVisibilityRestricted,
+                mAllowedPackages,
+                mSuitabilityStatus);
     }
 
     @Override
     public String toString() {
         // Note: mExtras is not printed here.
-        StringBuilder result = new StringBuilder()
-                .append("MediaRoute2Info{ ")
-                .append("id=").append(getId())
-                .append(", name=").append(getName())
-                .append(", features=").append(getFeatures())
-                .append(", iconUri=").append(getIconUri())
-                .append(", description=").append(getDescription())
-                .append(", connectionState=").append(getConnectionState())
-                .append(", clientPackageName=").append(getClientPackageName())
-                .append(", volumeHandling=").append(getVolumeHandling())
-                .append(", volumeMax=").append(getVolumeMax())
-                .append(", volume=").append(getVolume())
-                .append(", address=").append(getAddress())
-                .append(", deduplicationIds=").append(String.join(",", getDeduplicationIds()))
-                .append(", providerId=").append(getProviderId())
-                .append(", isVisibilityRestricted=").append(mIsVisibilityRestricted)
-                .append(", allowedPackages=").append(String.join(",", mAllowedPackages))
-                .append(" }");
+        StringBuilder result =
+                new StringBuilder()
+                        .append("MediaRoute2Info{ ")
+                        .append("id=")
+                        .append(getId())
+                        .append(", name=")
+                        .append(getName())
+                        .append(", features=")
+                        .append(getFeatures())
+                        .append(", iconUri=")
+                        .append(getIconUri())
+                        .append(", description=")
+                        .append(getDescription())
+                        .append(", connectionState=")
+                        .append(getConnectionState())
+                        .append(", clientPackageName=")
+                        .append(getClientPackageName())
+                        .append(", volumeHandling=")
+                        .append(getVolumeHandling())
+                        .append(", volumeMax=")
+                        .append(getVolumeMax())
+                        .append(", volume=")
+                        .append(getVolume())
+                        .append(", address=")
+                        .append(getAddress())
+                        .append(", deduplicationIds=")
+                        .append(String.join(",", getDeduplicationIds()))
+                        .append(", providerId=")
+                        .append(getProviderId())
+                        .append(", isVisibilityRestricted=")
+                        .append(mIsVisibilityRestricted)
+                        .append(", allowedPackages=")
+                        .append(String.join(",", mAllowedPackages))
+                        .append(", suitabilityStatus=")
+                        .append(mSuitabilityStatus)
+                        .append(" }");
         return result.toString();
     }
 
@@ -923,6 +1001,7 @@
         dest.writeString(mProviderId);
         dest.writeBoolean(mIsVisibilityRestricted);
         dest.writeString8Array(mAllowedPackages.toArray(new String[0]));
+        dest.writeInt(mSuitabilityStatus);
     }
 
     private static String getDeviceTypeString(@Type int deviceType) {
@@ -1005,6 +1084,7 @@
         private String mProviderId;
         private boolean mIsVisibilityRestricted;
         private Set<String> mAllowedPackages;
+        @SuitabilityStatus private int mSuitabilityStatus;
 
         /**
          * Constructor for builder to create {@link MediaRoute2Info}.
@@ -1028,6 +1108,7 @@
             mFeatures = new ArrayList<>();
             mDeduplicationIds = Set.of();
             mAllowedPackages = Set.of();
+            mSuitabilityStatus = SUITABILITY_STATUS_SUITABLE_FOR_DEFAULT_TRANSFER;
         }
 
         /**
@@ -1075,6 +1156,7 @@
             mProviderId = routeInfo.mProviderId;
             mIsVisibilityRestricted = routeInfo.mIsVisibilityRestricted;
             mAllowedPackages = routeInfo.mAllowedPackages;
+            mSuitabilityStatus = routeInfo.mSuitabilityStatus;
         }
 
         /**
@@ -1318,6 +1400,23 @@
         }
 
         /**
+         * Sets route suitability status.
+         *
+         * <p>The default value is {@link
+         * MediaRoute2Info#SUITABILITY_STATUS_SUITABLE_FOR_DEFAULT_TRANSFER}.
+         *
+         * <p> Apps are not supposed to set {@link
+         * MediaRoute2Info#SUITABILITY_STATUS_NOT_SUITABLE_FOR_TRANSFER}. Publishing a non-system
+         * route with such status throws {@link SecurityException}.
+         */
+        @NonNull
+        @FlaggedApi(FLAG_ENABLE_BUILT_IN_SPEAKER_ROUTE_SUITABILITY_STATUSES)
+        public Builder setSuitabilityStatus(@SuitabilityStatus int suitabilityStatus) {
+            mSuitabilityStatus = suitabilityStatus;
+            return this;
+        }
+
+        /**
          * Builds the {@link MediaRoute2Info media route info}.
          *
          * @throws IllegalArgumentException if no features are added.
diff --git a/media/java/android/media/MediaRouter2.java b/media/java/android/media/MediaRouter2.java
index ba26df9..5e23551 100644
--- a/media/java/android/media/MediaRouter2.java
+++ b/media/java/android/media/MediaRouter2.java
@@ -17,6 +17,7 @@
 package android.media;
 
 import static com.android.internal.util.function.pooled.PooledLambda.obtainMessage;
+import static com.android.media.flags.Flags.FLAG_ENABLE_BUILT_IN_SPEAKER_ROUTE_SUITABILITY_STATUSES;
 import static com.android.media.flags.Flags.FLAG_ENABLE_RLP_CALLBACKS_IN_MEDIA_ROUTER2;
 import static com.android.media.flags.Flags.FLAG_ENABLE_CROSS_USER_ROUTING_IN_MEDIA_ROUTER2;
 
@@ -699,15 +700,48 @@
      * @hide
      */
     @SystemApi
-    @RequiresPermission(Manifest.permission.MEDIA_CONTENT_CONTROL)
+    @RequiresPermission(android.Manifest.permission.MEDIA_CONTENT_CONTROL)
     public void transfer(@NonNull RoutingController controller, @NonNull MediaRoute2Info route) {
-        mImpl.transfer(controller.getRoutingSessionInfo(), route);
+        mImpl.transfer(
+                controller.getRoutingSessionInfo(),
+                route,
+                android.os.Process.myUserHandle(),
+                mContext.getPackageName());
+    }
+
+    /**
+     * Transfers the media of a routing controller to the given route.
+     *
+     * <p>This will be no-op for non-system media routers.
+     *
+     * @param controller a routing controller controlling media routing.
+     * @param route the route you want to transfer the media to.
+     * @param transferInitiatorUserHandle the user handle of the app that initiated the transfer
+     *     request.
+     * @param transferInitiatorPackageName the package name of the app that initiated the transfer.
+     *     This value is used with the user handle to populate {@link
+     *     RoutingController#wasTransferRequestedBySelf()}.
+     * @hide
+     */
+    @FlaggedApi(FLAG_ENABLE_BUILT_IN_SPEAKER_ROUTE_SUITABILITY_STATUSES)
+    public void transfer(
+            @NonNull RoutingController controller,
+            @NonNull MediaRoute2Info route,
+            @NonNull UserHandle transferInitiatorUserHandle,
+            @NonNull String transferInitiatorPackageName) {
+        mImpl.transfer(
+                controller.getRoutingSessionInfo(),
+                route,
+                transferInitiatorUserHandle,
+                transferInitiatorPackageName);
     }
 
     void requestCreateController(
             @NonNull RoutingController controller,
             @NonNull MediaRoute2Info route,
-            long managerRequestId) {
+            long managerRequestId,
+            @NonNull UserHandle transferInitiatorUserHandle,
+            @NonNull String transferInitiatorPackageName) {
 
         final int requestId = mNextRequestId.getAndIncrement();
 
@@ -736,7 +770,9 @@
                         managerRequestId,
                         controller.getRoutingSessionInfo(),
                         route,
-                        controllerHints);
+                        controllerHints,
+                        transferInitiatorUserHandle,
+                        transferInitiatorPackageName);
             } catch (RemoteException ex) {
                 Log.e(TAG, "createControllerForTransfer: "
                                 + "Failed to request for creating a controller.", ex);
@@ -1053,7 +1089,11 @@
     }
 
     void onRequestCreateControllerByManagerOnHandler(
-            RoutingSessionInfo oldSession, MediaRoute2Info route, long managerRequestId) {
+            RoutingSessionInfo oldSession,
+            MediaRoute2Info route,
+            long managerRequestId,
+            @NonNull UserHandle transferInitiatorUserHandle,
+            @NonNull String transferInitiatorPackageName) {
         Log.i(
                 TAG,
                 TextUtils.formatSimple(
@@ -1070,7 +1110,8 @@
         if (controller == null) {
             return;
         }
-        requestCreateController(controller, route, managerRequestId);
+        requestCreateController(controller, route, managerRequestId, transferInitiatorUserHandle,
+                transferInitiatorPackageName);
     }
 
     private List<MediaRoute2Info> getSortedRoutes(
@@ -1469,6 +1510,21 @@
         }
 
         /**
+         * Returns whether the transfer was requested by the calling app (as determined by comparing
+         * {@link UserHandle} and package name).
+         */
+        @FlaggedApi(FLAG_ENABLE_BUILT_IN_SPEAKER_ROUTE_SUITABILITY_STATUSES)
+        public boolean wasTransferRequestedBySelf() {
+            RoutingSessionInfo sessionInfo = getRoutingSessionInfo();
+
+            UserHandle transferInitiatorUserHandle = sessionInfo.getTransferInitiatorUserHandle();
+            String transferInitiatorPackageName = sessionInfo.getTransferInitiatorPackageName();
+
+            return Objects.equals(android.os.Process.myUserHandle(), transferInitiatorUserHandle)
+                    && Objects.equals(mContext.getPackageName(), transferInitiatorPackageName);
+        }
+
+        /**
          * Returns the current {@link RoutingSessionInfo} associated to this controller.
          */
         @NonNull
@@ -1980,14 +2036,20 @@
 
         @Override
         public void requestCreateSessionByManager(
-                long managerRequestId, RoutingSessionInfo oldSession, MediaRoute2Info route) {
+                long managerRequestId,
+                RoutingSessionInfo oldSession,
+                MediaRoute2Info route,
+                UserHandle transferInitiatorUserHandle,
+                String transferInitiatorPackageName) {
             mHandler.sendMessage(
                     obtainMessage(
                             MediaRouter2::onRequestCreateControllerByManagerOnHandler,
                             MediaRouter2.this,
                             oldSession,
                             route,
-                            managerRequestId));
+                            managerRequestId,
+                            transferInitiatorUserHandle,
+                            transferInitiatorPackageName));
         }
     }
 
@@ -2027,7 +2089,11 @@
 
         void stop();
 
-        void transfer(RoutingSessionInfo sessionInfo, MediaRoute2Info route);
+        void transfer(
+                @NonNull RoutingSessionInfo sessionInfo,
+                @NonNull MediaRoute2Info route,
+                @NonNull UserHandle transferInitiatorUserHandle,
+                @NonNull String transferInitiatorPackageName);
 
         List<RoutingController> getControllers();
 
@@ -2220,7 +2286,11 @@
 
             List<RoutingSessionInfo> sessionInfos = getRoutingSessions();
             RoutingSessionInfo targetSession = sessionInfos.get(sessionInfos.size() - 1);
-            transfer(targetSession, route);
+            transfer(
+                    targetSession,
+                    route,
+                    android.os.Process.myUserHandle(),
+                    mContext.getPackageName());
         }
 
         @Override
@@ -2243,14 +2313,24 @@
          *
          * @param sessionInfo The {@link RoutingSessionInfo routing session} to transfer.
          * @param route The {@link MediaRoute2Info route} to transfer to.
-         * @see #transferToRoute(RoutingSessionInfo, MediaRoute2Info)
+         * @param transferInitiatorUserHandle The user handle of the app that initiated the
+         *     transfer.
+         * @param transferInitiatorPackageName The package name if of the app that initiated the
+         *     transfer.
+         * @see #transferToRoute(RoutingSessionInfo, MediaRoute2Info, UserHandle, String)
          * @see #requestCreateSession(RoutingSessionInfo, MediaRoute2Info)
          */
         @Override
+        @SuppressWarnings("AndroidFrameworkRequiresPermission")
         public void transfer(
-                @NonNull RoutingSessionInfo sessionInfo, @NonNull MediaRoute2Info route) {
+                @NonNull RoutingSessionInfo sessionInfo,
+                @NonNull MediaRoute2Info route,
+                @NonNull UserHandle transferInitiatorUserHandle,
+                @NonNull String transferInitiatorPackageName) {
             Objects.requireNonNull(sessionInfo, "sessionInfo must not be null");
             Objects.requireNonNull(route, "route must not be null");
+            Objects.requireNonNull(transferInitiatorUserHandle);
+            Objects.requireNonNull(transferInitiatorPackageName);
 
             Log.v(
                     TAG,
@@ -2268,9 +2348,14 @@
             }
 
             if (sessionInfo.getTransferableRoutes().contains(route.getId())) {
-                transferToRoute(sessionInfo, route);
+                transferToRoute(
+                        sessionInfo,
+                        route,
+                        transferInitiatorUserHandle,
+                        transferInitiatorPackageName);
             } else {
-                requestCreateSession(sessionInfo, route);
+                requestCreateSession(sessionInfo, route, transferInitiatorUserHandle,
+                        transferInitiatorPackageName);
             }
         }
 
@@ -2282,21 +2367,30 @@
          * RoutingSessionInfo routing session's} {@link RoutingSessionInfo#getTransferableRoutes()
          * transferable routes list}. Otherwise, the request will fail.
          *
-         * <p>Use {@link #requestCreateSession(RoutingSessionInfo, MediaRoute2Info)} to request
-         * an out-of-session transfer.
+         * <p>Use {@link #requestCreateSession(RoutingSessionInfo, MediaRoute2Info)} to request an
+         * out-of-session transfer.
          *
          * @param session The {@link RoutingSessionInfo routing session} to transfer.
          * @param route The {@link MediaRoute2Info route} to transfer to. Must be one of the {@link
          *     RoutingSessionInfo routing session's} {@link
          *     RoutingSessionInfo#getTransferableRoutes() transferable routes}.
          */
+        @RequiresPermission(Manifest.permission.MEDIA_CONTENT_CONTROL)
         private void transferToRoute(
-                @NonNull RoutingSessionInfo session, @NonNull MediaRoute2Info route) {
+                @NonNull RoutingSessionInfo session,
+                @NonNull MediaRoute2Info route,
+                @NonNull UserHandle transferInitiatorUserHandle,
+                @NonNull String transferInitiatorPackageName) {
             int requestId = createTransferRequest(session, route);
 
             try {
                 mMediaRouterService.transferToRouteWithManager(
-                        mClient, requestId, session.getId(), route);
+                        mClient,
+                        requestId,
+                        session.getId(),
+                        route,
+                        transferInitiatorUserHandle,
+                        transferInitiatorPackageName);
             } catch (RemoteException ex) {
                 throw ex.rethrowFromSystemServer();
             }
@@ -2317,7 +2411,10 @@
          * @param route The {@link MediaRoute2Info route} to transfer to.
          */
         private void requestCreateSession(
-                @NonNull RoutingSessionInfo oldSession, @NonNull MediaRoute2Info route) {
+                @NonNull RoutingSessionInfo oldSession,
+                @NonNull MediaRoute2Info route,
+                @NonNull UserHandle transferInitiatorUserHandle,
+                @NonNull String transferInitiatorPackageName) {
             if (TextUtils.isEmpty(oldSession.getClientPackageName())) {
                 Log.w(TAG, "requestCreateSession: Can't create a session without package name.");
                 this.onTransferFailed(oldSession, route);
@@ -2328,7 +2425,12 @@
 
             try {
                 mMediaRouterService.requestCreateSessionWithManager(
-                        mClient, requestId, oldSession, route);
+                        mClient,
+                        requestId,
+                        oldSession,
+                        route,
+                        transferInitiatorUserHandle,
+                        transferInitiatorPackageName);
             } catch (RemoteException ex) {
                 throw ex.rethrowFromSystemServer();
             }
@@ -3055,7 +3157,8 @@
                 return;
             }
 
-            requestCreateController(controller, route, MANAGER_REQUEST_ID_NONE);
+            requestCreateController(controller, route, MANAGER_REQUEST_ID_NONE,
+                    android.os.Process.myUserHandle(), mContext.getPackageName());
         }
 
         @Override
@@ -3071,7 +3174,11 @@
          * #transferTo(MediaRoute2Info)}.
          */
         @Override
-        public void transfer(RoutingSessionInfo sessionInfo, MediaRoute2Info route) {
+        public void transfer(
+                @NonNull RoutingSessionInfo sessionInfo,
+                @NonNull MediaRoute2Info route,
+                @NonNull UserHandle transferInitiatorUserHandle,
+                @NonNull String transferInitiatorPackageName) {
             // Do nothing.
         }
 
diff --git a/media/java/android/media/MediaRouter2Manager.java b/media/java/android/media/MediaRouter2Manager.java
index 830708c..06c0996 100644
--- a/media/java/android/media/MediaRouter2Manager.java
+++ b/media/java/android/media/MediaRouter2Manager.java
@@ -18,9 +18,11 @@
 
 import static com.android.internal.util.function.pooled.PooledLambda.obtainMessage;
 
+import android.Manifest;
 import android.annotation.CallbackExecutor;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
+import android.annotation.RequiresPermission;
 import android.content.Context;
 import android.media.session.MediaController;
 import android.media.session.MediaSessionManager;
@@ -28,6 +30,7 @@
 import android.os.Message;
 import android.os.RemoteException;
 import android.os.ServiceManager;
+import android.os.UserHandle;
 import android.text.TextUtils;
 import android.util.ArrayMap;
 import android.util.ArraySet;
@@ -467,30 +470,42 @@
      * <p>Same as {@link #transfer(RoutingSessionInfo, MediaRoute2Info)}, but resolves the routing
      * session based on the provided package name.
      */
-    public void transfer(@NonNull String packageName, @NonNull MediaRoute2Info route) {
+    @RequiresPermission(Manifest.permission.MEDIA_CONTENT_CONTROL)
+    public void transfer(
+            @NonNull String packageName,
+            @NonNull MediaRoute2Info route,
+            @NonNull UserHandle userHandle) {
         Objects.requireNonNull(packageName, "packageName must not be null");
         Objects.requireNonNull(route, "route must not be null");
 
         List<RoutingSessionInfo> sessionInfos = getRoutingSessions(packageName);
         RoutingSessionInfo targetSession = sessionInfos.get(sessionInfos.size() - 1);
-        transfer(targetSession, route);
+        transfer(targetSession, route, userHandle, packageName);
     }
 
     /**
      * Transfers a routing session to a media route.
+     *
      * <p>{@link Callback#onTransferred} or {@link Callback#onTransferFailed} will be called
      * depending on the result.
      *
      * @param sessionInfo the routing session info to transfer
      * @param route the route transfer to
-     *
+     * @param transferInitiatorUserHandle the user handle of an app initiated the transfer
+     * @param transferInitiatorPackageName the package name of an app initiated the transfer
      * @see Callback#onTransferred(RoutingSessionInfo, RoutingSessionInfo)
      * @see Callback#onTransferFailed(RoutingSessionInfo, MediaRoute2Info)
      */
-    public void transfer(@NonNull RoutingSessionInfo sessionInfo,
-            @NonNull MediaRoute2Info route) {
+    @RequiresPermission(Manifest.permission.MEDIA_CONTENT_CONTROL)
+    public void transfer(
+            @NonNull RoutingSessionInfo sessionInfo,
+            @NonNull MediaRoute2Info route,
+            @NonNull UserHandle transferInitiatorUserHandle,
+            @NonNull String transferInitiatorPackageName) {
         Objects.requireNonNull(sessionInfo, "sessionInfo must not be null");
         Objects.requireNonNull(route, "route must not be null");
+        Objects.requireNonNull(transferInitiatorUserHandle);
+        Objects.requireNonNull(transferInitiatorPackageName);
 
         Log.v(TAG, "Transferring routing session. session= " + sessionInfo + ", route=" + route);
 
@@ -503,9 +518,11 @@
         }
 
         if (sessionInfo.getTransferableRoutes().contains(route.getId())) {
-            transferToRoute(sessionInfo, route);
+            transferToRoute(
+                    sessionInfo, route, transferInitiatorUserHandle, transferInitiatorPackageName);
         } else {
-            requestCreateSession(sessionInfo, route);
+            requestCreateSession(sessionInfo, route, transferInitiatorUserHandle,
+                    transferInitiatorPackageName);
         }
     }
 
@@ -873,19 +890,30 @@
      *
      * @hide
      */
-    private void transferToRoute(@NonNull RoutingSessionInfo session,
-            @NonNull MediaRoute2Info route) {
+    @RequiresPermission(Manifest.permission.MEDIA_CONTENT_CONTROL)
+    private void transferToRoute(
+            @NonNull RoutingSessionInfo session,
+            @NonNull MediaRoute2Info route,
+            @NonNull UserHandle transferInitiatorUserHandle,
+            @NonNull String transferInitiatorPackageName) {
         int requestId = createTransferRequest(session, route);
 
         try {
             mMediaRouterService.transferToRouteWithManager(
-                    mClient, requestId, session.getId(), route);
+                    mClient,
+                    requestId,
+                    session.getId(),
+                    route,
+                    transferInitiatorUserHandle,
+                    transferInitiatorPackageName);
         } catch (RemoteException ex) {
             throw ex.rethrowFromSystemServer();
         }
     }
 
-    private void requestCreateSession(RoutingSessionInfo oldSession, MediaRoute2Info route) {
+    private void requestCreateSession(RoutingSessionInfo oldSession, MediaRoute2Info route,
+            @NonNull UserHandle transferInitiatorUserHandle,
+            @NonNull String transferInitiationPackageName) {
         if (TextUtils.isEmpty(oldSession.getClientPackageName())) {
             Log.w(TAG, "requestCreateSession: Can't create a session without package name.");
             notifyTransferFailed(oldSession, route);
@@ -896,7 +924,8 @@
 
         try {
             mMediaRouterService.requestCreateSessionWithManager(
-                    mClient, requestId, oldSession, route);
+                    mClient, requestId, oldSession, route, transferInitiatorUserHandle,
+                    transferInitiationPackageName);
         } catch (RemoteException ex) {
             throw ex.rethrowFromSystemServer();
         }
diff --git a/media/java/android/media/RoutingSessionInfo.java b/media/java/android/media/RoutingSessionInfo.java
index a77c943..d28c26d 100644
--- a/media/java/android/media/RoutingSessionInfo.java
+++ b/media/java/android/media/RoutingSessionInfo.java
@@ -16,18 +16,25 @@
 
 package android.media;
 
+import static com.android.media.flags.Flags.FLAG_ENABLE_BUILT_IN_SPEAKER_ROUTE_SUITABILITY_STATUSES;
+
+import android.annotation.FlaggedApi;
+import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.content.res.Resources;
 import android.os.Bundle;
 import android.os.Parcel;
 import android.os.Parcelable;
+import android.os.UserHandle;
 import android.text.TextUtils;
 
 import com.android.internal.util.Preconditions;
 
 import java.io.FileDescriptor;
 import java.io.PrintWriter;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.List;
@@ -55,6 +62,33 @@
     private static final String KEY_GROUP_ROUTE = "androidx.mediarouter.media.KEY_GROUP_ROUTE";
     private static final String KEY_VOLUME_HANDLING = "volumeHandling";
 
+    /**
+     * Indicates that the transfer happened by the default logic without explicit system's or user's
+     * request.
+     *
+     * <p>For example, an automatically connected Bluetooth device will have this transfer reason.
+     */
+    @FlaggedApi(FLAG_ENABLE_BUILT_IN_SPEAKER_ROUTE_SUITABILITY_STATUSES)
+    public static final int TRANSFER_REASON_FALLBACK = 0;
+
+    /** Indicates that the transfer happened from within a privileged application. */
+    @FlaggedApi(FLAG_ENABLE_BUILT_IN_SPEAKER_ROUTE_SUITABILITY_STATUSES)
+    public static final int TRANSFER_REASON_SYSTEM_REQUEST = 1;
+
+    /** Indicates that the transfer happened from a non-privileged app. */
+    @FlaggedApi(FLAG_ENABLE_BUILT_IN_SPEAKER_ROUTE_SUITABILITY_STATUSES)
+    public static final int TRANSFER_REASON_APP = 2;
+
+    /**
+     * Indicates the transfer reason.
+     *
+     * @hide
+     */
+    @FlaggedApi(FLAG_ENABLE_BUILT_IN_SPEAKER_ROUTE_SUITABILITY_STATUSES)
+    @IntDef(value = {TRANSFER_REASON_FALLBACK, TRANSFER_REASON_SYSTEM_REQUEST, TRANSFER_REASON_APP})
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface TransferReason {}
+
     @NonNull
     final String mId;
     @Nullable
@@ -82,6 +116,10 @@
     final Bundle mControlHints;
     final boolean mIsSystemSession;
 
+    @TransferReason final int mTransferReason;
+
+    @Nullable final UserHandle mTransferInitiatorUserHandle;
+    @Nullable final String mTransferInitiatorPackageName;
 
     RoutingSessionInfo(@NonNull Builder builder) {
         Objects.requireNonNull(builder, "builder must not be null.");
@@ -116,6 +154,9 @@
                         volumeAdjustmentForRemoteGroupSessions);
 
         mControlHints = updateVolumeHandlingInHints(builder.mControlHints, mVolumeHandling);
+        mTransferReason = builder.mTransferReason;
+        mTransferInitiatorUserHandle = builder.mTransferInitiatorUserHandle;
+        mTransferInitiatorPackageName = builder.mTransferInitiatorPackageName;
     }
 
     RoutingSessionInfo(@NonNull Parcel src) {
@@ -140,6 +181,9 @@
 
         mControlHints = src.readBundle();
         mIsSystemSession = src.readBoolean();
+        mTransferReason = src.readInt();
+        mTransferInitiatorUserHandle = src.readParcelable(null, android.os.UserHandle.class);
+        mTransferInitiatorPackageName = src.readString();
     }
 
     @Nullable
@@ -330,6 +374,27 @@
         return mIsSystemSession;
     }
 
+    /** Returns the transfer reason for this routing session. */
+    @FlaggedApi(FLAG_ENABLE_BUILT_IN_SPEAKER_ROUTE_SUITABILITY_STATUSES)
+    @TransferReason
+    public int getTransferReason() {
+        return mTransferReason;
+    }
+
+    /** @hide */
+    @FlaggedApi(FLAG_ENABLE_BUILT_IN_SPEAKER_ROUTE_SUITABILITY_STATUSES)
+    @Nullable
+    public UserHandle getTransferInitiatorUserHandle() {
+        return mTransferInitiatorUserHandle;
+    }
+
+    /** @hide */
+    @FlaggedApi(FLAG_ENABLE_BUILT_IN_SPEAKER_ROUTE_SUITABILITY_STATUSES)
+    @Nullable
+    public String getTransferInitiatorPackageName() {
+        return mTransferInitiatorPackageName;
+    }
+
     @Override
     public int describeContents() {
         return 0;
@@ -351,6 +416,13 @@
         dest.writeInt(mVolume);
         dest.writeBundle(mControlHints);
         dest.writeBoolean(mIsSystemSession);
+        dest.writeInt(mTransferReason);
+        if (mTransferInitiatorUserHandle != null) {
+            mTransferInitiatorUserHandle.writeToParcel(dest, /* flags= */ 0);
+        } else {
+            dest.writeParcelable(null, /* flags= */ 0);
+        }
+        dest.writeString(mTransferInitiatorPackageName);
     }
 
     /**
@@ -379,6 +451,9 @@
         pw.println(indent + "mVolume=" + mVolume);
         pw.println(indent + "mControlHints=" + mControlHints);
         pw.println(indent + "mIsSystemSession=" + mIsSystemSession);
+        pw.println(indent + "mTransferReason=" + mTransferReason);
+        pw.println(indent + "mtransferInitiatorUserHandle=" + mTransferInitiatorUserHandle);
+        pw.println(indent + "mtransferInitiatorPackageName=" + mTransferInitiatorPackageName);
     }
 
     @Override
@@ -406,39 +481,69 @@
                 && Objects.equals(mTransferableRoutes, other.mTransferableRoutes)
                 && (mVolumeHandling == other.mVolumeHandling)
                 && (mVolumeMax == other.mVolumeMax)
-                && (mVolume == other.mVolume);
+                && (mVolume == other.mVolume)
+                && (mTransferReason == other.mTransferReason)
+                && Objects.equals(mTransferInitiatorUserHandle, other.mTransferInitiatorUserHandle)
+                && Objects.equals(
+                mTransferInitiatorPackageName, other.mTransferInitiatorPackageName);
     }
 
     @Override
     public int hashCode() {
-        return Objects.hash(mId, mName, mOwnerPackageName, mClientPackageName, mProviderId,
-                mSelectedRoutes, mSelectableRoutes, mDeselectableRoutes, mTransferableRoutes,
-                mVolumeMax, mVolumeHandling, mVolume);
+        return Objects.hash(
+                mId,
+                mName,
+                mOwnerPackageName,
+                mClientPackageName,
+                mProviderId,
+                mSelectedRoutes,
+                mSelectableRoutes,
+                mDeselectableRoutes,
+                mTransferableRoutes,
+                mVolumeMax,
+                mVolumeHandling,
+                mVolume,
+                mTransferReason,
+                mTransferInitiatorUserHandle,
+                mTransferInitiatorPackageName);
     }
 
     @Override
     public String toString() {
-        StringBuilder result = new StringBuilder()
-                .append("RoutingSessionInfo{ ")
-                .append("sessionId=").append(getId())
-                .append(", name=").append(getName())
-                .append(", clientPackageName=").append(getClientPackageName())
-                .append(", selectedRoutes={")
-                .append(String.join(",", getSelectedRoutes()))
-                .append("}")
-                .append(", selectableRoutes={")
-                .append(String.join(",", getSelectableRoutes()))
-                .append("}")
-                .append(", deselectableRoutes={")
-                .append(String.join(",", getDeselectableRoutes()))
-                .append("}")
-                .append(", transferableRoutes={")
-                .append(String.join(",", getTransferableRoutes()))
-                .append("}")
-                .append(", volumeHandling=").append(getVolumeHandling())
-                .append(", volumeMax=").append(getVolumeMax())
-                .append(", volume=").append(getVolume())
-                .append(" }");
+        StringBuilder result =
+                new StringBuilder()
+                        .append("RoutingSessionInfo{ ")
+                        .append("sessionId=")
+                        .append(getId())
+                        .append(", name=")
+                        .append(getName())
+                        .append(", clientPackageName=")
+                        .append(getClientPackageName())
+                        .append(", selectedRoutes={")
+                        .append(String.join(",", getSelectedRoutes()))
+                        .append("}")
+                        .append(", selectableRoutes={")
+                        .append(String.join(",", getSelectableRoutes()))
+                        .append("}")
+                        .append(", deselectableRoutes={")
+                        .append(String.join(",", getDeselectableRoutes()))
+                        .append("}")
+                        .append(", transferableRoutes={")
+                        .append(String.join(",", getTransferableRoutes()))
+                        .append("}")
+                        .append(", volumeHandling=")
+                        .append(getVolumeHandling())
+                        .append(", volumeMax=")
+                        .append(getVolumeMax())
+                        .append(", volume=")
+                        .append(getVolume())
+                        .append(", transferReason=")
+                        .append(getTransferReason())
+                        .append(", transferInitiatorUserHandle=")
+                        .append(getTransferInitiatorUserHandle())
+                        .append(", transferInitiatorPackageName=")
+                        .append(getTransferInitiatorPackageName())
+                        .append(" }");
         return result.toString();
     }
 
@@ -494,6 +599,9 @@
         @Nullable
         private Bundle mControlHints;
         private boolean mIsSystemSession;
+        @TransferReason private int mTransferReason = TRANSFER_REASON_FALLBACK;
+        @Nullable private UserHandle mTransferInitiatorUserHandle;
+        @Nullable private String mTransferInitiatorPackageName;
 
         /**
          * Constructor for builder to create {@link RoutingSessionInfo}.
@@ -555,6 +663,9 @@
 
             mControlHints = sessionInfo.mControlHints;
             mIsSystemSession = sessionInfo.mIsSystemSession;
+            mTransferReason = sessionInfo.mTransferReason;
+            mTransferInitiatorUserHandle = sessionInfo.mTransferInitiatorUserHandle;
+            mTransferInitiatorPackageName = sessionInfo.mTransferInitiatorPackageName;
         }
 
         /**
@@ -784,6 +895,35 @@
         }
 
         /**
+         * Sets transfer reason for the current session.
+         *
+         * <p>By default the transfer reason is set to {@link
+         * RoutingSessionInfo#TRANSFER_REASON_FALLBACK}.
+         */
+        @NonNull
+        @FlaggedApi(FLAG_ENABLE_BUILT_IN_SPEAKER_ROUTE_SUITABILITY_STATUSES)
+        public Builder setTransferReason(@TransferReason int transferReason) {
+            mTransferReason = transferReason;
+            return this;
+        }
+
+        /**
+         * Sets the user handle and package name of the process that initiated the transfer.
+         *
+         * <p>By default the transfer initiation user handle and package name are set to {@code
+         * null}.
+         */
+        @NonNull
+        @FlaggedApi(FLAG_ENABLE_BUILT_IN_SPEAKER_ROUTE_SUITABILITY_STATUSES)
+        public Builder setTransferInitiator(
+                @Nullable UserHandle transferInitiatorUserHandle,
+                @Nullable String transferInitiatorPackageName) {
+            mTransferInitiatorUserHandle = transferInitiatorUserHandle;
+            mTransferInitiatorPackageName = transferInitiatorPackageName;
+            return this;
+        }
+
+        /**
          * Builds a routing session info.
          *
          * @throws IllegalArgumentException if no selected routes are added.
diff --git a/media/java/android/media/flags/media_better_together.aconfig b/media/java/android/media/flags/media_better_together.aconfig
index 07f63e5..3da52cc 100644
--- a/media/java/android/media/flags/media_better_together.aconfig
+++ b/media/java/android/media/flags/media_better_together.aconfig
@@ -69,3 +69,11 @@
     description: "Use BluetoothDevice.getAlias to populate the name of Bluetooth MediaRoute2Infos."
     bug: "314324170"
 }
+
+flag {
+     name: "enable_built_in_speaker_route_suitability_statuses"
+     namespace: "media_solutions"
+     description: "Make MediaRoute2Info provide information about routes suitability for transfer."
+     bug: "279555229"
+}
+
diff --git a/media/tests/MediaRouter/src/com/android/mediaroutertest/MediaRouter2ManagerTest.java b/media/tests/MediaRouter/src/com/android/mediaroutertest/MediaRouter2ManagerTest.java
index 8ed4bf2..c836df3 100644
--- a/media/tests/MediaRouter/src/com/android/mediaroutertest/MediaRouter2ManagerTest.java
+++ b/media/tests/MediaRouter/src/com/android/mediaroutertest/MediaRouter2ManagerTest.java
@@ -385,7 +385,9 @@
         MediaRoute2Info routeToSelect = routes.get(ROUTE_ID1);
         assertThat(routeToSelect).isNotNull();
 
-        mManager.transfer(mPackageName, routeToSelect);
+        mManager.transfer(
+                mPackageName, routeToSelect,
+                android.os.Process.myUserHandle());
         assertThat(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS)).isTrue();
         assertThat(mManager.getRemoteSessions()).hasSize(1);
     }
@@ -411,7 +413,9 @@
 
         assertThat(mManager.getRoutingSessions(mPackageName)).hasSize(1);
 
-        mManager.transfer(mPackageName, routeToSelect);
+        mManager.transfer(
+                mPackageName, routeToSelect,
+                android.os.Process.myUserHandle());
         assertThat(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS)).isTrue();
 
         List<RoutingSessionInfo> sessions = mManager.getRoutingSessions(mPackageName);
@@ -450,7 +454,11 @@
                 .addFeature(FEATURE_REMOTE_PLAYBACK)
                 .build();
 
-        mManager.transfer(mManager.getSystemRoutingSession(null), unknownRoute);
+        mManager.transfer(
+                mManager.getSystemRoutingSession(null),
+                unknownRoute,
+                android.os.Process.myUserHandle(),
+                mContext.getPackageName());
         assertThat(onSessionCreatedLatch.await(WAIT_TIME_MS, TimeUnit.MILLISECONDS)).isFalse();
         assertThat(onTransferFailedLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS)).isTrue();
     }
@@ -484,7 +492,11 @@
         assertThat(mManager.getRoutingSessions(mPackageName)).hasSize(1);
         assertThat(mRouter2.getControllers()).hasSize(1);
 
-        mManager.transfer(mManager.getRoutingSessions(mPackageName).get(0), routeToSelect);
+        mManager.transfer(
+                mManager.getRoutingSessions(mPackageName).get(0),
+                routeToSelect,
+                android.os.Process.myUserHandle(),
+                mContext.getPackageName());
         assertThat(transferLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS)).isTrue();
 
         assertThat(mManager.getRoutingSessions(mPackageName)).hasSize(2);
@@ -516,7 +528,11 @@
             }
         });
         awaitOnRouteChangedManager(
-                () -> mManager.transfer(mPackageName, routes.get(ROUTE_ID1)),
+                () ->
+                        mManager.transfer(
+                                mPackageName,
+                                routes.get(ROUTE_ID1),
+                                android.os.Process.myUserHandle()),
                 ROUTE_ID1,
                 route -> TextUtils.equals(route.getClientPackageName(), mPackageName));
         assertThat(onSessionCreatedLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS)).isTrue();
@@ -527,7 +543,11 @@
         RoutingSessionInfo sessionInfo = sessions.get(1);
 
         awaitOnRouteChangedManager(
-                () -> mManager.transfer(mPackageName, routes.get(ROUTE_ID5_TO_TRANSFER_TO)),
+                () ->
+                        mManager.transfer(
+                                mPackageName,
+                                routes.get(ROUTE_ID5_TO_TRANSFER_TO),
+                                android.os.Process.myUserHandle()),
                 ROUTE_ID5_TO_TRANSFER_TO,
                 route -> TextUtils.equals(route.getClientPackageName(), mPackageName));
 
@@ -585,9 +605,11 @@
         assertThat(route1).isNotNull();
         assertThat(route2).isNotNull();
 
-        mManager.transfer(mPackageName, route1);
+        mManager.transfer(
+                mPackageName, route1, android.os.Process.myUserHandle());
         assertThat(successLatch1.await(TIMEOUT_MS, TimeUnit.MILLISECONDS)).isTrue();
-        mManager.transfer(mPackageName, route2);
+        mManager.transfer(
+                mPackageName, route2, android.os.Process.myUserHandle());
         assertThat(successLatch2.await(TIMEOUT_MS, TimeUnit.MILLISECONDS)).isTrue();
 
         // onTransferFailed/onSessionReleased should not be called.
@@ -634,7 +656,11 @@
 
         List<RoutingSessionInfo> sessions = mManager.getRoutingSessions(mPackageName);
         RoutingSessionInfo targetSession = sessions.get(sessions.size() - 1);
-        mManager.transfer(targetSession, routes.get(ROUTE_ID6_TO_BE_IGNORED));
+        mManager.transfer(
+                targetSession,
+                routes.get(ROUTE_ID6_TO_BE_IGNORED),
+                android.os.Process.myUserHandle(),
+                mContext.getPackageName());
 
         assertThat(onSessionCreatedLatch.await(WAIT_TIME_MS, TimeUnit.MILLISECONDS)).isFalse();
         assertThat(onFailedLatch.await(MediaRouter2Manager.TRANSFER_TIMEOUT_MS,
@@ -705,7 +731,10 @@
             }
         });
 
-        mManager.transfer(mPackageName, routes.get(ROUTE_ID1));
+        mManager.transfer(
+                mPackageName,
+                routes.get(ROUTE_ID1),
+                android.os.Process.myUserHandle());
         assertThat(onSessionCreatedLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS)).isTrue();
 
         List<RoutingSessionInfo> sessions = mManager.getRoutingSessions(mPackageName);
@@ -860,7 +889,8 @@
         });
 
         mRouter2.setOnGetControllerHintsListener(listener);
-        mManager.transfer(mPackageName, route);
+        mManager.transfer(
+                mPackageName, route, android.os.Process.myUserHandle());
         assertThat(hintLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS)).isTrue();
         assertThat(successLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS)).isTrue();
 
@@ -905,7 +935,10 @@
             }
         });
 
-        mManager.transfer(mPackageName, routes.get(ROUTE_ID4_TO_SELECT_AND_DESELECT));
+        mManager.transfer(
+                mPackageName,
+                routes.get(ROUTE_ID4_TO_SELECT_AND_DESELECT),
+                android.os.Process.myUserHandle());
         assertThat(onSessionCreatedLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS)).isTrue();
     }
 
diff --git a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/card/CardPageProvider.kt b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/card/CardPageProvider.kt
index 66bd6f5..d5cf1a35 100644
--- a/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/card/CardPageProvider.kt
+++ b/packages/SettingsLib/Spa/gallery/src/com/android/settingslib/spa/gallery/card/CardPageProvider.kt
@@ -26,6 +26,7 @@
 import androidx.compose.material.icons.outlined.PowerOff
 import androidx.compose.material.icons.outlined.Shield
 import androidx.compose.material.icons.outlined.WarningAmber
+import androidx.compose.material3.MaterialTheme
 import androidx.compose.material3.Text
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.getValue
@@ -78,24 +79,19 @@
                 imageVector = Icons.Outlined.WarningAmber,
                 buttons = listOf(
                     CardButton(text = "Action") {},
-                    CardButton(text = "Action", isMain = true) {},
-                )
+                ),
+                tintColor = MaterialTheme.colorScheme.error,
+                containerColor = MaterialTheme.colorScheme.errorContainer,
             )
         )
     }
 
     @Composable
     private fun SettingsCardWithoutIcon() {
-        var isVisible by rememberSaveable { mutableStateOf(true) }
         SettingsCard(
             CardModel(
                 title = stringResource(R.string.sample_title),
                 text = stringResource(R.string.sample_text),
-                isVisible = { isVisible },
-                onDismiss = { isVisible = false },
-                buttons = listOf(
-                    CardButton(text = "Action") {},
-                ),
             )
         )
     }
@@ -104,6 +100,7 @@
     fun SampleSettingsCollapsibleCard() {
         val context = LocalContext.current
         var isVisible0 by rememberSaveable { mutableStateOf(true) }
+        var isVisible1 by rememberSaveable { mutableStateOf(true) }
         val cards = remember {
             mutableStateListOf(
                 CardModel(
@@ -114,16 +111,17 @@
                     onDismiss = { isVisible0 = false },
                     buttons = listOf(
                         CardButton(text = "Action") {},
-                    )
+                    ),
                 ),
                 CardModel(
                     title = context.getString(R.string.sample_title),
                     text = context.getString(R.string.sample_text),
                     imageVector = Icons.Outlined.Shield,
+                    isVisible = { isVisible1 },
+                    onDismiss = { isVisible1 = false },
                     buttons = listOf(
                         CardButton(text = "Action") {},
-                        CardButton(text = "Main action", isMain = true) {},
-                    )
+                    ),
                 )
             )
         }
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/SettingsDimension.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/SettingsDimension.kt
index 993cb4a..c143390 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/SettingsDimension.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/framework/theme/SettingsDimension.kt
@@ -37,7 +37,6 @@
     val itemPaddingAround = 8.dp
     val itemDividerHeight = 32.dp
 
-    val iconSmall = 16.dp
     val iconLarge = 48.dp
 
     /** The size when app icon is displayed in list. */
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/card/CardModel.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/card/CardModel.kt
index b18a1bc..b2a8b87 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/card/CardModel.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/card/CardModel.kt
@@ -16,11 +16,11 @@
 
 package com.android.settingslib.spa.widget.card
 
+import androidx.compose.ui.graphics.Color
 import androidx.compose.ui.graphics.vector.ImageVector
 
 data class CardButton(
     val text: String,
-    val isMain: Boolean = false,
     val onClick: () -> Unit,
 )
 
@@ -38,4 +38,10 @@
     val onDismiss: (() -> Unit)? = null,
 
     val buttons: List<CardButton> = emptyList(),
+
+    /** If specified, this color will be used to tint the icon and the buttons. */
+    val tintColor: Color = Color.Unspecified,
+
+    /** If specified, this color will be used to tint the icon and the buttons. */
+    val containerColor: Color = Color.Unspecified,
 )
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/card/SettingsCard.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/card/SettingsCard.kt
index 7eec888..c7845fa 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/card/SettingsCard.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/card/SettingsCard.kt
@@ -23,26 +23,26 @@
 import androidx.compose.foundation.layout.Row
 import androidx.compose.foundation.layout.Spacer
 import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.height
 import androidx.compose.foundation.layout.padding
 import androidx.compose.foundation.layout.size
 import androidx.compose.foundation.shape.CircleShape
 import androidx.compose.material.icons.Icons
 import androidx.compose.material.icons.outlined.Close
 import androidx.compose.material.icons.outlined.WarningAmber
-import androidx.compose.material3.Button
-import androidx.compose.material3.ButtonDefaults
 import androidx.compose.material3.Card
 import androidx.compose.material3.CardDefaults
 import androidx.compose.material3.Icon
 import androidx.compose.material3.IconButton
 import androidx.compose.material3.MaterialTheme
-import androidx.compose.material3.OutlinedButton
 import androidx.compose.material3.Surface
 import androidx.compose.material3.Text
+import androidx.compose.material3.TextButton
 import androidx.compose.runtime.Composable
 import androidx.compose.ui.Alignment
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.graphics.takeOrElse
 import androidx.compose.ui.graphics.vector.ImageVector
 import androidx.compose.ui.res.stringResource
 import androidx.compose.ui.unit.dp
@@ -72,11 +72,14 @@
 }
 
 @Composable
-fun SettingsCardContent(content: @Composable ColumnScope.() -> Unit) {
+fun SettingsCardContent(
+    containerColor: Color = Color.Unspecified,
+    content: @Composable ColumnScope.() -> Unit,
+) {
     Card(
         shape = CornerExtraSmall,
         colors = CardDefaults.cardColors(
-            containerColor = SettingsTheme.colorScheme.surface,
+            containerColor = containerColor.takeOrElse { SettingsTheme.colorScheme.surface },
         ),
         modifier = Modifier
             .fillMaxWidth()
@@ -95,37 +98,43 @@
 @Composable
 internal fun SettingsCardImpl(model: CardModel) {
     AnimatedVisibility(visible = model.isVisible()) {
-        SettingsCardContent {
+        SettingsCardContent(containerColor = model.containerColor) {
             Column(
-                modifier = Modifier.padding(SettingsDimension.itemPaddingStart),
+                modifier = Modifier.padding(
+                    horizontal = SettingsDimension.dialogItemPaddingHorizontal,
+                    vertical = SettingsDimension.itemPaddingAround,
+                ),
                 verticalArrangement = Arrangement.spacedBy(SettingsDimension.itemPaddingAround)
             ) {
-                CardHeader(model.imageVector, model.onDismiss)
+                CardHeader(model.imageVector, model.tintColor, model.onDismiss)
                 SettingsTitle(model.title)
                 SettingsBody(model.text)
-                Buttons(model.buttons)
+                Buttons(model.buttons, model.tintColor)
             }
         }
     }
 }
 
 @Composable
-fun CardHeader(imageVector: ImageVector?, onDismiss: (() -> Unit)? = null) {
+fun CardHeader(imageVector: ImageVector?, iconColor: Color, onDismiss: (() -> Unit)? = null) {
+    if (imageVector != null || onDismiss != null) {
+        Spacer(Modifier.height(SettingsDimension.buttonPaddingVertical))
+    }
     Row(Modifier.fillMaxWidth()) {
-        CardIcon(imageVector)
+        CardIcon(imageVector, iconColor)
         Spacer(modifier = Modifier.weight(1f))
         DismissButton(onDismiss)
     }
 }
 
 @Composable
-private fun CardIcon(imageVector: ImageVector?) {
+private fun CardIcon(imageVector: ImageVector?, color: Color) {
     if (imageVector != null) {
         Icon(
             imageVector = imageVector,
             contentDescription = null,
             modifier = Modifier.size(SettingsDimension.itemIconSize),
-            tint = MaterialTheme.colorScheme.primary,
+            tint = color.takeOrElse { MaterialTheme.colorScheme.primary },
         )
     }
 }
@@ -146,52 +155,35 @@
                 contentDescription = stringResource(
                     androidx.compose.material3.R.string.m3c_snackbar_dismiss
                 ),
-                modifier = Modifier.size(SettingsDimension.iconSmall),
+                modifier = Modifier.padding(SettingsDimension.paddingSmall),
             )
         }
     }
 }
 
 @Composable
-private fun Buttons(buttons: List<CardButton>) {
+private fun Buttons(buttons: List<CardButton>, color: Color) {
     if (buttons.isNotEmpty()) {
         Row(
-            modifier = Modifier
-                .fillMaxWidth()
-                .padding(top = SettingsDimension.itemPaddingAround),
+            modifier = Modifier.fillMaxWidth(),
             horizontalArrangement = Arrangement.spacedBy(
                 space = SettingsDimension.itemPaddingEnd,
                 alignment = Alignment.End,
             ),
         ) {
             for (button in buttons) {
-                Button(button)
+                Button(button, color)
             }
         }
+    } else {
+        Spacer(Modifier.height(SettingsDimension.itemPaddingAround))
     }
 }
 
 @Composable
-private fun Button(button: CardButton) {
-    if (button.isMain) {
-        Button(
-            onClick = button.onClick,
-            colors = ButtonDefaults.buttonColors(
-                containerColor = SettingsTheme.colorScheme.primaryContainer,
-            ),
-        ) {
-            Text(
-                text = button.text,
-                color = SettingsTheme.colorScheme.onPrimaryContainer,
-            )
-        }
-    } else {
-        OutlinedButton(onClick = button.onClick) {
-            Text(
-                text = button.text,
-                color = MaterialTheme.colorScheme.onSurface,
-            )
-        }
+private fun Button(button: CardButton, color: Color) {
+    TextButton(onClick = button.onClick) {
+        Text(text = button.text, color = color)
     }
 }
 
@@ -206,7 +198,6 @@
                 imageVector = Icons.Outlined.WarningAmber,
                 buttons = listOf(
                     CardButton(text = "Action") {},
-                    CardButton(text = "Action", isMain = true) {},
                 )
             )
         )
diff --git a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/card/SettingsCollapsibleCard.kt b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/card/SettingsCollapsibleCard.kt
index 6e36490..c34df65 100644
--- a/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/card/SettingsCollapsibleCard.kt
+++ b/packages/SettingsLib/Spa/spa/src/com/android/settingslib/spa/widget/card/SettingsCollapsibleCard.kt
@@ -141,7 +141,6 @@
                     imageVector = Icons.Outlined.Shield,
                     buttons = listOf(
                         CardButton(text = "Action") {},
-                        CardButton(text = "Main action", isMain = true) {},
                     )
                 )
             )
diff --git a/packages/SettingsLib/src/com/android/settingslib/RestrictedLockUtilsInternal.java b/packages/SettingsLib/src/com/android/settingslib/RestrictedLockUtilsInternal.java
index c51a9a0..8e5396f 100644
--- a/packages/SettingsLib/src/com/android/settingslib/RestrictedLockUtilsInternal.java
+++ b/packages/SettingsLib/src/com/android/settingslib/RestrictedLockUtilsInternal.java
@@ -23,8 +23,6 @@
 import static com.android.settingslib.Utils.getColorAttrDefaultColor;
 
 import android.Manifest;
-import android.annotation.NonNull;
-import android.annotation.Nullable;
 import android.annotation.UserIdInt;
 import android.app.AppGlobals;
 import android.app.AppOpsManager;
@@ -52,6 +50,8 @@
 import android.view.MenuItem;
 import android.widget.TextView;
 
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
 import androidx.annotation.RequiresApi;
 import androidx.annotation.VisibleForTesting;
 
diff --git a/packages/SettingsLib/src/com/android/settingslib/RestrictedSwitchPreference.java b/packages/SettingsLib/src/com/android/settingslib/RestrictedSwitchPreference.java
index 3b8f665..70ece0f 100644
--- a/packages/SettingsLib/src/com/android/settingslib/RestrictedSwitchPreference.java
+++ b/packages/SettingsLib/src/com/android/settingslib/RestrictedSwitchPreference.java
@@ -21,7 +21,6 @@
 
 import static com.android.settingslib.RestrictedLockUtils.EnforcedAdmin;
 
-import android.annotation.NonNull;
 import android.app.AppOpsManager;
 import android.app.admin.DevicePolicyManager;
 import android.content.Context;
@@ -35,6 +34,7 @@
 import android.widget.LinearLayout;
 import android.widget.TextView;
 
+import androidx.annotation.NonNull;
 import androidx.annotation.VisibleForTesting;
 import androidx.preference.PreferenceManager;
 import androidx.preference.PreferenceViewHolder;
diff --git a/packages/SettingsLib/src/com/android/settingslib/Utils.java b/packages/SettingsLib/src/com/android/settingslib/Utils.java
index 107d5f8..c2be571 100644
--- a/packages/SettingsLib/src/com/android/settingslib/Utils.java
+++ b/packages/SettingsLib/src/com/android/settingslib/Utils.java
@@ -3,7 +3,6 @@
 import static android.app.admin.DevicePolicyResources.Strings.Settings.WORK_PROFILE_USER_LABEL;
 
 import android.annotation.ColorInt;
-import android.annotation.Nullable;
 import android.app.admin.DevicePolicyManager;
 import android.content.Context;
 import android.content.Intent;
@@ -23,10 +22,10 @@
 import android.graphics.ColorMatrix;
 import android.graphics.ColorMatrixColorFilter;
 import android.graphics.drawable.Drawable;
-import android.hardware.usb.flags.Flags;
 import android.hardware.usb.UsbManager;
 import android.hardware.usb.UsbPort;
 import android.hardware.usb.UsbPortStatus;
+import android.hardware.usb.flags.Flags;
 import android.location.LocationManager;
 import android.media.AudioManager;
 import android.net.NetworkCapabilities;
@@ -47,6 +46,7 @@
 import android.util.Log;
 
 import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
 import androidx.annotation.RequiresApi;
 import androidx.core.graphics.drawable.RoundedBitmapDrawable;
 import androidx.core.graphics.drawable.RoundedBitmapDrawableFactory;
diff --git a/packages/SettingsLib/src/com/android/settingslib/applications/DefaultAppInfo.java b/packages/SettingsLib/src/com/android/settingslib/applications/DefaultAppInfo.java
index dae48db..b0db16f 100644
--- a/packages/SettingsLib/src/com/android/settingslib/applications/DefaultAppInfo.java
+++ b/packages/SettingsLib/src/com/android/settingslib/applications/DefaultAppInfo.java
@@ -17,7 +17,6 @@
 package com.android.settingslib.applications;
 
 import android.app.AppGlobals;
-import android.annotation.Nullable;
 import android.content.ComponentName;
 import android.content.Context;
 import android.content.pm.ApplicationInfo;
@@ -28,6 +27,8 @@
 import android.os.RemoteException;
 import android.util.IconDrawableFactory;
 
+import androidx.annotation.Nullable;
+
 import com.android.settingslib.widget.CandidateInfo;
 
 /**
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothCallback.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothCallback.java
index fa056e2b..416b369 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothCallback.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/BluetoothCallback.java
@@ -26,9 +26,9 @@
 import static android.bluetooth.BluetoothAdapter.STATE_TURNING_ON;
 
 import android.annotation.IntDef;
-import android.annotation.Nullable;
 
 import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
 
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/HapClientProfile.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/HapClientProfile.java
index 1900575..cf4d6be 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/HapClientProfile.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/HapClientProfile.java
@@ -21,8 +21,6 @@
 
 import android.annotation.CallbackExecutor;
 import android.annotation.IntDef;
-import android.annotation.NonNull;
-import android.annotation.Nullable;
 import android.bluetooth.BluetoothAdapter;
 import android.bluetooth.BluetoothClass;
 import android.bluetooth.BluetoothCsipSetCoordinator;
@@ -35,6 +33,9 @@
 import android.content.Context;
 import android.util.Log;
 
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
 import com.android.settingslib.R;
 
 import java.lang.annotation.Retention;
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/HearingAidAudioRoutingHelper.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/HearingAidAudioRoutingHelper.java
index c9512cd..8eaea0e 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/HearingAidAudioRoutingHelper.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/HearingAidAudioRoutingHelper.java
@@ -151,7 +151,9 @@
     private boolean removePreferredDeviceForStrategies(List<AudioProductStrategy> strategies) {
         boolean status = true;
         for (AudioProductStrategy strategy : strategies) {
-            status &= mAudioManager.removePreferredDeviceForStrategy(strategy);
+            if (mAudioManager.getPreferredDeviceForStrategy(strategy) != null) {
+                status &= mAudioManager.removePreferredDeviceForStrategy(strategy);
+            }
         }
 
         return status;
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/LeAudioProfile.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LeAudioProfile.java
index 51164e8..57012aa 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/LeAudioProfile.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LeAudioProfile.java
@@ -21,7 +21,6 @@
 import static android.bluetooth.BluetoothProfile.CONNECTION_POLICY_ALLOWED;
 import static android.bluetooth.BluetoothProfile.CONNECTION_POLICY_FORBIDDEN;
 
-import android.annotation.Nullable;
 import android.bluetooth.BluetoothAdapter;
 import android.bluetooth.BluetoothClass;
 import android.bluetooth.BluetoothDevice;
@@ -31,6 +30,7 @@
 import android.os.Build;
 import android.util.Log;
 
+import androidx.annotation.Nullable;
 import androidx.annotation.RequiresApi;
 
 import com.android.settingslib.R;
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcast.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcast.java
index 49ac0f8..de21c54 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcast.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcast.java
@@ -19,7 +19,6 @@
 import static android.bluetooth.BluetoothProfile.CONNECTION_POLICY_FORBIDDEN;
 
 import android.annotation.CallbackExecutor;
-import android.annotation.NonNull;
 import android.bluetooth.BluetoothAdapter;
 import android.bluetooth.BluetoothClass;
 import android.bluetooth.BluetoothDevice;
@@ -44,6 +43,7 @@
 import android.text.TextUtils;
 import android.util.Log;
 
+import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 import androidx.annotation.RequiresApi;
 
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcastAssistant.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcastAssistant.java
index 34008ac..34c60a1 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcastAssistant.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/LocalBluetoothLeBroadcastAssistant.java
@@ -20,7 +20,6 @@
 import static android.bluetooth.BluetoothProfile.CONNECTION_POLICY_FORBIDDEN;
 
 import android.annotation.CallbackExecutor;
-import android.annotation.NonNull;
 import android.bluetooth.BluetoothAdapter;
 import android.bluetooth.BluetoothClass;
 import android.bluetooth.BluetoothDevice;
@@ -33,6 +32,7 @@
 import android.os.Build;
 import android.util.Log;
 
+import androidx.annotation.NonNull;
 import androidx.annotation.RequiresApi;
 
 import com.android.settingslib.R;
diff --git a/packages/SettingsLib/src/com/android/settingslib/bluetooth/VolumeControlProfile.java b/packages/SettingsLib/src/com/android/settingslib/bluetooth/VolumeControlProfile.java
index 3774b88..ab7a3db 100644
--- a/packages/SettingsLib/src/com/android/settingslib/bluetooth/VolumeControlProfile.java
+++ b/packages/SettingsLib/src/com/android/settingslib/bluetooth/VolumeControlProfile.java
@@ -21,7 +21,6 @@
 
 import android.annotation.CallbackExecutor;
 import android.annotation.IntRange;
-import android.annotation.NonNull;
 import android.bluetooth.BluetoothAdapter;
 import android.bluetooth.BluetoothClass;
 import android.bluetooth.BluetoothDevice;
@@ -31,6 +30,7 @@
 import android.os.Build;
 import android.util.Log;
 
+import androidx.annotation.NonNull;
 import androidx.annotation.RequiresApi;
 
 import java.util.ArrayList;
diff --git a/packages/SettingsLib/src/com/android/settingslib/connectivity/ConnectivitySubsystemsRecoveryManager.java b/packages/SettingsLib/src/com/android/settingslib/connectivity/ConnectivitySubsystemsRecoveryManager.java
index be420ac..43c6c0d 100644
--- a/packages/SettingsLib/src/com/android/settingslib/connectivity/ConnectivitySubsystemsRecoveryManager.java
+++ b/packages/SettingsLib/src/com/android/settingslib/connectivity/ConnectivitySubsystemsRecoveryManager.java
@@ -16,7 +16,6 @@
 
 package com.android.settingslib.connectivity;
 
-import android.annotation.NonNull;
 import android.content.BroadcastReceiver;
 import android.content.Context;
 import android.content.Intent;
@@ -31,6 +30,7 @@
 import android.telephony.TelephonyManager;
 import android.util.Log;
 
+import androidx.annotation.NonNull;
 import androidx.annotation.VisibleForTesting;
 
 /**
diff --git a/packages/SettingsLib/src/com/android/settingslib/core/instrumentation/SharedPreferencesLogger.java b/packages/SettingsLib/src/com/android/settingslib/core/instrumentation/SharedPreferencesLogger.java
index 067afa4..f847154 100644
--- a/packages/SettingsLib/src/com/android/settingslib/core/instrumentation/SharedPreferencesLogger.java
+++ b/packages/SettingsLib/src/com/android/settingslib/core/instrumentation/SharedPreferencesLogger.java
@@ -14,7 +14,6 @@
 
 package com.android.settingslib.core.instrumentation;
 
-import android.annotation.Nullable;
 import android.app.settings.SettingsEnums;
 import android.content.ComponentName;
 import android.content.Context;
@@ -24,6 +23,7 @@
 import android.text.TextUtils;
 import android.util.Log;
 
+import androidx.annotation.Nullable;
 import androidx.annotation.VisibleForTesting;
 
 import java.util.Map;
diff --git a/packages/SettingsLib/src/com/android/settingslib/core/lifecycle/ObservableActivity.java b/packages/SettingsLib/src/com/android/settingslib/core/lifecycle/ObservableActivity.java
index 2bd0b27..e974e70 100644
--- a/packages/SettingsLib/src/com/android/settingslib/core/lifecycle/ObservableActivity.java
+++ b/packages/SettingsLib/src/com/android/settingslib/core/lifecycle/ObservableActivity.java
@@ -22,13 +22,13 @@
 import static androidx.lifecycle.Lifecycle.Event.ON_START;
 import static androidx.lifecycle.Lifecycle.Event.ON_STOP;
 
-import android.annotation.Nullable;
 import android.app.Activity;
 import android.os.Bundle;
 import android.os.PersistableBundle;
 import android.view.Menu;
 import android.view.MenuItem;
 
+import androidx.annotation.Nullable;
 import androidx.fragment.app.FragmentActivity;
 import androidx.lifecycle.LifecycleOwner;
 
diff --git a/packages/SettingsLib/src/com/android/settingslib/datetime/ZoneGetter.java b/packages/SettingsLib/src/com/android/settingslib/datetime/ZoneGetter.java
index bbb1ec6..0029e20 100644
--- a/packages/SettingsLib/src/com/android/settingslib/datetime/ZoneGetter.java
+++ b/packages/SettingsLib/src/com/android/settingslib/datetime/ZoneGetter.java
@@ -16,7 +16,6 @@
 
 package com.android.settingslib.datetime;
 
-import android.annotation.Nullable;
 import android.content.Context;
 import android.content.res.XmlResourceParser;
 import android.icu.text.TimeZoneFormat;
@@ -29,6 +28,7 @@
 import android.util.Log;
 import android.view.View;
 
+import androidx.annotation.Nullable;
 import androidx.annotation.VisibleForTesting;
 import androidx.core.text.BidiFormatter;
 import androidx.core.text.TextDirectionHeuristicsCompat;
diff --git a/packages/SettingsLib/src/com/android/settingslib/drawable/UserIconDrawable.java b/packages/SettingsLib/src/com/android/settingslib/drawable/UserIconDrawable.java
index 11fae24..f07daa3 100644
--- a/packages/SettingsLib/src/com/android/settingslib/drawable/UserIconDrawable.java
+++ b/packages/SettingsLib/src/com/android/settingslib/drawable/UserIconDrawable.java
@@ -22,7 +22,6 @@
 
 import android.annotation.ColorInt;
 import android.annotation.DrawableRes;
-import android.annotation.NonNull;
 import android.app.admin.DevicePolicyManager;
 import android.content.Context;
 import android.content.res.ColorStateList;
@@ -45,6 +44,7 @@
 import android.os.Build;
 import android.os.UserHandle;
 
+import androidx.annotation.NonNull;
 import androidx.annotation.RequiresApi;
 import androidx.annotation.VisibleForTesting;
 
diff --git a/packages/SettingsLib/src/com/android/settingslib/enterprise/ActionDisabledByAdminController.java b/packages/SettingsLib/src/com/android/settingslib/enterprise/ActionDisabledByAdminController.java
index 0b3a519..f329023 100644
--- a/packages/SettingsLib/src/com/android/settingslib/enterprise/ActionDisabledByAdminController.java
+++ b/packages/SettingsLib/src/com/android/settingslib/enterprise/ActionDisabledByAdminController.java
@@ -16,11 +16,11 @@
 
 package com.android.settingslib.enterprise;
 
-import android.annotation.NonNull;
 import android.annotation.UserIdInt;
 import android.content.Context;
 import android.content.DialogInterface;
 
+import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 
 import com.android.settingslib.RestrictedLockUtils;
diff --git a/packages/SettingsLib/src/com/android/settingslib/enterprise/BiometricActionDisabledByAdminController.java b/packages/SettingsLib/src/com/android/settingslib/enterprise/BiometricActionDisabledByAdminController.java
index 714accc..7e3395b 100644
--- a/packages/SettingsLib/src/com/android/settingslib/enterprise/BiometricActionDisabledByAdminController.java
+++ b/packages/SettingsLib/src/com/android/settingslib/enterprise/BiometricActionDisabledByAdminController.java
@@ -16,7 +16,6 @@
 
 package com.android.settingslib.enterprise;
 
-import android.annotation.NonNull;
 import android.content.Context;
 import android.content.DialogInterface;
 import android.content.Intent;
@@ -24,6 +23,7 @@
 import android.provider.Settings;
 import android.util.Log;
 
+import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 
 import com.android.settingslib.RestrictedLockUtils;
diff --git a/packages/SettingsLib/src/com/android/settingslib/graph/BatteryMeterDrawableBase.java b/packages/SettingsLib/src/com/android/settingslib/graph/BatteryMeterDrawableBase.java
index 17c2b02..71d5809 100644
--- a/packages/SettingsLib/src/com/android/settingslib/graph/BatteryMeterDrawableBase.java
+++ b/packages/SettingsLib/src/com/android/settingslib/graph/BatteryMeterDrawableBase.java
@@ -16,7 +16,6 @@
 
 package com.android.settingslib.graph;
 
-import android.annotation.Nullable;
 import android.content.Context;
 import android.content.res.Resources;
 import android.content.res.TypedArray;
@@ -35,6 +34,8 @@
 import android.graphics.drawable.Drawable;
 import android.util.TypedValue;
 
+import androidx.annotation.Nullable;
+
 import com.android.settingslib.R;
 import com.android.settingslib.Utils;
 
diff --git a/packages/SettingsLib/src/com/android/settingslib/graph/BluetoothDeviceLayerDrawable.java b/packages/SettingsLib/src/com/android/settingslib/graph/BluetoothDeviceLayerDrawable.java
index f01eb2a..65d53f3 100644
--- a/packages/SettingsLib/src/com/android/settingslib/graph/BluetoothDeviceLayerDrawable.java
+++ b/packages/SettingsLib/src/com/android/settingslib/graph/BluetoothDeviceLayerDrawable.java
@@ -16,7 +16,7 @@
 
 package com.android.settingslib.graph;
 
-import android.annotation.NonNull;
+
 import android.content.Context;
 import android.content.res.Resources;
 import android.graphics.PorterDuff;
@@ -25,6 +25,7 @@
 import android.graphics.drawable.LayerDrawable;
 import android.view.Gravity;
 
+import androidx.annotation.NonNull;
 import androidx.annotation.VisibleForTesting;
 
 import com.android.settingslib.R;
diff --git a/packages/SettingsLib/src/com/android/settingslib/graph/SignalDrawable.java b/packages/SettingsLib/src/com/android/settingslib/graph/SignalDrawable.java
index 4d0804e..9a19f93 100644
--- a/packages/SettingsLib/src/com/android/settingslib/graph/SignalDrawable.java
+++ b/packages/SettingsLib/src/com/android/settingslib/graph/SignalDrawable.java
@@ -16,8 +16,6 @@
 
 import android.animation.ArgbEvaluator;
 import android.annotation.IntRange;
-import android.annotation.NonNull;
-import android.annotation.Nullable;
 import android.content.Context;
 import android.content.res.ColorStateList;
 import android.graphics.Canvas;
@@ -36,6 +34,9 @@
 import android.util.LayoutDirection;
 import android.util.PathParser;
 
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
 import com.android.settingslib.R;
 import com.android.settingslib.Utils;
 
diff --git a/packages/SettingsLib/src/com/android/settingslib/inputmethod/InputMethodAndSubtypeUtil.java b/packages/SettingsLib/src/com/android/settingslib/inputmethod/InputMethodAndSubtypeUtil.java
index 1712a6b..318e3dd 100644
--- a/packages/SettingsLib/src/com/android/settingslib/inputmethod/InputMethodAndSubtypeUtil.java
+++ b/packages/SettingsLib/src/com/android/settingslib/inputmethod/InputMethodAndSubtypeUtil.java
@@ -16,8 +16,6 @@
 
 package com.android.settingslib.inputmethod;
 
-import android.annotation.NonNull;
-import android.annotation.Nullable;
 import android.content.ContentResolver;
 import android.content.Context;
 import android.content.SharedPreferences;
@@ -30,6 +28,8 @@
 import android.view.inputmethod.InputMethodInfo;
 import android.view.inputmethod.InputMethodSubtype;
 
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
 import androidx.preference.Preference;
 import androidx.preference.PreferenceFragment;
 import androidx.preference.PreferenceScreen;
diff --git a/packages/SettingsLib/src/com/android/settingslib/inputmethod/InputMethodAndSubtypeUtilCompat.java b/packages/SettingsLib/src/com/android/settingslib/inputmethod/InputMethodAndSubtypeUtilCompat.java
index 78ec58b..a2316435 100644
--- a/packages/SettingsLib/src/com/android/settingslib/inputmethod/InputMethodAndSubtypeUtilCompat.java
+++ b/packages/SettingsLib/src/com/android/settingslib/inputmethod/InputMethodAndSubtypeUtilCompat.java
@@ -16,8 +16,6 @@
 
 package com.android.settingslib.inputmethod;
 
-import android.annotation.NonNull;
-import android.annotation.Nullable;
 import android.annotation.UserIdInt;
 import android.content.ContentResolver;
 import android.content.Context;
@@ -32,6 +30,8 @@
 import android.view.inputmethod.InputMethodInfo;
 import android.view.inputmethod.InputMethodSubtype;
 
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
 import androidx.preference.Preference;
 import androidx.preference.PreferenceFragmentCompat;
 import androidx.preference.PreferenceScreen;
diff --git a/packages/SettingsLib/src/com/android/settingslib/inputmethod/InputMethodSettingValuesWrapper.java b/packages/SettingsLib/src/com/android/settingslib/inputmethod/InputMethodSettingValuesWrapper.java
index 4384400..5c0c979 100644
--- a/packages/SettingsLib/src/com/android/settingslib/inputmethod/InputMethodSettingValuesWrapper.java
+++ b/packages/SettingsLib/src/com/android/settingslib/inputmethod/InputMethodSettingValuesWrapper.java
@@ -17,7 +17,6 @@
 package com.android.settingslib.inputmethod;
 
 import android.annotation.AnyThread;
-import android.annotation.NonNull;
 import android.annotation.UiThread;
 import android.content.ContentResolver;
 import android.content.Context;
@@ -26,6 +25,8 @@
 import android.view.inputmethod.InputMethodInfo;
 import android.view.inputmethod.InputMethodManager;
 
+import androidx.annotation.NonNull;
+
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.inputmethod.DirectBootAwareness;
 
diff --git a/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaManager.java b/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaManager.java
index 52b51d7..ba40a50 100644
--- a/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaManager.java
+++ b/packages/SettingsLib/src/com/android/settingslib/media/InfoMediaManager.java
@@ -43,8 +43,6 @@
 
 import static com.android.settingslib.media.LocalMediaManager.MediaDeviceState.STATE_SELECTED;
 
-import android.annotation.NonNull;
-import android.annotation.Nullable;
 import android.annotation.TargetApi;
 import android.app.Notification;
 import android.bluetooth.BluetoothAdapter;
@@ -59,6 +57,8 @@
 import android.util.Log;
 
 import androidx.annotation.DoNotInline;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
 import androidx.annotation.RequiresApi;
 
 import com.android.internal.annotations.VisibleForTesting;
diff --git a/packages/SettingsLib/src/com/android/settingslib/media/ManagerInfoMediaManager.java b/packages/SettingsLib/src/com/android/settingslib/media/ManagerInfoMediaManager.java
index 0be2e0e..97bbf12 100644
--- a/packages/SettingsLib/src/com/android/settingslib/media/ManagerInfoMediaManager.java
+++ b/packages/SettingsLib/src/com/android/settingslib/media/ManagerInfoMediaManager.java
@@ -16,8 +16,6 @@
 
 package com.android.settingslib.media;
 
-import android.annotation.NonNull;
-import android.annotation.Nullable;
 import android.app.Notification;
 import android.content.Context;
 import android.media.MediaRoute2Info;
@@ -27,6 +25,9 @@
 import android.text.TextUtils;
 import android.util.Log;
 
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.settingslib.bluetooth.LocalBluetoothManager;
 
@@ -80,14 +81,17 @@
 
     @Override
     protected void transferToRoute(@NonNull MediaRoute2Info route) {
-        mRouterManager.transfer(mPackageName, route);
+        // TODO: b/279555229 - provide real user handle of a caller.
+        mRouterManager.transfer(mPackageName, route, android.os.Process.myUserHandle());
     }
 
     @Override
     protected boolean connectDeviceWithoutPackageName(@NonNull MediaDevice device) {
         final RoutingSessionInfo info = mRouterManager.getSystemRoutingSession(null);
         if (info != null) {
-            mRouterManager.transfer(info, device.mRouteInfo);
+            // TODO: b/279555229 - provide real user handle and package name of a caller.
+            mRouterManager.transfer(
+                    info, device.mRouteInfo, android.os.Process.myUserHandle(), mPackageName);
             return true;
         }
         return false;
diff --git a/packages/SettingsLib/src/com/android/settingslib/media/PhoneMediaDevice.java b/packages/SettingsLib/src/com/android/settingslib/media/PhoneMediaDevice.java
index 80eeab5..0676ce5 100644
--- a/packages/SettingsLib/src/com/android/settingslib/media/PhoneMediaDevice.java
+++ b/packages/SettingsLib/src/com/android/settingslib/media/PhoneMediaDevice.java
@@ -29,7 +29,6 @@
 import static com.android.settingslib.media.MediaDevice.SelectionBehavior.SELECTION_BEHAVIOR_TRANSFER;
 
 import android.Manifest;
-import android.annotation.NonNull;
 import android.content.Context;
 import android.content.pm.PackageManager;
 import android.graphics.drawable.Drawable;
@@ -40,6 +39,7 @@
 import android.media.RouteListingPreference;
 import android.util.Log;
 
+import androidx.annotation.NonNull;
 import androidx.annotation.VisibleForTesting;
 
 import com.android.settingslib.R;
diff --git a/packages/SettingsLib/src/com/android/settingslib/net/NetworkCycleDataLoader.java b/packages/SettingsLib/src/com/android/settingslib/net/NetworkCycleDataLoader.java
index 5e9ac5a..bcfdebe 100644
--- a/packages/SettingsLib/src/com/android/settingslib/net/NetworkCycleDataLoader.java
+++ b/packages/SettingsLib/src/com/android/settingslib/net/NetworkCycleDataLoader.java
@@ -16,7 +16,6 @@
 
 package com.android.settingslib.net;
 
-import android.annotation.NonNull;
 import android.app.usage.NetworkStats;
 import android.app.usage.NetworkStatsManager;
 import android.content.Context;
@@ -27,6 +26,7 @@
 import android.util.Pair;
 import android.util.Range;
 
+import androidx.annotation.NonNull;
 import androidx.annotation.VisibleForTesting;
 import androidx.loader.content.AsyncTaskLoader;
 
diff --git a/packages/SettingsLib/src/com/android/settingslib/notification/EnableZenModeDialog.java b/packages/SettingsLib/src/com/android/settingslib/notification/EnableZenModeDialog.java
index 7fb959a..dfa5ed1 100644
--- a/packages/SettingsLib/src/com/android/settingslib/notification/EnableZenModeDialog.java
+++ b/packages/SettingsLib/src/com/android/settingslib/notification/EnableZenModeDialog.java
@@ -16,7 +16,6 @@
 
 package com.android.settingslib.notification;
 
-import android.annotation.Nullable;
 import android.app.ActivityManager;
 import android.app.AlarmManager;
 import android.app.AlertDialog;
@@ -43,6 +42,8 @@
 import android.widget.ScrollView;
 import android.widget.TextView;
 
+import androidx.annotation.Nullable;
+
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.policy.PhoneWindow;
 import com.android.settingslib.R;
diff --git a/packages/SettingsLib/src/com/android/settingslib/volume/MediaSessions.java b/packages/SettingsLib/src/com/android/settingslib/volume/MediaSessions.java
index fbf8a2f..23b2cc2 100644
--- a/packages/SettingsLib/src/com/android/settingslib/volume/MediaSessions.java
+++ b/packages/SettingsLib/src/com/android/settingslib/volume/MediaSessions.java
@@ -16,8 +16,6 @@
 
 package com.android.settingslib.volume;
 
-import android.annotation.NonNull;
-import android.annotation.Nullable;
 import android.app.PendingIntent;
 import android.content.Context;
 import android.content.Intent;
@@ -42,6 +40,9 @@
 import android.os.Message;
 import android.util.Log;
 
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
 import java.io.PrintWriter;
 import java.util.HashMap;
 import java.util.HashSet;
diff --git a/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPoint.java b/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPoint.java
index 303ee3c..cf45231 100644
--- a/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPoint.java
+++ b/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPoint.java
@@ -21,7 +21,6 @@
 
 import android.annotation.IntDef;
 import android.annotation.MainThread;
-import android.annotation.Nullable;
 import android.app.AppGlobals;
 import android.content.Context;
 import android.content.pm.ApplicationInfo;
@@ -58,6 +57,7 @@
 
 import androidx.annotation.GuardedBy;
 import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.util.CollectionUtils;
diff --git a/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPointPreference.java b/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPointPreference.java
index 98272cc..e508526 100644
--- a/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPointPreference.java
+++ b/packages/SettingsLib/src/com/android/settingslib/wifi/AccessPointPreference.java
@@ -15,7 +15,6 @@
  */
 package com.android.settingslib.wifi;
 
-import android.annotation.Nullable;
 import android.content.Context;
 import android.content.pm.PackageManager;
 import android.content.res.Resources;
@@ -32,6 +31,7 @@
 import android.widget.ImageView;
 import android.widget.TextView;
 
+import androidx.annotation.Nullable;
 import androidx.annotation.VisibleForTesting;
 import androidx.preference.Preference;
 import androidx.preference.PreferenceViewHolder;
diff --git a/packages/SettingsLib/src/com/android/settingslib/wifi/WifiRestrictionsCache.java b/packages/SettingsLib/src/com/android/settingslib/wifi/WifiRestrictionsCache.java
index 7ffae40..c0ca1252 100644
--- a/packages/SettingsLib/src/com/android/settingslib/wifi/WifiRestrictionsCache.java
+++ b/packages/SettingsLib/src/com/android/settingslib/wifi/WifiRestrictionsCache.java
@@ -18,12 +18,12 @@
 
 import static android.os.UserManager.DISALLOW_CONFIG_WIFI;
 
-import android.annotation.NonNull;
 import android.content.Context;
 import android.os.Bundle;
 import android.os.UserManager;
 import android.util.SparseArray;
 
+import androidx.annotation.NonNull;
 import androidx.annotation.VisibleForTesting;
 
 import java.util.HashMap;
diff --git a/packages/SettingsLib/src/com/android/settingslib/wifi/WifiStateWorker.java b/packages/SettingsLib/src/com/android/settingslib/wifi/WifiStateWorker.java
index a0c2698..14967ec 100644
--- a/packages/SettingsLib/src/com/android/settingslib/wifi/WifiStateWorker.java
+++ b/packages/SettingsLib/src/com/android/settingslib/wifi/WifiStateWorker.java
@@ -22,7 +22,6 @@
 import static android.net.wifi.WifiManager.WIFI_STATE_ENABLED;
 
 import android.annotation.AnyThread;
-import android.annotation.NonNull;
 import android.annotation.TestApi;
 import android.content.BroadcastReceiver;
 import android.content.Context;
@@ -34,6 +33,7 @@
 import android.util.Log;
 
 import androidx.annotation.GuardedBy;
+import androidx.annotation.NonNull;
 import androidx.annotation.VisibleForTesting;
 import androidx.annotation.WorkerThread;
 
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/HearingAidAudioRoutingHelperTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/HearingAidAudioRoutingHelperTest.java
index 8b5ea30..c835244 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/HearingAidAudioRoutingHelperTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/bluetooth/HearingAidAudioRoutingHelperTest.java
@@ -18,8 +18,10 @@
 
 import static com.google.common.truth.Truth.assertThat;
 
+import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.Mockito.atLeastOnce;
 import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.spy;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
@@ -43,6 +45,7 @@
 import org.mockito.junit.MockitoRule;
 import org.robolectric.RobolectricTestRunner;
 
+import java.util.Collections;
 import java.util.HashSet;
 import java.util.List;
 import java.util.Set;
@@ -79,6 +82,8 @@
         when(mAudioDeviceInfo.getAddress()).thenReturn(TEST_DEVICE_ADDRESS);
         when(mAudioManager.getDevices(AudioManager.GET_DEVICES_OUTPUTS)).thenReturn(
                 new AudioDeviceInfo[]{mAudioDeviceInfo});
+        doReturn(Collections.emptyList()).when(mAudioManager).getPreferredDevicesForStrategy(
+                any(AudioProductStrategy.class));
         when(mAudioStrategy.getAudioAttributesForLegacyStreamType(
                 AudioManager.STREAM_MUSIC))
                 .thenReturn((new AudioAttributes.Builder()).build());
@@ -92,7 +97,10 @@
     }
 
     @Test
-    public void setPreferredDeviceRoutingStrategies_valueAuto_callRemoveStrategy() {
+    public void setPreferredDeviceRoutingStrategies_hadValueThenValueAuto_callRemoveStrategy() {
+        when(mAudioManager.getPreferredDeviceForStrategy(mAudioStrategy)).thenReturn(
+                mHearingDeviceAttribute);
+
         mHelper.setPreferredDeviceRoutingStrategies(List.of(mAudioStrategy),
                 mHearingDeviceAttribute,
                 HearingAidAudioRoutingConstants.RoutingValue.AUTO);
@@ -101,6 +109,17 @@
     }
 
     @Test
+    public void setPreferredDeviceRoutingStrategies_NoValueThenValueAuto_notCallRemoveStrategy() {
+        when(mAudioManager.getPreferredDeviceForStrategy(mAudioStrategy)).thenReturn(null);
+
+        mHelper.setPreferredDeviceRoutingStrategies(List.of(mAudioStrategy),
+                mHearingDeviceAttribute,
+                HearingAidAudioRoutingConstants.RoutingValue.AUTO);
+
+        verify(mAudioManager, never()).removePreferredDeviceForStrategy(mAudioStrategy);
+    }
+
+    @Test
     public void setPreferredDeviceRoutingStrategies_valueHearingDevice_callSetStrategy() {
         mHelper.setPreferredDeviceRoutingStrategies(List.of(mAudioStrategy),
                 mHearingDeviceAttribute,
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/core/instrumentation/SettingsJankMonitorTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/core/instrumentation/SettingsJankMonitorTest.java
index 5aee8cd..194a0e2 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/core/instrumentation/SettingsJankMonitorTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/core/instrumentation/SettingsJankMonitorTest.java
@@ -27,9 +27,9 @@
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
-import android.annotation.NonNull;
 import android.view.View;
 
+import androidx.annotation.NonNull;
 import androidx.preference.PreferenceGroupAdapter;
 import androidx.preference.SwitchPreference;
 import androidx.recyclerview.widget.RecyclerView;
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/enterprise/FakeDeviceAdminStringProvider.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/enterprise/FakeDeviceAdminStringProvider.java
index 1d5f1b2..819d4b3 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/enterprise/FakeDeviceAdminStringProvider.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/enterprise/FakeDeviceAdminStringProvider.java
@@ -16,7 +16,7 @@
 
 package com.android.settingslib.enterprise;
 
-import android.annotation.Nullable;
+import androidx.annotation.Nullable;
 
 class FakeDeviceAdminStringProvider implements DeviceAdminStringProvider {
 
diff --git a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/net/NetworkCycleDataLoaderTest.java b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/net/NetworkCycleDataLoaderTest.java
index c79440e..77c46f7 100644
--- a/packages/SettingsLib/tests/robotests/src/com/android/settingslib/net/NetworkCycleDataLoaderTest.java
+++ b/packages/SettingsLib/tests/robotests/src/com/android/settingslib/net/NetworkCycleDataLoaderTest.java
@@ -26,7 +26,6 @@
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 
-import android.annotation.NonNull;
 import android.app.usage.NetworkStats;
 import android.app.usage.NetworkStatsManager;
 import android.content.Context;
@@ -37,6 +36,8 @@
 import android.text.format.DateUtils;
 import android.util.Range;
 
+import androidx.annotation.NonNull;
+
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
diff --git a/packages/SettingsLib/tests/robotests/testutils/com/android/settingslib/testutils/shadow/ShadowPermissionChecker.java b/packages/SettingsLib/tests/robotests/testutils/com/android/settingslib/testutils/shadow/ShadowPermissionChecker.java
index fae3aea..8448804 100644
--- a/packages/SettingsLib/tests/robotests/testutils/com/android/settingslib/testutils/shadow/ShadowPermissionChecker.java
+++ b/packages/SettingsLib/tests/robotests/testutils/com/android/settingslib/testutils/shadow/ShadowPermissionChecker.java
@@ -16,12 +16,13 @@
 
 package com.android.settingslib.testutils.shadow;
 
-import android.annotation.NonNull;
-import android.annotation.Nullable;
 import android.content.AttributionSource;
 import android.content.Context;
 import android.content.PermissionChecker;
 
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
 import org.robolectric.annotation.Implementation;
 import org.robolectric.annotation.Implements;
 
diff --git a/packages/SettingsLib/tests/robotests/testutils/com/android/settingslib/testutils/shadow/ShadowRouter2Manager.java b/packages/SettingsLib/tests/robotests/testutils/com/android/settingslib/testutils/shadow/ShadowRouter2Manager.java
index dac8142..fde378f 100644
--- a/packages/SettingsLib/tests/robotests/testutils/com/android/settingslib/testutils/shadow/ShadowRouter2Manager.java
+++ b/packages/SettingsLib/tests/robotests/testutils/com/android/settingslib/testutils/shadow/ShadowRouter2Manager.java
@@ -15,12 +15,13 @@
  */
 package com.android.settingslib.testutils.shadow;
 
-import android.annotation.NonNull;
-import android.annotation.Nullable;
 import android.media.MediaRoute2Info;
 import android.media.MediaRouter2Manager;
 import android.media.RoutingSessionInfo;
 
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
 import org.robolectric.RuntimeEnvironment;
 import org.robolectric.annotation.Implementation;
 import org.robolectric.annotation.Implements;
diff --git a/packages/SystemUI/aconfig/systemui.aconfig b/packages/SystemUI/aconfig/systemui.aconfig
index 8b27960..98a2d9f 100644
--- a/packages/SystemUI/aconfig/systemui.aconfig
+++ b/packages/SystemUI/aconfig/systemui.aconfig
@@ -261,3 +261,24 @@
         "prefer using alpha to distinguish network activity."
     bug: "310715220"
 }
+
+flag {
+    name: "haptic_volume_slider"
+    namespace: "systemui"
+    description: "Adds haptic feedback to the volume slider."
+    bug: "316953430"
+}
+
+flag {
+    name: "screenshare_notification_hiding"
+    namespace: "systemui"
+    description: "Enable hiding of notifications during screenshare"
+    bug: "312784809"
+}
+
+flag {
+   name: "bluetooth_qs_tile_dialog_auto_on_toggle"
+   namespace: "systemui"
+   description: "Displays the auto on toggle in the bluetooth QS tile dialog"
+   bug: "316985153"
+}
diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/AnimatableClockView.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/AnimatableClockView.kt
index b9d6643..2bfa7d9 100644
--- a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/AnimatableClockView.kt
+++ b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/AnimatableClockView.kt
@@ -33,6 +33,8 @@
 import com.android.systemui.animation.GlyphCallback
 import com.android.systemui.animation.TextAnimator
 import com.android.systemui.customization.R
+import com.android.systemui.log.core.LogcatOnlyMessageBuffer
+import com.android.systemui.log.core.LogLevel
 import com.android.systemui.log.core.Logger
 import com.android.systemui.log.core.MessageBuffer
 import java.io.PrintWriter
@@ -51,12 +53,13 @@
     defStyleAttr: Int = 0,
     defStyleRes: Int = 0
 ) : TextView(context, attrs, defStyleAttr, defStyleRes) {
-    var messageBuffer: MessageBuffer? = null
-        set(value) {
-            logger = if (value != null) Logger(value, TAG) else null
-        }
-
-    private var logger: Logger? = null
+    // To protect us from issues from this being null while the TextView constructor is running, we
+    // implement the get method and ensure a value is returned before initialization is complete.
+    private var logger = DEFAULT_LOGGER
+        get() = field ?: DEFAULT_LOGGER
+    var messageBuffer: MessageBuffer
+        get() = logger.buffer
+        set(value) { logger = Logger(value, TAG) }
 
     private val time = Calendar.getInstance()
 
@@ -133,8 +136,8 @@
     }
 
     override fun onAttachedToWindow() {
+        logger.d("onAttachedToWindow")
         super.onAttachedToWindow()
-        logger?.d("onAttachedToWindow")
         refreshFormat()
     }
 
@@ -150,13 +153,13 @@
         time.timeInMillis = timeOverrideInMillis ?: System.currentTimeMillis()
         contentDescription = DateFormat.format(descFormat, time)
         val formattedText = DateFormat.format(format, time)
-        logger?.d({ "refreshTime: new formattedText=$str1" }) { str1 = formattedText?.toString() }
+        logger.d({ "refreshTime: new formattedText=$str1" }) { str1 = formattedText?.toString() }
         // Setting text actually triggers a layout pass (because the text view is set to
         // wrap_content width and TextView always relayouts for this). Avoid needless
         // relayout if the text didn't actually change.
         if (!TextUtils.equals(text, formattedText)) {
             text = formattedText
-            logger?.d({ "refreshTime: done setting new time text to: $str1" }) {
+            logger.d({ "refreshTime: done setting new time text to: $str1" }) {
                 str1 = formattedText?.toString()
             }
             // Because the TextLayout may mutate under the hood as a result of the new text, we
@@ -165,21 +168,22 @@
             // without being notified TextInterpolator being notified.
             if (layout != null) {
                 textAnimator?.updateLayout(layout)
-                logger?.d("refreshTime: done updating textAnimator layout")
+                logger.d("refreshTime: done updating textAnimator layout")
             }
             requestLayout()
-            logger?.d("refreshTime: after requestLayout")
+            logger.d("refreshTime: after requestLayout")
         }
     }
 
     fun onTimeZoneChanged(timeZone: TimeZone?) {
+        logger.d({ "onTimeZoneChanged($str1)" }) { str1 = timeZone?.toString() }
         time.timeZone = timeZone
         refreshFormat()
-        logger?.d({ "onTimeZoneChanged newTimeZone=$str1" }) { str1 = timeZone?.toString() }
     }
 
     @SuppressLint("DrawAllocation")
     override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
+        logger.d("onMeasure")
         super.onMeasure(widthMeasureSpec, heightMeasureSpec)
         val animator = textAnimator
         if (animator == null) {
@@ -189,10 +193,10 @@
         } else {
             animator.updateLayout(layout)
         }
-        logger?.d("onMeasure")
     }
 
     override fun onDraw(canvas: Canvas) {
+        logger.d({ "onDraw($str1)"}) { str1 = text.toString() }
         // Use textAnimator to render text if animation is enabled.
         // Otherwise default to using standard draw functions.
         if (isAnimationEnabled) {
@@ -201,22 +205,23 @@
         } else {
             super.onDraw(canvas)
         }
-        logger?.d("onDraw")
     }
 
     override fun invalidate() {
+        @Suppress("UNNECESSARY_SAFE_CALL")
+        // logger won't be initialized when called by TextView's constructor
+        logger.d("invalidate")
         super.invalidate()
-        logger?.d("invalidate")
     }
 
     override fun onTextChanged(
-            text: CharSequence,
-            start: Int,
-            lengthBefore: Int,
-            lengthAfter: Int
+        text: CharSequence,
+        start: Int,
+        lengthBefore: Int,
+        lengthAfter: Int
     ) {
+        logger.d({ "onTextChanged($str1)" }) { str1 = text.toString() }
         super.onTextChanged(text, start, lengthBefore, lengthAfter)
-        logger?.d({ "onTextChanged text=$str1" }) { str1 = text.toString() }
     }
 
     fun setLineSpacingScale(scale: Float) {
@@ -230,7 +235,7 @@
     }
 
     fun animateColorChange() {
-        logger?.d("animateColorChange")
+        logger.d("animateColorChange")
         setTextStyle(
             weight = lockScreenWeight,
             textSize = -1f,
@@ -252,7 +257,7 @@
     }
 
     fun animateAppearOnLockscreen() {
-        logger?.d("animateAppearOnLockscreen")
+        logger.d("animateAppearOnLockscreen")
         setTextStyle(
             weight = dozingWeight,
             textSize = -1f,
@@ -278,7 +283,7 @@
         if (isAnimationEnabled && textAnimator == null) {
             return
         }
-        logger?.d("animateFoldAppear")
+        logger.d("animateFoldAppear")
         setTextStyle(
             weight = lockScreenWeightInternal,
             textSize = -1f,
@@ -305,7 +310,7 @@
             // Skip charge animation if dozing animation is already playing.
             return
         }
-        logger?.d("animateCharge")
+        logger.d("animateCharge")
         val startAnimPhase2 = Runnable {
             setTextStyle(
                 weight = if (isDozing()) dozingWeight else lockScreenWeight,
@@ -329,7 +334,7 @@
     }
 
     fun animateDoze(isDozing: Boolean, animate: Boolean) {
-        logger?.d("animateDoze")
+        logger.d("animateDoze")
         setTextStyle(
             weight = if (isDozing) dozingWeight else lockScreenWeight,
             textSize = -1f,
@@ -448,7 +453,7 @@
             isSingleLineInternal && !use24HourFormat -> Patterns.sClockView12
             else -> DOUBLE_LINE_FORMAT_12_HOUR
         }
-        logger?.d({ "refreshFormat format=$str1" }) { str1 = format?.toString() }
+        logger.d({ "refreshFormat($str1)" }) { str1 = format?.toString() }
 
         descFormat = if (use24HourFormat) Patterns.sClockView24 else Patterns.sClockView12
         refreshTime()
@@ -552,6 +557,8 @@
 
     companion object {
         private val TAG = AnimatableClockView::class.simpleName!!
+        private val DEFAULT_LOGGER = Logger(LogcatOnlyMessageBuffer(LogLevel.WARNING), TAG)
+
         const val ANIMATION_DURATION_FOLD_TO_AOD: Int = 600
         private const val DOUBLE_LINE_FORMAT_12_HOUR = "hh\nmm"
         private const val DOUBLE_LINE_FORMAT_24_HOUR = "HH\nmm"
diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/ClockRegistry.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/ClockRegistry.kt
index cdd074d..41bde52 100644
--- a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/ClockRegistry.kt
+++ b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/ClockRegistry.kt
@@ -21,20 +21,16 @@
 import android.net.Uri
 import android.os.UserHandle
 import android.provider.Settings
-import android.util.Log
 import androidx.annotation.OpenForTesting
-import com.android.systemui.log.LogMessageImpl
 import com.android.systemui.log.core.LogLevel
-import com.android.systemui.log.core.LogMessage
+import com.android.systemui.log.core.LogcatOnlyMessageBuffer
 import com.android.systemui.log.core.Logger
-import com.android.systemui.log.core.MessageBuffer
-import com.android.systemui.log.core.MessageInitializer
-import com.android.systemui.log.core.MessagePrinter
 import com.android.systemui.plugins.PluginLifecycleManager
 import com.android.systemui.plugins.PluginListener
 import com.android.systemui.plugins.PluginManager
 import com.android.systemui.plugins.clocks.ClockController
 import com.android.systemui.plugins.clocks.ClockId
+import com.android.systemui.plugins.clocks.ClockMessageBuffers
 import com.android.systemui.plugins.clocks.ClockMetadata
 import com.android.systemui.plugins.clocks.ClockProvider
 import com.android.systemui.plugins.clocks.ClockProviderPlugin
@@ -77,32 +73,6 @@
     return result ?: value
 }
 
-private val TMP_MESSAGE: LogMessage by lazy { LogMessageImpl.Factory.create() }
-
-private inline fun Logger?.tryLog(
-    tag: String,
-    level: LogLevel,
-    messageInitializer: MessageInitializer,
-    noinline messagePrinter: MessagePrinter,
-    ex: Throwable? = null,
-) {
-    if (this != null) {
-        // Wrap messagePrinter to convert it from crossinline to noinline
-        this.log(level, messagePrinter, ex, messageInitializer)
-    } else {
-        messageInitializer(TMP_MESSAGE)
-        val msg = messagePrinter(TMP_MESSAGE)
-        when (level) {
-            LogLevel.VERBOSE -> Log.v(tag, msg, ex)
-            LogLevel.DEBUG -> Log.d(tag, msg, ex)
-            LogLevel.INFO -> Log.i(tag, msg, ex)
-            LogLevel.WARNING -> Log.w(tag, msg, ex)
-            LogLevel.ERROR -> Log.e(tag, msg, ex)
-            LogLevel.WTF -> Log.wtf(tag, msg, ex)
-        }
-    }
-}
-
 /** ClockRegistry aggregates providers and plugins */
 open class ClockRegistry(
     val context: Context,
@@ -114,12 +84,15 @@
     val handleAllUsers: Boolean,
     defaultClockProvider: ClockProvider,
     val fallbackClockId: ClockId = DEFAULT_CLOCK_ID,
-    messageBuffer: MessageBuffer? = null,
+    val clockBuffers: ClockMessageBuffers? = null,
     val keepAllLoaded: Boolean,
     subTag: String,
     var isTransitClockEnabled: Boolean = false,
 ) {
     private val TAG = "${ClockRegistry::class.simpleName} ($subTag)"
+    private val logger: Logger =
+        Logger(clockBuffers?.infraMessageBuffer ?: LogcatOnlyMessageBuffer(LogLevel.DEBUG), TAG)
+
     interface ClockChangeListener {
         // Called when the active clock changes
         fun onCurrentClockChanged() {}
@@ -128,7 +101,6 @@
         fun onAvailableClocksChanged() {}
     }
 
-    private val logger: Logger? = if (messageBuffer != null) Logger(messageBuffer, TAG) else null
     private val availableClocks = ConcurrentHashMap<ClockId, ClockInfo>()
     private val clockChangeListeners = mutableListOf<ClockChangeListener>()
     private val settingObserver =
@@ -157,21 +129,15 @@
 
                 val knownClocks = KNOWN_PLUGINS.get(manager.getPackage())
                 if (knownClocks == null) {
-                    logger.tryLog(
-                        TAG,
-                        LogLevel.WARNING,
-                        { str1 = manager.getPackage() },
-                        { "Loading unrecognized clock package: $str1" }
-                    )
+                    logger.w({ "Loading unrecognized clock package: $str1" }) {
+                        str1 = manager.getPackage()
+                    }
                     return true
                 }
 
-                logger.tryLog(
-                    TAG,
-                    LogLevel.INFO,
-                    { str1 = manager.getPackage() },
-                    { "Skipping initial load of known clock package package: $str1" }
-                )
+                logger.i({ "Skipping initial load of known clock package package: $str1" }) {
+                    str1 = manager.getPackage()
+                }
 
                 var isCurrentClock = false
                 var isClockListChanged = false
@@ -185,19 +151,14 @@
                         }
 
                     if (manager != info.manager) {
-                        logger.tryLog(
-                            TAG,
-                            LogLevel.ERROR,
-                            {
-                                str1 = id
-                                str2 = info.manager.toString()
-                                str3 = manager.toString()
-                            },
-                            {
-                                "Clock Id conflict on attach: " +
-                                    "$str1 is double registered by $str2 and $str3"
-                            }
-                        )
+                        logger.e({
+                            "Clock Id conflict on attach: " +
+                                "$str1 is double registered by $str2 and $str3"
+                        }) {
+                            str1 = id
+                            str2 = info.manager.toString()
+                            str3 = manager.toString()
+                        }
                         continue
                     }
 
@@ -219,6 +180,8 @@
                 pluginContext: Context,
                 manager: PluginLifecycleManager<ClockProviderPlugin>
             ) {
+                plugin.initialize(clockBuffers)
+
                 var isClockListChanged = false
                 for (clock in plugin.getClocks()) {
                     val id = clock.clockId
@@ -233,19 +196,14 @@
                         }
 
                     if (manager != info.manager) {
-                        logger.tryLog(
-                            TAG,
-                            LogLevel.ERROR,
-                            {
-                                str1 = id
-                                str2 = info.manager.toString()
-                                str3 = manager.toString()
-                            },
-                            {
-                                "Clock Id conflict on load: " +
-                                    "$str1 is double registered by $str2 and $str3"
-                            }
-                        )
+                        logger.e({
+                            "Clock Id conflict on load: " +
+                                "$str1 is double registered by $str2 and $str3"
+                        }) {
+                            str1 = id
+                            str2 = info.manager.toString()
+                            str3 = manager.toString()
+                        }
                         manager.unloadPlugin()
                         continue
                     }
@@ -268,19 +226,14 @@
                     val id = clock.clockId
                     val info = availableClocks[id]
                     if (info?.manager != manager) {
-                        logger.tryLog(
-                            TAG,
-                            LogLevel.ERROR,
-                            {
-                                str1 = id
-                                str2 = info?.manager.toString()
-                                str3 = manager.toString()
-                            },
-                            {
-                                "Clock Id conflict on unload: " +
-                                    "$str1 is double registered by $str2 and $str3"
-                            }
-                        )
+                        logger.e({
+                            "Clock Id conflict on unload: " +
+                                "$str1 is double registered by $str2 and $str3"
+                        }) {
+                            str1 = id
+                            str2 = info?.manager.toString()
+                            str3 = manager.toString()
+                        }
                         continue
                     }
                     info.provider = null
@@ -350,7 +303,7 @@
 
                 ClockSettings.deserialize(json)
             } catch (ex: Exception) {
-                logger.tryLog(TAG, LogLevel.ERROR, {}, { "Failed to parse clock settings" }, ex)
+                logger.e("Failed to parse clock settings", ex)
                 null
             }
         settings = result
@@ -379,7 +332,7 @@
                 )
             }
         } catch (ex: Exception) {
-            logger.tryLog(TAG, LogLevel.ERROR, {}, { "Failed to set clock settings" }, ex)
+            logger.e("Failed to set clock settings", ex)
         }
         settings = value
     }
@@ -451,7 +404,8 @@
         }
 
     init {
-        // Register default clock designs
+        // Initialize & register default clock designs
+        defaultClockProvider.initialize(clockBuffers)
         for (clock in defaultClockProvider.getClocks()) {
             availableClocks[clock.clockId] = ClockInfo(clock, defaultClockProvider, null)
         }
@@ -514,12 +468,7 @@
     fun verifyLoadedProviders() {
         val shouldSchedule = isQueued.compareAndSet(false, true)
         if (!shouldSchedule) {
-            logger.tryLog(
-                TAG,
-                LogLevel.VERBOSE,
-                {},
-                { "verifyLoadedProviders: shouldSchedule=false" }
-            )
+            logger.v("verifyLoadedProviders: shouldSchedule=false")
             return
         }
 
@@ -528,12 +477,7 @@
             synchronized(availableClocks) {
                 isQueued.set(false)
                 if (keepAllLoaded) {
-                    logger.tryLog(
-                        TAG,
-                        LogLevel.INFO,
-                        {},
-                        { "verifyLoadedProviders: keepAllLoaded=true" }
-                    )
+                    logger.i("verifyLoadedProviders: keepAllLoaded=true")
                     // Enforce that all plugins are loaded if requested
                     for ((_, info) in availableClocks) {
                         info.manager?.loadPlugin()
@@ -543,12 +487,7 @@
 
                 val currentClock = availableClocks[currentClockId]
                 if (currentClock == null) {
-                    logger.tryLog(
-                        TAG,
-                        LogLevel.INFO,
-                        {},
-                        { "verifyLoadedProviders: currentClock=null" }
-                    )
+                    logger.i("verifyLoadedProviders: currentClock=null")
                     // Current Clock missing, load no plugins and use default
                     for ((_, info) in availableClocks) {
                         info.manager?.unloadPlugin()
@@ -556,12 +495,7 @@
                     return@launch
                 }
 
-                logger.tryLog(
-                    TAG,
-                    LogLevel.INFO,
-                    {},
-                    { "verifyLoadedProviders: load currentClock" }
-                )
+                logger.i("verifyLoadedProviders: load currentClock")
                 val currentManager = currentClock.manager
                 currentManager?.loadPlugin()
 
@@ -577,30 +511,26 @@
 
     private fun onConnected(info: ClockInfo) {
         val isCurrent = currentClockId == info.metadata.clockId
-        logger.tryLog(
-            TAG,
+        logger.log(
             if (isCurrent) LogLevel.INFO else LogLevel.DEBUG,
-            {
-                str1 = info.metadata.clockId
-                str2 = info.manager.toString()
-                bool1 = isCurrent
-            },
             { "Connected $str1 @$str2" + if (bool1) " (Current Clock)" else "" }
-        )
+        ) {
+            str1 = info.metadata.clockId
+            str2 = info.manager.toString()
+            bool1 = isCurrent
+        }
     }
 
     private fun onLoaded(info: ClockInfo) {
         val isCurrent = currentClockId == info.metadata.clockId
-        logger.tryLog(
-            TAG,
+        logger.log(
             if (isCurrent) LogLevel.INFO else LogLevel.DEBUG,
-            {
-                str1 = info.metadata.clockId
-                str2 = info.manager.toString()
-                bool1 = isCurrent
-            },
             { "Loaded $str1 @$str2" + if (bool1) " (Current Clock)" else "" }
-        )
+        ) {
+            str1 = info.metadata.clockId
+            str2 = info.manager.toString()
+            bool1 = isCurrent
+        }
 
         if (isCurrent) {
             triggerOnCurrentClockChanged()
@@ -609,16 +539,14 @@
 
     private fun onUnloaded(info: ClockInfo) {
         val isCurrent = currentClockId == info.metadata.clockId
-        logger.tryLog(
-            TAG,
+        logger.log(
             if (isCurrent) LogLevel.WARNING else LogLevel.DEBUG,
-            {
-                str1 = info.metadata.clockId
-                str2 = info.manager.toString()
-                bool1 = isCurrent
-            },
             { "Unloaded $str1 @$str2" + if (bool1) " (Current Clock)" else "" }
-        )
+        ) {
+            str1 = info.metadata.clockId
+            str2 = info.manager.toString()
+            bool1 = isCurrent
+        }
 
         if (isCurrent) {
             triggerOnCurrentClockChanged()
@@ -627,16 +555,14 @@
 
     private fun onDisconnected(info: ClockInfo) {
         val isCurrent = currentClockId == info.metadata.clockId
-        logger.tryLog(
-            TAG,
+        logger.log(
             if (isCurrent) LogLevel.INFO else LogLevel.DEBUG,
-            {
-                str1 = info.metadata.clockId
-                str2 = info.manager.toString()
-                bool1 = isCurrent
-            },
             { "Disconnected $str1 @$str2" + if (bool1) " (Current Clock)" else "" }
-        )
+        ) {
+            str1 = info.metadata.clockId
+            str2 = info.manager.toString()
+            bool1 = isCurrent
+        }
     }
 
     fun getClocks(): List<ClockMetadata> {
@@ -676,23 +602,13 @@
         if (isEnabled && clockId.isNotEmpty()) {
             val clock = createClock(clockId)
             if (clock != null) {
-                logger.tryLog(TAG, LogLevel.INFO, { str1 = clockId }, { "Rendering clock $str1" })
+                logger.i({ "Rendering clock $str1" }) { str1 = clockId }
                 return clock
             } else if (availableClocks.containsKey(clockId)) {
-                logger.tryLog(
-                    TAG,
-                    LogLevel.WARNING,
-                    { str1 = clockId },
-                    { "Clock $str1 not loaded; using default" }
-                )
+                logger.w({ "Clock $str1 not loaded; using default" }) { str1 = clockId }
                 verifyLoadedProviders()
             } else {
-                logger.tryLog(
-                    TAG,
-                    LogLevel.ERROR,
-                    { str1 = clockId },
-                    { "Clock $str1 not found; using default" }
-                )
+                logger.e({ "Clock $str1 not found; using default" }) { str1 = clockId }
             }
         }
 
diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/DefaultClockController.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/DefaultClockController.kt
index 01c03b1..99d3216 100644
--- a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/DefaultClockController.kt
+++ b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/DefaultClockController.kt
@@ -33,6 +33,7 @@
 import com.android.systemui.plugins.clocks.ClockFaceConfig
 import com.android.systemui.plugins.clocks.ClockFaceController
 import com.android.systemui.plugins.clocks.ClockFaceEvents
+import com.android.systemui.plugins.clocks.ClockMessageBuffers
 import com.android.systemui.plugins.clocks.ClockSettings
 import com.android.systemui.plugins.clocks.DefaultClockFaceLayout
 import com.android.systemui.plugins.clocks.WeatherData
@@ -41,8 +42,6 @@
 import java.util.Locale
 import java.util.TimeZone
 
-private val TAG = DefaultClockController::class.simpleName
-
 /**
  * Controls the default clock visuals.
  *
@@ -56,6 +55,7 @@
     private val settings: ClockSettings?,
     private val hasStepClockAnimation: Boolean = false,
     private val migratedClocks: Boolean = false,
+    messageBuffers: ClockMessageBuffers? = null,
 ) : ClockController {
     override val smallClock: DefaultClockFaceController
     override val largeClock: LargeClockFaceController
@@ -83,13 +83,15 @@
             DefaultClockFaceController(
                 layoutInflater.inflate(R.layout.clock_default_small, parent, false)
                     as AnimatableClockView,
-                settings?.seedColor
+                settings?.seedColor,
+                messageBuffers?.smallClockMessageBuffer
             )
         largeClock =
             LargeClockFaceController(
                 layoutInflater.inflate(R.layout.clock_default_large, parent, false)
                     as AnimatableClockView,
-                settings?.seedColor
+                settings?.seedColor,
+                messageBuffers?.largeClockMessageBuffer
             )
         clocks = listOf(smallClock.view, largeClock.view)
 
@@ -110,6 +112,7 @@
     open inner class DefaultClockFaceController(
         override val view: AnimatableClockView,
         var seedColor: Int?,
+        messageBuffer: MessageBuffer?,
     ) : ClockFaceController {
 
         // MAGENTA is a placeholder, and will be assigned correctly in initialize
@@ -120,12 +123,6 @@
         override val config = ClockFaceConfig()
         override val layout = DefaultClockFaceLayout(view)
 
-        override var messageBuffer: MessageBuffer?
-            get() = view.messageBuffer
-            set(value) {
-                view.messageBuffer = value
-            }
-
         override var animations: DefaultClockAnimations = DefaultClockAnimations(view, 0f, 0f)
             internal set
 
@@ -134,6 +131,7 @@
                 currentColor = seedColor!!
             }
             view.setColors(DOZE_COLOR, currentColor)
+            messageBuffer?.let { view.messageBuffer = it }
         }
 
         override val events =
@@ -188,7 +186,8 @@
     inner class LargeClockFaceController(
         view: AnimatableClockView,
         seedColor: Int?,
-    ) : DefaultClockFaceController(view, seedColor) {
+        messageBuffer: MessageBuffer?,
+    ) : DefaultClockFaceController(view, seedColor, messageBuffer) {
         override val layout = DefaultClockFaceLayout(view)
         override val config =
             ClockFaceConfig(hasCustomPositionUpdatedAnimation = hasStepClockAnimation)
diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/DefaultClockProvider.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/DefaultClockProvider.kt
index a219be5..20f87a0 100644
--- a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/DefaultClockProvider.kt
+++ b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/DefaultClockProvider.kt
@@ -20,6 +20,7 @@
 import com.android.systemui.customization.R
 import com.android.systemui.plugins.clocks.ClockController
 import com.android.systemui.plugins.clocks.ClockId
+import com.android.systemui.plugins.clocks.ClockMessageBuffers
 import com.android.systemui.plugins.clocks.ClockMetadata
 import com.android.systemui.plugins.clocks.ClockProvider
 import com.android.systemui.plugins.clocks.ClockSettings
@@ -35,6 +36,12 @@
     val hasStepClockAnimation: Boolean = false,
     val migratedClocks: Boolean = false
 ) : ClockProvider {
+    private var messageBuffers: ClockMessageBuffers? = null
+
+    override fun initialize(buffers: ClockMessageBuffers?) {
+        messageBuffers = buffers
+    }
+
     override fun getClocks(): List<ClockMetadata> = listOf(ClockMetadata(DEFAULT_CLOCK_ID))
 
     override fun createClock(settings: ClockSettings): ClockController {
@@ -49,6 +56,7 @@
             settings,
             hasStepClockAnimation,
             migratedClocks,
+            messageBuffers,
         )
     }
 
diff --git a/packages/SystemUI/log/src/com/android/systemui/log/core/LogcatOnlyMessageBuffer.kt b/packages/SystemUI/log/src/com/android/systemui/log/core/LogcatOnlyMessageBuffer.kt
new file mode 100644
index 0000000..006b521
--- /dev/null
+++ b/packages/SystemUI/log/src/com/android/systemui/log/core/LogcatOnlyMessageBuffer.kt
@@ -0,0 +1,76 @@
+/*
+ * 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.log.core
+
+import android.util.Log
+import com.android.systemui.log.LogMessageImpl
+
+/**
+ * A simple implementation of [MessageBuffer] that forwards messages to [android.util.Log]
+ * immediately. This defeats the intention behind [LogBuffer] and should only be used when
+ * [LogBuffer]s are unavailable in a certain context.
+ */
+class LogcatOnlyMessageBuffer(
+    val targetLogLevel: LogLevel,
+) : MessageBuffer {
+    private val singleMessage = LogMessageImpl.Factory.create()
+    private var isObtained: Boolean = false
+
+    @Synchronized
+    override fun obtain(
+        tag: String,
+        level: LogLevel,
+        messagePrinter: MessagePrinter,
+        exception: Throwable?,
+    ): LogMessage {
+        if (isObtained) {
+            throw UnsupportedOperationException(
+                "Message has already been obtained. Call order is incorrect."
+            )
+        }
+
+        singleMessage.reset(tag, level, System.currentTimeMillis(), messagePrinter, exception)
+        isObtained = true
+        return singleMessage
+    }
+
+    @Synchronized
+    override fun commit(message: LogMessage) {
+        if (singleMessage != message) {
+            throw IllegalArgumentException("Message argument is not the expected message.")
+        }
+        if (!isObtained) {
+            throw UnsupportedOperationException(
+                "Message has not been obtained. Call order is incorrect."
+            )
+        }
+
+        if (message.level >= targetLogLevel) {
+            val strMessage = message.messagePrinter(message)
+            when (message.level) {
+                LogLevel.VERBOSE -> Log.v(message.tag, strMessage, message.exception)
+                LogLevel.DEBUG -> Log.d(message.tag, strMessage, message.exception)
+                LogLevel.INFO -> Log.i(message.tag, strMessage, message.exception)
+                LogLevel.WARNING -> Log.w(message.tag, strMessage, message.exception)
+                LogLevel.ERROR -> Log.e(message.tag, strMessage, message.exception)
+                LogLevel.WTF -> Log.wtf(message.tag, strMessage, message.exception)
+            }
+        }
+
+        isObtained = false
+    }
+}
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/authentication/data/repository/AuthenticationRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/authentication/data/repository/AuthenticationRepositoryTest.kt
index 64ddbc7..c961be9 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/authentication/data/repository/AuthenticationRepositoryTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/authentication/data/repository/AuthenticationRepositoryTest.kt
@@ -35,8 +35,10 @@
 import com.android.systemui.statusbar.pipeline.mobile.util.FakeMobileMappingsProxy
 import com.android.systemui.user.data.repository.FakeUserRepository
 import com.android.systemui.util.mockito.whenever
+import com.android.systemui.util.time.FakeSystemClock
 import com.google.common.truth.Truth.assertThat
 import java.util.function.Function
+import kotlin.time.Duration.Companion.seconds
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.runBlocking
 import kotlinx.coroutines.test.runCurrent
@@ -58,6 +60,7 @@
 
     private val testUtils = SceneTestUtils(this)
     private val testScope = testUtils.testScope
+    private val clock = FakeSystemClock()
     private val userRepository = FakeUserRepository()
     private lateinit var mobileConnectionsRepository: FakeMobileConnectionsRepository
 
@@ -78,8 +81,10 @@
         underTest =
             AuthenticationRepositoryImpl(
                 applicationScope = testScope.backgroundScope,
-                getSecurityMode = getSecurityMode,
                 backgroundDispatcher = testUtils.testDispatcher,
+                flags = testUtils.sceneContainerFlags,
+                clock = clock,
+                getSecurityMode = getSecurityMode,
                 userRepository = userRepository,
                 lockPatternUtils = lockPatternUtils,
                 broadcastDispatcher = fakeBroadcastDispatcher,
@@ -141,22 +146,6 @@
         }
 
     @Test
-    fun reportAuthenticationAttempt_emitsAuthenticationChallengeResult() =
-        testScope.runTest {
-            val authenticationChallengeResults by
-                collectValues(underTest.authenticationChallengeResult)
-
-            runCurrent()
-            underTest.reportAuthenticationAttempt(true)
-            runCurrent()
-            underTest.reportAuthenticationAttempt(false)
-            runCurrent()
-            underTest.reportAuthenticationAttempt(true)
-
-            assertThat(authenticationChallengeResults).isEqualTo(listOf(true, false, true))
-        }
-
-    @Test
     fun isPinEnhancedPrivacyEnabled() =
         testScope.runTest {
             whenever(lockPatternUtils.isPinEnhancedPrivacyEnabled(USER_INFOS[0].id))
@@ -172,6 +161,45 @@
             assertThat(values.last()).isTrue()
         }
 
+    @Test
+    fun lockoutEndTimestamp() =
+        testScope.runTest {
+            val lockoutEndMs = clock.elapsedRealtime() + 30.seconds.inWholeMilliseconds
+            whenever(lockPatternUtils.getLockoutAttemptDeadline(USER_INFOS[0].id))
+                .thenReturn(lockoutEndMs)
+            whenever(lockPatternUtils.getLockoutAttemptDeadline(USER_INFOS[1].id)).thenReturn(0)
+
+            // Switch to a user who is not locked-out.
+            userRepository.setSelectedUserInfo(USER_INFOS[1])
+            assertThat(underTest.lockoutEndTimestamp).isNull()
+
+            // Switch back to the locked-out user, verify the timestamp is up-to-date.
+            userRepository.setSelectedUserInfo(USER_INFOS[0])
+            assertThat(underTest.lockoutEndTimestamp).isEqualTo(lockoutEndMs)
+
+            // After the lockout expires, null is returned.
+            clock.setElapsedRealtime(lockoutEndMs)
+            assertThat(underTest.lockoutEndTimestamp).isNull()
+        }
+
+    @Test
+    fun hasLockoutOccurred() =
+        testScope.runTest {
+            val hasLockoutOccurred by collectLastValue(underTest.hasLockoutOccurred)
+            assertThat(hasLockoutOccurred).isFalse()
+
+            underTest.reportLockoutStarted(1000)
+            assertThat(hasLockoutOccurred).isTrue()
+
+            clock.setElapsedRealtime(clock.elapsedRealtime() + 60.seconds.inWholeMilliseconds)
+
+            underTest.reportAuthenticationAttempt(isSuccessful = false)
+            assertThat(hasLockoutOccurred).isTrue()
+
+            underTest.reportAuthenticationAttempt(isSuccessful = true)
+            assertThat(hasLockoutOccurred).isFalse()
+        }
+
     private fun setSecurityModeAndDispatchBroadcast(
         securityMode: KeyguardSecurityModel.SecurityMode,
     ) {
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractorTest.kt
index 08cd7ed..c113b37 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractorTest.kt
@@ -21,14 +21,18 @@
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.authentication.data.repository.FakeAuthenticationRepository
-import com.android.systemui.authentication.shared.model.AuthenticationLockoutModel
-import com.android.systemui.authentication.shared.model.AuthenticationMethodModel
+import com.android.systemui.authentication.shared.model.AuthenticationMethodModel.None
+import com.android.systemui.authentication.shared.model.AuthenticationMethodModel.Password
+import com.android.systemui.authentication.shared.model.AuthenticationMethodModel.Pattern
+import com.android.systemui.authentication.shared.model.AuthenticationMethodModel.Pin
 import com.android.systemui.authentication.shared.model.AuthenticationPatternCoordinate
 import com.android.systemui.coroutines.collectLastValue
 import com.android.systemui.scene.SceneTestUtils
 import com.google.common.truth.Truth.assertThat
+import kotlin.time.Duration.Companion.seconds
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.test.advanceTimeBy
+import kotlinx.coroutines.test.currentTime
 import kotlinx.coroutines.test.runCurrent
 import kotlinx.coroutines.test.runTest
 import org.junit.Test
@@ -43,21 +47,23 @@
     private val testScope = utils.testScope
     private val underTest = utils.authenticationInteractor()
 
+    private val onAuthenticationResult by
+        testScope.collectLastValue(underTest.onAuthenticationResult)
+    private val failedAuthenticationAttempts by
+        testScope.collectLastValue(underTest.failedAuthenticationAttempts)
+
     @Test
     fun authenticationMethod() =
         testScope.runTest {
             val authMethod by collectLastValue(underTest.authenticationMethod)
             runCurrent()
-            assertThat(authMethod).isEqualTo(AuthenticationMethodModel.Pin)
-            assertThat(underTest.getAuthenticationMethod()).isEqualTo(AuthenticationMethodModel.Pin)
+            assertThat(authMethod).isEqualTo(Pin)
+            assertThat(underTest.getAuthenticationMethod()).isEqualTo(Pin)
 
-            utils.authenticationRepository.setAuthenticationMethod(
-                AuthenticationMethodModel.Password
-            )
+            utils.authenticationRepository.setAuthenticationMethod(Password)
 
-            assertThat(authMethod).isEqualTo(AuthenticationMethodModel.Password)
-            assertThat(underTest.getAuthenticationMethod())
-                .isEqualTo(AuthenticationMethodModel.Password)
+            assertThat(authMethod).isEqualTo(Password)
+            assertThat(underTest.getAuthenticationMethod()).isEqualTo(Password)
         }
 
     @Test
@@ -66,51 +72,45 @@
             val authMethod by collectLastValue(underTest.authenticationMethod)
             runCurrent()
 
-            utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.None)
+            utils.authenticationRepository.setAuthenticationMethod(None)
 
-            assertThat(authMethod).isEqualTo(AuthenticationMethodModel.None)
-            assertThat(underTest.getAuthenticationMethod())
-                .isEqualTo(AuthenticationMethodModel.None)
+            assertThat(authMethod).isEqualTo(None)
+            assertThat(underTest.getAuthenticationMethod()).isEqualTo(None)
         }
 
     @Test
     fun authenticate_withCorrectPin_succeeds() =
         testScope.runTest {
-            val lockout by collectLastValue(underTest.lockout)
-            utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
+            utils.authenticationRepository.setAuthenticationMethod(Pin)
 
-            assertThat(underTest.authenticate(FakeAuthenticationRepository.DEFAULT_PIN))
-                .isEqualTo(AuthenticationResult.SUCCEEDED)
-            assertThat(lockout).isNull()
-            assertThat(utils.authenticationRepository.lockoutStartedReportCount).isEqualTo(0)
+            assertSucceeded(underTest.authenticate(FakeAuthenticationRepository.DEFAULT_PIN))
         }
 
     @Test
     fun authenticate_withIncorrectPin_fails() =
         testScope.runTest {
-            utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
+            utils.authenticationRepository.setAuthenticationMethod(Pin)
 
-            assertThat(underTest.authenticate(listOf(9, 8, 7, 6, 5, 4)))
-                .isEqualTo(AuthenticationResult.FAILED)
+            assertFailed(underTest.authenticate(listOf(9, 8, 7, 6, 5, 4)))
         }
 
     @Test(expected = IllegalArgumentException::class)
     fun authenticate_withEmptyPin_throwsException() =
         testScope.runTest {
-            utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
+            utils.authenticationRepository.setAuthenticationMethod(Pin)
             underTest.authenticate(listOf())
         }
 
     @Test
     fun authenticate_withCorrectMaxLengthPin_succeeds() =
         testScope.runTest {
-            val pin = List(16) { 9 }
+            val correctMaxLengthPin = List(16) { 9 }
             utils.authenticationRepository.apply {
-                setAuthenticationMethod(AuthenticationMethodModel.Pin)
-                overrideCredential(pin)
+                setAuthenticationMethod(Pin)
+                overrideCredential(correctMaxLengthPin)
             }
 
-            assertThat(underTest.authenticate(pin)).isEqualTo(AuthenticationResult.SUCCEEDED)
+            assertSucceeded(underTest.authenticate(correctMaxLengthPin))
         }
 
     @Test
@@ -122,88 +122,64 @@
             // If the policy changes, there is work to do in SysUI.
             assertThat(DevicePolicyManager.MAX_PASSWORD_LENGTH).isLessThan(17)
 
-            utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
-            assertThat(underTest.authenticate(List(17) { 9 }))
-                .isEqualTo(AuthenticationResult.FAILED)
+            utils.authenticationRepository.setAuthenticationMethod(Pin)
+
+            assertFailed(underTest.authenticate(List(17) { 9 }))
         }
 
     @Test
     fun authenticate_withCorrectPassword_succeeds() =
         testScope.runTest {
-            val lockout by collectLastValue(underTest.lockout)
-            utils.authenticationRepository.setAuthenticationMethod(
-                AuthenticationMethodModel.Password
-            )
+            utils.authenticationRepository.setAuthenticationMethod(Password)
 
-            assertThat(underTest.authenticate("password".toList()))
-                .isEqualTo(AuthenticationResult.SUCCEEDED)
-            assertThat(lockout).isNull()
-            assertThat(utils.authenticationRepository.lockoutStartedReportCount).isEqualTo(0)
+            assertSucceeded(underTest.authenticate("password".toList()))
         }
 
     @Test
     fun authenticate_withIncorrectPassword_fails() =
         testScope.runTest {
-            utils.authenticationRepository.setAuthenticationMethod(
-                AuthenticationMethodModel.Password
-            )
+            utils.authenticationRepository.setAuthenticationMethod(Password)
 
-            assertThat(underTest.authenticate("alohomora".toList()))
-                .isEqualTo(AuthenticationResult.FAILED)
+            assertFailed(underTest.authenticate("alohomora".toList()))
         }
 
     @Test
     fun authenticate_withCorrectPattern_succeeds() =
         testScope.runTest {
-            utils.authenticationRepository.setAuthenticationMethod(
-                AuthenticationMethodModel.Pattern
-            )
+            utils.authenticationRepository.setAuthenticationMethod(Pattern)
 
-            assertThat(underTest.authenticate(FakeAuthenticationRepository.PATTERN))
-                .isEqualTo(AuthenticationResult.SUCCEEDED)
+            assertSucceeded(underTest.authenticate(FakeAuthenticationRepository.PATTERN))
         }
 
     @Test
     fun authenticate_withIncorrectPattern_fails() =
         testScope.runTest {
-            utils.authenticationRepository.setAuthenticationMethod(
-                AuthenticationMethodModel.Pattern
-            )
-
-            assertThat(
-                    underTest.authenticate(
-                        listOf(
-                            AuthenticationPatternCoordinate(x = 2, y = 0),
-                            AuthenticationPatternCoordinate(x = 2, y = 1),
-                            AuthenticationPatternCoordinate(x = 2, y = 2),
-                            AuthenticationPatternCoordinate(x = 1, y = 2),
-                        )
-                    )
+            utils.authenticationRepository.setAuthenticationMethod(Pattern)
+            val wrongPattern =
+                listOf(
+                    AuthenticationPatternCoordinate(x = 2, y = 0),
+                    AuthenticationPatternCoordinate(x = 2, y = 1),
+                    AuthenticationPatternCoordinate(x = 2, y = 2),
+                    AuthenticationPatternCoordinate(x = 1, y = 2),
                 )
-                .isEqualTo(AuthenticationResult.FAILED)
+
+            assertFailed(underTest.authenticate(wrongPattern))
         }
 
     @Test
     fun tryAutoConfirm_withAutoConfirmPinAndShorterPin_returnsNull() =
         testScope.runTest {
             val isAutoConfirmEnabled by collectLastValue(underTest.isAutoConfirmEnabled)
-            val lockout by collectLastValue(underTest.lockout)
             utils.authenticationRepository.apply {
-                setAuthenticationMethod(AuthenticationMethodModel.Pin)
+                setAuthenticationMethod(Pin)
                 setAutoConfirmFeatureEnabled(true)
             }
             assertThat(isAutoConfirmEnabled).isTrue()
+            val shorterPin =
+                FakeAuthenticationRepository.DEFAULT_PIN.toMutableList().apply { removeLast() }
 
-            assertThat(
-                    underTest.authenticate(
-                        FakeAuthenticationRepository.DEFAULT_PIN.toMutableList().apply {
-                            removeLast()
-                        },
-                        tryAutoConfirm = true
-                    )
-                )
-                .isEqualTo(AuthenticationResult.SKIPPED)
-            assertThat(lockout).isNull()
+            assertSkipped(underTest.authenticate(shorterPin, tryAutoConfirm = true))
+            assertThat(underTest.lockoutEndTimestamp).isNull()
             assertThat(utils.authenticationRepository.lockoutStartedReportCount).isEqualTo(0)
         }
 
@@ -212,18 +188,17 @@
         testScope.runTest {
             val isAutoConfirmEnabled by collectLastValue(underTest.isAutoConfirmEnabled)
             utils.authenticationRepository.apply {
-                setAuthenticationMethod(AuthenticationMethodModel.Pin)
+                setAuthenticationMethod(Pin)
                 setAutoConfirmFeatureEnabled(true)
             }
             assertThat(isAutoConfirmEnabled).isTrue()
 
-            assertThat(
-                    underTest.authenticate(
-                        FakeAuthenticationRepository.DEFAULT_PIN.map { it + 1 },
-                        tryAutoConfirm = true
-                    )
-                )
-                .isEqualTo(AuthenticationResult.FAILED)
+            val wrongPin = FakeAuthenticationRepository.DEFAULT_PIN.map { it + 1 }
+
+            assertFailed(
+                underTest.authenticate(wrongPin, tryAutoConfirm = true),
+                assertNoResultEvents = true,
+            )
         }
 
     @Test
@@ -231,18 +206,17 @@
         testScope.runTest {
             val isAutoConfirmEnabled by collectLastValue(underTest.isAutoConfirmEnabled)
             utils.authenticationRepository.apply {
-                setAuthenticationMethod(AuthenticationMethodModel.Pin)
+                setAuthenticationMethod(Pin)
                 setAutoConfirmFeatureEnabled(true)
             }
             assertThat(isAutoConfirmEnabled).isTrue()
 
-            assertThat(
-                    underTest.authenticate(
-                        FakeAuthenticationRepository.DEFAULT_PIN + listOf(7),
-                        tryAutoConfirm = true
-                    )
-                )
-                .isEqualTo(AuthenticationResult.FAILED)
+            val longerPin = FakeAuthenticationRepository.DEFAULT_PIN + listOf(7)
+
+            assertFailed(
+                underTest.authenticate(longerPin, tryAutoConfirm = true),
+                assertNoResultEvents = true,
+            )
         }
 
     @Test
@@ -250,69 +224,54 @@
         testScope.runTest {
             val isAutoConfirmEnabled by collectLastValue(underTest.isAutoConfirmEnabled)
             utils.authenticationRepository.apply {
-                setAuthenticationMethod(AuthenticationMethodModel.Pin)
+                setAuthenticationMethod(Pin)
                 setAutoConfirmFeatureEnabled(true)
             }
             assertThat(isAutoConfirmEnabled).isTrue()
 
-            assertThat(
-                    underTest.authenticate(
-                        FakeAuthenticationRepository.DEFAULT_PIN,
-                        tryAutoConfirm = true
-                    )
-                )
-                .isEqualTo(AuthenticationResult.SUCCEEDED)
+            val correctPin = FakeAuthenticationRepository.DEFAULT_PIN
+
+            assertSucceeded(underTest.authenticate(correctPin, tryAutoConfirm = true))
         }
 
     @Test
     fun tryAutoConfirm_withAutoConfirmCorrectPinButDuringLockout_returnsNull() =
         testScope.runTest {
             val isAutoConfirmEnabled by collectLastValue(underTest.isAutoConfirmEnabled)
-            val isUnlocked by collectLastValue(utils.deviceEntryRepository.isUnlocked)
             val hintedPinLength by collectLastValue(underTest.hintedPinLength)
             utils.authenticationRepository.apply {
-                setAuthenticationMethod(AuthenticationMethodModel.Pin)
+                setAuthenticationMethod(Pin)
                 setAutoConfirmFeatureEnabled(true)
-                setLockoutDuration(42)
+                reportLockoutStarted(42)
             }
 
-            val authResult =
-                underTest.authenticate(
-                    FakeAuthenticationRepository.DEFAULT_PIN,
-                    tryAutoConfirm = true
-                )
+            val correctPin = FakeAuthenticationRepository.DEFAULT_PIN
 
-            assertThat(authResult).isEqualTo(AuthenticationResult.SKIPPED)
+            assertSkipped(underTest.authenticate(correctPin, tryAutoConfirm = true))
             assertThat(isAutoConfirmEnabled).isFalse()
-            assertThat(isUnlocked).isFalse()
             assertThat(hintedPinLength).isNull()
+            assertThat(underTest.lockoutEndTimestamp).isNotNull()
         }
 
     @Test
     fun tryAutoConfirm_withoutAutoConfirmButCorrectPin_returnsNull() =
         testScope.runTest {
             utils.authenticationRepository.apply {
-                setAuthenticationMethod(AuthenticationMethodModel.Pin)
+                setAuthenticationMethod(Pin)
                 setAutoConfirmFeatureEnabled(false)
             }
-            assertThat(
-                    underTest.authenticate(
-                        FakeAuthenticationRepository.DEFAULT_PIN,
-                        tryAutoConfirm = true
-                    )
-                )
-                .isEqualTo(AuthenticationResult.SKIPPED)
+
+            val correctPin = FakeAuthenticationRepository.DEFAULT_PIN
+
+            assertSkipped(underTest.authenticate(correctPin, tryAutoConfirm = true))
         }
 
     @Test
     fun tryAutoConfirm_withoutCorrectPassword_returnsNull() =
         testScope.runTest {
-            utils.authenticationRepository.setAuthenticationMethod(
-                AuthenticationMethodModel.Password
-            )
+            utils.authenticationRepository.setAuthenticationMethod(Password)
 
-            assertThat(underTest.authenticate("password".toList(), tryAutoConfirm = true))
-                .isEqualTo(AuthenticationResult.SKIPPED)
+            assertSkipped(underTest.authenticate("password".toList(), tryAutoConfirm = true))
         }
 
     @Test
@@ -337,7 +296,6 @@
     fun isAutoConfirmEnabled_featureEnabledButDisabledByLockout() =
         testScope.runTest {
             val isAutoConfirmEnabled by collectLastValue(underTest.isAutoConfirmEnabled)
-            val lockout by collectLastValue(underTest.lockout)
             utils.authenticationRepository.setAutoConfirmFeatureEnabled(true)
 
             // The feature is enabled.
@@ -345,92 +303,104 @@
 
             // Make many wrong attempts to trigger lockout.
             repeat(FakeAuthenticationRepository.MAX_FAILED_AUTH_TRIES_BEFORE_LOCKOUT) {
-                underTest.authenticate(listOf(5, 6, 7)) // Wrong PIN
+                assertFailed(underTest.authenticate(listOf(5, 6, 7))) // Wrong PIN
             }
-            assertThat(lockout).isNotNull()
+            assertThat(underTest.lockoutEndTimestamp).isNotNull()
             assertThat(utils.authenticationRepository.lockoutStartedReportCount).isEqualTo(1)
 
             // Lockout disabled auto-confirm.
             assertThat(isAutoConfirmEnabled).isFalse()
 
             // Move the clock forward one more second, to completely finish the lockout period:
-            advanceTimeBy(FakeAuthenticationRepository.LOCKOUT_DURATION_MS + 1000L)
-            assertThat(lockout).isNull()
+            advanceTimeBy(
+                FakeAuthenticationRepository.LOCKOUT_DURATION_SECONDS.seconds.plus(1.seconds)
+            )
+            assertThat(underTest.lockoutEndTimestamp).isNull()
 
             // Auto-confirm is still disabled, because lockout occurred at least once in this
             // session.
             assertThat(isAutoConfirmEnabled).isFalse()
 
             // Correct PIN and unlocks successfully, resetting the 'session'.
-            assertThat(underTest.authenticate(FakeAuthenticationRepository.DEFAULT_PIN))
-                .isEqualTo(AuthenticationResult.SUCCEEDED)
+            assertSucceeded(underTest.authenticate(FakeAuthenticationRepository.DEFAULT_PIN))
 
             // Auto-confirm is re-enabled.
             assertThat(isAutoConfirmEnabled).isTrue()
-
-            assertThat(utils.authenticationRepository.lockoutStartedReportCount).isEqualTo(1)
         }
 
     @Test
-    fun lockout() =
+    fun failedAuthenticationAttempts() =
         testScope.runTest {
-            val lockout by collectLastValue(underTest.lockout)
-            utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
-            underTest.authenticate(FakeAuthenticationRepository.DEFAULT_PIN)
-            assertThat(lockout).isNull()
+            val failedAuthenticationAttempts by
+                collectLastValue(underTest.failedAuthenticationAttempts)
+
+            utils.authenticationRepository.setAuthenticationMethod(Pin)
+            val correctPin = FakeAuthenticationRepository.DEFAULT_PIN
+
+            assertSucceeded(underTest.authenticate(correctPin))
+            assertThat(failedAuthenticationAttempts).isEqualTo(0)
+
+            // Make many wrong attempts, leading to lockout:
+            repeat(FakeAuthenticationRepository.MAX_FAILED_AUTH_TRIES_BEFORE_LOCKOUT) { index ->
+                underTest.authenticate(listOf(5, 6, 7)) // Wrong PIN
+                assertThat(failedAuthenticationAttempts).isEqualTo(index + 1)
+            }
+
+            // Correct PIN, but locked out, so doesn't attempt it:
+            assertSkipped(underTest.authenticate(correctPin), assertNoResultEvents = false)
+            assertThat(failedAuthenticationAttempts)
+                .isEqualTo(FakeAuthenticationRepository.MAX_FAILED_AUTH_TRIES_BEFORE_LOCKOUT)
+
+            // Move the clock forward to finish the lockout period:
+            advanceTimeBy(FakeAuthenticationRepository.LOCKOUT_DURATION_SECONDS.seconds)
+            assertThat(failedAuthenticationAttempts)
+                .isEqualTo(FakeAuthenticationRepository.MAX_FAILED_AUTH_TRIES_BEFORE_LOCKOUT)
+
+            // Correct PIN and no longer locked out so unlocks successfully:
+            assertSucceeded(underTest.authenticate(correctPin))
+            assertThat(failedAuthenticationAttempts).isEqualTo(0)
+        }
+
+    @Test
+    fun lockoutEndTimestamp() =
+        testScope.runTest {
+            utils.authenticationRepository.setAuthenticationMethod(Pin)
+            val correctPin = FakeAuthenticationRepository.DEFAULT_PIN
+
+            underTest.authenticate(correctPin)
+            assertThat(underTest.lockoutEndTimestamp).isNull()
 
             // Make many wrong attempts, but just shy of what's needed to get locked out:
             repeat(FakeAuthenticationRepository.MAX_FAILED_AUTH_TRIES_BEFORE_LOCKOUT - 1) {
                 underTest.authenticate(listOf(5, 6, 7)) // Wrong PIN
-                assertThat(lockout).isNull()
+                assertThat(underTest.lockoutEndTimestamp).isNull()
             }
 
             // Make one more wrong attempt, leading to lockout:
             underTest.authenticate(listOf(5, 6, 7)) // Wrong PIN
-            assertThat(lockout)
-                .isEqualTo(
-                    AuthenticationLockoutModel(
-                        failedAttemptCount =
-                            FakeAuthenticationRepository.MAX_FAILED_AUTH_TRIES_BEFORE_LOCKOUT,
-                        remainingSeconds = FakeAuthenticationRepository.LOCKOUT_DURATION_SECONDS,
-                    )
-                )
+
+            val expectedLockoutEndTimestamp =
+                testScope.currentTime + FakeAuthenticationRepository.LOCKOUT_DURATION_MS
+            assertThat(underTest.lockoutEndTimestamp).isEqualTo(expectedLockoutEndTimestamp)
             assertThat(utils.authenticationRepository.lockoutStartedReportCount).isEqualTo(1)
 
             // Correct PIN, but locked out, so doesn't attempt it:
-            assertThat(underTest.authenticate(FakeAuthenticationRepository.DEFAULT_PIN))
-                .isEqualTo(AuthenticationResult.SKIPPED)
-            assertThat(lockout)
-                .isEqualTo(
-                    AuthenticationLockoutModel(
-                        failedAttemptCount =
-                            FakeAuthenticationRepository.MAX_FAILED_AUTH_TRIES_BEFORE_LOCKOUT,
-                        remainingSeconds = FakeAuthenticationRepository.LOCKOUT_DURATION_SECONDS,
-                    )
-                )
+            assertSkipped(underTest.authenticate(correctPin), assertNoResultEvents = false)
+            assertThat(underTest.lockoutEndTimestamp).isEqualTo(expectedLockoutEndTimestamp)
 
             // Move the clock forward to ALMOST skip the lockout, leaving one second to go:
-            val lockoutTimeoutSec = FakeAuthenticationRepository.LOCKOUT_DURATION_SECONDS
-            repeat(FakeAuthenticationRepository.LOCKOUT_DURATION_SECONDS - 1) { time ->
-                advanceTimeBy(1000)
-                assertThat(lockout)
-                    .isEqualTo(
-                        AuthenticationLockoutModel(
-                            failedAttemptCount =
-                                FakeAuthenticationRepository.MAX_FAILED_AUTH_TRIES_BEFORE_LOCKOUT,
-                            remainingSeconds = lockoutTimeoutSec - (time + 1),
-                        )
-                    )
+            repeat(FakeAuthenticationRepository.LOCKOUT_DURATION_SECONDS - 1) {
+                advanceTimeBy(1.seconds)
+                assertThat(underTest.lockoutEndTimestamp).isEqualTo(expectedLockoutEndTimestamp)
             }
 
             // Move the clock forward one more second, to completely finish the lockout period:
-            advanceTimeBy(1000)
-            assertThat(lockout).isNull()
+            advanceTimeBy(1.seconds)
+            assertThat(underTest.lockoutEndTimestamp).isNull()
 
             // Correct PIN and no longer locked out so unlocks successfully:
-            assertThat(underTest.authenticate(FakeAuthenticationRepository.DEFAULT_PIN))
-                .isEqualTo(AuthenticationResult.SUCCEEDED)
-            assertThat(lockout).isNull()
+            assertSucceeded(underTest.authenticate(correctPin))
+            assertThat(underTest.lockoutEndTimestamp).isNull()
         }
 
     @Test
@@ -438,7 +408,7 @@
         testScope.runTest {
             val hintedPinLength by collectLastValue(underTest.hintedPinLength)
             utils.authenticationRepository.apply {
-                setAuthenticationMethod(AuthenticationMethodModel.Pin)
+                setAuthenticationMethod(Pin)
                 setAutoConfirmFeatureEnabled(false)
             }
 
@@ -450,7 +420,7 @@
         testScope.runTest {
             val hintedPinLength by collectLastValue(underTest.hintedPinLength)
             utils.authenticationRepository.apply {
-                setAuthenticationMethod(AuthenticationMethodModel.Pin)
+                setAuthenticationMethod(Pin)
                 overrideCredential(
                     buildList {
                         repeat(utils.authenticationRepository.hintedPinLength - 1) { add(it + 1) }
@@ -467,7 +437,7 @@
         testScope.runTest {
             val hintedPinLength by collectLastValue(underTest.hintedPinLength)
             utils.authenticationRepository.apply {
-                setAuthenticationMethod(AuthenticationMethodModel.Pin)
+                setAuthenticationMethod(Pin)
                 setAutoConfirmFeatureEnabled(true)
                 overrideCredential(
                     buildList {
@@ -484,7 +454,7 @@
         testScope.runTest {
             val hintedPinLength by collectLastValue(underTest.hintedPinLength)
             utils.authenticationRepository.apply {
-                setAuthenticationMethod(AuthenticationMethodModel.Pin)
+                setAuthenticationMethod(Pin)
                 overrideCredential(
                     buildList {
                         repeat(utils.authenticationRepository.hintedPinLength + 1) { add(it + 1) }
@@ -499,18 +469,45 @@
     @Test
     fun authenticate_withTooShortPassword() =
         testScope.runTest {
-            utils.authenticationRepository.setAuthenticationMethod(
-                AuthenticationMethodModel.Password
-            )
-            assertThat(
-                    underTest.authenticate(
-                        buildList {
-                            repeat(utils.authenticationRepository.minPasswordLength - 1) { time ->
-                                add("$time")
-                            }
-                        }
-                    )
-                )
-                .isEqualTo(AuthenticationResult.SKIPPED)
+            utils.authenticationRepository.setAuthenticationMethod(Password)
+
+            val tooShortPassword = buildList {
+                repeat(utils.authenticationRepository.minPasswordLength - 1) { time ->
+                    add("$time")
+                }
+            }
+            assertSkipped(underTest.authenticate(tooShortPassword))
         }
+
+    private fun assertSucceeded(authenticationResult: AuthenticationResult) {
+        assertThat(authenticationResult).isEqualTo(AuthenticationResult.SUCCEEDED)
+        assertThat(onAuthenticationResult).isTrue()
+        assertThat(underTest.lockoutEndTimestamp).isNull()
+        assertThat(utils.authenticationRepository.lockoutStartedReportCount).isEqualTo(0)
+        assertThat(failedAuthenticationAttempts).isEqualTo(0)
+    }
+
+    private fun assertFailed(
+        authenticationResult: AuthenticationResult,
+        assertNoResultEvents: Boolean = false,
+    ) {
+        assertThat(authenticationResult).isEqualTo(AuthenticationResult.FAILED)
+        if (assertNoResultEvents) {
+            assertThat(onAuthenticationResult).isNull()
+        } else {
+            assertThat(onAuthenticationResult).isFalse()
+        }
+    }
+
+    private fun assertSkipped(
+        authenticationResult: AuthenticationResult,
+        assertNoResultEvents: Boolean = true,
+    ) {
+        assertThat(authenticationResult).isEqualTo(AuthenticationResult.SKIPPED)
+        if (assertNoResultEvents) {
+            assertThat(onAuthenticationResult).isNull()
+        } else {
+            assertThat(onAuthenticationResult).isNotNull()
+        }
+    }
 }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractorTest.kt
index 9b1df7c..99c1874 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractorTest.kt
@@ -21,15 +21,15 @@
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.authentication.data.repository.FakeAuthenticationRepository
 import com.android.systemui.authentication.domain.interactor.AuthenticationResult
-import com.android.systemui.authentication.shared.model.AuthenticationLockoutModel
 import com.android.systemui.authentication.shared.model.AuthenticationMethodModel
 import com.android.systemui.authentication.shared.model.AuthenticationPatternCoordinate
 import com.android.systemui.coroutines.collectLastValue
+import com.android.systemui.coroutines.collectValues
 import com.android.systemui.keyguard.domain.interactor.KeyguardFaceAuthInteractor
 import com.android.systemui.res.R
 import com.android.systemui.scene.SceneTestUtils
 import com.google.common.truth.Truth.assertThat
-import kotlin.time.Duration.Companion.milliseconds
+import kotlin.time.Duration.Companion.seconds
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.test.advanceTimeBy
 import kotlinx.coroutines.test.runCurrent
@@ -79,7 +79,7 @@
             utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
             runCurrent()
             underTest.clearMessage()
-            assertThat(message).isEmpty()
+            assertThat(message).isNull()
 
             underTest.resetMessage()
             assertThat(message).isEqualTo(MESSAGE_ENTER_YOUR_PIN)
@@ -149,7 +149,7 @@
             // Incomplete input.
             assertThat(underTest.authenticate(listOf(1, 2), tryAutoConfirm = true))
                 .isEqualTo(AuthenticationResult.SKIPPED)
-            assertThat(message).isEmpty()
+            assertThat(message).isNull()
 
             // Correct input.
             assertThat(
@@ -159,7 +159,7 @@
                     )
                 )
                 .isEqualTo(AuthenticationResult.SKIPPED)
-            assertThat(message).isEmpty()
+            assertThat(message).isNull()
         }
 
     @Test
@@ -246,57 +246,40 @@
         }
 
     @Test
-    fun lockout() =
+    fun lockoutStarted() =
         testScope.runTest {
-            val lockout by collectLastValue(underTest.lockout)
+            val lockoutStartedEvents by collectValues(underTest.onLockoutStarted)
             val message by collectLastValue(underTest.message)
+
             utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
-            assertThat(lockout).isNull()
+            assertThat(lockoutStartedEvents).isEmpty()
+
+            // Try the wrong PIN repeatedly, until lockout is triggered:
             repeat(FakeAuthenticationRepository.MAX_FAILED_AUTH_TRIES_BEFORE_LOCKOUT) { times ->
                 // Wrong PIN.
                 assertThat(underTest.authenticate(listOf(6, 7, 8, 9)))
                     .isEqualTo(AuthenticationResult.FAILED)
                 if (times < FakeAuthenticationRepository.MAX_FAILED_AUTH_TRIES_BEFORE_LOCKOUT - 1) {
-                    assertThat(message).isEqualTo(MESSAGE_WRONG_PIN)
+                    assertThat(lockoutStartedEvents).isEmpty()
+                    assertThat(message).isNotEmpty()
                 }
             }
-            assertThat(lockout)
-                .isEqualTo(
-                    AuthenticationLockoutModel(
-                        failedAttemptCount =
-                            FakeAuthenticationRepository.MAX_FAILED_AUTH_TRIES_BEFORE_LOCKOUT,
-                        remainingSeconds = FakeAuthenticationRepository.LOCKOUT_DURATION_SECONDS,
-                    )
-                )
-            assertTryAgainMessage(
-                message,
-                FakeAuthenticationRepository.LOCKOUT_DURATION_MS.milliseconds.inWholeSeconds.toInt()
-            )
+            assertThat(authenticationInteractor.lockoutEndTimestamp).isNotNull()
+            assertThat(lockoutStartedEvents.size).isEqualTo(1)
+            assertThat(message).isNull()
 
-            // Correct PIN, but locked out, so doesn't change away from the bouncer scene:
-            assertThat(underTest.authenticate(FakeAuthenticationRepository.DEFAULT_PIN))
-                .isEqualTo(AuthenticationResult.SKIPPED)
-            assertTryAgainMessage(
-                message,
-                FakeAuthenticationRepository.LOCKOUT_DURATION_MS.milliseconds.inWholeSeconds.toInt()
-            )
+            // Advance the time to finish the lockout:
+            advanceTimeBy(FakeAuthenticationRepository.LOCKOUT_DURATION_SECONDS.seconds)
+            assertThat(authenticationInteractor.lockoutEndTimestamp).isNull()
+            assertThat(message).isNull()
+            assertThat(lockoutStartedEvents.size).isEqualTo(1)
 
-            lockout?.remainingSeconds?.let { seconds ->
-                repeat(seconds) { time ->
-                    advanceTimeBy(1000)
-                    val remainingTimeSec = seconds - time - 1
-                    if (remainingTimeSec > 0) {
-                        assertTryAgainMessage(message, remainingTimeSec)
-                    }
-                }
+            // Trigger lockout again:
+            repeat(FakeAuthenticationRepository.MAX_FAILED_AUTH_TRIES_BEFORE_LOCKOUT) {
+                // Wrong PIN.
+                underTest.authenticate(listOf(6, 7, 8, 9))
             }
-            assertThat(message).isEqualTo("")
-            assertThat(lockout).isNull()
-
-            // Correct PIN and no longer locked out so changes to the Gone scene:
-            assertThat(underTest.authenticate(FakeAuthenticationRepository.DEFAULT_PIN))
-                .isEqualTo(AuthenticationResult.SUCCEEDED)
-            assertThat(lockout).isNull()
+            assertThat(lockoutStartedEvents.size).isEqualTo(2)
         }
 
     @Test
@@ -326,13 +309,6 @@
             verify(keyguardFaceAuthInteractor).onPrimaryBouncerUserInput()
         }
 
-    private fun assertTryAgainMessage(
-        message: String?,
-        time: Int,
-    ) {
-        assertThat(message).isEqualTo("Try again in $time seconds.")
-    }
-
     companion object {
         private const val MESSAGE_ENTER_YOUR_PIN = "Enter your PIN"
         private const val MESSAGE_ENTER_YOUR_PASSWORD = "Enter your password"
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModelTest.kt
index 16a9359..4be9b0a 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModelTest.kt
@@ -33,6 +33,7 @@
 import kotlinx.coroutines.flow.launchIn
 import kotlinx.coroutines.flow.onEach
 import kotlinx.coroutines.test.advanceTimeBy
+import kotlinx.coroutines.test.currentTime
 import kotlinx.coroutines.test.runCurrent
 import kotlinx.coroutines.test.runTest
 import org.junit.Test
@@ -135,19 +136,47 @@
     fun message() =
         testScope.runTest {
             val message by collectLastValue(underTest.message)
-            val lockout by collectLastValue(bouncerInteractor.lockout)
             utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
             assertThat(message?.isUpdateAnimated).isTrue()
 
             repeat(FakeAuthenticationRepository.MAX_FAILED_AUTH_TRIES_BEFORE_LOCKOUT) {
-                // Wrong PIN.
-                bouncerInteractor.authenticate(listOf(3, 4, 5, 6))
+                bouncerInteractor.authenticate(WRONG_PIN)
             }
             assertThat(message?.isUpdateAnimated).isFalse()
 
-            lockout?.remainingSeconds?.let { remainingSeconds ->
-                advanceTimeBy(remainingSeconds.seconds.inWholeMilliseconds)
+            val lockoutEndMs = authenticationInteractor.lockoutEndTimestamp ?: 0
+            advanceTimeBy(lockoutEndMs - testScope.currentTime)
+            assertThat(message?.isUpdateAnimated).isTrue()
+        }
+
+    @Test
+    fun lockoutMessage() =
+        testScope.runTest {
+            val authMethodViewModel by collectLastValue(underTest.authMethodViewModel)
+            val message by collectLastValue(underTest.message)
+            utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
+            assertThat(utils.authenticationRepository.lockoutEndTimestamp).isNull()
+            assertThat(authMethodViewModel?.lockoutMessageId).isNotNull()
+
+            repeat(FakeAuthenticationRepository.MAX_FAILED_AUTH_TRIES_BEFORE_LOCKOUT) { times ->
+                bouncerInteractor.authenticate(WRONG_PIN)
+                if (times < FakeAuthenticationRepository.MAX_FAILED_AUTH_TRIES_BEFORE_LOCKOUT - 1) {
+                    assertThat(message?.text).isEqualTo(bouncerInteractor.message.value)
+                    assertThat(message?.isUpdateAnimated).isTrue()
+                }
             }
+            val lockoutSeconds = FakeAuthenticationRepository.LOCKOUT_DURATION_SECONDS
+            assertTryAgainMessage(message?.text, lockoutSeconds)
+            assertThat(message?.isUpdateAnimated).isFalse()
+
+            repeat(FakeAuthenticationRepository.LOCKOUT_DURATION_SECONDS) { time ->
+                advanceTimeBy(1.seconds)
+                val remainingSeconds = lockoutSeconds - time - 1
+                if (remainingSeconds > 0) {
+                    assertTryAgainMessage(message?.text, remainingSeconds)
+                }
+            }
+            assertThat(message?.text).isEmpty()
             assertThat(message?.isUpdateAnimated).isTrue()
         }
 
@@ -160,32 +189,30 @@
                         authViewModel?.isInputEnabled ?: emptyFlow()
                     }
                 )
-            val lockout by collectLastValue(bouncerInteractor.lockout)
             utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
             assertThat(isInputEnabled).isTrue()
 
             repeat(FakeAuthenticationRepository.MAX_FAILED_AUTH_TRIES_BEFORE_LOCKOUT) {
-                // Wrong PIN.
-                bouncerInteractor.authenticate(listOf(3, 4, 5, 6))
+                bouncerInteractor.authenticate(WRONG_PIN)
             }
             assertThat(isInputEnabled).isFalse()
 
-            lockout?.remainingSeconds?.let { remainingSeconds ->
-                advanceTimeBy(remainingSeconds.seconds.inWholeMilliseconds)
-            }
+            val lockoutEndMs = authenticationInteractor.lockoutEndTimestamp ?: 0
+            advanceTimeBy(lockoutEndMs - testScope.currentTime)
             assertThat(isInputEnabled).isTrue()
         }
 
     @Test
     fun dialogMessage() =
         testScope.runTest {
+            val authMethodViewModel by collectLastValue(underTest.authMethodViewModel)
             val dialogMessage by collectLastValue(underTest.dialogMessage)
             utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
+            assertThat(authMethodViewModel?.lockoutMessageId).isNotNull()
 
             repeat(FakeAuthenticationRepository.MAX_FAILED_AUTH_TRIES_BEFORE_LOCKOUT) {
-                // Wrong PIN.
                 assertThat(dialogMessage).isNull()
-                bouncerInteractor.authenticate(listOf(3, 4, 5, 6))
+                bouncerInteractor.authenticate(WRONG_PIN)
             }
             assertThat(dialogMessage).isNotEmpty()
 
@@ -241,4 +268,15 @@
             AuthenticationMethodModel.Sim,
         )
     }
+
+    private fun assertTryAgainMessage(
+        message: String?,
+        time: Int,
+    ) {
+        assertThat(message).isEqualTo("Try again in $time seconds.")
+    }
+
+    companion object {
+        private val WRONG_PIN = FakeAuthenticationRepository.DEFAULT_PIN.map { it + 1 }
+    }
 }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModelTest.kt
index 6d6baa5..64e6e57 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModelTest.kt
@@ -19,7 +19,6 @@
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
-import com.android.systemui.authentication.shared.model.AuthenticationLockoutModel
 import com.android.systemui.authentication.shared.model.AuthenticationMethodModel
 import com.android.systemui.coroutines.collectLastValue
 import com.android.systemui.coroutines.collectValues
@@ -28,6 +27,7 @@
 import com.android.systemui.scene.shared.model.SceneKey
 import com.android.systemui.scene.shared.model.SceneModel
 import com.google.common.truth.Truth.assertThat
+import kotlin.time.Duration.Companion.seconds
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.flow.MutableStateFlow
 import kotlinx.coroutines.flow.asStateFlow
@@ -45,11 +45,7 @@
 
     private val utils = SceneTestUtils(this)
     private val testScope = utils.testScope
-    private val authenticationRepository = utils.authenticationRepository
-    private val authenticationInteractor =
-        utils.authenticationInteractor(
-            repository = authenticationRepository,
-        )
+    private val authenticationInteractor = utils.authenticationInteractor()
     private val sceneInteractor = utils.sceneInteractor()
     private val bouncerInteractor =
         utils.bouncerInteractor(
@@ -61,12 +57,13 @@
             authenticationInteractor = authenticationInteractor,
             actionButtonInteractor = utils.bouncerActionButtonInteractor(),
         )
+    private val isInputEnabled = MutableStateFlow(true)
 
     private val underTest =
         PasswordBouncerViewModel(
             viewModelScope = testScope.backgroundScope,
             interactor = bouncerInteractor,
-            isInputEnabled = MutableStateFlow(true).asStateFlow(),
+            isInputEnabled.asStateFlow(),
         )
 
     @Before
@@ -123,8 +120,7 @@
     @Test
     fun onAuthenticateKeyPressed_whenCorrect() =
         testScope.runTest {
-            val authResult by
-                collectLastValue(authenticationInteractor.authenticationChallengeResult)
+            val authResult by collectLastValue(authenticationInteractor.onAuthenticationResult)
             lockDeviceAndOpenPasswordBouncer()
 
             underTest.onPasswordInputChanged("password")
@@ -169,8 +165,7 @@
     @Test
     fun onAuthenticateKeyPressed_correctAfterWrong() =
         testScope.runTest {
-            val authResult by
-                collectLastValue(authenticationInteractor.authenticationChallengeResult)
+            val authResult by collectLastValue(authenticationInteractor.onAuthenticationResult)
             val message by collectLastValue(bouncerViewModel.message)
             val password by collectLastValue(underTest.password)
             lockDeviceAndOpenPasswordBouncer()
@@ -333,19 +328,15 @@
     ) {
         if (isLockedOut) {
             repeat(failedAttemptCount) {
-                authenticationRepository.reportAuthenticationAttempt(false)
+                utils.authenticationRepository.reportAuthenticationAttempt(false)
             }
-            val remainingTimeSeconds = 30
-            authenticationRepository.setLockoutDuration(remainingTimeSeconds * 1000)
-            authenticationRepository.lockout.value =
-                AuthenticationLockoutModel(
-                    failedAttemptCount = failedAttemptCount,
-                    remainingSeconds = remainingTimeSeconds,
-                )
+            utils.authenticationRepository.reportLockoutStarted(
+                30.seconds.inWholeMilliseconds.toInt()
+            )
         } else {
-            authenticationRepository.reportAuthenticationAttempt(true)
-            authenticationRepository.lockout.value = null
+            utils.authenticationRepository.reportAuthenticationAttempt(true)
         }
+        isInputEnabled.value = !isLockedOut
 
         runCurrent()
     }
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/PatternBouncerViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/PatternBouncerViewModelTest.kt
index 8971423..ed76099 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/PatternBouncerViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/PatternBouncerViewModelTest.kt
@@ -111,8 +111,7 @@
     @Test
     fun onDragEnd_whenCorrect() =
         testScope.runTest {
-            val authResult by
-                collectLastValue(authenticationInteractor.authenticationChallengeResult)
+            val authResult by collectLastValue(authenticationInteractor.onAuthenticationResult)
             val selectedDots by collectLastValue(underTest.selectedDots)
             val currentDot by collectLastValue(underTest.currentDot)
             lockDeviceAndOpenPatternBouncer()
@@ -334,8 +333,7 @@
     @Test
     fun onDragEnd_correctAfterWrong() =
         testScope.runTest {
-            val authResult by
-                collectLastValue(authenticationInteractor.authenticationChallengeResult)
+            val authResult by collectLastValue(authenticationInteractor.onAuthenticationResult)
             val message by collectLastValue(bouncerViewModel.message)
             val selectedDots by collectLastValue(underTest.selectedDots)
             val currentDot by collectLastValue(underTest.currentDot)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModelTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModelTest.kt
index c30e405..db98d76 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModelTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/bouncer/ui/viewmodel/PinBouncerViewModelTest.kt
@@ -201,8 +201,7 @@
     @Test
     fun onAuthenticateButtonClicked_whenCorrect() =
         testScope.runTest {
-            val authResult by
-                collectLastValue(authenticationInteractor.authenticationChallengeResult)
+            val authResult by collectLastValue(authenticationInteractor.onAuthenticationResult)
             lockDeviceAndOpenPinBouncer()
 
             FakeAuthenticationRepository.DEFAULT_PIN.forEach(underTest::onPinButtonClicked)
@@ -236,8 +235,7 @@
     @Test
     fun onAuthenticateButtonClicked_correctAfterWrong() =
         testScope.runTest {
-            val authResult by
-                collectLastValue(authenticationInteractor.authenticationChallengeResult)
+            val authResult by collectLastValue(authenticationInteractor.onAuthenticationResult)
             val message by collectLastValue(bouncerViewModel.message)
             val pin by collectLastValue(underTest.pinInput.map { it.getPin() })
             lockDeviceAndOpenPinBouncer()
@@ -265,8 +263,7 @@
     fun onAutoConfirm_whenCorrect() =
         testScope.runTest {
             utils.authenticationRepository.setAutoConfirmFeatureEnabled(true)
-            val authResult by
-                collectLastValue(authenticationInteractor.authenticationChallengeResult)
+            val authResult by collectLastValue(authenticationInteractor.onAuthenticationResult)
             lockDeviceAndOpenPinBouncer()
 
             FakeAuthenticationRepository.DEFAULT_PIN.forEach(underTest::onPinButtonClicked)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/data/repository/DeviceEntryRepositoryTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/data/repository/DeviceEntryRepositoryTest.kt
index d3049d9..565049b 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/data/repository/DeviceEntryRepositoryTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/data/repository/DeviceEntryRepositoryTest.kt
@@ -99,7 +99,7 @@
         }
 
     @Test
-    fun reportSuccessfulAuthentication_shouldUpdateIsUnlocked() =
+    fun reportSuccessfulAuthentication_updatesIsUnlocked() =
         testScope.runTest {
             val isUnlocked by collectLastValue(underTest.isUnlocked)
             assertThat(isUnlocked).isFalse()
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractorTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractorTest.kt
index 910097e..ea19cb7 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractorTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractorTest.kt
@@ -19,6 +19,7 @@
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
+import com.android.systemui.authentication.data.repository.FakeAuthenticationRepository
 import com.android.systemui.authentication.shared.model.AuthenticationMethodModel
 import com.android.systemui.coroutines.collectLastValue
 import com.android.systemui.coroutines.collectValues
@@ -346,12 +347,14 @@
         }
 
     @Test
-    fun successfulAuthenticationChallengeAttempt_updatedIsUnlockedState() =
+    fun successfulAuthenticationChallengeAttempt_updatesIsUnlockedState() =
         testScope.runTest {
             val isUnlocked by collectLastValue(underTest.isUnlocked)
+            utils.authenticationRepository.setAuthenticationMethod(AuthenticationMethodModel.Pin)
+            utils.deviceEntryRepository.setLockscreenEnabled(true)
             assertThat(isUnlocked).isFalse()
 
-            utils.authenticationRepository.reportAuthenticationAttempt(true)
+            authenticationInteractor.authenticate(FakeAuthenticationRepository.DEFAULT_PIN)
 
             assertThat(isUnlocked).isTrue()
         }
diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/clocks/ClockProviderPlugin.kt b/packages/SystemUI/plugin/src/com/android/systemui/plugins/clocks/ClockProviderPlugin.kt
index 1c5f221..4436be7 100644
--- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/clocks/ClockProviderPlugin.kt
+++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/clocks/ClockProviderPlugin.kt
@@ -41,16 +41,13 @@
 
 /** Interface for building clocks and providing information about those clocks */
 interface ClockProvider {
+    /** Initializes the clock provider with debug log buffers */
+    fun initialize(buffers: ClockMessageBuffers?)
+
     /** Returns metadata for all clocks this provider knows about */
     fun getClocks(): List<ClockMetadata>
 
     /** Initializes and returns the target clock design */
-    @Deprecated("Use overload with ClockSettings")
-    fun createClock(id: ClockId): ClockController {
-        return createClock(ClockSettings(id, null))
-    }
-
-    /** Initializes and returns the target clock design */
     fun createClock(settings: ClockSettings): ClockController
 
     /** A static thumbnail for rendering in some examples */
@@ -98,11 +95,20 @@
 
     /** Triggers for various animations */
     val animations: ClockAnimations
-
-    /** Some clocks may log debug information */
-    var messageBuffer: MessageBuffer?
 }
 
+/** For clocks that want to report debug information */
+data class ClockMessageBuffers(
+    /** Message buffer for general infra */
+    val infraMessageBuffer: MessageBuffer,
+
+    /** Message buffer for small clock renering */
+    val smallClockMessageBuffer: MessageBuffer,
+
+    /** Message buffer for large clock rendering */
+    val largeClockMessageBuffer: MessageBuffer,
+)
+
 /** Specifies layout information for the */
 interface ClockFaceLayout {
     /** All clock views to add to the root constraint layout before applying constraints. */
diff --git a/packages/SystemUI/shared/src/com/android/systemui/shared/system/QuickStepContract.java b/packages/SystemUI/shared/src/com/android/systemui/shared/system/QuickStepContract.java
index d8c1e41..131eb6b 100644
--- a/packages/SystemUI/shared/src/com/android/systemui/shared/system/QuickStepContract.java
+++ b/packages/SystemUI/shared/src/com/android/systemui/shared/system/QuickStepContract.java
@@ -367,7 +367,7 @@
     /**
      * Corner radius that should be used on windows in order to cover the display.
      * These values are expressed in pixels because they should not respect display or font
-     * scaling, this means that we don't have to reload them on config changes.
+     * scaling. The corner radius may change when folding/unfolding the device.
      */
     public static float getWindowCornerRadius(Context context) {
         return ScreenDecorationsUtils.getWindowCornerRadius(context);
diff --git a/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt b/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt
index 76abad8..bcc2044 100644
--- a/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt
+++ b/packages/SystemUI/src/com/android/keyguard/ClockEventController.kt
@@ -45,12 +45,11 @@
 import com.android.systemui.keyguard.shared.KeyguardShadeMigrationNssl
 import com.android.systemui.keyguard.shared.model.TransitionState
 import com.android.systemui.lifecycle.repeatWhenAttached
-import com.android.systemui.log.LogBuffer
+import com.android.systemui.log.core.Logger
 import com.android.systemui.log.core.LogLevel.DEBUG
-import com.android.systemui.log.dagger.KeyguardLargeClockLog
-import com.android.systemui.log.dagger.KeyguardSmallClockLog
 import com.android.systemui.plugins.clocks.ClockController
 import com.android.systemui.plugins.clocks.ClockFaceController
+import com.android.systemui.plugins.clocks.ClockMessageBuffers
 import com.android.systemui.plugins.clocks.ClockTickRate
 import com.android.systemui.plugins.clocks.AlarmData
 import com.android.systemui.plugins.clocks.WeatherData
@@ -91,117 +90,120 @@
     private val context: Context,
     @Main private val mainExecutor: DelayableExecutor,
     @Background private val bgExecutor: Executor,
-    @KeyguardSmallClockLog private val smallLogBuffer: LogBuffer?,
-    @KeyguardLargeClockLog private val largeLogBuffer: LogBuffer?,
+    private val clockBuffers: ClockMessageBuffers,
     private val featureFlags: FeatureFlags,
     private val zenModeController: ZenModeController,
 ) {
+    var loggers = listOf(
+        clockBuffers.infraMessageBuffer,
+        clockBuffers.smallClockMessageBuffer,
+        clockBuffers.largeClockMessageBuffer
+    ).map { Logger(it, TAG) }
+
     var clock: ClockController? = null
+        get() = field
         set(value) {
-            smallClockOnAttachStateChangeListener?.let {
-                field?.smallClock?.view?.removeOnAttachStateChangeListener(it)
+            disconnectClock(field)
+            field = value
+            connectClock(value)
+        }
+
+    private fun disconnectClock(clock: ClockController?) {
+        if (clock == null) { return; }
+        smallClockOnAttachStateChangeListener?.let {
+            clock.smallClock.view.removeOnAttachStateChangeListener(it)
+            smallClockFrame?.viewTreeObserver
+                    ?.removeOnGlobalLayoutListener(onGlobalLayoutListener)
+        }
+        largeClockOnAttachStateChangeListener?.let {
+            clock.largeClock.view.removeOnAttachStateChangeListener(it)
+        }
+    }
+
+    private fun connectClock(clock: ClockController?) {
+        if (clock == null) { return; }
+        val clockStr = clock.toString()
+        loggers.forEach { it.d({ "New Clock: $str1" }) { str1 = clockStr } }
+
+        clock.initialize(resources, dozeAmount, 0f)
+
+        if (!regionSamplingEnabled) {
+            updateColors()
+        } else {
+            smallRegionSampler = createRegionSampler(
+                clock.smallClock.view,
+                mainExecutor,
+                bgExecutor,
+                regionSamplingEnabled,
+                isLockscreen = true,
+                ::updateColors
+            ).apply { startRegionSampler() }
+
+            largeRegionSampler = createRegionSampler(
+                clock.largeClock.view,
+                mainExecutor,
+                bgExecutor,
+                regionSamplingEnabled,
+                isLockscreen = true,
+                ::updateColors
+            ).apply { startRegionSampler() }
+
+            updateColors()
+        }
+        updateFontSizes()
+        updateTimeListeners()
+
+        weatherData?.let {
+            if (WeatherData.DEBUG) {
+                Log.i(TAG, "Pushing cached weather data to new clock: $it")
+            }
+            clock.events.onWeatherDataChanged(it)
+        }
+        zenData?.let {
+            clock.events.onZenDataChanged(it)
+        }
+        alarmData?.let {
+            clock.events.onAlarmDataChanged(it)
+        }
+
+        smallClockOnAttachStateChangeListener = object : OnAttachStateChangeListener {
+            var pastVisibility: Int? = null
+            override fun onViewAttachedToWindow(view: View) {
+                clock.events.onTimeFormatChanged(DateFormat.is24HourFormat(context))
+                // Match the asing for view.parent's layout classes.
+                smallClockFrame = (view.parent as ViewGroup)?.also { frame ->
+                    pastVisibility = frame.visibility
+                    onGlobalLayoutListener = OnGlobalLayoutListener {
+                        val currentVisibility = frame.visibility
+                        if (pastVisibility != currentVisibility) {
+                            pastVisibility = currentVisibility
+                            // when small clock is visible,
+                            // recalculate bounds and sample
+                            if (currentVisibility == View.VISIBLE) {
+                                smallRegionSampler?.stopRegionSampler()
+                                smallRegionSampler?.startRegionSampler()
+                            }
+                        }
+                    }
+                    frame.viewTreeObserver.addOnGlobalLayoutListener(onGlobalLayoutListener)
+                }
+            }
+
+            override fun onViewDetachedFromWindow(p0: View) {
                 smallClockFrame?.viewTreeObserver
                         ?.removeOnGlobalLayoutListener(onGlobalLayoutListener)
             }
-            largeClockOnAttachStateChangeListener?.let {
-                field?.largeClock?.view?.removeOnAttachStateChangeListener(it)
-            }
-
-            field = value
-            if (value != null) {
-                smallLogBuffer?.log(TAG, DEBUG, {}, { "New Clock" })
-                value.smallClock.messageBuffer = smallLogBuffer
-                largeLogBuffer?.log(TAG, DEBUG, {}, { "New Clock" })
-                value.largeClock.messageBuffer = largeLogBuffer
-
-                value.initialize(resources, dozeAmount, 0f)
-
-                if (!regionSamplingEnabled) {
-                    updateColors()
-                } else {
-                    clock?.let {
-                        smallRegionSampler = createRegionSampler(
-                                it.smallClock.view,
-                                mainExecutor,
-                                bgExecutor,
-                                regionSamplingEnabled,
-                                isLockscreen = true,
-                                ::updateColors
-                        )?.apply { startRegionSampler() }
-
-                        largeRegionSampler = createRegionSampler(
-                                it.largeClock.view,
-                                mainExecutor,
-                                bgExecutor,
-                                regionSamplingEnabled,
-                                isLockscreen = true,
-                                ::updateColors
-                        )?.apply { startRegionSampler() }
-
-                        updateColors()
-                    }
-                }
-                updateFontSizes()
-                updateTimeListeners()
-                weatherData?.let {
-                    if (WeatherData.DEBUG) {
-                        Log.i(TAG, "Pushing cached weather data to new clock: $it")
-                    }
-                    value.events.onWeatherDataChanged(it)
-                }
-                zenData?.let {
-                    value.events.onZenDataChanged(it)
-                }
-                alarmData?.let {
-                    value.events.onAlarmDataChanged(it)
-                }
-
-                smallClockOnAttachStateChangeListener =
-                    object : OnAttachStateChangeListener {
-                        var pastVisibility: Int? = null
-                        override fun onViewAttachedToWindow(view: View) {
-                            value.events.onTimeFormatChanged(DateFormat.is24HourFormat(context))
-                            // Match the asing for view.parent's layout classes.
-                            smallClockFrame = view.parent as ViewGroup
-                            smallClockFrame?.let { frame ->
-                                pastVisibility = frame.visibility
-                                onGlobalLayoutListener = OnGlobalLayoutListener {
-                                    val currentVisibility = frame.visibility
-                                    if (pastVisibility != currentVisibility) {
-                                        pastVisibility = currentVisibility
-                                        // when small clock is visible,
-                                        // recalculate bounds and sample
-                                        if (currentVisibility == View.VISIBLE) {
-                                            smallRegionSampler?.stopRegionSampler()
-                                            smallRegionSampler?.startRegionSampler()
-                                        }
-                                    }
-                                }
-                                frame.viewTreeObserver
-                                        .addOnGlobalLayoutListener(onGlobalLayoutListener)
-                            }
-                        }
-
-                        override fun onViewDetachedFromWindow(p0: View) {
-                            smallClockFrame?.viewTreeObserver
-                                    ?.removeOnGlobalLayoutListener(onGlobalLayoutListener)
-                        }
-                }
-                value.smallClock.view
-                        .addOnAttachStateChangeListener(smallClockOnAttachStateChangeListener)
-
-                largeClockOnAttachStateChangeListener =
-                    object : OnAttachStateChangeListener {
-                        override fun onViewAttachedToWindow(p0: View) {
-                            value.events.onTimeFormatChanged(DateFormat.is24HourFormat(context))
-                        }
-                        override fun onViewDetachedFromWindow(p0: View) {
-                        }
-                }
-                value.largeClock.view
-                        .addOnAttachStateChangeListener(largeClockOnAttachStateChangeListener)
-            }
         }
+        clock.smallClock.view.addOnAttachStateChangeListener(smallClockOnAttachStateChangeListener)
+
+        largeClockOnAttachStateChangeListener = object : OnAttachStateChangeListener {
+            override fun onViewAttachedToWindow(p0: View) {
+                clock.events.onTimeFormatChanged(DateFormat.is24HourFormat(context))
+            }
+            override fun onViewDetachedFromWindow(p0: View) {}
+        }
+        clock.largeClock.view.addOnAttachStateChangeListener(largeClockOnAttachStateChangeListener)
+    }
 
     @VisibleForTesting
     var smallClockOnAttachStateChangeListener: OnAttachStateChangeListener? = null
@@ -247,6 +249,7 @@
             largeClock.events.onRegionDarknessChanged(isRegionDark)
         }
     }
+
     protected open fun createRegionSampler(
         sampledView: View,
         mainExecutor: Executor?,
@@ -254,7 +257,7 @@
         regionSamplingEnabled: Boolean,
         isLockscreen: Boolean,
         updateColors: () -> Unit
-    ): RegionSampler? {
+    ): RegionSampler {
         return RegionSampler(
             sampledView,
             mainExecutor,
diff --git a/packages/SystemUI/src/com/android/keyguard/dagger/ClockRegistryModule.java b/packages/SystemUI/src/com/android/keyguard/dagger/ClockRegistryModule.java
index 661ce2c..878a5d8 100644
--- a/packages/SystemUI/src/com/android/keyguard/dagger/ClockRegistryModule.java
+++ b/packages/SystemUI/src/com/android/keyguard/dagger/ClockRegistryModule.java
@@ -28,9 +28,8 @@
 import com.android.systemui.dagger.qualifiers.Main;
 import com.android.systemui.flags.FeatureFlags;
 import com.android.systemui.flags.Flags;
-import com.android.systemui.log.LogBuffer;
-import com.android.systemui.log.dagger.KeyguardClockLog;
 import com.android.systemui.plugins.PluginManager;
+import com.android.systemui.plugins.clocks.ClockMessageBuffers;
 import com.android.systemui.res.R;
 import com.android.systemui.shared.clocks.ClockRegistry;
 import com.android.systemui.shared.clocks.DefaultClockProvider;
@@ -56,7 +55,7 @@
             FeatureFlags featureFlags,
             @Main Resources resources,
             LayoutInflater layoutInflater,
-            @KeyguardClockLog LogBuffer logBuffer) {
+            ClockMessageBuffers clockBuffers) {
         ClockRegistry registry = new ClockRegistry(
                 context,
                 pluginManager,
@@ -72,7 +71,7 @@
                         featureFlags.isEnabled(Flags.STEP_CLOCK_ANIMATION),
                         migrateClocksToBlueprint()),
                 context.getString(R.string.lockscreen_clock_id_fallback),
-                logBuffer,
+                clockBuffers,
                 /* keepAllLoaded = */ false,
                 /* subTag = */ "System",
                 /* isTransitClockEnabled = */ featureFlags.isEnabled(Flags.TRANSIT_CLOCK));
diff --git a/packages/SystemUI/src/com/android/systemui/authentication/data/repository/AuthenticationRepository.kt b/packages/SystemUI/src/com/android/systemui/authentication/data/repository/AuthenticationRepository.kt
index fda23b7f..f2b55f4 100644
--- a/packages/SystemUI/src/com/android/systemui/authentication/data/repository/AuthenticationRepository.kt
+++ b/packages/SystemUI/src/com/android/systemui/authentication/data/repository/AuthenticationRepository.kt
@@ -24,13 +24,13 @@
 import com.android.internal.widget.LockPatternUtils
 import com.android.internal.widget.LockscreenCredential
 import com.android.keyguard.KeyguardSecurityModel
-import com.android.systemui.authentication.shared.model.AuthenticationLockoutModel
 import com.android.systemui.authentication.shared.model.AuthenticationMethodModel
 import com.android.systemui.authentication.shared.model.AuthenticationResultModel
 import com.android.systemui.broadcast.BroadcastDispatcher
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Application
 import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.scene.shared.flag.SceneContainerFlags
 import com.android.systemui.statusbar.pipeline.mobile.data.repository.MobileConnectionsRepository
 import com.android.systemui.user.data.repository.UserRepository
 import com.android.systemui.util.kotlin.pairwise
@@ -43,9 +43,7 @@
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.MutableSharedFlow
 import kotlinx.coroutines.flow.MutableStateFlow
-import kotlinx.coroutines.flow.SharedFlow
 import kotlinx.coroutines.flow.StateFlow
 import kotlinx.coroutines.flow.asStateFlow
 import kotlinx.coroutines.flow.combine
@@ -60,12 +58,6 @@
 /** Defines interface for classes that can access authentication-related application state. */
 interface AuthenticationRepository {
     /**
-     * Emits the result whenever a PIN/Pattern/Password security challenge is attempted by the user
-     * in order to unlock the device.
-     */
-    val authenticationChallengeResult: SharedFlow<Boolean>
-
-    /**
      * The exact length a PIN should be for us to enable PIN length hinting.
      *
      * A PIN that's shorter or longer than this is not eligible for the UI to render hints showing
@@ -80,16 +72,6 @@
     val isPatternVisible: StateFlow<Boolean>
 
     /**
-     * The current authentication lockout (aka "throttling") state, set when the user has to wait
-     * before being able to try another authentication attempt. `null` indicates throttling isn't
-     * active.
-     */
-    val lockout: MutableStateFlow<AuthenticationLockoutModel?>
-
-    /** Whether throttling has occurred at least once since the last successful authentication. */
-    val hasLockoutOccurred: MutableStateFlow<Boolean>
-
-    /**
      * Whether the auto confirm feature is enabled for the currently-selected user.
      *
      * Note that the length of the PIN is also important to take into consideration, please see
@@ -98,6 +80,28 @@
     val isAutoConfirmFeatureEnabled: StateFlow<Boolean>
 
     /**
+     * The number of failed authentication attempts for the selected user since their last
+     * successful authentication.
+     */
+    val failedAuthenticationAttempts: StateFlow<Int>
+
+    /**
+     * Timestamp for when the current lockout (aka "throttling") will end, allowing the user to
+     * attempt authentication again. Returns `null` if no lockout is active.
+     *
+     * Note that the value is in milliseconds and matches [SystemClock.elapsedRealtime].
+     *
+     * Also note that the value may change when the selected user is changed.
+     */
+    val lockoutEndTimestamp: Long?
+
+    /**
+     * Whether lockout has occurred at least once since the last successful authentication of any
+     * user.
+     */
+    val hasLockoutOccurred: StateFlow<Boolean>
+
+    /**
      * The currently-configured authentication method. This determines how the authentication
      * challenge needs to be completed in order to unlock an otherwise locked device.
      *
@@ -142,23 +146,6 @@
     /** Reports that the user has entered a temporary device lockout (throttling). */
     suspend fun reportLockoutStarted(durationMs: Int)
 
-    /** Returns the current number of failed authentication attempts. */
-    suspend fun getFailedAuthenticationAttemptCount(): Int
-
-    /**
-     * Returns the timestamp for when the current lockout will end, allowing the user to attempt
-     * authentication again.
-     *
-     * Note that this is in milliseconds and it matches [SystemClock.elapsedRealtime].
-     */
-    suspend fun getLockoutEndTimestamp(): Long
-
-    /**
-     * Sets the lockout timeout duration (time during which the user should not be allowed to
-     * attempt authentication).
-     */
-    suspend fun setLockoutDuration(durationMs: Int)
-
     /**
      * Checks the given [LockscreenCredential] to see if it's correct, returning an
      * [AuthenticationResultModel] representing what happened.
@@ -172,6 +159,8 @@
 constructor(
     @Application private val applicationScope: CoroutineScope,
     @Background private val backgroundDispatcher: CoroutineDispatcher,
+    flags: SceneContainerFlags,
+    private val clock: SystemClock,
     private val getSecurityMode: Function<Int, KeyguardSecurityModel.SecurityMode>,
     private val userRepository: UserRepository,
     private val lockPatternUtils: LockPatternUtils,
@@ -179,8 +168,6 @@
     mobileConnectionsRepository: MobileConnectionsRepository,
 ) : AuthenticationRepository {
 
-    override val authenticationChallengeResult = MutableSharedFlow<Boolean>()
-
     override val hintedPinLength: Int = 6
 
     override val isPatternVisible: StateFlow<Boolean> =
@@ -189,10 +176,6 @@
             getFreshValue = lockPatternUtils::isVisiblePatternEnabled,
         )
 
-    override val lockout: MutableStateFlow<AuthenticationLockoutModel?> = MutableStateFlow(null)
-
-    override val hasLockoutOccurred: MutableStateFlow<Boolean> = MutableStateFlow(false)
-
     override val isAutoConfirmFeatureEnabled: StateFlow<Boolean> =
         refreshingFlow(
             initialValue = false,
@@ -234,6 +217,31 @@
             getFreshValue = { userId -> lockPatternUtils.isPinEnhancedPrivacyEnabled(userId) },
         )
 
+    private val _failedAuthenticationAttempts = MutableStateFlow(0)
+    override val failedAuthenticationAttempts: StateFlow<Int> =
+        _failedAuthenticationAttempts.asStateFlow()
+
+    override val lockoutEndTimestamp: Long?
+        get() =
+            lockPatternUtils.getLockoutAttemptDeadline(selectedUserId).takeIf {
+                clock.elapsedRealtime() < it
+            }
+
+    private val _hasLockoutOccurred = MutableStateFlow(false)
+    override val hasLockoutOccurred: StateFlow<Boolean> = _hasLockoutOccurred.asStateFlow()
+
+    init {
+        if (flags.isEnabled()) {
+            // Hydrate failedAuthenticationAttempts initially and whenever the selected user
+            // changes.
+            applicationScope.launch {
+                userRepository.selectedUserInfo.collect {
+                    _failedAuthenticationAttempts.value = getFailedAuthenticationAttemptCount()
+                }
+            }
+        }
+    }
+
     override suspend fun getAuthenticationMethod(): AuthenticationMethodModel {
         return withContext(backgroundDispatcher) {
             blockingAuthenticationMethodInternal(selectedUserId)
@@ -248,35 +256,20 @@
         withContext(backgroundDispatcher) {
             if (isSuccessful) {
                 lockPatternUtils.reportSuccessfulPasswordAttempt(selectedUserId)
+                _hasLockoutOccurred.value = false
             } else {
                 lockPatternUtils.reportFailedPasswordAttempt(selectedUserId)
             }
-            authenticationChallengeResult.emit(isSuccessful)
+            _failedAuthenticationAttempts.value = getFailedAuthenticationAttemptCount()
         }
     }
 
     override suspend fun reportLockoutStarted(durationMs: Int) {
-        return withContext(backgroundDispatcher) {
+        lockPatternUtils.setLockoutAttemptDeadline(selectedUserId, durationMs)
+        withContext(backgroundDispatcher) {
             lockPatternUtils.reportPasswordLockout(durationMs, selectedUserId)
         }
-    }
-
-    override suspend fun getFailedAuthenticationAttemptCount(): Int {
-        return withContext(backgroundDispatcher) {
-            lockPatternUtils.getCurrentFailedPasswordAttempts(selectedUserId)
-        }
-    }
-
-    override suspend fun getLockoutEndTimestamp(): Long {
-        return withContext(backgroundDispatcher) {
-            lockPatternUtils.getLockoutAttemptDeadline(selectedUserId)
-        }
-    }
-
-    override suspend fun setLockoutDuration(durationMs: Int) {
-        withContext(backgroundDispatcher) {
-            lockPatternUtils.setLockoutAttemptDeadline(selectedUserId, durationMs)
-        }
+        _hasLockoutOccurred.value = true
     }
 
     override suspend fun checkCredential(
@@ -292,6 +285,12 @@
         }
     }
 
+    private suspend fun getFailedAuthenticationAttemptCount(): Int {
+        return withContext(backgroundDispatcher) {
+            lockPatternUtils.getCurrentFailedPasswordAttempts(selectedUserId)
+        }
+    }
+
     private val selectedUserId: Int
         get() = userRepository.getSelectedUserInfo().id
 
diff --git a/packages/SystemUI/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractor.kt b/packages/SystemUI/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractor.kt
index 797154e..c85ffe6 100644
--- a/packages/SystemUI/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractor.kt
@@ -16,36 +16,25 @@
 
 package com.android.systemui.authentication.domain.interactor
 
-import com.android.app.tracing.TraceUtils.Companion.withContext
 import com.android.internal.widget.LockPatternView
 import com.android.internal.widget.LockscreenCredential
 import com.android.systemui.authentication.data.repository.AuthenticationRepository
-import com.android.systemui.authentication.shared.model.AuthenticationLockoutModel
 import com.android.systemui.authentication.shared.model.AuthenticationMethodModel
 import com.android.systemui.authentication.shared.model.AuthenticationPatternCoordinate
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.dagger.qualifiers.Application
-import com.android.systemui.dagger.qualifiers.Background
-import com.android.systemui.user.data.repository.UserRepository
 import com.android.systemui.util.time.SystemClock
 import javax.inject.Inject
-import kotlin.math.ceil
-import kotlin.math.max
-import kotlin.time.Duration.Companion.seconds
-import kotlinx.coroutines.CoroutineDispatcher
 import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.Job
-import kotlinx.coroutines.async
-import kotlinx.coroutines.delay
 import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.MutableSharedFlow
 import kotlinx.coroutines.flow.SharedFlow
 import kotlinx.coroutines.flow.SharingStarted
 import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.asSharedFlow
 import kotlinx.coroutines.flow.combine
-import kotlinx.coroutines.flow.distinctUntilChanged
 import kotlinx.coroutines.flow.map
 import kotlinx.coroutines.flow.stateIn
-import kotlinx.coroutines.launch
 
 /**
  * Hosts application business logic related to user authentication.
@@ -59,10 +48,7 @@
 @Inject
 constructor(
     @Application private val applicationScope: CoroutineScope,
-    @Background private val backgroundDispatcher: CoroutineDispatcher,
     private val repository: AuthenticationRepository,
-    private val userRepository: UserRepository,
-    private val clock: SystemClock,
 ) {
     /**
      * The currently-configured authentication method. This determines how the authentication
@@ -85,13 +71,6 @@
     val authenticationMethod: Flow<AuthenticationMethodModel> = repository.authenticationMethod
 
     /**
-     * The current authentication lockout (aka "throttling") state, set when the user has to wait
-     * before being able to try another authentication attempt. `null` indicates lockout isn't
-     * active.
-     */
-    val lockout: StateFlow<AuthenticationLockoutModel?> = repository.lockout
-
-    /**
      * Whether the auto confirm feature is enabled for the currently-selected user.
      *
      * Note that the length of the PIN is also important to take into consideration, please see
@@ -130,26 +109,35 @@
     /** Whether the pattern should be visible for the currently-selected user. */
     val isPatternVisible: StateFlow<Boolean> = repository.isPatternVisible
 
+    private val _onAuthenticationResult = MutableSharedFlow<Boolean>()
     /**
      * Emits the outcome (successful or unsuccessful) whenever a PIN/Pattern/Password security
      * challenge is attempted by the user in order to unlock the device.
      */
-    val authenticationChallengeResult: SharedFlow<Boolean> =
-        repository.authenticationChallengeResult
+    val onAuthenticationResult: SharedFlow<Boolean> = _onAuthenticationResult.asSharedFlow()
 
     /** Whether the "enhanced PIN privacy" setting is enabled for the current user. */
     val isPinEnhancedPrivacyEnabled: StateFlow<Boolean> = repository.isPinEnhancedPrivacyEnabled
 
-    private var lockoutCountdownJob: Job? = null
+    /**
+     * The number of failed authentication attempts for the selected user since the last successful
+     * authentication.
+     */
+    val failedAuthenticationAttempts: StateFlow<Int> = repository.failedAuthenticationAttempts
 
-    init {
-        applicationScope.launch {
-            userRepository.selectedUserInfo
-                .map { it.id }
-                .distinctUntilChanged()
-                .collect { onSelectedUserChanged() }
-        }
-    }
+    /**
+     * Timestamp for when the current lockout (aka "throttling") will end, allowing the user to
+     * attempt authentication again. Returns `null` if no lockout is active.
+     *
+     * To be notified whenever a lockout is started, the caller should subscribe to
+     * [onAuthenticationResult].
+     *
+     * Note that the value is in milliseconds and matches [SystemClock.elapsedRealtime].
+     *
+     * Also note that the value may change when the selected user is changed.
+     */
+    val lockoutEndTimestamp: Long?
+        get() = repository.lockoutEndTimestamp
 
     /**
      * Returns the currently-configured authentication method. This determines how the
@@ -190,7 +178,7 @@
         val skipCheck =
             when {
                 // Lockout is active, the UI layer should not have called this; skip the attempt.
-                lockout.value != null -> true
+                repository.lockoutEndTimestamp != null -> true
                 // The input is too short; skip the attempt.
                 input.isTooShort(authMethod) -> true
                 // Auto-confirm attempt when the feature is not enabled; skip the attempt.
@@ -211,27 +199,16 @@
         credential.zeroize()
 
         if (authenticationResult.isSuccessful || !tryAutoConfirm) {
-            repository.reportAuthenticationAttempt(
-                isSuccessful = authenticationResult.isSuccessful,
-            )
+            repository.reportAuthenticationAttempt(authenticationResult.isSuccessful)
         }
 
         // Check if lockout should start and, if so, kick off the countdown:
         if (!authenticationResult.isSuccessful && authenticationResult.lockoutDurationMs > 0) {
-            repository.apply {
-                setLockoutDuration(durationMs = authenticationResult.lockoutDurationMs)
-                reportLockoutStarted(durationMs = authenticationResult.lockoutDurationMs)
-                hasLockoutOccurred.value = true
-            }
-            startLockoutCountdown()
+            repository.reportLockoutStarted(authenticationResult.lockoutDurationMs)
         }
 
-        if (authenticationResult.isSuccessful) {
-            // Since authentication succeeded, refresh lockout to make sure the state is completely
-            // reflecting the upstream source of truth.
-            refreshLockout()
-
-            repository.hasLockoutOccurred.value = false
+        if (authenticationResult.isSuccessful || !tryAutoConfirm) {
+            _onAuthenticationResult.emit(authenticationResult.isSuccessful)
         }
 
         return if (authenticationResult.isSuccessful) {
@@ -249,54 +226,6 @@
         }
     }
 
-    /** Starts refreshing the lockout state every second. */
-    private suspend fun startLockoutCountdown() {
-        cancelLockoutCountdown()
-        lockoutCountdownJob =
-            applicationScope.launch {
-                while (refreshLockout()) {
-                    delay(1.seconds.inWholeMilliseconds)
-                }
-            }
-    }
-
-    /** Cancels any lockout state countdown started in [startLockoutCountdown]. */
-    private fun cancelLockoutCountdown() {
-        lockoutCountdownJob?.cancel()
-        lockoutCountdownJob = null
-    }
-
-    /** Notifies that the currently-selected user has changed. */
-    private suspend fun onSelectedUserChanged() {
-        cancelLockoutCountdown()
-        if (refreshLockout()) {
-            startLockoutCountdown()
-        }
-    }
-
-    /**
-     * Refreshes the lockout state, hydrating the repository with the latest state.
-     *
-     * @return Whether lockout is active or not.
-     */
-    private suspend fun refreshLockout(): Boolean {
-        withContext("$TAG#refreshLockout", backgroundDispatcher) {
-            val failedAttemptCount = async { repository.getFailedAuthenticationAttemptCount() }
-            val deadline = async { repository.getLockoutEndTimestamp() }
-            val remainingMs = max(0, deadline.await() - clock.elapsedRealtime())
-            repository.lockout.value =
-                if (remainingMs > 0) {
-                    AuthenticationLockoutModel(
-                        failedAttemptCount = failedAttemptCount.await(),
-                        remainingSeconds = ceil(remainingMs / 1000f).toInt(),
-                    )
-                } else {
-                    null // Lockout ended.
-                }
-        }
-        return repository.lockout.value != null
-    }
-
     private fun AuthenticationMethodModel.createCredential(
         input: List<Any>
     ): LockscreenCredential? {
diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractor.kt b/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractor.kt
index 724c0fe..1095abe 100644
--- a/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/bouncer/domain/interactor/BouncerInteractor.kt
@@ -19,7 +19,6 @@
 import android.content.Context
 import com.android.systemui.authentication.domain.interactor.AuthenticationInteractor
 import com.android.systemui.authentication.domain.interactor.AuthenticationResult
-import com.android.systemui.authentication.shared.model.AuthenticationLockoutModel
 import com.android.systemui.authentication.shared.model.AuthenticationMethodModel
 import com.android.systemui.bouncer.data.repository.BouncerRepository
 import com.android.systemui.classifier.FalsingClassifier
@@ -29,17 +28,15 @@
 import com.android.systemui.keyguard.domain.interactor.KeyguardFaceAuthInteractor
 import com.android.systemui.power.domain.interactor.PowerInteractor
 import com.android.systemui.res.R
-import com.android.systemui.scene.shared.flag.SceneContainerFlags
-import com.android.systemui.util.kotlin.pairwise
 import javax.inject.Inject
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.async
+import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.MutableSharedFlow
 import kotlinx.coroutines.flow.SharedFlow
-import kotlinx.coroutines.flow.SharingStarted
 import kotlinx.coroutines.flow.StateFlow
-import kotlinx.coroutines.flow.combine
-import kotlinx.coroutines.flow.stateIn
+import kotlinx.coroutines.flow.filter
+import kotlinx.coroutines.flow.map
 import kotlinx.coroutines.launch
 
 /** Encapsulates business logic and application state accessing use-cases. */
@@ -52,33 +49,12 @@
     private val repository: BouncerRepository,
     private val authenticationInteractor: AuthenticationInteractor,
     private val keyguardFaceAuthInteractor: KeyguardFaceAuthInteractor,
-    flags: SceneContainerFlags,
     private val falsingInteractor: FalsingInteractor,
     private val powerInteractor: PowerInteractor,
     private val simBouncerInteractor: SimBouncerInteractor,
 ) {
-
-    /** The user-facing message to show in the bouncer. */
-    val message: StateFlow<String?> =
-        combine(repository.message, authenticationInteractor.lockout) { message, lockout ->
-                messageOrLockoutMessage(message, lockout)
-            }
-            .stateIn(
-                scope = applicationScope,
-                started = SharingStarted.WhileSubscribed(),
-                initialValue =
-                    messageOrLockoutMessage(
-                        repository.message.value,
-                        authenticationInteractor.lockout.value,
-                    )
-            )
-
-    /**
-     * The current authentication lockout (aka "throttling") state, set when the user has to wait
-     * before being able to try another authentication attempt. `null` indicates lockout isn't
-     * active.
-     */
-    val lockout: StateFlow<AuthenticationLockoutModel?> = authenticationInteractor.lockout
+    /** The user-facing message to show in the bouncer when lockout is not active. */
+    val message: StateFlow<String?> = repository.message
 
     /** Whether the auto confirm feature is enabled for the currently-selected user. */
     val isAutoConfirmEnabled: StateFlow<Boolean> = authenticationInteractor.isAutoConfirmEnabled
@@ -101,18 +77,13 @@
     /** Emits a [Unit] each time the IME (keyboard) is hidden by the user. */
     val onImeHiddenByUser: SharedFlow<Unit> = _onImeHiddenByUser
 
-    init {
-        if (flags.isEnabled()) {
-            // Clear the message if moved from locked-out to no-longer locked-out.
-            applicationScope.launch {
-                lockout.pairwise().collect { (previous, current) ->
-                    if (previous != null && current == null) {
-                        clearMessage()
-                    }
-                }
+    /** Emits a [Unit] each time a lockout is started for the selected user. */
+    val onLockoutStarted: Flow<Unit> =
+        authenticationInteractor.onAuthenticationResult
+            .filter { successfullyAuthenticated ->
+                !successfullyAuthenticated && authenticationInteractor.lockoutEndTimestamp != null
             }
-        }
-    }
+            .map {}
 
     /** Notifies that the user has places down a pointer, not necessarily dragging just yet. */
     fun onDown() {
@@ -188,7 +159,7 @@
         }
 
         if (authenticationInteractor.getAuthenticationMethod() == AuthenticationMethodModel.Sim) {
-            // We authenticate sim in SimInteractor
+            // SIM is authenticated in SimBouncerInteractor.
             return AuthenticationResult.SKIPPED
         }
 
@@ -196,18 +167,20 @@
         // view-models, whose lifecycle (and thus scope) is shorter than this interactor.
         // This allows the task to continue running properly even when the calling scope has been
         // cancelled.
-        return applicationScope
-            .async {
-                val authResult = authenticationInteractor.authenticate(input, tryAutoConfirm)
-                if (
-                    authResult == AuthenticationResult.FAILED ||
-                        (authResult == AuthenticationResult.SKIPPED && !tryAutoConfirm)
-                ) {
-                    showErrorMessage()
-                }
-                authResult
-            }
-            .await()
+        val authResult =
+            applicationScope
+                .async { authenticationInteractor.authenticate(input, tryAutoConfirm) }
+                .await()
+
+        if (authenticationInteractor.lockoutEndTimestamp != null) {
+            clearMessage()
+        } else if (
+            authResult == AuthenticationResult.FAILED ||
+                (authResult == AuthenticationResult.SKIPPED && !tryAutoConfirm)
+        ) {
+            showErrorMessage()
+        }
+        return authResult
     }
 
     /**
@@ -250,19 +223,4 @@
             else -> ""
         }
     }
-
-    private fun messageOrLockoutMessage(
-        message: String?,
-        lockoutModel: AuthenticationLockoutModel?,
-    ): String {
-        return when {
-            lockoutModel != null ->
-                applicationContext.getString(
-                    com.android.internal.R.string.lockscreen_too_many_failed_attempts_countdown,
-                    lockoutModel.remainingSeconds,
-                )
-            message != null -> message
-            else -> ""
-        }
-    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModel.kt b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModel.kt
index 4b14343..be6cf85 100644
--- a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/BouncerViewModel.kt
@@ -19,6 +19,7 @@
 import android.content.Context
 import android.graphics.Bitmap
 import androidx.core.graphics.drawable.toBitmap
+import com.android.internal.R
 import com.android.systemui.authentication.domain.interactor.AuthenticationInteractor
 import com.android.systemui.authentication.shared.model.AuthenticationMethodModel
 import com.android.systemui.bouncer.domain.interactor.BouncerActionButtonInteractor
@@ -34,19 +35,24 @@
 import com.android.systemui.user.ui.viewmodel.UserActionViewModel
 import com.android.systemui.user.ui.viewmodel.UserSwitcherViewModel
 import com.android.systemui.user.ui.viewmodel.UserViewModel
+import com.android.systemui.util.time.SystemClock
 import dagger.Module
 import dagger.Provides
+import kotlin.math.ceil
+import kotlin.math.max
+import kotlin.time.Duration.Companion.seconds
 import kotlinx.coroutines.CoroutineDispatcher
 import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Job
 import kotlinx.coroutines.SupervisorJob
 import kotlinx.coroutines.cancel
+import kotlinx.coroutines.delay
 import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.MutableStateFlow
 import kotlinx.coroutines.flow.SharingStarted
 import kotlinx.coroutines.flow.StateFlow
 import kotlinx.coroutines.flow.asStateFlow
 import kotlinx.coroutines.flow.combine
-import kotlinx.coroutines.flow.distinctUntilChanged
 import kotlinx.coroutines.flow.map
 import kotlinx.coroutines.flow.stateIn
 import kotlinx.coroutines.job
@@ -58,13 +64,14 @@
     @Application private val applicationScope: CoroutineScope,
     @Main private val mainDispatcher: CoroutineDispatcher,
     private val bouncerInteractor: BouncerInteractor,
-    authenticationInteractor: AuthenticationInteractor,
+    private val authenticationInteractor: AuthenticationInteractor,
     flags: SceneContainerFlags,
     selectedUser: Flow<UserViewModel>,
     users: Flow<List<UserViewModel>>,
     userSwitcherMenu: Flow<List<UserActionViewModel>>,
     actionButtonInteractor: BouncerActionButtonInteractor,
     private val simBouncerInteractor: SimBouncerInteractor,
+    private val clock: SystemClock,
 ) {
     val selectedUserImage: StateFlow<Bitmap?> =
         selectedUser
@@ -104,15 +111,6 @@
     val isUserSwitcherVisible: Boolean
         get() = bouncerInteractor.isUserSwitcherVisible
 
-    private val isInputEnabled: StateFlow<Boolean> =
-        bouncerInteractor.lockout
-            .map { it == null }
-            .stateIn(
-                scope = applicationScope,
-                started = SharingStarted.WhileSubscribed(),
-                initialValue = bouncerInteractor.lockout.value == null,
-            )
-
     // Handle to the scope of the child ViewModel (stored in [authMethod]).
     private var childViewModelScope: CoroutineScope? = null
     private val _dialogMessage = MutableStateFlow<String?>(null)
@@ -138,19 +136,22 @@
      */
     val dialogMessage: StateFlow<String?> = _dialogMessage.asStateFlow()
 
+    /**
+     * A message shown when the user has attempted the wrong credential too many times and now must
+     * wait a while before attempting to authenticate again.
+     *
+     * This is updated every second (countdown) during the lockout duration. When lockout is not
+     * active, this is `null` and no lockout message should be shown.
+     */
+    private val lockoutMessage = MutableStateFlow<String?>(null)
+
     /** The user-facing message to show in the bouncer. */
     val message: StateFlow<MessageViewModel> =
-        combine(bouncerInteractor.message, bouncerInteractor.lockout) { message, lockout ->
-                toMessageViewModel(message, isLockedOut = lockout != null)
-            }
+        combine(bouncerInteractor.message, lockoutMessage) { _, _ -> createMessageViewModel() }
             .stateIn(
                 scope = applicationScope,
                 started = SharingStarted.WhileSubscribed(),
-                initialValue =
-                    toMessageViewModel(
-                        message = bouncerInteractor.message.value,
-                        isLockedOut = bouncerInteractor.lockout.value != null,
-                    ),
+                initialValue = createMessageViewModel(),
             )
 
     /**
@@ -194,24 +195,29 @@
                 initialValue = isFoldSplitRequired(authMethodViewModel.value),
             )
 
+    private val isInputEnabled: StateFlow<Boolean> =
+        lockoutMessage
+            .map { it == null }
+            .stateIn(
+                scope = applicationScope,
+                started = SharingStarted.WhileSubscribed(),
+                initialValue = authenticationInteractor.lockoutEndTimestamp == null,
+            )
+
+    private var lockoutCountdownJob: Job? = null
+
     init {
         if (flags.isEnabled()) {
             applicationScope.launch {
-                combine(bouncerInteractor.lockout, authMethodViewModel) {
-                        lockout,
-                        authMethodViewModel ->
-                        if (lockout != null && authMethodViewModel != null) {
-                            applicationContext.getString(
-                                authMethodViewModel.lockoutMessageId,
-                                lockout.failedAttemptCount,
-                                lockout.remainingSeconds,
-                            )
-                        } else {
-                            null
-                        }
-                    }
-                    .distinctUntilChanged()
-                    .collect { dialogMessage -> _dialogMessage.value = dialogMessage }
+                bouncerInteractor.onLockoutStarted.collect {
+                    showLockoutDialog()
+                    startLockoutCountdown()
+                }
+            }
+
+            applicationScope.launch {
+                // Update the lockout countdown whenever the selected user is switched.
+                selectedUser.collect { startLockoutCountdown() }
             }
         }
     }
@@ -221,6 +227,48 @@
         _dialogMessage.value = null
     }
 
+    private fun showLockoutDialog() {
+        applicationScope.launch {
+            val failedAttempts = authenticationInteractor.failedAuthenticationAttempts.value
+            _dialogMessage.value =
+                authMethodViewModel.value?.lockoutMessageId?.let { messageId ->
+                    applicationContext.getString(
+                        messageId,
+                        failedAttempts,
+                        remainingLockoutSeconds()
+                    )
+                }
+        }
+    }
+
+    /** Shows the countdown message and refreshes it every second. */
+    private fun startLockoutCountdown() {
+        lockoutCountdownJob?.cancel()
+        lockoutCountdownJob =
+            applicationScope.launch {
+                do {
+                    val remainingSeconds = remainingLockoutSeconds()
+                    lockoutMessage.value =
+                        if (remainingSeconds > 0) {
+                            applicationContext.getString(
+                                R.string.lockscreen_too_many_failed_attempts_countdown,
+                                remainingSeconds,
+                            )
+                        } else {
+                            null
+                        }
+                    delay(1.seconds)
+                } while (remainingSeconds > 0)
+                lockoutCountdownJob = null
+            }
+    }
+
+    private fun remainingLockoutSeconds(): Int {
+        val endTimestampMs = authenticationInteractor.lockoutEndTimestamp ?: 0
+        val remainingMs = max(0, endTimestampMs - clock.elapsedRealtime())
+        return ceil(remainingMs / 1000f).toInt()
+    }
+
     private fun isSideBySideSupported(authMethod: AuthMethodBouncerViewModel?): Boolean {
         return isUserSwitcherVisible || authMethod !is PasswordBouncerViewModel
     }
@@ -229,12 +277,11 @@
         return authMethod !is PasswordBouncerViewModel
     }
 
-    private fun toMessageViewModel(
-        message: String?,
-        isLockedOut: Boolean,
-    ): MessageViewModel {
+    private fun createMessageViewModel(): MessageViewModel {
+        val isLockedOut = lockoutMessage.value != null
         return MessageViewModel(
-            text = message ?: "",
+            // A lockout message takes precedence over the non-lockout message.
+            text = lockoutMessage.value ?: bouncerInteractor.message.value ?: "",
             isUpdateAnimated = !isLockedOut,
         )
     }
@@ -328,6 +375,7 @@
         userSwitcherViewModel: UserSwitcherViewModel,
         actionButtonInteractor: BouncerActionButtonInteractor,
         simBouncerInteractor: SimBouncerInteractor,
+        clock: SystemClock,
     ): BouncerViewModel {
         return BouncerViewModel(
             applicationContext = applicationContext,
@@ -341,6 +389,7 @@
             userSwitcherMenu = userSwitcherViewModel.menu,
             actionButtonInteractor = actionButtonInteractor,
             simBouncerInteractor = simBouncerInteractor,
+            clock = clock,
         )
     }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModel.kt b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModel.kt
index b682717..8e14778 100644
--- a/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/bouncer/ui/viewmodel/PasswordBouncerViewModel.kt
@@ -56,13 +56,11 @@
 
     /** Whether the UI should request focus on the text field element. */
     val isTextFieldFocusRequested =
-        combine(interactor.lockout, isTextFieldFocused) { throttling, hasFocus ->
-                throttling == null && !hasFocus
-            }
+        combine(isInputEnabled, isTextFieldFocused) { hasInput, hasFocus -> hasInput && !hasFocus }
             .stateIn(
                 scope = viewModelScope,
                 started = SharingStarted.WhileSubscribed(),
-                initialValue = interactor.lockout.value == null && !isTextFieldFocused.value,
+                initialValue = isInputEnabled.value && !isTextFieldFocused.value,
             )
 
     override fun onHidden() {
@@ -104,7 +102,7 @@
      * hidden.
      */
     suspend fun onImeVisibilityChanged(isVisible: Boolean) {
-        if (isImeVisible && !isVisible && interactor.lockout.value == null) {
+        if (isImeVisible && !isVisible && isInputEnabled.value) {
             interactor.onImeHiddenByUser()
         }
 
diff --git a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractor.kt b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractor.kt
index 47be8ab..f6a9570 100644
--- a/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/deviceentry/domain/interactor/DeviceEntryInteractor.kt
@@ -159,7 +159,7 @@
     fun attemptDeviceEntry() {
         // TODO (b/307768356),
         //       1. Check if the device is already authenticated by trust agent/passive biometrics
-        //       2. show SPFS/UDFPS bouncer if it is available AlternateBouncerInteractor.show
+        //       2. Show SPFS/UDFPS bouncer if it is available AlternateBouncerInteractor.show
         //       3. For face auth only setups trigger face auth, delay transitioning to bouncer for
         //          a small amount of time.
         //       4. Transition to bouncer scene
@@ -197,8 +197,8 @@
     init {
         if (flags.isEnabled()) {
             applicationScope.launch {
-                authenticationInteractor.authenticationChallengeResult.collectLatest { successful ->
-                    if (successful) {
+                authenticationInteractor.onAuthenticationResult.collectLatest { isSuccessful ->
+                    if (isSuccessful) {
                         repository.reportSuccessfulAuthentication()
                     }
                 }
diff --git a/packages/SystemUI/src/com/android/systemui/flags/FlagDependenciesBase.kt b/packages/SystemUI/src/com/android/systemui/flags/FlagDependenciesBase.kt
index 6560ee3..14fda5e 100644
--- a/packages/SystemUI/src/com/android/systemui/flags/FlagDependenciesBase.kt
+++ b/packages/SystemUI/src/com/android/systemui/flags/FlagDependenciesBase.kt
@@ -73,9 +73,19 @@
     ) {
         val isMet = !alphaEnabled || betaEnabled
         override fun toString(): String {
-            val isMetBullet = if (isMet) "+" else "-"
-            return "$isMetBullet $alphaName ($alphaEnabled) DEPENDS ON $betaName ($betaEnabled)"
+            val prefix =
+                when {
+                    !isMet -> "  [NOT MET]"
+                    alphaEnabled -> "      [met]"
+                    betaEnabled -> "    [ready]"
+                    else -> "[not ready]"
+                }
+            val alphaState = if (alphaEnabled) "enabled" else "disabled"
+            val betaState = if (betaEnabled) "enabled" else "disabled"
+            return "$prefix $alphaName ($alphaState) DEPENDS ON $betaName ($betaState)"
         }
+        /** Used whe posting a notification of unmet dependencies */
+        fun shortUnmetString(): String = "$alphaName DEPENDS ON $betaName"
     }
 
     protected infix fun UnreleasedFlag.dependsOn(other: UnreleasedFlag) =
@@ -124,7 +134,7 @@
         unmet: List<FlagDependenciesBase.Dependency>
     ) {
         val title = "Invalid flag dependencies: ${unmet.size} of ${all.size}"
-        val details = unmet.joinToString("\n")
+        val details = unmet.joinToString("\n") { it.shortUnmetString() }
         Log.e("FlagDependencies", "$title:\n$details")
         val channel = NotificationChannel("FLAGS", "Flags", NotificationManager.IMPORTANCE_DEFAULT)
         val notification =
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardClockViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardClockViewBinder.kt
index 7d290c3..05fe0b2 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardClockViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardClockViewBinder.kt
@@ -32,8 +32,6 @@
 import com.android.systemui.res.R
 import kotlinx.coroutines.launch
 
-private val TAG = KeyguardClockViewBinder::class.simpleName
-
 object KeyguardClockViewBinder {
     @JvmStatic
     fun bind(
@@ -74,12 +72,6 @@
                         applyConstraints(clockSection, keyguardRootView, true)
                     }
                 }
-                launch {
-                    if (!migrateClocksToBlueprint()) return@launch
-                    viewModel.hasCustomWeatherDataDisplay.collect {
-                        applyConstraints(clockSection, keyguardRootView, true)
-                    }
-                }
             }
         }
     }
@@ -132,7 +124,7 @@
     fun applyConstraints(
         clockSection: ClockSection,
         rootView: ConstraintLayout,
-        animated: Boolean
+        animated: Boolean,
     ) {
         val constraintSet = ConstraintSet().apply { clone(rootView) }
         clockSection.applyConstraints(constraintSet)
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardSmartspaceViewBinder.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardSmartspaceViewBinder.kt
index 954d2cf..e36819b 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardSmartspaceViewBinder.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/binder/KeyguardSmartspaceViewBinder.kt
@@ -16,13 +16,19 @@
 
 package com.android.systemui.keyguard.ui.binder
 
+import android.transition.TransitionManager
+import android.view.View
+import androidx.constraintlayout.helper.widget.Layer
 import androidx.constraintlayout.widget.ConstraintLayout
 import androidx.constraintlayout.widget.ConstraintSet
 import androidx.lifecycle.Lifecycle
 import androidx.lifecycle.repeatOnLifecycle
 import com.android.systemui.keyguard.ui.view.layout.sections.SmartspaceSection
 import com.android.systemui.keyguard.ui.viewmodel.KeyguardClockViewModel
+import com.android.systemui.keyguard.ui.viewmodel.KeyguardSmartspaceViewModel
 import com.android.systemui.lifecycle.repeatWhenAttached
+import com.android.systemui.res.R
+import kotlinx.coroutines.launch
 
 object KeyguardSmartspaceViewBinder {
     @JvmStatic
@@ -30,15 +36,63 @@
         smartspaceSection: SmartspaceSection,
         keyguardRootView: ConstraintLayout,
         clockViewModel: KeyguardClockViewModel,
+        smartspaceViewModel: KeyguardSmartspaceViewModel,
     ) {
         keyguardRootView.repeatWhenAttached {
             repeatOnLifecycle(Lifecycle.State.STARTED) {
-                clockViewModel.hasCustomWeatherDataDisplay.collect {
-                    val constraintSet = ConstraintSet().apply { clone(keyguardRootView) }
-                    smartspaceSection.applyConstraints(constraintSet)
-                    constraintSet.applyTo(keyguardRootView)
+                launch {
+                    clockViewModel.hasCustomWeatherDataDisplay.collect { hasCustomWeatherDataDisplay
+                        ->
+                        if (hasCustomWeatherDataDisplay) {
+                            removeDateWeatherToBurnInLayer(keyguardRootView, smartspaceViewModel)
+                        } else {
+                            addDateWeatherToBurnInLayer(keyguardRootView, smartspaceViewModel)
+                        }
+                        clockViewModel.burnInLayer?.updatePostLayout(keyguardRootView)
+                        val constraintSet = ConstraintSet().apply { clone(keyguardRootView) }
+                        smartspaceSection.applyConstraints(constraintSet)
+                        TransitionManager.beginDelayedTransition(keyguardRootView)
+                        constraintSet.applyTo(keyguardRootView)
+                    }
                 }
             }
         }
     }
+
+    private fun addDateWeatherToBurnInLayer(
+        constraintLayout: ConstraintLayout,
+        smartspaceViewModel: KeyguardSmartspaceViewModel
+    ) {
+        val burnInLayer = constraintLayout.requireViewById<Layer>(R.id.burn_in_layer)
+        burnInLayer.apply {
+            if (
+                smartspaceViewModel.isSmartspaceEnabled &&
+                    smartspaceViewModel.isDateWeatherDecoupled
+            ) {
+                val dateView = constraintLayout.requireViewById<View>(smartspaceViewModel.dateId)
+                val weatherView =
+                    constraintLayout.requireViewById<View>(smartspaceViewModel.weatherId)
+                addView(weatherView)
+                addView(dateView)
+            }
+        }
+    }
+
+    private fun removeDateWeatherToBurnInLayer(
+        constraintLayout: ConstraintLayout,
+        smartspaceViewModel: KeyguardSmartspaceViewModel
+    ) {
+        val burnInLayer = constraintLayout.requireViewById<Layer>(R.id.burn_in_layer)
+        burnInLayer.apply {
+            if (
+                smartspaceViewModel.isSmartspaceEnabled &&
+                    smartspaceViewModel.isDateWeatherDecoupled
+            ) {
+                val dateView = smartspaceViewModel.dateView
+                val weatherView = smartspaceViewModel.weatherView
+                removeView(weatherView)
+                removeView(dateView)
+            }
+        }
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/blueprints/SplitShadeKeyguardBlueprint.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/blueprints/SplitShadeKeyguardBlueprint.kt
index f890ae6..16539db 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/blueprints/SplitShadeKeyguardBlueprint.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/blueprints/SplitShadeKeyguardBlueprint.kt
@@ -30,6 +30,8 @@
 import com.android.systemui.keyguard.ui.view.layout.sections.DefaultStatusBarSection
 import com.android.systemui.keyguard.ui.view.layout.sections.DefaultStatusViewSection
 import com.android.systemui.keyguard.ui.view.layout.sections.KeyguardSectionsModule
+import com.android.systemui.keyguard.ui.view.layout.sections.SmartspaceSection
+import com.android.systemui.keyguard.ui.view.layout.sections.SplitShadeClockSection
 import com.android.systemui.keyguard.ui.view.layout.sections.SplitShadeGuidelines
 import com.android.systemui.keyguard.ui.view.layout.sections.SplitShadeNotificationStackScrollLayoutSection
 import com.android.systemui.util.kotlin.getOrNull
@@ -59,6 +61,8 @@
     aodNotificationIconsSection: AodNotificationIconsSection,
     aodBurnInSection: AodBurnInSection,
     communalTutorialIndicatorSection: CommunalTutorialIndicatorSection,
+    smartspaceSection: SmartspaceSection,
+    clockSection: SplitShadeClockSection,
 ) : KeyguardBlueprint {
     override val id: String = ID
 
@@ -73,8 +77,10 @@
             splitShadeNotificationStackScrollLayoutSection,
             splitShadeGuidelines,
             aodNotificationIconsSection,
+            smartspaceSection,
             aodBurnInSection,
             communalTutorialIndicatorSection,
+            clockSection,
             defaultDeviceEntrySection, // Add LAST: Intentionally has z-order above other views.
         )
 
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/AodBurnInSection.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/AodBurnInSection.kt
index 8166b45..d89e1e4 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/AodBurnInSection.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/AodBurnInSection.kt
@@ -23,7 +23,6 @@
 import androidx.constraintlayout.widget.ConstraintLayout
 import androidx.constraintlayout.widget.ConstraintSet
 import com.android.systemui.Flags.migrateClocksToBlueprint
-import com.android.systemui.flags.FeatureFlagsClassic
 import com.android.systemui.keyguard.shared.KeyguardShadeMigrationNssl
 import com.android.systemui.keyguard.shared.model.KeyguardSection
 import com.android.systemui.keyguard.ui.viewmodel.KeyguardClockViewModel
@@ -38,7 +37,6 @@
     private val context: Context,
     private val clockViewModel: KeyguardClockViewModel,
     private val smartspaceViewModel: KeyguardSmartspaceViewModel,
-    private val featureFlags: FeatureFlagsClassic,
 ) : KeyguardSection() {
     lateinit var burnInLayer: Layer
 
@@ -59,6 +57,8 @@
                 }
             }
         if (migrateClocksToBlueprint()) {
+            // weather and date parts won't be added here, cause their visibility doesn't align
+            // with others in burnInLayer
             addSmartspaceViews(constraintLayout)
         }
         constraintLayout.addView(burnInLayer)
@@ -89,14 +89,6 @@
                 val smartspaceView =
                     constraintLayout.requireViewById<View>(smartspaceViewModel.smartspaceViewId)
                 addView(smartspaceView)
-                if (smartspaceViewModel.isDateWeatherDecoupled) {
-                    val dateView =
-                        constraintLayout.requireViewById<View>(smartspaceViewModel.dateId)
-                    val weatherView =
-                        constraintLayout.requireViewById<View>(smartspaceViewModel.weatherId)
-                    addView(weatherView)
-                    addView(dateView)
-                }
             }
         }
     }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/AodNotificationIconsSection.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/AodNotificationIconsSection.kt
index 39a0547..b429ab4 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/AodNotificationIconsSection.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/AodNotificationIconsSection.kt
@@ -28,7 +28,6 @@
 import androidx.constraintlayout.widget.ConstraintSet.TOP
 import com.android.systemui.Flags.migrateClocksToBlueprint
 import com.android.systemui.common.ui.ConfigurationState
-import com.android.systemui.flags.FeatureFlagsClassic
 import com.android.systemui.keyguard.shared.KeyguardShadeMigrationNssl
 import com.android.systemui.keyguard.shared.model.KeyguardSection
 import com.android.systemui.keyguard.ui.viewmodel.KeyguardSmartspaceViewModel
@@ -49,7 +48,6 @@
 constructor(
     private val context: Context,
     private val configurationState: ConfigurationState,
-    private val featureFlags: FeatureFlagsClassic,
     private val iconBindingFailureTracker: StatusBarIconViewBindingFailureTracker,
     private val nicAodViewModel: NotificationIconContainerAlwaysOnDisplayViewModel,
     private val nicAodIconViewStore: AlwaysOnDisplayNotificationIconViewStore,
@@ -119,14 +117,8 @@
             }
         constraintSet.apply {
             if (migrateClocksToBlueprint()) {
-                connect(
-                    nicId,
-                    TOP,
-                    smartspaceViewModel.smartspaceViewId,
-                    topAlignment,
-                    bottomMargin
-                )
-                setGoneMargin(nicId, topAlignment, bottomMargin)
+                connect(nicId, TOP, smartspaceViewModel.smartspaceViewId, BOTTOM, bottomMargin)
+                setGoneMargin(nicId, BOTTOM, bottomMargin)
             } else {
                 connect(nicId, TOP, R.id.keyguard_status_view, topAlignment, bottomMargin)
             }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/ClockSection.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/ClockSection.kt
index 1df920a..656c75c 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/ClockSection.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/ClockSection.kt
@@ -27,7 +27,7 @@
 import androidx.constraintlayout.widget.ConstraintSet.START
 import androidx.constraintlayout.widget.ConstraintSet.TOP
 import androidx.constraintlayout.widget.ConstraintSet.WRAP_CONTENT
-import com.android.systemui.flags.FeatureFlagsClassic
+import com.android.systemui.Flags
 import com.android.systemui.keyguard.domain.interactor.KeyguardClockInteractor
 import com.android.systemui.keyguard.shared.model.KeyguardSection
 import com.android.systemui.keyguard.ui.binder.KeyguardClockViewBinder
@@ -50,19 +50,21 @@
     alpha: Float,
 ) = views.forEach { view -> this.setAlpha(view.id, alpha) }
 
-class ClockSection
+open class ClockSection
 @Inject
 constructor(
     private val clockInteractor: KeyguardClockInteractor,
-    private val keyguardClockViewModel: KeyguardClockViewModel,
-    val smartspaceViewModel: KeyguardSmartspaceViewModel,
+    protected val keyguardClockViewModel: KeyguardClockViewModel,
+    private val smartspaceViewModel: KeyguardSmartspaceViewModel,
     private val context: Context,
     private val splitShadeStateController: SplitShadeStateController,
-    private val featureFlags: FeatureFlagsClassic,
 ) : KeyguardSection() {
     override fun addViews(constraintLayout: ConstraintLayout) {}
 
     override fun bindData(constraintLayout: ConstraintLayout) {
+        if (!Flags.migrateClocksToBlueprint()) {
+            return
+        }
         KeyguardClockViewBinder.bind(
             this,
             constraintLayout,
@@ -72,6 +74,9 @@
     }
 
     override fun applyConstraints(constraintSet: ConstraintSet) {
+        if (!Flags.migrateClocksToBlueprint()) {
+            return
+        }
         clockInteractor.clock?.let { clock ->
             constraintSet.applyDeltaFrom(buildConstraints(clock, constraintSet))
         }
@@ -94,16 +99,6 @@
         }
     }
 
-    var largeClockEndGuideline = PARENT_ID
-
-    // Return if largeClockEndGuideline changes,
-    // and use it to decide whether to refresh blueprint
-    fun setClockShouldBeCentered(shouldBeCentered: Boolean): Boolean {
-        val previousValue = largeClockEndGuideline
-        largeClockEndGuideline = if (shouldBeCentered) PARENT_ID else R.id.split_shade_guideline
-        return previousValue != largeClockEndGuideline
-    }
-
     private fun getTargetClockFace(clock: ClockController): ClockFaceLayout =
         if (keyguardClockViewModel.useLargeClock) getLargeClockFace(clock)
         else getSmallClockFace(clock)
@@ -113,10 +108,10 @@
 
     private fun getLargeClockFace(clock: ClockController): ClockFaceLayout = clock.largeClock.layout
     private fun getSmallClockFace(clock: ClockController): ClockFaceLayout = clock.smallClock.layout
-    fun applyDefaultConstraints(constraints: ConstraintSet) {
+    open fun applyDefaultConstraints(constraints: ConstraintSet) {
         constraints.apply {
             connect(R.id.lockscreen_clock_view_large, START, PARENT_ID, START)
-            connect(R.id.lockscreen_clock_view_large, END, largeClockEndGuideline, END)
+            connect(R.id.lockscreen_clock_view_large, END, PARENT_ID, END)
             connect(R.id.lockscreen_clock_view_large, BOTTOM, R.id.lock_icon_view, TOP)
             var largeClockTopMargin =
                 context.resources.getDimensionPixelSize(R.dimen.status_bar_height) +
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/SmartspaceSection.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/SmartspaceSection.kt
index 368b388..8cd51cd 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/SmartspaceSection.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/SmartspaceSection.kt
@@ -17,7 +17,6 @@
 package com.android.systemui.keyguard.ui.view.layout.sections
 
 import android.content.Context
-import android.view.View
 import androidx.constraintlayout.widget.ConstraintLayout
 import androidx.constraintlayout.widget.ConstraintSet
 import androidx.constraintlayout.widget.ConstraintSet.BOTTOM
@@ -36,7 +35,7 @@
 import com.android.systemui.statusbar.lockscreen.LockscreenSmartspaceController
 import javax.inject.Inject
 
-class SmartspaceSection
+open class SmartspaceSection
 @Inject
 constructor(
     val keyguardClockViewModel: KeyguardClockViewModel,
@@ -45,9 +44,13 @@
     val smartspaceController: LockscreenSmartspaceController,
     val keyguardUnlockAnimationController: KeyguardUnlockAnimationController,
 ) : KeyguardSection() {
-    private var smartspaceView: View? = null
-    private var weatherView: View? = null
-    private var dateView: View? = null
+    var smartspaceView by keyguardSmartspaceViewModel::smartspaceView
+    var weatherView by keyguardSmartspaceViewModel::weatherView
+    var dateView by keyguardSmartspaceViewModel::dateView
+
+    val smartspaceViewId = keyguardSmartspaceViewModel.smartspaceViewId
+    val weatherViewId = keyguardSmartspaceViewModel.weatherId
+    val dateViewId = keyguardSmartspaceViewModel.dateId
 
     override fun addViews(constraintLayout: ConstraintLayout) {
         if (!migrateClocksToBlueprint()) {
@@ -67,10 +70,21 @@
     }
 
     override fun bindData(constraintLayout: ConstraintLayout) {
-        KeyguardSmartspaceViewBinder.bind(this, constraintLayout, keyguardClockViewModel)
+        if (!migrateClocksToBlueprint()) {
+            return
+        }
+        KeyguardSmartspaceViewBinder.bind(
+            this,
+            constraintLayout,
+            keyguardClockViewModel,
+            keyguardSmartspaceViewModel,
+        )
     }
 
     override fun applyConstraints(constraintSet: ConstraintSet) {
+        if (!migrateClocksToBlueprint()) {
+            return
+        }
         // Generally, weather should be next to dateView
         // smartspace should be below date & weather views
         constraintSet.apply {
@@ -130,7 +144,20 @@
                     }
                 }
             }
-            updateVisibility(constraintSet)
+        }
+        updateVisibility(constraintSet)
+    }
+
+    override fun removeViews(constraintLayout: ConstraintLayout) {
+        if (!migrateClocksToBlueprint()) {
+            return
+        }
+        listOf(smartspaceView, dateView, weatherView).forEach {
+            it?.let {
+                if (it.parent == constraintLayout) {
+                    constraintLayout.removeView(it)
+                }
+            }
         }
     }
 
@@ -158,14 +185,4 @@
             }
         }
     }
-
-    override fun removeViews(constraintLayout: ConstraintLayout) {
-        listOf(smartspaceView, dateView, weatherView).forEach {
-            it?.let {
-                if (it.parent == constraintLayout) {
-                    constraintLayout.removeView(it)
-                }
-            }
-        }
-    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/SplitShadeClockSection.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/SplitShadeClockSection.kt
new file mode 100644
index 0000000..1302bfa
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/SplitShadeClockSection.kt
@@ -0,0 +1,58 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.keyguard.ui.view.layout.sections
+
+import android.content.Context
+import androidx.constraintlayout.widget.ConstraintSet
+import com.android.systemui.keyguard.domain.interactor.KeyguardClockInteractor
+import com.android.systemui.keyguard.ui.viewmodel.KeyguardClockViewModel
+import com.android.systemui.keyguard.ui.viewmodel.KeyguardSmartspaceViewModel
+import com.android.systemui.res.R
+import com.android.systemui.statusbar.policy.SplitShadeStateController
+import javax.inject.Inject
+
+class SplitShadeClockSection
+@Inject
+constructor(
+    clockInteractor: KeyguardClockInteractor,
+    keyguardClockViewModel: KeyguardClockViewModel,
+    smartspaceViewModel: KeyguardSmartspaceViewModel,
+    context: Context,
+    splitShadeStateController: SplitShadeStateController,
+) :
+    ClockSection(
+        clockInteractor,
+        keyguardClockViewModel,
+        smartspaceViewModel,
+        context,
+        splitShadeStateController
+    ) {
+    override fun applyDefaultConstraints(constraints: ConstraintSet) {
+        super.applyDefaultConstraints(constraints)
+        val largeClockEndGuideline =
+            if (keyguardClockViewModel.clockShouldBeCentered.value) ConstraintSet.PARENT_ID
+            else R.id.split_shade_guideline
+        constraints.apply {
+            connect(
+                R.id.lockscreen_clock_view_large,
+                ConstraintSet.END,
+                largeClockEndGuideline,
+                ConstraintSet.END
+            )
+        }
+    }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/SplitShadeNotificationStackScrollLayoutSection.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/SplitShadeNotificationStackScrollLayoutSection.kt
index b0b5c81..0f8e673 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/SplitShadeNotificationStackScrollLayoutSection.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/view/layout/sections/SplitShadeNotificationStackScrollLayoutSection.kt
@@ -23,7 +23,6 @@
 import androidx.constraintlayout.widget.ConstraintSet.PARENT_ID
 import androidx.constraintlayout.widget.ConstraintSet.START
 import androidx.constraintlayout.widget.ConstraintSet.TOP
-import com.android.systemui.Flags.migrateClocksToBlueprint
 import com.android.systemui.dagger.qualifiers.Main
 import com.android.systemui.keyguard.shared.KeyguardShadeMigrationNssl
 import com.android.systemui.keyguard.ui.viewmodel.KeyguardSmartspaceViewModel
@@ -72,25 +71,9 @@
             return
         }
         constraintSet.apply {
-            val bottomMargin =
-                context.resources.getDimensionPixelSize(R.dimen.keyguard_status_view_bottom_margin)
-
-            if (migrateClocksToBlueprint()) {
-                connect(
-                    R.id.nssl_placeholder,
-                    TOP,
-                    smartspaceViewModel.smartspaceViewId,
-                    TOP,
-                    bottomMargin
-                )
-                setGoneMargin(R.id.nssl_placeholder, TOP, bottomMargin)
-            } else {
-                val splitShadeTopMargin =
-                    context.resources.getDimensionPixelSize(
-                        R.dimen.large_screen_shade_header_height
-                    )
-                connect(R.id.nssl_placeholder, TOP, PARENT_ID, TOP, splitShadeTopMargin)
-            }
+            val splitShadeTopMargin =
+                context.resources.getDimensionPixelSize(R.dimen.large_screen_shade_header_height)
+            connect(R.id.nssl_placeholder, TOP, PARENT_ID, TOP, splitShadeTopMargin)
 
             connect(R.id.nssl_placeholder, START, PARENT_ID, START)
             connect(R.id.nssl_placeholder, END, PARENT_ID, END)
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardClockViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardClockViewModel.kt
index 3aeff61..528a2ee 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardClockViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardClockViewModel.kt
@@ -27,8 +27,8 @@
 import com.android.systemui.plugins.clocks.ClockController
 import javax.inject.Inject
 import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.flow.Flow
 import kotlinx.coroutines.flow.SharingStarted
+import kotlinx.coroutines.flow.StateFlow
 import kotlinx.coroutines.flow.combine
 import kotlinx.coroutines.flow.stateIn
 
@@ -79,10 +79,10 @@
                         ?: false
             )
 
-    val clockShouldBeCentered: Flow<Boolean> =
+    val clockShouldBeCentered: StateFlow<Boolean> =
         keyguardInteractor.clockShouldBeCentered.stateIn(
             scope = applicationScope,
             started = SharingStarted.WhileSubscribed(),
-            initialValue = true
+            initialValue = false
         )
 }
diff --git a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardSmartspaceViewModel.kt b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardSmartspaceViewModel.kt
index 4541458..26e7ee8 100644
--- a/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardSmartspaceViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardSmartspaceViewModel.kt
@@ -18,6 +18,7 @@
 
 import android.content.Context
 import android.util.Log
+import android.view.View
 import com.android.systemui.dagger.SysUISingleton
 import com.android.systemui.statusbar.lockscreen.LockscreenSmartspaceController
 import javax.inject.Inject
@@ -25,7 +26,7 @@
 @SysUISingleton
 class KeyguardSmartspaceViewModel
 @Inject
-constructor(val context: Context, smartspaceController: LockscreenSmartspaceController) {
+constructor(val context: Context, val smartspaceController: LockscreenSmartspaceController) {
     val isSmartspaceEnabled: Boolean = smartspaceController.isEnabled()
     val isWeatherEnabled: Boolean = smartspaceController.isWeatherEnabled()
     val isDateWeatherDecoupled: Boolean = smartspaceController.isDateWeatherDecoupled()
@@ -38,6 +39,10 @@
     val weatherId: Int
         get() = getId("weather_smartspace_view")
 
+    var smartspaceView: View? = null
+    var weatherView: View? = null
+    var dateView: View? = null
+
     private fun getId(name: String): Int {
         return context.resources.getIdentifier(name, "id", context.packageName).also {
             if (it == 0) {
diff --git a/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java b/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java
index d8bb3e6..0d5ba64 100644
--- a/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java
+++ b/packages/SystemUI/src/com/android/systemui/log/dagger/LogModule.java
@@ -26,6 +26,7 @@
 import com.android.systemui.log.echo.LogcatEchoTrackerProd;
 import com.android.systemui.log.table.TableLogBuffer;
 import com.android.systemui.log.table.TableLogBufferFactory;
+import com.android.systemui.plugins.clocks.ClockMessageBuffers;
 import com.android.systemui.qs.QSFragmentLegacy;
 import com.android.systemui.qs.pipeline.shared.QSPipelineFlagsRepository;
 import com.android.systemui.qs.pipeline.shared.TileSpec;
@@ -417,6 +418,18 @@
     }
 
     /**
+     * Provides a {@link ClockMessageBuffers} which contains the keyguard clock message buffers.
+     */
+    @Provides
+    public static ClockMessageBuffers provideKeyguardClockMessageBuffers(
+            @KeyguardClockLog LogBuffer infraClockLog,
+            @KeyguardSmallClockLog LogBuffer smallClockLog,
+            @KeyguardLargeClockLog LogBuffer largeClockLog
+    ) {
+        return new ClockMessageBuffers(infraClockLog, smallClockLog, largeClockLog);
+    }
+
+    /**
      * Provides a {@link LogBuffer} for use by {@link com.android.keyguard.KeyguardUpdateMonitor}.
      */
     @Provides
diff --git a/packages/SystemUI/src/com/android/systemui/qs/external/QSTileServiceWrapper.java b/packages/SystemUI/src/com/android/systemui/qs/external/QSTileServiceWrapper.java
index 2345667..83b6f0d 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/external/QSTileServiceWrapper.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/external/QSTileServiceWrapper.java
@@ -19,16 +19,21 @@
 import android.service.quicksettings.IQSTileService;
 import android.util.Log;
 
+import androidx.annotation.NonNull;
+
 
 public class QSTileServiceWrapper {
     private static final String TAG = "IQSTileServiceWrapper";
 
+    @NonNull
     private final IQSTileService mService;
 
-    public QSTileServiceWrapper(IQSTileService service) {
+    public QSTileServiceWrapper(@NonNull IQSTileService service) {
         mService = service;
     }
 
+    // This can never be null, as it's the constructor parameter and it's final
+    @NonNull
     public IBinder asBinder() {
         return mService.asBinder();
     }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/external/TileLifecycleManager.java b/packages/SystemUI/src/com/android/systemui/qs/external/TileLifecycleManager.java
index e08eb37..880289e 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/external/TileLifecycleManager.java
+++ b/packages/SystemUI/src/com/android/systemui/qs/external/TileLifecycleManager.java
@@ -40,6 +40,7 @@
 import android.util.ArraySet;
 import android.util.Log;
 
+import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 import androidx.annotation.WorkerThread;
 
@@ -54,8 +55,10 @@
 
 import java.util.NoSuchElementException;
 import java.util.Objects;
+import java.util.Optional;
 import java.util.Set;
 import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.function.Predicate;
 
 /**
  * Manages the lifecycle of a TileService.
@@ -101,8 +104,8 @@
     private final ActivityManager mActivityManager;
 
     private Set<Integer> mQueuedMessages = new ArraySet<>();
-    @Nullable
-    private volatile QSTileServiceWrapper mWrapper;
+    @NonNull
+    private volatile Optional<QSTileServiceWrapper> mOptionalWrapper = Optional.empty();
     private boolean mListening;
     private IBinder mClickBinder;
 
@@ -222,6 +225,7 @@
                 // Only try a new binding if we are not currently bound.
                 mIsBound.compareAndSet(false, bindServices());
                 if (!mIsBound.get()) {
+                    Log.d(TAG, "Failed to bind to service");
                     mContext.unbindService(this);
                 }
             } catch (SecurityException e) {
@@ -281,7 +285,7 @@
             service.linkToDeath(this, 0);
         } catch (RemoteException e) {
         }
-        mWrapper = wrapper;
+        mOptionalWrapper = Optional.of(wrapper);
         handlePendingMessages();
     }
 
@@ -368,6 +372,10 @@
      * are supposed to be bound, we will try to bind after some amount of time.
      */
     private void handleDeath() {
+        if (!mIsBound.get()) {
+            // If we are already not bound, don't do anything else.
+            return;
+        }
         mExecutor.execute(() -> {
             if (!mIsBound.get()) {
                 // If we are already not bound, don't do anything else.
@@ -522,7 +530,7 @@
     @Override
     public void onTileAdded() {
         if (mDebug) Log.d(TAG, "onTileAdded " + getComponent());
-        if (mWrapper == null || !mWrapper.onTileAdded()) {
+        if (isNullOrFailedAction(mOptionalWrapper, QSTileServiceWrapper::onTileAdded)) {
             queueMessage(MSG_ON_ADDED);
             handleDeath();
         }
@@ -531,7 +539,7 @@
     @Override
     public void onTileRemoved() {
         if (mDebug) Log.d(TAG, "onTileRemoved " + getComponent());
-        if (mWrapper == null || !mWrapper.onTileRemoved()) {
+        if (isNullOrFailedAction(mOptionalWrapper, QSTileServiceWrapper::onTileRemoved)) {
             queueMessage(MSG_ON_REMOVED);
             handleDeath();
         }
@@ -541,7 +549,7 @@
     public void onStartListening() {
         if (mDebug) Log.d(TAG, "onStartListening " + getComponent());
         mListening = true;
-        if (mWrapper != null && !mWrapper.onStartListening()) {
+        if (isNotNullAndFailedAction(mOptionalWrapper, QSTileServiceWrapper::onStartListening)) {
             handleDeath();
         }
     }
@@ -550,7 +558,7 @@
     public void onStopListening() {
         if (mDebug) Log.d(TAG, "onStopListening " + getComponent());
         mListening = false;
-        if (mWrapper != null && !mWrapper.onStopListening()) {
+        if (isNotNullAndFailedAction(mOptionalWrapper, QSTileServiceWrapper::onStopListening)) {
             handleDeath();
         }
     }
@@ -558,7 +566,7 @@
     @Override
     public void onClick(IBinder iBinder) {
         if (mDebug) Log.d(TAG, "onClick " + iBinder + " " + getComponent() + " " + mUser);
-        if (mWrapper == null || !mWrapper.onClick(iBinder)) {
+        if (isNullOrFailedAction(mOptionalWrapper, (wrapper) -> wrapper.onClick(iBinder))) {
             mClickBinder = iBinder;
             queueMessage(MSG_ON_CLICK);
             handleDeath();
@@ -568,7 +576,7 @@
     @Override
     public void onUnlockComplete() {
         if (mDebug) Log.d(TAG, "onUnlockComplete " + getComponent());
-        if (mWrapper == null || !mWrapper.onUnlockComplete()) {
+        if (isNullOrFailedAction(mOptionalWrapper, QSTileServiceWrapper::onUnlockComplete)) {
             queueMessage(MSG_ON_UNLOCK_COMPLETE);
             handleDeath();
         }
@@ -577,7 +585,7 @@
     @Nullable
     @Override
     public IBinder asBinder() {
-        return mWrapper != null ? mWrapper.asBinder() : null;
+        return mOptionalWrapper.map(QSTileServiceWrapper::asBinder).orElse(null);
     }
 
     @Override
@@ -591,18 +599,42 @@
     }
 
     private void freeWrapper() {
-        if (mWrapper != null) {
+        if (mOptionalWrapper.isPresent()) {
             try {
-                mWrapper.asBinder().unlinkToDeath(this, 0);
+                mOptionalWrapper.ifPresent(
+                        (wrapper) -> wrapper.asBinder().unlinkToDeath(this, 0)
+                );
             } catch (NoSuchElementException e) {
                 Log.w(TAG, "Trying to unlink not linked recipient for component"
                         + mIntent.getComponent().flattenToShortString());
             }
-            mWrapper = null;
+            mOptionalWrapper = Optional.empty();
         }
     }
 
     public interface TileChangeListener {
         void onTileChanged(ComponentName tile);
     }
+
+    /**
+     * Returns true if the Optional is empty OR performing the action on the content of the Optional
+     * (when not empty) fails.
+     */
+    private static boolean isNullOrFailedAction(
+            Optional<QSTileServiceWrapper> optionalWrapper,
+            Predicate<QSTileServiceWrapper> action
+    ) {
+        return !optionalWrapper.map(action::test).orElse(false);
+    }
+
+    /**
+     * Returns true if the Optional is not empty AND performing the action on the content of
+     * the Optional fails.
+     */
+    private static boolean isNotNullAndFailedAction(
+            Optional<QSTileServiceWrapper> optionalWrapper,
+            Predicate<QSTileServiceWrapper> action
+    ) {
+        return  !optionalWrapper.map(action::test).orElse(true);
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/qs/tiles/RecordIssueTile.kt b/packages/SystemUI/src/com/android/systemui/qs/tiles/RecordIssueTile.kt
index 66da8bd..216d716 100644
--- a/packages/SystemUI/src/com/android/systemui/qs/tiles/RecordIssueTile.kt
+++ b/packages/SystemUI/src/com/android/systemui/qs/tiles/RecordIssueTile.kt
@@ -42,9 +42,7 @@
 import com.android.systemui.qs.tileimpl.QSTileImpl
 import com.android.systemui.recordissue.RecordIssueDialogDelegate
 import com.android.systemui.res.R
-import com.android.systemui.settings.UserContextProvider
 import com.android.systemui.statusbar.phone.KeyguardDismissUtil
-import com.android.systemui.statusbar.phone.SystemUIDialog
 import com.android.systemui.statusbar.policy.KeyguardStateController
 import javax.inject.Inject
 
@@ -63,8 +61,7 @@
     private val keyguardDismissUtil: KeyguardDismissUtil,
     private val keyguardStateController: KeyguardStateController,
     private val dialogLaunchAnimator: DialogLaunchAnimator,
-    private val sysuiDialogFactory: SystemUIDialog.Factory,
-    private val userContextProvider: UserContextProvider,
+    private val delegateFactory: RecordIssueDialogDelegate.Factory,
 ) :
     QSTileImpl<QSTile.BooleanState>(
         host,
@@ -102,7 +99,8 @@
 
     private fun showPrompt(view: View?) {
         val dialog: AlertDialog =
-            RecordIssueDialogDelegate(sysuiDialogFactory, userContextProvider) {
+            delegateFactory
+                .create {
                     isRecording = true
                     refreshState()
                 }
diff --git a/packages/SystemUI/src/com/android/systemui/recordissue/RecordIssueDialogDelegate.kt b/packages/SystemUI/src/com/android/systemui/recordissue/RecordIssueDialogDelegate.kt
index 5bf44fa..e051df4 100644
--- a/packages/SystemUI/src/com/android/systemui/recordissue/RecordIssueDialogDelegate.kt
+++ b/packages/SystemUI/src/com/android/systemui/recordissue/RecordIssueDialogDelegate.kt
@@ -24,27 +24,63 @@
 import android.content.res.ColorStateList
 import android.graphics.Color
 import android.os.Bundle
+import android.os.UserHandle
 import android.view.Gravity
 import android.view.LayoutInflater
 import android.view.WindowManager
 import android.widget.Button
 import android.widget.PopupMenu
 import android.widget.Switch
+import androidx.annotation.AnyThread
+import androidx.annotation.MainThread
+import com.android.systemui.dagger.qualifiers.Background
+import com.android.systemui.dagger.qualifiers.Main
+import com.android.systemui.flags.FeatureFlagsClassic
+import com.android.systemui.flags.Flags
+import com.android.systemui.flags.Flags.WM_ENABLE_PARTIAL_SCREEN_SHARING_ENTERPRISE_POLICIES
+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.qs.tiles.RecordIssueTile
 import com.android.systemui.res.R
 import com.android.systemui.screenrecord.RecordingService
 import com.android.systemui.screenrecord.ScreenRecordingAudioSource
 import com.android.systemui.settings.UserContextProvider
+import com.android.systemui.settings.UserFileManager
+import com.android.systemui.settings.UserTracker
 import com.android.systemui.statusbar.phone.SystemUIDialog
+import dagger.assisted.Assisted
+import dagger.assisted.AssistedFactory
+import dagger.assisted.AssistedInject
+import java.util.concurrent.Executor
 
-class RecordIssueDialogDelegate(
+class RecordIssueDialogDelegate
+@AssistedInject
+constructor(
     private val factory: SystemUIDialog.Factory,
     private val userContextProvider: UserContextProvider,
-    private val onStarted: Runnable
+    private val userTracker: UserTracker,
+    private val flags: FeatureFlagsClassic,
+    @Background private val bgExecutor: Executor,
+    @Main private val mainExecutor: Executor,
+    private val devicePolicyResolver: dagger.Lazy<ScreenCaptureDevicePolicyResolver>,
+    private val mediaProjectionMetricsLogger: MediaProjectionMetricsLogger,
+    private val userFileManager: UserFileManager,
+    @Assisted private val onStarted: Runnable,
 ) : SystemUIDialog.Delegate {
 
+    /** To inject dependencies and allow for easier testing */
+    @AssistedFactory
+    interface Factory {
+        /** Create a dialog object */
+        fun create(onStarted: Runnable): RecordIssueDialogDelegate
+    }
+
     @SuppressLint("UseSwitchCompatOrMaterialCode") private lateinit var screenRecordSwitch: Switch
     private lateinit var issueTypeButton: Button
 
+    @MainThread
     override fun beforeCreate(dialog: SystemUIDialog, savedInstanceState: Bundle?) {
         dialog.apply {
             setView(LayoutInflater.from(context).inflate(R.layout.record_issue_dialog, null))
@@ -63,17 +99,64 @@
 
     override fun createDialog(): SystemUIDialog = factory.create(this)
 
+    @MainThread
     override fun onCreate(dialog: SystemUIDialog, savedInstanceState: Bundle?) {
         dialog.apply {
             window?.addPrivateFlags(WindowManager.LayoutParams.SYSTEM_FLAG_SHOW_FOR_ALL_USERS)
             window?.setGravity(Gravity.CENTER)
 
             screenRecordSwitch = requireViewById(R.id.screenrecord_switch)
+            screenRecordSwitch.setOnCheckedChangeListener { _, isEnabled ->
+                onScreenRecordSwitchClicked(context, isEnabled)
+            }
             issueTypeButton = requireViewById(R.id.issue_type_button)
             issueTypeButton.setOnClickListener { onIssueTypeClicked(context) }
         }
     }
 
+    @AnyThread
+    private fun onScreenRecordSwitchClicked(context: Context, isEnabled: Boolean) {
+        if (!isEnabled) return
+
+        bgExecutor.execute {
+            if (
+                flags.isEnabled(WM_ENABLE_PARTIAL_SCREEN_SHARING_ENTERPRISE_POLICIES) &&
+                    devicePolicyResolver
+                        .get()
+                        .isScreenCaptureCompletelyDisabled(UserHandle.of(userTracker.userId))
+            ) {
+                mainExecutor.execute {
+                    ScreenCaptureDisabledDialog(context).show()
+                    screenRecordSwitch.isChecked = false
+                }
+                return@execute
+            }
+
+            mediaProjectionMetricsLogger.notifyProjectionInitiated(
+                userTracker.userId,
+                SessionCreationSource.SYSTEM_UI_SCREEN_RECORDER
+            )
+
+            if (flags.isEnabled(Flags.WM_ENABLE_PARTIAL_SCREEN_SHARING)) {
+                val prefs =
+                    userFileManager.getSharedPreferences(
+                        RecordIssueTile.TILE_SPEC,
+                        Context.MODE_PRIVATE,
+                        userTracker.userId
+                    )
+                if (!prefs.getBoolean(HAS_APPROVED_SCREEN_RECORDING, false)) {
+                    mainExecutor.execute {
+                        ScreenCapturePermissionDialogDelegate(factory, prefs).createDialog().apply {
+                            setOnCancelListener { screenRecordSwitch.isChecked = false }
+                            show()
+                        }
+                    }
+                }
+            }
+        }
+    }
+
+    @MainThread
     private fun onIssueTypeClicked(context: Context) {
         val selectedCategory = issueTypeButton.text.toString()
         val popupMenu = PopupMenu(context, issueTypeButton)
diff --git a/packages/SystemUI/src/com/android/systemui/recordissue/ScreenCapturePermissionDialogDelegate.kt b/packages/SystemUI/src/com/android/systemui/recordissue/ScreenCapturePermissionDialogDelegate.kt
new file mode 100644
index 0000000..de6d3f6
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/recordissue/ScreenCapturePermissionDialogDelegate.kt
@@ -0,0 +1,49 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.systemui.recordissue
+
+import android.content.SharedPreferences
+import android.os.Bundle
+import android.view.Gravity
+import android.view.WindowManager
+import com.android.systemui.res.R
+import com.android.systemui.statusbar.phone.SystemUIDialog
+
+const val HAS_APPROVED_SCREEN_RECORDING = "HasApprovedScreenRecord"
+
+class ScreenCapturePermissionDialogDelegate(
+    private val dialogFactory: SystemUIDialog.Factory,
+    private val sharedPreferences: SharedPreferences,
+) : SystemUIDialog.Delegate {
+
+    override fun beforeCreate(dialog: SystemUIDialog, savedInstanceState: Bundle?) {
+        dialog.apply {
+            setIcon(R.drawable.ic_screenrecord)
+            setTitle(R.string.screenrecord_permission_dialog_title)
+            setMessage(R.string.screenrecord_permission_dialog_warning_entire_screen)
+            setNegativeButton(R.string.slice_permission_deny) { _, _ -> cancel() }
+            setPositiveButton(R.string.slice_permission_allow) { _, _ ->
+                sharedPreferences.edit().putBoolean(HAS_APPROVED_SCREEN_RECORDING, true).apply()
+                dismiss()
+            }
+            window?.addPrivateFlags(WindowManager.LayoutParams.SYSTEM_FLAG_SHOW_FOR_ALL_USERS)
+            window?.setGravity(Gravity.CENTER)
+        }
+    }
+
+    override fun createDialog(): SystemUIDialog = dialogFactory.create(this)
+}
diff --git a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
index 878e6fa..17eb3c8 100644
--- a/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
+++ b/packages/SystemUI/src/com/android/systemui/shade/NotificationPanelViewController.java
@@ -1720,9 +1720,12 @@
         }
         // To prevent the weather clock from overlapping with the notification shelf on AOD, we use
         // the small clock here
-        if (mKeyguardStatusViewController.isLargeClockBlockingNotificationShelf()
-                && hasVisibleNotifications() && isOnAod()) {
-            return SMALL;
+        // With migrateClocksToBlueprint, weather clock will have behaviors similar to other clocks
+        if (!migrateClocksToBlueprint()) {
+            if (mKeyguardStatusViewController.isLargeClockBlockingNotificationShelf()
+                    && hasVisibleNotifications() && isOnAod()) {
+                return SMALL;
+            }
         }
         return LARGE;
     }
@@ -1742,8 +1745,9 @@
         } else {
             layout = mNotificationContainerParent;
         }
+
         if (migrateClocksToBlueprint()) {
-            mKeyguardInteractor.setClockShouldBeCentered(shouldBeCentered);
+            mKeyguardInteractor.setClockShouldBeCentered(mSplitShadeEnabled && shouldBeCentered);
         } else {
             mKeyguardStatusViewController.updateAlignment(
                     layout, mSplitShadeEnabled, shouldBeCentered, animate);
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/domain/interactor/NotificationIconsInteractor.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/domain/interactor/NotificationIconsInteractor.kt
index 30e2f0e0..9215568 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/domain/interactor/NotificationIconsInteractor.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/domain/interactor/NotificationIconsInteractor.kt
@@ -21,6 +21,7 @@
 import com.android.systemui.statusbar.data.repository.NotificationListenerSettingsRepository
 import com.android.systemui.statusbar.notification.data.repository.NotificationsKeyguardViewStateRepository
 import com.android.systemui.statusbar.notification.domain.interactor.ActiveNotificationsInteractor
+import com.android.systemui.statusbar.notification.domain.interactor.HeadsUpNotificationIconInteractor
 import com.android.systemui.statusbar.notification.shared.ActiveNotificationModel
 import com.android.wm.shell.bubbles.Bubbles
 import java.util.Optional
@@ -37,10 +38,12 @@
 constructor(
     private val activeNotificationsInteractor: ActiveNotificationsInteractor,
     private val bubbles: Optional<Bubbles>,
+    private val headsUpNotificationIconInteractor: HeadsUpNotificationIconInteractor,
     private val keyguardViewStateRepository: NotificationsKeyguardViewStateRepository,
 ) {
     /** Returns a subset of all active notifications based on the supplied filtration parameters. */
     fun filteredNotifSet(
+        forceShowHeadsUp: Boolean = false,
         showAmbient: Boolean = true,
         showLowPriority: Boolean = true,
         showDismissed: Boolean = true,
@@ -49,18 +52,21 @@
     ): Flow<Set<ActiveNotificationModel>> {
         return combine(
             activeNotificationsInteractor.topLevelRepresentativeNotifications,
+            headsUpNotificationIconInteractor.isolatedNotification,
             keyguardViewStateRepository.areNotificationsFullyHidden,
-        ) { notifications, notifsFullyHidden ->
+        ) { notifications, isolatedNotifKey, notifsFullyHidden ->
             notifications
                 .asSequence()
                 .filter { model: ActiveNotificationModel ->
                     shouldShowNotificationIcon(
                         model = model,
+                        forceShowHeadsUp = forceShowHeadsUp,
                         showAmbient = showAmbient,
                         showLowPriority = showLowPriority,
                         showDismissed = showDismissed,
                         showRepliedMessages = showRepliedMessages,
                         showPulsing = showPulsing,
+                        isolatedNotifKey = isolatedNotifKey,
                         notifsFullyHidden = notifsFullyHidden,
                     )
                 }
@@ -70,14 +76,17 @@
 
     private fun shouldShowNotificationIcon(
         model: ActiveNotificationModel,
+        forceShowHeadsUp: Boolean,
         showAmbient: Boolean,
         showLowPriority: Boolean,
         showDismissed: Boolean,
         showRepliedMessages: Boolean,
         showPulsing: Boolean,
+        isolatedNotifKey: String?,
         notifsFullyHidden: Boolean,
     ): Boolean {
         return when {
+            forceShowHeadsUp && model.key == isolatedNotifKey -> true
             !showAmbient && model.isAmbient -> false
             !showLowPriority && model.isSilent -> false
             !showDismissed && model.isRowDismissed -> false
@@ -118,6 +127,7 @@
     val statusBarNotifs: Flow<Set<ActiveNotificationModel>> =
         settingsRepository.showSilentStatusIcons.flatMapLatest { showSilentIcons ->
             iconsInteractor.filteredNotifSet(
+                forceShowHeadsUp = true,
                 showAmbient = false,
                 showLowPriority = showSilentIcons,
                 showDismissed = false,
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerStatusBarViewModel.kt b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerStatusBarViewModel.kt
index 6e5ac47..d00cd1f 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerStatusBarViewModel.kt
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerStatusBarViewModel.kt
@@ -96,8 +96,8 @@
                     iconsViewData.visibleIcons.firstOrNull { it.notifKey == isolatedNotif }
                 }
             }
-            .pairwise(initialValue = null)
             .distinctUntilChanged()
+            .pairwise(initialValue = null)
             .sample(shadeInteractor.shadeExpansion) { (prev, iconInfo), shadeExpansion ->
                 val animate =
                     when {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
index 5872840..31ca106 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableNotificationRow.java
@@ -513,15 +513,13 @@
     private void setImageViewAnimationRunning(ImageView imageView, boolean running) {
         if (imageView != null) {
             Drawable drawable = imageView.getDrawable();
-            if (drawable instanceof AnimationDrawable) {
-                AnimationDrawable animationDrawable = (AnimationDrawable) drawable;
+            if (drawable instanceof AnimationDrawable animationDrawable) {
                 if (running) {
                     animationDrawable.start();
                 } else {
                     animationDrawable.stop();
                 }
-            } else if (drawable instanceof AnimatedVectorDrawable) {
-                AnimatedVectorDrawable animationDrawable = (AnimatedVectorDrawable) drawable;
+            } else if (drawable instanceof AnimatedVectorDrawable animationDrawable) {
                 if (running) {
                     animationDrawable.start();
                 } else {
@@ -3439,8 +3437,7 @@
 
     @Override
     protected boolean childNeedsClipping(View child) {
-        if (child instanceof NotificationContentView) {
-            NotificationContentView contentView = (NotificationContentView) child;
+        if (child instanceof NotificationContentView contentView) {
             if (isClippingNeeded()) {
                 return true;
             } else if (hasRoundedCorner()
@@ -3522,8 +3519,7 @@
 
         @Override
         public void applyToView(View view) {
-            if (view instanceof ExpandableNotificationRow) {
-                ExpandableNotificationRow row = (ExpandableNotificationRow) view;
+            if (view instanceof ExpandableNotificationRow row) {
                 if (row.isExpandAnimationRunning()) {
                     return;
                 }
@@ -3543,8 +3539,7 @@
         @Override
         protected void onYTranslationAnimationFinished(View view) {
             super.onYTranslationAnimationFinished(view);
-            if (view instanceof ExpandableNotificationRow) {
-                ExpandableNotificationRow row = (ExpandableNotificationRow) view;
+            if (view instanceof ExpandableNotificationRow row) {
                 if (row.isHeadsUpAnimatingAway()) {
                     row.setHeadsUpAnimatingAway(false);
                 }
@@ -3553,8 +3548,7 @@
 
         @Override
         public void animateTo(View child, AnimationProperties properties) {
-            if (child instanceof ExpandableNotificationRow) {
-                ExpandableNotificationRow row = (ExpandableNotificationRow) child;
+            if (child instanceof ExpandableNotificationRow row) {
                 if (row.isExpandAnimationRunning()) {
                     return;
                 }
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableView.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableView.java
index 49674d6..c4d266e 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableView.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/row/ExpandableView.java
@@ -676,8 +676,7 @@
         mViewState.headsUpIsVisible = false;
 
         // handling reset for child notifications
-        if (this instanceof ExpandableNotificationRow) {
-            ExpandableNotificationRow row = (ExpandableNotificationRow) this;
+        if (this instanceof ExpandableNotificationRow row) {
             List<ExpandableNotificationRow> children = row.getAttachedChildren();
             if (row.isSummaryWithChildren() && children != null) {
                 for (ExpandableNotificationRow childRow : children) {
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainer.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainer.java
index 0236fc2..45b9c26 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainer.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationChildrenContainer.java
@@ -869,8 +869,7 @@
         Path clipPath = mChildClipPath;
         if (clipPath != null) {
             final float translation;
-            if (child instanceof ExpandableNotificationRow) {
-                ExpandableNotificationRow notificationRow = (ExpandableNotificationRow) child;
+            if (child instanceof ExpandableNotificationRow notificationRow) {
                 translation = notificationRow.getTranslation();
             } else {
                 translation = child.getTranslationX();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
index 62fdc29..0f640c9 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayout.java
@@ -993,8 +993,7 @@
         for (int i = 0; i < childCount; i++) {
             View child = getChildAt(i);
             if (child.getVisibility() != View.GONE
-                    && child instanceof ExpandableNotificationRow) {
-                ExpandableNotificationRow row = (ExpandableNotificationRow) child;
+                    && child instanceof ExpandableNotificationRow row) {
                 if ((row.isPinned() || row.isHeadsUpAnimatingAway()) && row.getTranslation() < 0
                         && row.getProvider().shouldShowGutsOnSnapOpen()) {
                     top = Math.min(top, row.getTranslationY());
@@ -1129,10 +1128,9 @@
             for (int i = 0; i < n; i++) {
                 View view = getChildAt(i);
                 if (view.getVisibility() == View.GONE
-                        || !(view instanceof ExpandableNotificationRow)) {
+                        || !(view instanceof ExpandableNotificationRow row)) {
                     continue;
                 }
-                ExpandableNotificationRow row = (ExpandableNotificationRow) view;
                 currentIndex++;
                 boolean beforeSpeedBump;
                 if (mHighPriorityBeforeSpeedBump) {
@@ -1769,16 +1767,14 @@
     }
 
     public static boolean isPinnedHeadsUp(View v) {
-        if (v instanceof ExpandableNotificationRow) {
-            ExpandableNotificationRow row = (ExpandableNotificationRow) v;
+        if (v instanceof ExpandableNotificationRow row) {
             return row.isHeadsUp() && row.isPinned();
         }
         return false;
     }
 
     private boolean isHeadsUp(View v) {
-        if (v instanceof ExpandableNotificationRow) {
-            ExpandableNotificationRow row = (ExpandableNotificationRow) v;
+        if (v instanceof ExpandableNotificationRow row) {
             return row.isHeadsUp();
         }
         return false;
@@ -1820,8 +1816,7 @@
 
             if ((bottom - top >= mMinInteractionHeight || !requireMinHeight)
                     && touchY >= top && touchY <= bottom && touchX >= left && touchX <= right) {
-                if (slidingChild instanceof ExpandableNotificationRow) {
-                    ExpandableNotificationRow row = (ExpandableNotificationRow) slidingChild;
+                if (slidingChild instanceof ExpandableNotificationRow row) {
                     NotificationEntry entry = row.getEntry();
                     if (!mIsExpanded && row.isHeadsUp() && row.isPinned()
                             && mTopHeadsUpEntry.getRow() != row
@@ -2364,8 +2359,7 @@
             float rowTranslation = child.getTranslationY();
             if (rowTranslation >= translationY) {
                 return child;
-            } else if (!ignoreChildren && child instanceof ExpandableNotificationRow) {
-                ExpandableNotificationRow row = (ExpandableNotificationRow) child;
+            } else if (!ignoreChildren && child instanceof ExpandableNotificationRow row) {
                 if (row.isSummaryWithChildren() && row.areChildrenExpanded()) {
                     List<ExpandableNotificationRow> notificationChildren =
                             row.getAttachedChildren();
@@ -2886,8 +2880,7 @@
     }
 
     private void focusNextViewIfFocused(View view) {
-        if (view instanceof ExpandableNotificationRow) {
-            ExpandableNotificationRow row = (ExpandableNotificationRow) view;
+        if (view instanceof ExpandableNotificationRow row) {
             if (row.shouldRefocusOnDismiss()) {
                 View nextView = row.getChildAfterViewWhenDismissed();
                 if (nextView == null) {
@@ -3035,8 +3028,7 @@
     }
 
     private int getIntrinsicHeight(View view) {
-        if (view instanceof ExpandableView) {
-            ExpandableView expandableView = (ExpandableView) view;
+        if (view instanceof ExpandableView expandableView) {
             return expandableView.getIntrinsicHeight();
         }
         return view.getHeight();
@@ -3126,8 +3118,7 @@
         generateAddAnimation(child, false /* fromMoreCard */);
         updateAnimationState(child);
         updateChronometerForChild(child);
-        if (child instanceof ExpandableNotificationRow) {
-            ExpandableNotificationRow row = (ExpandableNotificationRow) child;
+        if (child instanceof ExpandableNotificationRow row) {
             row.setDismissUsingRowTranslationX(mDismissUsingRowTranslationX);
 
         }
@@ -3196,8 +3187,7 @@
     }
 
     private void updateAnimationState(boolean running, View child) {
-        if (child instanceof ExpandableNotificationRow) {
-            ExpandableNotificationRow row = (ExpandableNotificationRow) child;
+        if (child instanceof ExpandableNotificationRow row) {
             row.setAnimationRunning(running);
         }
     }
@@ -3394,8 +3384,7 @@
             // we need to know the view after this one
             float removedTranslation = child.getTranslationY();
             boolean ignoreChildren = true;
-            if (child instanceof ExpandableNotificationRow) {
-                ExpandableNotificationRow row = (ExpandableNotificationRow) child;
+            if (child instanceof ExpandableNotificationRow row) {
                 if (row.isRemoved() && row.wasChildInGroupWhenRemoved()) {
                     removedTranslation = row.getTranslationWhenRemoved();
                     ignoreChildren = false;
@@ -3437,8 +3426,7 @@
     private void generatePositionChangeEvents() {
         for (ExpandableView child : mChildrenChangingPositions) {
             Integer duration = null;
-            if (child instanceof ExpandableNotificationRow) {
-                ExpandableNotificationRow row = (ExpandableNotificationRow) child;
+            if (child instanceof ExpandableNotificationRow row) {
                 if (row.getEntry().isMarkedForUserTriggeredMovement()) {
                     duration = StackStateAnimator.ANIMATION_DURATION_PRIORITY_CHANGE;
                     row.getEntry().markForUserTriggeredMovement(false);
@@ -4122,8 +4110,7 @@
     private void clearUserLockedViews() {
         for (int i = 0; i < getChildCount(); i++) {
             ExpandableView child = getChildAtIndex(i);
-            if (child instanceof ExpandableNotificationRow) {
-                ExpandableNotificationRow row = (ExpandableNotificationRow) child;
+            if (child instanceof ExpandableNotificationRow row) {
                 row.setUserLocked(false);
             }
         }
@@ -4137,8 +4124,7 @@
         );
         for (int i = 0; i < getChildCount(); i++) {
             ExpandableView child = getChildAtIndex(i);
-            if (child instanceof ExpandableNotificationRow) {
-                ExpandableNotificationRow row = (ExpandableNotificationRow) child;
+            if (child instanceof ExpandableNotificationRow row) {
                 clearTemporaryViewsInGroup(
                         /* viewGroup = */ row.getChildrenContainer(),
                         /* reason = */ "clearTemporaryViewsInGroup(row.getChildrenContainer())"
@@ -4223,8 +4209,7 @@
     }
 
     void updateChronometerForChild(View child) {
-        if (child instanceof ExpandableNotificationRow) {
-            ExpandableNotificationRow row = (ExpandableNotificationRow) child;
+        if (child instanceof ExpandableNotificationRow row) {
             row.setChronometerRunning(mIsExpanded);
         }
     }
@@ -4263,8 +4248,7 @@
     }
 
     private void updateScrollPositionOnExpandInBottom(ExpandableView view) {
-        if (view instanceof ExpandableNotificationRow && !onKeyguard()) {
-            ExpandableNotificationRow row = (ExpandableNotificationRow) view;
+        if (view instanceof ExpandableNotificationRow row && !onKeyguard()) {
             // TODO: once we're recycling this will need to check the adapter position of the child
             if (row.isUserLocked() && row != getFirstChildNotGone()) {
                 if (row.isSummaryWithChildren()) {
@@ -4323,8 +4307,7 @@
     private void clearHeadsUpDisappearRunning() {
         for (int i = 0; i < getChildCount(); i++) {
             View view = getChildAt(i);
-            if (view instanceof ExpandableNotificationRow) {
-                ExpandableNotificationRow row = (ExpandableNotificationRow) view;
+            if (view instanceof ExpandableNotificationRow row) {
                 row.setHeadsUpAnimatingAway(false);
                 if (row.isSummaryWithChildren()) {
                     for (ExpandableNotificationRow child : row.getAttachedChildren()) {
@@ -5208,8 +5191,7 @@
                     }
                     View swipedView = mSwipeHelper.getSwipedView();
                     pw.println("Swiped view: " + swipedView);
-                    if (swipedView instanceof ExpandableView) {
-                        ExpandableView expandableView = (ExpandableView) swipedView;
+                    if (swipedView instanceof ExpandableView expandableView) {
                         expandableView.dump(pw, args);
                     }
                 });
@@ -5292,8 +5274,7 @@
         if (view instanceof SectionHeaderView && silentSectionWillBeGone) {
             return true;
         }
-        if (view instanceof ExpandableNotificationRow) {
-            ExpandableNotificationRow row = (ExpandableNotificationRow) view;
+        if (view instanceof ExpandableNotificationRow row) {
             if (isVisible(row) && includeChildInClearAll(row, selection)) {
                 return true;
             }
@@ -5319,9 +5300,7 @@
             if (shouldHideParent(view, selection)) {
                 viewsToHide.add(view);
             }
-            if (view instanceof ExpandableNotificationRow) {
-                ExpandableNotificationRow parent = (ExpandableNotificationRow) view;
-
+            if (view instanceof ExpandableNotificationRow parent) {
                 if (isChildrenVisible(parent)) {
                     for (ExpandableNotificationRow child : parent.getAttachedChildren()) {
                         if (isVisible(child) && includeChildInClearAll(child, selection)) {
@@ -5341,10 +5320,9 @@
 
         for (int i = 0; i < childCount; i++) {
             final View view = getChildAt(i);
-            if (!(view instanceof ExpandableNotificationRow)) {
+            if (!(view instanceof ExpandableNotificationRow parent)) {
                 continue;
             }
-            ExpandableNotificationRow parent = (ExpandableNotificationRow) view;
             if (includeChildInClearAll(parent, selection)) {
                 viewsToRemove.add(parent);
             }
@@ -5983,8 +5961,7 @@
         for (int i = 0; i < getChildCount(); i++) {
             View child = getChildAt(i);
             mSwipeHelper.forceResetSwipeState(child);
-            if (child instanceof ExpandableNotificationRow) {
-                ExpandableNotificationRow childRow = (ExpandableNotificationRow) child;
+            if (child instanceof ExpandableNotificationRow childRow) {
                 List<ExpandableNotificationRow> grandchildren = childRow.getAttachedChildren();
                 if (grandchildren != null) {
                     for (ExpandableNotificationRow grandchild : grandchildren) {
@@ -6270,8 +6247,7 @@
     }
 
     static boolean canChildBeDismissed(View v) {
-        if (v instanceof ExpandableNotificationRow) {
-            ExpandableNotificationRow row = (ExpandableNotificationRow) v;
+        if (v instanceof ExpandableNotificationRow row) {
             if (row.areGutsExposed() || !row.getEntry().hasFinishedInitialization()) {
                 return false;
             }
@@ -6281,8 +6257,7 @@
     }
 
     static boolean canChildBeCleared(View v) {
-        if (v instanceof ExpandableNotificationRow) {
-            ExpandableNotificationRow row = (ExpandableNotificationRow) v;
+        if (v instanceof ExpandableNotificationRow row) {
             if (row.areGutsExposed() || !row.getEntry().hasFinishedInitialization()) {
                 return false;
             }
@@ -6377,8 +6352,7 @@
         /* Only ever called as a consequence of an expansion gesture in the shade. */
         @Override
         public void setUserExpandedChild(View v, boolean userExpanded) {
-            if (v instanceof ExpandableNotificationRow) {
-                ExpandableNotificationRow row = (ExpandableNotificationRow) v;
+            if (v instanceof ExpandableNotificationRow row) {
                 if (userExpanded && onKeyguard()) {
                     // Due to a race when locking the screen while touching, a notification may be
                     // expanded even after we went back to keyguard. An example of this happens if
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
index 7c7d943..6f5058c 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/NotificationStackScrollLayoutController.java
@@ -405,8 +405,7 @@
             if (!mAllowLongPress) {
                 return;
             }
-            if (view instanceof ExpandableNotificationRow) {
-                ExpandableNotificationRow row = (ExpandableNotificationRow) view;
+            if (view instanceof ExpandableNotificationRow row) {
                 mMetricsLogger.write(row.getEntry().getSbn().getLogMaker()
                         .setCategory(MetricsEvent.ACTION_TOUCH_GEAR)
                         .setType(MetricsEvent.TYPE_ACTION)
@@ -426,8 +425,7 @@
 
         @Override
         public void onMenuShown(View row) {
-            if (row instanceof ExpandableNotificationRow) {
-                ExpandableNotificationRow notificationRow = (ExpandableNotificationRow) row;
+            if (row instanceof ExpandableNotificationRow notificationRow) {
                 mMetricsLogger.write(notificationRow.getEntry().getSbn().getLogMaker()
                         .setCategory(MetricsEvent.ACTION_REVEAL_GEAR)
                         .setType(MetricsEvent.TYPE_ACTION));
@@ -492,10 +490,9 @@
                  */
                 @Override
                 public void onChildDismissed(View view) {
-                    if (!(view instanceof ActivatableNotificationView)) {
+                    if (!(view instanceof ActivatableNotificationView row)) {
                         return;
                     }
-                    ActivatableNotificationView row = (ActivatableNotificationView) view;
                     if (!row.isDismissed()) {
                         handleChildViewDismissed(view);
                     }
@@ -519,8 +516,7 @@
                     if (mView.getClearAllInProgress()) {
                         return;
                     }
-                    if (view instanceof ExpandableNotificationRow) {
-                        ExpandableNotificationRow row = (ExpandableNotificationRow) view;
+                    if (view instanceof ExpandableNotificationRow row) {
                         if (row.isHeadsUp()) {
                             mHeadsUpManager.addSwipedOutNotification(
                                     row.getEntry().getSbn().getKey());
@@ -551,8 +547,7 @@
                             ev.getY(),
                             true /* requireMinHeight */,
                             false /* ignoreDecors */);
-                    if (child instanceof ExpandableNotificationRow) {
-                        ExpandableNotificationRow row = (ExpandableNotificationRow) child;
+                    if (child instanceof ExpandableNotificationRow row) {
                         ExpandableNotificationRow parent = row.getNotificationParent();
                         if (parent != null && parent.areChildrenExpanded()
                                 && (parent.areGutsExposed()
@@ -582,8 +577,7 @@
                 @Override
                 public void onChildSnappedBack(View animView, float targetLeft) {
                     mView.onSwipeEnd();
-                    if (animView instanceof ExpandableNotificationRow) {
-                        ExpandableNotificationRow row = (ExpandableNotificationRow) animView;
+                    if (animView instanceof ExpandableNotificationRow row) {
                         if (row.isPinned() && !canChildBeDismissed(row)
                                 && row.getEntry().getSbn().getNotification().fullScreenIntent
                                 == null) {
@@ -1980,8 +1974,7 @@
 
             // Check if we need to clear any snooze leavebehinds
             if (guts != null && !NotificationSwipeHelper.isTouchInView(ev, guts)
-                    && guts.getGutsContent() instanceof NotificationSnooze) {
-                NotificationSnooze ns = (NotificationSnooze) guts.getGutsContent();
+                    && guts.getGutsContent() instanceof NotificationSnooze ns) {
                 if ((ns.isExpanded() && isCancelOrUp)
                         || (!horizontalSwipeWantsIt && scrollerWantsIt)) {
                     // If the leavebehind is expanded we clear it on the next up event, otherwise we
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java
index c4e6b909..664a6b6 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/notification/stack/StackScrollAlgorithm.java
@@ -254,8 +254,7 @@
 
     public static void logView(View view, String s) {
         String viewString = "";
-        if (view instanceof ExpandableNotificationRow) {
-            ExpandableNotificationRow row = ((ExpandableNotificationRow) view);
+        if (view instanceof ExpandableNotificationRow row) {
             if (row.getEntry() == null) {
                 viewString = "ExpandableNotificationRow has null NotificationEntry";
             } else {
@@ -289,8 +288,7 @@
         int childCount = algorithmState.visibleChildren.size();
         for (int i = 0; i < childCount; i++) {
             ExpandableView v = algorithmState.visibleChildren.get(i);
-            if (v instanceof ExpandableNotificationRow) {
-                ExpandableNotificationRow row = (ExpandableNotificationRow) v;
+            if (v instanceof ExpandableNotificationRow row) {
                 row.updateChildrenStates();
             }
         }
@@ -401,8 +399,7 @@
                     continue;
                 }
                 notGoneIndex = updateNotGoneIndex(state, notGoneIndex, v);
-                if (v instanceof ExpandableNotificationRow) {
-                    ExpandableNotificationRow row = (ExpandableNotificationRow) v;
+                if (v instanceof ExpandableNotificationRow row) {
 
                     // handle the notGoneIndex for the children as well
                     List<ExpandableNotificationRow> children = row.getAttachedChildren();
@@ -533,10 +530,9 @@
     private boolean hasNonClearableNotifs(StackScrollAlgorithmState algorithmState) {
         for (int i = 0; i < algorithmState.visibleChildren.size(); i++) {
             View child = algorithmState.visibleChildren.get(i);
-            if (!(child instanceof ExpandableNotificationRow)) {
+            if (!(child instanceof ExpandableNotificationRow row)) {
                 continue;
             }
-            final ExpandableNotificationRow row = (ExpandableNotificationRow) child;
             if (!row.canViewBeCleared()) {
                 return true;
             }
@@ -740,10 +736,9 @@
         ExpandableNotificationRow pulsingRow = null;
         for (int i = 0; i < childCount; i++) {
             View child = algorithmState.visibleChildren.get(i);
-            if (!(child instanceof ExpandableNotificationRow)) {
+            if (!(child instanceof ExpandableNotificationRow row)) {
                 continue;
             }
-            ExpandableNotificationRow row = (ExpandableNotificationRow) child;
             if (!row.showingPulsing() || (i == 0 && ambientState.isPulseExpanding())) {
                 continue;
             }
@@ -785,10 +780,9 @@
         ExpandableNotificationRow topHeadsUpEntry = null;
         for (int i = 0; i < childCount; i++) {
             View child = algorithmState.visibleChildren.get(i);
-            if (!(child instanceof ExpandableNotificationRow)) {
+            if (!(child instanceof ExpandableNotificationRow row)) {
                 continue;
             }
-            ExpandableNotificationRow row = (ExpandableNotificationRow) child;
             if (!(row.isHeadsUp() || row.isHeadsUpAnimatingAway())) {
                 continue;
             }
@@ -940,8 +934,7 @@
     }
 
     protected int getMaxAllowedChildHeight(View child) {
-        if (child instanceof ExpandableView) {
-            ExpandableView expandableView = (ExpandableView) child;
+        if (child instanceof ExpandableView expandableView) {
             return expandableView.getIntrinsicHeight();
         }
         return child == null ? mCollapsedSize : child.getHeight();
diff --git a/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragment.java b/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragment.java
index 2740cc6..11456ff 100644
--- a/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragment.java
+++ b/packages/SystemUI/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragment.java
@@ -453,11 +453,11 @@
     public void initNotificationIconArea() {
         ViewGroup notificationIconArea = mStatusBar.requireViewById(R.id.notification_icon_area);
         if (NotificationIconContainerRefactor.isEnabled()) {
-            mNotificationIconAreaInner =
-                LayoutInflater.from(getContext())
-                        .inflate(R.layout.notification_icon_area, notificationIconArea, true);
+            LayoutInflater.from(getContext())
+                    .inflate(R.layout.notification_icon_area, notificationIconArea, true);
             NotificationIconContainer notificationIcons =
                     notificationIconArea.requireViewById(R.id.notificationIcons);
+            mNotificationIconAreaInner = notificationIcons;
             mNicBindingDisposable = mNicViewBinder.bindWhileAttached(notificationIcons);
         } else {
             mNotificationIconAreaInner =
diff --git a/packages/SystemUI/tests/src/com/android/keyguard/ClockEventControllerTest.kt b/packages/SystemUI/tests/src/com/android/keyguard/ClockEventControllerTest.kt
index 6f58bc2..e6637e6 100644
--- a/packages/SystemUI/tests/src/com/android/keyguard/ClockEventControllerTest.kt
+++ b/packages/SystemUI/tests/src/com/android/keyguard/ClockEventControllerTest.kt
@@ -30,13 +30,15 @@
 import com.android.systemui.keyguard.shared.model.KeyguardState
 import com.android.systemui.keyguard.shared.model.TransitionState
 import com.android.systemui.keyguard.shared.model.TransitionStep
-import com.android.systemui.log.LogBuffer
+import com.android.systemui.log.core.LogLevel
+import com.android.systemui.log.core.LogcatOnlyMessageBuffer
 import com.android.systemui.plugins.clocks.ClockAnimations
 import com.android.systemui.plugins.clocks.ClockController
 import com.android.systemui.plugins.clocks.ClockEvents
 import com.android.systemui.plugins.clocks.ClockFaceConfig
 import com.android.systemui.plugins.clocks.ClockFaceController
 import com.android.systemui.plugins.clocks.ClockFaceEvents
+import com.android.systemui.plugins.clocks.ClockMessageBuffers
 import com.android.systemui.plugins.clocks.ClockTickRate
 import com.android.systemui.statusbar.policy.BatteryController
 import com.android.systemui.statusbar.policy.ConfigurationController
@@ -94,9 +96,9 @@
     @Mock private lateinit var largeClockEvents: ClockFaceEvents
     @Mock private lateinit var parentView: View
     private lateinit var repository: FakeKeyguardRepository
-    @Mock private lateinit var smallLogBuffer: LogBuffer
-    @Mock private lateinit var largeLogBuffer: LogBuffer
     @Mock private lateinit var keyguardTransitionInteractor: KeyguardTransitionInteractor
+    private val messageBuffer = LogcatOnlyMessageBuffer(LogLevel.DEBUG)
+    private val clockBuffers = ClockMessageBuffers(messageBuffer, messageBuffer, messageBuffer)
     private lateinit var underTest: ClockEventController
     @Mock private lateinit var zenModeController: ZenModeController
 
@@ -140,8 +142,7 @@
                 context,
                 mainExecutor,
                 bgExecutor,
-                smallLogBuffer,
-                largeLogBuffer,
+                clockBuffers,
                 withDeps.featureFlags,
                 zenModeController
             )
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/data/repository/BiometricStatusRepositoryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/data/repository/BiometricStatusRepositoryTest.kt
index 27d93eb..8f0e910 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/data/repository/BiometricStatusRepositoryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/data/repository/BiometricStatusRepositoryTest.kt
@@ -24,7 +24,6 @@
 import android.hardware.biometrics.BiometricRequestConstants.REASON_AUTH_SETTINGS
 import android.hardware.biometrics.BiometricRequestConstants.REASON_ENROLL_ENROLLING
 import android.hardware.biometrics.BiometricRequestConstants.REASON_ENROLL_FIND_SENSOR
-import android.platform.test.annotations.RequiresFlagsEnabled
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.biometrics.shared.model.AuthenticationReason
@@ -48,7 +47,6 @@
 import org.mockito.junit.MockitoJUnit
 import org.mockito.junit.MockitoRule
 
-@RequiresFlagsEnabled(FLAG_SIDEFPS_CONTROLLER_REFACTOR)
 @OptIn(ExperimentalCoroutinesApi::class)
 @SmallTest
 @RunWith(JUnit4::class)
@@ -62,6 +60,7 @@
 
     @Before
     fun setUp() {
+        mSetFlagsRule.enableFlags(FLAG_SIDEFPS_CONTROLLER_REFACTOR)
         underTest = BiometricStatusRepositoryImpl(testScope.backgroundScope, biometricManager)
     }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/BiometricStatusInteractorImplTest.kt b/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/BiometricStatusInteractorImplTest.kt
index 6978923..d7b7d79 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/BiometricStatusInteractorImplTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/biometrics/domain/interactor/BiometricStatusInteractorImplTest.kt
@@ -19,7 +19,6 @@
 import android.app.ActivityManager
 import android.app.ActivityTaskManager
 import android.content.ComponentName
-import android.platform.test.annotations.RequiresFlagsEnabled
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.biometrics.data.repository.FakeBiometricStatusRepository
@@ -44,7 +43,6 @@
 import org.mockito.junit.MockitoJUnit
 import org.mockito.junit.MockitoRule
 
-@RequiresFlagsEnabled(FLAG_SIDEFPS_CONTROLLER_REFACTOR)
 @OptIn(ExperimentalCoroutinesApi::class)
 @SmallTest
 @RunWith(JUnit4::class)
@@ -59,6 +57,7 @@
 
     @Before
     fun setup() {
+        mSetFlagsRule.enableFlags(FLAG_SIDEFPS_CONTROLLER_REFACTOR)
         biometricStatusRepository = FakeBiometricStatusRepository()
         underTest = BiometricStatusInteractorImpl(activityTaskManager, biometricStatusRepository)
     }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/DeviceEntrySideFpsOverlayInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/DeviceEntrySideFpsOverlayInteractorTest.kt
index 70d3f81..0616a34 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/DeviceEntrySideFpsOverlayInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/domain/interactor/DeviceEntrySideFpsOverlayInteractorTest.kt
@@ -17,7 +17,6 @@
 package com.android.systemui.keyguard.domain.interactor
 
 import android.os.Handler
-import android.platform.test.annotations.RequiresFlagsEnabled
 import androidx.test.filters.SmallTest
 import com.android.keyguard.KeyguardSecurityModel
 import com.android.keyguard.KeyguardUpdateMonitor
@@ -58,7 +57,6 @@
 import org.mockito.junit.MockitoRule
 
 @OptIn(ExperimentalCoroutinesApi::class)
-@RequiresFlagsEnabled(FLAG_SIDEFPS_CONTROLLER_REFACTOR)
 @SmallTest
 @RunWith(JUnit4::class)
 class DeviceEntrySideFpsOverlayInteractorTest : SysuiTestCase() {
@@ -80,6 +78,7 @@
 
     @Before
     fun setup() {
+        mSetFlagsRule.enableFlags(FLAG_SIDEFPS_CONTROLLER_REFACTOR)
         primaryBouncerInteractor =
             PrimaryBouncerInteractor(
                 bouncerRepository,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/sections/ClockSectionTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/sections/ClockSectionTest.kt
index e89b61f..dc0d9c5 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/sections/ClockSectionTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/sections/ClockSectionTest.kt
@@ -20,7 +20,6 @@
 import androidx.constraintlayout.widget.ConstraintSet
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
-import com.android.systemui.flags.FakeFeatureFlagsClassic
 import com.android.systemui.keyguard.domain.interactor.KeyguardClockInteractor
 import com.android.systemui.keyguard.ui.viewmodel.KeyguardClockViewModel
 import com.android.systemui.keyguard.ui.viewmodel.KeyguardSmartspaceViewModel
@@ -43,7 +42,6 @@
     @Mock private lateinit var keyguardClockViewModel: KeyguardClockViewModel
     @Mock private lateinit var smartspaceViewModel: KeyguardSmartspaceViewModel
     @Mock private lateinit var splitShadeStateController: SplitShadeStateController
-    private var featureFlags: FakeFeatureFlagsClassic = FakeFeatureFlagsClassic()
 
     private lateinit var underTest: ClockSection
 
@@ -88,7 +86,6 @@
                 smartspaceViewModel,
                 mContext,
                 splitShadeStateController,
-                featureFlags
             )
     }
 
@@ -147,24 +144,6 @@
         assetSmallClockTop(cs, expectedSmallClockTopMargin)
     }
 
-    @Test
-    fun testLargeClockShouldBeCentered() {
-        underTest.setClockShouldBeCentered(true)
-        val cs = ConstraintSet()
-        underTest.applyDefaultConstraints(cs)
-        val constraint = cs.getConstraint(R.id.lockscreen_clock_view_large)
-        assertThat(constraint.layout.endToEnd).isEqualTo(ConstraintSet.PARENT_ID)
-    }
-
-    @Test
-    fun testLargeClockShouldNotBeCentered() {
-        underTest.setClockShouldBeCentered(false)
-        val cs = ConstraintSet()
-        underTest.applyDefaultConstraints(cs)
-        val constraint = cs.getConstraint(R.id.lockscreen_clock_view_large)
-        assertThat(constraint.layout.endToEnd).isEqualTo(R.id.split_shade_guideline)
-    }
-
     private fun setLargeClock(useLargeClock: Boolean) {
         whenever(keyguardClockViewModel.useLargeClock).thenReturn(useLargeClock)
     }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/sections/SmartspaceSectionTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/sections/SmartspaceSectionTest.kt
index bff27f6..740110b4 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/sections/SmartspaceSectionTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/view/layout/sections/SmartspaceSectionTest.kt
@@ -69,14 +69,13 @@
                 keyguardUnlockAnimationController,
             )
         constraintLayout = ConstraintLayout(mContext)
-        whenever(lockscreenSmartspaceController.buildAndConnectView(constraintLayout))
-            .thenReturn(smartspaceView)
-        whenever(lockscreenSmartspaceController.buildAndConnectWeatherView(constraintLayout))
-            .thenReturn(weatherView)
-        whenever(lockscreenSmartspaceController.buildAndConnectDateView(constraintLayout))
-            .thenReturn(dateView)
+        whenever(keyguardSmartspaceViewModel.smartspaceView).thenReturn(smartspaceView)
+        whenever(keyguardSmartspaceViewModel.weatherView).thenReturn(weatherView)
+        whenever(keyguardSmartspaceViewModel.dateView).thenReturn(dateView)
         whenever(keyguardClockViewModel.hasCustomWeatherDataDisplay)
             .thenReturn(hasCustomWeatherDataDisplay)
+        whenever(keyguardSmartspaceViewModel.smartspaceController)
+            .thenReturn(lockscreenSmartspaceController)
         constraintSet = ConstraintSet()
     }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelTest.kt
index 459a74c..ee1be10 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/keyguard/ui/viewmodel/KeyguardRootViewModelTest.kt
@@ -24,7 +24,6 @@
 import com.android.systemui.Flags as AConfigFlags
 import com.android.systemui.Flags.FLAG_NEW_AOD_TRANSITION
 import com.android.systemui.SysuiTestCase
-import com.android.systemui.collectLastValue
 import com.android.systemui.common.ui.data.repository.fakeConfigurationRepository
 import com.android.systemui.common.ui.domain.interactor.configurationInteractor
 import com.android.systemui.coroutines.collectLastValue
diff --git a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/RecordIssueTileTest.kt b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/RecordIssueTileTest.kt
index 9b61447..c7479fd5 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/RecordIssueTileTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/qs/tiles/RecordIssueTileTest.kt
@@ -30,8 +30,8 @@
 import com.android.systemui.qs.QSHost
 import com.android.systemui.qs.QsEventLogger
 import com.android.systemui.qs.logging.QSLogger
+import com.android.systemui.recordissue.RecordIssueDialogDelegate
 import com.android.systemui.res.R
-import com.android.systemui.settings.UserContextProvider
 import com.android.systemui.statusbar.phone.KeyguardDismissUtil
 import com.android.systemui.statusbar.phone.SystemUIDialog
 import com.android.systemui.statusbar.policy.KeyguardStateController
@@ -65,9 +65,9 @@
     @Mock private lateinit var keyguardDismissUtil: KeyguardDismissUtil
     @Mock private lateinit var keyguardStateController: KeyguardStateController
     @Mock private lateinit var dialogLauncherAnimator: DialogLaunchAnimator
-    @Mock private lateinit var dialogFactory: SystemUIDialog.Factory
+    @Mock private lateinit var delegateFactory: RecordIssueDialogDelegate.Factory
+    @Mock private lateinit var dialogDelegate: RecordIssueDialogDelegate
     @Mock private lateinit var dialog: SystemUIDialog
-    @Mock private lateinit var userContextProvider: UserContextProvider
 
     private lateinit var testableLooper: TestableLooper
     private lateinit var tile: RecordIssueTile
@@ -76,7 +76,8 @@
     fun setUp() {
         MockitoAnnotations.initMocks(this)
         whenever(host.context).thenReturn(mContext)
-        whenever(dialogFactory.create(any())).thenReturn(dialog)
+        whenever(delegateFactory.create(any())).thenReturn(dialogDelegate)
+        whenever(dialogDelegate.createDialog()).thenReturn(dialog)
 
         testableLooper = TestableLooper.get(this)
         tile =
@@ -93,8 +94,7 @@
                 keyguardDismissUtil,
                 keyguardStateController,
                 dialogLauncherAnimator,
-                dialogFactory,
-                userContextProvider,
+                delegateFactory,
             )
     }
 
diff --git a/packages/SystemUI/tests/src/com/android/systemui/recordissue/RecordIssueDialogDelegateTest.kt b/packages/SystemUI/tests/src/com/android/systemui/recordissue/RecordIssueDialogDelegateTest.kt
index c5d3524..7ce51ae 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/recordissue/RecordIssueDialogDelegateTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/recordissue/RecordIssueDialogDelegateTest.kt
@@ -17,6 +17,9 @@
 package com.android.systemui.recordissue
 
 import android.app.Dialog
+import android.content.Context
+import android.content.SharedPreferences
+import android.os.UserHandle
 import android.testing.AndroidTestingRunner
 import android.testing.TestableLooper
 import android.widget.Button
@@ -25,48 +28,107 @@
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.animation.DialogLaunchAnimator
 import com.android.systemui.broadcast.BroadcastDispatcher
-import com.android.systemui.flags.FeatureFlags
+import com.android.systemui.flags.FeatureFlagsClassic
+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.model.SysUiState
+import com.android.systemui.qs.tiles.RecordIssueTile
 import com.android.systemui.res.R
+import com.android.systemui.settings.UserContextProvider
+import com.android.systemui.settings.UserFileManager
+import com.android.systemui.settings.UserTracker
 import com.android.systemui.statusbar.phone.SystemUIDialog
 import com.android.systemui.statusbar.phone.SystemUIDialogManager
-import com.android.systemui.util.mockito.mock
+import com.android.systemui.util.mockito.any
+import com.android.systemui.util.mockito.eq
 import com.android.systemui.util.mockito.whenever
 import com.google.common.truth.Truth.assertThat
 import java.util.concurrent.CountDownLatch
+import java.util.concurrent.Executor
 import java.util.concurrent.TimeUnit
 import org.junit.After
 import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
+import org.mockito.ArgumentCaptor
 import org.mockito.ArgumentMatchers.anyBoolean
 import org.mockito.ArgumentMatchers.anyInt
+import org.mockito.Mock
+import org.mockito.Mockito.never
+import org.mockito.Mockito.spy
+import org.mockito.Mockito.verify
+import org.mockito.MockitoAnnotations
 
 @SmallTest
 @RunWith(AndroidTestingRunner::class)
 @TestableLooper.RunWithLooper(setAsMainLooper = true)
 class RecordIssueDialogDelegateTest : SysuiTestCase() {
 
+    @Mock private lateinit var flags: FeatureFlagsClassic
+    @Mock private lateinit var devicePolicyResolver: ScreenCaptureDevicePolicyResolver
+    @Mock private lateinit var dprLazy: dagger.Lazy<ScreenCaptureDevicePolicyResolver>
+    @Mock private lateinit var mediaProjectionMetricsLogger: MediaProjectionMetricsLogger
+    @Mock private lateinit var userContextProvider: UserContextProvider
+    @Mock private lateinit var userTracker: UserTracker
+    @Mock private lateinit var userFileManager: UserFileManager
+    @Mock private lateinit var sharedPreferences: SharedPreferences
+
+    @Mock private lateinit var sysuiState: SysUiState
+    @Mock private lateinit var systemUIDialogManager: SystemUIDialogManager
+    @Mock private lateinit var broadcastDispatcher: BroadcastDispatcher
+    @Mock private lateinit var bgExecutor: Executor
+    @Mock private lateinit var mainExecutor: Executor
+    @Mock private lateinit var dialogLaunchAnimator: DialogLaunchAnimator
+
     private lateinit var dialog: SystemUIDialog
+    private lateinit var factory: SystemUIDialog.Factory
     private lateinit var latch: CountDownLatch
 
     @Before
     fun setup() {
-        val dialogFactory =
-            SystemUIDialog.Factory(
-                context,
-                mock<FeatureFlags>(),
-                mock<SystemUIDialogManager>(),
-                mock<SysUiState>().apply {
-                    whenever(setFlag(anyInt(), anyBoolean())).thenReturn(this)
-                },
-                mock<BroadcastDispatcher>(),
-                mock<DialogLaunchAnimator>()
+        MockitoAnnotations.initMocks(this)
+        whenever(dprLazy.get()).thenReturn(devicePolicyResolver)
+        whenever(sysuiState.setFlag(anyInt(), anyBoolean())).thenReturn(sysuiState)
+        whenever(userContextProvider.userContext).thenReturn(mContext)
+        whenever(
+                userFileManager.getSharedPreferences(
+                    eq(RecordIssueTile.TILE_SPEC),
+                    eq(Context.MODE_PRIVATE),
+                    anyInt()
+                )
+            )
+            .thenReturn(sharedPreferences)
+
+        factory =
+            spy(
+                SystemUIDialog.Factory(
+                    context,
+                    flags,
+                    systemUIDialogManager,
+                    sysuiState,
+                    broadcastDispatcher,
+                    dialogLaunchAnimator
+                )
             )
 
         latch = CountDownLatch(1)
         dialog =
-            RecordIssueDialogDelegate(dialogFactory, mock()) { latch.countDown() }.createDialog()
+            RecordIssueDialogDelegate(
+                    factory,
+                    userContextProvider,
+                    userTracker,
+                    flags,
+                    bgExecutor,
+                    mainExecutor,
+                    dprLazy,
+                    mediaProjectionMetricsLogger,
+                    userFileManager,
+                ) {
+                    latch.countDown()
+                }
+                .createDialog()
         dialog.show()
     }
 
@@ -91,4 +153,82 @@
         dialog.getButton(Dialog.BUTTON_POSITIVE).callOnClick()
         latch.await(1L, TimeUnit.MILLISECONDS)
     }
+
+    @Test
+    fun screenCaptureDisabledDialog_isShown_whenFunctionalityIsDisabled() {
+        whenever(flags.isEnabled(Flags.WM_ENABLE_PARTIAL_SCREEN_SHARING_ENTERPRISE_POLICIES))
+            .thenReturn(true)
+        whenever(devicePolicyResolver.isScreenCaptureCompletelyDisabled(any<UserHandle>()))
+            .thenReturn(true)
+
+        val screenRecordSwitch = dialog.requireViewById<Switch>(R.id.screenrecord_switch)
+        screenRecordSwitch.isChecked = true
+
+        val bgCaptor = ArgumentCaptor.forClass(Runnable::class.java)
+        verify(bgExecutor).execute(bgCaptor.capture())
+        bgCaptor.value.run()
+
+        val mainCaptor = ArgumentCaptor.forClass(Runnable::class.java)
+        verify(mainExecutor).execute(mainCaptor.capture())
+        mainCaptor.value.run()
+
+        verify(mediaProjectionMetricsLogger, never())
+            .notifyProjectionInitiated(
+                anyInt(),
+                eq(SessionCreationSource.SYSTEM_UI_SCREEN_RECORDER)
+            )
+        assertThat(screenRecordSwitch.isChecked).isFalse()
+    }
+
+    @Test
+    fun screenCapturePermissionDialog_isShown_correctly() {
+        whenever(flags.isEnabled(Flags.WM_ENABLE_PARTIAL_SCREEN_SHARING_ENTERPRISE_POLICIES))
+            .thenReturn(false)
+        whenever(devicePolicyResolver.isScreenCaptureCompletelyDisabled(any<UserHandle>()))
+            .thenReturn(false)
+        whenever(flags.isEnabled(Flags.WM_ENABLE_PARTIAL_SCREEN_SHARING)).thenReturn(true)
+        whenever(sharedPreferences.getBoolean(HAS_APPROVED_SCREEN_RECORDING, false))
+            .thenReturn(false)
+
+        val screenRecordSwitch = dialog.requireViewById<Switch>(R.id.screenrecord_switch)
+        screenRecordSwitch.isChecked = true
+
+        val bgCaptor = ArgumentCaptor.forClass(Runnable::class.java)
+        verify(bgExecutor).execute(bgCaptor.capture())
+        bgCaptor.value.run()
+
+        val mainCaptor = ArgumentCaptor.forClass(Runnable::class.java)
+        verify(mainExecutor).execute(mainCaptor.capture())
+        mainCaptor.value.run()
+
+        verify(mediaProjectionMetricsLogger)
+            .notifyProjectionInitiated(
+                anyInt(),
+                eq(SessionCreationSource.SYSTEM_UI_SCREEN_RECORDER)
+            )
+        verify(factory).create(any<ScreenCapturePermissionDialogDelegate>())
+    }
+
+    @Test
+    fun noDialogsAreShown_forScreenRecord_whenApprovalIsAlreadyGiven() {
+        whenever(flags.isEnabled(Flags.WM_ENABLE_PARTIAL_SCREEN_SHARING_ENTERPRISE_POLICIES))
+            .thenReturn(false)
+        whenever(devicePolicyResolver.isScreenCaptureCompletelyDisabled(any<UserHandle>()))
+            .thenReturn(false)
+        whenever(flags.isEnabled(Flags.WM_ENABLE_PARTIAL_SCREEN_SHARING)).thenReturn(false)
+
+        val screenRecordSwitch = dialog.requireViewById<Switch>(R.id.screenrecord_switch)
+        screenRecordSwitch.isChecked = true
+
+        val bgCaptor = ArgumentCaptor.forClass(Runnable::class.java)
+        verify(bgExecutor).execute(bgCaptor.capture())
+        bgCaptor.value.run()
+
+        verify(mediaProjectionMetricsLogger)
+            .notifyProjectionInitiated(
+                anyInt(),
+                eq(SessionCreationSource.SYSTEM_UI_SCREEN_RECORDER)
+            )
+        verify(factory, never()).create(any<ScreenCapturePermissionDialogDelegate>())
+    }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shared/clocks/ClockRegistryTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shared/clocks/ClockRegistryTest.kt
index ee94cbb..ee27c5c 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shared/clocks/ClockRegistryTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shared/clocks/ClockRegistryTest.kt
@@ -26,6 +26,7 @@
 import com.android.systemui.flags.Flags.TRANSIT_CLOCK
 import com.android.systemui.plugins.clocks.ClockController
 import com.android.systemui.plugins.clocks.ClockId
+import com.android.systemui.plugins.clocks.ClockMessageBuffers
 import com.android.systemui.plugins.clocks.ClockMetadata
 import com.android.systemui.plugins.clocks.ClockProviderPlugin
 import com.android.systemui.plugins.clocks.ClockSettings
@@ -128,6 +129,7 @@
         override fun createClock(settings: ClockSettings): ClockController =
             createCallbacks[settings.clockId!!]!!(settings.clockId!!)
         override fun getClockThumbnail(id: ClockId): Drawable? = thumbnailCallbacks[id]!!(id)
+        override fun initialize(buffers: ClockMessageBuffers?) { }
 
         fun addClock(
             id: ClockId,
diff --git a/packages/SystemUI/tests/src/com/android/systemui/shared/clocks/DefaultClockProviderTest.kt b/packages/SystemUI/tests/src/com/android/systemui/shared/clocks/DefaultClockProviderTest.kt
index fef262f..e0e8d1f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/shared/clocks/DefaultClockProviderTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/shared/clocks/DefaultClockProviderTest.kt
@@ -26,6 +26,7 @@
 import androidx.test.filters.SmallTest
 import com.android.systemui.SysuiTestCase
 import com.android.systemui.customization.R
+import com.android.systemui.plugins.clocks.ClockId
 import com.android.systemui.plugins.clocks.ClockSettings
 import com.android.systemui.shared.clocks.DefaultClockController.Companion.DOZE_COLOR
 import com.android.systemui.util.mockito.any
@@ -49,6 +50,9 @@
 import org.mockito.Mockito.`when` as whenever
 import org.mockito.junit.MockitoJUnit
 
+private fun DefaultClockProvider.createClock(id: ClockId): DefaultClockController =
+    createClock(ClockSettings(id, null)) as DefaultClockController
+
 @RunWith(AndroidTestingRunner::class)
 @SmallTest
 class DefaultClockProviderTest : SysuiTestCase() {
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/icon/domain/interactor/NotificationIconsInteractorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/icon/domain/interactor/NotificationIconsInteractorTest.kt
index 360a373..47feccf 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/icon/domain/interactor/NotificationIconsInteractorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/icon/domain/interactor/NotificationIconsInteractorTest.kt
@@ -30,6 +30,7 @@
 import com.android.systemui.statusbar.notification.data.repository.ActiveNotificationListRepository
 import com.android.systemui.statusbar.notification.data.repository.ActiveNotificationsStore
 import com.android.systemui.statusbar.notification.data.repository.FakeNotificationsKeyguardViewStateRepository
+import com.android.systemui.statusbar.notification.domain.interactor.HeadsUpNotificationIconInteractor
 import com.android.systemui.statusbar.notification.shared.byIsAmbient
 import com.android.systemui.statusbar.notification.shared.byIsLastMessageFromReply
 import com.android.systemui.statusbar.notification.shared.byIsPulsing
@@ -264,6 +265,7 @@
     interface TestComponent : SysUITestComponent<StatusBarNotificationIconsInteractor> {
 
         val activeNotificationListRepository: ActiveNotificationListRepository
+        val headsUpIconsInteractor: HeadsUpNotificationIconInteractor
         val keyguardViewStateRepository: FakeNotificationsKeyguardViewStateRepository
         val notificationListenerSettingsRepository: NotificationListenerSettingsRepository
 
@@ -336,6 +338,14 @@
                 .comparingElementsUsing(byIsLastMessageFromReply)
                 .doesNotContain(true)
         }
+
+    @Test
+    fun filteredEntrySet_includesIsolatedIcon() =
+        testComponent.runTest {
+            val filteredSet by collectLastValue(underTest.statusBarNotifs)
+            headsUpIconsInteractor.setIsolatedIconNotificationKey("notif5")
+            assertThat(filteredSet).comparingElementsUsing(byKey).contains("notif5")
+        }
 }
 
 private val testIcons =
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerStatusBarViewModelTest.kt b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerStatusBarViewModelTest.kt
index 349a35eb..c40401f 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerStatusBarViewModelTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/notification/icon/ui/viewmodel/NotificationIconContainerStatusBarViewModelTest.kt
@@ -399,4 +399,29 @@
 
             assertThat(isolatedIcon?.value?.notifKey).isEqualTo("notif1")
         }
+
+    @Test
+    fun isolatedIcon_lastMessageIsFromReply_notNull() =
+        testComponent.runTest {
+            val icon: Icon = mock()
+            headsUpViewStateRepository.isolatedNotification.value = "notif1"
+            activeNotificationsRepository.activeNotifications.value =
+                ActiveNotificationsStore.Builder()
+                    .apply {
+                        addIndividualNotif(
+                            activeNotificationModel(
+                                key = "notif1",
+                                groupKey = "group",
+                                statusBarIcon = icon,
+                                isLastMessageFromReply = true,
+                            )
+                        )
+                    }
+                    .build()
+
+            val isolatedIcon by collectLastValue(underTest.isolatedIcon)
+            runCurrent()
+
+            assertThat(isolatedIcon?.value?.notifKey).isEqualTo("notif1")
+        }
 }
diff --git a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentTest.java b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentTest.java
index 14751c2..54d3607 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/statusbar/phone/fragment/CollapsedStatusBarFragmentTest.java
@@ -36,6 +36,7 @@
 import android.provider.Settings;
 import android.testing.AndroidTestingRunner;
 import android.testing.TestableLooper.RunWithLooper;
+import android.view.LayoutInflater;
 import android.view.View;
 
 import androidx.test.filters.SmallTest;
@@ -91,7 +92,6 @@
 
     private NotificationIconAreaController mMockNotificationAreaController;
     private ShadeExpansionStateManager mShadeExpansionStateManager;
-    private View mNotificationAreaInner;
     private OngoingCallController mOngoingCallController;
     private SystemStatusAnimationScheduler mAnimationScheduler;
     private StatusBarLocationPublisher mLocationPublisher;
@@ -270,15 +270,15 @@
 
         fragment.disable(DEFAULT_DISPLAY, StatusBarManager.DISABLE_NOTIFICATION_ICONS, 0, false);
 
-        assertEquals(View.INVISIBLE, mNotificationAreaInner.getVisibility());
+        assertEquals(View.INVISIBLE, getNotificationAreaView().getVisibility());
 
         fragment.disable(DEFAULT_DISPLAY, 0, 0, false);
 
-        assertEquals(View.VISIBLE, mNotificationAreaInner.getVisibility());
+        assertEquals(View.VISIBLE, getNotificationAreaView().getVisibility());
 
         fragment.disable(DEFAULT_DISPLAY, StatusBarManager.DISABLE_NOTIFICATION_ICONS, 0, false);
 
-        assertEquals(View.INVISIBLE, mNotificationAreaInner.getVisibility());
+        assertEquals(View.INVISIBLE, getNotificationAreaView().getVisibility());
     }
 
     @Test
@@ -310,7 +310,7 @@
 
         // THEN all views are hidden
         assertEquals(View.INVISIBLE, getClockView().getVisibility());
-        assertEquals(View.INVISIBLE, mNotificationAreaInner.getVisibility());
+        assertEquals(View.INVISIBLE, getNotificationAreaView().getVisibility());
         assertEquals(View.INVISIBLE, getEndSideContentView().getVisibility());
     }
 
@@ -326,7 +326,7 @@
 
         // THEN all views are shown
         assertEquals(View.VISIBLE, getClockView().getVisibility());
-        assertEquals(View.VISIBLE, mNotificationAreaInner.getVisibility());
+        assertEquals(View.VISIBLE, getNotificationAreaView().getVisibility());
         assertEquals(View.VISIBLE, getEndSideContentView().getVisibility());
     }
 
@@ -343,7 +343,7 @@
 
         // THEN all views are hidden
         assertEquals(View.INVISIBLE, getClockView().getVisibility());
-        assertEquals(View.INVISIBLE, mNotificationAreaInner.getVisibility());
+        assertEquals(View.INVISIBLE, getNotificationAreaView().getVisibility());
         assertEquals(View.INVISIBLE, getEndSideContentView().getVisibility());
 
         // WHEN the shade is updated to no longer be open
@@ -354,7 +354,7 @@
 
         // THEN all views are shown
         assertEquals(View.VISIBLE, getClockView().getVisibility());
-        assertEquals(View.VISIBLE, mNotificationAreaInner.getVisibility());
+        assertEquals(View.VISIBLE, getNotificationAreaView().getVisibility());
         assertEquals(View.VISIBLE, getEndSideContentView().getVisibility());
     }
 
@@ -368,7 +368,7 @@
 
         // THEN all views are shown
         assertEquals(View.VISIBLE, getClockView().getVisibility());
-        assertEquals(View.VISIBLE, mNotificationAreaInner.getVisibility());
+        assertEquals(View.VISIBLE, getNotificationAreaView().getVisibility());
         assertEquals(View.VISIBLE, getEndSideContentView().getVisibility());
     }
 
@@ -382,7 +382,7 @@
 
         // THEN all views are hidden
         assertEquals(View.GONE, getClockView().getVisibility());
-        assertEquals(View.INVISIBLE, mNotificationAreaInner.getVisibility());
+        assertEquals(View.INVISIBLE, getNotificationAreaView().getVisibility());
         assertEquals(View.INVISIBLE, getEndSideContentView().getVisibility());
     }
 
@@ -396,7 +396,7 @@
 
         // THEN all views are hidden
         assertEquals(View.GONE, getClockView().getVisibility());
-        assertEquals(View.INVISIBLE, mNotificationAreaInner.getVisibility());
+        assertEquals(View.INVISIBLE, getNotificationAreaView().getVisibility());
         assertEquals(View.INVISIBLE, getEndSideContentView().getVisibility());
 
         // WHEN the transition has finished
@@ -405,7 +405,7 @@
 
         // THEN all views are shown
         assertEquals(View.VISIBLE, getClockView().getVisibility());
-        assertEquals(View.VISIBLE, mNotificationAreaInner.getVisibility());
+        assertEquals(View.VISIBLE, getNotificationAreaView().getVisibility());
         assertEquals(View.VISIBLE, getEndSideContentView().getVisibility());
     }
 
@@ -438,7 +438,7 @@
 
         assertEquals(View.VISIBLE,
                 mFragment.getView().findViewById(R.id.ongoing_call_chip).getVisibility());
-        assertEquals(View.INVISIBLE, mNotificationAreaInner.getVisibility());
+        assertEquals(View.INVISIBLE, getNotificationAreaView().getVisibility());
     }
 
     @Test
@@ -503,8 +503,8 @@
         fragment.disable(DEFAULT_DISPLAY, 0, 0, true);
 
         // Notification area is hidden without delay
-        assertEquals(0f, mNotificationAreaInner.getAlpha(), 0.01);
-        assertEquals(View.INVISIBLE, mNotificationAreaInner.getVisibility());
+        assertEquals(0f, getNotificationAreaView().getAlpha(), 0.01);
+        assertEquals(View.INVISIBLE, getNotificationAreaView().getVisibility());
     }
 
     @Test
@@ -723,11 +723,10 @@
 
     private void setUpNotificationIconAreaController() {
         mMockNotificationAreaController = mock(NotificationIconAreaController.class);
-
-        mNotificationAreaInner = new View(mContext);
-
-        when(mMockNotificationAreaController.getNotificationInnerAreaView()).thenReturn(
-                mNotificationAreaInner);
+        View notificationAreaInner =
+                LayoutInflater.from(mContext).inflate(R.layout.notification_icon_area, null);
+        when(mMockNotificationAreaController.getNotificationInnerAreaView())
+                .thenReturn(notificationAreaInner);
     }
 
     /**
@@ -782,4 +781,8 @@
     private View getEndSideContentView() {
         return mFragment.getView().findViewById(R.id.status_bar_end_side_content);
     }
+
+    private View getNotificationAreaView() {
+        return mFragment.getView().findViewById(R.id.notificationIcons);
+    }
 }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/authentication/data/repository/FakeAuthenticationRepository.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/authentication/data/repository/FakeAuthenticationRepository.kt
index 7c5696c..8486508 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/authentication/data/repository/FakeAuthenticationRepository.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/authentication/data/repository/FakeAuthenticationRepository.kt
@@ -20,7 +20,6 @@
 import com.android.internal.widget.LockPatternView
 import com.android.internal.widget.LockscreenCredential
 import com.android.keyguard.KeyguardSecurityModel.SecurityMode
-import com.android.systemui.authentication.shared.model.AuthenticationLockoutModel
 import com.android.systemui.authentication.shared.model.AuthenticationMethodModel
 import com.android.systemui.authentication.shared.model.AuthenticationPatternCoordinate
 import com.android.systemui.authentication.shared.model.AuthenticationResultModel
@@ -29,7 +28,6 @@
 import dagger.Module
 import dagger.Provides
 import kotlinx.coroutines.ExperimentalCoroutinesApi
-import kotlinx.coroutines.flow.MutableSharedFlow
 import kotlinx.coroutines.flow.MutableStateFlow
 import kotlinx.coroutines.flow.StateFlow
 import kotlinx.coroutines.flow.asStateFlow
@@ -40,15 +38,11 @@
     private val currentTime: () -> Long,
 ) : AuthenticationRepository {
 
-    override val authenticationChallengeResult = MutableSharedFlow<Boolean>()
-
     override val hintedPinLength: Int = HINTING_PIN_LENGTH
 
     private val _isPatternVisible = MutableStateFlow(true)
     override val isPatternVisible: StateFlow<Boolean> = _isPatternVisible.asStateFlow()
 
-    override val lockout: MutableStateFlow<AuthenticationLockoutModel?> = MutableStateFlow(null)
-
     override val hasLockoutOccurred = MutableStateFlow(false)
 
     private val _isAutoConfirmFeatureEnabled = MutableStateFlow(false)
@@ -68,8 +62,6 @@
     override val isPinEnhancedPrivacyEnabled: StateFlow<Boolean> =
         _isPinEnhancedPrivacyEnabled.asStateFlow()
 
-    private var failedAttemptCount = 0
-    private var lockoutEndTimestamp = 0L
     private var credentialOverride: List<Any>? = null
     private var securityMode: SecurityMode = DEFAULT_AUTHENTICATION_METHOD.toSecurityMode()
 
@@ -89,11 +81,27 @@
     }
 
     override suspend fun reportAuthenticationAttempt(isSuccessful: Boolean) {
-        failedAttemptCount = if (isSuccessful) 0 else failedAttemptCount + 1
-        authenticationChallengeResult.emit(isSuccessful)
+        if (isSuccessful) {
+            _failedAuthenticationAttempts.value = 0
+            _lockoutEndTimestamp = null
+            hasLockoutOccurred.value = false
+            lockoutStartedReportCount = 0
+        } else {
+            _failedAuthenticationAttempts.value++
+        }
     }
 
+    private var _failedAuthenticationAttempts = MutableStateFlow(0)
+    override val failedAuthenticationAttempts: StateFlow<Int> =
+        _failedAuthenticationAttempts.asStateFlow()
+
+    private var _lockoutEndTimestamp: Long? = null
+    override val lockoutEndTimestamp: Long?
+        get() = if (currentTime() < (_lockoutEndTimestamp ?: 0)) _lockoutEndTimestamp else null
+
     override suspend fun reportLockoutStarted(durationMs: Int) {
+        _lockoutEndTimestamp = (currentTime() + durationMs).takeIf { durationMs > 0 }
+        hasLockoutOccurred.value = true
         lockoutStartedReportCount++
     }
 
@@ -101,25 +109,10 @@
         return (credentialOverride ?: DEFAULT_PIN).size
     }
 
-    override suspend fun getFailedAuthenticationAttemptCount(): Int {
-        return failedAttemptCount
-    }
-
-    override suspend fun getLockoutEndTimestamp(): Long {
-        return lockoutEndTimestamp
-    }
-
     fun setAutoConfirmFeatureEnabled(isEnabled: Boolean) {
         _isAutoConfirmFeatureEnabled.value = isEnabled
     }
 
-    override suspend fun setLockoutDuration(durationMs: Int) {
-        lockoutEndTimestamp = if (durationMs > 0) currentTime() + durationMs else 0
-        if (durationMs > 0) {
-            hasLockoutOccurred.value = true
-        }
-    }
-
     override suspend fun checkCredential(
         credential: LockscreenCredential
     ): AuthenticationResultModel {
@@ -136,8 +129,8 @@
                 else -> error("Unexpected credential type ${credential.type}!")
             }
 
-        return if (isSuccessful || failedAttemptCount < MAX_FAILED_AUTH_TRIES_BEFORE_LOCKOUT - 1) {
-            hasLockoutOccurred.value = false
+        val failedAttempts = _failedAuthenticationAttempts.value
+        return if (isSuccessful || failedAttempts < MAX_FAILED_AUTH_TRIES_BEFORE_LOCKOUT - 1) {
             AuthenticationResultModel(
                 isSuccessful = isSuccessful,
                 lockoutDurationMs = 0,
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractorKosmos.kt
index 060ca4c..05cb059 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/authentication/domain/interactor/AuthenticationInteractorKosmos.kt
@@ -19,17 +19,11 @@
 import com.android.systemui.authentication.data.repository.authenticationRepository
 import com.android.systemui.kosmos.Kosmos
 import com.android.systemui.kosmos.applicationCoroutineScope
-import com.android.systemui.kosmos.testDispatcher
-import com.android.systemui.user.data.repository.userRepository
-import com.android.systemui.util.time.fakeSystemClock
 
 val Kosmos.authenticationInteractor by
     Kosmos.Fixture {
         AuthenticationInteractor(
             applicationScope = applicationCoroutineScope,
             repository = authenticationRepository,
-            backgroundDispatcher = testDispatcher,
-            userRepository = userRepository,
-            clock = fakeSystemClock,
         )
     }
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/scene/SceneTestUtils.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/scene/SceneTestUtils.kt
index 0b41926..25b97b3 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/scene/SceneTestUtils.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/scene/SceneTestUtils.kt
@@ -91,7 +91,6 @@
 import com.android.systemui.telephony.data.repository.TelephonyRepository
 import com.android.systemui.telephony.domain.interactor.TelephonyInteractor
 import com.android.systemui.user.data.repository.FakeUserRepository
-import com.android.systemui.user.data.repository.UserRepository
 import com.android.systemui.user.domain.interactor.SelectedUserInteractor
 import com.android.systemui.user.ui.viewmodel.UserActionViewModel
 import com.android.systemui.user.ui.viewmodel.UserViewModel
@@ -174,7 +173,7 @@
             mobileConnectionsRepository = mobileConnectionsRepository,
         )
 
-    val userRepository: UserRepository by lazy {
+    val userRepository: FakeUserRepository by lazy {
         FakeUserRepository().apply {
             val users = listOf(UserInfo(/* id=  */ 0, "name", /* flags= */ 0))
             setUserInfos(users)
@@ -236,9 +235,6 @@
         return AuthenticationInteractor(
             applicationScope = applicationScope(),
             repository = repository,
-            backgroundDispatcher = testDispatcher,
-            userRepository = userRepository,
-            clock = mock { whenever(elapsedRealtime()).thenAnswer { testScope.currentTime } }
         )
     }
 
@@ -274,7 +270,6 @@
             repository = bouncerRepository,
             authenticationInteractor = authenticationInteractor,
             keyguardFaceAuthInteractor = keyguardFaceAuthInteractor,
-            flags = sceneContainerFlags,
             falsingInteractor = falsingInteractor(),
             powerInteractor = powerInteractor(),
             simBouncerInteractor = simBouncerInteractor,
@@ -312,6 +307,7 @@
             userSwitcherMenu = flowOf(createMenuActions()),
             actionButtonInteractor = actionButtonInteractor,
             simBouncerInteractor = simBouncerInteractor,
+            clock = mock { whenever(elapsedRealtime()).thenAnswer { testScope.currentTime } },
         )
     }
 
diff --git a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/icon/domain/interactor/NotificationIconsInteractorKosmos.kt b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/icon/domain/interactor/NotificationIconsInteractorKosmos.kt
index 5c8fe0d..2d2f546 100644
--- a/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/icon/domain/interactor/NotificationIconsInteractorKosmos.kt
+++ b/packages/SystemUI/tests/utils/src/com/android/systemui/statusbar/notification/icon/domain/interactor/NotificationIconsInteractorKosmos.kt
@@ -24,6 +24,7 @@
 import com.android.systemui.statusbar.data.repository.notificationListenerSettingsRepository
 import com.android.systemui.statusbar.notification.data.repository.notificationsKeyguardViewStateRepository
 import com.android.systemui.statusbar.notification.domain.interactor.activeNotificationsInteractor
+import com.android.systemui.statusbar.notification.domain.interactor.headsUpNotificationIconInteractor
 import com.android.wm.shell.bubbles.bubblesOptional
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 
@@ -43,6 +44,7 @@
     NotificationIconsInteractor(
         activeNotificationsInteractor = activeNotificationsInteractor,
         bubbles = bubblesOptional,
+        headsUpNotificationIconInteractor = headsUpNotificationIconInteractor,
         keyguardViewStateRepository = notificationsKeyguardViewStateRepository,
     )
 }
diff --git a/services/autofill/java/com/android/server/autofill/SecondaryProviderHandler.java b/services/autofill/java/com/android/server/autofill/SecondaryProviderHandler.java
index d9741c8..4a6d5c9b 100644
--- a/services/autofill/java/com/android/server/autofill/SecondaryProviderHandler.java
+++ b/services/autofill/java/com/android/server/autofill/SecondaryProviderHandler.java
@@ -25,9 +25,6 @@
 import android.service.autofill.FillResponse;
 import android.util.Slog;
 
-import java.util.Objects;
-
-
 /**
  * Requests autofill response from a Remote Autofill Service. This autofill service can be
  * either a Credential Autofill Service or the user-opted autofill service.
@@ -51,7 +48,6 @@
 
     private final RemoteFillService mRemoteFillService;
     private final SecondaryProviderCallback mCallback;
-    private FillRequest mLastFillRequest;
     private int mLastFlag;
 
     SecondaryProviderHandler(
@@ -97,17 +93,11 @@
     }
 
     /**
-     * Requests a new fill response. If the fill request is same as the last requested fill request,
-     * then the request is duped.
+     * Requests a new fill response.
      */
     public void onFillRequest(FillRequest pendingFillRequest, int flag) {
-        if (Objects.equals(pendingFillRequest, mLastFillRequest)) {
-            Slog.v(TAG, "Deduping fill request to secondary provider.");
-            return;
-        }
         Slog.v(TAG, "Requesting fill response to secondary provider.");
         mLastFlag = flag;
-        mLastFillRequest = pendingFillRequest;
         mRemoteFillService.onFillRequest(pendingFillRequest);
     }
 
diff --git a/services/autofill/java/com/android/server/autofill/Session.java b/services/autofill/java/com/android/server/autofill/Session.java
index d527ce0..c4e8f12 100644
--- a/services/autofill/java/com/android/server/autofill/Session.java
+++ b/services/autofill/java/com/android/server/autofill/Session.java
@@ -367,6 +367,9 @@
     @GuardedBy("mLock")
     private SparseArray<FillResponse> mResponses;
 
+    @GuardedBy("mLock")
+    private SparseArray<FillResponse> mSecondaryResponses;
+
     /**
      * Contexts read from the app; they will be updated (sanitized, change values for save) before
      * sent to {@link AutofillService}. Ordered by the time they were read.
@@ -713,7 +716,14 @@
                         mPendingFillRequest.getDelayedFillIntentSender());
             }
             mLastFillRequest = mPendingFillRequest;
-            mRemoteFillService.onFillRequest(mPendingFillRequest);
+            if (shouldRequestSecondaryProvider(mPendingFillRequest.getFlags())
+                    && mSecondaryProviderHandler != null) {
+                Slog.v(TAG, "Requesting fill response to secondary provider.");
+                mSecondaryProviderHandler.onFillRequest(mPendingFillRequest,
+                        mPendingFillRequest.getFlags());
+            } else if (mRemoteFillService != null) {
+                mRemoteFillService.onFillRequest(mPendingFillRequest);
+            }
             mPendingInlineSuggestionsRequest = null;
             mWaitForInlineRequest = false;
             mPendingFillRequest = null;
@@ -1196,7 +1206,8 @@
     @GuardedBy("mLock")
     private void requestNewFillResponseLocked(@NonNull ViewState viewState, int newState,
             int flags) {
-        final FillResponse existingResponse = viewState.getResponse();
+        final FillResponse existingResponse = shouldRequestSecondaryProvider(flags)
+                ? viewState.getSecondaryResponse() : viewState.getResponse();
         mFillRequestEventLogger.startLogForNewRequest();
         mRequestCount++;
         mFillRequestEventLogger.maybeSetAppPackageUid(uid);
@@ -1804,6 +1815,10 @@
             return;
         }
         synchronized (mLock) {
+            if (mSecondaryResponses == null) {
+                mSecondaryResponses = new SparseArray<>(2);
+            }
+            mSecondaryResponses.put(fillResponse.getRequestId(), fillResponse);
             setViewStatesLocked(fillResponse, ViewState.STATE_FILLABLE, /* clearResponse= */ false,
                     /* isPrimary= */ false);
 
@@ -3980,7 +3995,7 @@
         }
 
         // If it's not, then check if it should start a partition.
-        if (shouldStartNewPartitionLocked(id)) {
+        if (shouldStartNewPartitionLocked(id, flags)) {
             if (sDebug) {
                 Slog.d(TAG, "Starting partition or augmented request for view id " + id + ": "
                         + viewState.getStateAsString());
@@ -4008,9 +4023,11 @@
      * @return {@code true} if a new partition should be started
      */
     @GuardedBy("mLock")
-    private boolean shouldStartNewPartitionLocked(@NonNull AutofillId id) {
+    private boolean shouldStartNewPartitionLocked(@NonNull AutofillId id, int flags) {
         final ViewState currentView = mViewStates.get(id);
-        if (mResponses == null) {
+        SparseArray<FillResponse> responses = shouldRequestSecondaryProvider(flags)
+                ? mSecondaryResponses : mResponses;
+        if (responses == null) {
             return currentView != null && (currentView.getState()
                     & ViewState.STATE_PENDING_CREATE_INLINE_REQUEST) == 0;
         }
@@ -4022,7 +4039,7 @@
             return true;
         }
 
-        final int numResponses = mResponses.size();
+        final int numResponses = responses.size();
         if (numResponses >= AutofillManagerService.getPartitionMaxCount()) {
             Slog.e(TAG, "Not starting a new partition on " + id + " because session " + this.id
                     + " reached maximum of " + AutofillManagerService.getPartitionMaxCount());
@@ -4030,7 +4047,7 @@
         }
 
         for (int responseNum = 0; responseNum < numResponses; responseNum++) {
-            final FillResponse response = mResponses.valueAt(responseNum);
+            final FillResponse response = responses.valueAt(responseNum);
 
             if (ArrayUtils.contains(response.getIgnoredIds(), id)) {
                 return false;
@@ -4066,6 +4083,10 @@
     }
 
     boolean shouldRequestSecondaryProvider(int flags) {
+        if (!mService.isAutofillCredmanIntegrationEnabled()
+                || mSecondaryProviderHandler == null) {
+            return false;
+        }
         if (mIsPrimaryCredential) {
             return (flags & FLAG_VIEW_REQUESTS_CREDMAN_SERVICE) == 0;
         } else {
@@ -4205,12 +4226,6 @@
                 }
                 break;
             case ACTION_VIEW_ENTERED:
-                if (shouldRequestSecondaryProvider(flags)
-                        && mSecondaryProviderHandler != null
-                        && mAssistReceiver.mLastFillRequest != null) {
-                    mSecondaryProviderHandler.onFillRequest(mAssistReceiver.mLastFillRequest,
-                            flags);
-                }
                 mLatencyBaseTime = SystemClock.elapsedRealtime();
                 boolean wasPreviouslyFillDialog = mPreviouslyFillDialogPotentiallyStarted;
                 mPreviouslyFillDialogPotentiallyStarted = false;
@@ -4225,6 +4240,19 @@
                     viewState.setCurrentValue(value);
                 }
 
+                if (shouldRequestSecondaryProvider(flags)) {
+                    if (requestNewFillResponseOnViewEnteredIfNecessaryLocked(
+                            id, viewState, flags)) {
+                        Slog.v(TAG, "Started a new fill request for secondary provider.");
+                        return;
+                    }
+                    // If the ViewState is ready to be displayed, onReady() will be called.
+                    viewState.update(value, virtualBounds, flags);
+
+                    // return here because primary provider logic is not applicable.
+                    return;
+                }
+
                 if (mCompatMode && (viewState.getState() & ViewState.STATE_URL_BAR) != 0) {
                     if (sDebug) Slog.d(TAG, "Ignoring VIEW_ENTERED on URL BAR (id=" + id + ")");
                     return;
diff --git a/services/autofill/java/com/android/server/autofill/ViewState.java b/services/autofill/java/com/android/server/autofill/ViewState.java
index b0bb9ec..fec5aa5 100644
--- a/services/autofill/java/com/android/server/autofill/ViewState.java
+++ b/services/autofill/java/com/android/server/autofill/ViewState.java
@@ -162,6 +162,11 @@
         return mPrimaryFillResponse;
     }
 
+    @Nullable
+    FillResponse getSecondaryResponse() {
+        return mSecondaryFillResponse;
+    }
+
     void setResponse(FillResponse response) {
         setResponse(response, /* isPrimary= */ true);
     }
diff --git a/services/companion/java/com/android/server/companion/transport/CompanionTransportManager.java b/services/companion/java/com/android/server/companion/transport/CompanionTransportManager.java
index df74770..62c6703 100644
--- a/services/companion/java/com/android/server/companion/transport/CompanionTransportManager.java
+++ b/services/companion/java/com/android/server/companion/transport/CompanionTransportManager.java
@@ -130,7 +130,7 @@
         synchronized (mTransports) {
             for (int i = 0; i < associationIds.length; i++) {
                 if (mTransports.contains(associationIds[i])) {
-                    mTransports.get(associationIds[i]).requestForResponse(message, data);
+                    mTransports.get(associationIds[i]).sendMessage(message, data);
                 }
             }
         }
@@ -220,7 +220,7 @@
             if (transport == null) {
                 return CompletableFuture.failedFuture(new IOException("Missing transport"));
             }
-            return transport.requestForResponse(MESSAGE_REQUEST_PERMISSION_RESTORE, data);
+            return transport.sendMessage(MESSAGE_REQUEST_PERMISSION_RESTORE, data);
         }
     }
 
diff --git a/services/companion/java/com/android/server/companion/transport/Transport.java b/services/companion/java/com/android/server/companion/transport/Transport.java
index 32d4061..22b18ac 100644
--- a/services/companion/java/com/android/server/companion/transport/Transport.java
+++ b/services/companion/java/com/android/server/companion/transport/Transport.java
@@ -16,6 +16,9 @@
 
 package com.android.server.companion.transport;
 
+import static android.companion.CompanionDeviceManager.MESSAGE_ONEWAY_FROM_WEARABLE;
+import static android.companion.CompanionDeviceManager.MESSAGE_ONEWAY_PING;
+import static android.companion.CompanionDeviceManager.MESSAGE_ONEWAY_TO_WEARABLE;
 import static android.companion.CompanionDeviceManager.MESSAGE_REQUEST_CONTEXT_SYNC;
 import static android.companion.CompanionDeviceManager.MESSAGE_REQUEST_PERMISSION_RESTORE;
 import static android.companion.CompanionDeviceManager.MESSAGE_REQUEST_PING;
@@ -80,6 +83,10 @@
         return (message & 0xFF000000) == 0x33000000;
     }
 
+    private static boolean isOneway(int message) {
+        return (message & 0xFF000000) == 0x43000000;
+    }
+
     @GuardedBy("mPendingRequests")
     protected final SparseArray<CompletableFuture<byte[]>> mPendingRequests =
             new SparseArray<>();
@@ -134,6 +141,42 @@
     protected abstract void sendMessage(int message, int sequence, @NonNull byte[] data)
             throws IOException;
 
+    /**
+     * Send a message using this transport. If the message was a request, then the returned Future
+     * object will complete successfully only if the remote device both received and processed it
+     * as expected. If the message was a send-and-forget type message, then the Future object will
+     * resolve successfully immediately (with null) upon sending the message.
+     *
+     * @param message the message type
+     * @param data the message payload
+     * @return Future object containing the result of the sent message.
+     */
+    public Future<byte[]> sendMessage(int message, byte[] data) {
+        final CompletableFuture<byte[]> pending = new CompletableFuture<>();
+        if (isOneway(message)) {
+            return sendAndForget(message, data);
+        } else if (isRequest(message)) {
+            return requestForResponse(message, data);
+        } else {
+            Slog.w(TAG, "Failed to send message 0x" + Integer.toHexString(message));
+            pending.completeExceptionally(new IllegalArgumentException(
+                    "The message being sent must be either a one-way or a request."
+            ));
+        }
+        return pending;
+    }
+
+    /**
+     * @deprecated Method was renamed to sendMessage(int, byte[]) to support both
+     * send-and-forget type messages as well as wait-for-response type messages.
+     *
+     * @param message request message type
+     * @param data the message payload
+     * @return future object containing the result of the request.
+     *
+     * @see #sendMessage(int, byte[])
+     */
+    @Deprecated
     public Future<byte[]> requestForResponse(int message, byte[] data) {
         if (DEBUG) Slog.d(TAG, "Requesting for response");
         final int sequence = mNextSequence.incrementAndGet();
@@ -154,6 +197,20 @@
         return pending;
     }
 
+    private Future<byte[]> sendAndForget(int message, byte[]data) {
+        if (DEBUG) Slog.d(TAG, "Sending a one-way message");
+        final CompletableFuture<byte[]> pending = new CompletableFuture<>();
+
+        try {
+            sendMessage(message, -1, data);
+            pending.complete(null);
+        } catch (IOException e) {
+            pending.completeExceptionally(e);
+        }
+
+        return pending;
+    }
+
     protected final void handleMessage(int message, int sequence, @NonNull byte[] data)
             throws IOException {
         if (DEBUG) {
@@ -162,7 +219,9 @@
                     + " from association " + mAssociationId);
         }
 
-        if (isRequest(message)) {
+        if (isOneway(message)) {
+            processOneway(message, data);
+        } else if (isRequest(message)) {
             try {
                 processRequest(message, sequence, data);
             } catch (IOException e) {
@@ -175,6 +234,21 @@
         }
     }
 
+    private void processOneway(int message, byte[] data) {
+        switch (message) {
+            case MESSAGE_ONEWAY_PING:
+            case MESSAGE_ONEWAY_FROM_WEARABLE:
+            case MESSAGE_ONEWAY_TO_WEARABLE: {
+                callback(message, data);
+                break;
+            }
+            default: {
+                Slog.w(TAG, "Ignoring unknown message 0x" + Integer.toHexString(message));
+                break;
+            }
+        }
+    }
+
     private void processRequest(int message, int sequence, byte[] data)
             throws IOException {
         switch (message) {
diff --git a/services/core/java/com/android/server/am/ActivityManagerShellCommand.java b/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
index ae0cd65..00dd169 100644
--- a/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
+++ b/services/core/java/com/android/server/am/ActivityManagerShellCommand.java
@@ -2238,6 +2238,7 @@
 
         pw.println("Performing idle maintenance...");
         mInterface.sendIdleJobTrigger();
+        mInternal.performIdleMaintenance();
         return 0;
     }
 
diff --git a/services/core/java/com/android/server/am/UserController.java b/services/core/java/com/android/server/am/UserController.java
index badd7f0..2b81dbc 100644
--- a/services/core/java/com/android/server/am/UserController.java
+++ b/services/core/java/com/android/server/am/UserController.java
@@ -2059,9 +2059,6 @@
             mTargetUserId = targetUserId;
             userSwitchUiEnabled = mUserSwitchUiEnabled;
         }
-        if (android.multiuser.Flags.useAllCpusDuringUserSwitch()) {
-            mInjector.setHasTopUi(true);
-        }
         if (userSwitchUiEnabled) {
             UserInfo currentUserInfo = getUserInfo(currentUserId);
             Pair<UserInfo, UserInfo> userNames = new Pair<>(currentUserInfo, targetUserInfo);
@@ -2130,9 +2127,6 @@
     }
 
     private void endUserSwitch() {
-        if (android.multiuser.Flags.useAllCpusDuringUserSwitch()) {
-            mInjector.setHasTopUi(false);
-        }
         final int nextUserId;
         synchronized (mLock) {
             nextUserId = ObjectUtils.getOrElse(mPendingTargetUserIds.poll(), UserHandle.USER_NULL);
@@ -3816,15 +3810,6 @@
             getSystemServiceManager().onUserStarting(TimingsTraceAndSlog.newAsyncLog(), userId);
         }
 
-        void setHasTopUi(boolean hasTopUi) {
-            try {
-                Slogf.i(TAG, "Setting hasTopUi to " + hasTopUi);
-                mService.setHasTopUi(hasTopUi);
-            } catch (RemoteException e) {
-                Slogf.e(TAG, "Failed to allow using all CPU cores", e);
-            }
-        }
-
         void onSystemUserVisibilityChanged(boolean visible) {
             getUserManagerInternal().onSystemUserVisibilityChanged(visible);
         }
diff --git a/services/core/java/com/android/server/display/AutomaticBrightnessController.java b/services/core/java/com/android/server/display/AutomaticBrightnessController.java
index 2314bb7..2d6177d 100644
--- a/services/core/java/com/android/server/display/AutomaticBrightnessController.java
+++ b/services/core/java/com/android/server/display/AutomaticBrightnessController.java
@@ -16,6 +16,8 @@
 
 package com.android.server.display;
 
+import static com.android.server.display.config.DisplayBrightnessMappingConfig.autoBrightnessModeToString;
+
 import android.annotation.IntDef;
 import android.annotation.NonNull;
 import android.annotation.Nullable;
@@ -72,12 +74,14 @@
     @IntDef(prefix = { "AUTO_BRIGHTNESS_MODE_" }, value = {
             AUTO_BRIGHTNESS_MODE_DEFAULT,
             AUTO_BRIGHTNESS_MODE_IDLE,
+            AUTO_BRIGHTNESS_MODE_DOZE
     })
     @Retention(RetentionPolicy.SOURCE)
     public @interface AutomaticBrightnessMode{}
 
     public static final int AUTO_BRIGHTNESS_MODE_DEFAULT = 0;
     public static final int AUTO_BRIGHTNESS_MODE_IDLE = 1;
+    public static final int AUTO_BRIGHTNESS_MODE_DOZE = 2;
 
     // How long the current sensor reading is assumed to be valid beyond the current time.
     // This provides a bit of prediction, as well as ensures that the weight for the last sample is
@@ -399,7 +403,8 @@
                     | (!mAmbientLuxValid ? BrightnessEvent.FLAG_INVALID_LUX : 0)
                     | (mDisplayPolicy == DisplayPowerRequest.POLICY_DOZE
                         ? BrightnessEvent.FLAG_DOZE_SCALE : 0)
-                    | (isInIdleMode() ? BrightnessEvent.FLAG_IDLE_CURVE : 0));
+                    | (getMode() == AUTO_BRIGHTNESS_MODE_IDLE
+                        ? BrightnessEvent.FLAG_IDLE_CURVE : 0));
         }
 
         if (!mAmbientLuxValid) {
@@ -510,7 +515,8 @@
         if (mLoggingEnabled) {
             Slog.d(TAG, "Display policy transitioning from " + oldPolicy + " to " + policy);
         }
-        if (!isInteractivePolicy(policy) && isInteractivePolicy(oldPolicy) && !isInIdleMode()) {
+        if (!isInteractivePolicy(policy) && isInteractivePolicy(oldPolicy)
+                && getMode() != AUTO_BRIGHTNESS_MODE_IDLE) {
             mHandler.sendEmptyMessageDelayed(MSG_INVALIDATE_CURRENT_SHORT_TERM_MODEL,
                     mCurrentBrightnessMapper.getShortTermModelTimeout());
         } else if (isInteractivePolicy(policy) && !isInteractivePolicy(oldPolicy)) {
@@ -551,7 +557,7 @@
             boolean shouldResetShortTermModel) {
         if (mBrightnessMappingStrategyMap.get(AUTO_BRIGHTNESS_MODE_DEFAULT)
                 .setBrightnessConfiguration(configuration)) {
-            if (!isInIdleMode() && shouldResetShortTermModel) {
+            if (getMode() != AUTO_BRIGHTNESS_MODE_IDLE && shouldResetShortTermModel) {
                 resetShortTermModel();
             }
             return true;
@@ -559,8 +565,9 @@
         return false;
     }
 
-    public boolean isInIdleMode() {
-        return mCurrentBrightnessMapper.getMode() == AUTO_BRIGHTNESS_MODE_IDLE;
+    @AutomaticBrightnessController.AutomaticBrightnessMode
+    public int getMode() {
+        return mCurrentBrightnessMapper.getMode();
     }
 
     public void dump(PrintWriter pw) {
@@ -616,12 +623,12 @@
         pw.println("  mPendingForegroundAppPackageName=" + mPendingForegroundAppPackageName);
         pw.println("  mForegroundAppCategory=" + mForegroundAppCategory);
         pw.println("  mPendingForegroundAppCategory=" + mPendingForegroundAppCategory);
-        pw.println("  Current mode=" + mCurrentBrightnessMapper.getMode());
+        pw.println("  Current mode=" + autoBrightnessModeToString(getMode()));
 
         pw.println();
         for (int i = 0; i < mBrightnessMappingStrategyMap.size(); i++) {
-            pw.println("  Mapper for mode " + modeToString(mBrightnessMappingStrategyMap.keyAt(i))
-                    + "=");
+            pw.println("  Mapper for mode "
+                    + autoBrightnessModeToString(mBrightnessMappingStrategyMap.keyAt(i)) + "=");
             mBrightnessMappingStrategyMap.valueAt(i).dump(pw,
                     mBrightnessRangeController.getNormalBrightnessMax());
         }
@@ -738,7 +745,7 @@
             lux = 0;
         }
         mAmbientLux = lux;
-        if (isInIdleMode()) {
+        if (getMode() == AUTO_BRIGHTNESS_MODE_IDLE) {
             mAmbientBrighteningThreshold =
                     mAmbientBrightnessThresholdsIdle.getBrighteningThreshold(lux);
             mAmbientDarkeningThreshold =
@@ -830,7 +837,7 @@
             }
             earliestValidTime = mAmbientLightRingBuffer.getTime(i);
         }
-        return earliestValidTime + (isInIdleMode()
+        return earliestValidTime + (getMode() == AUTO_BRIGHTNESS_MODE_IDLE
                 ? mBrighteningLightDebounceConfigIdle : mBrighteningLightDebounceConfig);
     }
 
@@ -843,7 +850,7 @@
             }
             earliestValidTime = mAmbientLightRingBuffer.getTime(i);
         }
-        return earliestValidTime + (isInIdleMode()
+        return earliestValidTime + (getMode() == AUTO_BRIGHTNESS_MODE_IDLE
                 ? mDarkeningLightDebounceConfigIdle : mDarkeningLightDebounceConfig);
     }
 
@@ -968,7 +975,7 @@
                 mPreThresholdBrightness = mScreenAutoBrightness;
             }
             mScreenAutoBrightness = newScreenAutoBrightness;
-            if (isInIdleMode()) {
+            if (getMode() == AUTO_BRIGHTNESS_MODE_IDLE) {
                 mScreenBrighteningThreshold = clampScreenBrightness(
                         mScreenBrightnessThresholdsIdle.getBrighteningThreshold(
                                 newScreenAutoBrightness));
@@ -1224,14 +1231,6 @@
         }
     }
 
-    private String modeToString(@AutomaticBrightnessMode int mode) {
-        return switch (mode) {
-            case AUTO_BRIGHTNESS_MODE_DEFAULT -> "default";
-            case AUTO_BRIGHTNESS_MODE_IDLE -> "idle";
-            default -> Integer.toString(mode);
-        };
-    }
-
     private class ShortTermModel {
         // When the short term model is invalidated, we don't necessarily reset it (i.e. clear the
         // user's adjustment) immediately, but wait for a drastic enough change in the ambient
diff --git a/services/core/java/com/android/server/display/BrightnessMappingStrategy.java b/services/core/java/com/android/server/display/BrightnessMappingStrategy.java
index f2ffd4d..8405e0a 100644
--- a/services/core/java/com/android/server/display/BrightnessMappingStrategy.java
+++ b/services/core/java/com/android/server/display/BrightnessMappingStrategy.java
@@ -19,15 +19,18 @@
 import static android.text.TextUtils.formatSimple;
 
 import static com.android.server.display.AutomaticBrightnessController.AUTO_BRIGHTNESS_MODE_DEFAULT;
+import static com.android.server.display.AutomaticBrightnessController.AUTO_BRIGHTNESS_MODE_DOZE;
 import static com.android.server.display.AutomaticBrightnessController.AUTO_BRIGHTNESS_MODE_IDLE;
 
 import android.annotation.Nullable;
+import android.content.Context;
 import android.content.pm.ApplicationInfo;
-import android.content.res.Resources;
 import android.content.res.TypedArray;
 import android.hardware.display.BrightnessConfiguration;
 import android.hardware.display.BrightnessCorrection;
 import android.os.PowerManager;
+import android.os.UserHandle;
+import android.provider.Settings;
 import android.util.LongArray;
 import android.util.MathUtils;
 import android.util.Pair;
@@ -79,41 +82,50 @@
      * Creates a BrightnessMapping strategy. We do not create a simple mapping strategy for idle
      * mode.
      *
-     * @param resources
+     * @param context
      * @param displayDeviceConfig
      * @param mode The auto-brightness mode. Different modes use different brightness curves
      * @param displayWhiteBalanceController
      * @return the BrightnessMappingStrategy
      */
     @Nullable
-    static BrightnessMappingStrategy create(Resources resources,
+    static BrightnessMappingStrategy create(Context context,
             DisplayDeviceConfig displayDeviceConfig,
             @AutomaticBrightnessController.AutomaticBrightnessMode int mode,
-            DisplayWhiteBalanceController displayWhiteBalanceController) {
+            @Nullable DisplayWhiteBalanceController displayWhiteBalanceController) {
 
         // Display independent, mode dependent values
         float[] brightnessLevelsNits = null;
         float[] brightnessLevels = null;
         float[] luxLevels = null;
+        int preset = Settings.System.getIntForUser(context.getContentResolver(),
+                Settings.System.SCREEN_BRIGHTNESS_FOR_ALS,
+                Settings.System.SCREEN_BRIGHTNESS_AUTOMATIC_NORMAL, UserHandle.USER_CURRENT);
         switch (mode) {
             case AUTO_BRIGHTNESS_MODE_DEFAULT -> {
                 brightnessLevelsNits = displayDeviceConfig.getAutoBrightnessBrighteningLevelsNits();
-                luxLevels = displayDeviceConfig.getAutoBrightnessBrighteningLevelsLux();
-                brightnessLevels = displayDeviceConfig.getAutoBrightnessBrighteningLevels();
+                luxLevels = displayDeviceConfig.getAutoBrightnessBrighteningLevelsLux(mode, preset);
+                brightnessLevels =
+                        displayDeviceConfig.getAutoBrightnessBrighteningLevels(mode, preset);
             }
             case AUTO_BRIGHTNESS_MODE_IDLE -> {
-                brightnessLevelsNits = getFloatArray(resources.obtainTypedArray(
+                brightnessLevelsNits = getFloatArray(context.getResources().obtainTypedArray(
                         com.android.internal.R.array.config_autoBrightnessDisplayValuesNitsIdle));
-                luxLevels = getLuxLevels(resources.getIntArray(
+                luxLevels = getLuxLevels(context.getResources().getIntArray(
                         com.android.internal.R.array.config_autoBrightnessLevelsIdle));
             }
+            case AUTO_BRIGHTNESS_MODE_DOZE -> {
+                luxLevels = displayDeviceConfig.getAutoBrightnessBrighteningLevelsLux(mode, preset);
+                brightnessLevels =
+                        displayDeviceConfig.getAutoBrightnessBrighteningLevels(mode, preset);
+            }
         }
 
         // Display independent, mode independent values
-        float autoBrightnessAdjustmentMaxGamma = resources.getFraction(
+        float autoBrightnessAdjustmentMaxGamma = context.getResources().getFraction(
                 com.android.internal.R.fraction.config_autoBrightnessAdjustmentMaxGamma,
                 1, 1);
-        long shortTermModelTimeout = resources.getInteger(
+        long shortTermModelTimeout = context.getResources().getInteger(
                 com.android.internal.R.integer.config_autoBrightnessShortTermModelTimeout);
 
         // Display dependent values - used for physical mapping strategy nits -> brightness
@@ -818,6 +830,8 @@
         private float mAutoBrightnessAdjustment;
         private float mUserLux;
         private float mUserBrightness;
+
+        @Nullable
         private final DisplayWhiteBalanceController mDisplayWhiteBalanceController;
 
         @AutomaticBrightnessController.AutomaticBrightnessMode
@@ -833,7 +847,7 @@
         public PhysicalMappingStrategy(BrightnessConfiguration config, float[] nits,
                 float[] brightness, float maxGamma,
                 @AutomaticBrightnessController.AutomaticBrightnessMode int mode,
-                DisplayWhiteBalanceController displayWhiteBalanceController) {
+                @Nullable DisplayWhiteBalanceController displayWhiteBalanceController) {
 
             Preconditions.checkArgument(nits.length != 0 && brightness.length != 0,
                     "Nits and brightness arrays must not be empty!");
diff --git a/services/core/java/com/android/server/display/DisplayDeviceConfig.java b/services/core/java/com/android/server/display/DisplayDeviceConfig.java
index 7d22a87..bd22e1d 100644
--- a/services/core/java/com/android/server/display/DisplayDeviceConfig.java
+++ b/services/core/java/com/android/server/display/DisplayDeviceConfig.java
@@ -1591,25 +1591,18 @@
     }
 
     /**
-     * @return The default auto-brightness brightening ambient lux levels
-     */
-    public float[] getAutoBrightnessBrighteningLevelsLux() {
-        if (mDisplayBrightnessMapping == null) {
-            return null;
-        }
-        return mDisplayBrightnessMapping.getLuxArray();
-    }
-
-    /**
      * @param mode The auto-brightness mode
-     * @param setting The brightness setting
-     * @return Auto brightness brightening ambient lux levels for the specified mode and setting
+     * @param preset The brightness preset. Presets are used on devices that allow users to choose
+     *               from a set of predefined options in display auto-brightness settings.
+     * @return The default auto-brightness brightening ambient lux levels for the specified mode
+     * and preset
      */
-    public float[] getAutoBrightnessBrighteningLevelsLux(String mode, String setting) {
+    public float[] getAutoBrightnessBrighteningLevelsLux(
+            @AutomaticBrightnessController.AutomaticBrightnessMode int mode, int preset) {
         if (mDisplayBrightnessMapping == null) {
             return null;
         }
-        return mDisplayBrightnessMapping.getLuxArray(mode, setting);
+        return mDisplayBrightnessMapping.getLuxArray(mode, preset);
     }
 
     /**
@@ -1623,25 +1616,17 @@
     }
 
     /**
-     * @return The default auto-brightness brightening levels
-     */
-    public float[] getAutoBrightnessBrighteningLevels() {
-        if (mDisplayBrightnessMapping == null) {
-            return null;
-        }
-        return mDisplayBrightnessMapping.getBrightnessArray();
-    }
-
-    /**
      * @param mode The auto-brightness mode
-     * @param setting The brightness setting
-     * @return Auto brightness brightening backlight levels for the specified mode and setting
+     * @param preset The brightness preset. Presets are used on devices that allow users to choose
+     *               from a set of predefined options in display auto-brightness settings.
+     * @return The default auto-brightness brightening levels for the specified mode and preset
      */
-    public float[] getAutoBrightnessBrighteningLevels(String mode, String setting) {
+    public float[] getAutoBrightnessBrighteningLevels(
+            @AutomaticBrightnessController.AutomaticBrightnessMode int mode, int preset) {
         if (mDisplayBrightnessMapping == null) {
             return null;
         }
-        return mDisplayBrightnessMapping.getBrightnessArray(mode, setting);
+        return mDisplayBrightnessMapping.getBrightnessArray(mode, preset);
     }
 
     /**
diff --git a/services/core/java/com/android/server/display/DisplayOffloadSessionImpl.java b/services/core/java/com/android/server/display/DisplayOffloadSessionImpl.java
index 4e341a9..c177091 100644
--- a/services/core/java/com/android/server/display/DisplayOffloadSessionImpl.java
+++ b/services/core/java/com/android/server/display/DisplayOffloadSessionImpl.java
@@ -65,6 +65,16 @@
         return true;
     }
 
+    @Override
+    public float[] getCurrentAutoBrightnessLevels() {
+        return mDisplayPowerController.getCurrentAutoBrightnessLevels();
+    }
+
+    @Override
+    public float[] getCurrentAutoBrightnessLuxLevels() {
+        return mDisplayPowerController.getCurrentAutoBrightnessLuxLevels();
+    }
+
     /**
      * Start the offload session. The method returns if the session is already active.
      * @return Whether the session was started successfully
diff --git a/services/core/java/com/android/server/display/DisplayPowerController.java b/services/core/java/com/android/server/display/DisplayPowerController.java
index 2685efe..c17378d 100644
--- a/services/core/java/com/android/server/display/DisplayPowerController.java
+++ b/services/core/java/com/android/server/display/DisplayPowerController.java
@@ -735,7 +735,7 @@
             mCdsi = null;
         }
 
-        setUpAutoBrightness(resources, handler);
+        setUpAutoBrightness(context, handler);
 
         mColorFadeEnabled = !ActivityManager.isLowRamDeviceStatic()
                 && !resources.getBoolean(
@@ -1075,7 +1075,7 @@
         loadBrightnessRampRates();
         loadProximitySensor();
         loadNitsRange(mContext.getResources());
-        setUpAutoBrightness(mContext.getResources(), mHandler);
+        setUpAutoBrightness(mContext, mHandler);
         reloadReduceBrightColours();
         setAnimatorRampSpeeds(/* isIdleMode= */ false);
         mBrightnessRangeController.loadFromConfig(hbmMetadata, token, info, mDisplayDeviceConfig);
@@ -1119,7 +1119,7 @@
                 DisplayPowerState.SCREEN_BRIGHTNESS_FLOAT,
                 DisplayPowerState.SCREEN_SDR_BRIGHTNESS_FLOAT);
         setAnimatorRampSpeeds(mAutomaticBrightnessController != null
-                && mAutomaticBrightnessController.isInIdleMode());
+                && mAutomaticBrightnessController.getMode() == AUTO_BRIGHTNESS_MODE_IDLE);
         mScreenBrightnessRampAnimator.setListener(mRampAnimatorListener);
 
         noteScreenState(mPowerState.getScreenState());
@@ -1145,7 +1145,7 @@
         handleBrightnessModeChange();
     }
 
-    private void setUpAutoBrightness(Resources resources, Handler handler) {
+    private void setUpAutoBrightness(Context context, Handler handler) {
         mUseSoftwareAutoBrightnessConfig = mDisplayDeviceConfig.isAutoBrightnessAvailable();
 
         if (!mUseSoftwareAutoBrightnessConfig) {
@@ -1155,21 +1155,19 @@
         SparseArray<BrightnessMappingStrategy> brightnessMappers = new SparseArray<>();
 
         BrightnessMappingStrategy defaultModeBrightnessMapper =
-                mInjector.getDefaultModeBrightnessMapper(resources, mDisplayDeviceConfig,
+                mInjector.getDefaultModeBrightnessMapper(context, mDisplayDeviceConfig,
                         mDisplayWhiteBalanceController);
         brightnessMappers.append(AUTO_BRIGHTNESS_MODE_DEFAULT,
                 defaultModeBrightnessMapper);
 
-        final boolean isIdleScreenBrightnessEnabled = resources.getBoolean(
+        final boolean isIdleScreenBrightnessEnabled = context.getResources().getBoolean(
                 R.bool.config_enableIdleScreenBrightnessMode);
         if (isIdleScreenBrightnessEnabled) {
             BrightnessMappingStrategy idleModeBrightnessMapper =
-                    BrightnessMappingStrategy.create(resources, mDisplayDeviceConfig,
-                            AUTO_BRIGHTNESS_MODE_IDLE,
-                            mDisplayWhiteBalanceController);
+                    BrightnessMappingStrategy.create(context, mDisplayDeviceConfig,
+                            AUTO_BRIGHTNESS_MODE_IDLE, mDisplayWhiteBalanceController);
             if (idleModeBrightnessMapper != null) {
-                brightnessMappers.append(AUTO_BRIGHTNESS_MODE_IDLE,
-                        idleModeBrightnessMapper);
+                brightnessMappers.append(AUTO_BRIGHTNESS_MODE_IDLE, idleModeBrightnessMapper);
             }
         }
 
@@ -1181,7 +1179,7 @@
         }
 
         if (defaultModeBrightnessMapper != null) {
-            final float dozeScaleFactor = resources.getFraction(
+            final float dozeScaleFactor = context.getResources().getFraction(
                     com.android.internal.R.fraction.config_screenAutoBrightnessDozeScaleFactor,
                     1, 1);
 
@@ -1265,14 +1263,14 @@
                     .getAutoBrightnessBrighteningLightDebounceIdle();
             long darkeningLightDebounceIdle = mDisplayDeviceConfig
                     .getAutoBrightnessDarkeningLightDebounceIdle();
-            boolean autoBrightnessResetAmbientLuxAfterWarmUp = resources.getBoolean(
+            boolean autoBrightnessResetAmbientLuxAfterWarmUp = context.getResources().getBoolean(
                     com.android.internal.R.bool.config_autoBrightnessResetAmbientLuxAfterWarmUp);
 
-            int lightSensorWarmUpTimeConfig = resources.getInteger(
+            int lightSensorWarmUpTimeConfig = context.getResources().getInteger(
                     com.android.internal.R.integer.config_lightSensorWarmupTime);
-            int lightSensorRate = resources.getInteger(
+            int lightSensorRate = context.getResources().getInteger(
                     com.android.internal.R.integer.config_autoBrightnessLightSensorRate);
-            int initialLightSensorRate = resources.getInteger(
+            int initialLightSensorRate = context.getResources().getInteger(
                     com.android.internal.R.integer.config_autoBrightnessInitialLightSensorRate);
             if (initialLightSensorRate == -1) {
                 initialLightSensorRate = lightSensorRate;
@@ -1992,7 +1990,8 @@
                     boolean isIncreasing = animateValue > currentBrightness;
                     final float rampSpeed;
                     final boolean idle = mAutomaticBrightnessController != null
-                            && mAutomaticBrightnessController.isInIdleMode();
+                            && mAutomaticBrightnessController.getMode()
+                            == AUTO_BRIGHTNESS_MODE_IDLE;
                     if (isIncreasing && slowChange) {
                         rampSpeed = idle ? mBrightnessRampRateSlowIncreaseIdle
                                 : mBrightnessRampRateSlowIncrease;
@@ -2216,6 +2215,18 @@
     }
 
     @Override
+    public float[] getCurrentAutoBrightnessLevels() {
+        // The old DPC is no longer supported
+        return new float[0];
+    }
+
+    @Override
+    public float[] getCurrentAutoBrightnessLuxLevels() {
+        // The old DPC is no longer supported
+        return new float[0];
+    }
+
+    @Override
     public BrightnessInfo getBrightnessInfo() {
         synchronized (mCachedBrightnessInfo) {
             return new BrightnessInfo(
@@ -2900,7 +2911,7 @@
         // or the nits is invalid.
         if (brightnessIsTemporary
                 || mAutomaticBrightnessController == null
-                || mAutomaticBrightnessController.isInIdleMode()
+                || mAutomaticBrightnessController.getMode() == AUTO_BRIGHTNESS_MODE_IDLE
                 || !autobrightnessEnabled
                 || mBrightnessTracker == null
                 || !mUseAutoBrightness
@@ -3645,12 +3656,11 @@
                     userNits);
         }
 
-        BrightnessMappingStrategy getDefaultModeBrightnessMapper(Resources resources,
+        BrightnessMappingStrategy getDefaultModeBrightnessMapper(Context context,
                 DisplayDeviceConfig displayDeviceConfig,
                 DisplayWhiteBalanceController displayWhiteBalanceController) {
-            return BrightnessMappingStrategy.create(resources,
-                    displayDeviceConfig, AUTO_BRIGHTNESS_MODE_DEFAULT,
-                    displayWhiteBalanceController);
+            return BrightnessMappingStrategy.create(context, displayDeviceConfig,
+                    AUTO_BRIGHTNESS_MODE_DEFAULT, displayWhiteBalanceController);
         }
 
         HysteresisLevels getHysteresisLevels(float[] brighteningThresholdsPercentages,
diff --git a/services/core/java/com/android/server/display/DisplayPowerController2.java b/services/core/java/com/android/server/display/DisplayPowerController2.java
index 6d09cc9..2efda08 100644
--- a/services/core/java/com/android/server/display/DisplayPowerController2.java
+++ b/services/core/java/com/android/server/display/DisplayPowerController2.java
@@ -17,7 +17,9 @@
 package com.android.server.display;
 
 import static com.android.server.display.AutomaticBrightnessController.AUTO_BRIGHTNESS_MODE_DEFAULT;
+import static com.android.server.display.AutomaticBrightnessController.AUTO_BRIGHTNESS_MODE_DOZE;
 import static com.android.server.display.AutomaticBrightnessController.AUTO_BRIGHTNESS_MODE_IDLE;
+import static com.android.server.display.config.DisplayBrightnessMappingConfig.autoBrightnessPresetToString;
 
 import android.animation.Animator;
 import android.animation.ObjectAnimator;
@@ -623,7 +625,7 @@
             mCdsi = null;
         }
 
-        setUpAutoBrightness(resources, handler);
+        setUpAutoBrightness(context, handler);
 
         mColorFadeEnabled = mInjector.isColorFadeEnabled()
                 && !resources.getBoolean(
@@ -904,7 +906,7 @@
         // updated here.
         loadBrightnessRampRates();
         loadNitsRange(mContext.getResources());
-        setUpAutoBrightness(mContext.getResources(), mHandler);
+        setUpAutoBrightness(mContext, mHandler);
         reloadReduceBrightColours();
         setAnimatorRampSpeeds(/* isIdleMode= */ false);
 
@@ -949,7 +951,7 @@
                 DisplayPowerState.SCREEN_BRIGHTNESS_FLOAT,
                 DisplayPowerState.SCREEN_SDR_BRIGHTNESS_FLOAT);
         setAnimatorRampSpeeds(mAutomaticBrightnessController != null
-                && mAutomaticBrightnessController.isInIdleMode());
+                && mAutomaticBrightnessController.getMode() == AUTO_BRIGHTNESS_MODE_IDLE);
         mScreenBrightnessRampAnimator.setListener(mRampAnimatorListener);
 
         noteScreenState(mPowerState.getScreenState());
@@ -975,10 +977,15 @@
         mContext.getContentResolver().registerContentObserver(
                 Settings.System.getUriFor(Settings.System.SCREEN_BRIGHTNESS_MODE),
                 false /*notifyForDescendants*/, mSettingsObserver, UserHandle.USER_ALL);
+        if (mFlags.areAutoBrightnessModesEnabled()) {
+            mContext.getContentResolver().registerContentObserver(
+                    Settings.System.getUriFor(Settings.System.SCREEN_BRIGHTNESS_FOR_ALS),
+                    /* notifyForDescendants= */ false, mSettingsObserver, UserHandle.USER_CURRENT);
+        }
         handleBrightnessModeChange();
     }
 
-    private void setUpAutoBrightness(Resources resources, Handler handler) {
+    private void setUpAutoBrightness(Context context, Handler handler) {
         mUseSoftwareAutoBrightnessConfig = mDisplayDeviceConfig.isAutoBrightnessAvailable();
 
         if (!mUseSoftwareAutoBrightnessConfig) {
@@ -988,16 +995,16 @@
         SparseArray<BrightnessMappingStrategy> brightnessMappers = new SparseArray<>();
 
         BrightnessMappingStrategy defaultModeBrightnessMapper =
-                mInjector.getDefaultModeBrightnessMapper(resources, mDisplayDeviceConfig,
+                mInjector.getDefaultModeBrightnessMapper(context, mDisplayDeviceConfig,
                         mDisplayWhiteBalanceController);
         brightnessMappers.append(AUTO_BRIGHTNESS_MODE_DEFAULT,
                 defaultModeBrightnessMapper);
 
-        final boolean isIdleScreenBrightnessEnabled = resources.getBoolean(
+        final boolean isIdleScreenBrightnessEnabled = context.getResources().getBoolean(
                 R.bool.config_enableIdleScreenBrightnessMode);
         if (isIdleScreenBrightnessEnabled) {
             BrightnessMappingStrategy idleModeBrightnessMapper =
-                    BrightnessMappingStrategy.create(resources, mDisplayDeviceConfig,
+                    BrightnessMappingStrategy.create(context, mDisplayDeviceConfig,
                             AUTO_BRIGHTNESS_MODE_IDLE,
                             mDisplayWhiteBalanceController);
             if (idleModeBrightnessMapper != null) {
@@ -1006,6 +1013,13 @@
             }
         }
 
+        BrightnessMappingStrategy dozeModeBrightnessMapper =
+                BrightnessMappingStrategy.create(context, mDisplayDeviceConfig,
+                        AUTO_BRIGHTNESS_MODE_DOZE, mDisplayWhiteBalanceController);
+        if (mFlags.areAutoBrightnessModesEnabled() && dozeModeBrightnessMapper != null) {
+            brightnessMappers.put(AUTO_BRIGHTNESS_MODE_DOZE, dozeModeBrightnessMapper);
+        }
+
         float userLux = BrightnessMappingStrategy.INVALID_LUX;
         float userNits = BrightnessMappingStrategy.INVALID_NITS;
         if (mAutomaticBrightnessController != null) {
@@ -1014,7 +1028,7 @@
         }
 
         if (defaultModeBrightnessMapper != null) {
-            final float dozeScaleFactor = resources.getFraction(
+            final float dozeScaleFactor = context.getResources().getFraction(
                     R.fraction.config_screenAutoBrightnessDozeScaleFactor,
                     1, 1);
 
@@ -1098,14 +1112,14 @@
                     .getAutoBrightnessBrighteningLightDebounceIdle();
             long darkeningLightDebounceIdle = mDisplayDeviceConfig
                     .getAutoBrightnessDarkeningLightDebounceIdle();
-            boolean autoBrightnessResetAmbientLuxAfterWarmUp = resources.getBoolean(
+            boolean autoBrightnessResetAmbientLuxAfterWarmUp = context.getResources().getBoolean(
                     R.bool.config_autoBrightnessResetAmbientLuxAfterWarmUp);
 
-            int lightSensorWarmUpTimeConfig = resources.getInteger(
+            int lightSensorWarmUpTimeConfig = context.getResources().getInteger(
                     R.integer.config_lightSensorWarmupTime);
-            int lightSensorRate = resources.getInteger(
+            int lightSensorRate = context.getResources().getInteger(
                     R.integer.config_autoBrightnessLightSensorRate);
-            int initialLightSensorRate = resources.getInteger(
+            int initialLightSensorRate = context.getResources().getInteger(
                     R.integer.config_autoBrightnessInitialLightSensorRate);
             if (initialLightSensorRate == -1) {
                 initialLightSensorRate = lightSensorRate;
@@ -1349,6 +1363,13 @@
         animateScreenStateChange(state, mDisplayStateController.shouldPerformScreenOffTransition());
         state = mPowerState.getScreenState();
 
+        // Switch to doze auto-brightness mode if needed
+        if (mFlags.areAutoBrightnessModesEnabled() && mAutomaticBrightnessController != null
+                && mAutomaticBrightnessController.getMode() != AUTO_BRIGHTNESS_MODE_IDLE) {
+            setAutomaticScreenBrightnessMode(Display.isDozeState(state)
+                    ? AUTO_BRIGHTNESS_MODE_DOZE : AUTO_BRIGHTNESS_MODE_DEFAULT);
+        }
+
         final boolean userSetBrightnessChanged = mDisplayBrightnessController
                 .updateUserSetScreenBrightness();
 
@@ -1633,7 +1654,8 @@
                     boolean isIncreasing = animateValue > currentBrightness;
                     final float rampSpeed;
                     final boolean idle = mAutomaticBrightnessController != null
-                            && mAutomaticBrightnessController.isInIdleMode();
+                            && mAutomaticBrightnessController.getMode()
+                            == AUTO_BRIGHTNESS_MODE_IDLE;
                     if (isIncreasing && slowChange) {
                         rampSpeed = idle ? mBrightnessRampRateSlowIncreaseIdle
                                 : mBrightnessRampRateSlowIncrease;
@@ -1865,6 +1887,18 @@
     }
 
     @Override
+    public float[] getCurrentAutoBrightnessLevels() {
+        return mDisplayDeviceConfig.getAutoBrightnessBrighteningLevels(
+                mAutomaticBrightnessController.getMode());
+    }
+
+    @Override
+    public float[] getCurrentAutoBrightnessLuxLevels() {
+        return mDisplayDeviceConfig.getAutoBrightnessBrighteningLevelsLux(
+                mAutomaticBrightnessController.getMode());
+    }
+
+    @Override
     public BrightnessInfo getBrightnessInfo() {
         synchronized (mCachedBrightnessInfo) {
             return new BrightnessInfo(
@@ -2439,7 +2473,7 @@
         // or the nits is invalid.
         if (brightnessIsTemporary
                 || mAutomaticBrightnessController == null
-                || mAutomaticBrightnessController.isInIdleMode()
+                || mAutomaticBrightnessController.getMode() == AUTO_BRIGHTNESS_MODE_IDLE
                 || !autobrightnessEnabled
                 || mBrightnessTracker == null
                 || !shouldUseAutoBrightness
@@ -2984,6 +3018,16 @@
         public void onChange(boolean selfChange, Uri uri) {
             if (uri.equals(Settings.System.getUriFor(Settings.System.SCREEN_BRIGHTNESS_MODE))) {
                 handleBrightnessModeChange();
+            } else if (uri.equals(Settings.System.getUriFor(
+                    Settings.System.SCREEN_BRIGHTNESS_FOR_ALS))) {
+                int preset = Settings.System.getIntForUser(mContext.getContentResolver(),
+                        Settings.System.SCREEN_BRIGHTNESS_FOR_ALS,
+                        Settings.System.SCREEN_BRIGHTNESS_AUTOMATIC_NORMAL,
+                        UserHandle.USER_CURRENT);
+                Slog.i(mTag, "Setting up auto-brightness for preset "
+                        + autoBrightnessPresetToString(preset));
+                setUpAutoBrightness(mContext, mHandler);
+                sendUpdatePowerState();
             } else {
                 handleSettingsChange(false /* userSwitch */);
             }
@@ -3102,12 +3146,11 @@
                     userNits);
         }
 
-        BrightnessMappingStrategy getDefaultModeBrightnessMapper(Resources resources,
+        BrightnessMappingStrategy getDefaultModeBrightnessMapper(Context context,
                 DisplayDeviceConfig displayDeviceConfig,
                 DisplayWhiteBalanceController displayWhiteBalanceController) {
-            return BrightnessMappingStrategy.create(resources,
-                    displayDeviceConfig, AUTO_BRIGHTNESS_MODE_DEFAULT,
-                    displayWhiteBalanceController);
+            return BrightnessMappingStrategy.create(context, displayDeviceConfig,
+                    AUTO_BRIGHTNESS_MODE_DEFAULT, displayWhiteBalanceController);
         }
 
         HysteresisLevels getHysteresisLevels(float[] brighteningThresholdsPercentages,
diff --git a/services/core/java/com/android/server/display/DisplayPowerControllerInterface.java b/services/core/java/com/android/server/display/DisplayPowerControllerInterface.java
index c279184..5b350db 100644
--- a/services/core/java/com/android/server/display/DisplayPowerControllerInterface.java
+++ b/services/core/java/com/android/server/display/DisplayPowerControllerInterface.java
@@ -237,4 +237,14 @@
      * Indicate that boot has been completed and the screen is ready to update.
      */
     void onBootCompleted();
+
+    /**
+     * Get the current brightness levels used to determine automatic brightness based on lux levels.
+     */
+    float[] getCurrentAutoBrightnessLevels();
+
+    /**
+     * Get the current lux levels used to determine automatic brightness.
+     */
+    float[] getCurrentAutoBrightnessLuxLevels();
 }
diff --git a/services/core/java/com/android/server/display/config/DisplayBrightnessMappingConfig.java b/services/core/java/com/android/server/display/config/DisplayBrightnessMappingConfig.java
index 2162850..6978686 100644
--- a/services/core/java/com/android/server/display/config/DisplayBrightnessMappingConfig.java
+++ b/services/core/java/com/android/server/display/config/DisplayBrightnessMappingConfig.java
@@ -16,11 +16,17 @@
 
 package com.android.server.display.config;
 
+import static com.android.server.display.AutomaticBrightnessController.AUTO_BRIGHTNESS_MODE_DEFAULT;
+import static com.android.server.display.AutomaticBrightnessController.AUTO_BRIGHTNESS_MODE_DOZE;
+import static com.android.server.display.AutomaticBrightnessController.AUTO_BRIGHTNESS_MODE_IDLE;
+
 import android.content.Context;
 import android.os.PowerManager;
+import android.provider.Settings;
 import android.util.Spline;
 
 import com.android.internal.display.BrightnessSynchronizer;
+import com.android.server.display.AutomaticBrightnessController;
 import com.android.server.display.DisplayDeviceConfig;
 import com.android.server.display.feature.DisplayManagerFlags;
 
@@ -33,7 +39,9 @@
  */
 public class DisplayBrightnessMappingConfig {
 
-    private static final String DEFAULT_BRIGHTNESS_MAPPING_KEY = "default_normal";
+    private static final String DEFAULT_BRIGHTNESS_MAPPING_KEY =
+            AutoBrightnessModeName._default.getRawName() + "_"
+                    + AutoBrightnessSettingName.normal.getRawName();
 
     /**
      * Array of desired screen brightness in nits corresponding to the lux values
@@ -45,19 +53,22 @@
 
     /**
      * Map of arrays of desired screen brightness corresponding to the lux values
-     * in mBrightnessLevelsLuxMap, indexed by the auto-brightness mode and the brightness setting.
+     * in mBrightnessLevelsLuxMap, indexed by the auto-brightness mode and the brightness preset.
      * The brightness values must be non-negative and non-decreasing. They must be between
      * {@link PowerManager.BRIGHTNESS_MIN} and {@link PowerManager.BRIGHTNESS_MAX}.
      *
-     * The keys are a concatenation of the auto-brightness mode and the brightness setting
-     * separated by an underscore, e.g. default_normal, default_dim, default_bright, doze_normal,
-     * doze_dim, doze_bright.
+     * The keys are a concatenation of the auto-brightness mode and the brightness preset separated
+     * by an underscore, e.g. default_normal, default_dim, default_bright, doze_normal, doze_dim,
+     * doze_bright.
+     *
+     * The presets are used on devices that allow users to choose from a set of predefined options
+     * in display auto-brightness settings.
      */
     private final Map<String, float[]> mBrightnessLevelsMap = new HashMap<>();
 
     /**
      * Map of arrays of light sensor lux values to define our levels for auto-brightness support,
-     * indexed by the auto-brightness mode and the brightness setting.
+     * indexed by the auto-brightness mode and the brightness preset.
      *
      * The first lux value in every array is always 0.
      *
@@ -69,9 +80,12 @@
      * Spline interpolation is used to determine the auto-brightness values for lux levels between
      * these control points.
      *
-     * The keys are a concatenation of the auto-brightness mode and the brightness setting
-     * separated by an underscore, e.g. default_normal, default_dim, default_bright, doze_normal,
-     * doze_dim, doze_bright.
+     * The keys are a concatenation of the auto-brightness mode and the brightness preset separated
+     * by an underscore, e.g. default_normal, default_dim, default_bright, doze_normal, doze_dim,
+     * doze_bright.
+     *
+     * The presets are used on devices that allow users to choose from a set of predefined options
+     * in display auto-brightness settings.
      */
     private final Map<String, float[]> mBrightnessLevelsLuxMap = new HashMap<>();
 
@@ -138,19 +152,16 @@
     }
 
     /**
-     * @return The default auto-brightness brightening ambient lux levels
-     */
-    public float[] getLuxArray() {
-        return mBrightnessLevelsLuxMap.get(DEFAULT_BRIGHTNESS_MAPPING_KEY);
-    }
-
-    /**
      * @param mode The auto-brightness mode
-     * @param setting The brightness setting
-     * @return Auto brightness brightening ambient lux levels for the specified mode and setting
+     * @param preset The brightness preset. Presets are used on devices that allow users to choose
+     *               from a set of predefined options in display auto-brightness settings.
+     * @return The default auto-brightness brightening ambient lux levels for the specified mode
+     * and preset
      */
-    public float[] getLuxArray(String mode, String setting) {
-        return mBrightnessLevelsLuxMap.get(mode + "_" + setting);
+    public float[] getLuxArray(@AutomaticBrightnessController.AutomaticBrightnessMode int mode,
+            int preset) {
+        return mBrightnessLevelsLuxMap.get(
+                autoBrightnessModeToString(mode) + "_" + autoBrightnessPresetToString(preset));
     }
 
     /**
@@ -161,19 +172,15 @@
     }
 
     /**
-     * @return The default auto-brightness brightening levels
-     */
-    public float[] getBrightnessArray() {
-        return mBrightnessLevelsMap.get(DEFAULT_BRIGHTNESS_MAPPING_KEY);
-    }
-
-    /**
      * @param mode The auto-brightness mode
-     * @param setting The brightness setting
-     * @return Auto brightness brightening ambient lux levels for the specified mode and setting
+     * @param preset The brightness preset. Presets are used on devices that allow users to choose
+     *               from a set of predefined options in display auto-brightness settings.
+     * @return The default auto-brightness brightening levels for the specified mode and preset
      */
-    public float[] getBrightnessArray(String mode, String setting) {
-        return mBrightnessLevelsMap.get(mode + "_" + setting);
+    public float[] getBrightnessArray(
+            @AutomaticBrightnessController.AutomaticBrightnessMode int mode, int preset) {
+        return mBrightnessLevelsMap.get(
+                autoBrightnessModeToString(mode) + "_" + autoBrightnessPresetToString(preset));
     }
 
     @Override
@@ -205,6 +212,44 @@
                 + ", mBrightnessLevelsMap= " + brightnessLevelsMapString;
     }
 
+    /**
+     * @param mode The auto-brightness mode
+     * @return The string representing the mode
+     */
+    public static String autoBrightnessModeToString(
+            @AutomaticBrightnessController.AutomaticBrightnessMode int mode) {
+        switch (mode) {
+            case AUTO_BRIGHTNESS_MODE_DEFAULT -> {
+                return AutoBrightnessModeName._default.getRawName();
+            }
+            case AUTO_BRIGHTNESS_MODE_IDLE -> {
+                return AutoBrightnessModeName.idle.getRawName();
+            }
+            case AUTO_BRIGHTNESS_MODE_DOZE -> {
+                return AutoBrightnessModeName.doze.getRawName();
+            }
+            default -> throw new IllegalArgumentException("Unknown auto-brightness mode: " + mode);
+        }
+    }
+
+    /**
+     * @param preset The brightness preset. Presets are used on devices that allow users to choose
+     *               from a set of predefined options in display auto-brightness settings.
+     * @return The string representing the preset
+     */
+    public static String autoBrightnessPresetToString(int preset) {
+        return switch (preset) {
+            case Settings.System.SCREEN_BRIGHTNESS_AUTOMATIC_DIM ->
+                    AutoBrightnessSettingName.dim.getRawName();
+            case Settings.System.SCREEN_BRIGHTNESS_AUTOMATIC_NORMAL ->
+                    AutoBrightnessSettingName.normal.getRawName();
+            case Settings.System.SCREEN_BRIGHTNESS_AUTOMATIC_BRIGHT ->
+                    AutoBrightnessSettingName.bright.getRawName();
+            default -> throw new IllegalArgumentException(
+                    "Unknown auto-brightness preset value: " + preset);
+        };
+    }
+
     private float[] brightnessArrayIntToFloat(int[] brightnessInt,
             Spline backlightToBrightnessSpline) {
         float[] brightnessFloat = new float[brightnessInt.length];
diff --git a/services/core/java/com/android/server/hdmi/HdmiCecLocalDevice.java b/services/core/java/com/android/server/hdmi/HdmiCecLocalDevice.java
index 64abb81..81204ef 100644
--- a/services/core/java/com/android/server/hdmi/HdmiCecLocalDevice.java
+++ b/services/core/java/com/android/server/hdmi/HdmiCecLocalDevice.java
@@ -1314,7 +1314,6 @@
      */
     protected void disableDevice(
             boolean initiatedByCec, final PendingActionClearedCallback originalCallback) {
-        removeAction(AbsoluteVolumeAudioStatusAction.class);
         removeAction(SetAudioVolumeLevelDiscoveryAction.class);
         removeAction(ActiveSourceAction.class);
         removeAction(ResendCecCommandAction.class);
diff --git a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceSource.java b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceSource.java
index 29303aa..6157402 100644
--- a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceSource.java
+++ b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceSource.java
@@ -308,7 +308,6 @@
     protected void disableDevice(boolean initiatedByCec, PendingActionClearedCallback callback) {
         removeAction(OneTouchPlayAction.class);
         removeAction(DevicePowerStatusAction.class);
-        removeAction(AbsoluteVolumeAudioStatusAction.class);
 
         super.disableDevice(initiatedByCec, callback);
     }
diff --git a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java
index 5831b29..1cd267d 100644
--- a/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java
+++ b/services/core/java/com/android/server/hdmi/HdmiCecLocalDeviceTv.java
@@ -1336,7 +1336,6 @@
         removeAction(OneTouchRecordAction.class);
         removeAction(TimerRecordingAction.class);
         removeAction(NewDeviceAction.class);
-        removeAction(AbsoluteVolumeAudioStatusAction.class);
         // Remove pending actions.
         removeAction(RequestActiveSourceAction.class);
 
diff --git a/services/core/java/com/android/server/hdmi/HdmiControlService.java b/services/core/java/com/android/server/hdmi/HdmiControlService.java
index 9253706..eaf754d 100644
--- a/services/core/java/com/android/server/hdmi/HdmiControlService.java
+++ b/services/core/java/com/android/server/hdmi/HdmiControlService.java
@@ -3793,6 +3793,11 @@
                 }
             }
         });
+
+        // Make sure we switch away from absolute volume behavior (AVB) when entering standby.
+        // We do this because AVB should not be used unless AbsoluteVolumeAudioStatusAction exists,
+        // and the action cannot exist in standby because there are no local devices.
+        checkAndUpdateAbsoluteVolumeBehavior();
     }
 
     boolean canGoToStandby() {
@@ -4446,10 +4451,11 @@
      * This allows the volume level of the System Audio device to be tracked and set by Android.
      *
      * Absolute volume behavior requires the following conditions:
-     * 1. If the System Audio Device is an Audio System: System Audio Mode is active
-     * 2. All AVB-capable audio output devices are already using full/absolute volume behavior
-     * 3. CEC volume is enabled
-     * 4. The System Audio device supports the <Set Audio Volume Level> message
+     * 1. The device is not in standby or transient to standby
+     * 2. If the System Audio Device is an Audio System: System Audio Mode is active
+     * 3. All AVB-capable audio output devices are already using full/absolute volume behavior
+     * 4. CEC volume is enabled
+     * 5. The System Audio device supports the <Set Audio Volume Level> message
      *
      * This method enables adjust-only absolute volume behavior on TV panels when conditions
      * 1, 2, and 3 are met, but condition 4 is not. This allows TVs to track the volume level of
@@ -4465,10 +4471,16 @@
             return;
         }
 
+        // Condition 1: The device is not in standby or transient to standby
+        if (mPowerStatusController != null && isPowerStandbyOrTransient()) {
+            switchToFullVolumeBehavior();
+            return;
+        }
+
         HdmiCecLocalDevice localCecDevice;
         if (isTvDevice() && tv() != null) {
             localCecDevice = tv();
-            // Condition 1: TVs need System Audio Mode to be active
+            // Condition 2: TVs need System Audio Mode to be active
             // (Doesn't apply to Playback Devices, where if SAM isn't active, we assume the
             // TV is the System Audio Device instead.)
             if (!isSystemAudioActivated()) {
@@ -4485,7 +4497,7 @@
         HdmiDeviceInfo systemAudioDeviceInfo = getDeviceInfo(
                 localCecDevice.findAudioReceiverAddress());
 
-        // Condition 2: All AVB-capable audio outputs already use full/absolute volume behavior
+        // Condition 3: All AVB-capable audio outputs already use full/absolute volume behavior
         // We only need to check the first AVB-capable audio output because only TV panels
         // have more than one of them, and they always have the same volume behavior.
         @AudioManager.DeviceVolumeBehavior int currentVolumeBehavior =
@@ -4493,7 +4505,7 @@
         boolean alreadyUsingFullOrAbsoluteVolume =
                 FULL_AND_ABSOLUTE_VOLUME_BEHAVIORS.contains(currentVolumeBehavior);
 
-        // Condition 3: CEC volume is enabled
+        // Condition 4: CEC volume is enabled
         boolean cecVolumeEnabled =
                 getHdmiCecVolumeControl() == HdmiControlManager.VOLUME_CONTROL_ENABLED;
 
@@ -4509,7 +4521,7 @@
             return;
         }
 
-        // Condition 4: The System Audio device supports <Set Audio Volume Level>
+        // Condition 5: The System Audio device supports <Set Audio Volume Level>
         switch (systemAudioDeviceInfo.getDeviceFeatures().getSetAudioVolumeLevelSupport()) {
             case DeviceFeatures.FEATURE_SUPPORTED:
                 if (currentVolumeBehavior != AudioManager.DEVICE_VOLUME_BEHAVIOR_ABSOLUTE) {
@@ -4556,6 +4568,8 @@
      * are currently used. Removes the action for handling volume updates for these behaviors.
      */
     private void switchToFullVolumeBehavior() {
+        Slog.d(TAG, "Switching to full volume behavior");
+
         if (playback() != null) {
             playback().removeAvbAudioStatusAction();
         } else if (tv() != null) {
@@ -4597,12 +4611,14 @@
         // Otherwise, enable adjust-only AVB on TVs only.
         if (systemAudioDevice.getDeviceFeatures().getSetAudioVolumeLevelSupport()
                 == DeviceFeatures.FEATURE_SUPPORTED) {
+            Slog.d(TAG, "Enabling absolute volume behavior");
             for (AudioDeviceAttributes device : getAvbCapableAudioOutputDevices()) {
                 getAudioDeviceVolumeManager().setDeviceAbsoluteVolumeBehavior(
                         device, volumeInfo, mServiceThreadExecutor,
                         mAbsoluteVolumeChangedListener, true);
             }
         } else if (tv() != null) {
+            Slog.d(TAG, "Enabling adjust-only absolute volume behavior");
             for (AudioDeviceAttributes device : getAvbCapableAudioOutputDevices()) {
                 getAudioDeviceVolumeManager().setDeviceAbsoluteVolumeAdjustOnlyBehavior(
                         device, volumeInfo, mServiceThreadExecutor,
diff --git a/services/core/java/com/android/server/inputmethod/InputMethodUtils.java b/services/core/java/com/android/server/inputmethod/InputMethodUtils.java
index 773293f..a6c5ad5 100644
--- a/services/core/java/com/android/server/inputmethod/InputMethodUtils.java
+++ b/services/core/java/com/android/server/inputmethod/InputMethodUtils.java
@@ -356,15 +356,6 @@
                     new TextUtils.SimpleStringSplitter(INPUT_METHOD_SUBTYPE_SEPARATOR));
         }
 
-        List<String> getEnabledInputMethodNames() {
-            List<String> result = new ArrayList<>();
-            for (Pair<String, ArrayList<String>> pair :
-                    getEnabledInputMethodsAndSubtypeListLocked()) {
-                result.add(pair.first);
-            }
-            return result;
-        }
-
         void appendAndPutEnabledInputMethodLocked(String id) {
             if (TextUtils.isEmpty(mEnabledInputMethodsStrCache)) {
                 // Add in the newly enabled input method.
diff --git a/services/core/java/com/android/server/integrity/AppIntegrityManagerServiceImpl.java b/services/core/java/com/android/server/integrity/AppIntegrityManagerServiceImpl.java
index f2dcba4..5514ec7 100644
--- a/services/core/java/com/android/server/integrity/AppIntegrityManagerServiceImpl.java
+++ b/services/core/java/com/android/server/integrity/AppIntegrityManagerServiceImpl.java
@@ -59,6 +59,7 @@
 
 import com.android.internal.R;
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.pm.parsing.PackageParser2;
 import com.android.internal.pm.pkg.parsing.ParsingPackageUtils;
 import com.android.internal.util.ArrayUtils;
 import com.android.internal.util.FrameworkStatsLog;
@@ -67,7 +68,7 @@
 import com.android.server.integrity.model.IntegrityCheckResult;
 import com.android.server.integrity.model.RuleMetadata;
 import com.android.server.pm.PackageManagerServiceUtils;
-import com.android.server.pm.parsing.PackageParser2;
+import com.android.server.pm.parsing.PackageParserUtils;
 
 import java.io.ByteArrayInputStream;
 import java.io.File;
@@ -141,7 +142,7 @@
         return new AppIntegrityManagerServiceImpl(
                 context,
                 LocalServices.getService(PackageManagerInternal.class),
-                PackageParser2::forParsingFileWithDefaults,
+                PackageParserUtils::forParsingFileWithDefaults,
                 RuleEvaluationEngine.getRuleEvaluationEngine(),
                 IntegrityFileManager.getInstance(),
                 handlerThread.getThreadHandler());
diff --git a/services/core/java/com/android/server/media/AudioPoliciesDeviceRouteController.java b/services/core/java/com/android/server/media/AudioPoliciesDeviceRouteController.java
index 8504495..0eb9166 100644
--- a/services/core/java/com/android/server/media/AudioPoliciesDeviceRouteController.java
+++ b/services/core/java/com/android/server/media/AudioPoliciesDeviceRouteController.java
@@ -87,6 +87,8 @@
 
     @NonNull private final AudioDeviceCallback mAudioDeviceCallback = new AudioDeviceCallbackImpl();
 
+    @MediaRoute2Info.SuitabilityStatus private final int mBuiltInSpeakerSuitabilityStatus;
+
     @NonNull
     private final AudioManager.OnDevicesForAttributesChangedListener
             mOnDevicesForAttributesChangedListener = this::onDevicesForAttributesChangedListener;
@@ -113,6 +115,10 @@
         mHandler = new Handler(Objects.requireNonNull(looper));
         mStrategyForMedia = Objects.requireNonNull(strategyForMedia);
         mOnDeviceRouteChangedListener = Objects.requireNonNull(onDeviceRouteChangedListener);
+
+        mBuiltInSpeakerSuitabilityStatus =
+                DeviceRouteController.getBuiltInSpeakerSuitabilityStatus(mContext);
+
         mBluetoothRouteController =
                 new AudioPoliciesBluetoothRouteController(
                         mContext, btAdapter, this::rebuildAvailableRoutesAndNotify);
@@ -373,14 +379,19 @@
             // from getting an id using BluetoothRouteController#getRouteIdForBluetoothAddress.
             routeId = systemRouteInfo.mDefaultRouteId;
         }
-        return new MediaRoute2Info.Builder(routeId, humanReadableName)
+        MediaRoute2Info.Builder builder = new MediaRoute2Info.Builder(routeId, humanReadableName)
                 .setType(systemRouteInfo.mMediaRoute2InfoType)
                 .setAddress(address)
                 .setSystemRoute(true)
                 .addFeature(FEATURE_LIVE_AUDIO)
                 .addFeature(FEATURE_LOCAL_PLAYBACK)
-                .setConnectionState(MediaRoute2Info.CONNECTION_STATE_CONNECTED)
-                .build();
+                .setConnectionState(MediaRoute2Info.CONNECTION_STATE_CONNECTED);
+
+        if (systemRouteInfo.mMediaRoute2InfoType == MediaRoute2Info.TYPE_BUILTIN_SPEAKER) {
+            builder.setSuitabilityStatus(mBuiltInSpeakerSuitabilityStatus);
+        }
+
+        return builder.build();
     }
 
     /**
diff --git a/services/core/java/com/android/server/media/DeviceRouteController.java b/services/core/java/com/android/server/media/DeviceRouteController.java
index 9f175a9..8b62cc9 100644
--- a/services/core/java/com/android/server/media/DeviceRouteController.java
+++ b/services/core/java/com/android/server/media/DeviceRouteController.java
@@ -81,6 +81,30 @@
         }
     }
 
+    /** Returns device route availability status. */
+    @MediaRoute2Info.SuitabilityStatus
+    static int getBuiltInSpeakerSuitabilityStatus(@NonNull Context context) {
+        if (!Flags.enableBuiltInSpeakerRouteSuitabilityStatuses()) {
+            // Route is always suitable if the flag is disabled.
+            return MediaRoute2Info.SUITABILITY_STATUS_SUITABLE_FOR_DEFAULT_TRANSFER;
+        }
+
+        int availabilityStatus =
+                context.getResources()
+                        .getInteger(
+                                com.android.internal.R.integer
+                                        .config_mediaRouter_builtInSpeakerSuitability);
+
+        switch (availabilityStatus) {
+            case MediaRoute2Info.SUITABILITY_STATUS_SUITABLE_FOR_DEFAULT_TRANSFER:
+            case MediaRoute2Info.SUITABILITY_STATUS_SUITABLE_FOR_MANUAL_TRANSFER:
+            case MediaRoute2Info.SUITABILITY_STATUS_NOT_SUITABLE_FOR_TRANSFER:
+                return availabilityStatus;
+            default:
+                return MediaRoute2Info.SUITABILITY_STATUS_SUITABLE_FOR_DEFAULT_TRANSFER;
+        }
+    }
+
     /** Returns the currently selected device (built-in or wired) route. */
     @NonNull
     MediaRoute2Info getSelectedRoute();
diff --git a/services/core/java/com/android/server/media/LegacyDeviceRouteController.java b/services/core/java/com/android/server/media/LegacyDeviceRouteController.java
index c0f2834..65b0ad0 100644
--- a/services/core/java/com/android/server/media/LegacyDeviceRouteController.java
+++ b/services/core/java/com/android/server/media/LegacyDeviceRouteController.java
@@ -72,6 +72,8 @@
     @NonNull
     private final AudioRoutesObserver mAudioRoutesObserver = new AudioRoutesObserver();
 
+    @MediaRoute2Info.SuitabilityStatus private final int mBuiltInSpeakerSuitabilityStatus;
+
     private int mDeviceVolume;
     private MediaRoute2Info mDeviceRoute;
 
@@ -90,6 +92,9 @@
         mAudioManager = audioManager;
         mAudioService = audioService;
 
+        mBuiltInSpeakerSuitabilityStatus =
+                DeviceRouteController.getBuiltInSpeakerSuitabilityStatus(mContext);
+
         AudioRoutesInfo newAudioRoutes = null;
         try {
             newAudioRoutes = mAudioService.startWatchingRoutes(mAudioRoutesObserver);
@@ -165,19 +170,28 @@
         }
 
         synchronized (this) {
-            return new MediaRoute2Info.Builder(
-                    DEVICE_ROUTE_ID, mContext.getResources().getText(name).toString())
-                    .setVolumeHandling(mAudioManager.isVolumeFixed()
-                            ? MediaRoute2Info.PLAYBACK_VOLUME_FIXED
-                            : MediaRoute2Info.PLAYBACK_VOLUME_VARIABLE)
-                    .setVolume(mDeviceVolume)
-                    .setVolumeMax(mAudioManager.getStreamMaxVolume(AudioManager.STREAM_MUSIC))
-                    .setType(type)
-                    .addFeature(FEATURE_LIVE_AUDIO)
-                    .addFeature(FEATURE_LIVE_VIDEO)
-                    .addFeature(FEATURE_LOCAL_PLAYBACK)
-                    .setConnectionState(MediaRoute2Info.CONNECTION_STATE_CONNECTED)
-                    .build();
+            MediaRoute2Info.Builder builder =
+                    new MediaRoute2Info.Builder(
+                                    DEVICE_ROUTE_ID,
+                                    mContext.getResources().getText(name).toString())
+                            .setVolumeHandling(
+                                    mAudioManager.isVolumeFixed()
+                                            ? MediaRoute2Info.PLAYBACK_VOLUME_FIXED
+                                            : MediaRoute2Info.PLAYBACK_VOLUME_VARIABLE)
+                            .setVolume(mDeviceVolume)
+                            .setVolumeMax(
+                                    mAudioManager.getStreamMaxVolume(AudioManager.STREAM_MUSIC))
+                            .setType(type)
+                            .addFeature(FEATURE_LIVE_AUDIO)
+                            .addFeature(FEATURE_LIVE_VIDEO)
+                            .addFeature(FEATURE_LOCAL_PLAYBACK)
+                            .setConnectionState(MediaRoute2Info.CONNECTION_STATE_CONNECTED);
+
+            if (type == TYPE_BUILTIN_SPEAKER) {
+                builder.setSuitabilityStatus(mBuiltInSpeakerSuitabilityStatus);
+            }
+
+            return builder.build();
         }
     }
 
diff --git a/services/core/java/com/android/server/media/MediaRoute2Provider.java b/services/core/java/com/android/server/media/MediaRoute2Provider.java
index 8149847..1bc2a5e 100644
--- a/services/core/java/com/android/server/media/MediaRoute2Provider.java
+++ b/services/core/java/com/android/server/media/MediaRoute2Provider.java
@@ -24,6 +24,7 @@
 import android.media.RouteDiscoveryPreference;
 import android.media.RoutingSessionInfo;
 import android.os.Bundle;
+import android.os.UserHandle;
 
 import com.android.internal.annotations.GuardedBy;
 
@@ -54,8 +55,15 @@
         mCallback = callback;
     }
 
-    public abstract void requestCreateSession(long requestId, String packageName, String routeId,
-            @Nullable Bundle sessionHints);
+    public abstract void requestCreateSession(
+            long requestId,
+            String packageName,
+            String routeId,
+            @Nullable Bundle sessionHints,
+            @RoutingSessionInfo.TransferReason int transferReason,
+            @NonNull UserHandle transferInitiatorUserHandle,
+            @NonNull String transferInitiatorPackageName);
+
     public abstract void releaseSession(long requestId, String sessionId);
 
     public abstract void updateDiscoveryPreference(
@@ -63,7 +71,14 @@
 
     public abstract void selectRoute(long requestId, String sessionId, String routeId);
     public abstract void deselectRoute(long requestId, String sessionId, String routeId);
-    public abstract void transferToRoute(long requestId, String sessionId, String routeId);
+
+    public abstract void transferToRoute(
+            long requestId,
+            @NonNull UserHandle transferInitiatorUserHandle,
+            @NonNull String transferInitiatorPackageName,
+            String sessionId,
+            String routeId,
+            @RoutingSessionInfo.TransferReason int transferReason);
 
     public abstract void setRouteVolume(long requestId, String routeId, int volume);
     public abstract void setSessionVolume(long requestId, String sessionId, int volume);
diff --git a/services/core/java/com/android/server/media/MediaRoute2ProviderServiceProxy.java b/services/core/java/com/android/server/media/MediaRoute2ProviderServiceProxy.java
index 330818e..ae889d8 100644
--- a/services/core/java/com/android/server/media/MediaRoute2ProviderServiceProxy.java
+++ b/services/core/java/com/android/server/media/MediaRoute2ProviderServiceProxy.java
@@ -98,8 +98,14 @@
     }
 
     @Override
-    public void requestCreateSession(long requestId, String packageName, String routeId,
-            Bundle sessionHints) {
+    public void requestCreateSession(
+            long requestId,
+            String packageName,
+            String routeId,
+            Bundle sessionHints,
+            @RoutingSessionInfo.TransferReason int transferReason,
+            @NonNull UserHandle transferInitiatorUserHandle,
+            @NonNull String transferInitiatorPackageName) {
         if (mConnectionReady) {
             mActiveConnection.requestCreateSession(requestId, packageName, routeId, sessionHints);
             updateBinding();
@@ -141,7 +147,13 @@
     }
 
     @Override
-    public void transferToRoute(long requestId, String sessionId, String routeId) {
+    public void transferToRoute(
+            long requestId,
+            @NonNull UserHandle transferInitiatorUserHandle,
+            @NonNull String transferInitiatorPackageName,
+            String sessionId,
+            String routeId,
+            @RoutingSessionInfo.TransferReason int transferReason) {
         if (mConnectionReady) {
             mActiveConnection.transferToRoute(requestId, sessionId, routeId);
         }
@@ -649,6 +661,14 @@
                                     + "Disallowed route: "
                                     + route);
                 }
+
+                if (route.getSuitabilityStatus()
+                        == MediaRoute2Info.SUITABILITY_STATUS_NOT_SUITABLE_FOR_TRANSFER) {
+                    throw new SecurityException(
+                            "Only the system is allowed to set not suitable for transfer status. "
+                                    + "Disallowed route: "
+                                    + route);
+                }
             }
 
             Connection connection = mConnectionRef.get();
diff --git a/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java b/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java
index 5e18727..38f0df4 100644
--- a/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java
+++ b/services/core/java/com/android/server/media/MediaRouter2ServiceImpl.java
@@ -337,18 +337,47 @@
         }
     }
 
-    public void requestCreateSessionWithRouter2(@NonNull IMediaRouter2 router, int requestId,
-            long managerRequestId, @NonNull RoutingSessionInfo oldSession,
-            @NonNull MediaRoute2Info route, Bundle sessionHints) {
+    public void requestCreateSessionWithRouter2(
+            @NonNull IMediaRouter2 router,
+            int requestId,
+            long managerRequestId,
+            @NonNull RoutingSessionInfo oldSession,
+            @NonNull MediaRoute2Info route,
+            Bundle sessionHints,
+            @Nullable UserHandle transferInitiatorUserHandle,
+            @Nullable String transferInitiatorPackageName) {
         Objects.requireNonNull(router, "router must not be null");
         Objects.requireNonNull(oldSession, "oldSession must not be null");
         Objects.requireNonNull(route, "route must not be null");
 
+        synchronized (mLock) {
+            if (managerRequestId == MediaRoute2ProviderService.REQUEST_ID_NONE
+                    || transferInitiatorUserHandle == null
+                    || transferInitiatorPackageName == null) {
+                final IBinder binder = router.asBinder();
+                final RouterRecord routerRecord = mAllRouterRecords.get(binder);
+
+                transferInitiatorUserHandle = Binder.getCallingUserHandle();
+                if (routerRecord != null) {
+                    transferInitiatorPackageName = routerRecord.mPackageName;
+                } else {
+                    transferInitiatorPackageName = mContext.getPackageName();
+                }
+            }
+        }
+
         final long token = Binder.clearCallingIdentity();
         try {
             synchronized (mLock) {
-                requestCreateSessionWithRouter2Locked(requestId, managerRequestId,
-                        router, oldSession, route, sessionHints);
+                requestCreateSessionWithRouter2Locked(
+                        requestId,
+                        managerRequestId,
+                        transferInitiatorUserHandle,
+                        transferInitiatorPackageName,
+                        router,
+                        oldSession,
+                        route,
+                        sessionHints);
             }
         } finally {
             Binder.restoreCallingIdentity(token);
@@ -399,10 +428,11 @@
             throw new IllegalArgumentException("uniqueSessionId must not be empty");
         }
 
+        UserHandle userHandle = Binder.getCallingUserHandle();
         final long token = Binder.clearCallingIdentity();
         try {
             synchronized (mLock) {
-                transferToRouteWithRouter2Locked(router, uniqueSessionId, route);
+                transferToRouteWithRouter2Locked(router, userHandle, uniqueSessionId, route);
             }
         } finally {
             Binder.restoreCallingIdentity(token);
@@ -588,16 +618,28 @@
         }
     }
 
-    public void requestCreateSessionWithManager(@NonNull IMediaRouter2Manager manager,
-            int requestId, @NonNull RoutingSessionInfo oldSession, @NonNull MediaRoute2Info route) {
+    public void requestCreateSessionWithManager(
+            @NonNull IMediaRouter2Manager manager,
+            int requestId,
+            @NonNull RoutingSessionInfo oldSession,
+            @NonNull MediaRoute2Info route,
+            @NonNull UserHandle transferInitiatorUserHandle,
+            @NonNull String transferInitiatorPackageName) {
         Objects.requireNonNull(manager, "manager must not be null");
         Objects.requireNonNull(oldSession, "oldSession must not be null");
         Objects.requireNonNull(route, "route must not be null");
+        Objects.requireNonNull(transferInitiatorUserHandle);
 
         final long token = Binder.clearCallingIdentity();
         try {
             synchronized (mLock) {
-                requestCreateSessionWithManagerLocked(requestId, manager, oldSession, route);
+                requestCreateSessionWithManagerLocked(
+                        requestId,
+                        manager,
+                        oldSession,
+                        route,
+                        transferInitiatorUserHandle,
+                        transferInitiatorPackageName);
             }
         } finally {
             Binder.restoreCallingIdentity(token);
@@ -640,18 +682,32 @@
         }
     }
 
-    public void transferToRouteWithManager(@NonNull IMediaRouter2Manager manager, int requestId,
-            @NonNull String uniqueSessionId, @NonNull MediaRoute2Info route) {
+    public void transferToRouteWithManager(
+            @NonNull IMediaRouter2Manager manager,
+            int requestId,
+            @NonNull String uniqueSessionId,
+            @NonNull MediaRoute2Info route,
+            @NonNull UserHandle transferInitiatorUserHandle,
+            @NonNull String transferInitiatorPackageName) {
         Objects.requireNonNull(manager, "manager must not be null");
         if (TextUtils.isEmpty(uniqueSessionId)) {
             throw new IllegalArgumentException("uniqueSessionId must not be empty");
         }
         Objects.requireNonNull(route, "route must not be null");
+        Objects.requireNonNull(transferInitiatorUserHandle);
+        Objects.requireNonNull(transferInitiatorPackageName);
 
         final long token = Binder.clearCallingIdentity();
         try {
             synchronized (mLock) {
-                transferToRouteWithManagerLocked(requestId, manager, uniqueSessionId, route);
+                transferToRouteWithManagerLocked(
+                        requestId,
+                        manager,
+                        uniqueSessionId,
+                        route,
+                        RoutingSessionInfo.TRANSFER_REASON_SYSTEM_REQUEST,
+                        transferInitiatorUserHandle,
+                        transferInitiatorPackageName);
             }
         } finally {
             Binder.restoreCallingIdentity(token);
@@ -1038,9 +1094,15 @@
     }
 
     @GuardedBy("mLock")
-    private void requestCreateSessionWithRouter2Locked(int requestId, long managerRequestId,
-            @NonNull IMediaRouter2 router, @NonNull RoutingSessionInfo oldSession,
-            @NonNull MediaRoute2Info route, @Nullable Bundle sessionHints) {
+    private void requestCreateSessionWithRouter2Locked(
+            int requestId,
+            long managerRequestId,
+            @NonNull UserHandle transferInitiatorUserHandle,
+            @NonNull String transferInitiatorPackageName,
+            @NonNull IMediaRouter2 router,
+            @NonNull RoutingSessionInfo oldSession,
+            @NonNull MediaRoute2Info route,
+            @Nullable Bundle sessionHints) {
         final IBinder binder = router.asBinder();
         final RouterRecord routerRecord = mAllRouterRecords.get(binder);
 
@@ -1114,9 +1176,16 @@
 
         long uniqueRequestId = toUniqueRequestId(routerRecord.mRouterId, requestId);
         routerRecord.mUserRecord.mHandler.sendMessage(
-                obtainMessage(UserHandler::requestCreateSessionWithRouter2OnHandler,
+                obtainMessage(
+                        UserHandler::requestCreateSessionWithRouter2OnHandler,
                         routerRecord.mUserRecord.mHandler,
-                        uniqueRequestId, managerRequestId, routerRecord, oldSession, route,
+                        uniqueRequestId,
+                        managerRequestId,
+                        transferInitiatorUserHandle,
+                        transferInitiatorPackageName,
+                        routerRecord,
+                        oldSession,
+                        route,
                         sessionHints));
     }
 
@@ -1165,8 +1234,11 @@
     }
 
     @GuardedBy("mLock")
-    private void transferToRouteWithRouter2Locked(@NonNull IMediaRouter2 router,
-            @NonNull String uniqueSessionId, @NonNull MediaRoute2Info route) {
+    private void transferToRouteWithRouter2Locked(
+            @NonNull IMediaRouter2 router,
+            @NonNull UserHandle transferInitiatorUserHandle,
+            @NonNull String uniqueSessionId,
+            @NonNull MediaRoute2Info route) {
         final IBinder binder = router.asBinder();
         final RouterRecord routerRecord = mAllRouterRecords.get(binder);
 
@@ -1191,9 +1263,16 @@
                             routerRecord, toOriginalRequestId(DUMMY_REQUEST_ID)));
         } else {
             routerRecord.mUserRecord.mHandler.sendMessage(
-                    obtainMessage(UserHandler::transferToRouteOnHandler,
+                    obtainMessage(
+                            UserHandler::transferToRouteOnHandler,
                             routerRecord.mUserRecord.mHandler,
-                            DUMMY_REQUEST_ID, routerRecord, uniqueSessionId, route));
+                            DUMMY_REQUEST_ID,
+                            transferInitiatorUserHandle,
+                            routerRecord.mPackageName,
+                            routerRecord,
+                            uniqueSessionId,
+                            route,
+                            RoutingSessionInfo.TRANSFER_REASON_APP));
         }
     }
 
@@ -1416,9 +1495,13 @@
     }
 
     @GuardedBy("mLock")
-    private void requestCreateSessionWithManagerLocked(int requestId,
-            @NonNull IMediaRouter2Manager manager, @NonNull RoutingSessionInfo oldSession,
-            @NonNull MediaRoute2Info route) {
+    private void requestCreateSessionWithManagerLocked(
+            int requestId,
+            @NonNull IMediaRouter2Manager manager,
+            @NonNull RoutingSessionInfo oldSession,
+            @NonNull MediaRoute2Info route,
+            @NonNull UserHandle transferInitiatorUserHandle,
+            @NonNull String transferInitiatorPackageName) {
         ManagerRecord managerRecord = mAllManagerRecords.get(manager.asBinder());
         if (managerRecord == null) {
             return;
@@ -1464,9 +1547,16 @@
         // Before requesting to the provider, get session hints from the media router.
         // As a return, media router will request to create a session.
         routerRecord.mUserRecord.mHandler.sendMessage(
-                obtainMessage(UserHandler::requestRouterCreateSessionOnHandler,
+                obtainMessage(
+                        UserHandler::requestRouterCreateSessionOnHandler,
                         routerRecord.mUserRecord.mHandler,
-                        uniqueRequestId, routerRecord, managerRecord, oldSession, route));
+                        uniqueRequestId,
+                        routerRecord,
+                        managerRecord,
+                        oldSession,
+                        route,
+                        transferInitiatorUserHandle,
+                        transferInitiatorPackageName));
     }
 
     @GuardedBy("mLock")
@@ -1521,9 +1611,14 @@
     }
 
     @GuardedBy("mLock")
-    private void transferToRouteWithManagerLocked(int requestId,
+    private void transferToRouteWithManagerLocked(
+            int requestId,
             @NonNull IMediaRouter2Manager manager,
-            @NonNull String uniqueSessionId, @NonNull MediaRoute2Info route) {
+            @NonNull String uniqueSessionId,
+            @NonNull MediaRoute2Info route,
+            @RoutingSessionInfo.TransferReason int transferReason,
+            @NonNull UserHandle transferInitiatorUserHandle,
+            @NonNull String transferInitiatorPackageName) {
         final IBinder binder = manager.asBinder();
         ManagerRecord managerRecord = mAllManagerRecords.get(binder);
 
@@ -1541,9 +1636,16 @@
 
         long uniqueRequestId = toUniqueRequestId(managerRecord.mManagerId, requestId);
         managerRecord.mUserRecord.mHandler.sendMessage(
-                obtainMessage(UserHandler::transferToRouteOnHandler,
+                obtainMessage(
+                        UserHandler::transferToRouteOnHandler,
                         managerRecord.mUserRecord.mHandler,
-                        uniqueRequestId, routerRecord, uniqueSessionId, route));
+                        uniqueRequestId,
+                        transferInitiatorUserHandle,
+                        transferInitiatorPackageName,
+                        routerRecord,
+                        uniqueSessionId,
+                        route,
+                        transferReason));
     }
 
     @GuardedBy("mLock")
@@ -1850,6 +1952,19 @@
             }
         }
 
+        public void notifySessionCreated(int requestId, @NonNull RoutingSessionInfo sessionInfo) {
+            try {
+                mRouter.notifySessionCreated(
+                        requestId, maybeClearTransferInitiatorIdentity(sessionInfo));
+            } catch (RemoteException ex) {
+                Slog.w(
+                        TAG,
+                        "Failed to notify router of the session creation."
+                                + " Router probably died.",
+                        ex);
+            }
+        }
+
         /**
          * Sends the corresponding router an update for the given session.
          *
@@ -1857,12 +1972,27 @@
          */
         public void notifySessionInfoChanged(RoutingSessionInfo sessionInfo) {
             try {
-                mRouter.notifySessionInfoChanged(sessionInfo);
+                mRouter.notifySessionInfoChanged(maybeClearTransferInitiatorIdentity(sessionInfo));
             } catch (RemoteException ex) {
                 Slog.w(TAG, "Failed to notify session info changed. Router probably died.", ex);
             }
         }
 
+        private RoutingSessionInfo maybeClearTransferInitiatorIdentity(
+                @NonNull RoutingSessionInfo sessionInfo) {
+            UserHandle transferInitiatorUserHandle = sessionInfo.getTransferInitiatorUserHandle();
+            String transferInitiatorPackageName = sessionInfo.getTransferInitiatorPackageName();
+
+            if (!Objects.equals(UserHandle.of(mUserRecord.mUserId), transferInitiatorUserHandle)
+                    || !Objects.equals(mPackageName, transferInitiatorPackageName)) {
+                return new RoutingSessionInfo.Builder(sessionInfo)
+                        .setTransferInitiator(null, null)
+                        .build();
+            }
+
+            return sessionInfo;
+        }
+
         /**
          * Returns a filtered copy of {@code routes} that contains only the routes that are {@link
          * MediaRoute2Info#isVisibleTo visible} to the router corresponding to this record.
@@ -2307,9 +2437,14 @@
             return -1;
         }
 
-        private void requestRouterCreateSessionOnHandler(long uniqueRequestId,
-                @NonNull RouterRecord routerRecord, @NonNull ManagerRecord managerRecord,
-                @NonNull RoutingSessionInfo oldSession, @NonNull MediaRoute2Info route) {
+        private void requestRouterCreateSessionOnHandler(
+                long uniqueRequestId,
+                @NonNull RouterRecord routerRecord,
+                @NonNull ManagerRecord managerRecord,
+                @NonNull RoutingSessionInfo oldSession,
+                @NonNull MediaRoute2Info route,
+                @NonNull UserHandle transferInitiatorUserHandle,
+                @NonNull String transferInitiatorPackageName) {
             try {
                 if (route.isSystemRoute() && !routerRecord.hasSystemRoutingPermission()) {
                     // The router lacks permission to modify system routing, so we hide system
@@ -2317,7 +2452,11 @@
                     route = mSystemProvider.getDefaultRoute();
                 }
                 routerRecord.mRouter.requestCreateSessionByManager(
-                        uniqueRequestId, oldSession, route);
+                        uniqueRequestId,
+                        oldSession,
+                        route,
+                        transferInitiatorUserHandle,
+                        transferInitiatorPackageName);
             } catch (RemoteException ex) {
                 Slog.w(TAG, "getSessionHintsForCreatingSessionOnHandler: "
                         + "Failed to request. Router probably died.", ex);
@@ -2326,10 +2465,15 @@
             }
         }
 
-        private void requestCreateSessionWithRouter2OnHandler(long uniqueRequestId,
-                long managerRequestId, @NonNull RouterRecord routerRecord,
+        private void requestCreateSessionWithRouter2OnHandler(
+                long uniqueRequestId,
+                long managerRequestId,
+                @NonNull UserHandle transferInitiatorUserHandle,
+                @NonNull String transferInitiatorPackageName,
+                @NonNull RouterRecord routerRecord,
                 @NonNull RoutingSessionInfo oldSession,
-                @NonNull MediaRoute2Info route, @Nullable Bundle sessionHints) {
+                @NonNull MediaRoute2Info route,
+                @Nullable Bundle sessionHints) {
 
             final MediaRoute2Provider provider = findProvider(route.getProviderId());
             if (provider == null) {
@@ -2345,8 +2489,19 @@
                             managerRequestId, oldSession, route);
             mSessionCreationRequests.add(request);
 
-            provider.requestCreateSession(uniqueRequestId, routerRecord.mPackageName,
-                    route.getOriginalId(), sessionHints);
+            int transferReason = RoutingSessionInfo.TRANSFER_REASON_APP;
+            if (managerRequestId != MediaRoute2ProviderService.REQUEST_ID_NONE) {
+                transferReason = RoutingSessionInfo.TRANSFER_REASON_SYSTEM_REQUEST;
+            }
+
+            provider.requestCreateSession(
+                    uniqueRequestId,
+                    routerRecord.mPackageName,
+                    route.getOriginalId(),
+                    sessionHints,
+                    transferReason,
+                    transferInitiatorUserHandle,
+                    transferInitiatorPackageName);
         }
 
         // routerRecord can be null if the session is system's or RCN.
@@ -2386,9 +2541,14 @@
         }
 
         // routerRecord can be null if the session is system's or RCN.
-        private void transferToRouteOnHandler(long uniqueRequestId,
+        private void transferToRouteOnHandler(
+                long uniqueRequestId,
+                @NonNull UserHandle transferInitiatorUserHandle,
+                @NonNull String transferInitiatorPackageName,
                 @Nullable RouterRecord routerRecord,
-                @NonNull String uniqueSessionId, @NonNull MediaRoute2Info route) {
+                @NonNull String uniqueSessionId,
+                @NonNull MediaRoute2Info route,
+                @RoutingSessionInfo.TransferReason int transferReason) {
             if (!checkArgumentsForSessionControl(routerRecord, uniqueSessionId, route,
                     "transferring to")) {
                 return;
@@ -2399,8 +2559,13 @@
             if (provider == null) {
                 return;
             }
-            provider.transferToRoute(uniqueRequestId, getOriginalId(uniqueSessionId),
-                    route.getOriginalId());
+            provider.transferToRoute(
+                    uniqueRequestId,
+                    transferInitiatorUserHandle,
+                    transferInitiatorPackageName,
+                    getOriginalId(uniqueSessionId),
+                    route.getOriginalId(),
+                    transferReason);
         }
 
         // routerRecord is null if and only if the session is created without the request, which
@@ -2535,6 +2700,7 @@
                 // session info from them.
                 sessionInfo = mSystemProvider.getDefaultSessionInfo();
             }
+            // TODO: b/279555229 - replace with matchingRequest.mRouterRecord.notifySessionCreated.
             notifySessionCreatedToRouter(
                     matchingRequest.mRouterRecord,
                     toOriginalRequestId(uniqueRequestId),
@@ -2648,12 +2814,7 @@
 
         private void notifySessionCreatedToRouter(@NonNull RouterRecord routerRecord,
                 int requestId, @NonNull RoutingSessionInfo sessionInfo) {
-            try {
-                routerRecord.mRouter.notifySessionCreated(requestId, sessionInfo);
-            } catch (RemoteException ex) {
-                Slog.w(TAG, "Failed to notify router of the session creation."
-                        + " Router probably died.", ex);
-            }
+            routerRecord.notifySessionCreated(requestId, sessionInfo);
         }
 
         private void notifySessionCreationFailedToRouter(@NonNull RouterRecord routerRecord,
diff --git a/services/core/java/com/android/server/media/MediaRouterService.java b/services/core/java/com/android/server/media/MediaRouterService.java
index e562b3f..7dd1314 100644
--- a/services/core/java/com/android/server/media/MediaRouterService.java
+++ b/services/core/java/com/android/server/media/MediaRouterService.java
@@ -461,11 +461,24 @@
 
     // Binder call
     @Override
-    public void requestCreateSessionWithRouter2(IMediaRouter2 router, int requestId,
-            long managerRequestId, RoutingSessionInfo oldSession,
-            MediaRoute2Info route, Bundle sessionHints) {
-        mService2.requestCreateSessionWithRouter2(router, requestId, managerRequestId,
-                oldSession, route, sessionHints);
+    public void requestCreateSessionWithRouter2(
+            IMediaRouter2 router,
+            int requestId,
+            long managerRequestId,
+            RoutingSessionInfo oldSession,
+            MediaRoute2Info route,
+            Bundle sessionHints,
+            @Nullable UserHandle transferInitiatorUserHandle,
+            @Nullable String transferInitiatorPackageName) {
+        mService2.requestCreateSessionWithRouter2(
+                router,
+                requestId,
+                managerRequestId,
+                oldSession,
+                route,
+                sessionHints,
+                transferInitiatorUserHandle,
+                transferInitiatorPackageName);
     }
 
     // Binder call
@@ -580,9 +593,20 @@
 
     // Binder call
     @Override
-    public void requestCreateSessionWithManager(IMediaRouter2Manager manager,
-            int requestId, RoutingSessionInfo oldSession, MediaRoute2Info route) {
-        mService2.requestCreateSessionWithManager(manager, requestId, oldSession, route);
+    public void requestCreateSessionWithManager(
+            IMediaRouter2Manager manager,
+            int requestId,
+            RoutingSessionInfo oldSession,
+            MediaRoute2Info route,
+            UserHandle transferInitiatorUserHandle,
+            String transferInitiatorPackageName) {
+        mService2.requestCreateSessionWithManager(
+                manager,
+                requestId,
+                oldSession,
+                route,
+                transferInitiatorUserHandle,
+                transferInitiatorPackageName);
     }
 
     // Binder call
@@ -601,9 +625,20 @@
 
     // Binder call
     @Override
-    public void transferToRouteWithManager(IMediaRouter2Manager manager, int requestId,
-            String sessionId, MediaRoute2Info route) {
-        mService2.transferToRouteWithManager(manager, requestId, sessionId, route);
+    public void transferToRouteWithManager(
+            IMediaRouter2Manager manager,
+            int requestId,
+            String sessionId,
+            MediaRoute2Info route,
+            UserHandle transferInitiatorUserHandle,
+            String transferInitiatorPackageName) {
+        mService2.transferToRouteWithManager(
+                manager,
+                requestId,
+                sessionId,
+                route,
+                transferInitiatorUserHandle,
+                transferInitiatorPackageName);
     }
 
     // Binder call
diff --git a/services/core/java/com/android/server/media/SystemMediaRoute2Provider.java b/services/core/java/com/android/server/media/SystemMediaRoute2Provider.java
index 9d151c2..f7210dd 100644
--- a/services/core/java/com/android/server/media/SystemMediaRoute2Provider.java
+++ b/services/core/java/com/android/server/media/SystemMediaRoute2Provider.java
@@ -16,6 +16,7 @@
 
 package com.android.server.media;
 
+import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.content.BroadcastReceiver;
 import android.content.ComponentName;
@@ -26,6 +27,7 @@
 import android.media.MediaRoute2Info;
 import android.media.MediaRoute2ProviderInfo;
 import android.media.MediaRoute2ProviderService;
+import android.media.MediaRouter2Utils;
 import android.media.RouteDiscoveryPreference;
 import android.media.RoutingSessionInfo;
 import android.os.Bundle;
@@ -39,6 +41,7 @@
 import com.android.internal.annotations.GuardedBy;
 import com.android.media.flags.Flags;
 
+import java.util.ArrayList;
 import java.util.List;
 import java.util.Objects;
 import java.util.Set;
@@ -79,6 +82,10 @@
     @GuardedBy("mRequestLock")
     private volatile SessionCreationRequest mPendingSessionCreationRequest;
 
+    private final Object mTransferLock = new Object();
+    @GuardedBy("mTransferLock")
+    @Nullable private volatile SessionCreationRequest mPendingTransferRequest;
+
     SystemMediaRoute2Provider(Context context, UserHandle user) {
         super(COMPONENT_NAME);
         mIsSystemRouteProvider = true;
@@ -146,17 +153,30 @@
     }
 
     @Override
-    public void requestCreateSession(long requestId, String packageName, String routeId,
-            Bundle sessionHints) {
+    public void requestCreateSession(
+            long requestId,
+            String packageName,
+            String routeId,
+            Bundle sessionHints,
+            @RoutingSessionInfo.TransferReason int transferReason,
+            @NonNull UserHandle transferInitiatorUserHandle,
+            @NonNull String transferInitiatorPackageName) {
         // Assume a router without MODIFY_AUDIO_ROUTING permission can't request with
         // a route ID different from the default route ID. The service should've filtered.
         if (TextUtils.equals(routeId, MediaRoute2Info.ROUTE_ID_DEFAULT)) {
             mCallback.onSessionCreated(this, requestId, mDefaultSessionInfo);
             return;
         }
-        if (TextUtils.equals(routeId, mSelectedRouteId)) {
-            mCallback.onSessionCreated(this, requestId, mSessionInfos.get(0));
-            return;
+
+        if (!Flags.enableBuiltInSpeakerRouteSuitabilityStatuses()) {
+            if (TextUtils.equals(routeId, mSelectedRouteId)) {
+                RoutingSessionInfo currentSessionInfo;
+                synchronized (mLock) {
+                    currentSessionInfo = mSessionInfos.get(0);
+                }
+                mCallback.onSessionCreated(this, requestId, currentSessionInfo);
+                return;
+            }
         }
 
         synchronized (mRequestLock) {
@@ -165,10 +185,23 @@
                 mCallback.onRequestFailed(this, mPendingSessionCreationRequest.mRequestId,
                         MediaRoute2ProviderService.REASON_UNKNOWN_ERROR);
             }
-            mPendingSessionCreationRequest = new SessionCreationRequest(requestId, routeId);
+            mPendingSessionCreationRequest =
+                    new SessionCreationRequest(
+                            requestId,
+                            routeId,
+                            RoutingSessionInfo.TRANSFER_REASON_FALLBACK,
+                            transferInitiatorUserHandle,
+                            transferInitiatorPackageName);
         }
 
-        transferToRoute(requestId, SYSTEM_SESSION_ID, routeId);
+        // Only unprivileged routers call this method, therefore we use TRANSFER_REASON_APP.
+        transferToRoute(
+                requestId,
+                transferInitiatorUserHandle,
+                transferInitiatorPackageName,
+                SYSTEM_SESSION_ID,
+                routeId,
+                transferReason);
     }
 
     @Override
@@ -193,12 +226,31 @@
     }
 
     @Override
-    public void transferToRoute(long requestId, String sessionId, String routeId) {
+    public void transferToRoute(
+            long requestId,
+            @NonNull UserHandle transferInitiatorUserHandle,
+            @NonNull String transferInitiatorPackageName,
+            String sessionId,
+            String routeId,
+            @RoutingSessionInfo.TransferReason int transferReason) {
         if (TextUtils.equals(routeId, MediaRoute2Info.ROUTE_ID_DEFAULT)) {
             // The currently selected route is the default route.
             Log.w(TAG, "Ignoring transfer to " + MediaRoute2Info.ROUTE_ID_DEFAULT);
             return;
         }
+
+        if (Flags.enableBuiltInSpeakerRouteSuitabilityStatuses()) {
+            synchronized (mTransferLock) {
+                mPendingTransferRequest =
+                        new SessionCreationRequest(
+                                requestId,
+                                routeId,
+                                transferReason,
+                                transferInitiatorUserHandle,
+                                transferInitiatorPackageName);
+            }
+        }
+
         MediaRoute2Info selectedDeviceRoute = mDeviceRouteController.getSelectedRoute();
         boolean isAvailableDeviceRoute =
                 mDeviceRouteController.getAvailableRoutes().stream()
@@ -218,6 +270,11 @@
             mDeviceRouteController.transferTo(null);
             mBluetoothRouteController.transferTo(routeId);
         }
+
+        if (Flags.enableBuiltInSpeakerRouteSuitabilityStatuses()
+                && updateSessionInfosIfNeeded()) {
+            notifySessionInfoUpdated();
+        }
     }
 
     @Override
@@ -322,9 +379,11 @@
             MediaRoute2Info selectedDeviceRoute = mDeviceRouteController.getSelectedRoute();
             MediaRoute2Info selectedRoute = selectedDeviceRoute;
             MediaRoute2Info selectedBtRoute = mBluetoothRouteController.getSelectedRoute();
+            List<String> transferableRoutes = new ArrayList<>();
+
             if (selectedBtRoute != null) {
                 selectedRoute = selectedBtRoute;
-                builder.addTransferableRoute(selectedDeviceRoute.getId());
+                transferableRoutes.add(selectedDeviceRoute.getId());
             }
             mSelectedRouteId = selectedRoute.getId();
             mDefaultRoute =
@@ -337,12 +396,54 @@
                 for (MediaRoute2Info route : mDeviceRouteController.getAvailableRoutes()) {
                     String routeId = route.getId();
                     if (!mSelectedRouteId.equals(routeId)) {
-                        builder.addTransferableRoute(routeId);
+                        transferableRoutes.add(routeId);
                     }
                 }
             }
             for (MediaRoute2Info route : mBluetoothRouteController.getTransferableRoutes()) {
-                builder.addTransferableRoute(route.getId());
+                transferableRoutes.add(route.getId());
+            }
+
+            for (String route : transferableRoutes) {
+                builder.addTransferableRoute(route);
+            }
+
+            if (Flags.enableBuiltInSpeakerRouteSuitabilityStatuses()) {
+                int transferReason = RoutingSessionInfo.TRANSFER_REASON_FALLBACK;
+                UserHandle transferInitiatorUserHandle = null;
+                String transferInitiatorPackageName = null;
+
+                if (oldSessionInfo != null
+                        && containsSelectedRouteWithId(oldSessionInfo, selectedRoute.getId())) {
+                    transferReason = oldSessionInfo.getTransferReason();
+                    transferInitiatorUserHandle = oldSessionInfo.getTransferInitiatorUserHandle();
+                    transferInitiatorPackageName = oldSessionInfo.getTransferInitiatorPackageName();
+                }
+
+                synchronized (mTransferLock) {
+                    if (mPendingTransferRequest != null) {
+                        boolean isTransferringToTheSelectedRoute =
+                                mPendingTransferRequest.isTargetRoute(selectedRoute);
+                        boolean canBePotentiallyTransferred =
+                                mPendingTransferRequest.isInsideOfRoutesList(transferableRoutes);
+
+                        if (isTransferringToTheSelectedRoute) {
+                            transferReason = mPendingTransferRequest.mTransferReason;
+                            transferInitiatorUserHandle =
+                                    mPendingTransferRequest.mTransferInitiatorUserHandle;
+                            transferInitiatorPackageName =
+                                    mPendingTransferRequest.mTransferInitiatorPackageName;
+
+                            mPendingTransferRequest = null;
+                        } else if (!canBePotentiallyTransferred) {
+                            mPendingTransferRequest = null;
+                        }
+                    }
+                }
+
+                builder.setTransferReason(transferReason)
+                        .setTransferInitiator(
+                                transferInitiatorUserHandle, transferInitiatorPackageName);
             }
 
             RoutingSessionInfo newSessionInfo = builder.setProviderId(mUniqueId).build();
@@ -424,6 +525,22 @@
         return false;
     }
 
+    private boolean containsSelectedRouteWithId(
+            @Nullable RoutingSessionInfo sessionInfo, @NonNull String selectedRouteId) {
+        if (sessionInfo == null) {
+            return false;
+        }
+
+        List<String> selectedRoutes = sessionInfo.getSelectedRoutes();
+
+        if (selectedRoutes.size() != 1) {
+            throw new IllegalStateException("Selected routes list should contain only 1 route id.");
+        }
+
+        String oldSelectedRouteId = MediaRouter2Utils.getOriginalId(selectedRoutes.get(0));
+        return oldSelectedRouteId != null && oldSelectedRouteId.equals(selectedRouteId);
+    }
+
     void publishProviderState() {
         updateProviderState();
         notifyProviderState();
@@ -452,12 +569,47 @@
     }
 
     private static class SessionCreationRequest {
-        final long mRequestId;
-        final String mRouteId;
+        private final long mRequestId;
+        @NonNull private final String mRouteId;
 
-        SessionCreationRequest(long requestId, String routeId) {
-            this.mRequestId = requestId;
-            this.mRouteId = routeId;
+        @RoutingSessionInfo.TransferReason private final int mTransferReason;
+
+        @NonNull private final UserHandle mTransferInitiatorUserHandle;
+        @NonNull private final String mTransferInitiatorPackageName;
+
+        SessionCreationRequest(
+                long requestId,
+                @NonNull String routeId,
+                @RoutingSessionInfo.TransferReason int transferReason,
+                @NonNull UserHandle transferInitiatorUserHandle,
+                @NonNull String transferInitiatorPackageName) {
+            mRequestId = requestId;
+            mRouteId = routeId;
+            mTransferReason = transferReason;
+            mTransferInitiatorUserHandle = transferInitiatorUserHandle;
+            mTransferInitiatorPackageName = transferInitiatorPackageName;
+        }
+
+        private boolean isTargetRoute(@Nullable MediaRoute2Info route2Info) {
+            if (route2Info == null) {
+                return false;
+            }
+
+            return isTargetRoute(route2Info.getId());
+        }
+
+        private boolean isTargetRoute(@Nullable String routeId) {
+            return mRouteId.equals(routeId);
+        }
+
+        private boolean isInsideOfRoutesList(@NonNull List<String> routesList) {
+            for (String routeId : routesList) {
+                if (isTargetRoute(routeId)) {
+                    return true;
+                }
+            }
+
+            return false;
         }
     }
 
diff --git a/services/core/java/com/android/server/notification/NotificationManagerService.java b/services/core/java/com/android/server/notification/NotificationManagerService.java
index 135a467..f49d51c 100755
--- a/services/core/java/com/android/server/notification/NotificationManagerService.java
+++ b/services/core/java/com/android/server/notification/NotificationManagerService.java
@@ -5617,7 +5617,8 @@
             return !isCompatChangeEnabled
                     || isCallerSystemOrSystemUi()
                     || hasCompanionDevice(callingPkg, UserHandle.getUserId(callingUid),
-                            AssociationRequest.DEVICE_PROFILE_WATCH);
+                            Set.of(AssociationRequest.DEVICE_PROFILE_WATCH,
+                                    AssociationRequest.DEVICE_PROFILE_AUTOMOTIVE_PROJECTION));
         }
 
         private void enforcePolicyAccess(String pkg, String method) {
@@ -10800,7 +10801,7 @@
     }
 
     private boolean hasCompanionDevice(String pkg, @UserIdInt int userId,
-            @Nullable @AssociationRequest.DeviceProfile String withDeviceProfile) {
+            @Nullable Set</* @AssociationRequest.DeviceProfile */ String> withDeviceProfiles) {
         if (mCompanionManager == null) {
             mCompanionManager = getCompanionManager();
         }
@@ -10812,7 +10813,7 @@
         try {
             List<AssociationInfo> associations = mCompanionManager.getAssociations(pkg, userId);
             for (AssociationInfo association : associations) {
-                if (withDeviceProfile == null || withDeviceProfile.equals(
+                if (withDeviceProfiles == null || withDeviceProfiles.contains(
                         association.getDeviceProfile())) {
                     return true;
                 }
diff --git a/services/core/java/com/android/server/notification/flags.aconfig b/services/core/java/com/android/server/notification/flags.aconfig
index 55d8a0f..8e79922a 100644
--- a/services/core/java/com/android/server/notification/flags.aconfig
+++ b/services/core/java/com/android/server/notification/flags.aconfig
@@ -41,3 +41,12 @@
   description: "This flag controls the fix for notifications autogroup summary icon updates"
   bug: "227693160"
 }
+
+flag {
+  name: "sensitive_notification_app_protection"
+  namespace: "systemui"
+  description: "This flag controls the sensitive notification app protections while screen sharing"
+  bug: "312784351"
+  # Referenced in WM where WM starts before DeviceConfig
+  is_fixed_read_only: true
+}
diff --git a/services/core/java/com/android/server/pm/InitAppsHelper.java b/services/core/java/com/android/server/pm/InitAppsHelper.java
index 3b9f9c8..41d0176 100644
--- a/services/core/java/com/android/server/pm/InitAppsHelper.java
+++ b/services/core/java/com/android/server/pm/InitAppsHelper.java
@@ -46,11 +46,11 @@
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.content.om.OverlayConfig;
+import com.android.internal.pm.parsing.PackageParser2;
 import com.android.internal.pm.pkg.parsing.ParsingPackageUtils;
 import com.android.internal.util.FrameworkStatsLog;
 import com.android.server.EventLogTags;
 import com.android.server.pm.parsing.PackageCacher;
-import com.android.server.pm.parsing.PackageParser2;
 import com.android.server.pm.pkg.AndroidPackage;
 import com.android.server.utils.WatchedArrayMap;
 
diff --git a/services/core/java/com/android/server/pm/InstallPackageHelper.java b/services/core/java/com/android/server/pm/InstallPackageHelper.java
index 65bfb2f..51ba3d4 100644
--- a/services/core/java/com/android/server/pm/InstallPackageHelper.java
+++ b/services/core/java/com/android/server/pm/InstallPackageHelper.java
@@ -146,6 +146,7 @@
 import android.util.ArrayMap;
 import android.util.ArraySet;
 import android.util.EventLog;
+import android.util.ExceptionUtils;
 import android.util.Log;
 import android.util.Pair;
 import android.util.Slog;
@@ -154,6 +155,7 @@
 
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.content.F2fsUtils;
+import com.android.internal.pm.parsing.PackageParser2;
 import com.android.internal.pm.parsing.PackageParserException;
 import com.android.internal.pm.parsing.pkg.AndroidPackageLegacyUtils;
 import com.android.internal.pm.parsing.pkg.ParsedPackage;
@@ -178,7 +180,6 @@
 import com.android.server.pm.dex.DexManager;
 import com.android.server.pm.dex.DexoptOptions;
 import com.android.server.pm.parsing.PackageCacher;
-import com.android.server.pm.parsing.PackageParser2;
 import com.android.server.pm.parsing.pkg.AndroidPackageUtils;
 import com.android.server.pm.permission.Permission;
 import com.android.server.pm.permission.PermissionManagerServiceInternal;
@@ -1167,8 +1168,9 @@
                         parseFlags);
                 archivedPackage = request.getPackageLite().getArchivedPackage();
             }
-        } catch (PackageManagerException | PackageParserException e) {
-            throw new PrepareFailure("Failed parse during installPackageLI", e);
+        } catch (PackageParserException e) {
+            throw new PrepareFailure(e.error,
+                    ExceptionUtils.getCompleteMessage("Failed parse during installPackageLI", e));
         } finally {
             Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
         }
@@ -2858,14 +2860,17 @@
                 mPm.notifyPackageChanged(packageName, request.getAppId());
             }
 
-            for (int userId : firstUserIds) {
-                // Apply restricted settings on potentially dangerous packages. Needs to happen
-                // after appOpsManager is notified of the new package
-                if (request.getPackageSource() == PackageInstaller.PACKAGE_SOURCE_LOCAL_FILE
-                        || request.getPackageSource()
-                        == PackageInstaller.PACKAGE_SOURCE_DOWNLOADED_FILE) {
-                    enableRestrictedSettings(packageName, request.getAppId(), userId);
-                }
+            // Apply restricted settings on potentially dangerous packages. Needs to happen
+            // after appOpsManager is notified of the new package
+            if (request.getPackageSource() == PackageInstaller.PACKAGE_SOURCE_LOCAL_FILE
+                    || request.getPackageSource()
+                    == PackageInstaller.PACKAGE_SOURCE_DOWNLOADED_FILE) {
+                final int appId = request.getAppId();
+                mPm.mHandler.post(() -> {
+                    for (int userId : firstUserIds) {
+                        enableRestrictedSettings(packageName, appId, userId);
+                    }
+                });
             }
 
             // Log current value of "unknown sources" setting
@@ -3680,6 +3685,8 @@
         final ParsedPackage parsedPackage;
         try (PackageParser2 pp = mPm.mInjector.getScanningPackageParser()) {
             parsedPackage = pp.parsePackage(scanFile, parseFlags, false);
+        } catch (PackageParserException e) {
+            throw new PackageManagerException(e.error, e.getMessage(), e);
         } finally {
             Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
         }
diff --git a/services/core/java/com/android/server/pm/InstallingSession.java b/services/core/java/com/android/server/pm/InstallingSession.java
index 187cada..e970d2c 100644
--- a/services/core/java/com/android/server/pm/InstallingSession.java
+++ b/services/core/java/com/android/server/pm/InstallingSession.java
@@ -51,8 +51,8 @@
 import com.android.internal.content.F2fsUtils;
 import com.android.internal.content.InstallLocationUtils;
 import com.android.internal.content.NativeLibraryHelper;
+import com.android.internal.pm.parsing.PackageParser2;
 import com.android.internal.util.Preconditions;
-import com.android.server.pm.parsing.PackageParser2;
 
 import libcore.io.IoUtils;
 
diff --git a/services/core/java/com/android/server/pm/PackageInstallerService.java b/services/core/java/com/android/server/pm/PackageInstallerService.java
index 2942bbb..cbd65a4 100644
--- a/services/core/java/com/android/server/pm/PackageInstallerService.java
+++ b/services/core/java/com/android/server/pm/PackageInstallerService.java
@@ -111,6 +111,7 @@
 import com.android.internal.content.InstallLocationUtils;
 import com.android.internal.messages.nano.SystemMessageProto.SystemMessage;
 import com.android.internal.notification.SystemNotificationChannels;
+import com.android.internal.pm.parsing.PackageParser2;
 import com.android.internal.util.ImageUtils;
 import com.android.internal.util.IndentingPrintWriter;
 import com.android.modules.utils.TypedXmlPullParser;
@@ -120,7 +121,6 @@
 import com.android.server.SystemConfig;
 import com.android.server.SystemService;
 import com.android.server.SystemServiceManager;
-import com.android.server.pm.parsing.PackageParser2;
 import com.android.server.pm.pkg.PackageStateInternal;
 import com.android.server.pm.utils.RequestThrottle;
 
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index 1d5b8c3..81d5d81 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -181,6 +181,7 @@
 import com.android.internal.content.F2fsUtils;
 import com.android.internal.content.InstallLocationUtils;
 import com.android.internal.content.om.OverlayConfig;
+import com.android.internal.pm.parsing.PackageParser2;
 import com.android.internal.pm.parsing.pkg.AndroidPackageInternal;
 import com.android.internal.pm.parsing.pkg.ParsedPackage;
 import com.android.internal.pm.pkg.component.ParsedInstrumentation;
@@ -221,8 +222,8 @@
 import com.android.server.pm.dex.DexManager;
 import com.android.server.pm.dex.DynamicCodeLogger;
 import com.android.server.pm.local.PackageManagerLocalImpl;
+import com.android.server.pm.parsing.PackageCacher;
 import com.android.server.pm.parsing.PackageInfoUtils;
-import com.android.server.pm.parsing.PackageParser2;
 import com.android.server.pm.parsing.pkg.AndroidPackageUtils;
 import com.android.server.pm.permission.LegacyPermissionManagerInternal;
 import com.android.server.pm.permission.LegacyPermissionManagerService;
@@ -1698,7 +1699,7 @@
                         () -> LocalServices.getService(UserManagerInternal.class)),
                 (i, pm) -> new DisplayMetrics(),
                 (i, pm) -> new PackageParser2(pm.mSeparateProcesses, i.getDisplayMetrics(),
-                        pm.mCacheDir,
+                        new PackageCacher(pm.mCacheDir),
                         pm.mPackageParserCallback) /* scanningCachingPackageParserProducer */,
                 (i, pm) -> new PackageParser2(pm.mSeparateProcesses, i.getDisplayMetrics(), null,
                         pm.mPackageParserCallback) /* scanningPackageParserProducer */,
diff --git a/services/core/java/com/android/server/pm/PackageManagerServiceInjector.java b/services/core/java/com/android/server/pm/PackageManagerServiceInjector.java
index ebf1c04..049737d 100644
--- a/services/core/java/com/android/server/pm/PackageManagerServiceInjector.java
+++ b/services/core/java/com/android/server/pm/PackageManagerServiceInjector.java
@@ -27,12 +27,12 @@
 import android.util.DisplayMetrics;
 
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.pm.parsing.PackageParser2;
 import com.android.server.SystemConfig;
 import com.android.server.compat.PlatformCompat;
 import com.android.server.pm.dex.ArtManagerService;
 import com.android.server.pm.dex.DexManager;
 import com.android.server.pm.dex.DynamicCodeLogger;
-import com.android.server.pm.parsing.PackageParser2;
 import com.android.server.pm.permission.LegacyPermissionManagerInternal;
 import com.android.server.pm.permission.PermissionManagerServiceInternal;
 import com.android.server.pm.resolution.ComponentResolver;
diff --git a/services/core/java/com/android/server/pm/PackageManagerServiceTestParams.java b/services/core/java/com/android/server/pm/PackageManagerServiceTestParams.java
index 655b9c9..86d78dc 100644
--- a/services/core/java/com/android/server/pm/PackageManagerServiceTestParams.java
+++ b/services/core/java/com/android/server/pm/PackageManagerServiceTestParams.java
@@ -31,10 +31,10 @@
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.content.om.OverlayConfig;
+import com.android.internal.pm.parsing.PackageParser2;
 import com.android.server.pm.dex.ArtManagerService;
 import com.android.server.pm.dex.DexManager;
 import com.android.server.pm.dex.DynamicCodeLogger;
-import com.android.server.pm.parsing.PackageParser2;
 import com.android.server.pm.permission.LegacyPermissionManagerInternal;
 import com.android.server.pm.pkg.AndroidPackage;
 
diff --git a/services/core/java/com/android/server/pm/PackageSessionVerifier.java b/services/core/java/com/android/server/pm/PackageSessionVerifier.java
index 15d2fdc..1fe49c7 100644
--- a/services/core/java/com/android/server/pm/PackageSessionVerifier.java
+++ b/services/core/java/com/android/server/pm/PackageSessionVerifier.java
@@ -41,10 +41,11 @@
 
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.content.InstallLocationUtils;
+import com.android.internal.pm.parsing.PackageParser2;
+import com.android.internal.pm.parsing.PackageParserException;
 import com.android.internal.pm.parsing.pkg.ParsedPackage;
 import com.android.server.LocalServices;
 import com.android.server.SystemConfig;
-import com.android.server.pm.parsing.PackageParser2;
 import com.android.server.rollback.RollbackManagerInternal;
 
 import java.io.File;
@@ -399,7 +400,7 @@
             try (PackageParser2 packageParser = mPackageParserSupplier.get()) {
                 File apexFile = new File(apexInfo.modulePath);
                 parsedPackage = packageParser.parsePackage(apexFile, 0, false);
-            } catch (PackageManagerException e) {
+            } catch (PackageParserException e) {
                 throw new PackageManagerException(
                         PackageManager.INSTALL_FAILED_VERIFICATION_FAILURE,
                         "Failed to parse APEX package " + apexInfo.modulePath + " : " + e, e);
diff --git a/services/core/java/com/android/server/pm/ParallelPackageParser.java b/services/core/java/com/android/server/pm/ParallelPackageParser.java
index 1089ac9..0511639 100644
--- a/services/core/java/com/android/server/pm/ParallelPackageParser.java
+++ b/services/core/java/com/android/server/pm/ParallelPackageParser.java
@@ -22,9 +22,10 @@
 import android.os.Trace;
 
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.pm.parsing.PackageParser2;
+import com.android.internal.pm.parsing.PackageParserException;
 import com.android.internal.pm.parsing.pkg.ParsedPackage;
 import com.android.internal.util.ConcurrentUtils;
-import com.android.server.pm.parsing.PackageParser2;
 
 import java.io.File;
 import java.util.concurrent.ArrayBlockingQueue;
@@ -125,6 +126,10 @@
     @VisibleForTesting
     protected ParsedPackage parsePackage(File scanFile, int parseFlags)
             throws PackageManagerException {
-        return mPackageParser.parsePackage(scanFile, parseFlags, true);
+        try {
+            return mPackageParser.parsePackage(scanFile, parseFlags, true);
+        } catch (PackageParserException e) {
+            throw new PackageManagerException(e.error, e.getMessage(), e);
+        }
     }
 }
diff --git a/services/core/java/com/android/server/pm/parsing/PackageCacher.java b/services/core/java/com/android/server/pm/parsing/PackageCacher.java
index 79c9c8e..b6267c4 100644
--- a/services/core/java/com/android/server/pm/parsing/PackageCacher.java
+++ b/services/core/java/com/android/server/pm/parsing/PackageCacher.java
@@ -28,6 +28,7 @@
 import android.util.Slog;
 
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.pm.parsing.IPackageCacher;
 import com.android.internal.pm.parsing.pkg.PackageImpl;
 import com.android.internal.pm.parsing.pkg.ParsedPackage;
 import com.android.server.pm.ApexManager;
@@ -39,7 +40,7 @@
 import java.io.IOException;
 import java.util.concurrent.atomic.AtomicInteger;
 
-public class PackageCacher {
+public class PackageCacher implements IPackageCacher {
 
     private static final String TAG = "PackageCacher";
 
@@ -162,6 +163,7 @@
      * Returns the cached parse result for {@code packageFile} for parse flags {@code flags},
      * or {@code null} if no cached result exists.
      */
+    @Override
     public ParsedPackage getCachedResult(File packageFile, int flags) {
         final String cacheKey = getCacheKey(packageFile, flags);
         final File cacheFile = new File(mCacheDir, cacheKey);
@@ -192,6 +194,7 @@
     /**
      * Caches the parse result for {@code packageFile} with flags {@code flags}.
      */
+    @Override
     public void cacheResult(File packageFile, int flags, ParsedPackage parsed) {
         try {
             final String cacheKey = getCacheKey(packageFile, flags);
diff --git a/services/core/java/com/android/server/pm/parsing/PackageParserUtils.java b/services/core/java/com/android/server/pm/parsing/PackageParserUtils.java
new file mode 100644
index 0000000..03a7a37
--- /dev/null
+++ b/services/core/java/com/android/server/pm/parsing/PackageParserUtils.java
@@ -0,0 +1,79 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.server.pm.parsing;
+
+import android.annotation.NonNull;
+import android.annotation.SuppressLint;
+import android.content.Context;
+import android.content.pm.ApplicationInfo;
+import android.os.ServiceManager;
+import android.util.Slog;
+
+import com.android.internal.compat.IPlatformCompat;
+import com.android.internal.pm.parsing.PackageParser2;
+import com.android.internal.pm.pkg.parsing.ParsingUtils;
+import com.android.server.SystemConfig;
+import com.android.server.pm.PackageManagerService;
+
+import java.util.Set;
+
+public class PackageParserUtils {
+    /**
+     * For parsing inside the system server but outside of {@link PackageManagerService}.
+     * Generally used for parsing information in an APK that hasn't been installed yet.
+     *
+     * This must be called inside the system process as it relies on {@link ServiceManager}.
+     */
+    @NonNull
+    @SuppressLint("AndroidFrameworkRequiresPermission")
+    public static PackageParser2 forParsingFileWithDefaults() {
+        IPlatformCompat platformCompat = IPlatformCompat.Stub.asInterface(
+                ServiceManager.getService(Context.PLATFORM_COMPAT_SERVICE));
+        return new PackageParser2(null /* separateProcesses */, null /* displayMetrics */,
+                null /* cacheDir */, new PackageParser2.Callback() {
+            @Override
+            @SuppressLint("AndroidFrameworkRequiresPermission")
+            public boolean isChangeEnabled(long changeId, @NonNull ApplicationInfo appInfo) {
+                try {
+                    return platformCompat.isChangeEnabled(changeId, appInfo);
+                } catch (Exception e) {
+                    // This shouldn't happen, but assume enforcement if it does
+                    Slog.wtf(ParsingUtils.TAG, "IPlatformCompat query failed", e);
+                    return true;
+                }
+            }
+
+            @Override
+            public boolean hasFeature(String feature) {
+                // Assume the device doesn't support anything. This will affect permission parsing
+                // and will force <uses-permission/> declarations to include all requiredNotFeature
+                // permissions and exclude all requiredFeature permissions. This mirrors the old
+                // behavior.
+                return false;
+            }
+
+            @Override
+            public Set<String> getHiddenApiWhitelistedApps() {
+                return SystemConfig.getInstance().getHiddenApiWhitelistedApps();
+            }
+
+            @Override
+            public Set<String> getInstallConstraintsAllowlist() {
+                return SystemConfig.getInstance().getInstallConstraintsAllowlist();
+            }
+        });
+    }
+}
diff --git a/services/core/java/com/android/server/policy/DeferredKeyActionExecutor.java b/services/core/java/com/android/server/policy/DeferredKeyActionExecutor.java
index b531b0e..611e4ed 100644
--- a/services/core/java/com/android/server/policy/DeferredKeyActionExecutor.java
+++ b/services/core/java/com/android/server/policy/DeferredKeyActionExecutor.java
@@ -76,6 +76,18 @@
         getActionsBufferWithLazyCleanUp(keyCode, downTime).setExecutable();
     }
 
+    /**
+     * Clears all the queued action for given key code.
+     *
+     * @param keyCode the key code whose queued actions will be cleared.
+     */
+    public void cancelQueuedAction(int keyCode) {
+        TimedActionsBuffer actionsBuffer = mBuffers.get(keyCode);
+        if (actionsBuffer != null) {
+            actionsBuffer.clear();
+        }
+    }
+
     private TimedActionsBuffer getActionsBufferWithLazyCleanUp(int keyCode, long downTime) {
         TimedActionsBuffer buffer = mBuffers.get(keyCode);
         if (buffer == null || buffer.getDownTime() != downTime) {
@@ -146,6 +158,10 @@
             mActions.clear();
         }
 
+        void clear() {
+            mActions.clear();
+        }
+
         void dump(String prefix, PrintWriter pw) {
             if (mExecutable) {
                 pw.println(prefix + "  " + KeyEvent.keyCodeToString(mKeyCode) + ": executable");
diff --git a/services/core/java/com/android/server/policy/PhoneWindowManager.java b/services/core/java/com/android/server/policy/PhoneWindowManager.java
index e8b54d58..3000a1c 100644
--- a/services/core/java/com/android/server/policy/PhoneWindowManager.java
+++ b/services/core/java/com/android/server/policy/PhoneWindowManager.java
@@ -103,6 +103,7 @@
 import android.app.ActivityManager.RecentTaskInfo;
 import android.app.ActivityManagerInternal;
 import android.app.ActivityTaskManager;
+import android.app.ActivityTaskManager.RootTaskInfo;
 import android.app.AppOpsManager;
 import android.app.IActivityManager;
 import android.app.IUiModeManager;
@@ -584,6 +585,10 @@
     private int mLongPressOnStemPrimaryBehavior;
     private RecentTaskInfo mBackgroundRecentTaskInfoOnStemPrimarySingleKeyUp;
 
+    // The focused task at the time when the first STEM_PRIMARY key was released. This can only
+    // be accessed from the looper thread.
+    private RootTaskInfo mFocusedTaskInfoOnStemPrimarySingleKeyUp;
+
     private boolean mHandleVolumeKeysInWM;
 
     private boolean mPendingKeyguardOccluded;
@@ -2135,12 +2140,10 @@
     static class Injector {
         private final Context mContext;
         private final WindowManagerFuncs mWindowManagerFuncs;
-        private final Looper mLooper;
 
-        Injector(Context context, WindowManagerFuncs funcs, Looper looper) {
+        Injector(Context context, WindowManagerFuncs funcs) {
             mContext = context;
             mWindowManagerFuncs = funcs;
-            mLooper = looper;
         }
 
         Context getContext() {
@@ -2152,7 +2155,7 @@
         }
 
         Looper getLooper() {
-            return mLooper;
+            return Looper.myLooper();
         }
 
         AccessibilityShortcutController getAccessibilityShortcutController(
@@ -2195,7 +2198,7 @@
     /** {@inheritDoc} */
     @Override
     public void init(Context context, WindowManagerFuncs funcs) {
-        init(new Injector(context, funcs, Looper.myLooper()));
+        init(new Injector(context, funcs));
     }
 
     @VisibleForTesting
@@ -2723,7 +2726,7 @@
 
         @Override
         void onPress(long downTime, int unusedDisplayId) {
-            if (mShouldEarlyShortPressOnStemPrimary) {
+            if (shouldHandleStemPrimaryEarlyShortPress()) {
                 return;
             }
             // Short-press should be triggered only if app doesn't handle it.
@@ -2747,6 +2750,11 @@
             if (count == 3
                     && mTriplePressOnStemPrimaryBehavior
                     == TRIPLE_PRESS_PRIMARY_TOGGLE_ACCESSIBILITY) {
+                // Cancel any queued actions for current key code to prevent them from being
+                // launched after a11y layer enabled. If the action happens early,
+                // undoEarlySinglePress will make sure the correct task is on top.
+                mDeferredKeyActionExecutor.cancelQueuedAction(KeyEvent.KEYCODE_STEM_PRIMARY);
+                undoEarlySinglePress();
                 stemPrimaryPress(count);
             } else {
                 // Other multi-press gestures should be triggered only if app doesn't handle it.
@@ -2755,6 +2763,27 @@
             }
         }
 
+        /**
+         * This method undo the previously launched early-single-press action by bringing the
+         * focused task before launching early-single-press back to top.
+         */
+        private void undoEarlySinglePress() {
+            if (shouldHandleStemPrimaryEarlyShortPress()
+                    && mFocusedTaskInfoOnStemPrimarySingleKeyUp != null) {
+                try {
+                    mActivityManagerService.startActivityFromRecents(
+                            mFocusedTaskInfoOnStemPrimarySingleKeyUp.taskId, null);
+                } catch (RemoteException | IllegalArgumentException e) {
+                    Slog.e(
+                            TAG,
+                            "Failed to start task "
+                                    + mFocusedTaskInfoOnStemPrimarySingleKeyUp.taskId
+                                    + " from recents",
+                            e);
+                }
+            }
+        }
+
         @Override
         void onKeyUp(long eventTime, int count, int unusedDisplayId) {
             if (count == 1) {
@@ -2763,15 +2792,49 @@
                 // It is possible that we may navigate away from this task before the double
                 // press is detected, as a result of the first press, so we save the  current
                 // most recent task before that happens.
+                // TODO(b/311497918): guard this with DOUBLE_PRESS_PRIMARY_SWITCH_RECENT_APP
                 mBackgroundRecentTaskInfoOnStemPrimarySingleKeyUp =
                         mActivityTaskManagerInternal.getMostRecentTaskFromBackground();
-                if (mShouldEarlyShortPressOnStemPrimary) {
+
+                mFocusedTaskInfoOnStemPrimarySingleKeyUp = null;
+
+                if (shouldHandleStemPrimaryEarlyShortPress()) {
                     // Key-up gesture should be triggered only if app doesn't handle it.
                     mDeferredKeyActionExecutor.queueKeyAction(
-                            KeyEvent.KEYCODE_STEM_PRIMARY, eventTime, () -> stemPrimaryPress(1));
+                            KeyEvent.KEYCODE_STEM_PRIMARY,
+                            eventTime,
+                            () -> {
+                                // Save the info of the focused task on screen. This may be used
+                                // later to bring the current focused task back to top. For
+                                // example, stem primary triple press enables the A11y interface
+                                // on top of the current focused task. When early single press is
+                                // enabled for stem primary, the focused task could change to
+                                // something else upon first key up event. In that case, we will
+                                // bring the task recorded by this variable back to top. Then, start
+                                // A11y interface.
+                                try {
+                                    mFocusedTaskInfoOnStemPrimarySingleKeyUp =
+                                            mActivityManagerService.getFocusedRootTaskInfo();
+                                } catch (RemoteException e) {
+                                    Slog.e(
+                                            TAG,
+                                            "StemPrimaryKeyRule: onKeyUp: error while getting "
+                                                    + "focused task "
+                                                    + "info.",
+                                            e);
+                                }
+
+                                stemPrimaryPress(1);
+                            });
                 }
             }
         }
+
+        // TODO(b/311497918): make a shouldHandlePowerEarlyShortPress for power button.
+        private boolean shouldHandleStemPrimaryEarlyShortPress() {
+            return mShouldEarlyShortPressOnStemPrimary
+                    && mShortPressOnStemPrimaryBehavior == SHORT_PRESS_PRIMARY_LAUNCH_ALL_APPS;
+        }
     }
 
     private void initSingleKeyGestureRules(Looper looper) {
diff --git a/services/core/java/com/android/server/wm/ActivityRecord.java b/services/core/java/com/android/server/wm/ActivityRecord.java
index 1ce87a7..145eb3b 100644
--- a/services/core/java/com/android/server/wm/ActivityRecord.java
+++ b/services/core/java/com/android/server/wm/ActivityRecord.java
@@ -125,6 +125,7 @@
 import static android.view.WindowManager.TRANSIT_FLAG_OPEN_BEHIND;
 import static android.view.WindowManager.TRANSIT_OLD_UNSET;
 import static android.view.WindowManager.TRANSIT_RELAUNCH;
+import static android.window.TransitionInfo.FLAGS_IS_OCCLUDED_NO_ANIMATION;
 import static android.window.TransitionInfo.FLAG_IS_OCCLUDED;
 import static android.window.TransitionInfo.FLAG_STARTING_WINDOW_TRANSFER_RECIPIENT;
 
@@ -683,6 +684,7 @@
     private final WindowState.UpdateReportedVisibilityResults mReportedVisibilityResults =
             new WindowState.UpdateReportedVisibilityResults();
 
+    // TODO(b/317000737): Replace it with visibility states lookup.
     int mTransitionChangeFlags;
 
     /** Whether we need to setup the animation to animate only within the letterbox. */
@@ -5468,8 +5470,13 @@
         // Defer committing visibility until transition starts.
         if (isCollecting) {
             // It may be occluded by the activity above that calls convertFromTranslucent().
-            if (!visible && mTransitionController.inPlayingTransition(this)) {
-                mTransitionChangeFlags |= FLAG_IS_OCCLUDED;
+            // Or it may be restoring transient launch to invisible when finishing transition.
+            if (!visible) {
+                if (mTransitionController.inPlayingTransition(this)) {
+                    mTransitionChangeFlags |= FLAG_IS_OCCLUDED;
+                } else if (mTransitionController.inFinishingTransition(this)) {
+                    mTransitionChangeFlags |= FLAGS_IS_OCCLUDED_NO_ANIMATION;
+                }
             }
             return;
         }
diff --git a/services/core/java/com/android/server/wm/BackgroundActivityStartController.java b/services/core/java/com/android/server/wm/BackgroundActivityStartController.java
index 4929df80..eed46fe 100644
--- a/services/core/java/com/android/server/wm/BackgroundActivityStartController.java
+++ b/services/core/java/com/android/server/wm/BackgroundActivityStartController.java
@@ -36,6 +36,7 @@
 import static com.android.server.wm.PendingRemoteAnimationRegistry.TIMEOUT_MS;
 
 import static java.lang.annotation.RetentionPolicy.SOURCE;
+import static java.util.Objects.requireNonNull;
 
 import android.annotation.IntDef;
 import android.annotation.NonNull;
@@ -152,36 +153,25 @@
     static final int BAL_ALLOW_SDK_SANDBOX = 10;
 
     static String balCodeToString(@BalCode int balCode) {
-        switch (balCode) {
-            case BAL_ALLOW_ALLOWLISTED_COMPONENT:
-                return "BAL_ALLOW_ALLOWLISTED_COMPONENT";
-            case BAL_ALLOW_ALLOWLISTED_UID:
-                return "BAL_ALLOW_ALLOWLISTED_UID";
-            case BAL_ALLOW_DEFAULT:
-                return "BAL_ALLOW_DEFAULT";
-            case BAL_ALLOW_FOREGROUND:
-                return "BAL_ALLOW_FOREGROUND";
-            case BAL_ALLOW_GRACE_PERIOD:
-                return "BAL_ALLOW_GRACE_PERIOD";
-            case BAL_ALLOW_PENDING_INTENT:
-                return "BAL_ALLOW_PENDING_INTENT";
-            case BAL_ALLOW_PERMISSION:
-                return "BAL_ALLOW_PERMISSION";
-            case BAL_ALLOW_SAW_PERMISSION:
-                return "BAL_ALLOW_SAW_PERMISSION";
-            case BAL_ALLOW_SDK_SANDBOX:
-                return "BAL_ALLOW_SDK_SANDBOX";
-            case BAL_ALLOW_VISIBLE_WINDOW:
-                return "BAL_ALLOW_VISIBLE_WINDOW";
-            case BAL_BLOCK:
-                return "BAL_BLOCK";
-            default:
-                throw new IllegalArgumentException("Unexpected value: " + balCode);
-        }
+        return switch (balCode) {
+            case BAL_ALLOW_ALLOWLISTED_COMPONENT -> "BAL_ALLOW_ALLOWLISTED_COMPONENT";
+            case BAL_ALLOW_ALLOWLISTED_UID -> "BAL_ALLOW_ALLOWLISTED_UID";
+            case BAL_ALLOW_DEFAULT -> "BAL_ALLOW_DEFAULT";
+            case BAL_ALLOW_FOREGROUND -> "BAL_ALLOW_FOREGROUND";
+            case BAL_ALLOW_GRACE_PERIOD -> "BAL_ALLOW_GRACE_PERIOD";
+            case BAL_ALLOW_PENDING_INTENT -> "BAL_ALLOW_PENDING_INTENT";
+            case BAL_ALLOW_PERMISSION -> "BAL_ALLOW_PERMISSION";
+            case BAL_ALLOW_SAW_PERMISSION -> "BAL_ALLOW_SAW_PERMISSION";
+            case BAL_ALLOW_SDK_SANDBOX -> "BAL_ALLOW_SDK_SANDBOX";
+            case BAL_ALLOW_VISIBLE_WINDOW -> "BAL_ALLOW_VISIBLE_WINDOW";
+            case BAL_BLOCK -> "BAL_BLOCK";
+            default -> throw new IllegalArgumentException("Unexpected value: " + balCode);
+        };
     }
 
     @GuardedBy("mService.mGlobalLock")
-    private HashMap<Integer, FinishedActivityEntry> mTaskIdToFinishedActivity = new HashMap<>();
+    private final HashMap<Integer, FinishedActivityEntry> mTaskIdToFinishedActivity =
+            new HashMap<>();
     @GuardedBy("mService.mGlobalLock")
     private FinishedActivityEntry mTopFinishedActivity = null;
 
@@ -467,9 +457,8 @@
             return !blocks();
         }
 
-        BalVerdict setOnlyCreatorAllows(boolean onlyCreatorAllows) {
+        void setOnlyCreatorAllows(boolean onlyCreatorAllows) {
             mOnlyCreatorAllows = onlyCreatorAllows;
-            return this;
         }
 
         boolean onlyCreatorAllows() {
@@ -481,10 +470,6 @@
             return this;
         }
 
-        private boolean isBasedOnRealCaller() {
-            return mBasedOnRealCaller;
-        }
-
         public String toString() {
             StringBuilder builder = new StringBuilder();
             builder.append(balCodeToString(mCode));
@@ -583,15 +568,14 @@
         BalVerdict resultForCaller = checkBackgroundActivityStartAllowedByCaller(state);
 
         if (!state.hasRealCaller()) {
-            BalVerdict resultForRealCaller = null; // nothing to compute
             if (resultForCaller.allows()) {
                 if (DEBUG_ACTIVITY_STARTS) {
                     Slog.d(TAG, "Background activity start allowed. "
-                            + state.dump(resultForCaller, resultForRealCaller));
+                            + state.dump(resultForCaller, resultForCaller));
                 }
                 return statsLog(resultForCaller, state);
             }
-            return abortLaunch(state, resultForCaller, resultForRealCaller);
+            return abortLaunch(state, resultForCaller, resultForCaller);
         }
 
         // The realCaller result is only calculated for PendingIntents (indicated by a valid
@@ -653,7 +637,7 @@
                                 + " if the PI creator upgrades target_sdk to 35+"
                                 + " AND the PI sender upgrades target_sdk to 34+! "
                                 + state.dump(resultForCaller, resultForRealCaller));
-                showBalRiskToast("BAL would be blocked", state);
+                showBalRiskToast();
                 // return the realCaller result for backwards compatibility
                 return statsLog(resultForRealCaller, state);
             }
@@ -679,7 +663,7 @@
                                 + " if the PI creator upgrades target_sdk to 35+! "
                                 + " (missing opt in by PI creator)! "
                                 + state.dump(resultForCaller, resultForRealCaller));
-                showBalRiskToast("BAL would be blocked", state);
+                showBalRiskToast();
                 return statsLog(resultForCaller, state);
             }
             Slog.wtf(TAG,
@@ -696,7 +680,7 @@
                                 + " if the PI sender upgrades target_sdk to 34+! "
                                 + " (missing opt in by PI sender)! "
                                 + state.dump(resultForCaller, resultForRealCaller));
-                showBalRiskToast("BAL would be blocked", state);
+                showBalRiskToast();
                 return statsLog(resultForRealCaller, state);
             }
             Slog.wtf(TAG, "Without Android 14 BAL hardening this activity start would be allowed"
@@ -712,7 +696,7 @@
             BalVerdict resultForRealCaller) {
         Slog.w(TAG, "Background activity launch blocked! "
                 + state.dump(resultForCaller, resultForRealCaller));
-        showBalBlockedToast("BAL blocked", state);
+        showBalBlockedToast();
         return statsLog(BalVerdict.BLOCK, state);
     }
 
@@ -910,7 +894,7 @@
 
     /**
      * Check if the app allows BAL.
-     *
+     * <p>
      * See {@link BackgroundLaunchProcessController#areBackgroundActivityStartsAllowed(int, int,
      * String, int, boolean, boolean, boolean, long, long, long)} for details on the
      * exceptions.
@@ -1104,19 +1088,15 @@
         return true;
     }
 
-    private void showBalBlockedToast(String toastText, BalState state) {
+    private void showBalBlockedToast() {
         if (balShowToastsBlocked()) {
-            showToast(toastText
-                    + " caller:" + state.mCallingPackage
-                    + " realCaller:" + state.mRealCallingPackage);
+            showToast("BAL blocked. go/debug-bal");
         }
     }
 
-    private void showBalRiskToast(String toastText, BalState state) {
+    private void showBalRiskToast() {
         if (balShowToasts()) {
-            showToast(toastText
-                    + " caller:" + state.mCallingPackage
-                    + " realCaller:" + state.mRealCallingPackage);
+            showToast("BAL allowed in compat mode. go/debug-bal");
         }
     }
 
@@ -1281,7 +1261,7 @@
      * 2. Or top of an adjacent task fragment to (1)
      * <p>
      * The 'sourceRecord' can be considered top even if it is 'finishing'
-     *
+     * <p>
      * Returns a class where the elements are:
      * <pre>
      * shouldBlockActivityStart: {@code true} if we should actually block the transition (takes into
@@ -1344,7 +1324,7 @@
     /**
      * Determines if a source is allowed to add or remove activities from the task,
      * if the current ActivityRecord is above it in the stack
-     *
+     * <p>
      * A transition is blocked ({@code false} returned) if all of the following are met:
      * <pre>
      * 1. The source activity and the current activity record belong to different apps
@@ -1489,8 +1469,8 @@
 
         if (code == BAL_ALLOW_PENDING_INTENT
                 && (callingUid == Process.SYSTEM_UID || realCallingUid == Process.SYSTEM_UID)) {
-            String activityName =
-                    intent != null ? intent.getComponent().flattenToShortString() : "";
+            String activityName = intent != null
+                    ? requireNonNull(intent.getComponent()).flattenToShortString() : "";
             FrameworkStatsLog.write(FrameworkStatsLog.BAL_ALLOWED,
                     activityName,
                     BAL_ALLOW_PENDING_INTENT,
diff --git a/services/core/java/com/android/server/wm/Transition.java b/services/core/java/com/android/server/wm/Transition.java
index f020bfa..3117db5 100644
--- a/services/core/java/com/android/server/wm/Transition.java
+++ b/services/core/java/com/android/server/wm/Transition.java
@@ -43,6 +43,7 @@
 import static android.view.WindowManager.TransitionType;
 import static android.view.WindowManager.transitTypeToString;
 import static android.window.TaskFragmentAnimationParams.DEFAULT_ANIMATION_BACKGROUND_COLOR;
+import static android.window.TransitionInfo.FLAGS_IS_OCCLUDED_NO_ANIMATION;
 import static android.window.TransitionInfo.FLAG_DISPLAY_HAS_ALERT_WINDOWS;
 import static android.window.TransitionInfo.FLAG_FILLS_TASK;
 import static android.window.TransitionInfo.FLAG_IN_TASK_WITH_EMBEDDED_ACTIVITY;
@@ -3067,6 +3068,10 @@
                         Slog.e(TAG, "Unexpected launch-task-behind operation in shell transition");
                         flags |= FLAG_TASK_LAUNCHING_BEHIND;
                     }
+                    if ((topActivity.mTransitionChangeFlags & FLAGS_IS_OCCLUDED_NO_ANIMATION)
+                            == FLAGS_IS_OCCLUDED_NO_ANIMATION) {
+                        flags |= FLAGS_IS_OCCLUDED_NO_ANIMATION;
+                    }
                 }
                 if (task.voiceSession != null) {
                     flags |= FLAG_IS_VOICE_INTERACTION;
diff --git a/services/core/java/com/android/server/wm/WindowState.java b/services/core/java/com/android/server/wm/WindowState.java
index 9c21e4c..ec4bdf9 100644
--- a/services/core/java/com/android/server/wm/WindowState.java
+++ b/services/core/java/com/android/server/wm/WindowState.java
@@ -202,7 +202,6 @@
 import android.os.Debug;
 import android.os.IBinder;
 import android.os.PowerManager;
-import android.os.PowerManager.WakeReason;
 import android.os.RemoteCallbackList;
 import android.os.RemoteException;
 import android.os.SystemClock;
@@ -702,11 +701,6 @@
      */
     private final Region mTapExcludeRegion = new Region();
 
-    /**
-     * Used for testing because the real PowerManager is final.
-     */
-    private PowerManagerWrapper mPowerManagerWrapper;
-
     private static final StringBuilder sTmpSB = new StringBuilder();
 
     /**
@@ -1061,34 +1055,9 @@
         return mOnBackInvokedCallbackInfo;
     }
 
-    interface PowerManagerWrapper {
-        void wakeUp(long time, @WakeReason int reason, String details);
-
-        boolean isInteractive();
-
-    }
-
     WindowState(WindowManagerService service, Session s, IWindow c, WindowToken token,
             WindowState parentWindow, int appOp, WindowManager.LayoutParams a, int viewVisibility,
             int ownerId, int showUserId, boolean ownerCanAddInternalSystemWindow) {
-        this(service, s, c, token, parentWindow, appOp, a, viewVisibility, ownerId, showUserId,
-                ownerCanAddInternalSystemWindow, new PowerManagerWrapper() {
-                    @Override
-                    public void wakeUp(long time, @WakeReason int reason, String details) {
-                        service.mPowerManager.wakeUp(time, reason, details);
-                    }
-
-                    @Override
-                    public boolean isInteractive() {
-                        return service.mPowerManager.isInteractive();
-                    }
-                });
-    }
-
-    WindowState(WindowManagerService service, Session s, IWindow c, WindowToken token,
-            WindowState parentWindow, int appOp, WindowManager.LayoutParams a, int viewVisibility,
-            int ownerId, int showUserId, boolean ownerCanAddInternalSystemWindow,
-            PowerManagerWrapper powerManagerWrapper) {
         super(service);
         mTmpTransaction = service.mTransactionFactory.get();
         mSession = s;
@@ -1106,7 +1075,6 @@
         mViewVisibility = viewVisibility;
         mPolicy = mWmService.mPolicy;
         mContext = mWmService.mContext;
-        mPowerManagerWrapper = powerManagerWrapper;
         mForceSeamlesslyRotate = token.mRoundedCornerOverlay;
         mInputWindowHandle = new InputWindowHandleWrapper(new InputWindowHandle(
                 mActivityRecord != null
@@ -2831,12 +2799,12 @@
             boolean canTurnScreenOn = mActivityRecord == null || mActivityRecord.currentLaunchCanTurnScreenOn();
 
             if (allowTheaterMode && canTurnScreenOn
-                        && (mWmService.mAtmService.isDreaming()
-                        || !mPowerManagerWrapper.isInteractive())) {
+                    && (mWmService.mAtmService.isDreaming()
+                            || !mWmService.mPowerManager.isInteractive())) {
                 if (DEBUG_VISIBILITY || DEBUG_POWER) {
                     Slog.v(TAG, "Relayout window turning screen on: " + this);
                 }
-                mPowerManagerWrapper.wakeUp(SystemClock.uptimeMillis(),
+                mWmService.mPowerManager.wakeUp(SystemClock.uptimeMillis(),
                         PowerManager.WAKE_REASON_APPLICATION, "android.server.wm:SCREEN_ON_FLAG");
             }
 
diff --git a/services/core/jni/OWNERS b/services/core/jni/OWNERS
index 061fe0f..cc08488 100644
--- a/services/core/jni/OWNERS
+++ b/services/core/jni/OWNERS
@@ -31,5 +31,5 @@
 per-file com_android_server_am_CachedAppOptimizer.cpp = timmurray@google.com, edgararriaga@google.com, dualli@google.com, carmenjackson@google.com, philipcuadra@google.com
 per-file com_android_server_companion_virtual_InputController.cpp = file:/services/companion/java/com/android/server/companion/virtual/OWNERS
 
-# Bug component : 158088 = per-file com_android_server_utils_AnrTimer*.java
-per-file com_android_server_utils_AnrTimer*.java = file:/PERFORMANCE_OWNERS
+# Bug component : 158088 = per-file *AnrTimer*
+per-file *AnrTimer* = file:/PERFORMANCE_OWNERS
diff --git a/services/core/xsd/display-device-config/display-device-config.xsd b/services/core/xsd/display-device-config/display-device-config.xsd
index adbd3c9..3cbceec 100644
--- a/services/core/xsd/display-device-config/display-device-config.xsd
+++ b/services/core/xsd/display-device-config/display-device-config.xsd
@@ -631,8 +631,8 @@
             <xs:annotation name="nonnull"/>
             <xs:annotation name="final"/>
         </xs:element>
-        <xs:element name="mode" type="xs:string" minOccurs="0"/>
-        <xs:element name="setting" type="xs:string" minOccurs="0"/>
+        <xs:element name="mode" type="AutoBrightnessModeName" minOccurs="0"/>
+        <xs:element name="setting" type="AutoBrightnessSettingName" minOccurs="0"/>
     </xs:complexType>
 
     <!-- Represents a point in the display brightness mapping, representing the lux level from the
@@ -765,4 +765,23 @@
             </xs:element>
         </xs:sequence>
     </xs:complexType>
+
+    <!-- Predefined type names as defined by
+    AutomaticBrightnessController.AutomaticBrightnessMode -->
+    <xs:simpleType  name="AutoBrightnessModeName">
+        <xs:restriction base="xs:string">
+            <xs:enumeration value="default"/>
+            <xs:enumeration value="idle"/>
+            <xs:enumeration value="doze"/>
+        </xs:restriction>
+    </xs:simpleType>
+
+    <!-- Predefined auto-brighntess settings -->
+    <xs:simpleType  name="AutoBrightnessSettingName">
+        <xs:restriction base="xs:string">
+            <xs:enumeration value="dim"/>
+            <xs:enumeration value="normal"/>
+            <xs:enumeration value="bright"/>
+        </xs:restriction>
+    </xs:simpleType>
 </xs:schema>
diff --git a/services/core/xsd/display-device-config/schema/current.txt b/services/core/xsd/display-device-config/schema/current.txt
index 98c95ed..79ea274 100644
--- a/services/core/xsd/display-device-config/schema/current.txt
+++ b/services/core/xsd/display-device-config/schema/current.txt
@@ -16,6 +16,20 @@
     method public void setEnabled(boolean);
   }
 
+  public enum AutoBrightnessModeName {
+    method public String getRawName();
+    enum_constant public static final com.android.server.display.config.AutoBrightnessModeName _default;
+    enum_constant public static final com.android.server.display.config.AutoBrightnessModeName doze;
+    enum_constant public static final com.android.server.display.config.AutoBrightnessModeName idle;
+  }
+
+  public enum AutoBrightnessSettingName {
+    method public String getRawName();
+    enum_constant public static final com.android.server.display.config.AutoBrightnessSettingName bright;
+    enum_constant public static final com.android.server.display.config.AutoBrightnessSettingName dim;
+    enum_constant public static final com.android.server.display.config.AutoBrightnessSettingName normal;
+  }
+
   public class BlockingZoneConfig {
     ctor public BlockingZoneConfig();
     method public final com.android.server.display.config.BlockingZoneThreshold getBlockingZoneThreshold();
@@ -219,11 +233,11 @@
   public class LuxToBrightnessMapping {
     ctor public LuxToBrightnessMapping();
     method @NonNull public final com.android.server.display.config.NonNegativeFloatToFloatMap getMap();
-    method public String getMode();
-    method public String getSetting();
+    method public com.android.server.display.config.AutoBrightnessModeName getMode();
+    method public com.android.server.display.config.AutoBrightnessSettingName getSetting();
     method public final void setMap(@NonNull com.android.server.display.config.NonNegativeFloatToFloatMap);
-    method public void setMode(String);
-    method public void setSetting(String);
+    method public void setMode(com.android.server.display.config.AutoBrightnessModeName);
+    method public void setSetting(com.android.server.display.config.AutoBrightnessSettingName);
   }
 
   public class NitsMap {
diff --git a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageParserTest.java b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageParserTest.java
index 03e45a2..71f5c75 100644
--- a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageParserTest.java
+++ b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/PackageParserTest.java
@@ -58,6 +58,8 @@
 import androidx.test.filters.SmallTest;
 import androidx.test.runner.AndroidJUnit4;
 
+import com.android.internal.pm.parsing.PackageParser2;
+import com.android.internal.pm.parsing.PackageParserException;
 import com.android.internal.pm.parsing.pkg.PackageImpl;
 import com.android.internal.pm.parsing.pkg.ParsedPackage;
 import com.android.internal.pm.permission.CompatibilityPermissionInfo;
@@ -84,7 +86,7 @@
 import com.android.internal.util.ArrayUtils;
 import com.android.server.pm.parsing.PackageCacher;
 import com.android.server.pm.parsing.PackageInfoUtils;
-import com.android.server.pm.parsing.PackageParser2;
+import com.android.server.pm.parsing.PackageParserUtils;
 import com.android.server.pm.parsing.TestPackageParser2;
 import com.android.server.pm.parsing.pkg.AndroidPackageUtils;
 import com.android.server.pm.pkg.AndroidPackage;
@@ -185,7 +187,7 @@
 
     @Test
     public void test_serializePackage() throws Exception {
-        try (PackageParser2 pp = PackageParser2.forParsingFileWithDefaults()) {
+        try (PackageParser2 pp = PackageParserUtils.forParsingFileWithDefaults()) {
             AndroidPackage pkg = pp.parsePackage(FRAMEWORK, 0 /* parseFlags */,
                     true /* useCaches */).hideAsFinal();
 
@@ -363,7 +365,7 @@
                     actualDisplayCategory = activity.getRequiredDisplayCategory();
                 }
             }
-        } catch (PackageManagerException e) {
+        } catch (PackageParserException e) {
             assertThat(e.getMessage()).contains(
                     "requiredDisplayCategory attribute can only consist"
                             + " of alphanumeric characters, '_', and '.'");
diff --git a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/ParallelPackageParserTest.java b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/ParallelPackageParserTest.java
index 8a74e24..3761240 100644
--- a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/ParallelPackageParserTest.java
+++ b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/ParallelPackageParserTest.java
@@ -21,8 +21,8 @@
 
 import androidx.test.runner.AndroidJUnit4;
 
+import com.android.internal.pm.parsing.PackageParser2;
 import com.android.internal.pm.parsing.pkg.ParsedPackage;
-import com.android.server.pm.parsing.PackageParser2;
 import com.android.server.pm.parsing.TestPackageParser2;
 
 import junit.framework.Assert;
diff --git a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/parsing/PackageParserLegacyCoreTest.java b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/parsing/PackageParserLegacyCoreTest.java
index b63950c..a28b28a 100644
--- a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/parsing/PackageParserLegacyCoreTest.java
+++ b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/parsing/PackageParserLegacyCoreTest.java
@@ -38,6 +38,7 @@
 import androidx.test.filters.SmallTest;
 import androidx.test.runner.AndroidJUnit4;
 
+import com.android.internal.pm.parsing.PackageParserException;
 import com.android.internal.pm.parsing.pkg.ParsedPackage;
 import com.android.internal.pm.pkg.component.ParsedActivityUtils;
 import com.android.internal.pm.pkg.component.ParsedComponent;
@@ -45,7 +46,6 @@
 import com.android.internal.pm.pkg.component.ParsedPermission;
 import com.android.internal.pm.pkg.component.ParsedPermissionUtils;
 import com.android.internal.util.ArrayUtils;
-import com.android.server.pm.PackageManagerException;
 import com.android.server.pm.pkg.AndroidPackage;
 import com.android.server.pm.test.service.server.R;
 
@@ -608,7 +608,7 @@
             try {
                 parsePackage(filename, resId, x -> x);
                 expect.withMessage("Expected parsing error %s from %s", result, filename).fail();
-            } catch (PackageManagerException expected) {
+            } catch (PackageParserException expected) {
                 expect.that(expected.error).isEqualTo(result);
             }
         }
diff --git a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/parsing/SystemPartitionParseTest.kt b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/parsing/SystemPartitionParseTest.kt
index 98af63c..1d668cd 100644
--- a/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/parsing/SystemPartitionParseTest.kt
+++ b/services/tests/PackageManagerServiceTests/server/src/com/android/server/pm/parsing/SystemPartitionParseTest.kt
@@ -18,8 +18,8 @@
 
 import android.content.pm.PackageManager
 import android.platform.test.annotations.Postsubmit
+import com.android.internal.pm.parsing.PackageParserException
 import com.android.internal.pm.pkg.parsing.ParsingPackageUtils
-import com.android.server.pm.PackageManagerException
 import com.android.server.pm.PackageManagerService
 import com.android.server.pm.PackageManagerServiceUtils
 import java.io.File
@@ -39,7 +39,7 @@
 @Postsubmit
 class SystemPartitionParseTest {
 
-    private val parser = PackageParser2.forParsingFileWithDefaults()
+    private val parser = PackageParserUtils.forParsingFileWithDefaults()
 
     @get:Rule
     val tempFolder = TemporaryFolder()
@@ -86,7 +86,7 @@
                     }
                 }
                 .mapNotNull { it.exceptionOrNull() }
-                .filterNot { (it as? PackageManagerException)?.error ==
+                .filterNot { (it as? PackageParserException)?.error ==
                         PackageManager.INSTALL_PARSE_FAILED_SKIPPED }
 
         if (exceptions.isEmpty()) return
diff --git a/services/tests/displayservicetests/src/com/android/server/display/BrightnessMappingStrategyTest.java b/services/tests/displayservicetests/src/com/android/server/display/BrightnessMappingStrategyTest.java
index c5a1ba1..fb73aff 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/BrightnessMappingStrategyTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/BrightnessMappingStrategyTest.java
@@ -17,6 +17,7 @@
 package com.android.server.display;
 
 import static com.android.server.display.AutomaticBrightnessController.AUTO_BRIGHTNESS_MODE_DEFAULT;
+import static com.android.server.display.AutomaticBrightnessController.AUTO_BRIGHTNESS_MODE_DOZE;
 import static com.android.server.display.AutomaticBrightnessController.AUTO_BRIGHTNESS_MODE_IDLE;
 
 import static org.junit.Assert.assertEquals;
@@ -29,21 +30,22 @@
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.when;
 
-import android.content.res.Resources;
 import android.content.res.TypedArray;
 import android.hardware.display.BrightnessConfiguration;
 import android.os.PowerManager;
+import android.provider.Settings;
+import android.testing.TestableContext;
 import android.util.MathUtils;
 import android.util.Spline;
 
 import androidx.test.filters.SmallTest;
+import androidx.test.platform.app.InstrumentationRegistry;
 import androidx.test.runner.AndroidJUnit4;
 
-import com.android.server.display.whitebalance.DisplayWhiteBalanceController;
-
+import org.junit.Before;
+import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
-import org.mockito.Mock;
 
 import java.util.Arrays;
 
@@ -154,15 +156,23 @@
 
     private static final float TOLERANCE = 0.0001f;
 
-    @Mock
-    DisplayWhiteBalanceController mMockDwbc;
+    @Rule
+    public final TestableContext mContext = new TestableContext(
+            InstrumentationRegistry.getInstrumentation().getContext());
+
+    @Before
+    public void setUp() {
+        Settings.System.putInt(mContext.getContentResolver(),
+                Settings.System.SCREEN_BRIGHTNESS_FOR_ALS,
+                Settings.System.SCREEN_BRIGHTNESS_AUTOMATIC_NORMAL);
+    }
 
     @Test
     public void testSimpleStrategyMappingAtControlPoints() {
-        Resources res = createResources();
+        setUpResources();
         DisplayDeviceConfig ddc = new DdcBuilder().setAutoBrightnessLevels(DISPLAY_LEVELS).build();
-        BrightnessMappingStrategy simple = BrightnessMappingStrategy.create(res, ddc,
-                AUTO_BRIGHTNESS_MODE_DEFAULT, mMockDwbc);
+        BrightnessMappingStrategy simple = BrightnessMappingStrategy.create(mContext, ddc,
+                AUTO_BRIGHTNESS_MODE_DEFAULT, /* displayWhiteBalanceController= */ null);
         assertNotNull("BrightnessMappingStrategy should not be null", simple);
         for (int i = 0; i < LUX_LEVELS.length; i++) {
             assertEquals(DISPLAY_LEVELS[i], simple.getBrightness(LUX_LEVELS[i]), TOLERANCE);
@@ -171,10 +181,10 @@
 
     @Test
     public void testSimpleStrategyMappingBetweenControlPoints() {
-        Resources res = createResources();
+        setUpResources();
         DisplayDeviceConfig ddc = new DdcBuilder().setAutoBrightnessLevels(DISPLAY_LEVELS).build();
-        BrightnessMappingStrategy simple = BrightnessMappingStrategy.create(res, ddc,
-                AUTO_BRIGHTNESS_MODE_DEFAULT, mMockDwbc);
+        BrightnessMappingStrategy simple = BrightnessMappingStrategy.create(mContext, ddc,
+                AUTO_BRIGHTNESS_MODE_DEFAULT, /* displayWhiteBalanceController= */ null);
         assertNotNull("BrightnessMappingStrategy should not be null", simple);
         for (int i = 1; i < LUX_LEVELS.length; i++) {
             final float lux = (LUX_LEVELS[i - 1] + LUX_LEVELS[i]) / 2;
@@ -186,10 +196,10 @@
 
     @Test
     public void testSimpleStrategyIgnoresNewConfiguration() {
-        Resources res = createResources();
+        setUpResources();
         DisplayDeviceConfig ddc = new DdcBuilder().setAutoBrightnessLevels(DISPLAY_LEVELS).build();
-        BrightnessMappingStrategy strategy = BrightnessMappingStrategy.create(res, ddc,
-                AUTO_BRIGHTNESS_MODE_DEFAULT, mMockDwbc);
+        BrightnessMappingStrategy strategy = BrightnessMappingStrategy.create(mContext, ddc,
+                AUTO_BRIGHTNESS_MODE_DEFAULT, /* displayWhiteBalanceController= */ null);
 
         final float[] lux = { 0f, 1f };
         final float[] nits = { 0, PowerManager.BRIGHTNESS_ON };
@@ -202,10 +212,10 @@
 
     @Test
     public void testSimpleStrategyIgnoresNullConfiguration() {
-        Resources res = createResources();
+        setUpResources();
         DisplayDeviceConfig ddc = new DdcBuilder().setAutoBrightnessLevels(DISPLAY_LEVELS).build();
-        BrightnessMappingStrategy strategy = BrightnessMappingStrategy.create(res, ddc,
-                AUTO_BRIGHTNESS_MODE_DEFAULT, mMockDwbc);
+        BrightnessMappingStrategy strategy = BrightnessMappingStrategy.create(mContext, ddc,
+                AUTO_BRIGHTNESS_MODE_DEFAULT, /* displayWhiteBalanceController= */ null);
 
         strategy.setBrightnessConfiguration(null);
         final int n = DISPLAY_LEVELS.length;
@@ -216,11 +226,11 @@
 
     @Test
     public void testPhysicalStrategyMappingAtControlPoints() {
-        Resources res = createResources();
+        setUpResources();
         DisplayDeviceConfig ddc = new DdcBuilder().setAutoBrightnessLevelsNits(DISPLAY_LEVELS_NITS)
                 .build();
-        BrightnessMappingStrategy physical = BrightnessMappingStrategy.create(res, ddc,
-                AUTO_BRIGHTNESS_MODE_DEFAULT, mMockDwbc);
+        BrightnessMappingStrategy physical = BrightnessMappingStrategy.create(mContext, ddc,
+                AUTO_BRIGHTNESS_MODE_DEFAULT, /* displayWhiteBalanceController= */ null);
         assertNotNull("BrightnessMappingStrategy should not be null", physical);
         for (int i = 0; i < LUX_LEVELS.length; i++) {
             final float expectedLevel = MathUtils.map(DISPLAY_RANGE_NITS[0], DISPLAY_RANGE_NITS[1],
@@ -235,11 +245,11 @@
 
     @Test
     public void testPhysicalStrategyMappingBetweenControlPoints() {
-        Resources res = createResources();
+        setUpResources();
         DisplayDeviceConfig ddc = new DdcBuilder().setBrightnessRange(BACKLIGHT_RANGE_ZERO_TO_ONE)
                 .setAutoBrightnessLevelsNits(DISPLAY_LEVELS_NITS).build();
-        BrightnessMappingStrategy physical = BrightnessMappingStrategy.create(res, ddc,
-                AUTO_BRIGHTNESS_MODE_DEFAULT, mMockDwbc);
+        BrightnessMappingStrategy physical = BrightnessMappingStrategy.create(mContext, ddc,
+                AUTO_BRIGHTNESS_MODE_DEFAULT, /* displayWhiteBalanceController= */ null);
         assertNotNull("BrightnessMappingStrategy should not be null", physical);
         Spline brightnessToNits =
                 Spline.createSpline(BACKLIGHT_RANGE_ZERO_TO_ONE, DISPLAY_RANGE_NITS);
@@ -254,11 +264,11 @@
 
     @Test
     public void testPhysicalStrategyUsesNewConfigurations() {
-        Resources res = createResources();
+        setUpResources();
         DisplayDeviceConfig ddc = new DdcBuilder().setAutoBrightnessLevelsNits(DISPLAY_LEVELS_NITS)
                 .build();
-        BrightnessMappingStrategy strategy = BrightnessMappingStrategy.create(res, ddc,
-                AUTO_BRIGHTNESS_MODE_DEFAULT, mMockDwbc);
+        BrightnessMappingStrategy strategy = BrightnessMappingStrategy.create(mContext, ddc,
+                AUTO_BRIGHTNESS_MODE_DEFAULT, /* displayWhiteBalanceController= */ null);
 
         final float[] lux = {0f, 1f};
         final float[] nits = {
@@ -281,11 +291,11 @@
 
     @Test
     public void testPhysicalStrategyRecalculateSplines() {
-        Resources res = createResources();
+        setUpResources();
         DisplayDeviceConfig ddc = new DdcBuilder().setAutoBrightnessLevelsNits(DISPLAY_LEVELS_NITS)
                 .build();
-        BrightnessMappingStrategy strategy = BrightnessMappingStrategy.create(res, ddc,
-                AUTO_BRIGHTNESS_MODE_DEFAULT, mMockDwbc);
+        BrightnessMappingStrategy strategy = BrightnessMappingStrategy.create(mContext, ddc,
+                AUTO_BRIGHTNESS_MODE_DEFAULT, /* displayWhiteBalanceController= */ null);
         float[] adjustedNits50p = new float[DISPLAY_RANGE_NITS.length];
         for (int i = 0; i < DISPLAY_RANGE_NITS.length; i++) {
             adjustedNits50p[i] = DISPLAY_RANGE_NITS[i] * 0.5f;
@@ -326,12 +336,12 @@
 
     @Test
     public void testDefaultStrategyIsPhysical() {
-        Resources res = createResources();
+        setUpResources();
         DisplayDeviceConfig ddc = new DdcBuilder().setAutoBrightnessLevels(DISPLAY_LEVELS)
                 .setAutoBrightnessLevelsNits(DISPLAY_LEVELS_NITS)
                 .build();
-        BrightnessMappingStrategy strategy = BrightnessMappingStrategy.create(res, ddc,
-                AUTO_BRIGHTNESS_MODE_DEFAULT, mMockDwbc);
+        BrightnessMappingStrategy strategy = BrightnessMappingStrategy.create(mContext, ddc,
+                AUTO_BRIGHTNESS_MODE_DEFAULT, /* displayWhiteBalanceController= */ null);
         assertTrue(strategy instanceof BrightnessMappingStrategy.PhysicalMappingStrategy);
     }
 
@@ -342,19 +352,19 @@
         float tmp = lux[idx];
         lux[idx] = lux[idx + 1];
         lux[idx + 1] = tmp;
-        Resources res = createResources();
+        setUpResources();
         DisplayDeviceConfig ddc = new DdcBuilder().setAutoBrightnessLevelsLux(lux)
                 .setAutoBrightnessLevelsNits(DISPLAY_LEVELS_NITS).build();
-        BrightnessMappingStrategy strategy = BrightnessMappingStrategy.create(res, ddc,
-                AUTO_BRIGHTNESS_MODE_DEFAULT, mMockDwbc);
+        BrightnessMappingStrategy strategy = BrightnessMappingStrategy.create(mContext, ddc,
+                AUTO_BRIGHTNESS_MODE_DEFAULT, /* displayWhiteBalanceController= */ null);
         assertNull(strategy);
 
         // And make sure we get the same result even if it's monotone but not increasing.
         lux[idx] = lux[idx + 1];
         ddc = new DdcBuilder().setAutoBrightnessLevelsLux(lux)
                 .setAutoBrightnessLevelsNits(DISPLAY_LEVELS_NITS).build();
-        strategy = BrightnessMappingStrategy.create(res, ddc, AUTO_BRIGHTNESS_MODE_DEFAULT,
-                mMockDwbc);
+        strategy = BrightnessMappingStrategy.create(mContext, ddc, AUTO_BRIGHTNESS_MODE_DEFAULT,
+                /* displayWhiteBalanceController= */ null);
         assertNull(strategy);
     }
 
@@ -365,74 +375,74 @@
         // Make sure it's strictly increasing so that the only failure is the differing array
         // lengths
         lux[lux.length - 1] = lux[lux.length - 2] + 1;
-        Resources res = createResources();
+        setUpResources();
         DisplayDeviceConfig ddc = new DdcBuilder().setAutoBrightnessLevelsLux(lux)
                 .setAutoBrightnessLevelsNits(DISPLAY_LEVELS_NITS).build();
-        BrightnessMappingStrategy strategy = BrightnessMappingStrategy.create(res, ddc,
-                AUTO_BRIGHTNESS_MODE_DEFAULT, mMockDwbc);
+        BrightnessMappingStrategy strategy = BrightnessMappingStrategy.create(mContext, ddc,
+                AUTO_BRIGHTNESS_MODE_DEFAULT, /* displayWhiteBalanceController= */ null);
         assertNull(strategy);
 
         ddc = new DdcBuilder().setAutoBrightnessLevelsLux(lux)
                 .setAutoBrightnessLevelsNits(DISPLAY_LEVELS_NITS)
                 .setAutoBrightnessLevels(DISPLAY_LEVELS).build();
-        strategy = BrightnessMappingStrategy.create(res, ddc, AUTO_BRIGHTNESS_MODE_DEFAULT,
-                mMockDwbc);
+        strategy = BrightnessMappingStrategy.create(mContext, ddc, AUTO_BRIGHTNESS_MODE_DEFAULT,
+                /* displayWhiteBalanceController= */ null);
         assertNull(strategy);
 
         // Extra backlight level
         final float[] backlight = Arrays.copyOf(DISPLAY_LEVELS, DISPLAY_LEVELS.length + 1);
         backlight[backlight.length - 1] = backlight[backlight.length - 2] + 1;
-        res = createResources();
+        setUpResources();
         ddc = new DdcBuilder().setAutoBrightnessLevels(backlight).build();
-        strategy = BrightnessMappingStrategy.create(res, ddc, AUTO_BRIGHTNESS_MODE_DEFAULT,
-                mMockDwbc);
+        strategy = BrightnessMappingStrategy.create(mContext, ddc, AUTO_BRIGHTNESS_MODE_DEFAULT,
+                /* displayWhiteBalanceController= */ null);
         assertNull(strategy);
 
         // Extra nits level
         final float[] nits = Arrays.copyOf(DISPLAY_RANGE_NITS, DISPLAY_LEVELS_NITS.length + 1);
         nits[nits.length - 1] = nits[nits.length - 2] + 1;
-        res = createResources();
+        setUpResources();
         ddc = new DdcBuilder().setAutoBrightnessLevelsNits(nits)
                 .setAutoBrightnessLevels(EMPTY_FLOAT_ARRAY).build();
-        strategy = BrightnessMappingStrategy.create(res, ddc, AUTO_BRIGHTNESS_MODE_DEFAULT,
-                mMockDwbc);
+        strategy = BrightnessMappingStrategy.create(mContext, ddc, AUTO_BRIGHTNESS_MODE_DEFAULT,
+                /* displayWhiteBalanceController= */ null);
         assertNull(strategy);
     }
 
     @Test
     public void testPhysicalStrategyRequiresNitsMapping() {
-        Resources res = createResources();
+        setUpResources();
         DisplayDeviceConfig ddc = new DdcBuilder().setNitsRange(EMPTY_FLOAT_ARRAY).build();
-        BrightnessMappingStrategy physical = BrightnessMappingStrategy.create(res, ddc,
-                AUTO_BRIGHTNESS_MODE_DEFAULT, mMockDwbc);
+        BrightnessMappingStrategy physical = BrightnessMappingStrategy.create(mContext, ddc,
+                AUTO_BRIGHTNESS_MODE_DEFAULT, /* displayWhiteBalanceController= */ null);
         assertNull(physical);
     }
 
     @Test
     public void testStrategiesAdaptToUserDataPoint() {
-        Resources res = createResources();
+        setUpResources();
         DisplayDeviceConfig ddc = new DdcBuilder().setBrightnessRange(BACKLIGHT_RANGE_ZERO_TO_ONE)
                 .setAutoBrightnessLevelsNits(DISPLAY_LEVELS_NITS).build();
-        assertStrategyAdaptsToUserDataPoints(BrightnessMappingStrategy.create(res, ddc,
-                AUTO_BRIGHTNESS_MODE_DEFAULT, mMockDwbc));
+        assertStrategyAdaptsToUserDataPoints(BrightnessMappingStrategy.create(mContext, ddc,
+                AUTO_BRIGHTNESS_MODE_DEFAULT, /* displayWhiteBalanceController= */ null));
         ddc = new DdcBuilder().setBrightnessRange(BACKLIGHT_RANGE_ZERO_TO_ONE)
                 .setAutoBrightnessLevels(DISPLAY_LEVELS).build();
-        res = createResources();
-        assertStrategyAdaptsToUserDataPoints(BrightnessMappingStrategy.create(res, ddc,
-                AUTO_BRIGHTNESS_MODE_DEFAULT, mMockDwbc));
+        setUpResources();
+        assertStrategyAdaptsToUserDataPoints(BrightnessMappingStrategy.create(mContext, ddc,
+                AUTO_BRIGHTNESS_MODE_DEFAULT, /* displayWhiteBalanceController= */ null));
     }
 
     @Test
     public void testIdleModeConfigLoadsCorrectly() {
-        Resources res = createResources(LUX_LEVELS_IDLE, DISPLAY_LEVELS_NITS_IDLE);
+        setUpResources(LUX_LEVELS_IDLE, DISPLAY_LEVELS_NITS_IDLE);
         DisplayDeviceConfig ddc = new DdcBuilder().setBrightnessRange(BACKLIGHT_RANGE_ZERO_TO_ONE)
                 .build();
 
         // Create an idle mode bms
         // This will fail if it tries to fetch the wrong configuration.
-        BrightnessMappingStrategy bms = BrightnessMappingStrategy.create(res, ddc,
+        BrightnessMappingStrategy bms = BrightnessMappingStrategy.create(mContext, ddc,
                 AUTO_BRIGHTNESS_MODE_IDLE,
-                mMockDwbc);
+                /* displayWhiteBalanceController= */ null);
         assertNotNull("BrightnessMappingStrategy should not be null", bms);
 
         // Ensure that the config is the one we set
@@ -500,36 +510,31 @@
         assertEquals(minBrightness, strategy.getBrightness(LUX_LEVELS[0]), 0.0001f /*tolerance*/);
     }
 
-    private Resources createResources() {
-        return createResources(EMPTY_INT_ARRAY, EMPTY_FLOAT_ARRAY);
+    private void setUpResources() {
+        setUpResources(EMPTY_INT_ARRAY, EMPTY_FLOAT_ARRAY);
     }
 
-    private Resources createResources(int[] luxLevelsIdle, float[] brightnessLevelsNitsIdle) {
-
-        Resources mockResources = mock(Resources.class);
+    private void setUpResources(int[] luxLevelsIdle, float[] brightnessLevelsNitsIdle) {
         if (luxLevelsIdle.length > 0) {
             int[] luxLevelsIdleResource = Arrays.copyOfRange(luxLevelsIdle, 1,
                     luxLevelsIdle.length);
-            when(mockResources.getIntArray(
-                    com.android.internal.R.array.config_autoBrightnessLevelsIdle))
-                    .thenReturn(luxLevelsIdleResource);
+            mContext.getOrCreateTestableResources().addOverride(
+                    com.android.internal.R.array.config_autoBrightnessLevelsIdle,
+                    luxLevelsIdleResource);
         }
 
         TypedArray mockBrightnessLevelNitsIdle = createFloatTypedArray(brightnessLevelsNitsIdle);
-        when(mockResources.obtainTypedArray(
-                com.android.internal.R.array.config_autoBrightnessDisplayValuesNitsIdle))
-                .thenReturn(mockBrightnessLevelNitsIdle);
+        mContext.getOrCreateTestableResources().addOverride(
+                com.android.internal.R.array.config_autoBrightnessDisplayValuesNitsIdle,
+                mockBrightnessLevelNitsIdle);
 
-        when(mockResources.getInteger(
-                com.android.internal.R.integer.config_screenBrightnessSettingMinimum))
-                .thenReturn(1);
-        when(mockResources.getInteger(
-                com.android.internal.R.integer.config_screenBrightnessSettingMaximum))
-                .thenReturn(255);
-        when(mockResources.getFraction(
-                com.android.internal.R.fraction.config_autoBrightnessAdjustmentMaxGamma, 1, 1))
-                .thenReturn(MAXIMUM_GAMMA);
-        return mockResources;
+        mContext.getOrCreateTestableResources().addOverride(
+                com.android.internal.R.integer.config_screenBrightnessSettingMinimum, 1);
+        mContext.getOrCreateTestableResources().addOverride(
+                com.android.internal.R.integer.config_screenBrightnessSettingMaximum, 255);
+        mContext.getOrCreateTestableResources().addOverride(
+                com.android.internal.R.fraction.config_autoBrightnessAdjustmentMaxGamma,
+                MAXIMUM_GAMMA);
     }
 
     private TypedArray createFloatTypedArray(float[] vals) {
@@ -570,11 +575,11 @@
         final float y2 = GAMMA_CORRECTION_SPLINE.interpolate(x2);
         final float y3 = GAMMA_CORRECTION_SPLINE.interpolate(x3);
 
-        Resources resources = createResources();
+        setUpResources();
         DisplayDeviceConfig ddc = new DdcBuilder().setAutoBrightnessLevelsLux(GAMMA_CORRECTION_LUX)
                 .setAutoBrightnessLevelsNits(GAMMA_CORRECTION_NITS).build();
-        BrightnessMappingStrategy strategy = BrightnessMappingStrategy.create(resources, ddc,
-                AUTO_BRIGHTNESS_MODE_DEFAULT, mMockDwbc);
+        BrightnessMappingStrategy strategy = BrightnessMappingStrategy.create(mContext, ddc,
+                AUTO_BRIGHTNESS_MODE_DEFAULT, /* displayWhiteBalanceController= */ null);
         // Let's start with a validity check:
         assertEquals(y1, strategy.getBrightness(x1), 0.0001f /* tolerance */);
         assertEquals(y2, strategy.getBrightness(x2), 0.0001f /* tolerance */);
@@ -600,11 +605,11 @@
         final float y1 = GAMMA_CORRECTION_SPLINE.interpolate(x1);
         final float y2 = GAMMA_CORRECTION_SPLINE.interpolate(x2);
         final float y3 = GAMMA_CORRECTION_SPLINE.interpolate(x3);
-        Resources resources = createResources();
+        setUpResources();
         DisplayDeviceConfig ddc = new DdcBuilder().setAutoBrightnessLevelsLux(GAMMA_CORRECTION_LUX)
                 .setAutoBrightnessLevelsNits(GAMMA_CORRECTION_NITS).build();
-        BrightnessMappingStrategy strategy = BrightnessMappingStrategy.create(resources, ddc,
-                AUTO_BRIGHTNESS_MODE_DEFAULT, mMockDwbc);
+        BrightnessMappingStrategy strategy = BrightnessMappingStrategy.create(mContext, ddc,
+                AUTO_BRIGHTNESS_MODE_DEFAULT, /* displayWhiteBalanceController= */ null);
         // Validity check:
         assertEquals(y1, strategy.getBrightness(x1), 0.0001f /* tolerance */);
         assertEquals(y2, strategy.getBrightness(x2), 0.0001f /* tolerance */);
@@ -627,11 +632,11 @@
     public void testGammaCorrectionExtremeChangeAtCenter() {
         // Extreme changes (e.g. setting brightness to 0.0 or 1.0) can't be gamma corrected, so we
         // just make sure the adjustment reflects the change.
-        Resources resources = createResources();
+        setUpResources();
         DisplayDeviceConfig ddc = new DdcBuilder().setAutoBrightnessLevelsLux(GAMMA_CORRECTION_LUX)
                 .setAutoBrightnessLevelsNits(GAMMA_CORRECTION_NITS).build();
-        BrightnessMappingStrategy strategy = BrightnessMappingStrategy.create(resources, ddc,
-                AUTO_BRIGHTNESS_MODE_DEFAULT, mMockDwbc);
+        BrightnessMappingStrategy strategy = BrightnessMappingStrategy.create(mContext, ddc,
+                AUTO_BRIGHTNESS_MODE_DEFAULT, /* displayWhiteBalanceController= */ null);
         assertEquals(0.0f, strategy.getAutoBrightnessAdjustment(), /* delta= */ 0.0001f);
         strategy.addUserDataPoint(/* lux= */ 2500, /* brightness= */ 1.0f);
         assertEquals(+1.0f, strategy.getAutoBrightnessAdjustment(), /* delta= */ 0.0001f);
@@ -650,11 +655,11 @@
         final float y0 = GAMMA_CORRECTION_SPLINE.interpolate(x0);
         final float y2 = GAMMA_CORRECTION_SPLINE.interpolate(x2);
         final float y4 = GAMMA_CORRECTION_SPLINE.interpolate(x4);
-        Resources resources = createResources();
+        setUpResources();
         DisplayDeviceConfig ddc = new DdcBuilder().setAutoBrightnessLevelsLux(GAMMA_CORRECTION_LUX)
                 .setAutoBrightnessLevelsNits(GAMMA_CORRECTION_NITS).build();
-        BrightnessMappingStrategy strategy = BrightnessMappingStrategy.create(resources, ddc,
-                AUTO_BRIGHTNESS_MODE_DEFAULT, mMockDwbc);
+        BrightnessMappingStrategy strategy = BrightnessMappingStrategy.create(mContext, ddc,
+                AUTO_BRIGHTNESS_MODE_DEFAULT, /* displayWhiteBalanceController= */ null);
         // Validity, as per tradition:
         assertEquals(y0, strategy.getBrightness(x0), 0.0001f /* tolerance */);
         assertEquals(y2, strategy.getBrightness(x2), 0.0001f /* tolerance */);
@@ -679,15 +684,33 @@
 
     @Test
     public void testGetMode() {
-        Resources res = createResources(LUX_LEVELS_IDLE, DISPLAY_LEVELS_NITS_IDLE);
+        setUpResources(LUX_LEVELS_IDLE, DISPLAY_LEVELS_NITS_IDLE);
         DisplayDeviceConfig ddc = new DdcBuilder().setBrightnessRange(BACKLIGHT_RANGE_ZERO_TO_ONE)
                 .build();
-        BrightnessMappingStrategy strategy = BrightnessMappingStrategy.create(res, ddc,
-                AUTO_BRIGHTNESS_MODE_IDLE,
-                mMockDwbc);
+        BrightnessMappingStrategy strategy = BrightnessMappingStrategy.create(mContext, ddc,
+                AUTO_BRIGHTNESS_MODE_IDLE, /* displayWhiteBalanceController= */ null);
         assertEquals(AUTO_BRIGHTNESS_MODE_IDLE, strategy.getMode());
     }
 
+    @Test
+    public void testAutoBrightnessModeAndPreset() {
+        int mode = AUTO_BRIGHTNESS_MODE_DOZE;
+        int preset = Settings.System.SCREEN_BRIGHTNESS_AUTOMATIC_DIM;
+        Settings.System.putInt(mContext.getContentResolver(),
+                Settings.System.SCREEN_BRIGHTNESS_FOR_ALS, preset);
+
+        setUpResources();
+        DisplayDeviceConfig ddc = new DdcBuilder()
+                .setAutoBrightnessLevels(mode, preset, DISPLAY_LEVELS)
+                .setAutoBrightnessLevelsLux(mode, preset, LUX_LEVELS).build();
+        BrightnessMappingStrategy simple = BrightnessMappingStrategy.create(mContext, ddc, mode,
+                /* displayWhiteBalanceController= */ null);
+        assertNotNull("BrightnessMappingStrategy should not be null", simple);
+        for (int i = 0; i < LUX_LEVELS.length; i++) {
+            assertEquals(DISPLAY_LEVELS[i], simple.getBrightness(LUX_LEVELS[i]), TOLERANCE);
+        }
+    }
+
     private static class DdcBuilder {
         private DisplayDeviceConfig mDdc;
 
@@ -695,9 +718,12 @@
             mDdc = mock(DisplayDeviceConfig.class);
             when(mDdc.getNits()).thenReturn(DISPLAY_RANGE_NITS);
             when(mDdc.getBrightness()).thenReturn(DISPLAY_LEVELS_RANGE_BACKLIGHT_FLOAT);
-            when(mDdc.getAutoBrightnessBrighteningLevelsLux()).thenReturn(LUX_LEVELS);
+            when(mDdc.getAutoBrightnessBrighteningLevelsLux(AUTO_BRIGHTNESS_MODE_DEFAULT,
+                    Settings.System.SCREEN_BRIGHTNESS_AUTOMATIC_NORMAL)).thenReturn(LUX_LEVELS);
             when(mDdc.getAutoBrightnessBrighteningLevelsNits()).thenReturn(EMPTY_FLOAT_ARRAY);
-            when(mDdc.getAutoBrightnessBrighteningLevels()).thenReturn(EMPTY_FLOAT_ARRAY);
+            when(mDdc.getAutoBrightnessBrighteningLevels(AUTO_BRIGHTNESS_MODE_DEFAULT,
+                    Settings.System.SCREEN_BRIGHTNESS_AUTOMATIC_NORMAL))
+                    .thenReturn(EMPTY_FLOAT_ARRAY);
         }
 
         DdcBuilder setNitsRange(float[] nitsArray) {
@@ -711,7 +737,15 @@
         }
 
         DdcBuilder setAutoBrightnessLevelsLux(float[] luxLevels) {
-            when(mDdc.getAutoBrightnessBrighteningLevelsLux()).thenReturn(luxLevels);
+            when(mDdc.getAutoBrightnessBrighteningLevelsLux(AUTO_BRIGHTNESS_MODE_DEFAULT,
+                    Settings.System.SCREEN_BRIGHTNESS_AUTOMATIC_NORMAL)).thenReturn(luxLevels);
+            return this;
+        }
+
+        DdcBuilder setAutoBrightnessLevelsLux(
+                @AutomaticBrightnessController.AutomaticBrightnessMode int mode, int preset,
+                float[] luxLevels) {
+            when(mDdc.getAutoBrightnessBrighteningLevelsLux(mode, preset)).thenReturn(luxLevels);
             return this;
         }
 
@@ -721,7 +755,17 @@
         }
 
         DdcBuilder setAutoBrightnessLevels(float[] brightnessLevels) {
-            when(mDdc.getAutoBrightnessBrighteningLevels()).thenReturn(brightnessLevels);
+            when(mDdc.getAutoBrightnessBrighteningLevels(AUTO_BRIGHTNESS_MODE_DEFAULT,
+                    Settings.System.SCREEN_BRIGHTNESS_AUTOMATIC_NORMAL))
+                    .thenReturn(brightnessLevels);
+            return this;
+        }
+
+        DdcBuilder setAutoBrightnessLevels(
+                @AutomaticBrightnessController.AutomaticBrightnessMode int mode, int preset,
+                float[] brightnessLevels) {
+            when(mDdc.getAutoBrightnessBrighteningLevels(mode, preset))
+                    .thenReturn(brightnessLevels);
             return this;
         }
 
diff --git a/services/tests/displayservicetests/src/com/android/server/display/DisplayDeviceConfigTest.java b/services/tests/displayservicetests/src/com/android/server/display/DisplayDeviceConfigTest.java
index a4c15b5..7a84406 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/DisplayDeviceConfigTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/DisplayDeviceConfigTest.java
@@ -18,6 +18,8 @@
 
 
 import static com.android.internal.display.BrightnessSynchronizer.brightnessIntToFloat;
+import static com.android.server.display.AutomaticBrightnessController.AUTO_BRIGHTNESS_MODE_DEFAULT;
+import static com.android.server.display.AutomaticBrightnessController.AUTO_BRIGHTNESS_MODE_DOZE;
 import static com.android.server.display.config.SensorData.SupportedMode;
 import static com.android.server.display.utils.DeviceConfigParsingUtils.ambientBrightnessThresholdsIntToFloat;
 import static com.android.server.display.utils.DeviceConfigParsingUtils.displayBrightnessThresholdsIntToFloat;
@@ -40,6 +42,7 @@
 import android.content.res.TypedArray;
 import android.hardware.display.DisplayManagerInternal;
 import android.os.Temperature;
+import android.provider.Settings;
 import android.util.SparseArray;
 import android.util.Spline;
 import android.view.SurfaceControl;
@@ -605,13 +608,15 @@
 
     private void verifyConfigValuesFromConfigResource() {
         assertNull(mDisplayDeviceConfig.getName());
-        assertArrayEquals(mDisplayDeviceConfig.getAutoBrightnessBrighteningLevelsNits(), new
-                float[]{2.0f, 200.0f, 600.0f}, ZERO_DELTA);
-        assertArrayEquals(mDisplayDeviceConfig.getAutoBrightnessBrighteningLevelsLux(), new
-                float[]{0.0f, 110.0f, 500.0f}, ZERO_DELTA);
-        assertArrayEquals(mDisplayDeviceConfig.getAutoBrightnessBrighteningLevels(), new
-                float[]{brightnessIntToFloat(50), brightnessIntToFloat(100),
-                brightnessIntToFloat(150)}, SMALL_DELTA);
+        assertArrayEquals(mDisplayDeviceConfig.getAutoBrightnessBrighteningLevelsNits(),
+                new float[]{2.0f, 200.0f, 600.0f}, ZERO_DELTA);
+        assertArrayEquals(mDisplayDeviceConfig.getAutoBrightnessBrighteningLevelsLux(
+                AUTO_BRIGHTNESS_MODE_DEFAULT, Settings.System.SCREEN_BRIGHTNESS_AUTOMATIC_NORMAL),
+                new float[]{0.0f, 110.0f, 500.0f}, ZERO_DELTA);
+        assertArrayEquals(mDisplayDeviceConfig.getAutoBrightnessBrighteningLevels(
+                AUTO_BRIGHTNESS_MODE_DEFAULT, Settings.System.SCREEN_BRIGHTNESS_AUTOMATIC_NORMAL),
+                new float[]{brightnessIntToFloat(50), brightnessIntToFloat(100),
+                        brightnessIntToFloat(150)}, SMALL_DELTA);
 
         // Test thresholds
         assertEquals(0, mDisplayDeviceConfig.getAmbientLuxBrighteningMinThreshold(), ZERO_DELTA);
@@ -737,30 +742,40 @@
                 getValidProxSensor(), /* includeIdleMode= */ false));
 
         assertArrayEquals(new float[]{0.0f, 80},
-                mDisplayDeviceConfig.getAutoBrightnessBrighteningLevelsLux(), ZERO_DELTA);
+                mDisplayDeviceConfig.getAutoBrightnessBrighteningLevelsLux(
+                        AUTO_BRIGHTNESS_MODE_DEFAULT,
+                        Settings.System.SCREEN_BRIGHTNESS_AUTOMATIC_NORMAL), ZERO_DELTA);
         assertArrayEquals(new float[]{0.2f, 0.3f},
-                mDisplayDeviceConfig.getAutoBrightnessBrighteningLevels(), SMALL_DELTA);
+                mDisplayDeviceConfig.getAutoBrightnessBrighteningLevels(
+                        AUTO_BRIGHTNESS_MODE_DEFAULT,
+                        Settings.System.SCREEN_BRIGHTNESS_AUTOMATIC_NORMAL), SMALL_DELTA);
 
         assertArrayEquals(new float[]{0.0f, 90},
-                mDisplayDeviceConfig.getAutoBrightnessBrighteningLevelsLux("default", "dim"),
-                ZERO_DELTA);
+                mDisplayDeviceConfig.getAutoBrightnessBrighteningLevelsLux(
+                        AUTO_BRIGHTNESS_MODE_DEFAULT,
+                        Settings.System.SCREEN_BRIGHTNESS_AUTOMATIC_DIM), ZERO_DELTA);
         assertArrayEquals(new float[]{0.3f, 0.4f},
-                mDisplayDeviceConfig.getAutoBrightnessBrighteningLevels("default", "dim"),
-                SMALL_DELTA);
+                mDisplayDeviceConfig.getAutoBrightnessBrighteningLevels(
+                        AUTO_BRIGHTNESS_MODE_DEFAULT,
+                        Settings.System.SCREEN_BRIGHTNESS_AUTOMATIC_DIM), SMALL_DELTA);
 
         assertArrayEquals(new float[]{0.0f, 95},
-                mDisplayDeviceConfig.getAutoBrightnessBrighteningLevelsLux("doze", "normal"),
-                ZERO_DELTA);
+                mDisplayDeviceConfig.getAutoBrightnessBrighteningLevelsLux(
+                        AUTO_BRIGHTNESS_MODE_DOZE,
+                        Settings.System.SCREEN_BRIGHTNESS_AUTOMATIC_NORMAL), ZERO_DELTA);
         assertArrayEquals(new float[]{0.35f, 0.45f},
-                mDisplayDeviceConfig.getAutoBrightnessBrighteningLevels("doze", "normal"),
-                SMALL_DELTA);
+                mDisplayDeviceConfig.getAutoBrightnessBrighteningLevels(
+                        AUTO_BRIGHTNESS_MODE_DOZE,
+                        Settings.System.SCREEN_BRIGHTNESS_AUTOMATIC_NORMAL), SMALL_DELTA);
 
         assertArrayEquals(new float[]{0.0f, 100},
-                mDisplayDeviceConfig.getAutoBrightnessBrighteningLevelsLux("doze", "bright"),
-                ZERO_DELTA);
+                mDisplayDeviceConfig.getAutoBrightnessBrighteningLevelsLux(
+                        AUTO_BRIGHTNESS_MODE_DOZE,
+                        Settings.System.SCREEN_BRIGHTNESS_AUTOMATIC_BRIGHT), ZERO_DELTA);
         assertArrayEquals(new float[]{0.4f, 0.5f},
-                mDisplayDeviceConfig.getAutoBrightnessBrighteningLevels("doze", "bright"),
-                SMALL_DELTA);
+                mDisplayDeviceConfig.getAutoBrightnessBrighteningLevels(
+                        AUTO_BRIGHTNESS_MODE_DOZE,
+                        Settings.System.SCREEN_BRIGHTNESS_AUTOMATIC_BRIGHT), SMALL_DELTA);
     }
 
     @Test
@@ -772,9 +787,13 @@
 
         assertArrayEquals(new float[]{brightnessIntToFloat(50), brightnessIntToFloat(100),
                         brightnessIntToFloat(150)},
-                mDisplayDeviceConfig.getAutoBrightnessBrighteningLevels(), SMALL_DELTA);
+                mDisplayDeviceConfig.getAutoBrightnessBrighteningLevels(
+                        AUTO_BRIGHTNESS_MODE_DEFAULT,
+                        Settings.System.SCREEN_BRIGHTNESS_AUTOMATIC_NORMAL), SMALL_DELTA);
         assertArrayEquals(new float[]{0, 110, 500},
-                mDisplayDeviceConfig.getAutoBrightnessBrighteningLevelsLux(), ZERO_DELTA);
+                mDisplayDeviceConfig.getAutoBrightnessBrighteningLevelsLux(
+                        AUTO_BRIGHTNESS_MODE_DEFAULT,
+                        Settings.System.SCREEN_BRIGHTNESS_AUTOMATIC_NORMAL), ZERO_DELTA);
         assertArrayEquals(new float[]{2, 200, 600},
                 mDisplayDeviceConfig.getAutoBrightnessBrighteningLevelsNits(), SMALL_DELTA);
     }
diff --git a/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerController2Test.java b/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerController2Test.java
index 02bd35a..7a70848 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerController2Test.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerController2Test.java
@@ -18,6 +18,8 @@
 
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.doAnswer;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
+import static com.android.server.display.AutomaticBrightnessController.AUTO_BRIGHTNESS_MODE_DEFAULT;
+import static com.android.server.display.AutomaticBrightnessController.AUTO_BRIGHTNESS_MODE_DOZE;
 import static com.android.server.display.AutomaticBrightnessController.AUTO_BRIGHTNESS_MODE_IDLE;
 
 import static org.junit.Assert.assertNotNull;
@@ -773,7 +775,7 @@
         final float hdrBrightness = 0.3f;
         when(mHolder.displayPowerState.getScreenState()).thenReturn(Display.STATE_ON);
         when(mHolder.displayPowerState.getColorFadeLevel()).thenReturn(1.0f);
-        when(mHolder.automaticBrightnessController.isInIdleMode()).thenReturn(true);
+        when(mHolder.automaticBrightnessController.getMode()).thenReturn(AUTO_BRIGHTNESS_MODE_IDLE);
         when(mHolder.automaticBrightnessController.getAutomaticScreenBrightness(
                 any(BrightnessEvent.class))).thenReturn(sdrBrightness);
         when(mHolder.hdrClamper.getMaxBrightness()).thenReturn(1.0f);
@@ -1232,7 +1234,7 @@
         float brightness = 0.6f;
         when(mHolder.displayPowerState.getScreenState()).thenReturn(Display.STATE_ON);
         when(mHolder.displayPowerState.getColorFadeLevel()).thenReturn(1.0f);
-        when(mHolder.automaticBrightnessController.isInIdleMode()).thenReturn(true);
+        when(mHolder.automaticBrightnessController.getMode()).thenReturn(AUTO_BRIGHTNESS_MODE_IDLE);
         when(mHolder.automaticBrightnessController.getAutomaticScreenBrightness(
                 any(BrightnessEvent.class))).thenReturn(brightness);
 
@@ -1568,6 +1570,56 @@
                 eq(BRIGHTNESS_RAMP_RATE_FAST_INCREASE), eq(false));
     }
 
+    @Test
+    public void testSwitchToDozeAutoBrightnessMode() {
+        when(mDisplayManagerFlagsMock.areAutoBrightnessModesEnabled()).thenReturn(true);
+        when(mHolder.displayPowerState.getScreenState()).thenReturn(Display.STATE_DOZE);
+
+        DisplayPowerRequest dpr = new DisplayPowerRequest();
+        dpr.policy = DisplayPowerRequest.POLICY_DOZE;
+        mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
+        advanceTime(1); // Run updatePowerState
+
+        // One triggered by handleBrightnessModeChange, another triggered by requestPowerState
+        verify(mHolder.automaticBrightnessController, times(2))
+                .switchMode(AUTO_BRIGHTNESS_MODE_DOZE);
+
+        // Back to default mode
+        when(mHolder.displayPowerState.getScreenState()).thenReturn(Display.STATE_ON);
+        dpr.policy = DisplayPowerRequest.POLICY_BRIGHT;
+        mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
+        advanceTime(1); // Run updatePowerState
+
+        verify(mHolder.automaticBrightnessController).switchMode(AUTO_BRIGHTNESS_MODE_DEFAULT);
+    }
+
+    @Test
+    public void testDoesNotSwitchFromIdleToDozeAutoBrightnessMode() {
+        when(mDisplayManagerFlagsMock.areAutoBrightnessModesEnabled()).thenReturn(true);
+        when(mHolder.displayPowerState.getScreenState()).thenReturn(Display.STATE_DOZE);
+        when(mHolder.automaticBrightnessController.getMode()).thenReturn(AUTO_BRIGHTNESS_MODE_IDLE);
+
+        DisplayPowerRequest dpr = new DisplayPowerRequest();
+        mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
+        advanceTime(1); // Run updatePowerState
+
+        verify(mHolder.automaticBrightnessController, never())
+                .switchMode(AUTO_BRIGHTNESS_MODE_DOZE);
+    }
+
+    @Test
+    public void testDoesNotSwitchDozeAutoBrightnessModeIfFeatureFlagOff() {
+        when(mDisplayManagerFlagsMock.areAutoBrightnessModesEnabled()).thenReturn(false);
+        when(mHolder.displayPowerState.getScreenState()).thenReturn(Display.STATE_DOZE);
+
+        DisplayPowerRequest dpr = new DisplayPowerRequest();
+        mHolder.dpc.requestPowerState(dpr, /* waitForNegativeProximity= */ false);
+        advanceTime(1); // Run updatePowerState
+
+        verify(mHolder.automaticBrightnessController, never())
+                .switchMode(AUTO_BRIGHTNESS_MODE_DOZE);
+    }
+
     /**
      * Creates a mock and registers it to {@link LocalServices}.
      */
@@ -1853,7 +1905,7 @@
         }
 
         @Override
-        BrightnessMappingStrategy getDefaultModeBrightnessMapper(Resources resources,
+        BrightnessMappingStrategy getDefaultModeBrightnessMapper(Context context,
                 DisplayDeviceConfig displayDeviceConfig,
                 DisplayWhiteBalanceController displayWhiteBalanceController) {
             return mBrightnessMappingStrategy;
diff --git a/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerControllerTest.java b/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerControllerTest.java
index 64cdac4..d69c140 100644
--- a/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerControllerTest.java
+++ b/services/tests/displayservicetests/src/com/android/server/display/DisplayPowerControllerTest.java
@@ -1164,7 +1164,7 @@
         float brightness = 0.6f;
         when(mHolder.displayPowerState.getScreenState()).thenReturn(Display.STATE_ON);
         when(mHolder.displayPowerState.getColorFadeLevel()).thenReturn(1.0f);
-        when(mHolder.automaticBrightnessController.isInIdleMode()).thenReturn(true);
+        when(mHolder.automaticBrightnessController.getMode()).thenReturn(AUTO_BRIGHTNESS_MODE_IDLE);
         when(mHolder.automaticBrightnessController.getAutomaticScreenBrightness(
                 any(BrightnessEvent.class))).thenReturn(brightness);
 
@@ -1669,7 +1669,7 @@
         }
 
         @Override
-        BrightnessMappingStrategy getDefaultModeBrightnessMapper(Resources resources,
+        BrightnessMappingStrategy getDefaultModeBrightnessMapper(Context context,
                 DisplayDeviceConfig displayDeviceConfig,
                 DisplayWhiteBalanceController displayWhiteBalanceController) {
             return mBrightnessMappingStrategy;
diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/ApexManagerTest.java b/services/tests/mockingservicestests/src/com/android/server/pm/ApexManagerTest.java
index c2b52b4..57326b2 100644
--- a/services/tests/mockingservicestests/src/com/android/server/pm/ApexManagerTest.java
+++ b/services/tests/mockingservicestests/src/com/android/server/pm/ApexManagerTest.java
@@ -50,9 +50,10 @@
 import androidx.test.filters.SmallTest;
 import androidx.test.runner.AndroidJUnit4;
 
+import com.android.internal.pm.parsing.PackageParser2;
+import com.android.internal.pm.parsing.PackageParserException;
 import com.android.internal.pm.parsing.pkg.AndroidPackageLegacyUtils;
 import com.android.internal.pm.pkg.parsing.ParsingPackageUtils;
-import com.android.server.pm.parsing.PackageParser2;
 import com.android.server.pm.pkg.AndroidPackage;
 
 import org.junit.Before;
@@ -175,7 +176,7 @@
                     mPmService.getPlatformPackage(), /* isUpdatedSystemApp */ false);
             // isUpdatedSystemApp is ignoreable above, only used for shared library adjustment
             return parsedPackage.hideAsFinal();
-        } catch (PackageManagerException e) {
+        } catch (PackageParserException e) {
             throw new RuntimeException(e);
         }
     }
diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/MockSystem.kt b/services/tests/mockingservicestests/src/com/android/server/pm/MockSystem.kt
index 7b29e2a..538c0ee 100644
--- a/services/tests/mockingservicestests/src/com/android/server/pm/MockSystem.kt
+++ b/services/tests/mockingservicestests/src/com/android/server/pm/MockSystem.kt
@@ -56,6 +56,7 @@
 import com.android.dx.mockito.inline.extended.StaticMockitoSession
 import com.android.dx.mockito.inline.extended.StaticMockitoSessionBuilder
 import com.android.internal.R
+import com.android.internal.pm.parsing.PackageParser2
 import com.android.internal.pm.parsing.pkg.PackageImpl
 import com.android.internal.pm.parsing.pkg.ParsedPackage
 import com.android.internal.pm.pkg.parsing.ParsingPackage
@@ -69,7 +70,6 @@
 import com.android.server.extendedtestutils.wheneverStatic
 import com.android.server.pm.dex.DexManager
 import com.android.server.pm.dex.DynamicCodeLogger
-import com.android.server.pm.parsing.PackageParser2
 import com.android.server.pm.permission.PermissionManagerServiceInternal
 import com.android.server.pm.pkg.AndroidPackage
 import com.android.server.pm.resolution.ComponentResolver
diff --git a/services/tests/mockingservicestests/src/com/android/server/pm/PackageManagerServiceBootTest.kt b/services/tests/mockingservicestests/src/com/android/server/pm/PackageManagerServiceBootTest.kt
index da929af..7feafef 100644
--- a/services/tests/mockingservicestests/src/com/android/server/pm/PackageManagerServiceBootTest.kt
+++ b/services/tests/mockingservicestests/src/com/android/server/pm/PackageManagerServiceBootTest.kt
@@ -21,6 +21,7 @@
 import android.os.Build
 import android.os.Process
 import android.util.Log
+import com.android.internal.pm.parsing.PackageParserException
 import com.android.server.pm.pkg.AndroidPackage
 import com.android.server.testutils.whenever
 import java.io.File
@@ -120,7 +121,7 @@
                 argThat { path: File -> path.path.contains("a.data.package") },
                 anyInt(),
                 anyBoolean()))
-                .thenThrow(PackageManagerException(
+                .thenThrow(PackageParserException(
                         PackageManager.INSTALL_FAILED_INVALID_APK, "Oh no!"))
         val pm = createPackageManagerService()
         verify(rule.mocks().settings, Mockito.never()).insertPackageSettingLPw(
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/SensorOverlaysTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/SensorOverlaysTest.java
index 94cb860..74f8f08 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/SensorOverlaysTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/SensorOverlaysTest.java
@@ -29,7 +29,7 @@
 import android.hardware.fingerprint.ISidefpsController;
 import android.hardware.fingerprint.IUdfpsOverlayController;
 import android.platform.test.annotations.Presubmit;
-import android.platform.test.annotations.RequiresFlagsDisabled;
+import android.platform.test.flag.junit.SetFlagsRule;
 
 import androidx.test.filters.SmallTest;
 
@@ -43,10 +43,10 @@
 import java.util.ArrayList;
 import java.util.List;
 
-@RequiresFlagsDisabled(FLAG_SIDEFPS_CONTROLLER_REFACTOR)
 @Presubmit
 @SmallTest
 public class SensorOverlaysTest {
+    @Rule public final SetFlagsRule mSetFlagsRule = new SetFlagsRule();
 
     private static final int SENSOR_ID = 11;
     private static final long REQUEST_ID = 8;
@@ -59,6 +59,7 @@
 
     @Before
     public void setup() {
+        mSetFlagsRule.disableFlags(FLAG_SIDEFPS_CONTROLLER_REFACTOR);
         when(mAcquisitionClient.getRequestId()).thenReturn(REQUEST_ID);
         when(mAcquisitionClient.hasRequestId()).thenReturn(true);
     }
diff --git a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClientTest.java b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClientTest.java
index c24227f..774ea5b 100644
--- a/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClientTest.java
+++ b/services/tests/servicestests/src/com/android/server/biometrics/sensors/fingerprint/aidl/FingerprintAuthenticationClientTest.java
@@ -161,6 +161,7 @@
 
     @Before
     public void setup() {
+        mSetFlagsRule.disableFlags(FLAG_SIDEFPS_CONTROLLER_REFACTOR);
         mContext.addMockSystemService(BiometricManager.class, mBiometricManager);
         when(mBiometricContext.getAuthSessionCoordinator()).thenReturn(mAuthSessionCoordinator);
         when(mBiometricLogger.getAmbientLightProbe(anyBoolean())).thenAnswer(i ->
diff --git a/services/tests/servicestests/src/com/android/server/hdmi/BaseAbsoluteVolumeBehaviorTest.java b/services/tests/servicestests/src/com/android/server/hdmi/BaseAbsoluteVolumeBehaviorTest.java
index a63d01b..e7da26e 100644
--- a/services/tests/servicestests/src/com/android/server/hdmi/BaseAbsoluteVolumeBehaviorTest.java
+++ b/services/tests/servicestests/src/com/android/server/hdmi/BaseAbsoluteVolumeBehaviorTest.java
@@ -456,6 +456,14 @@
     }
 
     @Test
+    public void avbEnabled_standby_avbDisabled() {
+        enableAbsoluteVolumeBehavior();
+        mHdmiControlService.onStandby(HdmiControlService.STANDBY_SCREEN_OFF);
+        assertThat(mAudioManager.getDeviceVolumeBehavior(getAudioOutputDevice())).isEqualTo(
+                AudioManager.DEVICE_VOLUME_BEHAVIOR_FULL);
+    }
+
+    @Test
     public void avbEnabled_cecVolumeDisabled_avbDisabled() {
         enableAbsoluteVolumeBehavior();
 
diff --git a/services/tests/servicestests/src/com/android/server/integrity/AppIntegrityManagerServiceImplTest.java b/services/tests/servicestests/src/com/android/server/integrity/AppIntegrityManagerServiceImplTest.java
index ce15c6d..eb9cce0 100644
--- a/services/tests/servicestests/src/com/android/server/integrity/AppIntegrityManagerServiceImplTest.java
+++ b/services/tests/servicestests/src/com/android/server/integrity/AppIntegrityManagerServiceImplTest.java
@@ -67,10 +67,10 @@
 import androidx.test.InstrumentationRegistry;
 
 import com.android.internal.R;
+import com.android.internal.pm.parsing.PackageParser2;
 import com.android.server.compat.PlatformCompat;
 import com.android.server.integrity.engine.RuleEvaluationEngine;
 import com.android.server.integrity.model.IntegrityCheckResult;
-import com.android.server.pm.parsing.PackageParser2;
 import com.android.server.pm.parsing.TestPackageParser2;
 import com.android.server.testutils.TestUtils;
 
diff --git a/services/tests/servicestests/src/com/android/server/pm/dex/DexMetadataHelperTest.java b/services/tests/servicestests/src/com/android/server/pm/dex/DexMetadataHelperTest.java
index 1bfd43f..25eedf5 100644
--- a/services/tests/servicestests/src/com/android/server/pm/dex/DexMetadataHelperTest.java
+++ b/services/tests/servicestests/src/com/android/server/pm/dex/DexMetadataHelperTest.java
@@ -37,6 +37,7 @@
 import androidx.test.runner.AndroidJUnit4;
 
 import com.android.frameworks.servicestests.R;
+import com.android.internal.pm.parsing.PackageParserException;
 import com.android.internal.pm.parsing.pkg.ParsedPackage;
 import com.android.server.pm.PackageManagerException;
 import com.android.server.pm.parsing.TestPackageParser2;
@@ -161,7 +162,8 @@
     }
 
     @Test
-    public void testParsePackageWithDmFileValid() throws IOException, PackageManagerException {
+    public void testParsePackageWithDmFileValid() throws IOException, PackageManagerException,
+            PackageParserException {
         copyApkToToTmpDir("install_split_base.apk", R.raw.install_split_base);
         createDexMetadataFile("install_split_base.apk");
         ParsedPackage pkg = new TestPackageParser2().parsePackage(mTmpDir, /*flags=*/0, false);
@@ -178,7 +180,7 @@
 
     @Test
     public void testParsePackageSplitsWithDmFileValid()
-            throws IOException, PackageManagerException {
+            throws IOException, PackageManagerException, PackageParserException {
         copyApkToToTmpDir("install_split_base.apk", R.raw.install_split_base);
         copyApkToToTmpDir("install_split_feature_a.apk", R.raw.install_split_feature_a);
         createDexMetadataFile("install_split_base.apk");
@@ -201,7 +203,7 @@
 
     @Test
     public void testParsePackageSplitsNoBaseWithDmFileValid()
-            throws IOException, PackageManagerException {
+            throws IOException, PackageManagerException, PackageParserException {
         copyApkToToTmpDir("install_split_base.apk", R.raw.install_split_base);
         copyApkToToTmpDir("install_split_feature_a.apk", R.raw.install_split_feature_a);
         createDexMetadataFile("install_split_feature_a.apk");
@@ -219,7 +221,7 @@
     }
 
     @Test
-    public void testParsePackageWithDmFileInvalid() throws IOException {
+    public void testParsePackageWithDmFileInvalid() throws IOException, PackageParserException {
         copyApkToToTmpDir("install_split_base.apk", R.raw.install_split_base);
         File invalidDmFile = new File(mTmpDir, "install_split_base.dm");
         Files.createFile(invalidDmFile.toPath());
@@ -242,7 +244,7 @@
 
     @Test
     public void testParsePackageSplitsWithDmFileInvalid()
-            throws IOException, PackageManagerException {
+            throws IOException, PackageParserException {
         copyApkToToTmpDir("install_split_base.apk", R.raw.install_split_base);
         copyApkToToTmpDir("install_split_feature_a.apk", R.raw.install_split_feature_a);
         createDexMetadataFile("install_split_base.apk");
@@ -268,7 +270,7 @@
 
     @Test
     public void testParsePackageWithDmFileInvalidManifest()
-            throws IOException, PackageManagerException {
+            throws IOException, PackageParserException {
         copyApkToToTmpDir("install_split_base.apk", R.raw.install_split_base);
         createDexMetadataFile("install_split_base.apk", /*validManifest=*/false);
 
@@ -283,7 +285,7 @@
 
     @Test
     public void testParsePackageWithDmFileEmptyManifest()
-            throws IOException, PackageManagerException {
+            throws IOException, PackageParserException {
         copyApkToToTmpDir("install_split_base.apk", R.raw.install_split_base);
         createDexMetadataFile("install_split_base.apk", /*packageName=*/"doesn't matter",
                 /*versionCode=*/-12345L, /*emptyManifest=*/true, /*validManifest=*/true);
@@ -299,7 +301,7 @@
 
     @Test
     public void testParsePackageWithDmFileBadPackageName()
-            throws IOException, PackageManagerException {
+            throws IOException, PackageParserException {
         copyApkToToTmpDir("install_split_base.apk", R.raw.install_split_base);
         createDexMetadataFile("install_split_base.apk", /*packageName=*/"bad package name",
                 DEX_METADATA_VERSION_CODE, /*emptyManifest=*/false, /*validManifest=*/true);
@@ -315,7 +317,7 @@
 
     @Test
     public void testParsePackageWithDmFileBadVersionCode()
-            throws IOException, PackageManagerException {
+            throws IOException, PackageParserException {
         copyApkToToTmpDir("install_split_base.apk", R.raw.install_split_base);
         createDexMetadataFile("install_split_base.apk", DEX_METADATA_PACKAGE_NAME,
                 /*versionCode=*/12345L, /*emptyManifest=*/false, /*validManifest=*/true);
@@ -331,7 +333,7 @@
 
     @Test
     public void testParsePackageWithDmFileMissingPackageName()
-            throws IOException, PackageManagerException {
+            throws IOException, PackageParserException {
         copyApkToToTmpDir("install_split_base.apk", R.raw.install_split_base);
         createDexMetadataFile("install_split_base.apk", /*packageName=*/null,
                 DEX_METADATA_VERSION_CODE, /*emptyManifest=*/false, /*validManifest=*/true);
@@ -347,7 +349,7 @@
 
     @Test
     public void testParsePackageWithDmFileMissingVersionCode()
-            throws IOException, PackageManagerException {
+            throws IOException, PackageParserException {
         copyApkToToTmpDir("install_split_base.apk", R.raw.install_split_base);
         createDexMetadataFile("install_split_base.apk", DEX_METADATA_PACKAGE_NAME,
                 /*versionCode=*/null, /*emptyManifest=*/false, /*validManifest=*/true);
diff --git a/services/tests/servicestests/src/com/android/server/pm/parsing/AndroidPackageParsingValidationTest.kt b/services/tests/servicestests/src/com/android/server/pm/parsing/AndroidPackageParsingValidationTest.kt
index 9b4ca2a..6a088d9 100644
--- a/services/tests/servicestests/src/com/android/server/pm/parsing/AndroidPackageParsingValidationTest.kt
+++ b/services/tests/servicestests/src/com/android/server/pm/parsing/AndroidPackageParsingValidationTest.kt
@@ -36,7 +36,7 @@
 @Postsubmit
 class AndroidPackageParsingValidationTest {
     companion object {
-        private val parser2 = PackageParser2.forParsingFileWithDefaults()
+        private val parser2 = PackageParserUtils.forParsingFileWithDefaults()
         private val apks = ((PackageManagerService.SYSTEM_PARTITIONS)
                 .flatMap {
                     listOfNotNull(it.privAppFolder, it.appFolder, it.overlayFolder)
diff --git a/services/tests/servicestests/src/com/android/server/pm/parsing/TestPackageParser2.kt b/services/tests/servicestests/src/com/android/server/pm/parsing/TestPackageParser2.kt
index c44f583..e420e4b 100644
--- a/services/tests/servicestests/src/com/android/server/pm/parsing/TestPackageParser2.kt
+++ b/services/tests/servicestests/src/com/android/server/pm/parsing/TestPackageParser2.kt
@@ -18,11 +18,12 @@
 
 import android.content.pm.ApplicationInfo
 import android.util.ArraySet
+import com.android.internal.pm.parsing.PackageParser2
 import java.io.File
 
 class TestPackageParser2(var cacheDir: File? = null) : PackageParser2(
         null /* separateProcesses */, null /* displayMetrics */,
-        cacheDir /* cacheDir */, object : PackageParser2.Callback() {
+    cacheDir?.let { PackageCacher(cacheDir) }, object : PackageParser2.Callback() {
     override fun isChangeEnabled(changeId: Long, appInfo: ApplicationInfo): Boolean {
         return true
     }
diff --git a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
index e004ca0..884ea31 100755
--- a/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
+++ b/services/tests/uiservicestests/src/com/android/server/notification/NotificationManagerServiceTest.java
@@ -297,6 +297,7 @@
 import org.junit.Assert;
 import org.junit.Before;
 import org.junit.ClassRule;
+import org.junit.Ignore;
 import org.junit.Rule;
 import org.junit.Test;
 import org.junit.rules.TestRule;
@@ -13615,7 +13616,31 @@
 
     @Test
     @EnableCompatChanges(NotificationManagerService.MANAGE_GLOBAL_ZEN_VIA_IMPLICIT_RULES)
-    public void setNotificationPolicy_watchCompanionApp_setsGlobalPolicy() throws RemoteException {
+    public void setNotificationPolicy_watchCompanionApp_setsGlobalPolicy()
+            throws RemoteException {
+        setNotificationPolicy_dependingOnCompanionAppDevice_maySetGlobalPolicy(
+                AssociationRequest.DEVICE_PROFILE_WATCH, true);
+    }
+
+    @Test
+    @EnableCompatChanges(NotificationManagerService.MANAGE_GLOBAL_ZEN_VIA_IMPLICIT_RULES)
+    public void setNotificationPolicy_autoCompanionApp_setsGlobalPolicy()
+            throws RemoteException {
+        setNotificationPolicy_dependingOnCompanionAppDevice_maySetGlobalPolicy(
+                AssociationRequest.DEVICE_PROFILE_AUTOMOTIVE_PROJECTION, true);
+    }
+
+    @Test
+    @EnableCompatChanges(NotificationManagerService.MANAGE_GLOBAL_ZEN_VIA_IMPLICIT_RULES)
+    public void setNotificationPolicy_otherCompanionApp_doesNotSetGlobalPolicy()
+            throws RemoteException {
+        setNotificationPolicy_dependingOnCompanionAppDevice_maySetGlobalPolicy(
+                AssociationRequest.DEVICE_PROFILE_NEARBY_DEVICE_STREAMING, false);
+    }
+
+    private void setNotificationPolicy_dependingOnCompanionAppDevice_maySetGlobalPolicy(
+            @AssociationRequest.DeviceProfile String deviceProfile, boolean canSetGlobalPolicy)
+            throws RemoteException {
         mSetFlagsRule.enableFlags(android.app.Flags.FLAG_MODES_API);
         mService.setCallerIsNormalPackage();
         ZenModeHelper zenModeHelper = mock(ZenModeHelper.class);
@@ -13625,14 +13650,19 @@
         when(mCompanionMgr.getAssociations(anyString(), anyInt()))
                 .thenReturn(ImmutableList.of(
                         new AssociationInfo.Builder(1, mUserId, "package")
-                                .setDisplayName("My watch")
-                                .setDeviceProfile(AssociationRequest.DEVICE_PROFILE_WATCH)
+                                .setDisplayName("My connected device")
+                                .setDeviceProfile(deviceProfile)
                                 .build()));
 
         NotificationManager.Policy policy = new NotificationManager.Policy(0, 0, 0);
         mBinderService.setNotificationPolicy("package", policy, false);
 
-        verify(zenModeHelper).setNotificationPolicy(eq(policy), anyInt(), anyInt());
+        if (canSetGlobalPolicy) {
+            verify(zenModeHelper).setNotificationPolicy(eq(policy), anyInt(), anyInt());
+        } else {
+            verify(zenModeHelper).applyGlobalPolicyAsImplicitZenRule(anyString(), anyInt(),
+                    eq(policy), anyInt());
+        }
     }
 
     @Test
@@ -13702,7 +13732,29 @@
 
     @Test
     @EnableCompatChanges(NotificationManagerService.MANAGE_GLOBAL_ZEN_VIA_IMPLICIT_RULES)
-    public void setInterruptionFilter_watchCompanionApp_setsGlobalPolicy() throws RemoteException {
+    public void setInterruptionFilter_watchCompanionApp_setsGlobalZen() throws RemoteException {
+        setInterruptionFilter_dependingOnCompanionAppDevice_maySetGlobalZen(
+                AssociationRequest.DEVICE_PROFILE_WATCH, true);
+    }
+
+    @Test
+    @EnableCompatChanges(NotificationManagerService.MANAGE_GLOBAL_ZEN_VIA_IMPLICIT_RULES)
+    public void setInterruptionFilter_autoCompanionApp_setsGlobalZen() throws RemoteException {
+        setInterruptionFilter_dependingOnCompanionAppDevice_maySetGlobalZen(
+                AssociationRequest.DEVICE_PROFILE_AUTOMOTIVE_PROJECTION, true);
+    }
+
+    @Test
+    @EnableCompatChanges(NotificationManagerService.MANAGE_GLOBAL_ZEN_VIA_IMPLICIT_RULES)
+    public void setInterruptionFilter_otherCompanionApp_doesNotSetGlobalZen()
+            throws RemoteException {
+        setInterruptionFilter_dependingOnCompanionAppDevice_maySetGlobalZen(
+                AssociationRequest.DEVICE_PROFILE_NEARBY_DEVICE_STREAMING, false);
+    }
+
+    private void setInterruptionFilter_dependingOnCompanionAppDevice_maySetGlobalZen(
+            @AssociationRequest.DeviceProfile String deviceProfile, boolean canSetGlobalPolicy)
+            throws RemoteException {
         mSetFlagsRule.enableFlags(android.app.Flags.FLAG_MODES_API);
         ZenModeHelper zenModeHelper = mock(ZenModeHelper.class);
         mService.mZenModeHelper = zenModeHelper;
@@ -13712,14 +13764,19 @@
         when(mCompanionMgr.getAssociations(anyString(), anyInt()))
                 .thenReturn(ImmutableList.of(
                         new AssociationInfo.Builder(1, mUserId, "package")
-                                .setDisplayName("My watch")
-                                .setDeviceProfile(AssociationRequest.DEVICE_PROFILE_WATCH)
+                                .setDisplayName("My connected device")
+                                .setDeviceProfile(deviceProfile)
                                 .build()));
 
         mBinderService.setInterruptionFilter("package", INTERRUPTION_FILTER_PRIORITY, false);
 
-        verify(zenModeHelper).setManualZenMode(eq(ZEN_MODE_IMPORTANT_INTERRUPTIONS), eq(null),
-                eq(ZenModeConfig.UPDATE_ORIGIN_APP), anyString(), eq("package"), anyInt());
+        if (canSetGlobalPolicy) {
+            verify(zenModeHelper).setManualZenMode(eq(ZEN_MODE_IMPORTANT_INTERRUPTIONS), eq(null),
+                    eq(ZenModeConfig.UPDATE_ORIGIN_APP), anyString(), eq("package"), anyInt());
+        } else {
+            verify(zenModeHelper).applyGlobalZenModeAsImplicitZenRule(anyString(), anyInt(),
+                    eq(ZEN_MODE_IMPORTANT_INTERRUPTIONS));
+        }
     }
 
     @Test
@@ -13737,6 +13794,7 @@
     }
 
     @Test
+    @Ignore("b/316989461")
     public void cancelNotificationsFromListener_rapidClear_oldNew_cancelOne()
             throws RemoteException {
         mSetFlagsRule.enableFlags(android.view.contentprotection.flags.Flags
@@ -13766,6 +13824,7 @@
     }
 
     @Test
+    @Ignore("b/316989461")
     public void cancelNotificationsFromListener_rapidClear_old_cancelOne() throws RemoteException {
         mSetFlagsRule.enableFlags(android.view.contentprotection.flags.Flags
                 .FLAG_RAPID_CLEAR_NOTIFICATIONS_BY_LISTENER_APP_OP_ENABLED);
@@ -13793,6 +13852,7 @@
     }
 
     @Test
+    @Ignore("b/316989461")
     public void cancelNotificationsFromListener_rapidClear_oldNew_cancelOne_flagDisabled()
             throws RemoteException {
         mSetFlagsRule.disableFlags(android.view.contentprotection.flags.Flags
@@ -13823,6 +13883,7 @@
     }
 
     @Test
+    @Ignore("b/316989461")
     public void cancelNotificationsFromListener_rapidClear_oldNew_cancelAll()
             throws RemoteException {
         mSetFlagsRule.enableFlags(android.view.contentprotection.flags.Flags
@@ -13851,6 +13912,7 @@
     }
 
     @Test
+    @Ignore("b/316989461")
     public void cancelNotificationsFromListener_rapidClear_old_cancelAll() throws RemoteException {
         mSetFlagsRule.enableFlags(android.view.contentprotection.flags.Flags
                 .FLAG_RAPID_CLEAR_NOTIFICATIONS_BY_LISTENER_APP_OP_ENABLED);
@@ -13877,6 +13939,7 @@
     }
 
     @Test
+    @Ignore("b/316989461")
     public void cancelNotificationsFromListener_rapidClear_oldNew_cancelAll_flagDisabled()
             throws RemoteException {
         mSetFlagsRule.disableFlags(android.view.contentprotection.flags.Flags
diff --git a/services/tests/wmtests/src/com/android/server/policy/DeferredKeyActionExecutorTests.java b/services/tests/wmtests/src/com/android/server/policy/DeferredKeyActionExecutorTests.java
index d2ef180..ca3787e 100644
--- a/services/tests/wmtests/src/com/android/server/policy/DeferredKeyActionExecutorTests.java
+++ b/services/tests/wmtests/src/com/android/server/policy/DeferredKeyActionExecutorTests.java
@@ -95,6 +95,26 @@
         assertFalse(action.executed);
     }
 
+    @Test
+    public void queueKeyAction_beforeAndAfterCancelQueuedActions_onlyActionsAfterCancelExecuted() {
+        TestAction action1 = new TestAction();
+        TestAction action2 = new TestAction();
+        TestAction action3 = new TestAction();
+        mKeyActionExecutor.queueKeyAction(
+                KeyEvent.KEYCODE_STEM_PRIMARY, /* downTime= */ 1, action1);
+        mKeyActionExecutor.queueKeyAction(
+                KeyEvent.KEYCODE_STEM_PRIMARY, /* downTime= */ 1, action2);
+        mKeyActionExecutor.cancelQueuedAction(KeyEvent.KEYCODE_STEM_PRIMARY);
+        mKeyActionExecutor.queueKeyAction(
+                KeyEvent.KEYCODE_STEM_PRIMARY, /* downTime= */ 1, action3);
+
+        mKeyActionExecutor.setActionsExecutable(KeyEvent.KEYCODE_STEM_PRIMARY, /* downTime= */ 1);
+
+        assertFalse(action1.executed);
+        assertFalse(action2.executed);
+        assertTrue(action3.executed);
+    }
+
     static class TestAction implements Runnable {
         public boolean executed;
 
diff --git a/services/tests/wmtests/src/com/android/server/policy/StemKeyGestureTests.java b/services/tests/wmtests/src/com/android/server/policy/StemKeyGestureTests.java
index f7ad2a8..50d37ec 100644
--- a/services/tests/wmtests/src/com/android/server/policy/StemKeyGestureTests.java
+++ b/services/tests/wmtests/src/com/android/server/policy/StemKeyGestureTests.java
@@ -19,14 +19,18 @@
 import static android.provider.Settings.Global.STEM_PRIMARY_BUTTON_DOUBLE_PRESS;
 import static android.provider.Settings.Global.STEM_PRIMARY_BUTTON_LONG_PRESS;
 import static android.provider.Settings.Global.STEM_PRIMARY_BUTTON_SHORT_PRESS;
+import static android.provider.Settings.Global.STEM_PRIMARY_BUTTON_TRIPLE_PRESS;
 import static android.view.KeyEvent.KEYCODE_STEM_PRIMARY;
 
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn;
+import static com.android.server.policy.PhoneWindowManager.DOUBLE_PRESS_PRIMARY_SWITCH_RECENT_APP;
 import static com.android.server.policy.PhoneWindowManager.LONG_PRESS_PRIMARY_LAUNCH_VOICE_ASSISTANT;
 import static com.android.server.policy.PhoneWindowManager.SHORT_PRESS_PRIMARY_LAUNCH_ALL_APPS;
 import static com.android.server.policy.PhoneWindowManager.SHORT_PRESS_PRIMARY_LAUNCH_TARGET_ACTIVITY;
+import static com.android.server.policy.PhoneWindowManager.TRIPLE_PRESS_PRIMARY_TOGGLE_ACCESSIBILITY;
 
 import android.app.ActivityManager.RecentTaskInfo;
+import android.app.ActivityTaskManager.RootTaskInfo;
 import android.content.ComponentName;
 import android.os.RemoteException;
 import android.provider.Settings;
@@ -50,6 +54,7 @@
     public void stemSingleKey_duringSetup_doNothing() {
         overrideBehavior(STEM_PRIMARY_BUTTON_SHORT_PRESS, SHORT_PRESS_PRIMARY_LAUNCH_ALL_APPS);
         setUpPhoneWindowManager(/* supportSettingsUpdate= */ true);
+        mPhoneWindowManager.overrideShouldEarlyShortPressOnStemPrimary(false);
         mPhoneWindowManager.setKeyguardServiceDelegateIsShowing(false);
         mPhoneWindowManager.overrideIsUserSetupComplete(false);
 
@@ -65,6 +70,7 @@
     public void stemSingleKey_AfterSetup_openAllApp() {
         overrideBehavior(STEM_PRIMARY_BUTTON_SHORT_PRESS, SHORT_PRESS_PRIMARY_LAUNCH_ALL_APPS);
         setUpPhoneWindowManager(/* supportSettingsUpdate= */ true);
+        mPhoneWindowManager.overrideShouldEarlyShortPressOnStemPrimary(false);
         mPhoneWindowManager.overrideStartActivity();
         mPhoneWindowManager.setKeyguardServiceDelegateIsShowing(false);
         mPhoneWindowManager.overrideIsUserSetupComplete(true);
@@ -83,6 +89,7 @@
                 STEM_PRIMARY_BUTTON_SHORT_PRESS,
                 SHORT_PRESS_PRIMARY_LAUNCH_TARGET_ACTIVITY);
         setUpPhoneWindowManager(/* supportSettingsUpdate= */ true);
+        mPhoneWindowManager.overrideShouldEarlyShortPressOnStemPrimary(false);
         mPhoneWindowManager.overrideStartActivity();
         mPhoneWindowManager.setKeyguardServiceDelegateIsShowing(false);
         mPhoneWindowManager.overrideIsUserSetupComplete(true);
@@ -104,6 +111,7 @@
         mPhoneWindowManager.setKeyguardServiceDelegateIsShowing(false);
         mPhoneWindowManager.overrideIsUserSetupComplete(true);
         mPhoneWindowManager.overrideFocusedWindowButtonOverridePermission(true);
+
         setDispatchedKeyHandler(keyEvent -> true);
 
         sendKey(KEYCODE_STEM_PRIMARY);
@@ -131,6 +139,7 @@
                 STEM_PRIMARY_BUTTON_LONG_PRESS,
                 LONG_PRESS_PRIMARY_LAUNCH_VOICE_ASSISTANT);
         setUpPhoneWindowManager(/* supportSettingsUpdate= */ true);
+        mPhoneWindowManager.overrideShouldEarlyShortPressOnStemPrimary(false);
         mPhoneWindowManager.setupAssistForLaunch();
         mPhoneWindowManager.overrideIsUserSetupComplete(true);
 
@@ -144,6 +153,7 @@
                 STEM_PRIMARY_BUTTON_LONG_PRESS,
                 LONG_PRESS_PRIMARY_LAUNCH_VOICE_ASSISTANT);
         setUpPhoneWindowManager(/* supportSettingsUpdate= */ true);
+        mPhoneWindowManager.overrideShouldEarlyShortPressOnStemPrimary(false);
         mPhoneWindowManager.setupAssistForLaunch();
         mPhoneWindowManager.overrideSearchManager(null);
         mPhoneWindowManager.overrideStatusBarManagerInternal();
@@ -156,7 +166,8 @@
     @Test
     public void stemDoubleKey_EarlyShortPress_AllAppsThenSwitchToMostRecent()
             throws RemoteException {
-        overrideBehavior(STEM_PRIMARY_BUTTON_DOUBLE_PRESS, SHORT_PRESS_PRIMARY_LAUNCH_ALL_APPS);
+        overrideBehavior(STEM_PRIMARY_BUTTON_SHORT_PRESS, SHORT_PRESS_PRIMARY_LAUNCH_ALL_APPS);
+        overrideBehavior(STEM_PRIMARY_BUTTON_DOUBLE_PRESS, DOUBLE_PRESS_PRIMARY_SWITCH_RECENT_APP);
         setUpPhoneWindowManager(/* supportSettingsUpdate= */ true);
         mPhoneWindowManager.overrideShouldEarlyShortPressOnStemPrimary(true);
         mPhoneWindowManager.setKeyguardServiceDelegateIsShowing(false);
@@ -171,14 +182,47 @@
         sendKey(KEYCODE_STEM_PRIMARY);
 
         mPhoneWindowManager.assertOpenAllAppView();
-        mPhoneWindowManager.assertSwitchToRecent(referenceId);
+        mPhoneWindowManager.assertSwitchToTask(referenceId);
     }
 
     @Test
-    public void stemDoubleKey_NoEarlyShortPress_SwitchToMostRecent() throws RemoteException {
+    public void stemTripleKey_EarlyShortPress_AllAppsThenBackToOriginalThenToggleA11y()
+            throws RemoteException {
+        overrideBehavior(STEM_PRIMARY_BUTTON_SHORT_PRESS, SHORT_PRESS_PRIMARY_LAUNCH_ALL_APPS);
+        overrideBehavior(
+                STEM_PRIMARY_BUTTON_TRIPLE_PRESS, TRIPLE_PRESS_PRIMARY_TOGGLE_ACCESSIBILITY);
+        setUpPhoneWindowManager(/* supportSettingsUpdate= */ true);
+        mPhoneWindowManager.overrideShouldEarlyShortPressOnStemPrimary(true);
+        mPhoneWindowManager.overrideTalkbackShortcutGestureEnabled(true);
+        mPhoneWindowManager.setKeyguardServiceDelegateIsShowing(false);
+        mPhoneWindowManager.overrideIsUserSetupComplete(true);
+        RootTaskInfo allAppsTask = new RootTaskInfo();
+        int referenceId = 777;
+        allAppsTask.taskId = referenceId;
+        doReturn(allAppsTask)
+                .when(mPhoneWindowManager.mActivityManagerService)
+                .getFocusedRootTaskInfo();
+
+        mPhoneWindowManager.assertTalkBack(/* expectEnabled= */ false);
+
+        sendKey(KEYCODE_STEM_PRIMARY);
+        sendKey(KEYCODE_STEM_PRIMARY);
+        sendKey(KEYCODE_STEM_PRIMARY);
+
+        mPhoneWindowManager.assertOpenAllAppView();
+        mPhoneWindowManager.assertSwitchToTask(referenceId);
+        mPhoneWindowManager.assertTalkBack(/* expectEnabled= */ true);
+    }
+
+    @Test
+    public void stemMultiKey_NoEarlyPress_NoOpenAllApp() throws RemoteException {
+        overrideBehavior(STEM_PRIMARY_BUTTON_SHORT_PRESS, SHORT_PRESS_PRIMARY_LAUNCH_ALL_APPS);
         overrideBehavior(STEM_PRIMARY_BUTTON_DOUBLE_PRESS, SHORT_PRESS_PRIMARY_LAUNCH_ALL_APPS);
+        overrideBehavior(
+                STEM_PRIMARY_BUTTON_TRIPLE_PRESS, TRIPLE_PRESS_PRIMARY_TOGGLE_ACCESSIBILITY);
         setUpPhoneWindowManager(/* supportSettingsUpdate= */ true);
         mPhoneWindowManager.overrideShouldEarlyShortPressOnStemPrimary(false);
+        mPhoneWindowManager.overrideTalkbackShortcutGestureEnabled(true);
         mPhoneWindowManager.setKeyguardServiceDelegateIsShowing(false);
         mPhoneWindowManager.overrideIsUserSetupComplete(true);
         RecentTaskInfo recentTaskInfo = new RecentTaskInfo();
@@ -189,9 +233,16 @@
 
         sendKey(KEYCODE_STEM_PRIMARY);
         sendKey(KEYCODE_STEM_PRIMARY);
+        sendKey(KEYCODE_STEM_PRIMARY);
 
         mPhoneWindowManager.assertNotOpenAllAppView();
-        mPhoneWindowManager.assertSwitchToRecent(referenceId);
+        mPhoneWindowManager.assertTalkBack(/* expectEnabled= */ true);
+
+        sendKey(KEYCODE_STEM_PRIMARY);
+        sendKey(KEYCODE_STEM_PRIMARY);
+
+        mPhoneWindowManager.assertNotOpenAllAppView();
+        mPhoneWindowManager.assertSwitchToTask(referenceId);
     }
 
     @Test
@@ -215,7 +266,7 @@
         sendKey(KEYCODE_STEM_PRIMARY);
 
         mPhoneWindowManager.assertNotOpenAllAppView();
-        mPhoneWindowManager.assertSwitchToRecent(referenceId);
+        mPhoneWindowManager.assertSwitchToTask(referenceId);
     }
 
     private void overrideBehavior(String key, int expectedBehavior) {
diff --git a/services/tests/wmtests/src/com/android/server/policy/TestPhoneWindowManager.java b/services/tests/wmtests/src/com/android/server/policy/TestPhoneWindowManager.java
index 0678210..7c2f7ee 100644
--- a/services/tests/wmtests/src/com/android/server/policy/TestPhoneWindowManager.java
+++ b/services/tests/wmtests/src/com/android/server/policy/TestPhoneWindowManager.java
@@ -73,6 +73,7 @@
 import android.os.Handler;
 import android.os.HandlerThread;
 import android.os.IBinder;
+import android.os.Looper;
 import android.os.PowerManager;
 import android.os.PowerManagerInternal;
 import android.os.RemoteException;
@@ -176,8 +177,9 @@
     private Handler mHandler;
 
     private boolean mIsTalkBackEnabled;
+    private boolean mIsTalkBackShortcutGestureEnabled;
 
-    class TestTalkbackShortcutController extends TalkbackShortcutController {
+    private class TestTalkbackShortcutController extends TalkbackShortcutController {
         TestTalkbackShortcutController(Context context) {
             super(context);
         }
@@ -190,13 +192,18 @@
 
         @Override
         boolean isTalkBackShortcutGestureEnabled() {
-            return true;
+            return mIsTalkBackShortcutGestureEnabled;
         }
     }
 
     private class TestInjector extends PhoneWindowManager.Injector {
         TestInjector(Context context, WindowManagerPolicy.WindowManagerFuncs funcs) {
-            super(context, funcs, mTestLooper.getLooper());
+            super(context, funcs);
+        }
+
+        @Override
+        Looper getLooper() {
+            return mTestLooper.getLooper();
         }
 
         AccessibilityShortcutController getAccessibilityShortcutController(
@@ -410,6 +417,10 @@
         mPhoneWindowManager.mShouldEarlyShortPressOnStemPrimary = shouldEarlyShortPress;
     }
 
+    void overrideTalkbackShortcutGestureEnabled(boolean enabled) {
+        mIsTalkBackShortcutGestureEnabled = enabled;
+    }
+
      // Override assist perform function.
     void overrideLongPressOnPower(int behavior) {
         mPhoneWindowManager.mLongPressOnPowerBehavior = behavior;
@@ -714,7 +725,7 @@
     }
 
     void assertOpenAllAppView() {
-        mTestLooper.dispatchAll();
+        moveTimeForward(TEST_SINGLE_KEY_DELAY_MILLIS);
         ArgumentCaptor<Intent> intentCaptor = ArgumentCaptor.forClass(Intent.class);
         verify(mContext, timeout(TEST_SINGLE_KEY_DELAY_MILLIS))
                 .startActivityAsUser(intentCaptor.capture(), isNull(), any(UserHandle.class));
@@ -728,7 +739,7 @@
     }
 
     void assertActivityTargetLaunched(ComponentName targetActivity) {
-        mTestLooper.dispatchAll();
+        moveTimeForward(TEST_SINGLE_KEY_DELAY_MILLIS);
         ArgumentCaptor<Intent> intentCaptor = ArgumentCaptor.forClass(Intent.class);
         verify(mContext, timeout(TEST_SINGLE_KEY_DELAY_MILLIS))
                 .startActivityAsUser(intentCaptor.capture(), isNull(), any(UserHandle.class));
@@ -743,10 +754,15 @@
                         expectedModifierState, deviceBus), description(errorMsg));
     }
 
-    void assertSwitchToRecent(int persistentId) throws RemoteException {
+    void assertSwitchToTask(int persistentId) throws RemoteException {
         mTestLooper.dispatchAll();
         verify(mActivityManagerService,
                 timeout(TEST_SINGLE_KEY_DELAY_MILLIS)).startActivityFromRecents(eq(persistentId),
                 isNull());
     }
+
+    void assertTalkBack(boolean expectEnabled) {
+        mTestLooper.dispatchAll();
+        Assert.assertEquals(expectEnabled, mIsTalkBackEnabled);
+    }
 }
diff --git a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
index 85c6f9e..718c598 100644
--- a/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/ActivityRecordTests.java
@@ -202,8 +202,7 @@
     }
 
     private TestStartingWindowOrganizer registerTestStartingWindowOrganizer() {
-        return new TestStartingWindowOrganizer(mAtm,
-                mSystemServicesTestRule.getPowerManagerWrapper());
+        return new TestStartingWindowOrganizer(mAtm);
     }
 
     @Test
diff --git a/services/tests/wmtests/src/com/android/server/wm/SystemServicesTestRule.java b/services/tests/wmtests/src/com/android/server/wm/SystemServicesTestRule.java
index 51f0404..8cd9ff3 100644
--- a/services/tests/wmtests/src/com/android/server/wm/SystemServicesTestRule.java
+++ b/services/tests/wmtests/src/com/android/server/wm/SystemServicesTestRule.java
@@ -134,7 +134,6 @@
     private StaticMockitoSession mMockitoSession;
     private ActivityTaskManagerService mAtmService;
     private WindowManagerService mWmService;
-    private WindowState.PowerManagerWrapper mPowerManagerWrapper;
     private InputManagerService mImService;
     private InputChannel mInputChannel;
     private Runnable mOnBeforeServicesCreated;
@@ -360,7 +359,6 @@
     }
 
     private void setUpWindowManagerService() {
-        mPowerManagerWrapper = mock(WindowState.PowerManagerWrapper.class);
         TestWindowManagerPolicy wmPolicy = new TestWindowManagerPolicy();
         TestDisplayWindowSettingsProvider testDisplayWindowSettingsProvider =
                 new TestDisplayWindowSettingsProvider();
@@ -485,10 +483,6 @@
         return mAtmService;
     }
 
-    WindowState.PowerManagerWrapper getPowerManagerWrapper() {
-        return mPowerManagerWrapper;
-    }
-
     /** Creates a no-op wakelock object. */
     PowerManager.WakeLock createStubbedWakeLock(boolean needVerification) {
         if (needVerification) {
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java b/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java
index 2007f68..75e252f 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowStateTests.java
@@ -52,7 +52,6 @@
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.doThrow;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.mock;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.never;
-import static com.android.dx.mockito.inline.extended.ExtendedMockito.reset;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.spy;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn;
 import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify;
@@ -368,28 +367,26 @@
         firstWindow.mAttrs.flags |= WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON;
         secondWindow.mAttrs.flags |= WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON;
 
-        final WindowState.PowerManagerWrapper powerManagerWrapper =
-                mSystemServicesTestRule.getPowerManagerWrapper();
-        reset(powerManagerWrapper);
+        final var powerManager = mWm.mPowerManager;
+        clearInvocations(powerManager);
         firstWindow.prepareWindowToDisplayDuringRelayout(false /*wasVisible*/);
-        verify(powerManagerWrapper).wakeUp(anyLong(), anyInt(), anyString());
+        verify(powerManager).wakeUp(anyLong(), anyInt(), anyString());
 
-        reset(powerManagerWrapper);
+        clearInvocations(powerManager);
         secondWindow.prepareWindowToDisplayDuringRelayout(false /*wasVisible*/);
-        verify(powerManagerWrapper).wakeUp(anyLong(), anyInt(), anyString());
+        verify(powerManager).wakeUp(anyLong(), anyInt(), anyString());
     }
 
     private void testPrepareWindowToDisplayDuringRelayout(WindowState appWindow,
             boolean expectedWakeupCalled, boolean expectedCurrentLaunchCanTurnScreenOn) {
-        final WindowState.PowerManagerWrapper powerManagerWrapper =
-                mSystemServicesTestRule.getPowerManagerWrapper();
-        reset(powerManagerWrapper);
+        final var powerManager = mWm.mPowerManager;
+        clearInvocations(powerManager);
         appWindow.prepareWindowToDisplayDuringRelayout(false /* wasVisible */);
 
         if (expectedWakeupCalled) {
-            verify(powerManagerWrapper).wakeUp(anyLong(), anyInt(), anyString());
+            verify(powerManager).wakeUp(anyLong(), anyInt(), anyString());
         } else {
-            verify(powerManagerWrapper, never()).wakeUp(anyLong(), anyInt(), anyString());
+            verify(powerManager, never()).wakeUp(anyLong(), anyInt(), anyString());
         }
         // If wakeup is expected to be called, the currentLaunchCanTurnScreenOn should be false
         // because the state will be consumed.
diff --git a/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java b/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
index 616a23e..a5f6190f 100644
--- a/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
+++ b/services/tests/wmtests/src/com/android/server/wm/WindowTestsBase.java
@@ -637,14 +637,12 @@
     WindowState createWindow(WindowState parent, int type, WindowToken token, String name,
             int ownerId, boolean ownerCanAddInternalSystemWindow, IWindow iwindow) {
         return createWindow(parent, type, token, name, ownerId, UserHandle.getUserId(ownerId),
-                ownerCanAddInternalSystemWindow, mWm, getTestSession(token), iwindow,
-                mSystemServicesTestRule.getPowerManagerWrapper());
+                ownerCanAddInternalSystemWindow, mWm, getTestSession(token), iwindow);
     }
 
     static WindowState createWindow(WindowState parent, int type, WindowToken token,
             String name, int ownerId, int userId, boolean ownerCanAddInternalSystemWindow,
-            WindowManagerService service, Session session, IWindow iWindow,
-            WindowState.PowerManagerWrapper powerManagerWrapper) {
+            WindowManagerService service, Session session, IWindow iWindow) {
         SystemServicesTestRule.checkHoldsLock(service.mGlobalLock);
 
         final WindowManager.LayoutParams attrs = new WindowManager.LayoutParams(type);
@@ -652,9 +650,7 @@
         attrs.packageName = "test";
 
         final WindowState w = new WindowState(service, session, iWindow, token, parent,
-                OP_NONE, attrs, VISIBLE, ownerId, userId,
-                ownerCanAddInternalSystemWindow,
-                powerManagerWrapper);
+                OP_NONE, attrs, VISIBLE, ownerId, userId, ownerCanAddInternalSystemWindow);
         // TODO: Probably better to make this call in the WindowState ctor to avoid errors with
         // adding it to the token...
         token.addWindow(w);
@@ -1738,17 +1734,14 @@
     static class TestStartingWindowOrganizer extends WindowOrganizerTests.StubOrganizer {
         private final ActivityTaskManagerService mAtm;
         private final WindowManagerService mWMService;
-        private final WindowState.PowerManagerWrapper mPowerManagerWrapper;
 
         private Runnable mRunnableWhenAddingSplashScreen;
         private final SparseArray<IBinder> mTaskAppMap = new SparseArray<>();
         private final HashMap<IBinder, WindowState> mAppWindowMap = new HashMap<>();
 
-        TestStartingWindowOrganizer(ActivityTaskManagerService service,
-                WindowState.PowerManagerWrapper powerManagerWrapper) {
+        TestStartingWindowOrganizer(ActivityTaskManagerService service) {
             mAtm = service;
             mWMService = mAtm.mWindowManager;
-            mPowerManagerWrapper = powerManagerWrapper;
             mAtm.mTaskOrganizerController.setDeferTaskOrgCallbacksConsumer(Runnable::run);
             mAtm.mTaskOrganizerController.registerTaskOrganizer(this);
         }
@@ -1767,8 +1760,7 @@
                 final WindowState window = WindowTestsBase.createWindow(null,
                         TYPE_APPLICATION_STARTING, activity,
                         "Starting window", 0 /* ownerId */, 0 /* userId*/,
-                        false /* internalWindows */, mWMService, createTestSession(mAtm), iWindow,
-                        mPowerManagerWrapper);
+                        false /* internalWindows */, mWMService, createTestSession(mAtm), iWindow);
                 activity.mStartingWindow = window;
                 mAppWindowMap.put(info.appToken, window);
                 mTaskAppMap.put(info.taskInfo.taskId, info.appToken);
diff --git a/services/usage/java/com/android/server/usage/StorageStatsService.java b/services/usage/java/com/android/server/usage/StorageStatsService.java
index 4c978ad..2445f51 100644
--- a/services/usage/java/com/android/server/usage/StorageStatsService.java
+++ b/services/usage/java/com/android/server/usage/StorageStatsService.java
@@ -28,6 +28,7 @@
 import android.annotation.UserIdInt;
 import android.app.AppOpsManager;
 import android.app.usage.ExternalStorageStats;
+import android.app.usage.Flags;
 import android.app.usage.IStorageStatsManager;
 import android.app.usage.StorageStats;
 import android.app.usage.UsageStatsManagerInternal;
@@ -434,6 +435,7 @@
         final long[] ceDataInodes = new long[packageNames.length];
         String[] codePaths = new String[0];
 
+        final PackageStats stats = new PackageStats(TAG);
         for (int i = 0; i < packageNames.length; i++) {
             try {
                 final ApplicationInfo appInfo = mPackage.getApplicationInfoAsUser(packageNames[i],
@@ -443,7 +445,11 @@
                 } else {
                     if (appInfo.getCodePath() != null) {
                         codePaths = ArrayUtils.appendElement(String.class, codePaths,
-                                appInfo.getCodePath());
+                            appInfo.getCodePath());
+                    }
+                    if (Flags.getAppBytesByDataTypeApi()) {
+                        computeAppStatsByDataTypes(
+                            stats, appInfo.sourceDir);
                     }
                 }
             } catch (NameNotFoundException e) {
@@ -451,7 +457,6 @@
             }
         }
 
-        final PackageStats stats = new PackageStats(TAG);
         try {
             mInstaller.getAppSize(volumeUuid, packageNames, userId, getDefaultFlags(),
                     appId, ceDataInodes, codePaths, stats);
@@ -587,6 +592,9 @@
         res.codeBytes = stats.codeSize + stats.externalCodeSize;
         res.dataBytes = stats.dataSize + stats.externalDataSize;
         res.cacheBytes = stats.cacheSize + stats.externalCacheSize;
+        res.apkBytes = stats.apkSize;
+        res.libBytes = stats.libSize;
+        res.dmBytes = stats.dmSize;
         res.externalCacheBytes = stats.externalCacheSize;
         return res;
     }
@@ -894,4 +902,61 @@
             mStorageStatsAugmenters.add(Pair.create(tag, storageStatsAugmenter));
         }
     }
+
+    private long getDirBytes(File dir) {
+        if (!dir.isDirectory()) {
+            return 0;
+        }
+
+        long size = 0;
+        try {
+            for (File file : dir.listFiles()) {
+                if (file.isFile()) {
+                    size += file.length();
+                    continue;
+                }
+                if (file.isDirectory()) {
+                    size += getDirBytes(file);
+                }
+            }
+        } catch (NullPointerException e) {
+            Slog.w(TAG, "Failed to list directory " + dir.getName());
+        }
+
+        return size;
+    }
+
+    private long getFileBytesInDir(File dir, String suffix) {
+        if (!dir.isDirectory()) {
+            return 0;
+        }
+
+        long size = 0;
+        try {
+            for (File file : dir.listFiles()) {
+                if (file.isFile() && file.getName().endsWith(suffix)) {
+                    size += file.length();
+                }
+            }
+        } catch (NullPointerException e) {
+             Slog.w(TAG, "Failed to list directory " + dir.getName());
+        }
+
+        return size;
+    }
+
+    private void computeAppStatsByDataTypes(
+        PackageStats stats, String sourceDirName) {
+
+        // Get apk, lib, dm file sizes.
+        File srcDir = new File(sourceDirName);
+        if (srcDir.isFile()) {
+            sourceDirName = srcDir.getParent();
+            srcDir = new File(sourceDirName);
+        }
+
+        stats.apkSize += getFileBytesInDir(srcDir, ".apk");
+        stats.dmSize += getFileBytesInDir(srcDir, ".dm");
+        stats.libSize += getDirBytes(new File(sourceDirName + "/lib/"));
+    }
 }
diff --git a/telephony/java/android/telephony/AnomalyReporter.java b/telephony/java/android/telephony/AnomalyReporter.java
index db38f88..575ec27 100644
--- a/telephony/java/android/telephony/AnomalyReporter.java
+++ b/telephony/java/android/telephony/AnomalyReporter.java
@@ -187,14 +187,15 @@
         }
 
         for (ResolveInfo r : packages) {
-            if (r.activityInfo == null
-                    || pm.checkPermission(
+            if (r.activityInfo == null) {
+                Rlog.w(TAG, "Found package without activity");
+                continue;
+            } else if (pm.checkPermission(
                             android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE,
                             r.activityInfo.packageName)
-                    != PackageManager.PERMISSION_GRANTED) {
-                Rlog.w(TAG,
-                        "Found package without proper permissions or no activity"
-                                + r.activityInfo.packageName);
+                      != PackageManager.PERMISSION_GRANTED) {
+                Rlog.w(TAG, "Found package without proper permissions"
+                                    + r.activityInfo.packageName);
                 continue;
             }
             Rlog.d(TAG, "Found a valid package " + r.activityInfo.packageName);
diff --git a/telephony/java/android/telephony/CarrierConfigManager.java b/telephony/java/android/telephony/CarrierConfigManager.java
index 101d285..c7b84a3 100644
--- a/telephony/java/android/telephony/CarrierConfigManager.java
+++ b/telephony/java/android/telephony/CarrierConfigManager.java
@@ -3721,19 +3721,19 @@
      * This configuration allows the system UI to display different 5G icons for different 5G
      * scenarios.
      *
-     * There are five 5G scenarios:
-     * 1. connected_mmwave: device currently connected to 5G cell as the secondary cell and using
-     *    millimeter wave.
-     * 2. connected: device currently connected to 5G cell as the secondary cell but not using
-     *    millimeter wave.
-     * 3. not_restricted_rrc_idle: device camped on a network that has 5G capability(not necessary
-     *    to connect a 5G cell as a secondary cell) and the use of 5G is not restricted and RRC
-     *    currently in IDLE state.
-     * 4. not_restricted_rrc_con: device camped on a network that has 5G capability(not necessary
-     *    to connect a 5G cell as a secondary cell) and the use of 5G is not restricted and RRC
-     *    currently in CONNECTED state.
-     * 5. restricted: device camped on a network that has 5G capability(not necessary to connect a
-     *    5G cell as a secondary cell) but the use of 5G is restricted.
+     * There are six 5G scenarios for icon configuration:
+     * 1. connected_mmwave: device currently connected to 5G cell as the primary or secondary cell
+     *    and considered NR advanced.
+     * 2. connected: device currently connected to 5G cell as the primary or secondary cell but not
+     *    considered NR advanced.
+     * 3. connected_rrc_idle: device currently connected to 5G cell as the primary or secondary cell
+     *    and RRC currently in IDLE state.
+     * 4. not_restricted_rrc_idle: device camped on a network that has 5G capability and the use of
+     *    5G is not restricted and RRC currently in IDLE state.
+     * 5. not_restricted_rrc_con: device camped on a network that has 5G capability and the use of
+     *    5G is not restricted and RRC currently in CONNECTED state.
+     * 6. restricted: device camped on a network that has 5G capability but the use of 5G is
+     *    restricted.
      *
      * The configured string contains multiple key-value pairs separated by comma. For each pair,
      * the key and value are separated by a colon. The key corresponds to a 5G status above and
@@ -3754,21 +3754,21 @@
      * This configuration allows the system UI to determine how long to continue to display 5G icons
      * when the device switches between different 5G scenarios.
      *
-     * There are seven 5G scenarios:
-     * 1. connected_mmwave: device currently connected to 5G cell as the secondary cell and using
-     *    millimeter wave.
-     * 2. connected: device currently connected to 5G cell as the secondary cell but not using
-     *    millimeter wave.
-     * 3. not_restricted_rrc_idle: device camped on a network that has 5G capability (not necessary
-     *    to connect a 5G cell as a secondary cell) and the use of 5G is not restricted and RRC
-     *    currently in IDLE state.
-     * 4. not_restricted_rrc_con: device camped on a network that has 5G capability (not necessary
-     *    to connect a 5G cell as a secondary cell) and the use of 5G is not restricted and RRC
-     *    currently in CONNECTED state.
-     * 5. restricted: device camped on a network that has 5G capability (not necessary to connect a
-     *    5G cell as a secondary cell) but the use of 5G is restricted.
-     * 6. legacy: device is not camped on a network that has 5G capability
-     * 7. any: any of the above scenarios
+     * There are eight 5G scenarios:
+     * 1. connected_mmwave: device currently connected to 5G cell as the primary or secondary cell
+     *    and considered NR advanced.
+     * 2. connected: device currently connected to 5G cell as the primary or secondary cell but not
+     *    considered NR advanced.
+     * 3. connected_rrc_idle: device currently connected to 5G cell as the primary or secondary cell
+     *    and RRC currently in IDLE state.
+     * 4. not_restricted_rrc_idle: device camped on a network that has 5G capability and the use of
+     *    5G is not restricted and RRC currently in IDLE state.
+     * 5. not_restricted_rrc_con: device camped on a network that has 5G capability and the use of
+     *    5G is not restricted and RRC currently in CONNECTED state.
+     * 6. restricted: device camped on a network that has 5G capability but the use of 5G is
+     *    restricted.
+     * 7. legacy: device is not camped on a network that has 5G capability
+     * 8. any: any of the above scenarios
      *
      * The configured string contains various timer rules separated by a semicolon.
      * Each rule will have three items: prior 5G scenario, current 5G scenario, and grace period
@@ -3776,8 +3776,8 @@
      * 5G scenario, the system UI will continue to show the icon for the prior 5G scenario (defined
      * in {@link #KEY_5G_ICON_CONFIGURATION_STRING}) for the amount of time specified by the grace
      * period. If the prior 5G scenario is reestablished, the timer will reset and start again if
-     * the UE changes 5G scenarios again. Defined states (5G scenarios #1-5) take precedence over
-     * 'any' (5G scenario #6), and unspecified transitions have a default grace period of 0.
+     * the UE changes 5G scenarios again. Defined states (5G scenarios #1-7) take precedence over
+     * 'any' (5G scenario #8), and unspecified transitions have a default grace period of 0.
      * The order of rules in the configuration determines the priority (the first applicable timer
      * rule will be used).
      *
@@ -3800,21 +3800,21 @@
      * This configuration extends {@link #KEY_5G_ICON_DISPLAY_GRACE_PERIOD_STRING} to allow the
      * system UI to continue displaying 5G icons after the initial timer expires.
      *
-     * There are seven 5G scenarios:
-     * 1. connected_mmwave: device currently connected to 5G cell as the secondary cell and using
-     *    millimeter wave.
-     * 2. connected: device currently connected to 5G cell as the secondary cell but not using
-     *    millimeter wave.
-     * 3. not_restricted_rrc_idle: device camped on a network that has 5G capability (not necessary
-     *    to connect a 5G cell as a secondary cell) and the use of 5G is not restricted and RRC
-     *    currently in IDLE state.
-     * 4. not_restricted_rrc_con: device camped on a network that has 5G capability (not necessary
-     *    to connect a 5G cell as a secondary cell) and the use of 5G is not restricted and RRC
-     *    currently in CONNECTED state.
-     * 5. restricted: device camped on a network that has 5G capability (not necessary to connect a
-     *    5G cell as a secondary cell) but the use of 5G is restricted.
-     * 6. legacy: device is not camped on a network that has 5G capability
-     * 7. any: any of the above scenarios
+     * There are eight 5G scenarios:
+     * 1. connected_mmwave: device currently connected to 5G cell as the primary or secondary cell
+     *    and considered NR advanced.
+     * 2. connected: device currently connected to 5G cell as the primary or secondary cell but not
+     *    considered NR advanced.
+     * 3. connected_rrc_idle: device currently connected to 5G cell as the primary or secondary cell
+     *    and RRC currently in IDLE state.
+     * 4. not_restricted_rrc_idle: device camped on a network that has 5G capability and the use of
+     *    5G is not restricted and RRC currently in IDLE state.
+     * 5. not_restricted_rrc_con: device camped on a network that has 5G capability and the use of
+     *    5G is not restricted and RRC currently in CONNECTED state.
+     * 6. restricted: device camped on a network that has 5G capability but the use of 5G is
+     *    restricted.
+     * 7. legacy: device is not camped on a network that has 5G capability
+     * 8. any: any of the above scenarios
      *
      * The configured string contains various timer rules separated by a semicolon.
      * Each rule will have three items: primary 5G scenario, secondary 5G scenario, and
@@ -3824,7 +3824,7 @@
      * period. If the primary 5G scenario is reestablished, the timers will reset and the system UI
      * will continue to display the icon for the primary 5G scenario without interruption. If the
      * secondary 5G scenario is lost, the timer will reset and the icon will reflect the true state.
-     * Defined states (5G scenarios #1-5) take precedence over 'any' (5G scenario #6), and
+     * Defined states (5G scenarios #1-7) take precedence over 'any' (5G scenario #8), and
      * unspecified transitions have a default grace period of 0. The order of rules in the
      * configuration determines the priority (the first applicable timer rule will be used).
      *
@@ -8891,18 +8891,18 @@
                 KEY_PREFIX + "epdg_static_address_roaming_string";
 
         /**
-         * Controls if the multiple SA proposals allowed for IKE session to include
-         * all the 3GPP TS 33.210 and RFC 8221 supported cipher suites in multiple
-         * IKE SA proposals as per RFC 7296.
+         * Enables the use of multiple IKE SA proposals, encompassing both carrier-preferred
+         * ciphers and all supported ciphers from 3GPP TS 33.210 and RFC 8221,
+         * as defined in RFC 7296.
          */
         @FlaggedApi(Flags.FLAG_ENABLE_MULTIPLE_SA_PROPOSALS)
         public static final String KEY_SUPPORTS_IKE_SESSION_MULTIPLE_SA_PROPOSALS_BOOL =
                 KEY_PREFIX + "supports_ike_session_multiple_sa_proposals_bool";
 
         /**
-         * Controls if the multiple SA proposals allowed for Child session to include
-         * all the 3GPP TS 33.210 and RFC 8221 supported cipher suites in multiple
-         * Child SA proposals as per RFC 7296.
+         * Enables the use of multiple Child SA proposals, encompassing both carrier-preferred
+         * ciphers and all supported ciphers from 3GPP TS 33.210 and RFC 8221,
+         * as defined in RFC 7296.
          */
         @FlaggedApi(Flags.FLAG_ENABLE_MULTIPLE_SA_PROPOSALS)
         public static final String KEY_SUPPORTS_CHILD_SESSION_MULTIPLE_SA_PROPOSALS_BOOL =
@@ -10050,6 +10050,49 @@
     public static final String KEY_AUTO_DATA_SWITCH_RAT_SIGNAL_SCORE_BUNDLE =
             "auto_data_switch_rat_signal_score_string_bundle";
 
+    // TODO(b/316183370): replace @code with @link in javadoc after feature is released
+    /**
+     * An array of cellular services supported by a subscription.
+     *
+     * <p>Permissible values include:
+     * <ul>
+     *   <li>{@code SubscriptionManager#SERVICE_CAPABILITY_VOICE} for voice services</li>
+     *   <li>{@code SubscriptionManager#SERVICE_CAPABILITY_SMS} for SMS services</li>
+     *   <li>{@code SubscriptionManager#SERVICE_CAPABILITY_DATA} for data services</li>
+     * </ul>
+     *
+     * <p>Carrier-specific factors may influence how these services are supported. Therefore,
+     * modifying this carrier configuration might not always enable the specified services. These
+     * capability bitmasks should be considered as indicators of a carrier's preferred services
+     * to enhance user experience, rather than as absolute platform guarantees.
+     *
+     * <p>Device-level service capabilities, defined by
+     * {@code TelephonyManager#isDeviceVoiceCapable} and
+     * {@code TelephonyManager#isDeviceSmsCapable}, take precedence over these subscription-level
+     * settings. For instance, a device where {@code TelephonyManager#isDeviceVoiceCapable} returns
+     * false may not be able to make voice calls, even if subscribed to a service marked as
+     * voice-capable.
+     *
+     * <p>To determine a subscription's cellular service capabilities, use
+     * {@code SubscriptionInfo#getServiceCapabilities()}. To track changes in services, register
+     * a {@link SubscriptionManager.OnSubscriptionsChangedListener} and invoke the
+     * same method in its callback.
+     *
+     * <p>Emergency service availability may not depend on the cellular service capabilities.
+     * For example, emergency calls might be possible on a subscription even if it lacks
+     * {@code SubscriptionManager#SERVICE_CAPABILITY_VOICE}.
+     *
+     * <p>If unset, the default value is “[1, 2, 3]” (supports all cellular services).
+     *
+     * @see TelephonyManager#isDeviceVoiceCapable
+     * @see TelephonyManager#isDeviceSmsCapable
+     * @see SubscriptionInfo#getServiceCapabilities()
+     * @see SubscriptionManager.OnSubscriptionsChangedListener
+     */
+    @FlaggedApi(Flags.FLAG_DATA_ONLY_CELLULAR_SERVICE)
+    public static final String KEY_CELLULAR_SERVICE_CAPABILITIES_INT_ARRAY =
+            "cellular_service_capabilities_int_array";
+
     /** The default value for every variable. */
     private static final PersistableBundle sDefaults;
 
@@ -10565,7 +10608,7 @@
         sDefaults.putBoolean(KEY_USE_CALL_WAITING_USSD_BOOL, false);
         sDefaults.putInt(KEY_CALL_WAITING_SERVICE_CLASS_INT, 1 /* SERVICE_CLASS_VOICE */);
         sDefaults.putString(KEY_5G_ICON_CONFIGURATION_STRING,
-                "connected_mmwave:5G,connected:5G,not_restricted_rrc_idle:5G,"
+                "connected_mmwave:5G,connected:5G,connected_rrc_idle:5G,not_restricted_rrc_idle:5G,"
                         + "not_restricted_rrc_con:5G");
         sDefaults.putString(KEY_5G_ICON_DISPLAY_GRACE_PERIOD_STRING, "");
         sDefaults.putString(KEY_5G_ICON_DISPLAY_SECONDARY_GRACE_PERIOD_STRING, "");
@@ -10837,6 +10880,7 @@
                 new boolean[] {false, false, true, false, false});
         sDefaults.putStringArray(KEY_CARRIER_SERVICE_NAME_STRING_ARRAY, new String[0]);
         sDefaults.putStringArray(KEY_CARRIER_SERVICE_NUMBER_STRING_ARRAY, new String[0]);
+        sDefaults.putIntArray(KEY_CELLULAR_SERVICE_CAPABILITIES_INT_ARRAY, new int[]{1, 2, 3});
     }
 
     /**
diff --git a/telephony/java/android/telephony/SecurityAlgorithmUpdate.java b/telephony/java/android/telephony/SecurityAlgorithmUpdate.java
new file mode 100644
index 0000000..61d7ead
--- /dev/null
+++ b/telephony/java/android/telephony/SecurityAlgorithmUpdate.java
@@ -0,0 +1,214 @@
+/*
+ * 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;
+
+import android.annotation.IntDef;
+import android.annotation.NonNull;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.util.Objects;
+
+/**
+ * A single occurrence capturing a notable change to previously reported
+ * cryptography algorithms for a given network and network event.
+ *
+ * @hide
+ */
+public final class SecurityAlgorithmUpdate implements Parcelable {
+    private static final String TAG = "SecurityAlgorithmUpdate";
+
+    private @ConnectionEvent int mConnectionEvent;
+    private @SecurityAlgorithm int mEncryption;
+    private @SecurityAlgorithm int mIntegrity;
+    private boolean mIsUnprotectedEmergency;
+
+    public SecurityAlgorithmUpdate(@ConnectionEvent int connectionEvent,
+            @SecurityAlgorithm int encryption, @SecurityAlgorithm int integrity,
+            boolean isUnprotectedEmergency) {
+        mConnectionEvent = connectionEvent;
+        mEncryption = encryption;
+        mIntegrity = integrity;
+        mIsUnprotectedEmergency = isUnprotectedEmergency;
+    }
+
+    private SecurityAlgorithmUpdate(Parcel in) {
+        readFromParcel(in);
+    }
+
+    public @ConnectionEvent int getConnectionEvent() {
+        return mConnectionEvent;
+    }
+
+    public @SecurityAlgorithm int getEncryption() {
+        return mEncryption;
+    }
+
+    public @SecurityAlgorithm int getIntegrity() {
+        return mIntegrity;
+    }
+
+    public boolean isUnprotectedEmergency() {
+        return mIsUnprotectedEmergency;
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(Parcel out, int flags) {
+        out.writeInt(mConnectionEvent);
+        out.writeInt(mEncryption);
+        out.writeInt(mIntegrity);
+        out.writeBoolean(mIsUnprotectedEmergency);
+    }
+
+    private void readFromParcel(@NonNull Parcel in) {
+        mConnectionEvent = in.readInt();
+        mEncryption = in.readInt();
+        mIntegrity = in.readInt();
+        mIsUnprotectedEmergency = in.readBoolean();
+    }
+
+    public static final Parcelable.Creator<SecurityAlgorithmUpdate> CREATOR =
+            new Parcelable.Creator<SecurityAlgorithmUpdate>() {
+                public SecurityAlgorithmUpdate createFromParcel(Parcel in) {
+                    return new SecurityAlgorithmUpdate(in);
+                }
+
+                public SecurityAlgorithmUpdate[] newArray(int size) {
+                    return new SecurityAlgorithmUpdate[size];
+                }
+            };
+
+    @Override
+    public String toString() {
+        return TAG + ":{ mConnectionEvent = " + mConnectionEvent + " mEncryption = " + mEncryption
+                + " mIntegrity = " + mIntegrity + " mIsUnprotectedEmergency = "
+                + mIsUnprotectedEmergency;
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) return true;
+        if (!(o instanceof SecurityAlgorithmUpdate)) return false;
+        SecurityAlgorithmUpdate that = (SecurityAlgorithmUpdate) o;
+        return mConnectionEvent == that.mConnectionEvent
+                && mEncryption == that.mEncryption
+                && mIntegrity == that.mIntegrity
+                && mIsUnprotectedEmergency == that.mIsUnprotectedEmergency;
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(mConnectionEvent, mEncryption, mIntegrity, mIsUnprotectedEmergency);
+    }
+
+    public static final int CONNECTION_EVENT_CS_SIGNALLING_GSM = 0;
+    public static final int CONNECTION_EVENT_PS_SIGNALLING_GPRS = 1;
+    public static final int CONNECTION_EVENT_CS_SIGNALLING_3G = 2;
+    public static final int CONNECTION_EVENT_PS_SIGNALLING_3G = 3;
+    public static final int CONNECTION_EVENT_NAS_SIGNALLING_LTE = 4;
+    public static final int CONNECTION_EVENT_AS_SIGNALLING_LTE = 5;
+    public static final int CONNECTION_EVENT_VOLTE_SIP = 6;
+    public static final int CONNECTION_EVENT_VOLTE_RTP = 7;
+    public static final int CONNECTION_EVENT_NAS_SIGNALLING_5G = 8;
+    public static final int CONNECTION_EVENT_AS_SIGNALLING_5G = 9;
+    public static final int CONNECTION_EVENT_VONR_SIP = 10;
+    public static final int CONNECTION_EVENT_VONR_RTP = 11;
+
+    /** @hide */
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef(prefix = {"CONNECTION_EVENT_"}, value = {CONNECTION_EVENT_CS_SIGNALLING_GSM,
+            CONNECTION_EVENT_PS_SIGNALLING_GPRS, CONNECTION_EVENT_CS_SIGNALLING_3G,
+            CONNECTION_EVENT_PS_SIGNALLING_3G, CONNECTION_EVENT_NAS_SIGNALLING_LTE,
+            CONNECTION_EVENT_AS_SIGNALLING_LTE, CONNECTION_EVENT_VOLTE_SIP,
+            CONNECTION_EVENT_VOLTE_RTP, CONNECTION_EVENT_NAS_SIGNALLING_5G,
+            CONNECTION_EVENT_AS_SIGNALLING_5G, CONNECTION_EVENT_VONR_SIP,
+            CONNECTION_EVENT_VONR_RTP})
+    public @interface ConnectionEvent {
+    }
+
+    public static final int SECURITY_ALGORITHM_A50 = 0;
+    public static final int SECURITY_ALGORITHM_A51 = 1;
+    public static final int SECURITY_ALGORITHM_A52 = 2;
+    public static final int SECURITY_ALGORITHM_A53 = 3;
+    public static final int SECURITY_ALGORITHM_A54 = 4;
+    public static final int SECURITY_ALGORITHM_GEA0 = 14;
+    public static final int SECURITY_ALGORITHM_GEA1 = 15;
+    public static final int SECURITY_ALGORITHM_GEA2 = 16;
+    public static final int SECURITY_ALGORITHM_GEA3 = 17;
+    public static final int SECURITY_ALGORITHM_GEA4 = 18;
+    public static final int SECURITY_ALGORITHM_GEA5 = 19;
+    public static final int SECURITY_ALGORITHM_UEA0 = 29;
+    public static final int SECURITY_ALGORITHM_UEA1 = 30;
+    public static final int SECURITY_ALGORITHM_UEA2 = 31;
+    public static final int SECURITY_ALGORITHM_EEA0 = 41;
+    public static final int SECURITY_ALGORITHM_EEA1 = 42;
+    public static final int SECURITY_ALGORITHM_EEA2 = 43;
+    public static final int SECURITY_ALGORITHM_EEA3 = 44;
+    public static final int SECURITY_ALGORITHM_NEA0 = 55;
+    public static final int SECURITY_ALGORITHM_NEA1 = 56;
+    public static final int SECURITY_ALGORITHM_NEA2 = 57;
+    public static final int SECURITY_ALGORITHM_NEA3 = 58;
+    public static final int SECURITY_ALGORITHM_SIP_NULL = 68;
+    public static final int SECURITY_ALGORITHM_AES_GCM = 69;
+    public static final int SECURITY_ALGORITHM_AES_GMAC = 70;
+    public static final int SECURITY_ALGORITHM_AES_CBC = 71;
+    public static final int SECURITY_ALGORITHM_DES_EDE3_CBC = 72;
+    public static final int SECURITY_ALGORITHM_AES_EDE3_CBC = 73;
+    public static final int SECURITY_ALGORITHM_HMAC_SHA1_96 = 74;
+    public static final int SECURITY_ALGORITHM_HMAC_SHA1_96_NULL = 75;
+    public static final int SECURITY_ALGORITHM_HMAC_MD5_96 = 76;
+    public static final int SECURITY_ALGORITHM_HMAC_MD5_96_NULL = 77;
+    public static final int SECURITY_ALGORITHM_SRTP_AES_COUNTER = 87;
+    public static final int SECURITY_ALGORITHM_SRTP_AES_F8 = 88;
+    public static final int SECURITY_ALGORITHM_SRTP_HMAC_SHA1 = 89;
+    public static final int SECURITY_ALGORITHM_ENCR_AES_GCM_16 = 99;
+    public static final int SECURITY_ALGORITHM_ENCR_AES_CBC = 100;
+    public static final int SECURITY_ALGORITHM_AUTH_HMAC_SHA2_256_128 = 101;
+    public static final int SECURITY_ALGORITHM_UNKNOWN = 113;
+    public static final int SECURITY_ALGORITHM_OTHER = 114;
+    public static final int SECURITY_ALGORITHM_ORYX = 124;
+
+    /** @hide */
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef(prefix = {"CONNECTION_EVENT_"}, value = {SECURITY_ALGORITHM_A50, SECURITY_ALGORITHM_A51,
+            SECURITY_ALGORITHM_A52, SECURITY_ALGORITHM_A53,
+            SECURITY_ALGORITHM_A54, SECURITY_ALGORITHM_GEA0, SECURITY_ALGORITHM_GEA1,
+            SECURITY_ALGORITHM_GEA2, SECURITY_ALGORITHM_GEA3, SECURITY_ALGORITHM_GEA4,
+            SECURITY_ALGORITHM_GEA5, SECURITY_ALGORITHM_UEA0, SECURITY_ALGORITHM_UEA1,
+            SECURITY_ALGORITHM_UEA2, SECURITY_ALGORITHM_EEA0, SECURITY_ALGORITHM_EEA1,
+            SECURITY_ALGORITHM_EEA2, SECURITY_ALGORITHM_EEA3, SECURITY_ALGORITHM_NEA0,
+            SECURITY_ALGORITHM_NEA1, SECURITY_ALGORITHM_NEA2, SECURITY_ALGORITHM_NEA3,
+            SECURITY_ALGORITHM_SIP_NULL, SECURITY_ALGORITHM_AES_GCM,
+            SECURITY_ALGORITHM_AES_GMAC, SECURITY_ALGORITHM_AES_CBC,
+            SECURITY_ALGORITHM_DES_EDE3_CBC, SECURITY_ALGORITHM_AES_EDE3_CBC,
+            SECURITY_ALGORITHM_HMAC_SHA1_96, SECURITY_ALGORITHM_HMAC_SHA1_96_NULL,
+            SECURITY_ALGORITHM_HMAC_MD5_96, SECURITY_ALGORITHM_HMAC_MD5_96_NULL,
+            SECURITY_ALGORITHM_SRTP_AES_COUNTER, SECURITY_ALGORITHM_SRTP_AES_F8,
+            SECURITY_ALGORITHM_SRTP_HMAC_SHA1, SECURITY_ALGORITHM_ENCR_AES_GCM_16,
+            SECURITY_ALGORITHM_ENCR_AES_CBC, SECURITY_ALGORITHM_AUTH_HMAC_SHA2_256_128,
+            SECURITY_ALGORITHM_UNKNOWN, SECURITY_ALGORITHM_OTHER, SECURITY_ALGORITHM_ORYX})
+    public @interface SecurityAlgorithm {
+    }
+
+}
diff --git a/telephony/java/android/telephony/SubscriptionInfo.java b/telephony/java/android/telephony/SubscriptionInfo.java
index 6ebf3be..a188581 100644
--- a/telephony/java/android/telephony/SubscriptionInfo.java
+++ b/telephony/java/android/telephony/SubscriptionInfo.java
@@ -54,6 +54,7 @@
 import java.util.Collections;
 import java.util.List;
 import java.util.Objects;
+import java.util.Set;
 
 /**
  * A Parcelable class for Subscription Information.
@@ -262,6 +263,11 @@
     private final boolean mIsOnlyNonTerrestrialNetwork;
 
     /**
+     * The service capabilities (in the form of bitmask combination) the subscription supports.
+     */
+    private final int mServiceCapabilities;
+
+    /**
      * @hide
      *
      * @deprecated Use {@link SubscriptionInfo.Builder}.
@@ -386,6 +392,7 @@
         this.mPortIndex = portIndex;
         this.mUsageSetting = usageSetting;
         this.mIsOnlyNonTerrestrialNetwork = false;
+        this.mServiceCapabilities = 0;
     }
 
     /**
@@ -425,6 +432,7 @@
         this.mPortIndex = builder.mPortIndex;
         this.mUsageSetting = builder.mUsageSetting;
         this.mIsOnlyNonTerrestrialNetwork = builder.mIsOnlyNonTerrestrialNetwork;
+        this.mServiceCapabilities = builder.mServiceCapabilities;
     }
 
     /**
@@ -882,6 +890,44 @@
         return mIsOnlyNonTerrestrialNetwork;
     }
 
+    // TODO(b/316183370): replace @code with @link in javadoc after feature is released
+    /**
+     * Retrieves the service capabilities for the current subscription.
+     *
+     * <p>These capabilities are hint to system components and applications, allowing them to
+     * enhance user experience. For instance, a Dialer application can inform the user that the
+     * current subscription is incapable of making voice calls if the voice service is not
+     * available.
+     *
+     * <p>Correct usage of these service capabilities must also consider the device's overall
+     * service capabilities. For example, even if the subscription supports voice calls, a voice
+     * call might not be feasible on a device that only supports data services. To determine the
+     * device's capabilities for voice and SMS services, refer to
+     * {@code TelephonyManager#isDeviceVoiceCapable()} and
+     * {@code TelephonyManager#isDeviceSmsCapable()}.
+     *
+     * <p>Emergency service availability may not directly correlate with the subscription or
+     * device's general service capabilities. In some cases, emergency calls might be possible
+     * even if the subscription or device does not typically support voice services.
+     *
+     * @return A set of integer representing the subscription's service capabilities,
+     * defined by {@code SubscriptionManager#SERVICE_CAPABILITY_VOICE},
+     * {@code SubscriptionManager#SERVICE_CAPABILITY_SMS}
+     * and {@code SubscriptionManager#SERVICE_CAPABILITY_DATA}.
+     *
+     * @see TelephonyManager#isDeviceVoiceCapable()
+     * @see TelephonyManager#isDeviceSmsCapable()
+     * @see CarrierConfigManager#KEY_CELLULAR_SERVICE_CAPABILITIES_INT_ARRAY
+     * @see SubscriptionManager#SERVICE_CAPABILITY_VOICE
+     * @see SubscriptionManager#SERVICE_CAPABILITY_SMS
+     * @see SubscriptionManager#SERVICE_CAPABILITY_DATA
+     */
+    @NonNull
+    @FlaggedApi(Flags.FLAG_DATA_ONLY_CELLULAR_SERVICE)
+    public @SubscriptionManager.ServiceCapability Set<Integer> getServiceCapabilities() {
+        return SubscriptionManager.getServiceCapabilitiesSet(mServiceCapabilities);
+    }
+
     @NonNull
     public static final Parcelable.Creator<SubscriptionInfo> CREATOR =
             new Parcelable.Creator<SubscriptionInfo>() {
@@ -919,6 +965,8 @@
                     .setUiccApplicationsEnabled(source.readBoolean())
                     .setUsageSetting(source.readInt())
                     .setOnlyNonTerrestrialNetwork(source.readBoolean())
+                    .setServiceCapabilities(
+                            SubscriptionManager.getServiceCapabilitiesSet(source.readInt()))
                     .build();
         }
 
@@ -961,6 +1009,7 @@
         dest.writeBoolean(mAreUiccApplicationsEnabled);
         dest.writeInt(mUsageSetting);
         dest.writeBoolean(mIsOnlyNonTerrestrialNetwork);
+        dest.writeInt(mServiceCapabilities);
     }
 
     @Override
@@ -1024,6 +1073,8 @@
                 + " areUiccApplicationsEnabled=" + mAreUiccApplicationsEnabled
                 + " usageSetting=" + SubscriptionManager.usageSettingToString(mUsageSetting)
                 + " isOnlyNonTerrestrialNetwork=" + mIsOnlyNonTerrestrialNetwork
+                + " serviceCapabilities=" + SubscriptionManager.getServiceCapabilitiesSet(
+                mServiceCapabilities).toString()
                 + "]";
     }
 
@@ -1049,7 +1100,8 @@
                 that.mNativeAccessRules) && Arrays.equals(mCarrierConfigAccessRules,
                 that.mCarrierConfigAccessRules) && Objects.equals(mGroupUuid, that.mGroupUuid)
                 && mCountryIso.equals(that.mCountryIso) && mGroupOwner.equals(that.mGroupOwner)
-                && mIsOnlyNonTerrestrialNetwork == that.mIsOnlyNonTerrestrialNetwork;
+                && mIsOnlyNonTerrestrialNetwork == that.mIsOnlyNonTerrestrialNetwork
+                && mServiceCapabilities == that.mServiceCapabilities;
     }
 
     @Override
@@ -1058,7 +1110,7 @@
                 mDisplayNameSource, mIconTint, mNumber, mDataRoaming, mMcc, mMnc, mIsEmbedded,
                 mCardString, mIsOpportunistic, mGroupUuid, mCountryIso, mCarrierId, mProfileClass,
                 mType, mGroupOwner, mAreUiccApplicationsEnabled, mPortIndex, mUsageSetting, mCardId,
-                mIsGroupDisabled, mIsOnlyNonTerrestrialNetwork);
+                mIsGroupDisabled, mIsOnlyNonTerrestrialNetwork, mServiceCapabilities);
         result = 31 * result + Arrays.hashCode(mEhplmns);
         result = 31 * result + Arrays.hashCode(mHplmns);
         result = 31 * result + Arrays.hashCode(mNativeAccessRules);
@@ -1263,6 +1315,11 @@
         private boolean mIsOnlyNonTerrestrialNetwork = false;
 
         /**
+         * Service capabilities bitmasks the subscription supports.
+         */
+        private int mServiceCapabilities = 0;
+
+        /**
          * Default constructor.
          */
         public Builder() {
@@ -1305,6 +1362,7 @@
             mPortIndex = info.mPortIndex;
             mUsageSetting = info.mUsageSetting;
             mIsOnlyNonTerrestrialNetwork = info.mIsOnlyNonTerrestrialNetwork;
+            mServiceCapabilities = info.mServiceCapabilities;
         }
 
         /**
@@ -1703,6 +1761,32 @@
         }
 
         /**
+         * Set the service capabilities that the subscription supports.
+         *
+         * @param capabilities Bitmask combination of SubscriptionManager
+         *                     .SERVICE_CAPABILITY_XXX.
+         * @return The builder.
+         *
+         * @throws IllegalArgumentException when any capability is not supported.
+         */
+        @NonNull
+        @FlaggedApi(Flags.FLAG_DATA_ONLY_CELLULAR_SERVICE)
+        public Builder setServiceCapabilities(
+                @NonNull @SubscriptionManager.ServiceCapability Set<Integer> capabilities) {
+            int combinedCapabilities = 0;
+            for (int capability : capabilities) {
+                if (capability < SubscriptionManager.SERVICE_CAPABILITY_VOICE
+                        || capability > SubscriptionManager.SERVICE_CAPABILITY_MAX) {
+                    throw new IllegalArgumentException(
+                            "Invalid service capability value: " + capability);
+                }
+                combinedCapabilities |= SubscriptionManager.serviceCapabilityToBitmask(capability);
+            }
+            mServiceCapabilities = combinedCapabilities;
+            return this;
+        }
+
+        /**
          * Build the {@link SubscriptionInfo}.
          *
          * @return The {@link SubscriptionInfo} instance.
diff --git a/telephony/java/android/telephony/SubscriptionManager.java b/telephony/java/android/telephony/SubscriptionManager.java
index 326b6f5..6c8663a 100644
--- a/telephony/java/android/telephony/SubscriptionManager.java
+++ b/telephony/java/android/telephony/SubscriptionManager.java
@@ -88,10 +88,12 @@
 import java.util.Arrays;
 import java.util.Collections;
 import java.util.HashMap;
+import java.util.HashSet;
 import java.util.List;
 import java.util.Locale;
 import java.util.Map;
 import java.util.Objects;
+import java.util.Set;
 import java.util.concurrent.Executor;
 import java.util.function.Consumer;
 import java.util.stream.Collectors;
@@ -1138,6 +1140,14 @@
      */
     public static final String IS_NTN = SimInfo.COLUMN_IS_NTN;
 
+    /**
+     * TelephonyProvider column name to identify service capabilities.
+     * Disabled by default.
+     * <P>Type: INTEGER (int)</P>
+     * @hide
+     */
+    public static final String SERVICE_CAPABILITIES = SimInfo.COLUMN_SERVICE_CAPABILITIES;
+
     /** @hide */
     @Retention(RetentionPolicy.SOURCE)
     @IntDef(prefix = {"USAGE_SETTING_"},
@@ -1347,6 +1357,86 @@
             })
     public @interface PhoneNumberSource {}
 
+    /** @hide */
+    @Retention(RetentionPolicy.SOURCE)
+    @IntDef(prefix = {"SERVICE_CAPABILITY"},
+            value = {
+                    SERVICE_CAPABILITY_VOICE,
+                    SERVICE_CAPABILITY_SMS,
+                    SERVICE_CAPABILITY_DATA,
+            })
+    public @interface ServiceCapability {
+    }
+
+    /**
+     * Represents a value indicating the voice calling capabilities of a subscription.
+     *
+     * <p>This value identifies whether the subscription supports various voice calling services.
+     * These services can include circuit-switched (CS) calling, packet-switched (PS) IMS (IP
+     * Multimedia Subsystem) calling, and over-the-top (OTT) calling options.
+     *
+     * <p>Note: The availability of emergency calling services is not solely dependent on this
+     * voice capability. Emergency services may be accessible even if the subscription lacks
+     * standard voice capabilities. However, the device's ability to support emergency calls
+     * can be influenced by its inherent voice capabilities, as determined by
+     * {@link TelephonyManager#isDeviceVoiceCapable()}.
+     *
+     * @see TelephonyManager#isDeviceVoiceCapable()
+     */
+    @FlaggedApi(Flags.FLAG_DATA_ONLY_CELLULAR_SERVICE)
+    public static final int SERVICE_CAPABILITY_VOICE = 1;
+
+    /**
+     * Represents a value indicating the SMS capabilities of a subscription.
+     *
+     * <p>This value identifies whether the subscription supports various sms services.
+     * These services can include circuit-switched (CS) SMS, packet-switched (PS) IMS (IP
+     * Multimedia Subsystem) SMS, and over-the-top (OTT) SMS options.
+     *
+     * <p>Note: The availability of emergency SMS services is not solely dependent on this
+     * sms capability. Emergency services may be accessible even if the subscription lacks
+     * standard sms capabilities. However, the device's ability to support emergency sms
+     * can be influenced by its inherent sms capabilities, as determined by
+     * {@link TelephonyManager#isDeviceSmsCapable()}.
+     *
+     * @see TelephonyManager#isDeviceSmsCapable()
+     */
+    @FlaggedApi(Flags.FLAG_DATA_ONLY_CELLULAR_SERVICE)
+    public static final int SERVICE_CAPABILITY_SMS = 2;
+
+    /**
+     * Represents a value indicating the data calling capabilities of a subscription.
+     */
+    @FlaggedApi(Flags.FLAG_DATA_ONLY_CELLULAR_SERVICE)
+    public static final int SERVICE_CAPABILITY_DATA = 3;
+
+    /**
+     * Maximum value of service capabilities supported so far.
+     * @hide
+     */
+    public static final int SERVICE_CAPABILITY_MAX = SERVICE_CAPABILITY_DATA;
+
+    /**
+     * Bitmask for {@code SERVICE_CAPABILITY_VOICE}.
+     * @hide
+     */
+    public static final int SERVICE_CAPABILITY_VOICE_BITMASK =
+            serviceCapabilityToBitmask(SERVICE_CAPABILITY_VOICE);
+
+    /**
+     * Bitmask for {@code SERVICE_CAPABILITY_SMS}.
+     * @hide
+     */
+    public static final int SERVICE_CAPABILITY_SMS_BITMASK =
+            serviceCapabilityToBitmask(SERVICE_CAPABILITY_SMS);
+
+    /**
+     * Bitmask for {@code SERVICE_CAPABILITY_DATA}.
+     * @hide
+     */
+    public static final int SERVICE_CAPABILITY_DATA_BITMASK =
+            serviceCapabilityToBitmask(SERVICE_CAPABILITY_DATA);
+
     private final Context mContext;
 
     /**
@@ -4484,4 +4574,38 @@
         }
         return new ArrayList<>();
     }
+
+    /**
+     * @return the bitmasks combination of all service capabilities.
+     * @hide
+     */
+    public static int getAllServiceCapabilityBitmasks() {
+        return SERVICE_CAPABILITY_VOICE_BITMASK | SERVICE_CAPABILITY_SMS_BITMASK
+                | SERVICE_CAPABILITY_DATA_BITMASK;
+    }
+
+    /**
+     * @return The set of service capability from a bitmask combined one.
+     * @hide
+     */
+    @NonNull
+    @ServiceCapability
+    public static Set<Integer> getServiceCapabilitiesSet(int combinedServiceCapabilities) {
+        Set<Integer> capabilities = new HashSet<>();
+        for (int i = SERVICE_CAPABILITY_VOICE; i <= SERVICE_CAPABILITY_MAX; i++) {
+            final int capabilityBitmask = serviceCapabilityToBitmask(i);
+            if ((combinedServiceCapabilities & capabilityBitmask) == capabilityBitmask) {
+                capabilities.add(i);
+            }
+        }
+        return Collections.unmodifiableSet(capabilities);
+    }
+
+    /**
+     * @return The service capability bitmask from a {@link ServiceCapability} value.
+     * @hide
+     */
+    public static int serviceCapabilityToBitmask(@ServiceCapability int capability) {
+        return 1 << (capability - 1);
+    }
 }
diff --git a/telephony/java/android/telephony/TelephonyManager.java b/telephony/java/android/telephony/TelephonyManager.java
index f8166e5..22a5cd7 100644
--- a/telephony/java/android/telephony/TelephonyManager.java
+++ b/telephony/java/android/telephony/TelephonyManager.java
@@ -6683,8 +6683,10 @@
      * PackageManager.FEATURE_TELEPHONY system feature, which is available
      * on any device with a telephony radio, even if the device is
      * data-only.
+     * @deprecated Replaced by {@link #isDeviceVoiceCapable()}
      */
     @RequiresFeature(PackageManager.FEATURE_TELEPHONY_CALLING)
+    @Deprecated
     public boolean isVoiceCapable() {
         if (mContext == null) return true;
         return mContext.getResources().getBoolean(
@@ -6692,6 +6694,30 @@
     }
 
     /**
+     * @return true if the current device is "voice capable".
+     * <p>
+     * "Voice capable" means that this device supports circuit-switched or IMS packet switched
+     * (i.e. voice) phone calls over the telephony network, and is allowed to display the in-call
+     * UI while a cellular voice call is active. This will be false on "data only" devices which
+     * can't make voice calls and don't support any in-call UI.
+     * <p>
+     * Note: the meaning of this flag is subtly different from the PackageManager
+     * .FEATURE_TELEPHONY system feature, which is available on any device with a telephony
+     * radio, even if the device is data-only.
+     * <p>
+     * To check if a subscription is "voice capable", call method
+     * {@link SubscriptionInfo#getServiceCapabilities()} and compare  the result with
+     * bitmask {@link SubscriptionManager#SERVICE_CAPABILITY_VOICE}.
+     *
+     * @see SubscriptionInfo#getServiceCapabilities()
+     */
+    @RequiresFeature(PackageManager.FEATURE_TELEPHONY_CALLING)
+    @FlaggedApi(Flags.FLAG_DATA_ONLY_CELLULAR_SERVICE)
+    public boolean isDeviceVoiceCapable() {
+        return isVoiceCapable();
+    }
+
+    /**
      * @return true if the current device supports sms service.
      * <p>
      * If true, this means that the device supports both sending and
@@ -6699,6 +6725,7 @@
      * <p>
      * Note: Voicemail waiting sms, cell broadcasting sms, and MMS are
      *       disabled when device doesn't support sms.
+     * @deprecated Replaced by {@link #isDeviceSmsCapable()}
      */
     @RequiresFeature(PackageManager.FEATURE_TELEPHONY_MESSAGING)
     public boolean isSmsCapable() {
@@ -6708,6 +6735,27 @@
     }
 
     /**
+     * @return true if the current device supports SMS service.
+     * <p>
+     * If true, this means that the device supports both sending and
+     * receiving SMS via the telephony network.
+     * <p>
+     * Note: Voicemail waiting SMS, cell broadcasting SMS, and MMS are
+     *       disabled when device doesn't support SMS.
+     * <p>
+     * To check if a subscription is "SMS capable", call method
+     * {@link SubscriptionInfo#getServiceCapabilities()} and compare result with
+     * bitmask {@link SubscriptionManager#SERVICE_CAPABILITY_SMS}.
+     *
+     * @see SubscriptionInfo#getServiceCapabilities()
+     */
+    @RequiresFeature(PackageManager.FEATURE_TELEPHONY_MESSAGING)
+    @FlaggedApi(Flags.FLAG_DATA_ONLY_CELLULAR_SERVICE)
+    public boolean isDeviceSmsCapable() {
+        return isSmsCapable();
+    }
+
+    /**
      * Requests all available cell information from all radios on the device including the
      * camped/registered, serving, and neighboring cells.
      *